@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.
Files changed (101) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +116 -387
  5. package/dist/agent-src/commands/analyze/inbox.md +36 -0
  6. package/dist/agent-src/rules/code-comment-discipline.md +6 -0
  7. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  8. package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
  9. package/dist/agent-src/scripts/stubs_due.ts +118 -2
  10. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/cli-delegate/cmd_conformance.js +38 -1
  13. package/dist/discovery/deprecation-report.md +1 -1
  14. package/dist/discovery/discovery-manifest.json +4 -4
  15. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  16. package/dist/discovery/discovery-manifest.summary.md +1 -1
  17. package/dist/discovery/dormancy-report.md +1 -1
  18. package/dist/discovery/orphan-report.md +1 -1
  19. package/dist/discovery/packs.json +2 -2
  20. package/dist/discovery/trust-report.md +1 -1
  21. package/dist/discovery/workspaces.json +2 -2
  22. package/dist/hooks/dispatch.js +1438 -711
  23. package/dist/mcp/registry-manifest.json +2 -2
  24. package/dist/mcp/server.json +2 -2
  25. package/dist/mcp/server.mjs +112 -17
  26. package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
  27. package/docs/guidelines/code-clarity.md +18 -0
  28. package/package.json +1 -1
  29. package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  30. package/src/agent-src/scripts/guarded_baseline.ts +22 -7
  31. package/src/agent-src/scripts/stubs_due.ts +118 -2
  32. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
  33. package/src/config/estate-count-budget.json +20 -1
  34. package/src/config/gate-coverage.yml +150 -0
  35. package/src/config/gate-violation-baselines.json +8 -3
  36. package/src/config/preamble-payload-budget.json +19 -11
  37. package/src/config/rule-stub-ceilings.json +14 -7
  38. package/src/scripts/_dispatch.bash +0 -4
  39. package/src/scripts/_lib/activation_ladder.ts +47 -3
  40. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  41. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  42. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  43. package/src/scripts/_lib/carrier_status.ts +118 -0
  44. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  45. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  46. package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
  47. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  48. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  49. package/src/scripts/_lib/evolution_roi.ts +562 -0
  50. package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
  51. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  52. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  53. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  54. package/src/scripts/_lib/pathology_archive.ts +393 -0
  55. package/src/scripts/_lib/promotion_capability.ts +315 -0
  56. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  57. package/src/scripts/_lib/promotion_review.ts +294 -0
  58. package/src/scripts/_lib/prompt_shape.ts +92 -0
  59. package/src/scripts/_lib/release_highlights.ts +70 -1
  60. package/src/scripts/_lib/release_material.ts +125 -2
  61. package/src/scripts/_lib/routing_corpus.ts +307 -0
  62. package/src/scripts/_lib/routing_index_input.ts +104 -0
  63. package/src/scripts/_lib/rule_injection.ts +22 -1
  64. package/src/scripts/_lib/semantic_noop.ts +167 -0
  65. package/src/scripts/activation_receipt.ts +258 -0
  66. package/src/scripts/ai_council/cross_exam.ts +128 -0
  67. package/src/scripts/ai_council/explain_route.ts +157 -0
  68. package/src/scripts/ai_council/information_gain.ts +293 -0
  69. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  70. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  71. package/src/scripts/ai_council/minority_retention.ts +171 -0
  72. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  73. package/src/scripts/ai_council/replay_route.ts +223 -0
  74. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  75. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  76. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  77. package/src/scripts/check_adr_frontmatter.ts +4 -9
  78. package/src/scripts/check_estate_count.ts +205 -24
  79. package/src/scripts/check_release_highlights.ts +96 -13
  80. package/src/scripts/check_roadmap_trackable.ts +17 -2
  81. package/src/scripts/corpus_manifest.ts +235 -0
  82. package/src/scripts/evolution_lab.ts +181 -8
  83. package/src/scripts/hook_manifest.json +1 -1
  84. package/src/scripts/hook_manifest.yaml +28 -6
  85. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  86. package/src/scripts/hooks/concern_registry.ts +2 -0
  87. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  88. package/src/scripts/language_mirror_hook.ts +14 -2
  89. package/src/scripts/lint_carrier_integrity.ts +820 -0
  90. package/src/scripts/lint_code_comments.ts +643 -0
  91. package/src/scripts/lint_plan_risk_register.ts +39 -19
  92. package/src/scripts/lint_promotion_paths.ts +779 -0
  93. package/src/scripts/llm_propose.ts +256 -0
  94. package/src/scripts/measure_delivery_sets.ts +361 -0
  95. package/src/scripts/measure_routing_signal.ts +307 -0
  96. package/src/scripts/model_rule_injection.ts +19 -3
  97. package/src/scripts/probe_council_retention.ts +495 -0
  98. package/src/scripts/release.ts +21 -37
  99. package/src/scripts/release_drill.ts +143 -0
  100. package/src/scripts/release_env.ts +35 -0
  101. 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
- for (const spec of LADDER) {
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
- if (state === 'unknown') return 'unknown';
177
- if (state === 'not-reached') return spec.family;
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;