v2.2.0 - the rest of the residue #90
No reviewers
Labels
No labels
already-shipped
bug
documentation
duplicate
enhancement
external-review
good first issue
help wanted
in progress
invalid
needs-decision
proposal
question
security
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: crenshawdev/cadence-archived#90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/rung-effort-check"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The six requirements v2.1.0 left unpicked, delivered: the config read face merges a layer once whatever its spelling (CFG-02), the git guard's parser is deleted for one small anchored reader (TOK-02), the release seam refuses downgrades and promotes Unreleased into the dated heading (REL-03), every shipped rung-ladder claim is true or gone (DOC-01), per-role effort is configurable and update-surviving (RNG-02), and the documented Forgejo install path is proven live from a fully cold state with committed transcripts (HST-02). Audit gate PASS (6/6 traced, 32/32 criteria covered). Manifest at 2.2.0; tag to be cut on main after merge.
METHOD.md and README.md both ended adjudication at the verdict ("the main model grounds and owns the verdict"), which is the claim this phase makes false. docs-verify catches drift, not omissions, so the update is an execution task rather than a later sweep. METHOD's "Adjudication inverts the hierarchy" gains a third paragraph: what survives is not a work order, it is a numbered list the session asks about, default none, at the four firing sites - with the opt-in unattended close as the one exception. README gains one sentence in the paragraph's own voice. skills/cad-plan-review/SKILL.md's step 3 cited the plan-creation flow as its contrast case ("not the plan-creation flow where cad-plan applies them"), which stopped being true when plan.md started triaging. The clause before it ("Do NOT auto-apply changes to the plan") was always correct and is why the file was otherwise scoped out.The widened rule matched vocabulary, not mood, so a compliant block reported its own reasons for being compliant. Four shapes, all measured at e9bd733: rationale "Issue the whole set in ONE message. Serializing it - one dispatch per message - only adds latency on the parallel path." -> 1, on the sentence that EXPLAINS the rule negation "Dispatch all reviewers in one message; never for each reviewer in turn, and never in parallel where the host allows." -> 1, on the clause that FORBIDS the shape inflection "On the parallel path every worktree issues its own findings, and all of them land in one report." -> 1 (`issues` as a third-person verb, `all of them` as a back-reference) catalog a `phase_diff` wiring row ("... after all worktree batches are sent ...") and a `plan` row ("fired for every plan on the parallel path") -> 1 each All four are 0 now. A sentence ISSUES work when a BARE-FORM dispatch verb opens a clause - the sentence itself past any list marker, or the position after `|`, `:`, `;`, `,`, `(`, `[`, or a conjunction, modal or infinitive `to`. `never` and `not` are not lead words, so a negation excludes itself from the shape it forbids; an inflection marks every mood but the imperative. Both arms require it, and the serial arm also takes a trailing colon, which issues the list under it - the exact shape #88 filed, which carries no verb at all. Sentence splitting now cuts at bracket depth zero only, so `(conventions.md Parallel work; seams.md concurrent dispatch)` stays one citation instead of a compliant half and an unbatched-looking half; that style appears at config-review.md:21, decision-review.md:69, phase.md:49, plan.md:139 and new-project.md:279. Only a bracket that CLOSES suppresses a split, or one stray `(` would glue a block into a single sentence where any compliant clause whitewashes every offender after it. Measured after: 0 issues over the 33 files under cadence-core/workflows and cadence-core/references; `Dispatch each reviewer concurrently.` still 1; the pre-phase execute.md item 1 still 1, quoting `one dispatch per message`; the two-sentence whitewash still 1, quoting the second sentence; the shipped execute.md item 1 still 0. Eight rows added, six of them red before this change.The consult ordering is the opposite of what the arm's comment left implied: cmdConsult resolves the provider FIRST and asserts the cap SECOND (review-provider.mjs:591,596), so the stub key file is what lets the flow reach the cap at all. Verified by dropping --key-file with the three key env vars unset and XDG_CONFIG_HOME at an empty dir - the seam answers no-key, not over-cap. That experiment also found a hermeticity hole: resolveKey falls back to ${XDG_CONFIG_HOME:-~/.config}/cadence/providers.env, so on a machine that has one, an arm without --key-file reads a REAL key and reports over-cap while a clean machine reports no-key. Every child env now pins XDG_CONFIG_HOME at an empty fixture dir, making the file's hermeticity claim true for the key file as well as the config layers. The land-cleanup arm's `action === 'cleanup'` was vacuous (on_land_cleanup defaults true) and the default state of every unconfigured install was a third get-vs-seam divergence the file neither pinned nor recorded: with git.base_branch unset, get reports null while cleanup reports base 'main' from the protectedBranches[0] fallback. It is recorded now, with the contrast that shows the fallback follows the configured list rather than the literal 'main'. The auto_close arm gains land-cleanup's gate beside git-publish, so the file proves the two seams AGREE on the narrowing its reach row now declares.A subcommand alone cannot tell an invocation that destroys uncommitted work from an ordinary one: `git checkout .` and `git checkout main` were byte-identical to every caller. Each git invocation now reports {sub, args, denyable}, and `subs`/`denyable` are derived from that one list rather than accumulated beside it. Where an invocation's args END is decided by position, not word shape. A git word at command position owns its whole simple command; a git word elsewhere is a speculative reading of another command's operands and stops where the next such reading begins. A word-shape boundary matches ordinary arguments - vendor/git, src/git, a message word `git` - as exactly as it matches a second invocation, and reported `git clean vendor/git -fdx` as a no-op `git clean`. Purely additive: subs, unplaced and denyable are unchanged against HEAD over 149,248 distinct inputs.The args boundary shipped in 776e6a0 only closed the command-position half. Behind any transparent prefix the git word is off command position again, so the args still truncated at the next git-SHAPED word: git clean vendor/git build -fdx -> args ["vendor/git","build","-fdx"] sudo git clean vendor/git build -fdx -> args [] timeout 60 git clean vendor/git build -fdx -> args [] Live git 2.55 prints "Removing build/" and "Removing vendor/git/" for all three, so a policy reading those args answered "no force flag" about a command that force-deletes untracked files. Same miss for nohup, xargs -I{} and sudo -u. No boundary can do this job: `sudo git clean vendor/git build -fdx` (one command git owns to the end) and `xargs -I{} git add . git push` (one argv xargs hands to git) are the same word shapes at the same positions. So the readings OVERLAP - every invocation runs to the end of its simple command - which is the safe direction: an extra reading costs at most a prompt, a dropped flag costs the work. Position now decides AUTHORITY only (denyable), never reach. `xargs -I{} git add . git push` reports `add` with ['.','git','push'] and `push` with none; that row is rewritten rather than deleted, since it pins the trade.A pure, total, zero-dep destructiveInvocation(invocations) with four arms - reset --hard, clean with force, a checkout/restore worktree overwrite, and a branch force-delete - and no others. It can only ever produce an ASK. It parses git's option grammar rather than matching spellings. Matching exact strings and letter clusters let live git 2.55 through three ways, each a silent unrecoverable deletion: (a) a long option abbreviates: `git reset --ha` and even `--h` ARE `--hard`; `git branch -d --forc x` deleted an unmerged branch. (b) an option consumes its value, glued or as the next word: `git clean -fdxenode` is `-e node` and deletes, `git clean -fe -n` is the pattern `-n` and deletes, `git restore -sSTABLE .` is `--source=STABLE` and overwrote the worktree - while `git clean -e -f` has no force flag at all and git refuses to clean. (c) `--` ends the options: `git clean -fdx -- -n` is a path named `-n`. Long-option resolution is deliberately permissive (a prefix is read as every option it prefixes), because an abbreviation git calls ambiguous is one git refuses to run: it can over-fire on a command that destroys nothing and can never under-fire. Option tables are transcribed from `git <sub> -h` on 2.55 and every asserted behaviour was run in a throwaway repo. Two exclusions, recorded in the module header rather than in a test row's name: 1. `git checkout <path>` with no `--` stays SILENT and does destroy - the word is byte-identical to a branch switch and only the repo can tell them apart, so firing means prompting on every `git checkout feature/x`. Checkout fires where the words settle it: `--`, --pathspec-from-file, a force flag, two or more operands, or an operand no ref name can be. `git restore <path>` fires, which covers the same edit under the verb whose operands are always pathspecs. 2. stash drop, push --force and rebase stay out; TOK-02 names four classes. 101 rows, 24 of which fail against the previous matcher.lib/shell-tokens.mjs, lib/destructive-git.mjs and both their test files are gone: 2,251 lines replaced by lib/git-segments.mjs, about thirty. A segment counts only when its command word is `git`, and the verb is its first non-flag word. Nothing else is inferred. WHY NOW, since the widener argument that justified the tokenizer is still sound. A detection widener is safe to get wrong, which is not the same as cheap to get wrong, and the bill came due three ways in one phase. The escape surface behind `bash -c`, `$(...)`, `${...}`, aliases and `ssh` is unbounded, so three consecutive blocking review panels each found new holes and each patch bought more grammar; the reader still went silent on `git switch -f main`. And the scan was O(K x N) in memory - 3.1GB at 224KB of input, a V8 abort at 280KB - in a hook that runs on EVERY Bash call and fails OPEN, so a long enough command line switched the guard off and let the push inside it run unprompted. The new reader is total and linear; that same 336KB input decides in milliseconds, and a test pins it above the measured abort point. The anchor also retires a second rule. Detection used to be any-position, so `rg -t sh "git commit"` was read as a commit, so refusal had to be narrowed back to command position by an enumerated prefix set that three review rounds kept finding new members of. Reading only the command word makes those silent up front: `denyable` and `unplaced` are deleted with nothing to replace them. ACCEPTED COST, stated rather than discovered later. A wrapped, substituted or prefixed invocation is now invisible: `bash -c "git push"`, `sh -c`, `eval`, `$(...)`, backticks, a subshell, `sudo`/`timeout`/`xargs` prefixes, `env -S`, `ssh host "..."`, `git -C "<path with space>" push`, and a line continuation. Each is a pinned test row in git-guard.test.mjs and git-segments.test.mjs rather than an absence. INTERNALS.md's guard section carries the reversal, including that this is its own line 29 rule ("delete the thing you would have had to parse") applied to this codebase a milestone late.Three loose ends from the phase-2 close, none of them code. **v2.1.0 had no CHANGELOG section.** The milestone closed in the planning docs at `e457e47` and REQUIREMENTS carries three requirements marked Complete against it (COV-01, TRI-02, REV-03), but no `## [2.1.0]` heading was ever written, so `## [Unreleased]` sat directly on `## [2.0.0]` with a whole release's notes missing. Reconstructed from the phase summaries at `643663e~1` and the range `fe2310f..e457e47`, and labelled as backfilled rather than passed off as contemporaneous. Worth naming: this gap is an instance of exactly what REL-03 (phase 3) exists to fix, so it is now also that phase's proof case. **Phase 2's SUMMARY said `status: partial`** and narrated a halt, which stopped being true when the phase completed. Rewritten to what shipped, with the scope change stated at the top rather than implied. The halted first attempt is kept VERBATIM under its own heading instead of being replaced: those three blocking panels and the O(K x N) measurement are the evidence the deletion decision was made from, and a summary that erased them would leave the decision looking arbitrary. **Five CAPTURE items closed as moot.** `#29`, `#31`, `#32` and `#201` all describe behaviour of `lib/shell-tokens.mjs` and `lib/destructive-git.mjs` - the wrapper-with-no-operand rule, the sudo/wrapper refusal asymmetry, the decidable-but-unclosed shapes, and the three families of silent destruction. None has code left to be true of. `#13` is the v1.4.0 open decision itself ("rip out vs keep investing, user leans rip-out"), decided rip-out on 2026-08-03; its part (a), the triage-gate design half, had already shipped as TRI-02. 158 open items to 153.