@arbiterforge/ca-pi 0.18.4 → 0.19.0

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.
Files changed (74) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/plugins/ca-pi/CHANGELOG.md +54 -0
  4. package/plugins/ca-pi/COMMANDS.md +2 -2
  5. package/plugins/ca-pi/SKILLS.md +5 -5
  6. package/plugins/ca-pi/SPRINT.md +43 -230
  7. package/plugins/ca-pi/agents/backend-author.md +5 -5
  8. package/plugins/ca-pi/agents/checkpoint-aggregator.md +7 -0
  9. package/plugins/ca-pi/agents/frontend-author.md +5 -5
  10. package/plugins/ca-pi/agents/infra-author.md +6 -7
  11. package/plugins/ca-pi/agents/scout.md +25 -24
  12. package/plugins/ca-pi/arbiter.md +6 -4
  13. package/plugins/ca-pi/generated/command-catalog.json +5 -5
  14. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-amd64 +0 -0
  15. package/plugins/ca-pi/helpers/artifacts/ca-artifact-darwin-arm64 +0 -0
  16. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-amd64 +0 -0
  17. package/plugins/ca-pi/helpers/artifacts/ca-artifact-linux-arm64 +0 -0
  18. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-amd64.exe +0 -0
  19. package/plugins/ca-pi/helpers/artifacts/ca-artifact-windows-arm64.exe +0 -0
  20. package/plugins/ca-pi/helpers/artifacts/release.json +37 -37
  21. package/plugins/ca-pi/hooks/_approvallib.py +41 -0
  22. package/plugins/ca-pi/hooks/_artifactlib.py +61 -115
  23. package/plugins/ca-pi/hooks/_artifactpromptlib.py +12 -12
  24. package/plugins/ca-pi/hooks/_bashguardlib.py +66 -4
  25. package/plugins/ca-pi/hooks/_contextselectlib.py +38 -35
  26. package/plugins/ca-pi/hooks/_contextwriterlib.py +114 -0
  27. package/plugins/ca-pi/hooks/_host.py +1 -1
  28. package/plugins/ca-pi/hooks/_prerequisitelib.py +35 -0
  29. package/plugins/ca-pi/hooks/_provenancelib.py +107 -1
  30. package/plugins/ca-pi/hooks/_readinjectlib.py +21 -7
  31. package/plugins/ca-pi/hooks/_reconciliationlib.py +167 -2
  32. package/plugins/ca-pi/hooks/_releaselib.py +6 -6
  33. package/plugins/ca-pi/hooks/_sprintapprovallib.py +52 -2
  34. package/plugins/ca-pi/hooks/artifact-reconcile.py +13 -4
  35. package/plugins/ca-pi/hooks/hostapi.py +1 -1
  36. package/plugins/ca-pi/hooks/init-codearbiter.py +34 -5
  37. package/plugins/ca-pi/includes/artifacts-attestation.md +434 -0
  38. package/plugins/ca-pi/includes/artifacts.md +133 -492
  39. package/plugins/ca-pi/includes/author-tdd-workflow.md +32 -21
  40. package/plugins/ca-pi/includes/fresh-verification.md +14 -12
  41. package/plugins/ca-pi/includes/harvest.md +3 -3
  42. package/plugins/ca-pi/includes/reference-map.md +1 -1
  43. package/plugins/ca-pi/includes/routing-table.md +3 -3
  44. package/plugins/ca-pi/includes/sprint-authority.md +6 -1
  45. package/plugins/ca-pi/includes/verification-boundary.md +11 -2
  46. package/plugins/ca-pi/package.json +1 -1
  47. package/plugins/ca-pi/routines/INDEX.md +8 -8
  48. package/plugins/ca-pi/routines/brainstorming/SKILL.md +65 -202
  49. package/plugins/ca-pi/routines/commit-gate/SKILL.md +60 -25
  50. package/plugins/ca-pi/routines/context-check/SKILL.md +89 -53
  51. package/plugins/ca-pi/routines/context-creation/SKILL.md +114 -220
  52. package/plugins/ca-pi/routines/decompose/SKILL.md +139 -167
  53. package/plugins/ca-pi/routines/executing-plans/SKILL.md +38 -136
  54. package/plugins/ca-pi/routines/finishing-a-development-branch/SKILL.md +45 -55
  55. package/plugins/ca-pi/routines/refactor/SKILL.md +27 -12
  56. package/plugins/ca-pi/routines/release/SKILL.md +1 -1
  57. package/plugins/ca-pi/routines/subagent-driven-development/SKILL.md +80 -230
  58. package/plugins/ca-pi/routines/tdd/SKILL.md +115 -228
  59. package/plugins/ca-pi/routines/writing-plans/SKILL.md +104 -174
  60. package/plugins/ca-pi/skills/ca-audit/SKILL.md +2 -2
  61. package/plugins/ca-pi/skills/ca-checkpoint/SKILL.md +1 -1
  62. package/plugins/ca-pi/skills/ca-commit/SKILL.md +60 -25
  63. package/plugins/ca-pi/skills/ca-context-check/SKILL.md +89 -53
  64. package/plugins/ca-pi/skills/ca-create-context/SKILL.md +114 -220
  65. package/plugins/ca-pi/skills/ca-decompose/SKILL.md +139 -167
  66. package/plugins/ca-pi/skills/ca-feature/SKILL.md +21 -38
  67. package/plugins/ca-pi/skills/ca-fix/SKILL.md +15 -9
  68. package/plugins/ca-pi/skills/ca-init/SKILL.md +18 -40
  69. package/plugins/ca-pi/skills/ca-pr/SKILL.md +45 -55
  70. package/plugins/ca-pi/skills/ca-refactor/SKILL.md +27 -12
  71. package/plugins/ca-pi/skills/ca-release/SKILL.md +1 -1
  72. package/plugins/ca-pi/skills/ca-review/SKILL.md +5 -8
  73. package/plugins/ca-pi/skills/ca-sprint/SKILL.md +11 -25
  74. package/plugins/ca-pi/skills/ca-status/SKILL.md +15 -6
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  import hashlib
12
12
  from functools import wraps
13
+ from itertools import islice
13
14
  import json
14
15
  import os
15
16
  from pathlib import Path
@@ -278,12 +279,61 @@ def resume(root: str | Path, client: Any, plan_id: str) -> dict[str, Any]:
278
279
  "reapproval_required": True}
279
280
 
280
281
 
282
+
283
+ def _published_reply_observed(root: Path, pending: dict[str, Any]) -> bool:
284
+ """Bounded crash-gap check for a reply published before pair submission.
285
+
286
+ The old producer publishes a content-addressed observation *before* its
287
+ durable submission record. A crash between these writes is not an
288
+ unanswered request. This rare cleanup-only read uses existing bounded,
289
+ no-follow readers; it never scans source files or validates execution.
290
+ """
291
+ relative = Path(".codearbiter/.artifacts/observations")
292
+ try:
293
+ directory = approval._safe_directory(root, relative, create=False)
294
+ except FileNotFoundError:
295
+ return False
296
+ expected_contexts = {
297
+ pending["spec_context"]["context_ref"],
298
+ pending["plan_context"]["context_ref"],
299
+ }
300
+ # Stop enumeration at the first over-limit entry; do not materialize an
301
+ # unbounded directory just to decide a rare legacy cleanup is uncertain.
302
+ entries = list(islice(directory.iterdir(), 513))
303
+ if len(entries) > 512:
304
+ raise approval.ApprovalError(
305
+ "SUBMISSION_RECOVERY_REQUIRED",
306
+ "too many historical observations to prove the paired prompt was unanswered",
307
+ )
308
+ for entry in entries:
309
+ if not approval.SHA256_RE.fullmatch(entry.stem) or entry.suffix != ".json":
310
+ # An incomplete publisher/temp or an unexpected file in immutable
311
+ # observation storage makes a zero-observed-reply claim unsafe.
312
+ raise approval.ApprovalError(
313
+ "SUBMISSION_RECOVERY_REQUIRED",
314
+ "observation storage contains an unrecognized entry; inspect before retiring",
315
+ )
316
+ observed = _read(root, relative / entry.name)
317
+ if _hash(observed) != entry.stem:
318
+ raise approval.ApprovalError(
319
+ "SUBMISSION_RECOVERY_REQUIRED",
320
+ "stored observation changed; do not discard the pending decision",
321
+ )
322
+ if (observed.get("format") == "codearbiter.observation/0.2.0"
323
+ and observed.get("kind") == "approval"
324
+ and observed.get("producer_profile") == "host-user-pair/0.1.0"
325
+ and observed.get("context_ref") in expected_contexts
326
+ and isinstance(observed.get("producer_result"), dict)
327
+ and observed["producer_result"].get("prompt_sha256") == pending["prompt_sha256"]):
328
+ return True
329
+ return False
330
+
281
331
  @_serialized
282
332
  def cancel(root: Path, pending: dict[str, Any]) -> dict[str, Any]:
283
333
  if approval._load_pending(root) != pending:
284
334
  raise approval.ApprovalError("PENDING_APPROVAL_MISMATCH", "pending pair changed before cancellation")
285
- if _submission(root, pending) is not None:
286
- raise approval.ApprovalError("SUBMITTED_APPROVAL", "recover the observed transaction before cancelling its pending state")
335
+ if _submission(root, pending) is not None or _published_reply_observed(root, pending):
336
+ raise approval.ApprovalError("SUBMITTED_APPROVAL", "recover the observed user reply before cancelling its pending state")
287
337
  _artifactpromptlib.unregister(root, "approval", pending["artifact_id"])
288
338
  (root / approval.PENDING).unlink()
289
339
  return {"artifact_id": pending["artifact_id"], "cancelled": True}
@@ -23,26 +23,35 @@ def main(argv=None):
23
23
  parser.add_argument("--assessment")
24
24
  parser.add_argument("--cancel", action="store_true", help="cancel an armed request before its mutation attempt")
25
25
  parser.add_argument("--cancel-orphan", action="store_true", help="cancel a pending request whose prompt route was never registered")
26
- parser.add_argument("--recover", action="store_true", help="replay an in-flight mutation before clearing its marker")
26
+ parser.add_argument("--recover", action="store_true", help="recover an observed reply or replay an in-flight mutation before clearing its marker")
27
+ parser.add_argument("--supersede", action="store_true", help="retire a verified unanswered legacy prompt without a code (never discards a durable attempt)")
27
28
  parser.add_argument("--prompt", help="exact armed prompt required for --cancel")
28
29
  args = parser.parse_args(argv)
29
30
  root = Path(args.root).resolve(strict=True)
30
31
  if args.cancel_orphan:
31
- if args.cancel or args.recover or args.prompt or args.target_id or args.operation or args.target_state or args.assessment:
32
+ if args.cancel or args.recover or args.supersede or args.prompt or args.target_id or args.operation or args.target_state or args.assessment:
32
33
  parser.error("--cancel-orphan excludes arming and exact-prompt cancellation fields")
33
34
  print(json.dumps(_reconciliationlib.cancel_orphaned_reconciliation(root, args.artifact_id), sort_keys=True))
34
35
  return 0
35
36
  if args.cancel:
36
- if args.recover or args.prompt is None or args.target_id or args.operation or args.target_state or args.assessment:
37
+ if args.recover or args.supersede or args.prompt is None or args.target_id or args.operation or args.target_state or args.assessment:
37
38
  parser.error("--cancel requires --prompt and excludes arming fields")
38
39
  print(json.dumps(_reconciliationlib.cancel_reconciliation(root, args.artifact_id, args.prompt), sort_keys=True))
39
40
  return 0
40
41
  if args.recover:
41
- if args.prompt or args.target_id or args.operation or args.target_state or args.assessment:
42
+ if args.supersede or args.prompt or args.target_id or args.operation or args.target_state or args.assessment:
42
43
  parser.error("--recover excludes arming and cancellation fields")
43
44
  client = _artifactlib.ArtifactClient(root, _artifactlib.helper_installation(__file__))
44
45
  print(json.dumps(_reconciliationlib.recover_reconciliation(root, client, args.artifact_id), sort_keys=True))
45
46
  return 0
47
+ if args.supersede:
48
+ if (args.prompt is not None or args.target_id or args.operation or
49
+ args.target_state or args.assessment or not args.reason.strip()):
50
+ parser.error("--supersede requires --reason and excludes all arming and prompt fields")
51
+ print(json.dumps(_reconciliationlib.supersede_unanswered_reconciliation(
52
+ root, args.artifact_id, reason=args.reason,
53
+ ), sort_keys=True))
54
+ return 0
46
55
  if not args.target_id or not args.operation or not args.assessment or args.prompt is not None:
47
56
  parser.error("arming requires --target-id, --operation, and --assessment, without --prompt")
48
57
  client = _artifactlib.ArtifactClient(root, _artifactlib.helper_installation(__file__))
@@ -313,7 +313,7 @@ class Host:
313
313
 
314
314
  name = "claude"
315
315
  adapter_name = "ca"
316
- adapter_version = "2.26.4"
316
+ adapter_version = "3.0.0"
317
317
 
318
318
  # Update-notifier descriptor. Each independently versioned host overrides
319
319
  # these three values in its per-plugin _host.py. Keeping the target,
@@ -222,27 +222,56 @@ def main(argv=None):
222
222
  ap.add_argument("--passive", action="store_true", help=(
223
223
  "inspect explicit root before host activation; no Git or network probes"))
224
224
  ap.add_argument("--context-request", help=argparse.SUPPRESS)
225
+ ap.add_argument("--context-draft", help="preview an explicit whole-document context refresh from a UTF-8 draft")
226
+ ap.add_argument("--document", choices=tuple(_artifactlib._CONTEXT_TARGETS))
227
+ ap.add_argument("--source", action="append", default=[], help="repository-relative source supporting the draft; repeat as needed")
228
+ ap.add_argument("--context-apply", help="apply the exact context preview reference returned by the writer")
229
+ ap.add_argument("--context-finalize", action="store_true", help="preview initialization of the five populated context documents")
230
+ ap.add_argument("--preview-sha256", help="SHA-256 of the retained context preview to apply")
225
231
  ap.add_argument("--repair-lock-exclusion", action="store_true", help=(
226
232
  "repair only Git-local info/exclude for an existing scaffold; retain the "
227
233
  "task-board OS lock, never rewrite project state (cannot combine with --check/--stage)"))
228
234
  args = ap.parse_args(argv)
229
- if args.context_request:
235
+ context_modes = sum(bool(value) for value in (
236
+ args.context_request, args.context_draft, args.context_apply, args.context_finalize))
237
+ if context_modes:
238
+ if context_modes != 1:
239
+ ap.error("select exactly one context request, draft, finalize, or apply")
230
240
  if not args.root or args.stage is not None or args.check or args.passive or args.repair_lock_exclusion:
231
- ap.error("--context-request requires --root and excludes scaffold flags")
241
+ ap.error("context writing requires --root and excludes scaffold flags")
242
+ if (args.context_draft and (not args.document or not args.source or args.preview_sha256)
243
+ or args.context_apply and (not args.preview_sha256 or args.document or args.source)
244
+ or (args.context_request or args.context_finalize)
245
+ and (args.document or args.source or args.preview_sha256)):
246
+ ap.error("a draft requires --document and --source; apply requires only --preview-sha256")
232
247
  try:
233
248
  root = _artifactlib._trusted_directory(args.root, "UNSAFE_ROOT")
234
- request = _artifactlib.read_context_preview_request(root, args.context_request)
249
+ if args.context_draft:
250
+ from _contextwriterlib import prepare_document_preview
251
+ request = {"operation": "preview", "preview": prepare_document_preview(
252
+ root, args.document, args.context_draft, args.source)}
253
+ elif args.context_apply:
254
+ request = {"operation": "apply", "preview_ref": args.context_apply,
255
+ "preview_sha256": args.preview_sha256}
256
+ elif args.context_finalize:
257
+ from _contextwriterlib import prepare_finalization_preview
258
+ request = {"operation": "preview", "preview": prepare_finalization_preview(root)}
259
+ else:
260
+ request = _artifactlib.read_context_preview_request(root, args.context_request)
235
261
  operation, payload = _artifactlib.validate_context_writer_request(request)
236
262
  client = _artifactlib.context_writer_client(root)
237
263
  if operation == "preview":
238
- from _artifactauthoritylib import arm_context_preview
239
- result = arm_context_preview(root, client, payload)
264
+ native_operation = ("context-finalize-preview" if payload["mode"] == "finalize"
265
+ else "context-preview")
266
+ result = client.call(native_operation, payload)
240
267
  else:
241
268
  result = client.call("context-apply", payload)
242
269
  except RuntimeError as exc:
243
270
  raise SystemExit(f"REFUSING context request: {exc}") from None
244
271
  print(json.dumps(result, sort_keys=True))
245
272
  return
273
+ if args.document or args.source or args.preview_sha256:
274
+ ap.error("context options require --context-draft or --context-apply")
246
275
  if args.passive:
247
276
  if not args.check or not args.root or args.stage is not None or args.repair_lock_exclusion:
248
277
  ap.error("--passive requires --check and --root; excludes --stage and repair")
@@ -0,0 +1,434 @@
1
+ # Optional artifact attestation and historical recovery
2
+
3
+ **Cold reference, not a default workflow.** This file holds the existing
4
+ strict evidence-producer, receipt, older approval and recovery contracts for
5
+ explicit claims. Start ordinary authoring and implementation at
6
+ `<plugin-root>/includes/artifacts.md` instead. Do not turn these historical
7
+ protocols into a second permission system, a mandatory reviewer chain, or a
8
+ precondition for tracking progress. Real host permissions and user stops always
9
+ remain independently enforceable.
10
+
11
+ ## Explicit verification-input and attestation contracts
12
+
13
+ The following input/receipt protocols describe explicit historical evidence and
14
+ attestation operations, not ordinary authoring or work-progress prerequisites.
15
+ Read only the portion needed for an actual evidence consumer. Do not create an
16
+ approval, method grant, or reviewer event just to satisfy a status transition.
17
+ Independent host permissions and genuinely requested user checkpoints still apply.
18
+
19
+ `verification_inputs.roots` and `exclude_directories` use exact slash-separated
20
+ repository-relative paths. Exclusions are directory paths, not globs or basename
21
+ matches and not relative to an individual root. For root `site`, a generated
22
+ dependency exclusion is `site/node_modules`, not `node_modules`. Review each
23
+ excluded output directory explicitly: the plan's roots and exclusions narrow
24
+ the ship set. In a git work tree `.gitignore` already keeps untracked ignored
25
+ files out of it; outside git nothing is filtered, so exclude generated
26
+ directories explicitly.
27
+ An exclusion outside all selected roots is rejected as ineffective. Overlapping
28
+ roots cannot reinclude an excluded directory. Roots must exist before execution;
29
+ use an existing containing directory for a planned new file so its creation
30
+ changes the snapshot. An oversized included file is reported with its path and
31
+ the 32 MiB per-file limit; revise the explicit policy rather than increasing the
32
+ limit or excluding source/test/governance inputs needed for the task.
33
+
34
+ Snapshot input identity is ship-set membership. In a git work tree the members
35
+ are the git index entries present on disk, plus untracked non-ignored files
36
+ within the plan's roots, minus its excludes. The one fixed exemption is
37
+ `.codearbiter/gate-events.log`: it is never counted, whatever the plan says, and
38
+ stays tracked as the audit trail; every other `.codearbiter` path still counts.
39
+ Outside git, membership is read from the filesystem (`membership: filesystem`);
40
+ the root must be the top level of its work tree, so a subdirectory root counts as
41
+ outside git. A failed git probe fails closed with `INPUT_MEMBERSHIP_UNAVAILABLE`. An unmerged
42
+ git index (conflicted paths) makes a snapshot fail closed until it is resolved.
43
+ The manifest format is `codearbiter.verification-inputs/0.2.0`; receipts bound to
44
+ a 0.1.0 manifest read as stale. Each evidence object is validated at most once
45
+ per operation and scans are bounded; raw reads are reused only up to a 64 MiB
46
+ per-operation retained-byte budget, past which an object can be read again, but
47
+ validation is never skipped.
48
+
49
+ The existing user, SMARTS, reviewer, or verification boundary must first persist
50
+ its actual policy-owned workflow event as canonical JSON in the reserved
51
+ content-addressed authority-source store. Interactive prompt approval currently
52
+ has production host seams in Claude Code and Codex. Pi 1.0.0 exposes an `input`
53
+ event, but codeArbiter has no Pi-native authority adapter. Under Pi do not arm this
54
+ adapter; the workflow remains blocked until that adapter is
55
+ implemented. On Claude Code or Codex, arm the exact current artifact immediately
56
+ before asking the approval question:
57
+
58
+ ```sh
59
+ python "<plugin-root>/hooks/_approvallib.py" arm --root "<project-root>" --artifact-id <artifact-id>
60
+ ```
61
+
62
+ On Claude Code, when the result carries `ask_envelope`, pass it unchanged as the
63
+ entire AskUserQuestion input (no `answers`, `annotations` or other field): the
64
+ user approves by clicking its Approve option, and the host-observed selection is
65
+ applied with origin `claude:AskUserQuestion:<session>`. Otherwise, or if the
66
+ user prefers typing, present the returned `short_reply` (a four-character code
67
+ such as `approve K7MQ`); the full `reply` is also accepted, and you
68
+ require that exact reply: the hook tolerates surrounding whitespace, invisible characters, a code
69
+ fence or quotes and a closing period, but nothing else in the message, and it
70
+ reports a mismatch instead of staying silent. The `UserPromptSubmit` hook
71
+ rechecks the armed identity, records the full armed reply as the host-observed
72
+ policy-owned source, captures its receipt, and applies approval. An ordinary
73
+ `yes`, a changed artifact, a wrong or expired code, a reply inside other text, or
74
+ a model-authored event does not confer authority. On the next turn, verify `validate` at the `approved`
75
+ gate before continuing. If the user declines or the artifact changes, cancel the
76
+ exact pending request before arming another one:
77
+
78
+ ```sh
79
+ python "<plugin-root>/hooks/_approvallib.py" cancel --root "<project-root>" --artifact-id <artifact-id>
80
+ ```
81
+
82
+ Cancellation records no authority. Other workflow authorities must use their
83
+ corresponding host-owned adapters. Legacy `capture` events remain readable for
84
+ inventory but are inspection-only: the engine refuses to wrap them in a current
85
+ receipt. Current authority requires `capture-observation` over a closed producer
86
+ profile and engine-issued evidence context; never create an authority source
87
+ merely to satisfy an artifact transition. `approve` checks the resulting receipt
88
+ against current ready content. A missing or changed receipt, captured event, or
89
+ still-present policy source returns `AUTHORITY_UNVERIFIED`. Receipt format 0.1.0
90
+ remains readable for inspection after upgrade but is not authority-bearing; the
91
+ applicable policy boundary must produce a fresh 0.2.0 attestation before further
92
+ approval, dispatch, acceptance, commit proof, or finalization. This is the
93
+ repository's cooperative same-user attestation model, not cryptographic identity
94
+ proof or protection from an unrestricted same-user filesystem writer.
95
+
96
+ For ordinary sequential planning, create a plan as `draft_preview`, consume existing criterion IDs,
97
+ then `plan-bind` to the approved spec and approve the plan through its existing workflow. Initial
98
+ combined HTML sprint review uses the explicit paired path below instead of separate approvals.
99
+ A normative change invalidates its approval. Rebinding is explicit and preserves
100
+ history; it does not waive coverage or permit silent reinterpretation.
101
+
102
+ When adversarial review revises a ready initial draft spec after its draft plan exists,
103
+ query both current identities. The installed engine's explicit `plan-bind` request with
104
+ `binding_mode: draft_preview` requires the plan's existing `expected` CAS and the spec's
105
+ `spec_expected` revision/model CAS. It rebinds only that same-ID, same-slug initial pair:
106
+ both artifacts must remain unapproved drafts, tasks pending and unevidenced, prerequisites
107
+ unsatisfied and unevidenced, and scopes empty. Optional typed plan corrections must leave
108
+ both drafts fully ready. The binding remains `draft_preview`; this operation grants no
109
+ approval, delegation, prerequisite satisfaction, execution or acceptance. Revalidate both
110
+ current drafts, repeat the affected review and arm the exact combined approval afterward.
111
+ Inspect the installed operation schema first. An engine without this explicit draft
112
+ request reports a capability gap; never edit HTML, recreate the pair, substitute sequential
113
+ approval or use ordinary approved-source `plan-bind` to repair it.
114
+
115
+ ## Historical paired approval and delegated methods
116
+
117
+ `<plugin-root>/includes/sprint-authority.md` retains the old paired/method
118
+ attestation adapter contract for inspecting or recovering those records. It is
119
+ not required to create drafts, bind a source, or maintain an in-scope method.
120
+ A historical grant is not permission to bypass a real user stop or host denial.
121
+
122
+ ## Explicit verification and review receipts
123
+
124
+ This is the existing attested-evidence path for a consumer that actually needs
125
+ that claim. It is not a mandatory author/reviewer sequence for ordinary work.
126
+ Do not apply the following arming protocol to receipt-free progress operations.
127
+
128
+ The orchestrator executes the declared verification through its existing governed
129
+ execution tool through the installed production authority adapter; it never
130
+ constructs a successful workflow event itself. Arm an exact task request first.
131
+ Verification and review authority below is supported on Codex and Claude Code.
132
+ On Pi, stop at this boundary rather than treating model-authored results as
133
+ authority; its installed prompt approval seam does not imply verification or
134
+ review support.
135
+ Every distinct `cwd` label in the engine context must be mapped once to an exact
136
+ linked Git worktree root. Mapped worktrees must share the artifact repository's
137
+ Git common directory, and the adapter freezes their filesystem identity, HEAD,
138
+ status digest, effective argv, and executable bytes:
139
+
140
+ ```sh
141
+ python "<plugin-root>/hooks/artifact-authority.py" arm --root "<project-root>" --artifact-id <plan-id> --record-id <task-id> --activity verification --workspace "candidate worktree=<absolute-worktree>" --workspace "baseline-evidence worktree=<absolute-worktree>"
142
+ ```
143
+
144
+ Use only the mappings actually named by that task; missing or extra labels fail
145
+ closed. Run the returned request through one plain governed exec invocation (no
146
+ compound shell expression), then publish only after the host's matching
147
+ `PostToolUse` has corroborated the wrapper's completed request-bound result.
148
+ For the `verify` invocation, use literal absolute paths for both the installed
149
+ script and repository root; do not submit an unexpanded plugin-root or project-root
150
+ variable. The selector rejects `$` and other shell metacharacters before shell
151
+ expansion on both supported hosts, so an exec shell exporting `PLUGIN_ROOT` does
152
+ not make that spelling valid. On Claude, run the exact `verify_command` returned
153
+ by `arm`. Otherwise substitute the resolved paths into this invocation:
154
+
155
+ ```sh
156
+ python "<absolute-plugin-root>/hooks/artifact-authority.py" verify --root "<absolute-project-root>" --request-id <request-id>
157
+ python "<plugin-root>/hooks/artifact-authority.py" publish --root "<project-root>" --request-id <request-id>
158
+ ```
159
+
160
+ The verifier launches exact engine-declared argv without a shell, supervises the
161
+ whole process tree, bounds output, requires exact named outcomes, and rejects
162
+ workspace or executable drift. If an attempt is interrupted, use the adapter's
163
+ `recover` operation to retain it as failed or abandoned; never rerun an uncertain
164
+ attempt under the same request.
165
+
166
+ Named Playwright commands require `test --reporter=json`; each required name must
167
+ equal a unique native `spec.title`. Named Vitest commands require
168
+ `run --reporter=verbose`; use the complete rendered `suite > case` identity after
169
+ the filename, not a leaf-name alias. Named unittest commands require `-v` or
170
+ `--verbose`. A passing row cannot conceal a duplicate, skip, failure or retry.
171
+ Plain exit checks cannot attest named tests.
172
+
173
+ Named npm commands support inspectable single-runner scripts, a contained
174
+ repository-relative `--prefix`, and arguments forwarded after `--`. A root script
175
+ may delegate once through `npm -w <name> run <script>` or
176
+ `npm --workspace <name> run <script>` when the name resolves uniquely in an array
177
+ of at most 32 explicit, contained workspace directories. The runner binds the root
178
+ manifest and every inspected workspace manifest. Globs, linked directories,
179
+ multiple workspace selectors and recursive npm delegation are unsupported.
180
+ Workspace delegation uses the declared cwd; a non-dot `--prefix` is unsupported
181
+ for this shape.
182
+ Preserve both npm argument boundaries when adding a reporter: for a root
183
+ `test:client` script containing `npm -w @example/client run test`, declare
184
+ `npm run test:client -- -- --reporter=verbose` to forward the reporter to Vitest.
185
+ The original root command still executes the full script. Compound
186
+ scripts and pre/post lifecycle scripts require separately declared direct-runner
187
+ commands; retain every check from the original suite when splitting commands.
188
+ On Windows, only the qualified standard `npm.cmd` layout is adapted: the runner
189
+ pins the wrapper, sibling `node.exe`, sibling `node_modules/npm/bin/npm-cli.js`,
190
+ and script manifest. It deliberately selects that sibling CLI rather than the
191
+ shim's optional global-prefix redirection. Unknown batch wrappers fail closed.
192
+ For workspace delegation, it also pins `npm-prefix.js` and checks that the nested
193
+ wrapper uses the same CLI, the default script shell, and no configured Node
194
+ options. Local executables shadowing nested npm are rejected.
195
+ Direct Node script entrypoints are also pinned. These launch-file bindings do
196
+ not establish transitive dependency closure for deliberately excluded inputs.
197
+
198
+ New observations use the closed `declared-command/0.3.0` producer profile to
199
+ retain collector and launch-file identities plus an engine-produced normalized
200
+ completion closure. Raw workspace fingerprints must still match before and
201
+ after command execution. Historical `0.1.0` and `0.2.0` bindings retain their
202
+ original contracts and cannot be relabelled as newer evidence.
203
+
204
+ For `spec_review` or `quality_review`, arm the corresponding activity without
205
+ workspace mappings and dispatch the returned `launch_envelope` unchanged to one
206
+ fresh host subagent. The bounded prompt names the retained, content-addressed
207
+ evidence context instead of embedding its full input manifest. The reviewer
208
+ must read that context and inspect its subject, coverage and source inputs.
209
+ The adapter verifies the context's canonical bytes and digest before launch and
210
+ publication; a missing or changed context blocks authority. Never shorten or
211
+ rewrite the returned envelope yourself.
212
+ The host hooks bind the exact launch call, child start and
213
+ the child's first stop; a pasted or coordinator-authored decision is not review
214
+ authority.
215
+
216
+ When reviewing completed work that relies on a verification run, linked
217
+ worktrees or supporting evidence, explicitly select the completion path. Add
218
+ `--completion-receipt <exact-published-receipt>` once per task and
219
+ `--supporting-file <absolute-regular-file>` for each supporting document to the
220
+ review `arm` command. A task review selects one verification receipt; a scope
221
+ review selects the exact receipt for every member. Supporting files require a
222
+ receipt selection. The existing review-before-verification path remains
223
+ available; it does not satisfy an explicitly requested completion review.
224
+
225
+ The native engine validates the selected published verification chain and
226
+ derives the worktree mappings from its qualified observation. It retains the
227
+ selected identities, worktree closure and supporting bytes in the immutable
228
+ context. Supporting material is data for review, not authority. Selection is
229
+ explicit: no newest-receipt search, directory import or attachment execution.
230
+ At most 16 regular files are admitted, each at most 64 KiB and together at most
231
+ 256 KiB; links, reparse indirection, duplicate paths and changed content fail
232
+ closed. Read canonical spec and plan artifacts through the native artifact API;
233
+ they cannot be imported as supporting files. The generated prompt directs the reviewer to assess performed work
234
+ against each criterion, step and `done_when` obligation, using selected evidence
235
+ and source references. A consistent plan or an empty diff is insufficient.
236
+
237
+ Use the returned `completion_sha256` in the native `task-review` or
238
+ `accept-scope` request. The completed review and its exact verification run must
239
+ agree; a definition-only review or a different successful run is rejected.
240
+ A task review retains the exact run it actually inspected; never relabel it as
241
+ having reviewed another run. A new scope review may select a separately validated
242
+ passing rerun without redispatching that task reviewer when the original task
243
+ review remains current, the reviewed workspaces match the new run's normalized
244
+ closure, and all supporting material is retained. The engine checks those facts;
245
+ a new receipt ID alone is not a reason to repeat the task review. Different
246
+ source, definitions, or supporting evidence still needs the affected assessment.
247
+ This does not infer equivalence from a caller-supplied label or a matching exit
248
+ code, and does not make a later failed run pass.
249
+
250
+ Creating a completion context for a `0.3.0` verification recomputes the normalized
251
+ worktree closure bound during that verification. Only validated native outputs
252
+ and execution bookkeeping are normalized; their publication must not invalidate
253
+ unchanged source. Historical `0.2.0` verification retains its initial exact raw
254
+ fingerprint comparison. Later freshness checks allow native task-status and
255
+ execution bookkeeping updates in validated canonical artifacts while retaining
256
+ their normative content hashes.
257
+ Changes to task definitions, other HTML or other governance files still affect
258
+ freshness under the normal snapshot policy. The closure covers
259
+ tracked working-tree bytes and non-ignored untracked files, not ignored
260
+ dependencies or outputs. The completion assessment is still an independent
261
+ reviewer's judgment; schema validation does not prove that every file was read
262
+ or every conclusion is correct. Preserve the unchanged launch envelope and
263
+ genuine host capture throughout this path.
264
+ Publish the completed request through the same `publish` command. `task-review`
265
+ records the separate verification and spec-review receipts. After every task in
266
+ the scope reaches `REVIEW`, reverify against one current source snapshot, run the
267
+ combined quality review, and use `accept-scope` once. Do not accept tasks
268
+ individually.
269
+
270
+ Current Codex native V1 completion reviews retain the full frozen context in
271
+ its immutable, content-addressed file and keep only its closed reference in
272
+ mutable request state. Loading validates the exact context locator, hash,
273
+ canonical bytes and review kind. The native context budget remains 8 MiB;
274
+ mutable request state remains bounded at 1 MiB. Before dispatch, the adapter
275
+ reserves the exact fixed result identities, six times the 64 KiB decision
276
+ budget for canonical JSON escaping, and 32 bounded 256-byte lifecycle slots.
277
+ A request without that capacity is refused before child launch. Historical
278
+ inline requests retain their existing limits and abandonment-only recovery.
279
+
280
+ An ARMED review that never launched can be abandoned through the adapter:
281
+
282
+ ```sh
283
+ python "<plugin-root>/hooks/artifact-authority.py" recover --root "<project-root>" --request-id <request-id> --disposition abandoned
284
+ ```
285
+
286
+ The adapter requires no attempt, launch, observation, receipt or prior recovery.
287
+ It retains the request and its evidence, creates no receipt, and prohibits
288
+ dispatching that request again. This also covers bounded legacy requests whose
289
+ inline manifests made them too large for normal dispatch. Launched or captured
290
+ requests remain subject to their existing recovery rules. Arm a new request
291
+ only after the retained request is terminal.
292
+
293
+ ## Assistive recovery of an unanswered legacy reconciliation prompt
294
+
295
+ After a session restart, **inspect the active task and any workers first**.
296
+ Ordinary `task-reconcile` records a reasoned progress update through the
297
+ native engine without a prompt token; it never clears an explicit user stop.
298
+ The following repair applies **only** when an older installed adapter has left
299
+ an *unanswered* reconciliation prompt that should no longer be re-armed:
300
+
301
+ ```sh
302
+ python "<plugin-root>/hooks/artifact-reconcile.py" --root "<project-root>" --artifact-id <plan-id> --supersede --reason "Old unanswered artifact prompt; the actual task was inspected and no user decision is being overridden."
303
+ ```
304
+
305
+ This calls the existing host adapter's locked, read-validated pending-state
306
+ handler. It requires the original prompt-route binding to match the pending
307
+ record, rejects ambiguous legacy/current markers, and **refuses any durable
308
+ mutation attempt**. It removes only an unanswered prompt/route, does not edit
309
+ spec/plan state, report tests passed, cancel a user stop, or fabricate an
310
+ approval. No existing receipt or evidence history is rewritten. If it fails
311
+ because a mutation may have started, use the exact `--recover` path below;
312
+ **never** reissue the work under a different operation ID. After superseding,
313
+ read `identity` and `eligible`, then reconcile only what actual observed
314
+ work requires. A cancelled question is not an accomplished task.
315
+
316
+ An **unanswered** old paired-sprint approval can be retired through the
317
+ existing approval adapter. The paired format persists a submitted reply before attempting a native
318
+ write. Its old producer publishes an immutable observation **before** that
319
+ submission record, so a crash between those two writes must not make the
320
+ reply appear unanswered. The transition lock rechecks both the exact
321
+ submission and a **bounded, content-hash-checked observation scan**.
322
+ It refuses retirement if either proves an observed reply or if historical
323
+ observation storage cannot be safely assessed. Recover confirmed/submitted
324
+ decisions with the existing `resume-sprint` path or investigate an
325
+ interrupted pre-submission observation; never discard its source:
326
+
327
+ ```sh
328
+ python "<plugin-root>/hooks/_approvallib.py" retire --root "<project-root>" --artifact-id <plan-id> --reason "The old paired approval was never answered; the current authorized work does not require that question."
329
+ ```
330
+
331
+ Old **single-artifact** approval format 0.1 cannot prove that a submitted
332
+ reply was never captured or committed. Its no-code retirement therefore
333
+ **fails closed** as `LEGACY_APPROVAL_RECOVERY_REQUIRED`; inspect the
334
+ current artifact, actual authority history and journal instead of deleting
335
+ the marker. A historical approval, refusal, or genuine user stop is not a
336
+ routine progress permission and must not be silently inverted.
337
+
338
+ An unanswered old prerequisite can use the same existing prerequisite
339
+ helper when its validated state is exactly `ARMED`:
340
+
341
+ ```sh
342
+ python "<plugin-root>/hooks/_prerequisitelib.py" retire --root "<project-root>" --artifact-id <plan-id> --prerequisite-id <prerequisite-id> --reason "No human reply was observed; use the real factual observation path if this condition still matters."
343
+ ```
344
+
345
+ Its per-plan transition lock rejects `CONFIRMED` or `CAPTURED` requests:
346
+ preserve the source and recover the actual submission instead. Either
347
+ retirement reports `unanswered_prompt_superseded`,
348
+ `authority_granted: false`, and `mutation_attempted: false`.
349
+ Neither is an automatic prompt sweep or a native permission operation.
350
+ No canonical task state, receipt history, or explicit user denial changes.
351
+ **Inspect workers and actual state first**; do not invoke these commands
352
+ for an actively handled request. Other older interrupted decisions use
353
+ their specific recovery contract. Do not bulk-delete markers, receipts,
354
+ or Git-admin attempt state. Actual host permissions remain final.
355
+
356
+ ## Historical pending-request recovery
357
+
358
+ Ordinary progress uses the receipt-free reconciliation path above. The following
359
+ legacy adapter operations describe retained armed requests, not a requirement to
360
+ create new approval prompts after every interruption. Do not delete an uncertain
361
+ mutation or relabel its history. Full upgrade retirement of obsolete prompts is
362
+ separate from ordinary task progress.
363
+
364
+ For an explicit historical authority-backed reconciliation, arm
365
+ the installed reconciliation adapter for the exact task or scope, present its
366
+ returned `short_reply` (`reconcile <STATE> <code>` for a task, `reconcile <code>`
367
+ for a scope; the full `reply` also works), and let `UserPromptSubmit` capture and apply the observed
368
+ user decision across repositories:
369
+
370
+ ```sh
371
+ python "<plugin-root>/hooks/artifact-reconcile.py" --root "<project-root>" --artifact-id <plan-id> --target-id <task-id> --operation task-reconcile --target-state PENDING --reason "<reviewed reason>" --assessment "<reviewed assessment>"
372
+ python "<plugin-root>/hooks/artifact-reconcile.py" --root "<project-root>" --artifact-id <plan-id> --target-id <checkpoint-id> --operation scope-reconcile --assessment "<reviewed assessment>"
373
+ ```
374
+
375
+ Ambiguous armed requests, changed identities, stale inputs, wrong replies, or a
376
+ foreign Git repository all fail closed. Re-run `eligible` after reconciliation;
377
+ the prompt reply itself is not proof that the lifecycle mutation committed.
378
+ If a mutation response is lost, submit the same exact reply again: the adapter
379
+ replays its durable operation ID and receipt without recapturing authority. A
380
+ request that has not reached mutation can be cancelled with:
381
+
382
+ ```sh
383
+ python "<plugin-root>/hooks/artifact-reconcile.py" --root "<project-root>" --artifact-id <plan-id> --cancel --prompt "<exact returned reply>"
384
+ ```
385
+
386
+ If a crash left a complete pending request before its prompt route was
387
+ registered, use this command instead. It refuses an active route or any
388
+ in-flight mutation:
389
+
390
+ ```sh
391
+ python "<plugin-root>/hooks/artifact-reconcile.py" --root "<project-root>" --artifact-id <plan-id> --cancel-orphan
392
+ ```
393
+
394
+ If a durable mutation attempt exists, neither cancellation path can clear it.
395
+ Replay the exact stored operation and receipt under the reconciliation lock:
396
+
397
+ ```sh
398
+ python "<plugin-root>/hooks/artifact-reconcile.py" --root "<project-root>" --artifact-id <plan-id> --recover
399
+ ```
400
+
401
+ Recovery clears the request only on a committed replay or a closed-set engine
402
+ result proving no commit (`REVISION_CONFLICT` or `OPERATION_ROLLED_BACK`). Other
403
+ errors retain it for investigation. Re-arm only after cancellation or recovery
404
+ succeeds; a committed recovery is not permission to repeat the mutation.
405
+
406
+ An `IN_PROGRESS` task after interruption requires inspecting the current
407
+ worktree, outstanding tools and actual task results, **not a new approval code**.
408
+ Use receipt-free `task-reconcile` only after establishing its real state; do
409
+ not assume completion or duplicate dispatch. `REVIEW` is implementation-ready,
410
+ not passing verification. Source changes affect applicable proof; presentation
411
+ and routine bookkeeping do not by themselves justify reviewer cascades.
412
+ The legacy `scope-reconcile` proof form remains an explicitly attested claim,
413
+ not the default method of resuming independent tasks.
414
+
415
+ ## Legacy transition and blocked capabilities
416
+
417
+ Legacy Markdown remains readable through the old workflow. Conversion is explicit:
418
+ `migration-preview` receives typed spec/plan models and a complete nonoverlapping
419
+ line-disposition map for each original file. Review the models and mappings, then
420
+ `migration-apply` binds to the exact preview digest and records the review. Both
421
+ new artifacts are drafts. Original approval/status prose is not transferred.
422
+ Cutover is recoverable as a pair; completed cutovers use `migration-rollback`, which
423
+ refuses any intervening candidate edit. Original bytes remain in transaction history.
424
+
425
+ **HTML farm dispatch remains disabled and out of scope.** The
426
+ engine can derive an immutable base-bound projection, verify it before canary,
427
+ permit only `meta.model`/`meta.apiBaseUrl` enrichment, and seal exact execution
428
+ bytes plus caller-supplied provider-selection assertions. Those assertions are
429
+ auditable correspondence, not authenticated provenance. The shared built dispatcher enforces the checks
430
+ before side effects. Keep the HTML `--farm` path disabled until real authority
431
+ adapters, native packaged payloads and the fresh frontier/low-tier qualification
432
+ matrix all pass; the June 2026 run earns no current credit. Existing legacy farm
433
+ behavior is unchanged. Enabling normal premium-path HTML authoring does not count
434
+ as farm qualification or authorize farm dispatch.