@event4u/agent-config 14.13.0 → 14.14.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 +116 -387
- package/dist/agent-src/commands/analyze/inbox.md +36 -0
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- 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-delegate/cmd_conformance.js +38 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +4 -4
- 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 +1438 -711
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +112 -17
- 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/estate-count-budget.json +20 -1
- package/src/config/gate-coverage.yml +150 -0
- package/src/config/gate-violation-baselines.json +8 -3
- package/src/config/preamble-payload-budget.json +19 -11
- package/src/config/rule-stub-ceilings.json +14 -7
- package/src/scripts/_dispatch.bash +0 -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/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/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/harness_evolution_guards.ts +10 -0
- 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/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 +70 -1
- package/src/scripts/_lib/release_material.ts +125 -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/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_estate_count.ts +205 -24
- package/src/scripts/check_release_highlights.ts +96 -13
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/evolution_lab.ts +181 -8
- 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/turn_end_gate_hook.ts +9 -1
- 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 +643 -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 +143 -0
- package/src/scripts/release_env.ts +35 -0
- package/src/scripts/release_publication.ts +84 -0
|
@@ -43,6 +43,26 @@ export const LADDER_RUNGS = [
|
|
|
43
43
|
] as const;
|
|
44
44
|
export type LadderRung = (typeof LADDER_RUNGS)[number];
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The receipt-bearing cascade stage that reads each rung, in ladder order.
|
|
48
|
+
*
|
|
49
|
+
* Derived from {@link LADDER_RUNGS} rather than written out, so a rung cannot
|
|
50
|
+
* exist without its stage and the two orders cannot drift. Named here rather
|
|
51
|
+
* than in the cascade because the RUNGS are this module's, and a second module
|
|
52
|
+
* spelling `receipt-<rung>` would be a second source of truth for the same
|
|
53
|
+
* enum.
|
|
54
|
+
*
|
|
55
|
+
* The cascade consumes these; nothing here imports the cascade. That direction
|
|
56
|
+
* is the one `docs/contracts/activation-receipt-trust-boundary.md` TB-1 fixes.
|
|
57
|
+
*/
|
|
58
|
+
export const RECEIPT_STAGES = LADDER_RUNGS.map((r) => `receipt-${r}` as const);
|
|
59
|
+
export type ReceiptStage = (typeof RECEIPT_STAGES)[number];
|
|
60
|
+
|
|
61
|
+
/** The rung a receipt-bearing stage reads. Total over {@link ReceiptStage}. */
|
|
62
|
+
export function rungForReceiptStage(stage: ReceiptStage): LadderRung {
|
|
63
|
+
return stage.slice('receipt-'.length) as LadderRung;
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
/**
|
|
47
67
|
* Which failure family a stall at each rung belongs to.
|
|
48
68
|
*
|
|
@@ -171,10 +191,34 @@ export function rungState(receipt: ActivationReceipt, rung: LadderRung): RungSta
|
|
|
171
191
|
* and the caller gets `null`.
|
|
172
192
|
*/
|
|
173
193
|
export function classifyFailure(receipt: ActivationReceipt): FailureFamily | null {
|
|
174
|
-
|
|
194
|
+
return firstStall(receipt)?.family ?? null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Where a receipt stopped climbing, and the family that stall belongs to. */
|
|
198
|
+
export interface LadderStall {
|
|
199
|
+
/** The rung the walk stopped at. */
|
|
200
|
+
readonly rung: LadderRung;
|
|
201
|
+
/** The receipt-bearing cascade stage that reads {@link rung}. */
|
|
202
|
+
readonly stage: ReceiptStage;
|
|
203
|
+
/** `unknown` when the rung was never observed; the rung's family otherwise. */
|
|
204
|
+
readonly family: FailureFamily;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The first rung a receipt did not reach, with its stage and its family.
|
|
209
|
+
*
|
|
210
|
+
* {@link classifyFailure} is a projection of this, and delegates to it rather
|
|
211
|
+
* than walking the ladder a second time. Two walks of the same ladder applying
|
|
212
|
+
* the same short-circuit rule is two places to change it — and the cascade needs
|
|
213
|
+
* the STAGE as well as the family, which is the only reason this exists
|
|
214
|
+
* separately at all.
|
|
215
|
+
*/
|
|
216
|
+
export function firstStall(receipt: ActivationReceipt): LadderStall | null {
|
|
217
|
+
for (const [i, spec] of LADDER.entries()) {
|
|
175
218
|
const state = rungState(receipt, spec.rung);
|
|
176
|
-
|
|
177
|
-
if (state === '
|
|
219
|
+
const stage = RECEIPT_STAGES[i] as ReceiptStage;
|
|
220
|
+
if (state === 'unknown') return { rung: spec.rung, stage, family: 'unknown' };
|
|
221
|
+
if (state === 'not-reached') return { rung: spec.rung, stage, family: spec.family };
|
|
178
222
|
}
|
|
179
223
|
return null;
|
|
180
224
|
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The activation-receipt producer — independent, append-only, zero model calls.
|
|
3
|
+
*
|
|
4
|
+
* `road-to-governed-evidence-production` step 1.1. The receipt SCHEMA
|
|
5
|
+
* ({@link ActivationReceipt}) and the CLASSIFIER ({@link classifyFailure}) both
|
|
6
|
+
* predate this module and are NOT reimplemented here; what was missing was a
|
|
7
|
+
* producer, so nothing ever wrote an `activation` object into an audit line and
|
|
8
|
+
* the receipt-bearing cascade stages had no subject.
|
|
9
|
+
*
|
|
10
|
+
* ## The claims this module is written against
|
|
11
|
+
*
|
|
12
|
+
* Stated once, in `docs/contracts/activation-receipt-trust-boundary.md`, and
|
|
13
|
+
* CITED here rather than restated — step 1.3's verify clause asks for exactly
|
|
14
|
+
* that split, and a second copy of a falsifiable claim is a second thing to
|
|
15
|
+
* keep true.
|
|
16
|
+
*
|
|
17
|
+
* | Claim | Where it binds in this file |
|
|
18
|
+
* |---|---|
|
|
19
|
+
* | TB-1 (no evaluation input decides a STATE) | the import list: no cascade, record, vector or verdict module appears |
|
|
20
|
+
* | TB-2 (an unobserved rung is absent) | {@link buildActivationReceipt} writes only the rungs it was given |
|
|
21
|
+
* | TB-3 (every state names an admitted source) | {@link EVIDENCE_SOURCES} + the refusal in {@link buildActivationReceipt} |
|
|
22
|
+
* | TB-4 (append, never rewrite) | {@link appendActivationLine} opens with `appendFileSync` and mints a fresh `id` |
|
|
23
|
+
* | EC-1 (zero model calls) | no transport import, no spawn, no key read — asserted by this module's test |
|
|
24
|
+
* | EC-3 (a missing observation is never bought) | {@link observeProjection} returns absence and does not retry |
|
|
25
|
+
*
|
|
26
|
+
* ## What is observable today, and what is not
|
|
27
|
+
*
|
|
28
|
+
* Three sources are admitted and all three ship an observer. `adhered` has no
|
|
29
|
+
* admitted source, so this producer CANNOT emit that rung and a real receipt
|
|
30
|
+
* reads `unknown` there. That is a coverage fact recorded rather than papered
|
|
31
|
+
* over: admitting a fourth source with no observer behind it would describe a
|
|
32
|
+
* capability that does not exist, which is the "population of zero" failure the
|
|
33
|
+
* roadmap already flags elsewhere.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import * as fs from 'node:fs';
|
|
37
|
+
import * as path from 'node:path';
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
LADDER_RUNGS,
|
|
41
|
+
type ActivationReceipt,
|
|
42
|
+
type LadderRung,
|
|
43
|
+
type PrecedenceReason,
|
|
44
|
+
type RungState,
|
|
45
|
+
} from './activation_ladder.js';
|
|
46
|
+
import type { NoFreeForm } from './runtime_journal.js';
|
|
47
|
+
import { type PrivacyClass } from './privacy_class.js';
|
|
48
|
+
|
|
49
|
+
type Assert<T extends true> = T;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The closed set of evidence sources a rung state may come from (TB-3).
|
|
53
|
+
*
|
|
54
|
+
* Each entry names a surface this producer can read without consulting the
|
|
55
|
+
* evaluation side. A source may not be added without an observer: the set
|
|
56
|
+
* describes what can be observed, never what one would like to observe.
|
|
57
|
+
*
|
|
58
|
+
* - `source-tree` — the authored artefact exists under `src/`, so it can match
|
|
59
|
+
* at all. Reads the `eligible` rung.
|
|
60
|
+
* - `discovery-manifest` — the built manifest names the artefact for the active
|
|
61
|
+
* install, i.e. the selector kept it. Reads the `selected` rung.
|
|
62
|
+
* - `host-projection` — a host tree carries a file for the artefact. Reads the
|
|
63
|
+
* `projected` rung.
|
|
64
|
+
*/
|
|
65
|
+
export const EVIDENCE_SOURCES = ['source-tree', 'discovery-manifest', 'host-projection'] as const;
|
|
66
|
+
export type EvidenceSource = (typeof EVIDENCE_SOURCES)[number];
|
|
67
|
+
|
|
68
|
+
/** The rung each admitted source is allowed to speak about. One source, one rung. */
|
|
69
|
+
export const SOURCE_RUNG: Readonly<Record<EvidenceSource, LadderRung>> = {
|
|
70
|
+
'source-tree': 'eligible',
|
|
71
|
+
'discovery-manifest': 'selected',
|
|
72
|
+
'host-projection': 'projected',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Rungs no admitted source covers today. Present so the gap is enumerable. */
|
|
76
|
+
export const UNOBSERVED_RUNGS: readonly LadderRung[] = LADDER_RUNGS.filter(
|
|
77
|
+
(r) => !Object.values(SOURCE_RUNG).includes(r),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* One observation. `state` is the evidence; `evidence_source` is where it came
|
|
82
|
+
* from.
|
|
83
|
+
*
|
|
84
|
+
* The field is `evidence_source` and not `source` on purpose: `source` is a
|
|
85
|
+
* member of `FREE_FORM_KEYS`, so a field of that name would make the privacy
|
|
86
|
+
* assertion at the bottom of this file `Assert<false>` and stop the build. The
|
|
87
|
+
* rename is the cheap half of keeping this input type incapable of carrying
|
|
88
|
+
* content.
|
|
89
|
+
*/
|
|
90
|
+
export interface RungObservation {
|
|
91
|
+
readonly rung: LadderRung;
|
|
92
|
+
readonly state: Exclude<RungState, 'unknown'>;
|
|
93
|
+
readonly evidence_source: EvidenceSource;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ReceiptBuild {
|
|
97
|
+
readonly receipt: ActivationReceipt | null;
|
|
98
|
+
readonly errors: readonly string[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build a receipt from observations.
|
|
103
|
+
*
|
|
104
|
+
* TB-2 in one line: the output's `rungs` map has exactly the keys the caller
|
|
105
|
+
* observed. Nothing is defaulted, nothing is filled in, and a rung nobody looked
|
|
106
|
+
* at is absent — which `rungState` reads as `unknown` and `ladderRate` keeps out
|
|
107
|
+
* of its denominator.
|
|
108
|
+
*
|
|
109
|
+
* TB-3 in the refusals: an unadmitted source, or an admitted source speaking
|
|
110
|
+
* about a rung that is not its own, is an ERROR and yields no receipt. It is
|
|
111
|
+
* not a warning and it is not silently dropped, because a receipt missing a rung
|
|
112
|
+
* is indistinguishable from a receipt whose rung was refused, and only one of
|
|
113
|
+
* those is honest.
|
|
114
|
+
*/
|
|
115
|
+
export function buildActivationReceipt(
|
|
116
|
+
artefact: string,
|
|
117
|
+
observations: readonly RungObservation[],
|
|
118
|
+
reason?: PrecedenceReason,
|
|
119
|
+
): ReceiptBuild {
|
|
120
|
+
const errors: string[] = [];
|
|
121
|
+
if (!artefact) errors.push('artefact id is required');
|
|
122
|
+
|
|
123
|
+
const rungs: Partial<Record<LadderRung, RungState>> = {};
|
|
124
|
+
for (const o of observations) {
|
|
125
|
+
if (!(EVIDENCE_SOURCES as readonly string[]).includes(o.evidence_source)) {
|
|
126
|
+
errors.push(`evidence_source '${o.evidence_source}' is not admitted (TB-3)`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (SOURCE_RUNG[o.evidence_source] !== o.rung) {
|
|
130
|
+
errors.push(
|
|
131
|
+
`evidence_source '${o.evidence_source}' may only observe rung ` +
|
|
132
|
+
`'${SOURCE_RUNG[o.evidence_source]}', not '${o.rung}' (TB-3)`,
|
|
133
|
+
);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (rungs[o.rung] !== undefined && rungs[o.rung] !== o.state) {
|
|
137
|
+
errors.push(`rung '${o.rung}' observed twice with conflicting states`);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
rungs[o.rung] = o.state;
|
|
141
|
+
}
|
|
142
|
+
if (errors.length > 0) return { receipt: null, errors };
|
|
143
|
+
|
|
144
|
+
const receipt: ActivationReceipt = reason === undefined
|
|
145
|
+
? { artefact, rungs }
|
|
146
|
+
: { artefact, rungs, reason };
|
|
147
|
+
return { receipt, errors: [] };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// --- observers ---------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Observe the `projected` rung: does a host tree carry a file for this artefact?
|
|
154
|
+
*
|
|
155
|
+
* EC-3 is the reason this returns `undefined` rather than probing further when
|
|
156
|
+
* the host root itself is missing. A host tree that does not exist is not
|
|
157
|
+
* evidence that the artefact was not projected into it — it is the absence of a
|
|
158
|
+
* place to look, and the honest output of a missing observation is nothing at
|
|
159
|
+
* all. Escalating from "read a path" to "go find the tree" is the unbounded-cost
|
|
160
|
+
* mistake EC-3 names, and it is the one a reader would forgive.
|
|
161
|
+
*/
|
|
162
|
+
export function observeProjection(
|
|
163
|
+
hostRoot: string,
|
|
164
|
+
artefactRelPath: string,
|
|
165
|
+
): RungObservation | undefined {
|
|
166
|
+
if (!existsQuiet(hostRoot)) return undefined;
|
|
167
|
+
return {
|
|
168
|
+
rung: 'projected',
|
|
169
|
+
state: existsQuiet(path.join(hostRoot, artefactRelPath)) ? 'reached' : 'not-reached',
|
|
170
|
+
evidence_source: 'host-projection',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Observe the `eligible` rung: is the artefact authored in the source tree?
|
|
176
|
+
*
|
|
177
|
+
* Same absence rule as {@link observeProjection}: an unreadable source root
|
|
178
|
+
* yields no observation rather than a negative one.
|
|
179
|
+
*/
|
|
180
|
+
export function observeSourceTree(
|
|
181
|
+
sourceRoot: string,
|
|
182
|
+
artefactRelPath: string,
|
|
183
|
+
): RungObservation | undefined {
|
|
184
|
+
if (!existsQuiet(sourceRoot)) return undefined;
|
|
185
|
+
return {
|
|
186
|
+
rung: 'eligible',
|
|
187
|
+
state: existsQuiet(path.join(sourceRoot, artefactRelPath)) ? 'reached' : 'not-reached',
|
|
188
|
+
evidence_source: 'source-tree',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Observe the `selected` rung from an already-loaded discovery-manifest id set.
|
|
194
|
+
*
|
|
195
|
+
* Takes the id set rather than a path: loading and parsing the manifest is the
|
|
196
|
+
* caller's, so this stays a pure predicate and EC-1's "no I/O beyond a read"
|
|
197
|
+
* holds trivially for it. An EMPTY set is treated as no manifest — not as a
|
|
198
|
+
* manifest that selected nothing — because those are different observations and
|
|
199
|
+
* folding them is exactly TB-2's failure.
|
|
200
|
+
*/
|
|
201
|
+
export function observeSelection(
|
|
202
|
+
selectedIds: ReadonlySet<string>,
|
|
203
|
+
artefactId: string,
|
|
204
|
+
): RungObservation | undefined {
|
|
205
|
+
if (selectedIds.size === 0) return undefined;
|
|
206
|
+
return {
|
|
207
|
+
rung: 'selected',
|
|
208
|
+
state: selectedIds.has(artefactId) ? 'reached' : 'not-reached',
|
|
209
|
+
evidence_source: 'discovery-manifest',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function existsQuiet(p: string): boolean {
|
|
214
|
+
try {
|
|
215
|
+
fs.statSync(p);
|
|
216
|
+
return true;
|
|
217
|
+
} catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// --- the audit line ----------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/** What THIS producer's lines carry: an artefact id, enums, and counts. */
|
|
225
|
+
const PRODUCER_PRIVACY_CLASS: PrivacyClass = 'ids-only';
|
|
226
|
+
|
|
227
|
+
export interface ActivationLineInput {
|
|
228
|
+
/** The artefact the receipt is about. An id, never a path. */
|
|
229
|
+
artefact: string;
|
|
230
|
+
/** The rungs actually observed. Absent rungs stay absent (TB-2). */
|
|
231
|
+
rungs: Readonly<Partial<Record<LadderRung, RungState>>>;
|
|
232
|
+
/**
|
|
233
|
+
* The precedence reason, when there is one.
|
|
234
|
+
*
|
|
235
|
+
* Named `precedence_reason` and NOT `reason`, which is a `FREE_FORM_KEYS`
|
|
236
|
+
* member: a field called `reason` on this type makes the assertion at the
|
|
237
|
+
* bottom of this file `Assert<false>` and stops the build, even though the
|
|
238
|
+
* value it would hold is a closed six-value enum. The emitted JSON key is
|
|
239
|
+
* still `reason`, because that is `ActivationReceipt`'s field name and the
|
|
240
|
+
* ladder module owns the receipt shape. The rename buys the compile-time
|
|
241
|
+
* floor at the cost of one line of mapping, which is the right trade: a
|
|
242
|
+
* guard that only rejects genuinely dangerous names is a guard someone has
|
|
243
|
+
* to keep tuning.
|
|
244
|
+
*/
|
|
245
|
+
precedence_reason?: PrecedenceReason | undefined;
|
|
246
|
+
/** ISO-8601 UTC; the caller supplies it so this stays pure. */
|
|
247
|
+
ts: string;
|
|
248
|
+
/** ULID, UUID or content hash; the caller supplies it. */
|
|
249
|
+
id: string;
|
|
250
|
+
work_id?: string | undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface BuiltActivationLine {
|
|
254
|
+
line: Record<string, unknown> | null;
|
|
255
|
+
errors: string[];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Build ONE audit-log-v1 line carrying an `activation` object.
|
|
260
|
+
*
|
|
261
|
+
* Deliberately the same envelope as `_lib/review_skipped_record.ts` — same
|
|
262
|
+
* mandatory fields, same `{line, errors}` contract, same refusal to return a
|
|
263
|
+
* half-built line. `skills_applied` is omitted for the same reason it is there:
|
|
264
|
+
* this producer has no skill observation to offer, and `[]` would assert one.
|
|
265
|
+
*/
|
|
266
|
+
export function buildActivationLine(input: ActivationLineInput): BuiltActivationLine {
|
|
267
|
+
const errors: string[] = [];
|
|
268
|
+
if (!input.artefact) errors.push('artefact is required');
|
|
269
|
+
if (!input.ts) errors.push('ts (ISO-8601 UTC) is required');
|
|
270
|
+
if (!input.id) errors.push('id (ULID, UUID, or content hash) is required');
|
|
271
|
+
const observed = Object.keys(input.rungs);
|
|
272
|
+
if (observed.length === 0) {
|
|
273
|
+
// A receipt that observed nothing is not a receipt. Writing one would
|
|
274
|
+
// add a line to an append-only ledger that carries no evidence and
|
|
275
|
+
// still counts as a record — the shape a later reader mistakes for
|
|
276
|
+
// coverage.
|
|
277
|
+
errors.push('a receipt must carry at least one observed rung');
|
|
278
|
+
}
|
|
279
|
+
for (const r of observed) {
|
|
280
|
+
if (!(LADDER_RUNGS as readonly string[]).includes(r)) {
|
|
281
|
+
errors.push(`'${r}' is not a ladder rung`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (errors.length) return { line: null, errors };
|
|
285
|
+
|
|
286
|
+
const activation: Record<string, unknown> = { artefact: input.artefact, rungs: { ...input.rungs } };
|
|
287
|
+
if (input.precedence_reason !== undefined) activation['reason'] = input.precedence_reason;
|
|
288
|
+
|
|
289
|
+
const line: Record<string, unknown> = {
|
|
290
|
+
schema_version: 1,
|
|
291
|
+
id: input.id,
|
|
292
|
+
ts: input.ts,
|
|
293
|
+
work_id: input.work_id ?? `activation-${input.ts}`,
|
|
294
|
+
phase: 'report',
|
|
295
|
+
outcome: 'success',
|
|
296
|
+
confidence_band: 'high',
|
|
297
|
+
risk_class: 'low',
|
|
298
|
+
memory: { asks: 0, hits: 0 },
|
|
299
|
+
verify: { claims: 0, first_try_passes: 0 },
|
|
300
|
+
rules_applied: [],
|
|
301
|
+
privacy_class: PRODUCER_PRIVACY_CLASS,
|
|
302
|
+
persona: null,
|
|
303
|
+
input_kind: 'prompt',
|
|
304
|
+
type: 'note',
|
|
305
|
+
activation,
|
|
306
|
+
};
|
|
307
|
+
return { line, errors: [] };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Append ONE line to the monthly audit log (TB-4).
|
|
312
|
+
*
|
|
313
|
+
* `appendFileSync` and nothing else: no read-modify-write, no truncation, no
|
|
314
|
+
* seek. Returns the file it wrote so a caller can assert the write happened
|
|
315
|
+
* rather than trusting that it did.
|
|
316
|
+
*/
|
|
317
|
+
export function appendActivationLine(
|
|
318
|
+
workspaceRoot: string,
|
|
319
|
+
line: Record<string, unknown>,
|
|
320
|
+
ts: string,
|
|
321
|
+
): string {
|
|
322
|
+
const dir = path.join(workspaceRoot, 'agents', 'runtime', 'state', 'audit');
|
|
323
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
324
|
+
const file = path.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
325
|
+
fs.appendFileSync(file, `${JSON.stringify(line)}\n`, 'utf8');
|
|
326
|
+
return file;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The privacy floor, by construction, on this producer's input type.
|
|
331
|
+
*
|
|
332
|
+
* Same mechanism as the two producers `docs/contracts/audit-log-v1.md`
|
|
333
|
+
* § Privacy floor already names. Adding a `path`, `detail`, `reason_text` or
|
|
334
|
+
* any other `FREE_FORM_KEYS` member below makes this `Assert<false>` and stops
|
|
335
|
+
* the build. `reason` is one of them, which is why the precedence field is
|
|
336
|
+
* called `precedence_reason` here and mapped to `reason` on the way out — see
|
|
337
|
+
* the field's own note.
|
|
338
|
+
*/
|
|
339
|
+
type _ActivationLineInputCarriesNoFreeFormField = Assert<
|
|
340
|
+
[NoFreeForm<ActivationLineInput>] extends [never] ? false : true
|
|
341
|
+
>;
|
|
@@ -308,6 +308,32 @@ export type EvidenceStrength = (typeof EVIDENCE_STRENGTHS)[number];
|
|
|
308
308
|
|
|
309
309
|
export const DISCOVERY_STATES = ['complete', 'incomplete'] as const;
|
|
310
310
|
export const AUTHORITY_BASES = ['evidence', 'owner_intent'] as const;
|
|
311
|
+
export type AuthorityBasis = (typeof AUTHORITY_BASES)[number];
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Reopen-authority vocabulary (`adr-layout.md` § Reopen authority).
|
|
315
|
+
*
|
|
316
|
+
* Moved here from `check_adr_frontmatter.ts` on 2026-08-31 so the ADR gate and
|
|
317
|
+
* `road-to-harness-promotion-bridge` step 7.2 read ONE list. 7.2's whole
|
|
318
|
+
* requirement is "route through the existing gate, not a second governance
|
|
319
|
+
* system"; importing the validator's private constant was not available, and
|
|
320
|
+
* copying it would have created the second system the step forbids.
|
|
321
|
+
*
|
|
322
|
+
* Both fields stay OPTIONAL at the ADR reader — an absent `reopen_policy`
|
|
323
|
+
* resolves to `unclassified`, never to `owner`.
|
|
324
|
+
*/
|
|
325
|
+
export const REOPEN_POLICIES = ['directional', 'owner', 'unclassified'] as const;
|
|
326
|
+
export type ReopenPolicy = (typeof REOPEN_POLICIES)[number];
|
|
327
|
+
|
|
328
|
+
export const PROTECTED_DIMENSIONS = [
|
|
329
|
+
'purpose',
|
|
330
|
+
'security_floor',
|
|
331
|
+
'privacy_floor',
|
|
332
|
+
'external_commitment',
|
|
333
|
+
'governance',
|
|
334
|
+
'none',
|
|
335
|
+
] as const;
|
|
336
|
+
export type ProtectedDimension = (typeof PROTECTED_DIMENSIONS)[number];
|
|
311
337
|
|
|
312
338
|
export interface AdrProvenance {
|
|
313
339
|
kind: string | null;
|