@event4u/agent-config 16.0.0 → 16.1.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.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +297 -137
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +50 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +188 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +90 -56
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +85 -38
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +1832 -1007
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2007 -1481
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +398 -39
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +17 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +26 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/config/agent-settings.template.yml +139 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +18 -5
- package/src/config/continuity-surface.json +21 -0
- package/src/config/gate-coverage.yml +146 -13
- package/src/config/gate-violation-baselines.json +6 -6
- package/src/config/hook-token-budget.json +2 -0
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +4 -3
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +90 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +402 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/release_highlights.ts +199 -10
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/closure_scan.ts +333 -0
- package/src/scripts/compile_hook_manifest.ts +43 -3
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +88 -9
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/evidence_independence.ts +96 -0
- package/src/scripts/hooks/memory_recall_hook.ts +540 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
- package/src/scripts/hooks/rule_inject_hook.ts +49 -1
- package/src/scripts/hooks/run_continuation_hook.ts +17 -20
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +106 -206
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_hook_manifest.ts +11 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +61 -0
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +51 -0
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/schemas/settings.ts +31 -7
- package/src/shared/missionExecution.ts +126 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime activation payload — what a trigger match charges per fire.
|
|
3
|
+
*
|
|
4
|
+
* The third of the three numbers `road-to-the-delivery-flip-that-tells-the-truth`
|
|
5
|
+
* Phase 3 separates. The other two already have names and a record: ADR-270
|
|
6
|
+
* calls them `source_corpus` (what the source tree could deliver) and
|
|
7
|
+
* `host_payload` (what one host actually receives at rest), and requires them to
|
|
8
|
+
* be structurally separate fields because "until the two are named apart, every
|
|
9
|
+
* ceiling discussion risks comparing unlike quantities". After a per-host
|
|
10
|
+
* projection flip a third quantity diverges from both: the bytes a delivery
|
|
11
|
+
* concern injects when a rule's trigger fires. A standing reduction published
|
|
12
|
+
* without it is a saving published without its cost.
|
|
13
|
+
*
|
|
14
|
+
* MEASURED THROUGH THE CONCERN'S OWN SELECTION, never a second model of it.
|
|
15
|
+
* `matchTierRules` + `selectForInjection` are the functions the runtime concern
|
|
16
|
+
* calls; an offline figure computed by a different matcher would price a set the
|
|
17
|
+
* concern does not deliver. That is the same single-definition rule
|
|
18
|
+
* `_lib/rule_injection.ts` was written for, applied one layer out.
|
|
19
|
+
*
|
|
20
|
+
* THE CAP IS A PARAMETER, not an import. The value lives on the concern
|
|
21
|
+
* (`hooks/rule_inject_hook.ts::CAP_BYTES`) and every caller passes it, so this
|
|
22
|
+
* module stays free of the hook's import closure and can be read from a gate, a
|
|
23
|
+
* report, or a bundled path without dragging a hook into it.
|
|
24
|
+
*
|
|
25
|
+
* Moved here from `report_standing_payload_by_host.ts`, which had the only
|
|
26
|
+
* implementation and is still its largest caller — same move, and for the same
|
|
27
|
+
* reason, as `censusRuleDir` into `_lib/carrier_divergence.ts`: a second reader
|
|
28
|
+
* needed it and a second copy is how two numbers start disagreeing.
|
|
29
|
+
*/
|
|
30
|
+
import * as fs from 'node:fs';
|
|
31
|
+
import * as path from 'node:path';
|
|
32
|
+
|
|
33
|
+
import { loadRouter, matchTierRules, selectForInjection } from './rule_injection.js';
|
|
34
|
+
|
|
35
|
+
/** The frozen corpus every fire figure in this module is sampled from. */
|
|
36
|
+
export const ROUTING_MATRIX_REL = path.join('tests', 'eval', 'routing-matrix');
|
|
37
|
+
|
|
38
|
+
/** One positive prompt of the frozen corpus, with the open files it declares. */
|
|
39
|
+
export interface CorpusPrompt {
|
|
40
|
+
prompt: string;
|
|
41
|
+
openFiles: string[] | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Every YAML shape inside `positives:` this hand parser cannot read.
|
|
46
|
+
*
|
|
47
|
+
* `readCorpusPositives` accepts only `- prompt: "…"` with a double-quoted
|
|
48
|
+
* same-line scalar and an inline `open_files: [...]`. Any other legal shape —
|
|
49
|
+
* single quotes, an unquoted scalar, a folded block, a block sequence for
|
|
50
|
+
* `open_files` — is SILENTLY SKIPPED, which drops fires out of the distribution
|
|
51
|
+
* a budget row is derived from. A measurement that governs a budget must fail
|
|
52
|
+
* loudly on a shape it cannot read rather than under-count, so callers refuse on
|
|
53
|
+
* a non-empty result here.
|
|
54
|
+
*
|
|
55
|
+
* Reported as a shape problem rather than fixed by widening the parser: this
|
|
56
|
+
* module must not grow a second YAML implementation, and the corpus is frozen,
|
|
57
|
+
* so the honest answer to an unreadable shape is to say which file carries it.
|
|
58
|
+
*/
|
|
59
|
+
export function corpusShapeProblems(root: string): string[] {
|
|
60
|
+
const dir = path.join(root, ROUTING_MATRIX_REL);
|
|
61
|
+
const problems: string[] = [];
|
|
62
|
+
if (!fs.existsSync(dir)) return problems;
|
|
63
|
+
for (const f of fs.readdirSync(dir).filter((n) => n.endsWith('.yaml')).sort()) {
|
|
64
|
+
let inPositives = false;
|
|
65
|
+
let lineNo = 0;
|
|
66
|
+
for (const raw of fs.readFileSync(path.join(dir, f), 'utf-8').split('\n')) {
|
|
67
|
+
lineNo += 1;
|
|
68
|
+
if (/^positives:/u.test(raw)) { inPositives = true; continue; }
|
|
69
|
+
if (/^near_misses:/u.test(raw)) { inPositives = false; continue; }
|
|
70
|
+
if (!inPositives) continue;
|
|
71
|
+
if (/^\s*-\s*prompt:/u.test(raw) && !/^\s*-\s*prompt:\s*"(.*)"\s*$/u.test(raw)) {
|
|
72
|
+
problems.push(
|
|
73
|
+
`${f}:${String(lineNo)}: a \`- prompt:\` this parser cannot read — only a ` +
|
|
74
|
+
`double-quoted same-line scalar is accepted: ${JSON.stringify(raw.trim())}`,
|
|
75
|
+
);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (/^\s*-\s*/u.test(raw) && !/^\s*-\s*(prompt|command|open_files):/u.test(raw)
|
|
79
|
+
&& raw.trim() !== '-' && raw.trim() !== '') {
|
|
80
|
+
// A bare sequence item under `positives:` is a shape with no
|
|
81
|
+
// `prompt:` key on its first line — folded or nested.
|
|
82
|
+
if (!/^\s{4,}/u.test(raw)) {
|
|
83
|
+
problems.push(
|
|
84
|
+
`${f}:${String(lineNo)}: a positives entry whose first line carries no ` +
|
|
85
|
+
`\`prompt:\` key: ${JSON.stringify(raw.trim())}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (/^\s*open_files:\s*$/u.test(raw)) {
|
|
90
|
+
problems.push(
|
|
91
|
+
`${f}:${String(lineNo)}: a BLOCK-sequence \`open_files:\` — only the inline ` +
|
|
92
|
+
'`[...]` form is read, so this entry\'s files would be dropped',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return problems;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Every positive prompt in the frozen routing-matrix corpus. Near-misses are not fires. */
|
|
101
|
+
export function readCorpusPositives(root: string): CorpusPrompt[] {
|
|
102
|
+
const dir = path.join(root, ROUTING_MATRIX_REL);
|
|
103
|
+
const out: CorpusPrompt[] = [];
|
|
104
|
+
if (!fs.existsSync(dir)) return out;
|
|
105
|
+
for (const f of fs.readdirSync(dir).filter((n) => n.endsWith('.yaml')).sort()) {
|
|
106
|
+
let inPositives = false;
|
|
107
|
+
let cur: CorpusPrompt | null = null;
|
|
108
|
+
for (const raw of fs.readFileSync(path.join(dir, f), 'utf-8').split('\n')) {
|
|
109
|
+
if (/^positives:/u.test(raw)) { inPositives = true; continue; }
|
|
110
|
+
if (/^near_misses:/u.test(raw)) { inPositives = false; continue; }
|
|
111
|
+
if (!inPositives) continue;
|
|
112
|
+
const p = /^\s*-\s*prompt:\s*"(.*)"\s*$/u.exec(raw);
|
|
113
|
+
if (p !== null) {
|
|
114
|
+
cur = { prompt: p[1] as string, openFiles: null };
|
|
115
|
+
out.push(cur);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const of = /^\s*open_files:\s*\[(.*)\]\s*$/u.exec(raw);
|
|
119
|
+
if (of !== null && cur !== null) {
|
|
120
|
+
cur.openFiles = (of[1] as string)
|
|
121
|
+
.split(',')
|
|
122
|
+
.map((x) => x.trim().replace(/^["']|["']$/gu, ''))
|
|
123
|
+
.filter((x) => x !== '');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Nearest-rank percentile over an unsorted sample. Empty sample → 0. */
|
|
131
|
+
export function percentile(xs: readonly number[], q: number): number {
|
|
132
|
+
if (xs.length === 0) return 0;
|
|
133
|
+
const s = [...xs].sort((a, b) => a - b);
|
|
134
|
+
return s[Math.min(s.length - 1, Math.floor(q * (s.length - 1)))] as number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface SlotFireSizes {
|
|
138
|
+
readonly slot: string;
|
|
139
|
+
readonly fires: number;
|
|
140
|
+
readonly p50: number;
|
|
141
|
+
readonly p90: number;
|
|
142
|
+
readonly max: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Gate-open per-fire emission sizes for the delivery concern, per slot.
|
|
147
|
+
*
|
|
148
|
+
* `pre_compact` is 0 by construction rather than by measurement: that branch of
|
|
149
|
+
* `rule_inject_hook` clears the seen-set and returns allow without writing to
|
|
150
|
+
* stdout, so there is no emission to sample.
|
|
151
|
+
*/
|
|
152
|
+
export function slotFireSizes(root: string, capBytes: number): SlotFireSizes[] {
|
|
153
|
+
const router = loadRouter(root);
|
|
154
|
+
const prompts = readCorpusPositives(root);
|
|
155
|
+
const sample = (mode: 'prompt' | 'file'): number[] => {
|
|
156
|
+
const out: number[] = [];
|
|
157
|
+
for (const r of prompts) {
|
|
158
|
+
if (mode === 'file' && (r.openFiles === null || r.openFiles.length === 0)) continue;
|
|
159
|
+
const matches =
|
|
160
|
+
mode === 'prompt'
|
|
161
|
+
? matchTierRules(router, r.prompt, null, null)
|
|
162
|
+
: matchTierRules(router, '', r.openFiles, null);
|
|
163
|
+
if (matches.length === 0) continue;
|
|
164
|
+
const sel = selectForInjection(root, matches, capBytes);
|
|
165
|
+
if (sel.selected.length > 0) out.push(sel.bytes);
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
};
|
|
169
|
+
const row = (slot: string, xs: number[]): SlotFireSizes => ({
|
|
170
|
+
slot,
|
|
171
|
+
fires: xs.length,
|
|
172
|
+
p50: percentile(xs, 0.5),
|
|
173
|
+
p90: percentile(xs, 0.9),
|
|
174
|
+
max: xs.length === 0 ? 0 : Math.max(...xs),
|
|
175
|
+
});
|
|
176
|
+
return [
|
|
177
|
+
row('user_prompt_submit', sample('prompt')),
|
|
178
|
+
row('pre_tool_use', sample('file')),
|
|
179
|
+
row('pre_compact', []),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The third labelled number, or the reason it could not be taken on this tree. */
|
|
184
|
+
export interface ActivationPayload {
|
|
185
|
+
/** Always the literal `activation-payload` — the label a reader and a caller key on. */
|
|
186
|
+
readonly scope: 'activation-payload';
|
|
187
|
+
/** `false` when the corpus or the router is absent; every figure is then 0. */
|
|
188
|
+
readonly measured: boolean;
|
|
189
|
+
/** Why it is unmeasured, or `null`. Never an estimate in place of a number. */
|
|
190
|
+
readonly unmeasured_reason: string | null;
|
|
191
|
+
/** The cap the sampled selection was taken at — the concern's `CAP_BYTES`. */
|
|
192
|
+
readonly cap_bytes: number;
|
|
193
|
+
readonly corpus: string;
|
|
194
|
+
readonly slots: readonly SlotFireSizes[];
|
|
195
|
+
readonly basis: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The runtime activation payload, or an honestly unmeasured reading.
|
|
200
|
+
*
|
|
201
|
+
* Never throws and never estimates: a missing corpus or router yields
|
|
202
|
+
* `measured: false` with the reason named, because a fabricated distribution
|
|
203
|
+
* published beside a standing saving is the exact defect this number exists to
|
|
204
|
+
* prevent.
|
|
205
|
+
*/
|
|
206
|
+
export function measureActivationPayload(root: string, capBytes: number): ActivationPayload {
|
|
207
|
+
const basis =
|
|
208
|
+
'Gate-open per-fire emission of the `rule-inject` concern over the frozen ' +
|
|
209
|
+
'`tests/eval/routing-matrix` positives, measured through the same ' +
|
|
210
|
+
'`matchTierRules` + `selectForInjection` the runtime concern calls, at the ' +
|
|
211
|
+
'concern\'s own CAP_BYTES. Bytes per fire, never tokens — the unit ' +
|
|
212
|
+
'`hook-token-budget.json` enforces.';
|
|
213
|
+
const empty = (reason: string): ActivationPayload => ({
|
|
214
|
+
scope: 'activation-payload',
|
|
215
|
+
measured: false,
|
|
216
|
+
unmeasured_reason: reason,
|
|
217
|
+
cap_bytes: capBytes,
|
|
218
|
+
corpus: ROUTING_MATRIX_REL,
|
|
219
|
+
slots: [],
|
|
220
|
+
basis,
|
|
221
|
+
});
|
|
222
|
+
if (!fs.existsSync(path.join(root, ROUTING_MATRIX_REL))) {
|
|
223
|
+
return empty(`the frozen corpus ${ROUTING_MATRIX_REL} is absent under ${root}`);
|
|
224
|
+
}
|
|
225
|
+
const shape = corpusShapeProblems(root);
|
|
226
|
+
if (shape.length > 0) {
|
|
227
|
+
return empty(
|
|
228
|
+
`${String(shape.length)} corpus entry/entries are in a shape the reader cannot parse, ` +
|
|
229
|
+
`so the distribution would be under-counted — first: ${shape[0] as string}`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
let slots: SlotFireSizes[];
|
|
233
|
+
try {
|
|
234
|
+
slots = slotFireSizes(root, capBytes);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
return empty(`the fire sample could not be taken: ${(err as Error).message}`);
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
scope: 'activation-payload',
|
|
240
|
+
measured: true,
|
|
241
|
+
unmeasured_reason: null,
|
|
242
|
+
cap_bytes: capBytes,
|
|
243
|
+
corpus: ROUTING_MATRIX_REL,
|
|
244
|
+
slots,
|
|
245
|
+
basis,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
@@ -53,6 +53,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
53
53
|
|
|
54
54
|
import * as user_global_paths from './user_global_paths.js';
|
|
55
55
|
import { carveOutKeys } from '../../shared/settingsCarveOut.js';
|
|
56
|
+
import { applyRenamedKeys } from './settings_renamed_keys.js';
|
|
56
57
|
import type * as YamlModule from 'yaml';
|
|
57
58
|
|
|
58
59
|
// ESM has no `require`; `createRequire(import.meta.url)` restores it so the
|
|
@@ -956,9 +957,9 @@ export function load_agent_settings(
|
|
|
956
957
|
// precedence here is the inverse of the server family's, so the order of
|
|
957
958
|
// these three statements is the whole contract.
|
|
958
959
|
const merged: SettingsDict = template_defaults(template_path ?? undefined);
|
|
959
|
-
_deep_merge(merged, user_global_filtered);
|
|
960
|
+
_deep_merge(merged, applyRenamedKeys(user_global_filtered));
|
|
960
961
|
for (const p of cascade) {
|
|
961
|
-
const layer = _read_yaml(p) ?? {};
|
|
962
|
+
const layer = applyRenamedKeys(_read_yaml(p) ?? {});
|
|
962
963
|
if (Object.keys(layer).length > 0) {
|
|
963
964
|
_deep_merge(merged, layer);
|
|
964
965
|
}
|
|
@@ -967,6 +968,7 @@ export function load_agent_settings(
|
|
|
967
968
|
return merged;
|
|
968
969
|
}
|
|
969
970
|
|
|
971
|
+
|
|
970
972
|
/**
|
|
971
973
|
* Settings keys this package has DELETED. A leftover value from an older
|
|
972
974
|
* install is ignored — never applied by any reader — and surfaced with ONE
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The strictest path, reserved for authority —
|
|
3
|
+
* `road-to-adversarial-verification-and-long-runs` 11.1.
|
|
4
|
+
*
|
|
5
|
+
* An authority-expanding change takes four independent passes — an independent
|
|
6
|
+
* TEST author, an independent GOVERNANCE reviewer, a COUNCIL pass, and a
|
|
7
|
+
* PROVIDER-DIVERSE reviewer — plus the ratification artefact. Until the artefact
|
|
8
|
+
* carries `verdict: ratified`, the new authority is **inert**.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* NEW AUTHORITY IS INERT UNTIL RATIFIED. AN AGENT MAY AUTHOR THE CHANGE
|
|
12
|
+
* AND MAY NEVER RATIFY ITS OWN INCREASE IN POWER (ADR-268 § 4).
|
|
13
|
+
* FOUR PASSES, ALL FOUR, AND THEY MAY NOT BE THE SAME REVIEWER TWICE.
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* **Inert-until-ratified is the property that makes the rest safe.** Without it
|
|
17
|
+
* the four passes are a process an author could complete and then merge; with it
|
|
18
|
+
* the change can land, be read, and still do nothing — so the ratification is a
|
|
19
|
+
* separate act on a separate turn by a party that is not the author.
|
|
20
|
+
*
|
|
21
|
+
* Pure.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The four passes. All four, and the list is closed. */
|
|
25
|
+
export const REQUIRED_PASSES = [
|
|
26
|
+
'independent-test-author',
|
|
27
|
+
'independent-governance-reviewer',
|
|
28
|
+
'council',
|
|
29
|
+
'provider-diverse-reviewer',
|
|
30
|
+
] as const;
|
|
31
|
+
export type Pass = (typeof REQUIRED_PASSES)[number];
|
|
32
|
+
|
|
33
|
+
export interface PassRecord {
|
|
34
|
+
readonly pass: Pass;
|
|
35
|
+
/** Who performed it. Two passes by the same identity is not two passes. */
|
|
36
|
+
readonly by: string;
|
|
37
|
+
/** The provider, so provider-diversity is checkable rather than asserted. */
|
|
38
|
+
readonly provider: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RatificationArtefact {
|
|
42
|
+
/** Anything other than `ratified` leaves the authority inert. */
|
|
43
|
+
readonly verdict: string;
|
|
44
|
+
readonly passes: readonly PassRecord[];
|
|
45
|
+
/** The author of the change under ratification. */
|
|
46
|
+
readonly author: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type AuthorityState = 'inert' | 'active';
|
|
50
|
+
|
|
51
|
+
export interface AuthorityVerdict {
|
|
52
|
+
readonly state: AuthorityState;
|
|
53
|
+
readonly blockers: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Is the new authority active yet?
|
|
58
|
+
*
|
|
59
|
+
* Returns EVERY blocker rather than the first: the artefact is a checklist a
|
|
60
|
+
* human completes, and handing them one item at a time turns four passes into
|
|
61
|
+
* four round trips.
|
|
62
|
+
*/
|
|
63
|
+
export function authorityState(a: RatificationArtefact | null): AuthorityVerdict {
|
|
64
|
+
if (a === null) {
|
|
65
|
+
return { state: 'inert', blockers: ['no ratification artefact exists'] };
|
|
66
|
+
}
|
|
67
|
+
const blockers: string[] = [];
|
|
68
|
+
|
|
69
|
+
for (const required of REQUIRED_PASSES) {
|
|
70
|
+
if (!a.passes.some((p) => p.pass === required)) {
|
|
71
|
+
blockers.push(`missing pass: ${required}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The author may perform NONE of the four. This is the ADR-268 § 4 clause
|
|
76
|
+
// and the one a process can satisfy on paper while violating in substance.
|
|
77
|
+
for (const p of a.passes) {
|
|
78
|
+
if (p.by === a.author) {
|
|
79
|
+
blockers.push(`${p.pass} was performed by the author (${p.by}) — an agent may not ratify its own increase in power`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Provider diversity is a property of the SET, so it is checked over the set
|
|
84
|
+
// rather than trusted from the pass that carries the name.
|
|
85
|
+
const providers = new Set(a.passes.map((p) => p.provider));
|
|
86
|
+
if (a.passes.length > 1 && providers.size < 2) {
|
|
87
|
+
blockers.push(
|
|
88
|
+
`every pass ran on one provider (${[...providers].join(', ')}) — provider-diverse means at least two`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (a.verdict !== 'ratified') {
|
|
93
|
+
blockers.push(`verdict is "${a.verdict}", not "ratified"`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { state: blockers.length === 0 ? 'active' : 'inert', blockers };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** `true` only for an artefact that clears everything. */
|
|
100
|
+
export function isRatified(a: RatificationArtefact | null): boolean {
|
|
101
|
+
return authorityState(a).state === 'active';
|
|
102
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cascade base — `road-to-adversarial-verification-and-long-runs` 5.1.
|
|
3
|
+
*
|
|
4
|
+
* `check_branch_freshness` asks whether a branch is behind THE BASE ITS PR
|
|
5
|
+
* TARGETS. That is one hop, and it is the right question for the common case
|
|
6
|
+
* where the base is the trunk. On a stacked PR it is only half of it: a branch
|
|
7
|
+
* targeting `feat/parent` can be perfectly current with `feat/parent` while
|
|
8
|
+
* `feat/parent` itself is fifty commits behind `main`, so the branch is stale
|
|
9
|
+
* against the tree it will eventually land in and every gate reports green.
|
|
10
|
+
*
|
|
11
|
+
* The cascade is the ordered list of refs a branch must be current with, nearest
|
|
12
|
+
* first. Merging in that order is not arbitrary: taking the trunk first would
|
|
13
|
+
* pull trunk commits past the parent and make the stack's own diff unreadable,
|
|
14
|
+
* which is the thing a stack exists to avoid.
|
|
15
|
+
*
|
|
16
|
+
* **Depth is bounded and the bound is deliberate.** A cascade is a chain of
|
|
17
|
+
* bases, and a chain can cycle — a branch whose PR targets a branch that targets
|
|
18
|
+
* it. `MAX_HOPS` stops the walk and the result says it was truncated, because
|
|
19
|
+
* a hop list silently cut short is indistinguishable from a short one.
|
|
20
|
+
*
|
|
21
|
+
* Pure: the caller supplies the base-of lookup, so the whole thing is testable
|
|
22
|
+
* without a forge.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** How many bases a cascade may chain through before the walk stops. */
|
|
26
|
+
export const MAX_HOPS = 8;
|
|
27
|
+
|
|
28
|
+
export interface Cascade {
|
|
29
|
+
/** Refs to merge in, nearest base first, trunk last. Never includes `branch`. */
|
|
30
|
+
readonly hops: readonly string[];
|
|
31
|
+
/** True when the walk stopped on a cycle rather than on the trunk. */
|
|
32
|
+
readonly cycle: boolean;
|
|
33
|
+
/** True when the walk stopped on {@link MAX_HOPS}. */
|
|
34
|
+
readonly truncated: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `null` when the ref has no PR base — i.e. it is a trunk or is unknown. */
|
|
38
|
+
export type BaseOf = (ref: string) => string | null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Walk from `branch` to `trunk`, collecting every base on the way.
|
|
42
|
+
*
|
|
43
|
+
* A branch whose base IS the trunk yields one hop — identical to the single-hop
|
|
44
|
+
* behaviour that existed before, which is the compatibility property that
|
|
45
|
+
* matters: this widens the check, it does not change the common case.
|
|
46
|
+
*/
|
|
47
|
+
export function cascadeBase(branch: string, trunk: string, baseOf: BaseOf): Cascade {
|
|
48
|
+
const hops: string[] = [];
|
|
49
|
+
const seen = new Set<string>([branch]);
|
|
50
|
+
let current = branch;
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < MAX_HOPS; i += 1) {
|
|
53
|
+
const base = baseOf(current);
|
|
54
|
+
if (base === null || base === '') {
|
|
55
|
+
// No PR base. The trunk is still owed unless we are already on it.
|
|
56
|
+
if (current !== trunk && !hops.includes(trunk)) hops.push(trunk);
|
|
57
|
+
return { hops, cycle: false, truncated: false };
|
|
58
|
+
}
|
|
59
|
+
if (seen.has(base)) {
|
|
60
|
+
return { hops, cycle: true, truncated: false };
|
|
61
|
+
}
|
|
62
|
+
seen.add(base);
|
|
63
|
+
hops.push(base);
|
|
64
|
+
if (base === trunk) {
|
|
65
|
+
return { hops, cycle: false, truncated: false };
|
|
66
|
+
}
|
|
67
|
+
current = base;
|
|
68
|
+
}
|
|
69
|
+
return { hops, cycle: false, truncated: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One ref's freshness, as the caller measured it. */
|
|
73
|
+
export interface HopReading {
|
|
74
|
+
readonly ref: string;
|
|
75
|
+
/** `true` behind · `false` current · `null` not measured. */
|
|
76
|
+
readonly behind: boolean | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Which hops still need merging, in order.
|
|
81
|
+
*
|
|
82
|
+
* An UNMEASURED hop is returned alongside the behind ones rather than skipped:
|
|
83
|
+
* "we could not tell" and "it is current" are different states, and treating the
|
|
84
|
+
* first as the second is how a single-hop check reported green on a stale stack
|
|
85
|
+
* in the first place.
|
|
86
|
+
*/
|
|
87
|
+
export function hopsNeedingMerge(readings: readonly HopReading[]): HopReading[] {
|
|
88
|
+
return readings.filter((r) => r.behind !== false);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** True only when every hop was measured and every hop is current. */
|
|
92
|
+
export function cascadeCurrent(readings: readonly HopReading[]): boolean {
|
|
93
|
+
return readings.length > 0 && readings.every((r) => r.behind === false);
|
|
94
|
+
}
|
|
@@ -231,8 +231,13 @@ export function archive_file_header(boundary: string): string {
|
|
|
231
231
|
'> `tests/test_changelog_eras.py`.\n' +
|
|
232
232
|
'>\n' +
|
|
233
233
|
'> **Read-only.** New entries land in `CHANGELOG.md`. Entries\n' +
|
|
234
|
-
'> here are not
|
|
235
|
-
'> for what shipped
|
|
234
|
+
'> here are not rewritten — git tags remain the canonical source\n' +
|
|
235
|
+
'> for what shipped. The one amendment an archive permits is a\n' +
|
|
236
|
+
'> **correction that adds**: where a published entry says something\n' +
|
|
237
|
+
'> the shipped tree contradicts, the correction is appended beside\n' +
|
|
238
|
+
'> it and names the commit that decided the matter. Nothing is\n' +
|
|
239
|
+
'> deleted, so both the original claim and its correction stay\n' +
|
|
240
|
+
'> readable.\n' +
|
|
236
241
|
'>\n' +
|
|
237
242
|
'> Entry shape follows\n' +
|
|
238
243
|
'> [`../contracts/CHANGELOG-conventions.md`](../contracts/CHANGELOG-conventions.md).\n' +
|
|
@@ -60,6 +60,122 @@ export const HALT_ACTIONS: readonly LadderAction[] = [
|
|
|
60
60
|
'halt-premise-invalidated',
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The delivery state machine — `road-to-adversarial-verification-and-long-runs`
|
|
65
|
+
* Phase 8.1, in the order a run walks it.
|
|
66
|
+
*
|
|
67
|
+
* A run's checkboxes and its DELIVERY are two different completions, and until
|
|
68
|
+
* now only the first one could end a run. That is the defect: a run whose
|
|
69
|
+
* roadmap reads zero-open while its PR sits on red CI, or on a target that moved
|
|
70
|
+
* underneath it, reported `complete` — a completion claim about work whose only
|
|
71
|
+
* evidence (the CI verdict on the final head) says otherwise.
|
|
72
|
+
*
|
|
73
|
+
* Two of these are ENDINGS and the rest are not:
|
|
74
|
+
* · `merged` — a grant existed and was spent.
|
|
75
|
+
* · `open-green` — no grant; the PR is open, its CI is green on the head CI
|
|
76
|
+
* actually observed, and the run says so. This is a success, not a
|
|
77
|
+
* shortfall: a run without a merge grant is not supposed to merge.
|
|
78
|
+
* Every earlier position means work remains, whatever the checkboxes read.
|
|
79
|
+
*/
|
|
80
|
+
export const DELIVERY_STATES = [
|
|
81
|
+
'working',
|
|
82
|
+
'local-green',
|
|
83
|
+
'pushed',
|
|
84
|
+
'pr-open',
|
|
85
|
+
'ci-pending',
|
|
86
|
+
'ci-red',
|
|
87
|
+
'target-sync-check',
|
|
88
|
+
'target-moved',
|
|
89
|
+
'delivery-ready',
|
|
90
|
+
'merged',
|
|
91
|
+
'open-green',
|
|
92
|
+
] as const;
|
|
93
|
+
export type DeliveryState = (typeof DELIVERY_STATES)[number];
|
|
94
|
+
|
|
95
|
+
/** The two positions at which a run has actually delivered. */
|
|
96
|
+
export const DELIVERY_ENDINGS: readonly DeliveryState[] = ['merged', 'open-green'];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* True when this delivery position means work remains.
|
|
100
|
+
*
|
|
101
|
+
* `null` — nothing recorded a position — is NOT incomplete. The ladder runs on
|
|
102
|
+
* the Stop path, where a `gh` probe is not affordable, so the position is READ
|
|
103
|
+
* from what the run wrote rather than measured here. An unrecorded position
|
|
104
|
+
* therefore means *this run does not report delivery*, and inventing
|
|
105
|
+
* incompleteness from that absence would hang every run that never adopted the
|
|
106
|
+
* field. Fail-open, in the one direction that cannot manufacture a stall.
|
|
107
|
+
*/
|
|
108
|
+
export function deliveryBlocksCompletion(state: DeliveryState | null | undefined): boolean {
|
|
109
|
+
if (state === null || state === undefined) return false;
|
|
110
|
+
return !DELIVERY_ENDINGS.includes(state);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The facts one terminal ledger line records about a run. */
|
|
114
|
+
export interface LedgerEventInput {
|
|
115
|
+
readonly action: LadderAction;
|
|
116
|
+
readonly runId: string;
|
|
117
|
+
readonly slug: string;
|
|
118
|
+
readonly turn: number;
|
|
119
|
+
readonly iterations: number;
|
|
120
|
+
readonly open: number;
|
|
121
|
+
readonly blocked: number;
|
|
122
|
+
readonly delivery: DeliveryState | null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build the terminal ledger line for a run.
|
|
127
|
+
*
|
|
128
|
+
* Extracted from `hooks/run_continuation_hook.ts` by
|
|
129
|
+
* `road-to-adversarial-verification-and-long-runs` 8.1, for the reason this
|
|
130
|
+
* module's own header gives: the hook sits past `check_source_size_budget`'s
|
|
131
|
+
* 1,500-line cap, where every added line is an added violation, so a phase pays
|
|
132
|
+
* for its additions by moving the code it is editing to a file under the cap.
|
|
133
|
+
* The `delivery` field is that addition.
|
|
134
|
+
*
|
|
135
|
+
* A ledger line is a persisted shape carrying a `RunTerminalState`, so it names
|
|
136
|
+
* the vocabulary version it was written against: a reader meeting an unknown
|
|
137
|
+
* value can then tell a newer writer from a corrupt row. `delivery` is OMITTED
|
|
138
|
+
* when the run reported no position rather than written as a null — `complete`
|
|
139
|
+
* alone cannot tell a merged run from an open-green one, and a null would read
|
|
140
|
+
* as "neither" rather than "unreported".
|
|
141
|
+
*/
|
|
142
|
+
export function buildLedgerEvent(
|
|
143
|
+
input: LedgerEventInput,
|
|
144
|
+
vocabularyVersion: number,
|
|
145
|
+
now: Date,
|
|
146
|
+
): Record<string, unknown> {
|
|
147
|
+
return {
|
|
148
|
+
event: input.action,
|
|
149
|
+
run_id: input.runId,
|
|
150
|
+
roadmap: input.slug,
|
|
151
|
+
turn: input.turn,
|
|
152
|
+
iterations: input.iterations,
|
|
153
|
+
open: input.open,
|
|
154
|
+
blocked: input.blocked,
|
|
155
|
+
terminal_state: terminalStateFor(input.action),
|
|
156
|
+
terminal_vocabulary_version: vocabularyVersion,
|
|
157
|
+
...(input.delivery !== null ? { delivery: input.delivery } : {}),
|
|
158
|
+
at: now.toISOString(),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Read a persisted delivery position tolerantly — `undefined` for anything this
|
|
164
|
+
* build does not recognise.
|
|
165
|
+
*
|
|
166
|
+
* Same forward-compatibility shape as {@link parseHaltStamp}, and the opposite
|
|
167
|
+
* fail direction on purpose: an unknown HALT is preserved, because dropping it
|
|
168
|
+
* re-engages a deliberately ended run; an unknown DELIVERY position is dropped,
|
|
169
|
+
* because preserving it would hold a run open on a state this build cannot
|
|
170
|
+
* evaluate. Both choose the branch that cannot manufacture a stall.
|
|
171
|
+
*/
|
|
172
|
+
export function parseDeliveryState(raw: unknown): DeliveryState | undefined {
|
|
173
|
+
if (typeof raw !== 'string') return undefined;
|
|
174
|
+
return (DELIVERY_STATES as readonly string[]).includes(raw)
|
|
175
|
+
? (raw as DeliveryState)
|
|
176
|
+
: undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
63
179
|
/**
|
|
64
180
|
* The subset of a run's state this decision reads. The hook's `RunState`
|
|
65
181
|
* satisfies it structurally; nothing here may widen beyond what the rungs use.
|
|
@@ -128,6 +244,10 @@ export function ladder(
|
|
|
128
244
|
// `_lib/context_observation.premiseMoved`. Optional and defaulted false, so
|
|
129
245
|
// a caller that cannot observe the world decides exactly as it did before.
|
|
130
246
|
premiseInvalidated: boolean = false,
|
|
247
|
+
// `road-to-adversarial-verification-and-long-runs` 8.1. The delivery position
|
|
248
|
+
// the run RECORDED, or `null` where it recorded none. Optional and defaulted,
|
|
249
|
+
// so a caller that does not track delivery decides exactly as it did before.
|
|
250
|
+
delivery: DeliveryState | null = null,
|
|
131
251
|
): LadderAction {
|
|
132
252
|
// A halt is terminal for this run id. Checked BEFORE `complete` so a
|
|
133
253
|
// halted run whose roadmap later reads zero-open does not report a
|
|
@@ -136,7 +256,26 @@ export function ladder(
|
|
|
136
256
|
// `scanOpenSteps` EXCLUDES `blocked-by:` steps from `openCount`, so zero-open
|
|
137
257
|
// with blocked steps left is exhaustion of runnable work, not completion —
|
|
138
258
|
// ADR-235's own terminal outcome, and never a sixth halt.
|
|
139
|
-
|
|
259
|
+
//
|
|
260
|
+
// 8.1 — delivery is checked INSIDE the zero-open branch and only against
|
|
261
|
+
// `complete`. Two boundaries, both deliberate. It does not touch `blocked`:
|
|
262
|
+
// a run whose remaining work is blocked has not failed to deliver, it has
|
|
263
|
+
// run out of deliverable work, and holding it open on a PR it cannot advance
|
|
264
|
+
// would convert a nameable blocker into a stall. And it is not checked while
|
|
265
|
+
// steps are still open, because there the run continues anyway — adding a
|
|
266
|
+
// second reason to continue would only make the ledger's `open` count lie
|
|
267
|
+
// about why.
|
|
268
|
+
const deliveringOnly =
|
|
269
|
+
openCount === 0 && blockedCount === 0 && deliveryBlocksCompletion(delivery);
|
|
270
|
+
if (openCount === 0) {
|
|
271
|
+
if (blockedCount > 0) return 'blocked';
|
|
272
|
+
if (!deliveringOnly) return 'complete';
|
|
273
|
+
// Fall THROUGH to the budget rungs rather than returning `engage` here.
|
|
274
|
+
// Returning early would put the run outside every bound in this function
|
|
275
|
+
// — an unbounded loop, which is the failure the ladder exists against —
|
|
276
|
+
// so a run held open for delivery is still capped by iterations and the
|
|
277
|
+
// wall clock. Only the STALL rung is exempted, below.
|
|
278
|
+
}
|
|
140
279
|
// BEFORE the counter rungs, deliberately: a missing credential, an absent
|
|
141
280
|
// binary or an exhausted quota is not closed by iterating, so spending the
|
|
142
281
|
// budget on it converts a nameable blocker into an anonymous cap-out.
|
|
@@ -151,8 +290,16 @@ export function ladder(
|
|
|
151
290
|
if (Number.isFinite(started) && nowMs - started >= caps.wallClockMs) {
|
|
152
291
|
return 'halt-wall-clock';
|
|
153
292
|
}
|
|
293
|
+
// The stall rung reads OPEN-STEP progress, and during delivery open-step
|
|
294
|
+
// progress is definitionally zero: the checkboxes are all flipped and the
|
|
295
|
+
// remaining work is a CI red or a moved target. Applying it here would halt
|
|
296
|
+
// a healthy delivery loop on its third turn — a stall manufactured by the
|
|
297
|
+
// stall detector, which is exactly the failure `run_continuation_hook`'s own
|
|
298
|
+
// header warns about in the abstract and the one this file's mechanics call
|
|
299
|
+
// "a metric that stops moving because the MEASUREMENT broke". The iteration
|
|
300
|
+
// and wall-clock caps above still bound the loop; only this rung is skipped.
|
|
154
301
|
const tail = state.history.slice(-caps.stallWindow);
|
|
155
|
-
if (tail.length >= caps.stallWindow && tail.every((n) => n === openCount)) {
|
|
302
|
+
if (!deliveringOnly && tail.length >= caps.stallWindow && tail.every((n) => n === openCount)) {
|
|
156
303
|
return 'halt-stall';
|
|
157
304
|
}
|
|
158
305
|
return 'engage';
|