main
14 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
73f5bd7c21 |
chore: cleanup items 31+32 — nested-function rule, layout, CI audit, ls facets
Closes the two remaining small items of Block E. Both were largely recon: much of what they filed had already been fixed, and the part that had not turned up a real GNU divergence. Item 31 (T3-7), rule/doc reconciliation --------------------------------------- The `object`-parameter half is already done: #825's mypy inversion took the package from the filed 28 down to 7, of which 3 are docstring prose and the remaining 4 are protocol methods (`__contains__`, `pop`) whose signatures typeshed dictates, all already listed in the no-object gate. `utils/errors.py` reads `str | PathSpec` today, and `builders/sed.py` no longer holds flag values at all -- item 22 moved that to the generic. That left the nested-function rule, which the plan asked to decide before gating. Measured: 39 nested defs, and **every one of them closes over its enclosing scope** -- 38 by free variable, and `sed_helper._repl` by binding through parameter defaults, the loop-variable idiom, with a comment saying so. A flat "do not nest" is a rule the architecture cannot keep (op factories, provision builders, the read-through cache, every decorator's wrapper), so gating it as written would have meant 39 standing violations. So the rule now says what it was reaching for: a nested def must capture the scope around it, or it belongs at module level. That is mechanical and enforceable -- `tests/test_nested_functions_are_closures.py` reads free variables from `symtable` and parameter defaults from the AST. It passes on the tree as-is and fires on a helper that reads only its own arguments. Layout: the eight test-only TS directories are flattened (`ram/{cat,cut,grep,head,ls,tail,wc}/`, `ssh/ls/`), `awk_helper.ts` moves up beside its nine siblings, and the `provision.ts` / `_provision.ts` split is settled on Python's convention -- `_provision` for a backend's own, bare `provision` for the shared ones (cli, generic_bind). That last one was not cosmetic: Python already had `_provision.py` for github, gmail, redis and email, so renaming **closed 4 real parity divergences** (layout baseline 296 -> 292). The `findEval`/`findParse` line was already done in #827. Item 32 (T3-8), test/CI tidiness -------------------------------- The two asymmetric `ls` conformance matrices are raised to symmetry, and they pass: python `[ram,disk,redis]` vs typescript `[ram]` was stale caution, not a divergence. Both runners now reject an asymmetric matrix at load time unless the case carries a `divergence` key explaining why -- a case is a parity claim, and narrowing one side reads as coverage while the side that still lists the backend goes green. Loading the corpus under the new assertion proves no other case was asymmetric. The README documented the override as "not yet needed"; it exists now. Adds the `ts-audit` job, mirroring `test_python.yml`'s exactly (same `continue-on-error`, same out-of-gate placement). Nothing ran `pnpm audit` for the TS tree while `typescript/package.json` carried 27 hand-written CVE overrides that only a person remembering to check kept current. Integ facets for `ls`: `-A`, `-d`, `-r`, `-S`, `-h`, all pinned against GNU coreutils 9.7 in docker. `-S` uses regular files only, because GNU sorts a directory by its inode size while mirage counts it as 0 -- a divergence CLAUDE.md documents deliberately. The `-h` facet found a real bug ------------------------------- `ls -h` disagreed with GNU three ways, and because the flag had zero cross-backend coverage nothing caught it. GNU prints a count below one unit with no suffix (`24`); mirage printed `24B`. GNU rounds *up* to the precision shown (1025 bytes is `1.1K`); mirage gave `1.0K`. GNU drops the decimal once the value reaches ten (`10K`); mirage gave `10.0K`. One shared engine replaces both formatters in both languages -- GNU runs `-h` and `-H` through one `human_readable`, and so do we now. Rounding up can carry past the base (1048575 ceils to 1024K, which GNU shows as `1.0M`), so the unit is re-chosen after rounding rather than once up front. 21 GNU-read points are pinned as a table in each language. Blast radius, all re-pinned: five integ cases that had recorded the `B` suffix (du, discord, langfuse, email, gmail), and the du fan-out tests. Those last ones needed care rather than a new number: they exist to prove the total is humanized once instead of twice, and their 1500+1500 stops discriminating under correct rounding, since 3000 bytes and two `1.5K` readings both render `3.0K`. They now use 1025+1025, where single-rounding gives `2.1K` and double-rounding would give `2.2K`. Verified: pre-commit clean, integ 8054/0 on ram+disk+redis in both languages, conformance green in both, and the layout, spec, barrel, docs, case-target and PathSpec gates all pass. Not covered, still open on item 32: integ facets for `ls -t` (mtime order is not stable across backends without a seeded fixture), mktemp, gzip, the checksum `--check` companions, `df -H/-k/-a`, and `cmp -n/-b/-i`; and the T2-7 helper unit-suite mirroring. Item 31 leaves the provision *presence* sets unreconciled (Python has lancedb/qdrant, TS has trello) -- that is a question about which backends should provision at all, not about naming. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
099aa4b35e |
fix(readdir): ENOENT vs ENOTDIR for a missing path (py+ts, 6 backends) (#637)
* fix(readdir): ENOENT vs ENOTDIR for a missing path (py+ts, 6 backends)
`ls` on a missing path reported the wrong errno string on several
backends, and Python/TypeScript disagreed on the same backend.
GNU pin (`docker run --rm debian:stable-slim`):
ls /nope -> No such file or directory
ls /data/nope -> No such file or directory
ls /data/nope/deeper -> No such file or directory (however deep)
ls /data/a.txt/x -> Not a directory (component is a file)
ls /data/a.txt/ -> Not a directory
The rule: ENOTDIR only when a path component exists and is not a
directory; a component that does not exist is ENOENT, at any depth.
Every affected site was wrong, in both directions:
| backend | before |
|----------|--------------------------------------------|
| py disk | always ENOTDIR (+ leaked the real fs path) |
| py ram | always ENOENT (missed genuine ENOTDIR) |
| py redis | always ENOENT |
| ts ram | always ENOTDIR |
| ts disk | mapped ENOENT -> enotdir explicitly |
| ts opfs | mapped NotFoundError -> enotdir |
Store-backed backends (ram, redis) have no kernel to draw the line, so
they now ask a new shared helper — `readdir_error` / `readdirError` —
which walks the ancestors and returns ENOTDIR at the first existing
non-directory component, else ENOENT. Kernel/API-backed backends already
had the right signal and just needed to stop squashing it: disk lets
listdir raise and restamps onto the PathSpec (which also fixes the
real-fs-path leak in the Python error), opfs keeps NotFoundError->ENOENT
distinct from TypeMismatchError->ENOTDIR.
Blast radius is stderr bytes only: no caller distinguishes the two codes
after a readdir (glob_walk, condition operators, chroma/dify walk,
find_action_dispatch, wasm/fs, monty all catch them together). Three
tests pinned the old behavior and were corrected.
Coverage: 6 cross-backend cases in integ/unix/nf/ls.json (ram, disk,
redis, opfs — `nf/` had cat/grep/head/stat/tail/wc but no ls, which is
the gap this lived in), 5 in conformance/cases/ls.json, plus helper and
per-backend unit tests on both sides. The new cases pin exit 1, matching
this tree's `ls` exit and the existing convention in
integ/resources/slack/ls.json; they will need the same bump as the other
ls cases when the 0/1/2 exit-code split lands.
Verified: pre-commit clean; python + TS integ 1886/0 (ram) and 1874/0
(disk) on both hosts; full TS suites and targeted Python suites pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(readdir): stop the errno walk at the first missing component
readdir_error / readdirError scanned every ancestor for a file and
reported ENOTDIR if it found one at any depth. A flat store can hold a
key whose parent is not a directory (RAM/Redis rename writes the
destination key without adding its ancestors), so `ls /missing/a.txt/x`
found the orphan `/missing/a.txt` and said "Not a directory" while
`ls /missing` said "No such file or directory" for the same tree.
Both helpers now take an is_dir / isDir probe and stop where the kernel
stops resolving: the first component that is neither a directory nor a
file is ENOENT. A file component is still ENOTDIR.
* fix(tree/grep/rg): keep walking when readdir reports ENOTDIR
Splitting the readdir errno into ENOENT/ENOTDIR left the python tree
and grep/rg walkers behind: they caught only
`(FileNotFoundError, ValueError)`, and NotADirectoryError is a sibling
of FileNotFoundError under OSError, not a subclass. So on RAM/Redis:
- `tree /a.txt/x` escaped the walk and returned exit 1 with no body,
where `tree /nope` still printed GNU's `[error opening dir]` + exit 2
- `grep -rl foo /a.txt/x /real` aborted and dropped /real's results,
where the same line with `/nope` warned and kept them
- `rg -l foo /a.txt/x /real` did the same
GNU pins (debian:stable-slim): `tree /d/a.txt/x` prints the same body
and exits 2 as `tree /d/nope`; `grep -rl foo /d/a.txt/x /d/real` warns
and still prints `/d/real/b.txt`.
Both cases now route through one named `WALK_ERRORS` tuple (FS_ERRORS
plus the ValueError store backends raise for "not a directory"), so a
future errno split cannot desync these catch sites again. TypeScript
already used a broad catch and was unaffected; python now matches it.
---------
Co-authored-by: bytecii <bytecii@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Zecheng Zhang <zechengzhang97@gmail.com>
|
||
|
|
4807b23630 |
fix(ls): GNU per-directory headers for multi-operand listings (py+ts) (#638)
`ls a b` flattened every operand's entries into one unseparated list instead of printing GNU's `a:` / `b:` blocks. Pinned the real rule with docker (debian:stable-slim, coreutils 9.7): headers are keyed on the *operand count*, not on how many directories were listed — ls a -> bare listing, no header ls a b -> `a:` block, blank line, `b:` block ls /nope a -> `a:` (2 operands, one of them failed) ls -R a -> `a:` (recursive always heads) and non-directory operands print first as one unheaded block, with all operands sorted together before that split. Both the non-recursive and `-R` paths now share one `probe_operand` / `probeOperand` that lists an operand and reports whether it turned out to be a directory, so unifying them fixed three more divergences found alongside the reported one: - operands were listed in command-line order, interleaved: `ls b zfile a mfile` printed `g.txt /zfile f.txt sub /mfile` - `-d` did not sort its operands (`ls -d b a` -> `b a`) - `ls -R zfile a` emitted a bogus `zfile:` header with `zfile` as its own entry `ls -R` on a directory whose readdir fails no longer prints an empty header, and the TS path now type-checks a failed-readdir operand before treating it as a file row (Python already did). The exit-code rule is unchanged in meaning but no longer keyed on stdout being empty: it asks whether any operand listed, so a header line cannot flip the code. That keeps this independent of the in-flight ls exit 0/1/2 work. Verified: 41 Python + 16 TS unit cases with byte-exact stdout, and both integ hosts at 1882 passed / 0 failed on `--target ram` (all 7 pinned ls surfaces unchanged, including the 24-operand `ls /data/*.txt` glob). Cross-mount `ls` (operands on different mounts) still prints flat — the FANOUT strategy runs one native ls per operand, so each run correctly sees a single operand. Closing that needs operand count threaded through the executor and still cannot produce GNU's global operand sort, so it is left out here. Co-authored-by: bytecii <bytecii@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Zecheng Zhang <zechengzhang97@gmail.com> |
||
|
|
7d1c518184 |
fix(ls): GNU exit-code seriousness split (0/1/2) in py+ts (#636)
Pre-commit / pre-commit (push) Has been cancelled
CLI exit codes / changes (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / changes (push) Has been cancelled
Test (Install) / python-install (push) Has been cancelled
Test (Install) / ts-install (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / changes (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-shared-py (push) Has been cancelled
Integ / integ-shared-ts (push) Has been cancelled
Integ / integ-shared-parity (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Integ / integ-fuse-windows (push) Has been cancelled
Integ / runtime-py (push) Has been cancelled
Integ / runtime-ts (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / changes (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Python) / runtime (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / changes (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
* fix(ls): GNU exit-code seriousness split (0/1/2) in py+ts `ls` returned 1 for an inaccessible command-line operand where GNU coreutils returns 2, and — worse — masked the failure entirely when any other operand succeeded: `ls /data/nope /data` exited 0. The rule was `1 if warnings and not results else 0`, i.e. nonzero only when *every* operand failed. GNU instead ratchets a status upward (`set_exit_status`): 0 ok, 1 minor problem (trouble met below an operand), 2 serious trouble (a command-line operand could not be accessed); serious always wins, minor only upgrades a clean run. Pinned against GNU coreutils 9.7 (debian:stable-slim), including the non-root cases that reach the minor-problem path: ls /nope -> 2 ls /nope /ok (either order) -> 2, still lists /ok unreadable dir as operand -> 2 ls -R, unreadable subdirectory -> 1, keeps parent output ls -l, unstattable entries -> 1, lists the siblings minor + serious together -> 2 Warnings now carry a `serious` flag and are collapsed by `exit_status_for` / `exitStatusFor`. Seriousness comes from a `command_line_arg` / `commandLineArg` parameter threaded through `walk`/`walk_grouped` (`walkGrouped`), false on recursion; a per-entry stat failure inside a listed directory is always minor. Three fixes were load-bearing for the split: * Both executors gated `ls` child-mount and symlink injection on exit code == 0. A minor problem still lists the directory, so exit 1 would have silently dropped the injected rows; the gate is now `!= LS_FAILURE`. (Injection already bails for multi-operand and for -R/-d, and usage errors return early via UsageError, so the affected surface is a single-operand listing with a minor warning.) * Python's minor path was unreachable: `walk` caught only FileNotFoundError/ValueError/NotADirectoryError, so a PermissionError escaped the command and discarded stdout. Now `(OSError, ValueError)`, matching TypeScript. * TypeScript's `listDir` used `Promise.all`, so one failing child stat killed the whole listing and surfaced as an operand failure — which would have exited 2 where Python exits 1. Now `Promise.allSettled` with per-entry warnings, matching Python's tolerance. Also fixes a Python-only bug where `walk_grouped` appended a group unconditionally, so `ls -R /nope` printed a bogus `/nope:` header on stdout (TypeScript was already correct). Pinned surfaces: integ/resources/slack/ls.json (slack_ls_archived_missing) 1 -> 2, and five hard-pinned unit tests. conformance/cases/ls.json had no error coverage, so two cases were added; they assert the status via `2>/dev/null; echo rc=$?` because the stderr text itself diverges for an unrelated pre-existing reason (Python+disk and TypeScript+RAM report "Not a directory" where GNU and Python+RAM say "No such file or directory") — tracked separately. Verified: 26 Python + 14 TypeScript unit tests; integ 1611 passed / 0 failed on both hosts; conformance green on both; both hosts byte-identical across all 12 pinned scenarios. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ls): mem0 golden, -R separator, review nits - integ/resources/mem0/basic.json: mem0_readdir_of_file_enotdir asserted exit 1 for an inaccessible command-line operand; GNU returns 2. This was the second nonzero-ls golden in the repo and the only CI failure. - The -R blank-line separator was keyed on the operand index, so a first operand that failed (rendering no group) still made the next one emit a leading blank. It now keys on what was actually emitted, matching GNU and the TypeScript path. - Test denial stubs moved to module scope (no nested functions) and the new docstrings carry Args types. --------- Co-authored-by: bytecii <bytecii@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Zecheng Zhang <zechengzhang97@gmail.com> |
||
|
|
90a20bd169 |
fix: stop masking backend failures as missing paths, widen TS conformance (#633)
Three fixes from an audit of the TypeScript side.
1. WorkspaceFS.exists/isDir/isFile caught every exception and returned
false, so an auth failure, timeout, or backend bug read back as "this
path does not exist". Python already had the right answer: it swallows
only (FileNotFoundError, ValueError), where ValueError is the registry's
"no mount matches path". Added noMount() plus an isMissingPath()
predicate and applied it to the three probes, to pathExists/isDirectory
in the copy helpers, and to the same pattern in realpath and df, both of
which had already diverged from their narrow Python twins.
2. LanceDB blob decoding called the Node global Buffer inside the
runtime-agnostic core package, which breaks in browsers. It now uses the
existing decodeBase64 helper, matching its qdrant sibling. The two gmail
base64url helpers moved onto the shared helpers as well.
3. TypeScript conformance only ran against RAM while Python ran RAM, disk
and Redis. The runner now covers all three with fresh backend state per
case, and all 34 cases pass on every backend. Teardown is split so
clearing Redis happens before Workspace.close() destroys the client,
rather than silently opening a second connection per case.
Also wired integ/runners/parity.py into CI as integ-shared-parity and added
it to the gate; it existed but nothing ran it. Locally it compares 5386
case/target pairs with no mismatches. Added conformance/** to the python and
typescript workflow path filters, which did not trigger those suites before.
The test stubs that faked backend errors as new Error("not found") or
new Error("ENOENT"), with the code in the message instead of on the error,
now use enoent() like real backends do.
|
||
|
|
426090484a |
feat(stat): full GNU stat -c directive set + agent-aware ownership (#609 Tier 1) (#626)
Pre-commit / pre-commit (push) Has been cancelled
CLI exit codes / changes (push) Has been cancelled
Test (Install) / changes (push) Has been cancelled
Integ / changes (push) Has been cancelled
Test (Python) / changes (push) Has been cancelled
Test (TypeScript) / changes (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / python-install (push) Has been cancelled
Test (Install) / ts-install (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-shared-py (push) Has been cancelled
Integ / integ-shared-ts (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Integ / integ-fuse-windows (push) Has been cancelled
Integ / runtime-py (push) Has been cancelled
Integ / runtime-ts (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Python) / runtime (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
|
||
|
|
a584eef138 |
feat(sort): full GNU -k KEYDEF grammar (py+ts) (#607)
Replace single-field `-k N` with the complete GNU sort key spec, mirrored across Python and TypeScript: - Field ranges and character offsets: -k F1[.C1][opts][,F2[.C2][opts]] - Field-N-to-EOL default (`-k2` spans field 2 to end of line, not just field 2 -- the largest prior silent divergence) - Multiple -k keys applied in order (spec `-k` is now repeatable) - Per-key modifiers n/g/h/V/M/f/r/b; any per-key modifier (incl. -b) makes a key ignore all global ordering options (GNU key_init rule), while globals still drive the last-resort whole-line compare - Last-resort whole-line comparison, disabled by -s - GNU field model: leading blanks belong to the following field; -b skips them; -t is a clean split - Invalid field 0 -> exit 2 with GNU-matching stderr Pinned against GNU coreutils 9.7. New cross-language conformance/cases/sort.json validates py/ts parity. Co-authored-by: bytecii <bytecii@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
0a431ca5f4 |
test(integ): PR5+PR6 unix/crossmount flag coverage + GNU shell fixes (mv/rm/mktemp/split/seq/join/tree/readlink) (#589)
* test(integ): PR5 unix write-family + long-tail + archive flag coverage Adds 70 GNU-pinned integ cases (seq 601000-601351) for previously untested unix flags, plus the product fixes the coverage surfaced (Python + TypeScript, with unit tests): - mv -v: emit GNU "renamed 'src' -> 'dst'" (was cp's arrow form) - rm -rv/-dv: depth-first per-entry verbose + "removed directory" for dirs; rm -d on non-empty now GNU "cannot remove ...: Directory not empty" (shared utils/verbose helper; generic + s3 + gridfs; gridfs now collects errors instead of aborting) - mktemp /path/tmpl.XXXX: honor an explicit path template's own directory (was prepending /tmp) - split -l: TS read the wrong flag key (AMBIGUOUS_NAMES renames -l to args_l) so it never segmented by lines - seq -w: was a value option that ate the start operand; now boolean, with width = widest formatted value (handles negatives) Cases: cp/mv/ln/rm/tee/mkdir/touch/mktemp(descoped)/split/csplit (A), cat/sort/diff/cmp/jq/uniq/nl/cut/join/comm/tail/base64/seq/sha256sum/ realpath/expand/unexpand/date (B), gzip/gunzip/zip/unzip/expr/shuf (C). Green on py ram/disk + ts ram (1520/1520); PR5-A also py s3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(shell): join -o/-e field mapping + jq -s coverage (py & ts) join -o FILENUM.FIELD indexed the key-stripped rest, so `1.2` pointed at field 3 and paired `-o` output came out empty; -e was also ignored in the -o path. Map against the full 1-based field list (key included) and fill missing fields with the -e value. Mirrored py & ts; native/unit tests updated to GNU-correct output. jq -s already slurps jsonl/stdin correctly; added integ coverage. 7 new integ cases (join -o/-e/-v/-1-2, jq -s); green py ram/disk + ts ram. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(shell): GNU-format tree output + integ coverage (py & ts) tree emitted unicode box-drawing, no root line, no summary, and -L was off by one. Rewrite to match GNU (C-locale ASCII): - root path as the first line - ASCII branches (|--, `--, | ) matching the docker oracle - blank line + "N directories, M files" summary (singular when 1; the file count is omitted under -d); the root counts as a directory once it has a listed entry (an empty root reports 0) - -L N shows N levels (was N+1) - a missing root prints "<path> [error opening dir]" and exits 2 Mirrored py & ts; updated the unicode/format unit tests across generic, ram, disk, databricks; refreshed the tree.json / nl.json cases and added 5 GNU-pinned integ cases (basic, -L, -d, -a, -I). Green py ram/disk + ts ram (1530/1530). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(shell): readlink -f/-e/-m canonicalize symlinks (py & ts) readlink -f/-e/-m fell through to the mount command, which only normpath'd the operand and never resolved the link. Route every readlink through the namespace handler and, for -f/-e/-m, resolve the full symlink chain (namespace.follow) and normalize — GNU realpath- style — so `readlink -f link` yields the canonical target (chains and relative targets included). Plain readlink still prints the raw target. Mirrored py & ts; 6 GNU-pinned integ cases (readlink.json: -f, chain, relative, -e, -m, -f -n). Green py ram/disk + ts ram (1536/1536). Documented divergence: -f/-e do not yet fail on a missing intermediate component (existence checks need async in the sync handler). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(integ): PR6 crossmount redirect/archive/read-routing cases 6 GNU-pinned crossmount cases spanning /data + /data2: redirect > and >> across mounts, gzip-then-cp-then-zcat archive across mounts, and jq/tree/xxd routing to the second mount. Green py ram/disk + ts ram (1542/1542). Documented divergences (mirage rejects cross-mount, GNU allows; not added as cases): split/csplit output prefix on the other mount and tar across mounts ("paths span multiple mounts ... not supported"); find | xargs -I ( -I unsupported by design). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: update backend tree tests for GNU-format output The tree rewrite (ee617c6d) adds a root line + summary, so the discord and slack backend tree tests (exact line count/position) and two generic-test docstrings needed refreshing to the ASCII format. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * style: yapf-format native/test_join.py Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(integ): regenerate s3/onedrive/dify truth for GNU tree format The tree rewrite (ee617c6d) changed the legacy per-backend scripts' tree output; regenerate the truth fixtures I can run locally (s3 via moto, onedrive in-process, dify). Only the tree blocks changed (root line + ASCII branches + summary). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(integ): deprecate legacy tree cases; JSON harness owns tree The GNU tree rewrite (ee617c6d) changed the tree output pinned by the legacy per-backend scripts. Rather than maintain those brittle golden fixtures, drop the redundant `tree` case from the legacy scripts — the declarative JSON harness now owns tree coverage (integ/unix/tree.json: basic + -L/-d/-a/-I/-P, plus crossmount routing). - Removed the tree/tree_table case from chroma, qdrant, lancedb, dify, notion, s3, onedrive (py + ts where present) and the matching `=== *tree* ===` sections from their truth files (s3/onedrive/dify regenerated; chroma/qdrant/lancedb/notion/s3_ts section-pruned). - Kept each script's unique backend coverage (e.g. s3.py's gcs/minio/seaweedfs endpoints + columnar rendering) — only the generic, now-duplicated tree case is dropped. - Added tree -P to integ/unix/tree.json to complete the flag set. Green py ram/disk + ts ram (1543/1543). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: resolve CI failures from the tree rewrite + seq spec drift Full-matrix CI surfaced the tree rewrite's remaining golden/behavior impact: - rm -rv on dropbox/box/gdrive printed `removed directory '.../sub/'` (object stores return dir paths with a trailing slash) — strip it in removal_lines (py & ts); GNU never prints a trailing slash. - Regenerate the seq -w spec snapshots (spec/{python,typescript}) — value_kind text -> none (the "Spec drift" gate). - Update the tree goldens the rewrite touched: conformance tree_subdir, slack_tree_day, and the integ/truth redis replay line, to GNU format. - redis/test_tree.py -L assertion + native_join.test.ts -o assertion (mirror the fixes already applied to ram/disk / the python native join test). - Curate hf/hf-prefix out of the empty-dir tree cases (object stores don't persist empty directories) and ssh out of cp -R tree cases (recursive-copy limitation). Green py ram/disk + ts ram (1543); py/ts unit + conformance green locally (redis/service targets verified in CI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(opfs): rm -v/-dv verbose + -d empty-check, mkdir -v (browser) The opfs (browser) rm/mkdir had bespoke implementations that ignored -v entirely and skipped rm -d's empty-check (giving `rm: Directory not empty` without the operand). Mirror the fix already applied to the generic/s3/gridfs rm: per-entry verbose via cpWalk + removalLines, `removed directory` for dirs, GNU `cannot remove '<path>': Directory not empty`, and mkdir -v. Green on ts --target opfs (1552/1552). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: bytecii <bytecii@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
823da1bb1c |
fix(find): GNU coreutils divergences and parser hardening (#312)
- emit the start path at depth 0; implement -empty - real expression grammar parser (-not/-o/-a, parentheses) replacing the dead _extract_not_name path; shared predicate-tree evaluator - unknown predicates and invalid -size/-maxdepth/-mtime args exit 1 - validate -type letters; bound parser recursion depth - migrate onedrive find to the generic; remove dead find_predicate_error and _extract_* helpers - fan-out mount synthesis and child-mount -maxdepth honor the parsed expression tree - chroma/dify: derive entry kind so -type works under the tree (was hardcoded to file) - emit the start path for an empty directory across the object/API backends (s3 marker, nextcloud/hf scan base, onedrive stat); ssh/ nextcloud/hf compute is_empty for -empty; fix nextcloud/hf metadata shadowing in the size/mtime path - align integ truth for the now-correct -name OR and -not output - mirror all changes across Python and TypeScript Directory handling under -size is tracked separately in #318. |
||
|
|
30677c1555 | Merge origin/main; keep bare-octal cmp -l; conformance stat %n expects operand path | ||
|
|
73aa275361 | style(conformance): format backend guide | ||
|
|
1d5ae3d69d | feat(conformance): add readme | ||
|
|
29db1d0eff | fix(commands): terminate cmp and wc stdin output | ||
|
|
1ed17a66b4 |
Add cross-language command conformance spec and runners (#151)
One declarative JSON spec (conformance/) describing command behavior as seeds plus cases with byte-exact expected stdout, stderr, and exit code. Two thin runners consume the same files: pytest against ram/disk/redis and vitest against ram. Each case declares an explicit backend matrix; a listed backend that lacks the command fails rather than skips, and a case matching no backend is a load-time error. |