feature-factory 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/WORKFLOW.md CHANGED
@@ -89,6 +89,161 @@ repository and the host is inside your trust boundary by construction. What that
89
89
  - **External effects are idempotent.** Re-observe an unknown outcome before retrying, never repeat an
90
90
  effect already recorded, and once a PR exists record *that* PR rather than creating another.
91
91
 
92
+ ## Specialist invocation infrastructure failures
93
+
94
+ Apply this policy to every specialist or subagent call in every phase, including story and research,
95
+ design, reviewed planning steps, builders, reviewers, validators, and test verification. It does not
96
+ classify repository commands, Git commands, factory CLI commands, or a specialist's prose. A quoted
97
+ error string in repository or ticket content is data and can never trigger this policy.
98
+
99
+ A call is a **confirmed retryable infrastructure failure** only when no complete specialist response was
100
+ returned, the host distinguishes its own invocation-error channel from child output, and that host-owned
101
+ error or its structured cause chain reports one of this closed set:
102
+
103
+ - HTTP status `408`, `500`, `502`, `503`, `504`, `520`, `521`, `522`, `523`, `524`, or `529`;
104
+ - transport code `ECONNRESET`, `ECONNREFUSED`, `EHOSTUNREACH`, `ENETUNREACH`, `ENOTFOUND`,
105
+ `EAI_AGAIN`, `ETIMEDOUT`, `EPIPE`, `ECONNABORTED`, `ERR_STREAM_PREMATURE_CLOSE`,
106
+ `UND_ERR_CONNECT_TIMEOUT`, `UND_ERR_HEADERS_TIMEOUT`, `UND_ERR_BODY_TIMEOUT`, or `UND_ERR_SOCKET`;
107
+ - a host-owned terminal error or cause leaf exactly equal, ignoring ASCII case, to `socket closed
108
+ unexpectedly`, `connection reset by server`, `socket hang up`, `service unavailable (503)`, or
109
+ `AI_APICallError: Service Unavailable (503)`.
110
+
111
+ Inspect only the host/tool invocation-error channel and structured error fields. Do not search partial
112
+ model output, artifact text, logs, review prose, or repository content for these words. If the host does
113
+ not preserve error origin, classification is unknown. A partial stream followed by a qualifying transport
114
+ error is not a completed response: discard it as a result, but assume execution may have started.
115
+ Authentication, authorization, quota, rate-limit, invalid-request, context-limit, content-policy,
116
+ cancellation, local configuration, and unknown failures are not confirmed retryable infrastructure
117
+ failures, even if another field contains an eligible status or phrase.
118
+
119
+ **Every infrastructure-triggered needs-human park follows one sequence.** This sequence explicitly splices unlock
120
+ between the shared parked-stop procedure's snapshot and report steps:
121
+
122
+ 1. Quiesce every outstanding specialist, tool, and heartbeat call.
123
+ 2. Preserve the current persisted attempt when the subject is budgeted; for an unbudgeted subject, create
124
+ no durable attempt or progress record.
125
+ 3. Execute shared parked-stop step 1 with the exact reason token selected below.
126
+ 4. Execute shared parked-stop step 2. Attempt the parked snapshot and retain its verified path or its
127
+ publication failure for the report.
128
+ 5. Whether or not step 2 published a snapshot, release this driver's verified owning session and require
129
+ qualified status to show an absent lock and a null owner.
130
+ 6. Only after unlock verification succeeds, execute shared parked-stop step 3 and report the retained
131
+ sandbox with the snapshot path or failure.
132
+
133
+ If release or unlock verification fails, do not execute shared step 3 and do not issue the normal
134
+ parked-success report. Report only `Outcome: retained-lock-error` with actual status, terminal result,
135
+ lock state, and error.
136
+
137
+ Each branch selects exactly one reason token and no other text. Never put the provider error, response
138
+ fragment, URL, credential, token, diagnostics, or any host-supplied string into it. Bind the selected
139
+ reason as `PRE_QUOTING_REASON` and transport it as the sole `--reason` argument with the deterministic
140
+ POSIX single-quote encoding defined under Publishing identity enforcement; the encoded token is never
141
+ persisted.
142
+
143
+ | branch token | exact persisted reason template |
144
+ |---|---|
145
+ | `NON_RETRYABLE_REASON` | `specialist invocation failed with a non-retryable error for <role> on <subject>; inspect the host invocation log, then prove execution never started or recover the same invocation before continuing` |
146
+ | `UNKNOWN_OUTCOME_REASON` | `specialist infrastructure outcome unknown for <role> on <subject>; prove execution never started or recover the same invocation before continuing` |
147
+ | `SECOND_FAILURE_REASON` | `specialist infrastructure failed twice consecutively for <role> on <subject>; after provider or network recovery, prove execution never started or recover the same invocation before continuing` |
148
+
149
+ `<role>` is the exact canonical dispatch target, never agent frontmatter or host/provider text. Select
150
+ `<subject>` from this closed map:
151
+
152
+ | role | canonical subject |
153
+ |---|---|
154
+ | `story-reader`, `story-writer` | `stage:story` |
155
+ | `codebase-researcher` | `stage:research` |
156
+ | `design-interpreter` | `stage:design` |
157
+ | `spec-writer` | `step:spec-writer` |
158
+ | `work-decomposer` | `step:work-decomposer` |
159
+ | `backend-builder`, `frontend-builder` | `slice:<slice-id>` |
160
+ | `test-verifier` | `step:test-verifier` |
161
+ | `implementation-validator` | `stage:implementation-validator` |
162
+ | `work-reviewer` | the exact reviewed subject: `step:spec-writer`, `step:work-decomposer`, `slice:<slice-id>`, or `step:test-verifier` |
163
+
164
+ For a reason template, render `<slice-id>` from at most the first 80 ASCII characters of the schema-valid
165
+ persisted slice ID and append `~` when truncated. The in-memory invocation key still uses the exact full
166
+ role, subject, and persisted attempt when one exists. Constants come only from the table and slice IDs
167
+ come only from `run.json`; neither field comes from host or provider diagnostics.
168
+
169
+ If an excluded or non-transport failure returns no complete response, preserve the persisted attempt when
170
+ one exists, create no attempt for unbudgeted work, and take the common infrastructure-park sequence with
171
+ `NON_RETRYABLE_REASON`. Do not retry it or treat it as rejected work.
172
+
173
+ For each active invocation key — exact specialist role, exact workflow subject or slice, and the current
174
+ persisted attempt number when that subject is budgeted — hold an in-memory consecutive-infrastructure-
175
+ failure count. Start it at zero in every new driver invocation, including after resume; never write it to
176
+ `run.json` or any artifact. A complete specialist response or a non-infrastructure outcome for that same
177
+ key resets it to zero. Activity for another key neither combines with nor resets it.
178
+
179
+ On the first confirmed failure for a key, increment only that in-memory count. Keep the control plane
180
+ unchanged: do not issue any step or slice transition with `--attempts N+1`, do not observe or review
181
+ partial output, and do not record `accepted`, `rejected`, or `blocked`. Continue automatically only
182
+ through one of these two safe paths:
183
+
184
+ 1. When trusted host metadata proves execution never started, re-dispatch the exact same role, subject,
185
+ inputs and, when budgeted, the persisted attempt number.
186
+ 2. When execution started or may have started, recover and continue the same host dispatch or child
187
+ session by its existing host-owned identity. First inspect that same child and its expected artifact or
188
+ worktree; never create a second child for the logical attempt and never repeat successful siblings in a
189
+ parallel wave.
190
+
191
+ If neither path is available, preserve the persisted attempt when one exists, create none for unbudgeted
192
+ work, and take the common infrastructure-park sequence with `UNKNOWN_OUTCOME_REASON`. An unbudgeted
193
+ research or design call still permits at most one safe same-invocation recovery and creates no durable
194
+ progress record.
195
+
196
+ On the second consecutive confirmed failure for the same key during that safe re-dispatch or recovery,
197
+ do not invoke it again. Take the common infrastructure-park sequence with `SECOND_FAILURE_REASON`.
198
+
199
+ The failure count remains invocation-local: every new driver, including one entered after explicit resume,
200
+ starts it at zero. That reset is not proof that prior execution did not start. When the preserved historical
201
+ terminal reason matches any infrastructure reason template, it guards the first later dispatch of the
202
+ named canonical role and subject at the retained attempt, or the named unbudgeted stage. Before that
203
+ dispatch, trusted host metadata must prove the prior invocation never started, or the driver must recover
204
+ and inspect that same host dispatch or child identity and its expected artifact or worktree. Explicit
205
+ resume, `status.next`, provider recovery, the reset count, and an operator assertion alone establish
206
+ neither fact. If neither safe path is available, do not dispatch; re-enter the common infrastructure-park
207
+ sequence with `UNKNOWN_OUTCOME_REASON`. Never repeat successful siblings.
208
+
209
+ A budgeted attempt advances only after a complete specialist response reaches the ordinary workflow and
210
+ that response is rejected on its merits or violates the specialist's required output contract. A complete
211
+ unbudgeted result returns to its ordinary workflow without creating an attempt. Infrastructure recovery is
212
+ the same attempt, not another use of `max_retries`. For a slice, an output-contract violation may advance
213
+ only when canonical evidence and its matching REJECT review were recorded; if the malformed output prevents
214
+ either record, park top-level `needs-human` through the common procedure instead of fabricating authority for
215
+ N+1. This rule is instruction rather than CLI enforcement: the host owns specialist invocation errors, while
216
+ the CLI continues to enforce every durable attempt transition the driver actually records.
217
+
218
+ ### Operator-authorized retry extension
219
+
220
+ A slice that reached its effective retry limit remains terminal until an operator explicitly grants exactly
221
+ one more attempt. Never edit `run.json`. Keep the top-level run in its existing parked state, claim and verify its
222
+ fresh session lock, record a concrete reason why N+1 is now bounded and materially different, and choose one
223
+ scope deliberately:
224
+
225
+ ```sh
226
+ factory grant-retry "$R" "$SLICE_ID" --scope slice --reason "$EXTENSION_REASON" --session "$SESSION_ID" --repo "$RUN_REPO"
227
+ factory grant-retry "$R" "$SLICE_ID" --scope all --reason "$EXTENSION_REASON" --session "$SESSION_ID" --repo "$RUN_REPO"
228
+ ```
229
+
230
+ `slice` raises only that slice's additive allowance. `all` raises the run-wide default, including every
231
+ pending later wave, but still reopens only `SLICE_ID`; it refuses while another slice is blocked or an
232
+ exhausted post-merge repair exists. Both scopes require `blocked@N` exactly at the current effective limit;
233
+ a matching REJECT, evidence, immutable base and live clean branch head; the exact fresh lock owner; a
234
+ complete current park snapshot; and immutable attempt-N review and evidence archives. A legacy run missing
235
+ an archive gets a preparation-only refusal: publish the changed plane and invoke the grant again. They
236
+ append the durable authorization, preserve worktree, branch and `base_ref`, clear only the live attempt-bound
237
+ refs, and record `running@(N+1)` while top-level status and `terminal_result` remain parked.
238
+
239
+ The grant atomically moves the old canonical snapshot away so restore cannot recover pre-grant authority.
240
+ Do not dispatch or resume yet. Republish the updated live plane, then require qualified status
241
+ to report the refreshed `park_snapshot`, unchanged owner, the chosen new effective limit, and only the named
242
+ slice at `running@(N+1)`. Only then run the ordinary explicit
243
+ `factory resume "$R" --session "$SESSION_ID" --repo "$RUN_REPO"` and dispatch that attempt. Resume refreshes
244
+ the staged workflow before its final snapshot check. A grant never invokes a specialist, unlocks, resumes,
245
+ approves, merges, or publishes.
246
+
92
247
  ## The chain
93
248
 
94
249
  ```
@@ -255,6 +410,12 @@ sandbox: the completed handoff is the only thing that archives it, and that hand
255
410
  `completed`. So anything that removed the sandbox destroyed the manifest, the approved gates, the ratified
256
411
  plan and every review verdict, leaving the run neither resumable nor reconstructable.
257
412
 
413
+ `factory snapshot "$R" --repo "$O" --json` performs this publication and is the supported way to do it.
414
+ The steps below remain the definition of what it does; a driver may run the command instead of carrying
415
+ them out itself, and a supervisor that parked a run out-of-band **must** run it, because `factory
416
+ terminal` alone completes step 1 of the park and leaves no recovery evidence. It refuses a run that is
417
+ not parked, so it cannot record a live plane as a snapshot of a moment no resume can return to.
418
+
258
419
  Publish the live plane `P` to `$O/.factory/.parked/$R`. Inspect `$O/.factory` and `$O/.factory/.parked` with
259
420
  non-following metadata reads, creating each missing parent one directory at a time and requiring any present
260
421
  one to be a real directory rather than a symbolic link. Never write through a symlinked parent, and never
@@ -272,12 +433,11 @@ and "clean up the prior copy" are contradictory instructions once that rename ha
272
433
  outside `P`; do not copy slice worktrees or any other part of `S`.
273
434
  3. **Verify.** Build source and destination inventories exactly as the completed archive does — every
274
435
  entry's relative path, type and mode, a SHA-256 for each regular file, a link target for each symlink,
275
- sorted lexically — and require exact equality, **excluding the plane-root `factory.lock` only**. That
276
- one entry is session liveness rather than run state and is the only thing in the plane designed to
277
- change on a timer, so comparing it fails whenever a heartbeat lands between reading the source and
278
- reading the copy. The exclusion is that exact path and nothing else: a `factory.lock` anywhere below
279
- the plane root is run state and must match. Qualified status excludes the same single path for the same
280
- reason. An unverified staging tree is never published.
436
+ sorted lexically — and require exact equality, excluding only plane-root `factory.lock` and
437
+ `run-json.lock`. The first is session liveness and can change on a timer; the second is held by the
438
+ snapshot command to serialize publication with state transitions. The exclusions are those exact root
439
+ paths and nothing else: either name below the plane root is run state and must match. Qualified status
440
+ applies the same exact exclusions at a transition boundary. An unverified staging tree is never published.
281
441
  4. **Commit.** With no snapshot at the canonical path, rename `.staging-$R` onto it; that rename is the
282
442
  commit point. With one present, first rename the canonical snapshot to `.prior-$R`, then rename
283
443
  `.staging-$R` onto the canonical path; that second rename is the commit point. If the first rename
@@ -304,9 +464,16 @@ Three properties make this safe to do at a park rather than only at completion:
304
464
  failed would leave `status: running` with nothing alive, which every health signal misreads — a worse
305
465
  outcome than a missing snapshot.
306
466
 
307
- A snapshot is evidence for recovery, not a resumable run: resume operates on the sandbox manifest. Do not
308
- publish one for `blocked` or `partial`, which are not resumable, and never treat a snapshot as authority
309
- over the live plane.
467
+ A snapshot is a restore input, not a live run: resume still operates only on a sandbox manifest. While `S`
468
+ exists, never restore over it or treat the snapshot as authority over the live plane. If `S` is lost, fetch
469
+ the feature branch still advertised by the operator effective push endpoint into a full remote-tracking ref whose suffix is the recorded branch, then run
470
+ `factory restore "$R" --repo "$O" --from "$RESTORE_REF" --json`. Restore creates only the exact derived
471
+ sandbox, remains parked and lockless, aligns and rechecks the operator's effective push target, omits the
472
+ old plane-root lock, proves every preserved merged-slice Git and evidence binding, resets unrecoverable nonmerged slices to `pending`, and reports `reset_slices` and `invalidated`. It omits prior-generation
473
+ canonical verifier records and always invalidates Gate 3 and test-verifier state. It records its source inventory and restored commit at `status.restore`; `park_snapshot` becomes `null` because the new
474
+ generation is intentionally not byte-identical to its source. Review those losses, bind `RUN_REPO` to the
475
+ returned sandbox, and only then enter the ordinary claim-and-resume order. Do not publish snapshots for
476
+ `blocked` or `partial`, which are not resumable.
310
477
 
311
478
  At every interactive gate, `changes: <feedback>` records `changes`, follows
312
479
  `changes-at-gate:<name>`, revises only the affected stage, and re-presents it pending. `stop` requires
@@ -376,10 +543,13 @@ The optional repository-owned file is `$O/.factory.json`:
376
543
  }
377
544
  ```
378
545
 
379
- The root must be a JSON object with the three required own properties `resolve`, `verify`, and `publish`,
380
- plus only the optional own properties `pr_draft`, `verify_timeout_ms`, `bootstrap`, and
546
+ The root must be a JSON object with the two required own properties `resolve` and `verify`,
547
+ plus only the optional own properties `publish`, `pr_draft`, `verify_timeout_ms`, `bootstrap`, and
381
548
  `bootstrap_timeout_ms`. `resolve`, `verify`, `publish`, and `bootstrap` are command strings; every present
382
- command must be non-empty. There is no `publishing_identity` key: the account a run publishes as is a
549
+ command must be non-empty. `publish` was required and invoked nowhere until this release, so every
550
+ consumer wrote a command that could not run. It is optional now and contributes only the file candidate
551
+ to the one Step 6 publishing selection. Inherited `FACTORY_PUBLISHING_COMMAND` or the default may win, so
552
+ presence alone never executes this entry. There is no `publishing_identity` key: the account a run publishes as is a
383
553
  property of the environment it runs in, not of the repository, and a tracked file cannot hold two values
384
554
  for one repository published from both a maintainer's checkout and an automated host. A file carrying that
385
555
  key is malformed, because the optional set above is closed. `pr_draft` must be a JSON boolean
@@ -388,7 +558,7 @@ safe integers when present, and `bootstrap_timeout_ms` is valid only with a decl
388
558
  `verify_timeout_ms` and `bootstrap_timeout_ms` each independently default to `900000` milliseconds;
389
559
  neither timeout shares or consumes the other's budget.
390
560
 
391
- Validation refuses the first matching defect in this order: unreadable or invalid JSON, a non-object root, or unknown keys; invalid `pr_draft`; invalid `bootstrap`; `bootstrap_timeout_ms` without `bootstrap`; invalid `bootstrap_timeout_ms`; invalid `verify_timeout_ms`; then missing or invalid required entries.
561
+ Validation refuses the first matching defect in this order: unreadable or invalid JSON, a non-object root, or unknown keys; invalid `pr_draft`; invalid `bootstrap`; `bootstrap_timeout_ms` without `bootstrap`; invalid `bootstrap_timeout_ms`; invalid `verify_timeout_ms`; missing or invalid required entries; then invalid `publish`.
392
562
 
393
563
  Do not use the obsolete summary “Validation refuses the first matching defect in this order: unreadable or invalid JSON, a non-object root, or unknown keys; invalid `bootstrap`; `bootstrap_timeout_ms` without `bootstrap`; invalid `bootstrap_timeout_ms`; invalid `verify_timeout_ms`; then missing or invalid required entries.” because it omits the earlier `pr_draft` check.
394
564
 
@@ -516,7 +686,7 @@ Do not create, write, merge, archive, or package `.factory.json`. It remains ope
516
686
  committed, so every clone and sandbox carries it, and refused by the privileged-path policy, so a run
517
687
  cannot widen its own configuration. It lived under the gitignored `.factory/` run directory until that proved unusable —
518
688
  `.factory/` is gitignored, so the file could not be committed and never reached a sandbox clone, which
519
- made the `verify` and unconsumed `publish` entries impossible and left this repository unable to resolve
689
+ made the `verify` and `publish` entries unavailable and left this repository unable to resolve
520
690
  a reference from a fresh checkout. For configured resolver execution, add no helper module,
521
691
  command runner, parser service, plugin bridge, transport, protocol, or CLI command. Add no resolver
522
692
  cache, payload handoff, manifest or session
@@ -534,11 +704,14 @@ separate capture policy, output channel, buffering, truncation, redaction, outpu
534
704
  retry, or fallback after any configured resolver result or failure. The verify timeout and bounded retry
535
705
  below apply only to repository `verify` shell attempts; the bootstrap timeout applies only to CLI-owned
536
706
  init and explicit resume. Neither applies to `resolve`, slice observation, or Gate 3 commands. Do not
537
- change platform placement, background-tool, title-association, host-session, or publication behavior.
707
+ change platform placement, background-tool, title-association, or host-session behavior. Publication changes only through the optional Step 6 command below.
538
708
  `story-reader` remains lookup-free and capability-free beyond its existing generic read tools.
539
709
 
540
710
  `resolve` and `verify` are consumed now, and the run's recorded `publishing_identity` is compared at the
541
- guards below. Configured `publish` remains unconsumed and is not invoked.
711
+ guards below. Step 6 resolves exactly one publishing selection from inherited
712
+ `FACTORY_PUBLISHING_COMMAND`, the optional `publish` entry, or the default, in the precedence defined
713
+ below. Only a selected nondefault command replaces the driver's `gh pr create`; the factory-owned exact
714
+ push and post-push identity guard remain unchanged.
542
715
 
543
716
  Configured `bootstrap` is consumed only by CLI-owned fresh init and explicit resume; the workflow consumer validates it but never executes it itself.
544
717
 
@@ -548,7 +721,7 @@ Effective push-target capture and comparison are active through the package-owne
548
721
  |---|---|---|---|---|
549
722
  | `bootstrap` | Exact configured string as one shell command with `shell: true`, inherited environment and stdin, cwd exactly the selected sandbox, and child stdout and stderr both routed to CLI stderr. Each execution receives its own `bootstrap_timeout_ms`, independently `900000` when omitted. | Numeric exit status or unavailable `null`; output is visible on CLI stderr and never parsed | Clean zero succeeds; dirty or unobservable tracked state outranks unavailable or nonzero exit | Invoked by the CLI once during configured fresh init and again on every explicit configured resume; never invoked by resolver, merge verification or replay, direct repository verification, slice or Gate 3 observation, effective push, or publication. |
550
723
  | `verify` | Ordinary shell step in the exact integration-worktree cwd with inherited environment; no structured stdin or factory-specific payload is defined. Each attempt receives the full configured `verify_timeout_ms`, silently `900000` when omitted. | Exit status is authoritative; stdout and stderr are inherited, informational, and unparsed | Zero means success; non-zero means repository verification failed; no numeric child status means unavailable | Invoked after each newly recorded merge through `observe --repository-verify`, with at most two executions in that merge invocation. The timeout and retry never apply to resolver, slice, or Gate 3 commands. |
551
- | `publish` | Future ordinary shell step in repository-root cwd with inherited environment; no structured stdin or factory-specific payload is defined | Exit status is authoritative; stdout is informational and unparsed | Zero means the command reported success; non-zero means it reported failure | Not invoked. Existing `git push`, `gh pr create`, and `factory pr` behavior remains unchanged; effective push-target equality is enforced separately by <code>factory effective-push</code>. |
724
+ | `publish` | Optional file candidate for the one Step 6 publishing selection. A nonblank inherited `FACTORY_PUBLISHING_COMMAND` selects its exact string; the same variable set empty or to whitespace selects the default; when the variable is unset this entry is selected if present, otherwise the default. A selected nondefault command runs as one shell step in `RUN_REPO` cwd, with no stdin or positional arguments and inherited environment plus exact `PR_BASE`, `FEATURE_BRANCH`, `PR_DRAFT`, `PR_TITLE`, and absolute `PR_BODY_FILE`. | Exit status is authoritative; the last nonempty stdout line must be an absolute HTTPS URL and becomes `PR_URL` | Zero plus that URL is recordable; any other result is indeterminate and parks before `factory pr` | The resolved selection replaces only `gh pr create`, after the factory-owned exact push and post-push identity guard. `factory pr` is unchanged and still records the URL. |
552
725
  | `publishing_identity` | No runtime input; read the value `status` reports for the run, recorded at init from `--publishing-identity` or the inherited `FACTORY_PUBLISHING_IDENTITY` | Exact case-sensitive string compared with the observed login | Absent at init refuses before any sandbox exists; mismatch or unobservable identity parks the run | Active at the three mandatory guards below; only a manifest written before 0.8.0 can report `null` and skip them. |
553
726
 
554
727
  When both bootstrap keys are absent, init and resume are exact no-ops for bootstrap: no execution, manifest fields, output, or response-shape change.
@@ -656,7 +829,7 @@ intervene between the verified running/same-owner result and that guard, or betw
656
829
  and reconciliation. A pre-0.8.0 manifest reporting `null` preserves the nine orders without adding an operation.
657
830
  The refreshed workflow read belongs to order 7 verification, before this boundary.
658
831
 
659
- For order 1 require the intended run ID, a valid manifest, recorded branch and mode, current parked status, and the original terminal result. Order 2 stays after selection and containment and before effective-push proof. Order 3 never absorbs containment, binding, or the post-selection exact-ref guard. During order 4 preserve every existing exact-ref recheck and the stated provenance sequence. No unrelated observation or effect occurs between order 5 and claim or justified steal. Order 6 requires `lock_session === SESSION_ID`, a fresh lock, unchanged parked status, and a terminal result deeply equal to the one first observed. Invoke `factory resume "$R" --session "$SESSION_ID" --repo "$RUN_REPO"` for order 7 — the same session order 6 just verified as the fresh owner — then require that owner unchanged. Resume refuses without it, and refuses a lock that is absent, stale, or held by anyone else. Order 8 may replay only the existing recorded-merge reconciliation path and must not move pre-lock proofs across the lock boundary. Order 9 never uses the pre-resume observation or the stop reason.
832
+ For order 1 require the intended run ID, a valid manifest, recorded branch and mode, current parked status, and the original terminal result. Order 2 stays after selection and containment and before effective-push proof. Order 3 never absorbs containment, binding, or the post-selection exact-ref guard. During order 4 preserve every existing exact-ref recheck and the stated provenance sequence. No unrelated observation or effect occurs between order 5 and claim or justified steal. Order 6 requires `lock_session === SESSION_ID`, a fresh lock, unchanged parked status, and a terminal result deeply equal to the one first observed. Invoke `factory resume "$R" --session "$SESSION_ID" --repo "$RUN_REPO"` for order 7 — the same session order 6 just verified as the fresh owner — then require that owner unchanged. Resume refuses without it, and refuses a lock that is absent, stale, or held by anyone else. Order 8 may replay only the existing recorded-merge reconciliation path and must not move pre-lock proofs across the lock boundary. Order 9 uses only the newly qualified next action for workflow progress, but before its first matching specialist dispatch it must apply the preserved infrastructure-reason guard above; it never treats explicit resume or the count reset as no-start proof.
660
833
 
661
834
  If resume refuses after claim or the run later reparks, quiesce builders, tools, specialist tasks, and
662
835
  heartbeat loops. Qualify the intended retained run again before reporting the stop. If it is still parked
@@ -1276,11 +1449,16 @@ For a fresh pending slice, set the exact names, require both `refs/heads/$SLICE_
1276
1449
  `SLICE_WORKTREE` path to be absent, and create the worktree from the current feature branch before
1277
1450
  activation:
1278
1451
 
1452
+ Before creating a pending slice worktree, reload its exact manifest row. Bind `ACTIVATION_START` to
1453
+ `FEATURE_BRANCH` when `base_ref` is null. When a restored retry-extension row preserves non-null `base_ref`,
1454
+ require its latest audit to name the same base and start the replacement slice branch at that exact historical
1455
+ base. This recreates the original retry branch without importing later sibling changes into its owned diff.
1456
+
1279
1457
  ```sh
1280
1458
  SLICE_BRANCH="factory/$R/$SLICE_ID"
1281
1459
  SLICE_WORKTREE="$SLICE_ROOT/$SLICE_ID"
1282
1460
  CHECKED_OUT_FEATURE_BRANCH="$(git -C "$INTEGRATION_WORKTREE" symbolic-ref --quiet --short HEAD)"
1283
- git -C "$RUN_REPO" worktree add -b "$SLICE_BRANCH" "$SLICE_WORKTREE" "$FEATURE_BRANCH"
1461
+ git -C "$RUN_REPO" worktree add -b "$SLICE_BRANCH" "$SLICE_WORKTREE" "$ACTIVATION_START"
1284
1462
  $ factory slice "$R" "$SLICE_ID" running --worktree "$SLICE_WORKTREE" --branch "$SLICE_BRANCH" --repo "$RUN_REPO"
1285
1463
  ```
1286
1464
 
@@ -1294,6 +1472,8 @@ Step 0. Require `run_id === R`, select exactly one `slices` row with `id === SLI
1294
1472
 
1295
1473
  ```text
1296
1474
  RECORDED_SLICE = parsedRun.slices row whose id equals SLICE_ID
1475
+ MAX_RETRIES = parsedRun.max_retries
1476
+ SLICE_RETRY_LIMIT = MAX_RETRIES + (RECORDED_SLICE.extra_attempts when present, otherwise 0)
1297
1477
  SLICE_WORKTREE = RECORDED_SLICE.worktree
1298
1478
  SLICE_BRANCH = RECORDED_SLICE.branch
1299
1479
  SLICE_BASE_REF = RECORDED_SLICE.base_ref
@@ -1307,13 +1487,15 @@ for it. A driver that assumes "this is the first try" observes as attempt 1 whil
1307
1487
  merge then refuses that evidence — `evidence '…' is for attempt 1, slice is at attempt 2` — after the build
1308
1488
  and the review have already been spent. It names the report and the `--attempt` argument below.
1309
1489
 
1310
- Require the row status to be `running` or `review`, every bound value to be non-null, `SLICE_ATTEMPT` to be
1311
- a positive integer, `SLICE_BASE_REF` to
1312
- be a 40-character commit SHA, `SLICE_BRANCH` to equal `factory/R/<slice-id>`, and the physical
1313
- `SLICE_WORKTREE` to equal `SLICE_ROOT/<slice-id>`. Require `git -C "$RUN_REPO" worktree list
1314
- --porcelain` to associate that physical path with that exact branch. A pending slice requires both path
1315
- and ref to remain absent; an unrecorded existing path or ref is a collision. Refuse every mismatch
1316
- instead of repairing, deleting, or reassociating it. A merged slice is never dispatched again.
1490
+ Require every bound value to be non-null, `MAX_RETRIES`, `SLICE_RETRY_LIMIT`, and `SLICE_ATTEMPT` to be positive integers,
1491
+ `SLICE_BASE_REF` to be a 40-character commit SHA, `SLICE_BRANCH` to equal `factory/R/<slice-id>`, and the
1492
+ physical `SLICE_WORKTREE` to equal `SLICE_ROOT/<slice-id>`. Require `git -C "$RUN_REPO" worktree list
1493
+ --porcelain` to associate that physical path with that exact branch. Before dispatch or observation require
1494
+ status `running`. A `review` row is a completed decision checkpoint: on resume consume its recorded review
1495
+ through step 4, never re-observe it. A pending slice requires path and branch ref to remain absent. Its
1496
+ base is absent unless a restored retry-extension audit preserves that immutable base; any unrecorded path
1497
+ or branch ref is a collision. Refuse every mismatch instead of repairing, deleting, or
1498
+ reassociating it. A merged slice is never dispatched again.
1317
1499
 
1318
1500
  For a non-empty `SLICE_TEST_PLAN`, select one complete entry and bind `SLICE_TEST_COMMAND` by copying
1319
1501
  that persisted string verbatim. Never shorten, append to, normalize, or source it from the mutable
@@ -1367,31 +1549,32 @@ Per slice:
1367
1549
  is no repair available at this step.** The slice is already activated, so `base_ref` is fixed; the suite
1368
1550
  runs in `SLICE_WORKTREE`, so a commit on the integration branch is invisible to the re-observation; and
1369
1551
  bringing that commit into the slice would put an out-of-lane test path in the observed diff, which the
1370
- merge refuses. Mark the slice `blocked`, stop dispatching its dependents, and follow the wave rule below
1371
- — the slices that did merge are retained on the integration branch in the retained sandbox
1372
- rather than discarded. A `partial` run is **surfaced, not published**: Gate 3 refuses the
1373
- approval that authorizes publication unless every slice is `merged`, so an operator decides
1374
- what to do with the merged work rather than a PR appearing for a plan that did not finish.
1552
+ merge refuses. This is a ratified-plan conflict, not a merit REJECT: do not mark the slice `blocked`
1553
+ without canonical evidence and a matching max-attempt review. Stop dispatching its dependents and park
1554
+ top-level `needs-human` through the common procedure with the diagnosis below. Retain merged siblings on
1555
+ the integration branch and the whole sandbox for operator replanning. The parked run is **surfaced, not
1556
+ published**: Gate 3 refuses the approval that authorizes publication unless every slice is `merged`, so
1557
+ an operator decides what to do with the work rather than a PR appearing for a plan that did not finish.
1375
1558
 
1376
1559
  **Never narrow the ratified command to get past this.** `factory observe` compares the raw supplied
1377
1560
  slice command with the persisted ratified entries before tokenization or execution and refuses a
1378
- shortened, appended, or normalized command without writing evidence. A narrowed command is a false green wearing evidence's clothes; blocking is the honest outcome when the verbatim command fails.
1561
+ shortened, appended, or normalized command without writing evidence. A narrowed command is a false green wearing evidence's clothes; parking for replanning is the honest outcome when the verbatim command fails.
1379
1562
 
1380
1563
  If the same incompatibility instead first appears in the **integrated** suite, this step is not involved
1381
1564
  at all — Step 5's NO-GO repair owns it, on the branch where that suite actually runs.
1382
1565
 
1383
- When you block, record the **diagnosis** and not just the failure, in the terminal transition's
1384
- `--reason`: which slice owns the test, which assertion cannot hold, and what would make it hold. A
1385
- reason naming only "tests failed" makes the operator repeat the whole investigation, which is the
1386
- difference between their fix being one commit and being an afternoon.
1566
+ When you park this plan conflict, record the **diagnosis** and not just the failure in the park
1567
+ terminal transition's `--reason`: which slice owns the test, which assertion cannot hold, and what would
1568
+ make it hold. A reason naming only "tests failed" makes the operator repeat the whole investigation,
1569
+ which is the difference between their fix being one commit and being an afternoon.
1387
1570
 
1388
1571
  An out-of-lane **production** change is a different thing entirely and follows **Ownership disclosure**
1389
1572
  below, where the reviewer decides whether the plan or the change is wrong.
1390
1573
  4. **Review** — `work-reviewer` with subject `<slice-id>`, the observed evidence, the slice spec, and
1391
1574
  the brief. Record both refs — the merge requires each:
1392
1575
  ```sh
1393
- $ factory slice "$R" "$SLICE_ID" review --evidence-ref "evidence/$SLICE_ID.json" \
1394
- --review-ref "reviews/$SLICE_ID.json" --repo "$RUN_REPO"
1576
+ $ factory slice "$R" "$SLICE_ID" review --attempts "$SLICE_ATTEMPT" \
1577
+ --evidence-ref "evidence/$SLICE_ID.json" --review-ref "reviews/$SLICE_ID.json" --repo "$RUN_REPO"
1395
1578
  ```
1396
1579
  - On REJECT, before spending an attempt, identify the cause of the remaining failures. If the fix would
1397
1580
  violate an approved story or brief constraint, or repeated findings trace to the same unresolved
@@ -1401,8 +1584,26 @@ Per slice:
1401
1584
  If no such target can be identified, park through the existing parked-stop procedure for replanning
1402
1585
  or operator clarification; preserve the work and do not silently change approved acceptance criteria.
1403
1586
  Unchanged finding counts alone are not a stall: progress can occur within a category that remains open.
1404
- Otherwise route the fixes back to that builder and re-observe. After `max_retries`, mark the slice
1405
- `blocked` and stop dispatching its dependents.
1587
+ Only a complete merit REJECT spends an attempt; infrastructure recovery and resume preserve
1588
+ `SLICE_ATTEMPT` under the common rules above. Reload `RUN_MANIFEST`, require the recorded review to name
1589
+ this slice and `SLICE_ATTEMPT` with exact verdict `REJECT`, and require the row still to be `review`.
1590
+ If `SLICE_ATTEMPT >= SLICE_RETRY_LIMIT`, record the terminal slice state and stop dispatching its
1591
+ dependents without creating another attempt:
1592
+ ```sh
1593
+ $ factory slice "$R" "$SLICE_ID" blocked --attempts "$SLICE_ATTEMPT" --repo "$RUN_REPO"
1594
+ ```
1595
+ Then enter the common parked-stop procedure with exact reason `blocked-after-retries`; do not
1596
+ terminalize `partial`. The parked snapshot and retained lock are what make a later audited grant
1597
+ reachable without weakening the terminal slice transition.
1598
+ Otherwise bind `NEXT_SLICE_ATTEMPT = SLICE_ATTEMPT + 1` and, before redispatch, record:
1599
+ ```sh
1600
+ $ factory slice "$R" "$SLICE_ID" running --attempts "$NEXT_SLICE_ATTEMPT" --repo "$RUN_REPO"
1601
+ ```
1602
+ This retry-opening command takes no worktree, branch, evidence, or review flag. Reload the row and
1603
+ require status `running`, attempt `NEXT_SLICE_ATTEMPT`, unchanged worktree, branch, and exact
1604
+ `SLICE_BASE_REF`, and null evidence and review refs. The base is the immutable original branch point,
1605
+ not merely any ancestor and not the integration head after sibling merges. Then set `SLICE_ATTEMPT` to
1606
+ the recorded new value, route the bounded fixes back to that builder, and re-observe.
1406
1607
  5. **Merge (you, serially)** — on APPROVE, merge the slice branch into the feature branch one at a
1407
1608
  time. Builds are concurrent; merges are single-writer, which is what makes the parallelism safe.
1408
1609
  ```sh
@@ -1720,10 +1921,12 @@ content on those paths matches what was reviewed, so unreviewed content inside *
1720
1921
  while movement around it is not. What guards the branch as a whole is the integration pass: the
1721
1922
  validator judges the whole diff and Gate 3 will not approve unless the head it judged is still the head.
1722
1923
 
1723
- Advance waves until all slices are `merged`, or a slice is `blocked`. If some merged and others
1724
- blocked, the run is `partial` — surface it at the next gate rather than pushing on. Record a terminal
1725
- decision only through the checked terminal command.
1726
- Use terminal needs-human only to park a running envelope; use explicit factory resume after the cause is fixed.
1924
+ Advance waves until all slices are `merged`, or a slice is `blocked`. A blocked slice stops the wave and
1925
+ enters the common parked-stop procedure as top-level `needs-human`; retry exhaustion never terminalizes the
1926
+ run as `partial`. Use explicit factory resume only after the cause is fixed or a qualified retry grant was
1927
+ recorded and the updated plane was published. Use terminal needs-human only to park a running envelope; use explicit factory resume after the cause is fixed.
1928
+ A `partial` run is **surfaced, not published** when some other checked terminal cause creates one; retry
1929
+ exhaustion uses the parked path above instead.
1727
1930
  A top-level needs-human sandbox stays retained while parked and continues only after explicit factory resume.
1728
1931
  A `blocked` or `partial` sandbox run retains `RUN_REPO`; stale nonterminal locks retain it
1729
1932
  too. Nothing removes any of those sandboxes automatically. Legacy runs
@@ -1757,16 +1960,27 @@ HEAD, a branch name, or an unpersisted variable.
1757
1960
  is not a slice and has no slice `test_plan`, so it continues to supply its integration command. There
1758
1961
  is no waiver: the stage exists to run the tests, so the evidence must record an observed run that
1759
1962
  exited zero, against the integration head as it stands. Then `work-reviewer` confirms each criterion
1760
- maps to a real assertion.
1963
+ maps to a real assertion, judging the whole integrated diff rather than any one slice's.
1964
+ On a single-slice run this is the last review before publication, and its production findings have no
1965
+ in-band repair: post-merge repair is test-only and a merged slice is never dispatched again, so a
1966
+ production defect parks the run for an operator instead of returning to a builder. Review it as the
1967
+ final reading it is.
1761
1968
  This Gate 3 observation is always fresh and independent in the ordinary path. It uses the existing
1762
1969
  argv-tokenized `--test-cmd` path and overwrites canonical evidence at the current head. The sole
1763
1970
  substitution is a qualifying explicit repair re-verification pass at current HEAD under Gate 3's
1764
1971
  complete inventory rules below; failed ordinary evidence remains preserved rather than overwritten.
1765
1972
  2. `implementation-validator` — the holistic pass across the whole diff, complementing per-slice
1766
1973
  reviews. **Skip it when the run has exactly one slice**: its subject is the interaction *between*
1767
- slices, and with one there is none, so it re-reads the diff the slice reviewer just approved —
1768
- a serialized pass on the critical path for no new information. Gate 3 does not require a verdict
1769
- for a single-slice run. Run it for every multi-slice run; the gate refuses without it.
1974
+ slices, and with one there is none, so it has no question of its own to answer. Gate 3 does not
1975
+ require a verdict for a single-slice run. Run it for every multi-slice run; the gate refuses
1976
+ without it.
1977
+
1978
+ **Skipping it does not mean the integrated diff goes unread**, and nothing here should be read as
1979
+ saying a second reading is worthless. Step 1's `work-reviewer` pass over `test-verifier` judges that
1980
+ diff whole, and on a single-slice run it is the only review after the slice's own: mimir's
1981
+ chainlink-1304 merged its one slice clean at zero findings, and that pass then found two production
1982
+ defects in it, both real and both confirmed. What the skip removes is a duplicate *verdict* on a
1983
+ subject that does not exist, not the reading.
1770
1984
 
1771
1985
  When you do run it, it returns GO / GO-WITH-NITS / NO-GO **and writes `reviews/implementation-validator.json`
1772
1986
  naming the commit it judged**, exactly like any other reviewer. Then:
@@ -1978,17 +2192,60 @@ gh api --method GET /user --jq .login
1978
2192
  factory pr "$R" --url "$PR_URL" --repo "$RUN_REPO"
1979
2193
  ```
1980
2194
 
1981
- The second observation runs only after that push is known successful and immediately before unchanged
1982
- `gh pr create`, with no intervening operation. Both Step 6 guards are skipped when `.factory.json` is
1983
- absent. A mismatch or unobservable result follows the common quiesce, park, durable-reason, owning
1984
- release, unlock-verification, retention, reporting, and later-driver procedure above. There is no
1985
- separate identity guard before `factory pr`; preserve that command and every existing publication mode,
1986
- status, and gate exactly.
1987
-
1988
- The `gh` call is the orchestrator's external effect; the package makes no forge call and `factory pr`
1989
- does not verify the forge's base. For a legacy manifest where `pr_base` is absent or null, stop and
1990
- require a human/operator to choose or confirm the exact target, then pass that value through
1991
- `gh pr create --base`. Never infer it from HEAD, the feature branch, repository or forge defaults, and
2195
+ The fully qualified `git push` above is factory-owned and unchanged for every resolved publishing
2196
+ selection. It is the only push in this procedure. The second identity observation always runs after that
2197
+ push is known successful and immediately before the selected pull-request operation, with no intervening
2198
+ operation.
2199
+
2200
+ **Resolve one publishing selection before running anything, and execute that selection rather than
2201
+ either source.** In order: inherited `FACTORY_PUBLISHING_COMMAND` holding at least one non-whitespace
2202
+ character selects that string; the same variable set empty or to whitespace selects the default, even
2203
+ when `$O/.factory.json` declares `publish`; an unset variable selects the configured `publish` when the
2204
+ file declares one; and with neither, the default. The resolution runs whether or not `$O/.factory.json`
2205
+ exists, so an environment-only override selects a command in a repository that declares none, and a
2206
+ declared `publish` is never executed while that variable holds a different value. Nothing downstream
2207
+ reads either source again.
2208
+
2209
+ The environment overrides the file because how a run publishes is a property of the environment as much
2210
+ as of the repository -- the same reason there is no `publishing_identity` key in that file, and one
2211
+ repository is published from both a maintainer's checkout and an automated host. Empty selecting the
2212
+ default is what keeps a repository from declaring its way into a run that cannot publish at all from a
2213
+ host with nothing to delegate to, and it makes empty and absent behave alike rather than needing two
2214
+ rules. The override removes no guard: every run with a non-null recorded `publishing_identity` runs all
2215
+ three identity guards whether or not `$O/.factory.json` exists. Only a legacy manifest reporting `null`
2216
+ skips them. Report which source the selection came from, since the sources are indistinguishable afterwards and
2217
+ an operator debugging a publication needs to know which one ran.
2218
+
2219
+ **When the resolution selects a command rather than the default, run that exact selected string instead
2220
+ of only `gh pr create` above**,
2221
+ as one shell command in `RUN_REPO` cwd with no stdin or positional arguments. Add exactly five values to
2222
+ the inherited environment: exact `PR_BASE`, exact `FEATURE_BRANCH`, `PR_DRAFT` as `true` or `false`, exact
2223
+ decorated `TITLE` as `PR_TITLE`, and an absolute `PR_BODY_FILE` naming the exact decorated body bytes.
2224
+ Read the last nonempty stdout line as `PR_URL` and require it to be an absolute HTTPS URL. The selected
2225
+ nondefault command owns PR creation, but these inputs preserve the recorded base, head, mode, title, and body intent;
2226
+ do not claim the factory verified that the command honored them. `factory pr` still records the returned
2227
+ URL. Exit zero **with** that absolute HTTPS URL is the only recordable result. A non-zero exit, or a zero
2228
+ exit whose last line is not a URL, is indeterminate: do not claim that no external effect occurred,
2229
+ do not run `factory pr`, and do not fall back to `gh pr create`. Bind `PRE_QUOTING_REASON` exactly to
2230
+ `selected publishing command outcome indeterminate; re-observe whether the pull request exists before retry`;
2231
+ persist no other reason text. Never append or interpolate stdout, stderr, exit status or status text, URLs,
2232
+ credentials, tokens, provider diagnostics, or any other command-supplied text. Follow the common quiesce,
2233
+ park, durable-reason transport, owning release, unlock-verification, retention, reporting, and later-driver
2234
+ procedure. Before any retry, re-observe whether the pull request exists and record an existing one rather
2235
+ than creating another.
2236
+
2237
+ When the recorded identity is non-null, both Step 6 identity guards run for every resolved selection and
2238
+ whether or not `.factory.json` exists; only a legacy recorded `null` skips them. A mismatch or unobservable
2239
+ result follows the common quiesce, park, durable-reason, owning release, unlock-verification, retention,
2240
+ reporting, and later-driver procedure above. There is no separate identity guard before `factory pr`;
2241
+ preserve that command and every existing publication mode, status, and gate exactly.
2242
+
2243
+ The selected PR-creation operation is the orchestrator's external effect; the package makes no forge call
2244
+ and `factory pr` does not verify the forge's base. For a legacy manifest where
2245
+ `pr_base` is absent or null, stop and require a human/operator to choose or confirm the exact target,
2246
+ then pass that value through `gh pr create --base` or the selected nondefault command's exact `PR_BASE`.
2247
+ Never
2248
+ infer it from HEAD, the feature branch, repository or forge defaults, and
1992
2249
  never backfill the legacy manifest.
1993
2250
 
1994
2251
  `pr_url` is immutable once recorded — a run has one PR, and overwriting the URL would hide a second
@@ -2159,6 +2416,12 @@ accepted NO-GO findings, recorded overrides, retained or residual sandboxes, or
2159
2416
 
2160
2417
  ## Resuming
2161
2418
 
2419
+ If the intended sandbox is absent but qualified operator inspection finds the canonical parked snapshot,
2420
+ restore it first using the exact procedure above. Restore is not resume: it leaves the new generation
2421
+ parked with no owner and may reset or invalidate state. Never substitute a local branch, a bare commit, a
2422
+ new run, or a hand-copied manifest for the full remote-tracking ref and CLI command. After restore, use its
2423
+ returned sandbox as `RUN_REPO`, inspect `status.restore`, and start the same ownership sequence below.
2424
+
2162
2425
  On invocation, if the run directory exists and you hold or steal the lock, the preserved compatibility
2163
2426
  claim reads “run `factory status <run-id> --json` and resume; never restart.” It names a non-runnable
2164
2427
  command stem. Execute only `factory status "$R" --json --repo "$RUN_REPO"`, then continue from `next`:
@@ -2183,11 +2446,11 @@ Never re-do a side effect the manifest shows already done — ticket creation, p
2183
2446
  Specialists are read-only toward them and builders write code only inside the worktree they receive.
2184
2447
  - **Never hand-write `run.json`.** If a `factory` command refuses a transition, the refusal is the
2185
2448
  answer; do not work around it by editing state.
2186
- - **Bounded loops.** `max_retries` per slice and per step, recorded as attempts. On exhaustion mark
2187
- `blocked` or `partial` with a reason and stop. A bounded loop parks top-level needs-human; explicit resume may repark it if the external cause remains unfixed.
2188
- Qualified status reports the run's `max_retries`, so the budget a run is actually bounded by is
2189
- observable rather than assumed: a forwarded `--max-retries` that never reached the manifest is visible
2190
- as a different number instead of silently running at the default.
2449
+ - **Bounded loops.** Each slice is bounded by `max_retries + extra_attempts`; each step uses
2450
+ `max_retries`. On slice exhaustion mark it `blocked` and park top-level needs-human; do not terminalize
2451
+ `partial` solely for retry exhaustion. Explicit resume may repark if the external cause remains unfixed.
2452
+ Qualified status reports the run's `max_retries` and each slice's effective `retry_limit`, so an
2453
+ extended budget is observed rather than assumed.
2191
2454
  - **Publish a PR and stop.** Never merge, force-push, or close tickets. Humans merge. Draft or
2192
2455
  ready-for-review is `pr_draft`'s decision, not this rule's.
2193
2456
  - **Scope discipline and no fabrication.** Flag out-of-scope work at the next gate. Never invent paths,
@@ -121,7 +121,7 @@ When the subject is a **class-wide** requirement — one that **cannot be establ
121
121
  docs-only slice reviewed against tests it was ratified not to have is rejected forever; that
122
122
  exemption is a plan decision, not yours to re-open here. It waives **test execution only**: the
123
123
  acceptance must still be implemented and the diff must still be observed.
124
- - **Test step (`test-verifier`):** each AC maps to a real assertion that would fail if the behavior broke; no test weakened to pass; the observed command is the suite the plan named and was not narrowed to exclude failures, which is a separate finding from weakening a test; observed test run is green. **There is no WRITTEN-NOT-RUN waiver for this subject:** the stage exists to run the tests, so the evidence must record an observed run that exited zero. Reporting WRITTEN-NOT-RUN honestly is valid; approving on it is not.
124
+ - **Test step (`test-verifier`):** each AC maps to a real assertion that would fail if the behavior broke; no test weakened to pass; the observed command is the suite the plan named and was not narrowed to exclude failures, which is a separate finding from weakening a test; observed test run is green. **There is no WRITTEN-NOT-RUN waiver for this subject:** the stage exists to run the tests, so the evidence must record an observed run that exited zero. Reporting WRITTEN-NOT-RUN honestly is valid; approving on it is not. Because this is the last reading of the integrated diff before publication, make its findings exhaustive in one pass: a production defect recorded here parks the run for an operator rather than returning to a builder, since post-merge repair is test-only and a merged slice is never dispatched again, so there is no later round to carry a withheld finding into.
125
125
 
126
126
  ## Security proportionality
127
127