Skip to content

fix(resolve): refuse the #414 pair before the session; inventory journal kinds and refusal sites - #761

Merged
pbean merged 15 commits into
pbean/dw-s4-sweep-dw3-dw5from
pbean/dw-s5-414-refusal-guards
Sep 4, 2026
Merged

fix(resolve): refuse the #414 pair before the session; inventory journal kinds and refusal sites#761
pbean merged 15 commits into
pbean/dw-s4-sweep-dw3-dw5from
pbean/dw-s5-414-refusal-guards

Conversation

@pbean

@pbean pbean commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

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 #414 isolation-conflict refusal moving ahead
of the interactive session, plus three enumerate-vs-declare coverage gates.

Why

bmad-loop resolve accepted the #414 pair and only refused it after the interactive session had
already 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 #414 pair before the interactive session rather than after it
    has mutated.
  • Journal-kind and refusal-site coverage gates — three inventories in
    tests/test_portability_guard.py: the 204 literal journal kinds (JOURNAL_KINDS, including
    constructor-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).
  • Forwarder-kind literals inventoried, both kind arms graded at once — a declared forwarder's
    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 ablated
before being trusted.

Changelog

Entries land under ## [Unreleased] in CHANGELOG.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:

entry why it stays open
DW-13 _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.
DW-55 Follow-up review recommended after the damping cap was spent.
DW-64 Follow-up review recommended after the damping cap was spent.
DW-78 Registering patch by name covers three kinds beyond the two the entry named — intended and documented, flagged for a second look.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: e4e4e79f-3b9a-4c01-a247-9decb66803f8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pbean

pbean commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T01:44:48.197111Z d7abf2b Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/bmad_loop/runs.py Outdated
Comment on lines +5060 to +5063
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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread src/bmad_loop/runs.py Outdated
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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from 9abe935 to cc0054f Compare September 2, 2026 23:38
@pbean

pbean commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread tests/test_portability_guard.py Outdated
Comment on lines +2256 to +2260
for kw in node.keywords:
if (
kw.arg == "kind"
and isinstance(kw.value, ast.Constant)
and isinstance(kw.value.value, str)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from cc0054f to 22768c0 Compare September 3, 2026 00:37
@pbean

pbean commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/bmad_loop/runs.py Outdated
Comment on lines +4010 to +4014
Journal(run_dir).append(
"rearm-code-root-restamped",
repo=new,
code_root_changed=True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from 22768c0 to 200fa8d Compare September 3, 2026 01:12
@pbean

pbean commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 200fa8d038

ℹ️ 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".

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from 200fa8d to 3c61038 Compare September 3, 2026 01:31
@pbean

pbean commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/bmad_loop/runs.py Outdated
Comment on lines +4016 to +4020
Journal(run_dir).append(
"rearm-code-root-restamped",
repo=new,
code_root_changed=True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from 3c61038 to 7b4c49d Compare September 3, 2026 01:41
@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from 7b4c49d to b48b2b8 Compare September 3, 2026 02:53
@pbean

pbean commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@pbean

pbean commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/bmad_loop/runs.py
Comment on lines +4107 to +4115
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread src/bmad_loop/runs.py Outdated
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",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from 4865b03 to e8664a1 Compare September 3, 2026 22:40
@pbean

pbean commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +2343 to +2344
literal = _positional_kind_literal(node, index)
if literal is not None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from e8664a1 to d7abf2b Compare September 4, 2026 01:27
@pbean

pbean commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/bmad_loop/cli.py
Comment on lines +2817 to +2820
# 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +2333 to +2335
for kw in node.keywords:
if kw.arg != "kind":
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from d7abf2b to 16dbf81 Compare September 4, 2026 02:58
t added 15 commits September 3, 2026 20:31
`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.
@pbean
pbean force-pushed the pbean/dw-s5-414-refusal-guards branch from 16dbf81 to 7f0be1f Compare September 4, 2026 03:31
@pbean
pbean merged commit c47333d into main Sep 4, 2026
11 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant