@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.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +138 -376
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +36 -0
- package/dist/agent-src/commands/pr/create.md +16 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +25 -4
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- package/dist/agent-src/rules/missing-skill-recovery.md +2 -2
- package/dist/agent-src/rules/token-budget-discipline.md +2 -2
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
- package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
- package/dist/agent-src/scripts/stubs_due.ts +118 -2
- package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli-delegate/{chunk-ILMUPLGW.js → chunk-7RXIKF62.js} +159 -86
- package/dist/cli-delegate/cmd_conformance.js +39 -2
- package/dist/cli-delegate/cmd_doctor.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +7 -7
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1696 -812
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +297 -115
- package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
- package/docs/guidelines/code-clarity.md +18 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
- package/src/agent-src/scripts/guarded_baseline.ts +22 -7
- package/src/agent-src/scripts/stubs_due.ts +118 -2
- package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
- package/src/config/assurance-capability-registry.json +1 -1
- package/src/config/estate-count-budget.json +20 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +211 -1
- package/src/config/gate-reachability-exemptions.json +1 -0
- package/src/config/gate-violation-baselines.json +9 -3
- package/src/config/preamble-payload-budget.json +19 -11
- package/src/config/rule-stub-ceilings.json +14 -7
- package/src/config/web-launch-readiness.json +4 -2
- package/src/scripts/_cli/_doctor_emit.ts +71 -0
- package/src/scripts/_cli/_doctor_strict.ts +163 -0
- package/src/scripts/_cli/cmd_doctor.ts +16 -37
- package/src/scripts/_dispatch.bash +11 -4
- package/src/scripts/_lib/activation_ladder.ts +47 -3
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/branch_convergence.ts +186 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/context_observation.ts +132 -0
- package/src/scripts/_lib/continuation_ladder.ts +188 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/experiment_binding.ts +95 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
- package/src/scripts/_lib/ignored_blocker.ts +8 -1
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/outcome_envelope.ts +6 -1
- package/src/scripts/_lib/outcome_vocabularies.ts +135 -5
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/release_highlights.ts +79 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/repeated_failure.ts +19 -2
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/runtime_journal.ts +11 -4
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/check_adr_frontmatter.ts +4 -9
- package/src/scripts/check_agent_artifact_location.ts +337 -0
- package/src/scripts/check_artefact_count_messaging.ts +74 -8
- package/src/scripts/check_enforcement_coverage.ts +40 -1
- package/src/scripts/check_estate_count.ts +205 -24
- package/src/scripts/check_release_highlights.ts +113 -14
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/check_web_launch_readiness.ts +310 -20
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/evolution_lab.ts +234 -9
- package/src/scripts/generate_capability_matrix.ts +16 -9
- package/src/scripts/grade_target_readiness.ts +221 -2
- package/src/scripts/hook_effect_doctor.ts +47 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +28 -6
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/run_continuation_hook.ts +78 -88
- package/src/scripts/hooks/session_eol_hook.ts +12 -1
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/install-hooks.sh +18 -0
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +748 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/model_rule_injection.ts +19 -3
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +199 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +142 -0
- package/src/scripts/roadmap_context.ts +15 -0
- package/src/scripts/skill_scout.ts +633 -0
- 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
|
+
}
|