fix(resolve): refuse the #414 pair before the session; inventory journal kinds and refusal sites - #761
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
7899f0b to
351627a
Compare
351627a to
9abe935
Compare
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9abe935aeb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| f"the recorded spec for this story ({spec}) could not be re-opened to " | ||
| f"`{status}` — it is not a readable file from here, and this run mounts " | ||
| "no worktree, so the re-drive reads that same path and will see the " | ||
| "escalated attempt's status", |
There was a problem hiding this comment.
Record redrive mode instead of inferring it from reachability
When worktree isolation uses an artifact directory configured outside both checkouts, spec_reaches_the_redrive() returns true via _spec_is_shared_with_the_redrive() even though the re-drive still mounts a fresh worktree. If that shared spec is missing or unreadable and produces this record, the new notice incorrectly says that the run mounts no worktree. Persist the actual isolation/redrive mode on the record instead of deriving it from reaches_redrive.
Useful? React with 👍 / 👎.
| f"`{status}` — it is not a readable file from here, and this run mounts " | ||
| "no worktree, so the re-drive reads that same path and will see the " | ||
| "escalated attempt's status", | ||
| "Check the recorded spec path before resuming", |
There was a problem hiding this comment.
Hold the resume before requesting a pre-resume check
When an in-place re-arm encounters a missing or renamed recorded spec, the producer writes reaches_redrive=True and refused=False, so this new branch tells the operator to check the path before resuming. However, rearm_holds_the_resume() does not recognize rearm-spec-flip-skipped, so the default and --resume CLI flows print this instruction and immediately call _resume_paused_run; the operator cannot perform the requested prerequisite. Either hold this proven-reachable failure or make the notice accurately advisory.
Useful? React with 👍 / 👎.
9abe935 to
cc0054f
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc0054f1c4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for kw in node.keywords: | ||
| if ( | ||
| kw.arg == "kind" | ||
| and isinstance(kw.value, ast.Constant) | ||
| and isinstance(kw.value.value, str) |
There was a problem hiding this comment.
Inspect positional kind arguments at dynamic sites
When a caller supplies a literal kind positionally, this loop emits nothing because it only examines node.keywords. That is valid for _close_bundle_ledger_when_spec_status(task, spec, status, "new-kind"), whose kind parameter is positional-or-keyword, so the new journal kind bypasses JOURNAL_KINDS and the inventory remains green. Resolve the declared function's kind parameter position or make all inventoried dynamic-kind parameters keyword-only.
Useful? React with 👍 / 👎.
cc0054f to
22768c0
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22768c01e5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Journal(run_dir).append( | ||
| "rearm-code-root-restamped", | ||
| repo=new, | ||
| code_root_changed=True, | ||
| ) |
There was a problem hiding this comment.
Preserve the code-root move record after append failures
If this append fails once—for example because the journal is temporarily unwritable—the preceding save_state has already committed the new repo_root, so cmd_resolve returns failure with no durable move record. On retry, restamp_code_root exits at state.repo_root == new, meaning the record is never retried, and the later run-resume entry reports code_root_changed=False; this permanently recreates the audit gap this record is intended to close. Keep the transition retryable by rolling back/persisting a pending marker when the append fails, or otherwise couple the repair write and its record.
AGENTS.md reference: AGENTS.md:L78-L81
Useful? React with 👍 / 👎.
22768c0 to
200fa8d
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
200fa8d to
3c61038
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3c61038e54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Journal(run_dir).append( | ||
| "rearm-code-root-restamped", | ||
| repo=new, | ||
| code_root_changed=True, | ||
| ) |
There was a problem hiding this comment.
Record the root move only after persisting it
If save_state fails after this append—for example, when the existing journal file is writable but the run directory cannot create state.json.tmp—the command leaves repo_root unchanged while permanently recording rearm-code-root-restamped with code_root_changed=True. Fresh evidence in this revision is that the append was moved before save_state, so it fixes the missing-record retry case by introducing a false completed-move record; repeated failed retries also append duplicates. Persist a pending/intent marker or otherwise distinguish the attempted move until the state write succeeds.
Useful? React with 👍 / 👎.
3c61038 to
7b4c49d
Compare
7b4c49d to
b48b2b8
Compare
|
@codex review |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4865b03545
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if state.repo_root != new: | ||
| moved = bool(state.repo_root) | ||
| state.repo_root = new | ||
| # The move and its intent marker land in ONE atomic state write: a | ||
| # save that fails here changes nothing on disk, so the retry simply | ||
| # redoes it, and a save that succeeds has durably recorded that a | ||
| # record is now owed. The migration of an empty (pre-field) root is | ||
| # not a move and owes nothing. | ||
| state.code_root_restamp_pending = moved |
There was a problem hiding this comment.
Preserve the owed root move across another config change
If the first restamp from root A to B persists this marker but its journal append fails, and the operator changes repo_root again before retrying, this branch overwrites the pending B move with the latest root. For example, restoring the original root A causes the retry to append repo=A, code_root_changed=True and warn that existing baselines belong to a previous repository, even though they were created in A and no re-arm ever ran in B; changing to C similarly loses the owed B record. The pending state needs to retain the move it describes rather than only a boolean, or journal/cancel that move before replacing it.
Useful? React with 👍 / 👎.
| f"`{status}` — it is not a readable file from here{mount}, so the " | ||
| "re-drive reads that same path and will see the escalated attempt's " | ||
| "status", | ||
| "Check the recorded spec path before resuming", |
There was a problem hiding this comment.
Require restoring the spec's routing status before resume
When this holding arm is reached because a missing or renamed spec is restored at the recorded path, merely checking that path does not ensure the file was reopened to ready-for-dev/in-review; restoring the old file commonly restores the escalated attempt's terminal status. The re-arm has already moved the task to PENDING, so resolve cannot be rerun, and following this instruction then running resume still burns the escalation on the routing failure the hold is meant to prevent. The next step should explicitly require making the path readable with the displayed target status before resuming.
Useful? React with 👍 / 👎.
4865b03 to
e8664a1
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8664a1ce0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| literal = _positional_kind_literal(node, index) | ||
| if literal is not None: |
There was a problem hiding this comment.
Reject non-literal arguments to dynamic kind positions
When an allowlisted helper is called with a variable or expression in its positional kind slot, _positional_kind_literal returns None and this branch emits nothing; the keyword arm likewise ignores non-literals. Because the helper's internal journal.append(kind, ...) is already waived by JOURNAL_DYNAMIC_KIND_ALLOW, code such as kind = "new-kind"; self._close_bundle_ledger_when_spec_status(task, spec, status, kind) can therefore write an undeclared journal kind while the new completeness guard remains green. Emit an unresolved-kind finding for non-literal call-site arguments so the negative inventory assertion fails on this ablation.
AGENTS.md reference: AGENTS.md:L62-L62
Useful? React with 👍 / 👎.
e8664a1 to
d7abf2b
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7abf2b5c1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # The `run-resume` record above IS the record an outstanding re-stamp owed, so the | ||
| # marker clears on the same write that persists the resume — never a separate | ||
| # one, which could land without it and leave the run owing a record it has. | ||
| state.code_root_restamp_pending = False |
There was a problem hiding this comment.
Preserve the pending root record across plain resume
When a restamp from root A to B persists code_root_restamp_pending but its append fails, and the operator then changes the config to A or C before running plain resume, this path emits only one generic run-resume boolean and later clears the marker while overwriting repo_root; the owed A→B rearm-code-root-restamped row is permanently lost. Restoring A also produces a false “changed since this run started” warning even though the resumed tree is the original baseline tree. Fresh evidence beyond the prior retry threads is that restamp_code_root now discharges this debt before a second move, but _prepare_resume_locked still does not; discharge the pending record before adopting a different paths.repo_root.
Useful? React with 👍 / 👎.
| for kw in node.keywords: | ||
| if kw.arg != "kind": | ||
| continue |
There was a problem hiding this comment.
Treat keyword splats as unresolved journal kinds
When a declared dynamic-kind helper is called as self._skip_review_and_commit(task, **{"kind": "new-kind"}) (or with a variable kwargs mapping), the keyword has kw.arg is None, so this loop skips it; the positional arm sees an empty slot and the definition arm emits only the default. The runtime can therefore write new-kind while JOURNAL_KINDS remains unchanged and the completeness guard stays green. Resolve literal mappings or emit UNRESOLVED_DYNAMIC_KIND for an unreadable ** splat, with an ablation probe for this valid call shape.
AGENTS.md reference: AGENTS.md:L62-L62
Useful? React with 👍 / 👎.
d7abf2b to
16dbf81
Compare
`cmd_resolve` checked `isolation = "worktree"` beside a `repo_root:` override only after the confirm, so an operator built adapters, conversed with a full agent and answered the re-arm prompt before being handed rc 1 for a pair that was knowable from config the whole time. `cmd_run` and `cmd_sweep` refuse it before provisioning anything. The check now also runs in the pre-session arm, and the `bmadconfig.load_paths` read moves above `_make_adapters` so it can precede the adapter build for the reason `cmd_run` puts it ahead of its queue and worktree-clean gates: this refusal says the configuration cannot run at all, so an adapter fault reported first sends the operator at the wrong problem. Ordering only -- `load_paths` is a read and its degrade arm is unchanged. The post-confirm refusal stays the authority: it re-reads the config after a conversation of unbounded length, and it is the only one that `--no-interactive` reaches. `restamp_code_root` now journals `rearm-code-root-restamped`. It is the gesture on which the code root actually moved, and it was the one leaving no durable trace: the re-stamp aligns the mirror `_resume_paused_run` later compares against config, so by the time `run-resume` computes `code_root_changed` the two necessarily agree and it records false. A stderr line and a TUI toast are not records. `rearm-spec-flip-skipped` carries `reaches_redrive`. `refused` is False for two disjoint reasons and a reader out of process cannot re-derive which, so the renderer asserted worktree behaviour -- "it mounts a fresh worktree and reads the COMMITTED spec" -- on a run that mounts nothing, telling the operator a failed flip was harmless at exactly the moment it is not. A record written before the field keeps the wording it was written under. `stale-restore-excluded` drops its completed-past-tense claim about a baseline that an abort never persists. Three of those fixes had landed with no test. Rows were added for each at the lowest layer that catches the regression, and `reaches_redrive` is declared in `JOURNAL_BENIGN_FIELDS`, which the routing guard had failed on. Every new negative assertion was ablated serially against a cp backup under PYTHONDONTWRITEBYTECODE=1 and graded on the named test reddening. Four documentation defects found by the same review are corrected. A `Changed` bullet claimed diagnose routes "by field name across every entry rather than by kind, so no existing run's dump changes shape" -- false in both halves, and it contradicted the `Security` entry beside it; the kind table does change pre-existing dumps, for `target` on the three merge kinds and `sentinel` on `sentinel-cleared`. Three `Fixed` entries repaired defects no released version had, and are folded into the `Added`/`Changed` entries that introduce them rather than deleted, since a release promotes `Unreleased` verbatim. The `Security` entry states that the branch-name leak pre-dates this work and that the resume hold and lists `resolve` among what the #414 pair refuses.
Enumerate-vs-declare inventories for the two surfaces review iteration 6 kept re-finding by hand: 200 literal journal kinds (JOURNAL_KINDS, fed by a literal-kind emit that also sees kind-only and constructor-inline writes), nine _refuse_*/_reject_* helper defs (REFUSAL_HELPER_DEFS, Counter multiplicity), and the eleven #414-family isolation-refusal call sites (ISOLATION_CONFLICT_CALLERS, with multiplicity). Exact in both directions; every new detector arm carries must-flag and must-stay-silent probe rows.
…at once Review pass 2 follow-ups to 8100c53: - A second journalkindliteral arm reads the literal kind= a caller hands a declared dynamic-kind position, and that position's kind parameter default, so review-skipped, review-skipped-awaiting-operator, sweep-bundle-closed and sweep-bundle-reclosed join JOURNAL_KINDS (200 -> 204). Only the f-string family stays outside the inventory; header, docstring, testing.md and CHANGELOG now say so. Must-flag rows landed failing-first. - test_journal_kind_inventory_is_complete grades undeclared and stale from one scan in one assertion (_journal_kind_inventory_drift), so a rename's single failure names both the new spelling and the stale row; a synthetic-findings probe pins the helper. - State the constructor arm's bounds: a Journal subclass constructed inline, super().append inside _RearmJournal's override, and an import-aliased constructor are invisible to the journal scan. - docs/testing.md: the "four detectors carry probe matrices" bullet rewritten to the current family list.
… flip `rearm-spec-flip-skipped` carried neither fact its operator surfaces need. The notice inferred the mount from `reaches_redrive` and told an ISOLATED run it "mounts no worktree". That arm reaches the re-drive through `_spec_is_shared_with_the_redrive` — an artifact dir configured outside the project tree, reachable precisely because every checkout sees that one file, with a worktree very much mounted. The producer now writes the live `redrive` mode, exactly as the sibling `rearm-spec-write-unreachable` already does, and the notice reads it instead of deriving it. An absent field DROPS the mode clause rather than taking the sibling's "absent means isolated" default: that rule is sound only because its in-place arm is newer than the field, while this kind was journalled from both modes before the field existed. `rearm_holds_the_resume` now also holds for that record's `reaches_redrive and not refused` leg, keyed on the FLAGS rather than the bare kind. The futility proof is the producer's own `refused = spec_path.is_file() and write_reaches_the_redrive`, so reaching and not refused means the path is unreadable here and the re-drive reads that same path. The other two arms must not hold: `refused` raises `RearmError` so no resume happens, and the third carries no next_step. Its "check the recorded spec path before resuming" had been rendering on the two surfaces that re-arm and resume in one gesture.
The dynamic-kind arm read `node.keywords` only, so a literal handed one of the declared positions POSITIONALLY was never inventoried. `kind` is positional-or-keyword on `sweep._close_bundle_ledger_when_spec_status`, so `self._close_bundle_ledger_when_spec_status(task, spec, status, "new-kind")` is legal Python that reaches the journal with no `JOURNAL_KINDS` row anyone had to decide on — and the guard stayed green, which is the failure mode this file exists to refuse. All three ways a literal reaches such a position now feed one emit: the caller's keyword, the caller's POSITIONAL argument, and the parameter default. `_kind_param_index` resolves the slot from the declared function's own signature and drops a leading `self`/`cls`, because a bound call never fills the receiver's slot; the unbound spelling would shift by one, does not occur here, and misses rather than false-alarms. Two bounds made explicit rather than left implicit. A `*args` splat covering the slot returns a sentinel kind no row can declare, so an unreadable position reddens the inventory naming its site instead of sharing its silence with "this call passed no literal". And a declared FORWARDER is skipped: `plugins/bus.py`'s `_log` is itself a journal write whose positional kind the main emit already reports, so reading it here would double-report every one of its call sites. Also declares `spec-deferral-sighting-stale`, the kind the harvest's stale seen-again arm writes. The inventory caught its absence unprompted, which is the guard working.
…iled append retries `restamp_code_root` committed the new `repo_root` under the lock and journalled `rearm-code-root-restamped` afterwards, outside it. An append that failed once — a journal temporarily unwritable — therefore left the move durable and the record absent: `cmd_resolve` returned failure, the retry exited at `state.repo_root == new`, and the later `run-resume` line reported `code_root_changed=False` on the one gesture where the root did move. That is the audit gap the record exists to close, recreated permanently. The append now runs inside the hold, ahead of `save_state`. A failed append leaves the mirror untouched so the retry redoes both; a save that fails after a successful append costs one duplicate true record on the retry — at least once, never never. The lock-inventory guard is unaffected: it counts call sites, not ordering.
…and its record Round 3a moved the `rearm-code-root-restamped` append ahead of `save_state` so a failed append left the move retryable. That traded one hole for another: a save that fails after the append — the journal writable, the run directory refusing `state.json.tmp` — leaves a permanent record of a completed move that state.json never made, and repeated retries append more of them. state.json and the journal are two files with no transaction across them, so the move and its record cannot be made durable in one step. The move now lands with `RunState.code_root_restamp_pending = True` in one atomic state write; the record follows it; the marker is cleared, with a second save, only once the append has returned. A failed first save changes nothing on disk. A failed append leaves the root moved and the marker set, so the retry re-enters past the "already agrees" return and writes the record the move still owes. The one residual is a clearing save that fails after a successful append, which costs a duplicate — true — record on the retry; a duplicate is recoverable from the journal, a missing record and a false one are not. The field is absent from the schema-1 `--json` projection, like `rearmed` / `resolved_redrive`.
…d toast `TuiApp._do_rearm` printed one hardcoded "commit the corrected spec" for every record `runs.rearm_holds_the_resume` recognises, and two of the four cannot be obeyed that way: `rearm-spec-write-unreachable`'s in-place arm needs the edit made in the main checkout, and `rearm-spec-flip-skipped`'s holding arm is journalled with `refused = spec_path.is_file() and write_reaches_the_redrive`, so the `reaches_redrive and not refused` gate entails the path is not a file — nothing to commit, and on the isolated arm possibly a shared artifact directory outside any repository. `RearmOutcome` gains a defaulted `hold_next_step`, captured first-wins in `_RearmJournal.append` so the operator is sent to the earliest holding cause. The hold toast composes it: the resume's absence leads, the step follows, and the tail says the run stays paused and resumable from this screen — rather than ordering a resume in the same breath as the hold. Advisory toasts still drop `next_step`; only the hold line carries it.
…gain `restamp_code_root`'s `code_root_restamp_pending` is a bare bool, so the root an unlanded `rearm-code-root-restamped` record was owed for is described only by `state.repo_root`. When the append failed and the operator re-pointed the root a second time before retrying, the retry overwrote that value and the earlier move's record was lost for good. The retry now appends the owed record under the root the marker still names, ahead of the overwrite. Ordering keeps the same at-least-once bargain the trailing append already defends: nothing is written or cleared before it, so an append that fails here leaves the root and the marker exactly as a further retry needs them. Both existing failure-path tests retry with the SAME root, where the owed root and the retry's root coincide and the loss cannot show; the new test covers the retry after a second move, restoring the original and moving to a third tree.
A holding record buys the operator one gesture before the re-drive reads the tree, and routing is decided by the spec's frontmatter status alone: a spec with none HALTs the re-driven session on `unrecognized status in existing story file`, and one still carrying the escalated attempt's terminal status routes to "ingest as context, do not resume". So an operator who obeyed a remedy naming only the FILE and the TREE could do exactly as told and still burn the escalation. All four holding arms now render the target `status:` through one shared `_redrive_status_clause`, read off the record's own `status` field rather than recomputed: the in-place and isolated arms of `rearm-spec-write-unreachable`, `rearm-spec-flip-skipped`'s holding arm, and `rearm-upstream-write-unreachable`. A record carrying no status drops the clause rather than guessing, on the same principle the `target_branch` clause follows. The upstream arm renders it for UNIFORMITY and renders it EMPTY on the leg its producer emits: that append carries no `status`, because the sentinel path deletes the spec and re-dispatches from PENDING, and `stories.yaml` rejects a `status` key outright. The producer and the record schema are unchanged, so every remedy that arm renders today is byte-identical to before; the arm carries a comment saying so, and a test row pins the empty rendering beside the one that pins the interpolation. Also corrects the flip-skipped arm's message. Its precondition (`reaches_redrive and not refused`, against `refused = spec_path .is_file() and write_reaches_the_redrive`) forces `is_file()` False, so the re-drive finds no spec at that path — it cannot "see the escalated attempt's status", and saying so contradicted the restore remedy beside it.
16dbf81 to
7f0be1f
Compare
What
Last of five stacked PRs draining Wave 2's deferred-work ledger. Not a sweep batch: these are the
findings review iteration 6 raised directly — the
#414isolation-conflict refusal moving aheadof the interactive session, plus three enumerate-vs-declare coverage gates.
Why
bmad-loop resolveaccepted the#414pair and only refused it after the interactive session hadalready launched and mutated. The refusal belongs before the session, not after. The guard
inventories then stop the refusal sites — and the journal kinds beside them — drifting out of
coverage silently.
How
fix(resolve): refuse the#414pair before the interactive session rather than after ithas mutated.
tests/test_portability_guard.py: the 204 literal journal kinds (JOURNAL_KINDS, includingconstructor-inline
Journal(run_dir).append(...)writes a receiver-spelled scan was blind to),the
_refuse_*/_reject_*helper definitions counted with multiplicity(
REFUSAL_HELPER_DEFS), and the eleven#414-family isolation-refusal call sites(
ISOLATION_CONFLICT_CALLERS).call sites (
plugins/bus.py::_log) enter the inventory.A new kind, refusal helper, or refusal call site reddens CI until its row lands; the row is the
PR-time decision, and the failure message demands the covering test land beside it. Each detector
arm carries must-flag and must-stay-silent probe rows.
Testing
uv run pytest -q -n logical— 7981 passed, 51 skipped.uv run pyright— 0 errors, 0 warnings,0 informations.
trunk check --all --no-fix— 259 files, no issues. Negative assertions ablatedbefore being trusted.
Changelog
Entries land under
## [Unreleased]inCHANGELOG.md.Stack (merge bottom-up): S1 → S2 → S3 → S4 → S5 (this). Base is S4, not
main.Four ledger entries stay open by decision, not omission:
_notice_reason's display cap belongs to Wave 3/#734, chartered to unify all six display surfaces. No line-shape test can be written before that fix lands without asserting the very behaviour deferred.patchby name covers three kinds beyond the two the entry named — intended and documented, flagged for a second look.