@dzhechkov/harness-core 0.3.144 → 0.3.145

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/.dz-manifest.json CHANGED
@@ -419,21 +419,37 @@
419
419
  "path": "dist/feature-adr-setup.js.map",
420
420
  "sha256": "dc50ce0dd4b4680afdda3e2c650d0d8665c7defce22e4afaeb24d2113c0dec9d"
421
421
  },
422
+ {
423
+ "path": "dist/guard-promotion.d.ts",
424
+ "sha256": "eba21256fa7ed8082acf9571c0606aa2926c0a13635fd994aa74f44d7f3db1d4"
425
+ },
426
+ {
427
+ "path": "dist/guard-promotion.d.ts.map",
428
+ "sha256": "6da389f6fbdae2b886d526db90b09ca7fabac88f26ff96e73cdfd98d1a6a90d4"
429
+ },
430
+ {
431
+ "path": "dist/guard-promotion.js",
432
+ "sha256": "a03bf0166ac3056d93c67a2571c6300cc92941424cc5a8aecce529a6879e4a47"
433
+ },
434
+ {
435
+ "path": "dist/guard-promotion.js.map",
436
+ "sha256": "858b3ad88b0c2fdf3a66a89075df493e3592cbf8c12f3df880be93a0e446ad8e"
437
+ },
422
438
  {
423
439
  "path": "dist/guard.d.ts",
424
- "sha256": "7a1df1aa511a5d96669e660b99d29ba633393e1a20dc346e1dedfa9aec221260"
440
+ "sha256": "5ac5bd9b2d927117d124861c5532a0618c5f3f4859b8a91a6b62203475db047e"
425
441
  },
426
442
  {
427
443
  "path": "dist/guard.d.ts.map",
428
- "sha256": "be03334a1746d3261afe69252f119c82f3718e0608957eebf5de311acf9d8a04"
444
+ "sha256": "f56619488145d4ccdac45298cb76647ea670d5b37d210f30972b602d3fad5f24"
429
445
  },
430
446
  {
431
447
  "path": "dist/guard.js",
432
- "sha256": "dc20d0ea9ff7e1299b52ed3ce03dbd2f8bb13b1b86ee2799a49837f33ec33883"
448
+ "sha256": "7dc53438c377aa007264384fc30f34764fcb969fd6d0bfae15a0b976bafd0e17"
433
449
  },
434
450
  {
435
451
  "path": "dist/guard.js.map",
436
- "sha256": "1696212693b8cd400ff6d2af843e2901ad0e79679ddd09ea9831306aa75e342a"
452
+ "sha256": "389544fa42d0010f48ae1bccdd81170053fbd56d5131c1479db87535c9a34662"
437
453
  },
438
454
  {
439
455
  "path": "dist/import-ecc.d.ts",
@@ -453,19 +469,19 @@
453
469
  },
454
470
  {
455
471
  "path": "dist/index.d.ts",
456
- "sha256": "88213a2320b2b99781919788883ab99da93c4043e7c235f14d7f97db034703e0"
472
+ "sha256": "88d54740d8b19ff5dd759810459d8484c2fa5b428f5d2bf56fcbc431b439eb47"
457
473
  },
458
474
  {
459
475
  "path": "dist/index.d.ts.map",
460
- "sha256": "e568286b5e7123dbd73a623268134af1b55555e61e90ea0271a9d4938184b6a0"
476
+ "sha256": "8538e8daa8b84ad85e961062b78a25ded82cbb575fd8a60d5ad1a01d3d924396"
461
477
  },
462
478
  {
463
479
  "path": "dist/index.js",
464
- "sha256": "a779d72721eb025257beee64dd6e29a8e54e1dde1f152a0887d94aa2f9c3f1be"
480
+ "sha256": "6d90d71faff6629442c9f553be6da1e8b3b1bf29c36c04ebd28b63b24cb63e67"
465
481
  },
466
482
  {
467
483
  "path": "dist/index.js.map",
468
- "sha256": "7f3cd564e49f49d2fb6a736e98ce837f987994776730ff07f04cb71c7113c22e"
484
+ "sha256": "a31f03dd37db8ddc701ddb181aaf46ec11259ea099924a03e91fdc2bb59ee125"
469
485
  },
470
486
  {
471
487
  "path": "dist/learning-backend.d.ts",
@@ -1045,7 +1061,7 @@
1045
1061
  },
1046
1062
  {
1047
1063
  "path": "package.json",
1048
- "sha256": "2b12d89fc52919b1963d3bd655cf21312df2306e3b7b3ff09c6812675197c58c"
1064
+ "sha256": "97904a633c24b3de454752e7d3d5f3b0bb8904d18b64ed90787d6963a3103087"
1049
1065
  },
1050
1066
  {
1051
1067
  "path": "src/__tests__/golden-baseline.test.ts",
@@ -1147,9 +1163,13 @@
1147
1163
  "path": "src/feature-adr-setup.ts",
1148
1164
  "sha256": "311f4344eed1a43a0a3bd2723cafce141e035078cdcc404509cdf26ee7df4904"
1149
1165
  },
1166
+ {
1167
+ "path": "src/guard-promotion.ts",
1168
+ "sha256": "45e05ef83ac730442760a55f9859f36b2b2a56db53465ca0267f517024646026"
1169
+ },
1150
1170
  {
1151
1171
  "path": "src/guard.ts",
1152
- "sha256": "f56d5f5e59e9ddecae339b054b9f888b81b3410c56d3a09eedd7be3c11d3438c"
1172
+ "sha256": "e7559a9f42448374ae133a323f8d9e4fa21625b15853d544e1c077ae9331a2ce"
1153
1173
  },
1154
1174
  {
1155
1175
  "path": "src/import-ecc.ts",
@@ -1157,7 +1177,7 @@
1157
1177
  },
1158
1178
  {
1159
1179
  "path": "src/index.ts",
1160
- "sha256": "98f3820a8d7eafeb6405e2c80b05513ddce58dfccec57612f1136cfa6abdf40e"
1180
+ "sha256": "30f96c27c2f364d5a3aac7311167dba0f154adea3295a182f9b1cfcc28d6f3a6"
1161
1181
  },
1162
1182
  {
1163
1183
  "path": "src/learning-backend.ts",
@@ -1447,6 +1467,10 @@
1447
1467
  "path": "test/fixtures/recall-floor-probes.json",
1448
1468
  "sha256": "9fdfa7db72ebe6d15c604fa0d3eeeec868adb7e41f9ea0f128005fe234e1395f"
1449
1469
  },
1470
+ {
1471
+ "path": "test/guard-promotion.test.ts",
1472
+ "sha256": "c7d38f9ba7d2a1a1d5e40b9422eeef0ffc7784b015adb41a9c8d89893f529c9e"
1473
+ },
1450
1474
  {
1451
1475
  "path": "test/guard.test.ts",
1452
1476
  "sha256": "49829d24440130cfe403a410b9068a76631c68e9baa2cfc978f5029df2d72f3a"
@@ -1633,5 +1657,5 @@
1633
1657
  }
1634
1658
  ]
1635
1659
  },
1636
- "signature": "9x1Af1YN2KgiryY+uLM532g4McRyNgwN7PcuPboXSZXDIyGDh/bc/FTCx30Z5ZsqHnUBRupAB57gyI+g1VP8CA=="
1660
+ "signature": "+MKRc3DHvGIgL2fuIvPauIW+qwbkQBjZi/EIiJf/3uUMxIlW9v+F8BdCeY3T6EBrBLko/OT9BxPo4JmURHSPDw=="
1637
1661
  }
@@ -0,0 +1,364 @@
1
+ /**
2
+ * `dz guard promote` — lesson → guard-rule PROMOTION with a "win twice to promote" gate.
3
+ *
4
+ * The cost-of-detection ladder says: put every check on the strongest layer that can express it.
5
+ * `dz compounding` MEASURED (2026-07-29) that this repo's learned store is ~82% write-only while the
6
+ * rules that DID reach layer 1 collapsed their own violation rate (no-workspace-star 31→0,
7
+ * readme-first 49→4). This module is the elevator: it moves a lesson from layer 5 (agent memory) to
8
+ * layer 1 (a deterministic rule) — but only when real evidence earns it.
9
+ *
10
+ * Ported from rUv's `@claude-flow/guidance` ADR-G008 (optimizer-promotion-rule, ACCEPTED) +
11
+ * `src/optimizer.ts` / `src/ledger.ts` (`score = frequency * cost`, promotionTracker, two
12
+ * consecutive wins, one loss resets). IMPROVEMENT OVER SOURCE: ADR-G008's own Negative section
13
+ * admits its A/B uses hard-coded SIMULATED reduction percentages. Here a "win" is a REPLAY of the
14
+ * candidate's check over REAL commits — the firings are real or there is no win.
15
+ *
16
+ * PURE: zero imports, zero I/O, no wall clock. Callers inject lessons, existing rules, and the
17
+ * change history; this module only computes. Same facts ⇒ byte-identical report.
18
+ *
19
+ * WHAT THIS DELIBERATELY IS NOT: a rule SYNTHESISER. Rule code is never generated from lesson text —
20
+ * that is layer-4 model judgment wearing layer-1 clothing, and its failure mode is silent. The fixed
21
+ * template vocabulary below is the entire executable surface (ADR-002).
22
+ */
23
+ /**
24
+ * The v1 templates. ALL THREE are CHANGE-SHAPED: predicates over the file list of one change, which
25
+ * is exactly what `git log --name-only` replays and what `git status` supplies at publish time.
26
+ *
27
+ * `presence-check` (a repo-STATE predicate, e.g. "every skill dir must carry a SKILL.md") is
28
+ * deliberately ABSENT. Shadow-replaying a state predicate would need a tree walk at every historical
29
+ * commit; the cheap substitute — evaluating it against TODAY's tree once per period — returns the
30
+ * same answer in every window and so MANUFACTURES two consecutive wins out of one observation.
31
+ * A fabricated win is the one thing this gate must never produce, so presence-shaped lessons are
32
+ * refused by name (see `classifyLesson`).
33
+ */
34
+ export type RuleTemplate = 'pairing-check' | 'absence-check' | 'format-match';
35
+ export declare const TEMPLATES: readonly RuleTemplate[];
36
+ export interface TemplateParams {
37
+ /** pairing-check: the glob whose presence in a change ARMS the rule. */
38
+ readonly when?: string;
39
+ /** pairing-check: the glob that must ALSO be in the change. */
40
+ readonly requires?: string;
41
+ /** absence-check: a change touching this glob is itself the violation. */
42
+ readonly forbid?: string;
43
+ /** format-match: the glob whose changed files must contain `mustMatch`. */
44
+ readonly file?: string;
45
+ /** format-match: a LITERAL substring (never a regex — a lesson-derived regex is unbounded risk). */
46
+ readonly mustMatch?: string;
47
+ }
48
+ /** One change under evaluation: a commit during shadow replay, or the working tree at guard time. */
49
+ export interface ChangeSet {
50
+ /** commit sha, or a synthetic id for the working tree. */
51
+ readonly id: string;
52
+ readonly ts: string;
53
+ readonly files: readonly string[];
54
+ /** path → text. Only `format-match` reads it; absent ⇒ that template cannot be evaluated. */
55
+ readonly contents?: Readonly<Record<string, string>>;
56
+ }
57
+ /** Params are well-formed for their template (a hand-edited config cannot smuggle a half-rule in). */
58
+ export declare function validTemplateParams(template: unknown, params: unknown): params is TemplateParams;
59
+ /**
60
+ * `**` matches any run of characters (including `/`); `*` matches any run WITHOUT `/`. Every other
61
+ * character is regex-escaped, so a lesson-derived token can never become an expression. Anchored at
62
+ * both ends. Never throws.
63
+ *
64
+ * The leading `**​/` is OPTIONAL — `**​/package.json` matches BOTH `packages/a/package.json` and a
65
+ * root-level `package.json`. A naive `.*` + `/` made the segment mandatory, so every promoted rule
66
+ * silently missed root-level files: the shadow replay of a real 12-commit history scored 0 firings
67
+ * and the candidate WAITED forever, looking like an honest verdict. A false gate is only ever found
68
+ * by RUNNING it — the unit tests were green throughout.
69
+ */
70
+ /**
71
+ * The most wildcard groups a glob may contain. Our own classifier emits exactly ONE (`**​/<token>`),
72
+ * so 2 is already generous; the cap exists because a regex built from `**a**a**a…` backtracks
73
+ * catastrophically (Codex QE MEASURED >10 s on such a pattern). Collapsing adjacent `.*` does NOT
74
+ * fix that — `.*a.*a.*a` is polynomial in the number of groups, so degree is the thing to bound.
75
+ * Refusal is the right answer here: these params come from a classifier we control, and a glob
76
+ * beyond the cap is a hand-edited config, not a promotion.
77
+ */
78
+ export declare const MAX_GLOB_WILDCARDS = 2;
79
+ /** Longest path a glob is matched against; beyond this the input is not a repo path. */
80
+ export declare const MAX_GLOB_PATH_LENGTH = 4096;
81
+ /** Longest glob accepted. Mirrors the length bound in {@link validTemplateParams}. */
82
+ export declare const MAX_GLOB_LENGTH = 200;
83
+ /** Collapse `***`/`****`/… runs to `**`, so padding cannot inflate the wildcard count. */
84
+ export declare function normalizeGlob(glob: string): string;
85
+ /** How many wildcard groups (`**` or `*`) a NORMALIZED glob contains. */
86
+ export declare function globWildcardCount(glob: unknown): number;
87
+ /** A glob this module is willing to compile: bounded length AND bounded wildcard degree. */
88
+ export declare function isSafeGlob(glob: unknown): glob is string;
89
+ export declare function globMatch(glob: unknown, path: unknown): boolean;
90
+ export type FireOutcome = {
91
+ readonly fired: boolean;
92
+ readonly detail?: string;
93
+ } | {
94
+ readonly undecidable: string;
95
+ };
96
+ /**
97
+ * Does this (template, params) fire on this change? ONE definition, used by BOTH the historical
98
+ * replay and `evaluateGuard`'s template checker — a second copy would let the promoter promise a
99
+ * rule the guard then enforces differently, silently.
100
+ *
101
+ * `undecidable` (not `fired:false`) when the evidence the template needs is absent: a
102
+ * `format-match` over a change whose contents were not fetched is NOT a clean change, and counting
103
+ * it as a non-firing would convert missing data into a LOSS (the INSUFFICIENT_DATA discipline).
104
+ */
105
+ export declare function templateFires(template: RuleTemplate, params: TemplateParams, change: ChangeSet): FireOutcome;
106
+ export interface ClassifiedLesson {
107
+ readonly template: RuleTemplate;
108
+ readonly params: TemplateParams;
109
+ readonly tokens: readonly string[];
110
+ }
111
+ export interface ClassifyRefusal {
112
+ readonly reason: string;
113
+ }
114
+ export declare function isClassified(x: ClassifiedLesson | ClassifyRefusal): x is ClassifiedLesson;
115
+ /** Artifact tokens in TEXT ORDER, deduped. A token with no `/` becomes a basename glob. */
116
+ export declare function artifactTokens(text: string): string[];
117
+ /** `README.md` → `**​/README.md`; `packages/x/README.md` → itself. */
118
+ export declare function tokenToGlob(token: string): string;
119
+ /**
120
+ * Reduce a lesson to a (template, params) pair, or refuse WITH A REASON.
121
+ *
122
+ * Conservative by construction and asymmetric by design: a false negative costs a missed promotion
123
+ * (the lesson stays exactly where it already was); a false positive is caught downstream by the
124
+ * win-twice gate and the duplicate refusal, and even a survivor lands SOFT + advisory.
125
+ */
126
+ export declare function classifyLesson(text: unknown): ClassifiedLesson | ClassifyRefusal;
127
+ /**
128
+ * FNV-1a, 32-bit — a DISCRIMINATOR, not a security primitive, and labelled as one.
129
+ *
130
+ * It exists solely to keep two DIFFERENT rule bodies from claiming the same id after slug
131
+ * normalisation (`a.b.json` and `a-b.json` both slug to `a-b-json`). Nothing trusts it for
132
+ * integrity or authenticity; the key space is a few dozen self-generated rule bodies, so a
133
+ * non-cryptographic 32-bit mix is ample. Kept in-module because this file is deliberately pure with
134
+ * zero imports (NFR-1) — reaching for `node:crypto` here would buy nothing the threat model needs.
135
+ */
136
+ export declare function fnv1a32(s: string): string;
137
+ /**
138
+ * Stable rule id derived from the template + its bound params.
139
+ *
140
+ * The trailing hash is load-bearing (Codex QE MED-6): the slug lowercases and collapses every
141
+ * non-alphanumeric run, so `a.b.json` and `a-b.json` — two genuinely different rules — produced the
142
+ * SAME id and the second silently read as a duplicate of the first. The hash is taken over the
143
+ * template and the actual PARAMS (not the pre-normalisation tokens), so two rules collide only if
144
+ * they would enforce exactly the same thing.
145
+ */
146
+ export declare function derivedRuleId(c: ClassifiedLesson): string;
147
+ /**
148
+ * The character set a promoted rule id may use. Enforced wherever an id becomes part of a FILE PATH:
149
+ * an id is data that has round-tripped through `.dz/promotion-state.json`, and a path segment built
150
+ * from unvalidated data is an arbitrary-write primitive (Codex QE HIGH-1).
151
+ */
152
+ export declare function isSafeRuleId(id: unknown): id is string;
153
+ /** The one place mini-ADR paths are defined (POSIX-relative, forward slashes). */
154
+ export declare const PROMOTIONS_REL_DIR = "features/guard-promotion/promotions";
155
+ /**
156
+ * DERIVE a mini-ADR path from a validated id + an integer sequence — the only way a promotion
157
+ * document path is ever produced (Codex QE HIGH-1). Returns `null` when either input fails
158
+ * validation, so a caller that gets `null` writes nothing rather than falling back to a raw string.
159
+ * The character set (`isSafeRuleId`) admits no `/`, no `.`, and no `..`, so the result cannot escape
160
+ * {@link PROMOTIONS_REL_DIR}; callers still assert containment after resolving, because a derivation
161
+ * that is correct today is not a substitute for checking the thing you are about to write.
162
+ */
163
+ export declare function promotionAdrRelPath(ruleId: unknown, seq: unknown): string | null;
164
+ /**
165
+ * Template-equivalents of the built-in guard rules that have one. Rule ids are plain literals — this
166
+ * module must not import `guard.ts` (guard.ts imports THIS one).
167
+ *
168
+ * DELIBERATELY PARTIAL. `no-secrets` (content regexes), `readme-consistency` (numeric parity),
169
+ * `no-skill-drift` (byte comparison) and `store-bloat-cap` (a counter) have no template equivalent
170
+ * and are simply absent. Partiality errs in the SAFE direction only because over-refusing costs a
171
+ * missed promotion while under-refusing ships a duplicate rule — so when in doubt, add an entry.
172
+ */
173
+ export declare const BUILTIN_COVERAGE: Readonly<Record<string, {
174
+ readonly template: RuleTemplate;
175
+ readonly params: TemplateParams;
176
+ }>>;
177
+ /** Order-insensitive, whitespace-insensitive params key for equality. */
178
+ export declare function paramsKey(template: RuleTemplate, params: TemplateParams): string;
179
+ /** A rule already present in the engine or the config, in the shape the dedup check consumes. */
180
+ export interface ExistingRuleView {
181
+ readonly id: string;
182
+ readonly template?: RuleTemplate;
183
+ readonly params?: TemplateParams;
184
+ }
185
+ /** The id of the rule that already covers this candidate, or `null`. */
186
+ export declare function coveringRule(c: ClassifiedLesson, existing: readonly ExistingRuleView[]): string | null;
187
+ export declare const DEFAULT_WINDOW_DAYS = 7;
188
+ export declare const DEFAULT_PERIODS = 4;
189
+ /** Below this many changes a window carries no information — it is SKIPPED, never counted a loss. */
190
+ export declare const MIN_CHANGES_PER_PERIOD = 5;
191
+ export declare const WINS_TO_PROMOTE = 2;
192
+ /** Cap on `git show` fetches per run; over it, a format-match candidate is insufficient-data. */
193
+ export declare const MAX_CONTENT_FETCHES = 200;
194
+ export interface Period {
195
+ readonly start: string;
196
+ readonly end: string;
197
+ readonly changes: readonly ChangeSet[];
198
+ }
199
+ /**
200
+ * Cut history into `periods` consecutive `windowDays` windows anchored at `nowMs`, walking BACKWARDS
201
+ * and returned oldest→newest. Wall-clock windows, NOT per-invocation and NOT per-commit-count: an
202
+ * operator's invocation frequency must never be an input to a safety gate (ADR-003 option A).
203
+ */
204
+ export declare function buildPeriods(changes: readonly ChangeSet[], nowMs: number, windowDays?: number, periods?: number): Period[];
205
+ export type PeriodOutcome = 'win' | 'loss' | 'skipped';
206
+ export interface PeriodResult {
207
+ readonly start: string;
208
+ readonly end: string;
209
+ readonly changes: number;
210
+ readonly firings: number;
211
+ readonly outcome: PeriodOutcome;
212
+ /** first firing's evidence — a real commit id, so a win is always citable. */
213
+ readonly evidence?: string;
214
+ }
215
+ export interface CandidateEvaluation {
216
+ readonly periods: readonly PeriodResult[];
217
+ readonly evaluatedPeriods: number;
218
+ /** consecutive wins ENDING at the newest evaluated period. A loss resets to 0. */
219
+ readonly wins: number;
220
+ readonly totalFirings: number;
221
+ /** set when a template could not be decided over the available evidence. */
222
+ readonly undecidable?: string;
223
+ }
224
+ /**
225
+ * Replay the candidate over each period.
226
+ *
227
+ * A period below {@link MIN_CHANGES_PER_PERIOD} is SKIPPED — a one-commit week that happens not to
228
+ * touch package.json is NOT evidence the pairing rule is worthless, it is NO evidence, and absence
229
+ * of data must never be converted into a negative observation.
230
+ */
231
+ export declare function evaluateCandidate(c: ClassifiedLesson, periods: readonly Period[], minChanges?: number): CandidateEvaluation;
232
+ export type CandidateVerdict = 'promote' | 'wait' | 'insufficient-data' | 'duplicate' | 'not-promotable';
233
+ export interface LessonInput {
234
+ readonly dzId: string;
235
+ readonly text: string;
236
+ readonly quarantined: boolean;
237
+ /** reinforcement/use count — the `cost` PROXY (see `score`). */
238
+ readonly uses: number;
239
+ }
240
+ export interface PromotionCandidate {
241
+ readonly lessonId: string;
242
+ readonly lessonText: string;
243
+ readonly ruleId: string | null;
244
+ readonly template: RuleTemplate | null;
245
+ readonly params: TemplateParams | null;
246
+ readonly verdict: CandidateVerdict;
247
+ readonly reason: string;
248
+ /** `totalFirings × (1 + uses)`. `cost` is an explicitly named PROXY, not a token/dollar figure. */
249
+ readonly score: number;
250
+ readonly firings: number;
251
+ readonly cost: number;
252
+ readonly wins: number;
253
+ readonly evaluatedPeriods: number;
254
+ readonly periods: readonly PeriodResult[];
255
+ /** the exact `.dz/guard.json` entry `--apply` would write (only for `promote`). */
256
+ readonly proposedRule: PromotedRule | null;
257
+ /** LOCAL-clock first observation (see {@link ELAPSED_WINDOWS_REQUIRED}); null until recorded. */
258
+ readonly firstSeenTs: string | null;
259
+ /** real elapsed ms since `firstSeenTs`, and the amount required, both for the report. */
260
+ readonly elapsedMs: number;
261
+ readonly elapsedRequiredMs: number;
262
+ }
263
+ export interface PromotionFacts {
264
+ readonly lessons: readonly LessonInput[];
265
+ readonly existingRules: readonly ExistingRuleView[];
266
+ readonly changes: readonly ChangeSet[];
267
+ readonly nowTs: string;
268
+ readonly windowDays?: number;
269
+ readonly periods?: number;
270
+ /**
271
+ * ruleId → the LOCAL-CLOCK timestamp at which this candidate was first recorded in
272
+ * `.dz/promotion-state.json`. See {@link ELAPSED_WINDOWS_REQUIRED}. Absent ⇒ first observation
273
+ * ⇒ the elapsed clock starts now, so nothing promotes on the very first recording run.
274
+ */
275
+ readonly firstSeen?: Readonly<Record<string, string>>;
276
+ }
277
+ /**
278
+ * A promotion also needs this many WINDOW-LENGTHS of REAL elapsed time since the candidate was first
279
+ * recorded — a defence Codex QE (MED-7) showed the window logic alone does not provide.
280
+ *
281
+ * The threat is not an attacker; it is ACCIDENTAL SELF-GAMING. Commit timestamps are author-supplied
282
+ * (`GIT_COMMITTER_DATE`, a rebase, an import, a clock skew), so a repo whose history is minted in one
283
+ * afternoon can present two full "windows" instantly, and the gate that is supposed to mean *"this
284
+ * recurred over two separate stretches of work"* would mean nothing.
285
+ *
286
+ * THE HONEST SPLIT, stated so it is not mistaken for more than it is:
287
+ * • committer dates are trusted for firing ATTRIBUTION — which commit a violation belongs to;
288
+ * • the LOCAL clock, journalled in state, gates ELAPSED time — how long we have been watching.
289
+ * This is not cryptographic and does not resist a determined forger (state is a local JSON file you
290
+ * can edit). It resists the realistic failure: history that only LOOKS like it spans two windows.
291
+ */
292
+ export declare const ELAPSED_WINDOWS_REQUIRED = 2;
293
+ export interface PromotionReport {
294
+ readonly candidates: readonly PromotionCandidate[];
295
+ readonly totalLessons: number;
296
+ readonly quarantinedSkipped: number;
297
+ readonly windowDays: number;
298
+ readonly periodCount: number;
299
+ readonly totalChanges: number;
300
+ readonly verdict: string;
301
+ }
302
+ /** The rule object written into `.dz/guard.json`. SOFT always — see ADR-004 / `resolveRules`. */
303
+ export interface PromotedRule {
304
+ readonly id: string;
305
+ readonly severity: 'soft';
306
+ readonly ops: readonly string[];
307
+ readonly enabled: true;
308
+ readonly template: RuleTemplate;
309
+ readonly params: TemplateParams;
310
+ readonly description: string;
311
+ }
312
+ export declare function promotedRuleObject(c: ClassifiedLesson, ruleId: string, lessonId: string): PromotedRule;
313
+ /**
314
+ * Rank every lesson and decide. Deterministic: the sort is (score desc, ruleId asc, lessonId asc), so
315
+ * ties never reorder between runs.
316
+ */
317
+ export declare function assembleCandidates(facts: PromotionFacts): PromotionReport;
318
+ export interface PromotionStateEntry {
319
+ readonly ruleId: string;
320
+ readonly lessonId: string;
321
+ readonly firstSeenTs: string;
322
+ readonly lastRunTs: string;
323
+ readonly wins: number;
324
+ readonly evaluatedPeriods: number;
325
+ readonly verdict: CandidateVerdict;
326
+ /**
327
+ * The mini-ADR's SEQUENCE NUMBER — never its path. The path is DERIVED from this integer and the
328
+ * validated rule id at write time (Codex QE HIGH-1): a path read back out of state and handed to
329
+ * `writeFileSync` is an arbitrary-write primitive, and a corrupted entry
330
+ * (`"adr": "../../victim"` or `".dz/guard.json"`) would overwrite files with no `--apply` at all.
331
+ */
332
+ readonly adrSeq?: number;
333
+ readonly appliedTs?: string;
334
+ }
335
+ export interface PromotionState {
336
+ readonly version: 1;
337
+ readonly nextAdrSeq: number;
338
+ readonly entries: Readonly<Record<string, PromotionStateEntry>>;
339
+ }
340
+ export declare const EMPTY_PROMOTION_STATE: PromotionState;
341
+ /**
342
+ * Read state defensively. `Object.hasOwn` (never `in`) so a prototype-polluted JSON cannot conjure an
343
+ * entry; `Number.isInteger` on every counter because `1e400` parses to `Infinity`, passes `> 0`, and
344
+ * this repo has already been bitten by exactly that twice (storeCap, auto-cost).
345
+ */
346
+ export declare function normalizePromotionState(raw: unknown): PromotionState;
347
+ /**
348
+ * Fold a report into the state journal.
349
+ *
350
+ * THE ANTI-GAMING PROPERTY (SP-3): `wins` is OVERWRITTEN with the freshly recomputed value — it is
351
+ * never `prev.wins + …`. The state is a JOURNAL, not the source of truth, so running the promoter
352
+ * ten times over unchanged history leaves the counter exactly where one run leaves it. (Recalled
353
+ * lesson: "a learning loop's write path can promote by EXPOSURE without anyone noticing.")
354
+ */
355
+ export declare function nextPromotionState(prev: PromotionState, report: PromotionReport, nowTs: string, adrSeqs?: Readonly<Record<string, number>>, newlyAllocated?: number): PromotionState;
356
+ export declare function renderPromotionReport(r: PromotionReport, limit?: number): string;
357
+ /**
358
+ * The mini-ADR for one decision. Written for PROMOTIONS and REJECTIONS alike (ADR-G008 requires
359
+ * both) — a refusal is a decision about the harness's own capability, and it is what turns the
360
+ * "not promotable" list into a roadmap instead of a shrug. `wait` / `insufficient-data` get NO
361
+ * document: they are not decisions yet, and one per run would bury the real ones.
362
+ */
363
+ export declare function renderPromotionAdr(c: PromotionCandidate, seq: number, nowTs: string): string;
364
+ //# sourceMappingURL=guard-promotion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard-promotion.d.ts","sourceRoot":"","sources":["../src/guard-promotion.ts"],"names":[],"mappings":"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"}