73f5bd7c211d4a356d8d6ce3afec6b77521f7169
5 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> |
||
|
|
d9ba2d2460 |
fix(himalaya): file the sender's own copy of a sent message (#761)
* fix(himalaya): file the sender's own copy of a sent message `--send` opened one SMTP conversation and stopped, so mail was delivered but the sender's Sent mailbox stayed empty. A Sent folder is not produced by sending: SMTP keeps no record of itself, and the copy is a second, separate IMAP APPEND that every mail client makes on its own. Four call sites, not one: `message send` plus the route compose, reply and forward share. Upstream v2.0.0 spells that APPEND as `--save <MAILBOX>` on all four verbs, having replaced 1.x's automatic `message.send.save-copy` config. Both are here, because they answer different needs: - `--save <MAILBOX>` is v2 parity. On its own, with no `--send`, it files the message without sending it, which is how a draft is written. - `save_copy` is an account-level default, on, and is mirage's own divergence: an agent that never learned the flag still leaves the record a human sender would. `sent_folder` pins the mailbox. The mailbox is asked for rather than guessed. A server implementing RFC 6154 tags one mailbox \Sent in its LIST reply, which is `[Gmail]/Sent Mail` on Gmail and `Sent Items` on Exchange; failing that the configured name, failing that `Sent`. This is ahead of upstream, whose v2 wizard notes IMAP pins the reserved INBOX alone while it waits on LIST RETURN (SPECIAL-USE) in io-imap. Failure splits on whether a send already happened. A copy that fails after a successful send is a warning on stderr with exit 0, because the message is already gone and a non-zero exit invites a retry that sends it twice. A `--save` that sends nothing fails loudly, because nothing happened yet and retrying is safe. Upstream propagates in both cases. Two bugs found on the way: - aioimaplib joins command arguments with spaces verbatim and quotes nothing, so an unquoted mailbox holding a space arrives as two arguments and the server reads only the first word. That breaks on exactly the two providers above. `quote_mailbox` now covers the APPEND and the pre-existing `select_folder`, which had the same latent bug. imapflow needs none of it, building commands from typed attributes. - The TypeScript test CONFIG was an untyped literal, so a new required field did not fail tsc and those tests passed vacuously through the `!saveCopy` early return. It is annotated now, and a global beforeEach stubs the accessor so no --send test reaches for a real connection. GreenMail hands a new account nothing but an INBOX, where a real provider ships a sent mailbox already made, so both integ seeders create one. Verified against a live GreenMail on both hosts: the email and cli-himalaya targets pass 86 and 74 cases including six new ones, and compose, reply, forward and send each land in Sent with \Seen. Python suite and TypeScript node suite green, pre-commit clean. * fix(email): parse a LIST mailbox as an astring, not as a quoted string Codex review on #761. A LIST mailbox is an astring, so a name needing no quoting may legally arrive bare, and some servers emit it that way. Splitting the line on quotes then reads the hierarchy delimiter as the name: `(\HasNoChildren \Sent) "/" Sent` answered `/`. That splitter predates this branch and already mis-listed every folder on such a server, but this branch is what made it consequential: the name now feeds an APPEND, so a sent copy would have been filed into a mailbox called `/`. The three tokens are walked in order instead. The atom form and the NIL delimiter both parse, and a quoted name is now unescaped rather than cut at the first inner quote, which the splitter also got wrong. Verified against a live GreenMail on both hosts, whose own wire format is the empty-attribute `() "." "INBOX"` shape. |
||
|
|
db807f304b |
fix(find): classify walked entries through stat, drop the is_dir_name heuristics (#719)
* fix(find): classify walked entries through stat, drop the is_dir_name heuristics * fix(test): satisfy noUncheckedIndexedAccess in the gmail find fixture * fix(dropbox): walk the native find over a scratch index * test(integ): pin find -type f over attachments and uploads * test(integ): find -type f matches a pdf upload too * ci: retrigger after the Actions outage * fix(email): route the TS find through the generic walk so every flag applies * chore(deps): bump h2 to 4.4.1 for GHSA-6hr6-w5qg-qmwg |
||
|
|
619be25d32 |
feat(himalaya): align the CLI with the upstream pimalaya grammar (#709)
* feat(himalaya): align the CLI with the upstream pimalaya grammar
- mailbox is -m/--mailbox, message id is a positional operand
- envelope search with upstream's query DSL, replacing list filter flags
- message compose writes RFC 5322 to stdout unless --send; message send
takes raw MIME, so the two chain through a pipe
- upstream aliases: ls, sr, write, new, fwd
core/email/send.* is deleted: the email mount is read-only, so the
composer and the SMTP transport moved into the himalaya package.
Fixes three node-side bugs found while testing:
- sendMail({raw}) needs an explicit envelope; Bcc is stripped by hand
- parseSearchCriteria silently dropped unknown keys, so NOT SEEN matched
every message
- parseImapDate rolled a bad month or day into a wrong date
* fix(himalaya): satisfy the CI eslint rules and stop the yapf/isort import flip
* fix(himalaya): search the Date header and bound envelope pages
Date conditions emit SENTON/SENTBEFORE/SENTSINCE rather than
ON/BEFORE/SINCE, which match the mailbox internal date, and the missing
upstream `before` condition is now parsed.
Envelope listing asked IMAP for every matching uid and fetched headers
for all of them before slicing one page. It now fetches the newest
page * page_size, capped by the account's max_messages.
* test(himalaya): cover every flag in integ, and fix what it found
Grows the himalaya integ suite from 38 to 64 cases: every search
condition, sorter, flag and alias, the composer flags, both quoting
options, stdin bodies, the send operand form, unknown flags and a
missing mailbox.
Three bugs it turned up, all in the email client:
- Five IMAP selects ignored their response, so a missing mailbox left
the session in AUTH and the next command complained about that
instead ('command SEARCH illegal in state AUTH' in python, imapflow's
bare 'Command failed' in typescript). Both now say no such mailbox.
- A refused SEARCH returned an empty uid list, so criteria the server
cannot answer read as 'matched nothing'. It now raises.
- Found because a mutation emitting a malformed date stayed green.
|
||
|
|
5ba62a8f0d |
refactor(integ): nest JSON test cases as category/name/facet (#602)
Restructure the JSON-runner integ corpus from a flat `category/command.json` layout into a three-level `category/name/facet.json` tree, so cases are navigable by the flag or feature they exercise (e.g. `unix/grep/n.json`, `unix/find/name.json`, `unix/meta/chmod.json`, `resources/dify/search.json`). Faceting rules: - unix command files split by the primary flag on the named command, with reserved `basic` / `error` / `operand` buckets, and populate the previously-dormant `flags` field on those cases (370 cases). - feature files plus bash/crossmount/resources split by the case-id's distinguishing segment (`meta_chmod_*` -> chmod, `col_parquet_*` -> parquet). - 9 high-cardinality flat files (provision, printf, gmail, ...) collapse to a single `basic.json` instead of exploding into 40+ singletons. - uppercase-only flags get a `.upper` suffix so filenames stay unique on case-insensitive filesystems (`grep -C` -> `C.upper.json`). Discovery now walks recursively: `rglob` in the Python harness and the existing `walkFiles` helper in the TypeScript harness. 177 flat files -> 701 facet files, 1898 cases. The migration preserves each case's `seq`, so the global execution order and every case body are byte-identical to before (only the additive `flags` field differs); verified 0 content mismatches vs the flat tree, unchanged id order, and the Python runner green (1582/1582 on ram). Adds runners/tools/refile_facets.py, the deterministic, re-runnable migration tool (with a case-collision guard). Co-authored-by: bytecii <bytecii@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |