@event4u/agent-config 14.13.0 → 14.14.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.
Files changed (138) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +138 -376
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/analyze/inbox.md +36 -0
  7. package/dist/agent-src/commands/pr/create.md +16 -1
  8. package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
  9. package/dist/agent-src/rules/code-comment-discipline.md +6 -0
  10. package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
  11. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  12. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  13. package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
  14. package/dist/agent-src/scripts/stubs_due.ts +118 -2
  15. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
  16. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  17. package/dist/cli/registry.js +1 -0
  18. package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
  19. package/dist/cli-delegate/cmd_conformance.js +39 -2
  20. package/dist/cli-delegate/cmd_doctor.js +1 -1
  21. package/dist/discovery/deprecation-report.md +1 -1
  22. package/dist/discovery/discovery-manifest.json +7 -7
  23. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  24. package/dist/discovery/discovery-manifest.summary.md +1 -1
  25. package/dist/discovery/dormancy-report.md +1 -1
  26. package/dist/discovery/orphan-report.md +1 -1
  27. package/dist/discovery/packs.json +2 -2
  28. package/dist/discovery/trust-report.md +1 -1
  29. package/dist/discovery/workspaces.json +2 -2
  30. package/dist/hooks/dispatch.js +1696 -812
  31. package/dist/mcp/registry-manifest.json +2 -2
  32. package/dist/mcp/server.json +2 -2
  33. package/dist/mcp/server.mjs +297 -115
  34. package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
  35. package/docs/guidelines/code-clarity.md +18 -0
  36. package/package.json +1 -1
  37. package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  38. package/src/agent-src/scripts/guarded_baseline.ts +22 -7
  39. package/src/agent-src/scripts/stubs_due.ts +118 -2
  40. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
  41. package/src/config/assurance-capability-registry.json +1 -1
  42. package/src/config/estate-count-budget.json +20 -1
  43. package/src/config/evaluator-budgets.json +4 -4
  44. package/src/config/gate-coverage.yml +211 -1
  45. package/src/config/gate-reachability-exemptions.json +1 -0
  46. package/src/config/gate-violation-baselines.json +9 -3
  47. package/src/config/preamble-payload-budget.json +19 -11
  48. package/src/config/rule-stub-ceilings.json +14 -7
  49. package/src/config/web-launch-readiness.json +4 -2
  50. package/src/scripts/_cli/_doctor_emit.ts +71 -0
  51. package/src/scripts/_cli/_doctor_strict.ts +163 -0
  52. package/src/scripts/_cli/cmd_doctor.ts +16 -37
  53. package/src/scripts/_dispatch.bash +11 -4
  54. package/src/scripts/_lib/activation_ladder.ts +47 -3
  55. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  56. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  57. package/src/scripts/_lib/branch_convergence.ts +186 -0
  58. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  59. package/src/scripts/_lib/carrier_status.ts +118 -0
  60. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  61. package/src/scripts/_lib/context_observation.ts +132 -0
  62. package/src/scripts/_lib/continuation_ladder.ts +188 -0
  63. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  64. package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
  65. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  66. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  67. package/src/scripts/_lib/evolution_roi.ts +562 -0
  68. package/src/scripts/_lib/experiment_binding.ts +95 -0
  69. package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
  70. package/src/scripts/_lib/ignored_blocker.ts +8 -1
  71. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  72. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  73. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  74. package/src/scripts/_lib/outcome_envelope.ts +6 -1
  75. package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
  76. package/src/scripts/_lib/pathology_archive.ts +393 -0
  77. package/src/scripts/_lib/promotion_capability.ts +315 -0
  78. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  79. package/src/scripts/_lib/promotion_review.ts +294 -0
  80. package/src/scripts/_lib/prompt_shape.ts +92 -0
  81. package/src/scripts/_lib/release_highlights.ts +79 -1
  82. package/src/scripts/_lib/release_material.ts +125 -2
  83. package/src/scripts/_lib/repeated_failure.ts +19 -2
  84. package/src/scripts/_lib/routing_corpus.ts +307 -0
  85. package/src/scripts/_lib/routing_index_input.ts +104 -0
  86. package/src/scripts/_lib/rule_injection.ts +22 -1
  87. package/src/scripts/_lib/runtime_journal.ts +11 -4
  88. package/src/scripts/_lib/semantic_noop.ts +167 -0
  89. package/src/scripts/activation_receipt.ts +258 -0
  90. package/src/scripts/ai_council/cross_exam.ts +128 -0
  91. package/src/scripts/ai_council/explain_route.ts +157 -0
  92. package/src/scripts/ai_council/information_gain.ts +293 -0
  93. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  94. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  95. package/src/scripts/ai_council/minority_retention.ts +171 -0
  96. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  97. package/src/scripts/ai_council/replay_route.ts +223 -0
  98. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  99. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  100. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  101. package/src/scripts/check_adr_frontmatter.ts +4 -9
  102. package/src/scripts/check_agent_artifact_location.ts +337 -0
  103. package/src/scripts/check_artefact_count_messaging.ts +74 -8
  104. package/src/scripts/check_enforcement_coverage.ts +40 -1
  105. package/src/scripts/check_estate_count.ts +205 -24
  106. package/src/scripts/check_release_highlights.ts +113 -14
  107. package/src/scripts/check_roadmap_trackable.ts +17 -2
  108. package/src/scripts/check_web_launch_readiness.ts +310 -20
  109. package/src/scripts/corpus_manifest.ts +235 -0
  110. package/src/scripts/evolution_lab.ts +234 -9
  111. package/src/scripts/generate_capability_matrix.ts +16 -9
  112. package/src/scripts/grade_target_readiness.ts +221 -2
  113. package/src/scripts/hook_effect_doctor.ts +47 -4
  114. package/src/scripts/hook_manifest.json +1 -1
  115. package/src/scripts/hook_manifest.yaml +28 -6
  116. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  117. package/src/scripts/hooks/concern_registry.ts +2 -0
  118. package/src/scripts/hooks/run_continuation_hook.ts +78 -88
  119. package/src/scripts/hooks/session_eol_hook.ts +12 -1
  120. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  121. package/src/scripts/install-hooks.sh +18 -0
  122. package/src/scripts/language_mirror_hook.ts +14 -2
  123. package/src/scripts/lint_carrier_integrity.ts +820 -0
  124. package/src/scripts/lint_code_comments.ts +748 -0
  125. package/src/scripts/lint_plan_risk_register.ts +39 -19
  126. package/src/scripts/lint_promotion_paths.ts +779 -0
  127. package/src/scripts/llm_propose.ts +256 -0
  128. package/src/scripts/measure_delivery_sets.ts +361 -0
  129. package/src/scripts/measure_routing_signal.ts +307 -0
  130. package/src/scripts/model_rule_injection.ts +19 -3
  131. package/src/scripts/probe_council_retention.ts +495 -0
  132. package/src/scripts/release.ts +21 -37
  133. package/src/scripts/release_drill.ts +199 -0
  134. package/src/scripts/release_env.ts +35 -0
  135. package/src/scripts/release_publication.ts +142 -0
  136. package/src/scripts/roadmap_context.ts +15 -0
  137. package/src/scripts/skill_scout.ts +633 -0
  138. package/src/scripts/sync_pr_branch.ts +521 -42
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The situational-awareness fingerprint a run last OBSERVED, recorded where a
3
+ * stop-path concern can read it without spawning anything.
4
+ *
5
+ * `road-to-wired-instruments` Phase 2.1. The fingerprint itself is built by
6
+ * `roadmap_context.ts` from `origin/main` plus every open PR head — which costs
7
+ * a `gh` call, so it can only be taken by the probe and never by a hook on the
8
+ * Stop path, where a subprocess is a cost paid on every reply.
9
+ *
10
+ * Why a file, and why this comparison and not another:
11
+ * `run_checkpoint.RunCheckpoint.context_fingerprint` already had the right idea
12
+ * and no producer: `session_eol_hook` built its checkpoints without ever passing
13
+ * one, so the field was `null` in every checkpoint this package has ever
14
+ * written. A drift detector whose input is structurally always-null reports no
15
+ * drift, forever — the defect class this roadmap exists over, in the mechanism
16
+ * meant to catch it. This module is the missing producer, and it makes the
17
+ * value reachable from BOTH consumers: the checkpoint and the continuation
18
+ * ladder.
19
+ *
20
+ * The comparison the ladder makes is deliberately narrow: the fingerprint this
21
+ * run ENGAGED under, against the newest one the run itself has OBSERVED. It is
22
+ * not "has `origin/main` moved", which changes constantly and would halt every
23
+ * long run within a day (Risk 1 of the roadmap). Nothing is observed unless the
24
+ * run re-probes, so the rung cannot fire on repository traffic the run never
25
+ * looked at — it fires when the run looked, and what it saw had changed.
26
+ *
27
+ * Best-effort throughout. A failed write or an unreadable file is `null`, which
28
+ * reads as "not known" and never as "unchanged": an absent observation must
29
+ * never manufacture a halt, and must never assert the world stood still either.
30
+ */
31
+
32
+ import * as fs from 'node:fs';
33
+ import * as path from 'node:path';
34
+
35
+ import { atomicWriteJson } from './loop_guards.js';
36
+
37
+ export const CONTEXT_OBSERVATION_REL = path.join(
38
+ 'agents',
39
+ 'runtime',
40
+ 'state',
41
+ 'context-observation.json',
42
+ );
43
+
44
+ /**
45
+ * One probe's reading.
46
+ *
47
+ * NOT keyed on a roadmap, because the fingerprint is not scoped to one:
48
+ * `contextFingerprint(base_sha, open_prs)` takes the base ref and every open PR
49
+ * head and no roadmap at all. A per-roadmap file would key a repository-wide
50
+ * fact on something it does not depend on, and would then miss every observation
51
+ * taken by an unscoped `roadmap:context` run. `roadmap` is recorded as
52
+ * provenance — which invocation took the reading — never as a lookup key.
53
+ */
54
+ export interface ContextObservation {
55
+ readonly schema_version: 1;
56
+ /** The roadmap the probe was scoped to, or `''` when it was unscoped. */
57
+ readonly roadmap: string;
58
+ /** `roadmap_context.contextFingerprint()` — compared, never parsed. */
59
+ readonly fingerprint: string;
60
+ /** ISO-8601 instant of the observation. */
61
+ readonly at: string;
62
+ }
63
+
64
+ /** The single observation file. */
65
+ export function observationFile(repoRoot: string): string {
66
+ return path.join(repoRoot, CONTEXT_OBSERVATION_REL);
67
+ }
68
+
69
+ /**
70
+ * Record what the probe just saw. Returns whether the write landed.
71
+ *
72
+ * Never throws: this is called from a reporting command, and a command that
73
+ * fails because it could not write a diagnostic breadcrumb is worse than one
74
+ * whose breadcrumb is missing.
75
+ */
76
+ export function recordContextObservation(
77
+ repoRoot: string,
78
+ scope: string,
79
+ fingerprint: string,
80
+ now: () => Date = () => new Date(),
81
+ ): boolean {
82
+ if (fingerprint.trim() === '') return false;
83
+ const obs: ContextObservation = {
84
+ schema_version: 1,
85
+ roadmap: scope,
86
+ fingerprint,
87
+ at: now().toISOString(),
88
+ };
89
+ try {
90
+ fs.mkdirSync(path.dirname(observationFile(repoRoot)), { recursive: true });
91
+ } catch {
92
+ return false;
93
+ }
94
+ return atomicWriteJson(observationFile(repoRoot), obs);
95
+ }
96
+
97
+ /** The newest recorded observation, or `null` when none reads. */
98
+ export function readContextObservation(repoRoot: string): ContextObservation | null {
99
+ try {
100
+ const parsed: unknown = JSON.parse(fs.readFileSync(observationFile(repoRoot), 'utf-8'));
101
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
102
+ const o = parsed as Record<string, unknown>;
103
+ if (typeof o['fingerprint'] !== 'string' || o['fingerprint'] === '') return null;
104
+ if (typeof o['at'] !== 'string') return null;
105
+ return {
106
+ schema_version: 1,
107
+ roadmap: typeof o['roadmap'] === 'string' ? o['roadmap'] : '',
108
+ fingerprint: o['fingerprint'],
109
+ at: o['at'],
110
+ };
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Has the plan premise moved?
118
+ *
119
+ * TRUE only when both sides are known AND differ. An unknown on either side is
120
+ * "not known", exactly as `run_checkpoint.verifyCheckpoint` treats `head` and
121
+ * `context_fingerprint`: a detector that reads "I could not tell" as "it moved"
122
+ * halts healthy runs, and one false alarm on a rung that ends a run costs more
123
+ * than a missed one, because it trains the operator to switch the rung off.
124
+ */
125
+ export function premiseMoved(
126
+ claimed: string | null | undefined,
127
+ observed: string | null | undefined,
128
+ ): boolean {
129
+ if (claimed === null || claimed === undefined || claimed === '') return false;
130
+ if (observed === null || observed === undefined || observed === '') return false;
131
+ return claimed !== observed;
132
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * The run-continuation termination ladder — the pure decision, on its own.
3
+ *
4
+ * Extracted from `hooks/run_continuation_hook.ts` by `road-to-wired-instruments`
5
+ * Phase 2, for two reasons and one constraint. The reasons: the ladder is a pure
6
+ * function over a state record and three numbers, and it is the surface Phase 2
7
+ * changes, so the rung under change gets a home where it can be read without
8
+ * 1,500 lines of I/O around it. The constraint: `check_source_size_budget` is a
9
+ * ratchet and the hook sits PAST the 1,500-line cap, where every added line is
10
+ * an added violation — so the phase pays for its additions by moving the code it
11
+ * is editing to a file under the cap, rather than by raising a baseline.
12
+ *
13
+ * `LadderState` is structural on purpose. The hook's `RunState` carries a dozen
14
+ * fields this decision has no business reading, and taking the whole record here
15
+ * would invert the dependency: a `_lib` module importing from a hook.
16
+ */
17
+
18
+ import type { UnavailableDependency } from './loop_guards.js';
19
+ import type { RunTerminalState } from './outcome_vocabularies.js';
20
+
21
+ /** Iterations one run may spend before the cap ends it. */
22
+ export const MAX_ITERATIONS = 25;
23
+ /** Wall-clock a run may span before the clock ends it. */
24
+ export const WALL_CLOCK_CAP_MS = 4 * 60 * 60 * 1000;
25
+ /** Identical consecutive open-step readings that read as a stall. */
26
+ export const STALL_WINDOW = 3;
27
+
28
+ export type LadderAction =
29
+ | 'engage'
30
+ | 'complete'
31
+ | 'blocked'
32
+ | 'halt-max-iterations'
33
+ | 'halt-wall-clock'
34
+ | 'halt-stall'
35
+ | 'halt-dependency-unavailable'
36
+ | 'halt-premise-invalidated';
37
+
38
+ /** The terminal rungs — the set `RunState.halted` may hold. */
39
+ /**
40
+ * `halt-roadmap-absent` is deliberately NOT in this union and not in
41
+ * `LadderAction`. It is an event name the caller emits directly when the claimed
42
+ * roadmap is unreadable from the authoritative tree, and it CLEARS the state
43
+ * rather than stamping `halted` — widening the union would let `parseRecord`
44
+ * accept it as a `halted` value, which would mean a run whose budget was cleared
45
+ * also carried a halt stamp. The two are mutually exclusive by construction.
46
+ */
47
+ export const HALT_ACTIONS: readonly LadderAction[] = [
48
+ 'halt-max-iterations',
49
+ 'halt-wall-clock',
50
+ 'halt-stall',
51
+ // Phase 5.4. Checked BEFORE the counter rungs: a dependency the run cannot
52
+ // obtain is not something more iterations close, so consuming the budget
53
+ // against it is the waste the rung exists to stop.
54
+ 'halt-dependency-unavailable',
55
+ // `road-to-wired-instruments` 2.2, and BEFORE the counter rungs for the same
56
+ // reason: a run whose plan premise moved does not become correct by running
57
+ // more iterations against the stale plan, and letting it cap out reports
58
+ // `exhausted` — a budget problem, whose remedy is a bigger budget — for what
59
+ // is actually a staleness problem, whose remedy is a re-probe.
60
+ 'halt-premise-invalidated',
61
+ ];
62
+
63
+ /**
64
+ * The subset of a run's state this decision reads. The hook's `RunState`
65
+ * satisfies it structurally; nothing here may widen beyond what the rungs use.
66
+ */
67
+ export interface LadderState {
68
+ readonly halted?: LadderAction;
69
+ readonly iterations: number;
70
+ readonly started_at: string;
71
+ readonly history: readonly number[];
72
+ }
73
+
74
+ /**
75
+ * The run terminal state each ladder action reports, or `null` for the one
76
+ * action that is not terminal.
77
+ *
78
+ * This is a REAL crossing between two vocabularies, and the reason it exists at
79
+ * all is that a terminal state nothing produces is the defect this roadmap is
80
+ * named after. The ladder decides in its own words — the rung a reader of the
81
+ * ledger needs — and the run vocabulary is what a closing report must say
82
+ * (`contexts/execution/terminal-states.md`). Registered as a row in
83
+ * `outcome_vocabularies.CROSS_DOMAIN_MAPPINGS`, which is the one place a reader
84
+ * looks to learn a crossing exists.
85
+ *
86
+ * `halt-wall-clock` and `halt-max-iterations` both report `exhausted`: they are
87
+ * two budgets, and the contract's word covers "a declared budget ran out"
88
+ * without distinguishing which. `halt-dependency-unavailable` reports `blocked`
89
+ * — a thing that is absent, which is that state's own definition.
90
+ */
91
+ export const TERMINAL_STATE_BY_ACTION: Readonly<Record<LadderAction, RunTerminalState | null>> = {
92
+ engage: null,
93
+ complete: 'success',
94
+ blocked: 'blocked',
95
+ 'halt-max-iterations': 'exhausted',
96
+ 'halt-wall-clock': 'exhausted',
97
+ 'halt-stall': 'stagnated',
98
+ 'halt-dependency-unavailable': 'blocked',
99
+ 'halt-premise-invalidated': 'premise-invalidated',
100
+ };
101
+
102
+ /** The run terminal state for a ladder action; `null` while the run continues. */
103
+ export function terminalStateFor(action: LadderAction): RunTerminalState | null {
104
+ return TERMINAL_STATE_BY_ACTION[action] ?? null;
105
+ }
106
+
107
+ /**
108
+ * The termination ladder, pure. `state` is the record BEFORE this stop;
109
+ * `openCount` is the scan of the claimed roadmap as it stands now.
110
+ */
111
+ export function ladder(
112
+ state: LadderState,
113
+ openCount: number,
114
+ nowMs: number,
115
+ // No default: `= 0` would silently restore the complete-instead-of-blocked
116
+ // behaviour for any caller that omitted it.
117
+ blockedCount: number,
118
+ caps: { maxIterations: number; wallClockMs: number; stallWindow: number } = {
119
+ maxIterations: MAX_ITERATIONS,
120
+ wallClockMs: WALL_CLOCK_CAP_MS,
121
+ stallWindow: STALL_WINDOW,
122
+ },
123
+ // Phase 5.4. `null` when nothing unobtainable was detected. Optional so every
124
+ // existing caller and test keeps its meaning unchanged.
125
+ unavailable: UnavailableDependency | null = null,
126
+ // `road-to-wired-instruments` 2.1. TRUE only when the run's engaged-under
127
+ // fingerprint and its newest observation are BOTH known and differ — see
128
+ // `_lib/context_observation.premiseMoved`. Optional and defaulted false, so
129
+ // a caller that cannot observe the world decides exactly as it did before.
130
+ premiseInvalidated: boolean = false,
131
+ ): LadderAction {
132
+ // A halt is terminal for this run id. Checked BEFORE `complete` so a
133
+ // halted run whose roadmap later reads zero-open does not report a
134
+ // completion it never reached.
135
+ if (state.halted) return state.halted;
136
+ // `scanOpenSteps` EXCLUDES `blocked-by:` steps from `openCount`, so zero-open
137
+ // with blocked steps left is exhaustion of runnable work, not completion —
138
+ // ADR-235's own terminal outcome, and never a sixth halt.
139
+ if (openCount === 0) return blockedCount > 0 ? 'blocked' : 'complete';
140
+ // BEFORE the counter rungs, deliberately: a missing credential, an absent
141
+ // binary or an exhausted quota is not closed by iterating, so spending the
142
+ // budget on it converts a nameable blocker into an anonymous cap-out.
143
+ if (unavailable !== null) return 'halt-dependency-unavailable';
144
+ // 2.2 — also before the counter rungs, and AFTER the zero-open rungs: a run
145
+ // that has actually finished its scope reports `complete`, because a stale
146
+ // premise cannot un-finish work already done and verified. Everything below
147
+ // this line is a budget, and a budget is the wrong word for staleness.
148
+ if (premiseInvalidated) return 'halt-premise-invalidated';
149
+ if (state.iterations >= caps.maxIterations) return 'halt-max-iterations';
150
+ const started = Date.parse(state.started_at);
151
+ if (Number.isFinite(started) && nowMs - started >= caps.wallClockMs) {
152
+ return 'halt-wall-clock';
153
+ }
154
+ const tail = state.history.slice(-caps.stallWindow);
155
+ if (tail.length >= caps.stallWindow && tail.every((n) => n === openCount)) {
156
+ return 'halt-stall';
157
+ }
158
+ return 'engage';
159
+ }
160
+
161
+ /**
162
+ * Read a persisted `halted` stamp tolerantly.
163
+ *
164
+ * `road-to-wired-instruments` 2.3. `LadderAction` is the SECOND value domain
165
+ * this phase widened — the run-state file records the rung that ended a run —
166
+ * so it owes the same forward-compatibility answer the run vocabulary got. The
167
+ * previous reader accepted only members of `HALT_ACTIONS` and dropped anything
168
+ * else, which does not crash but downgrades a halt stamped by a newer build to
169
+ * NO HALT: the older binary then re-engages a run that was deliberately ended.
170
+ * Fail-open in the one direction a budget must not fail open.
171
+ *
172
+ * So an unrecognised `halt-`prefixed value is PRESERVED. That is safe by
173
+ * inspection of every consumer: `ladder` returns a stamp verbatim, and the
174
+ * caller's branches are equality tests against known names whose default is
175
+ * "allow this stop and write nothing" — the correct behaviour for a halt whose
176
+ * name this build cannot interpret. The old reader's docblock claimed such a
177
+ * value would "become an action no branch below handles"; that was never true
178
+ * for a halt-prefixed one, and the claim is corrected here rather than carried.
179
+ *
180
+ * Anything not `halt-`prefixed is still dropped: `engage`, `complete` and
181
+ * `blocked` are not stamps, and accepting one here would immortalise a state
182
+ * file that the terminal branches delete.
183
+ */
184
+ export function parseHaltStamp(v: unknown): LadderAction | null {
185
+ if (typeof v !== 'string') return null;
186
+ if ((HALT_ACTIONS as readonly string[]).includes(v)) return v as LadderAction;
187
+ return v.startsWith('halt-') ? (v as LadderAction) : null;
188
+ }