@mmnto/totem 1.87.0 → 1.89.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/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/sarif.d.ts +17 -0
- package/dist/sarif.d.ts.map +1 -1
- package/dist/sarif.js +71 -5
- package/dist/sarif.js.map +1 -1
- package/dist/sarif.test.js +94 -1
- package/dist/sarif.test.js.map +1 -1
- package/dist/spine/authored-controls.d.ts +8 -0
- package/dist/spine/authored-controls.d.ts.map +1 -1
- package/dist/spine/authored-controls.js +15 -5
- package/dist/spine/authored-controls.js.map +1 -1
- package/dist/spine/authored-controls.test.js +31 -0
- package/dist/spine/authored-controls.test.js.map +1 -1
- package/dist/spine/authored-freeze-gates.d.ts +45 -0
- package/dist/spine/authored-freeze-gates.d.ts.map +1 -0
- package/dist/spine/authored-freeze-gates.js +167 -0
- package/dist/spine/authored-freeze-gates.js.map +1 -0
- package/dist/spine/authored-freeze-gates.test.d.ts +2 -0
- package/dist/spine/authored-freeze-gates.test.d.ts.map +1 -0
- package/dist/spine/authored-freeze-gates.test.js +159 -0
- package/dist/spine/authored-freeze-gates.test.js.map +1 -0
- package/dist/spine/authored-rule.d.ts +10 -0
- package/dist/spine/authored-rule.d.ts.map +1 -1
- package/dist/spine/authored-rule.js +11 -0
- package/dist/spine/authored-rule.js.map +1 -1
- package/dist/spine/authoring-ledger.d.ts +28 -0
- package/dist/spine/authoring-ledger.d.ts.map +1 -1
- package/dist/spine/authoring-ledger.js +27 -0
- package/dist/spine/authoring-ledger.js.map +1 -1
- package/dist/spine/authoring-ledger.test.js +8 -0
- package/dist/spine/authoring-ledger.test.js.map +1 -1
- package/dist/spine/cert-corpus-seed.d.ts +38 -8
- package/dist/spine/cert-corpus-seed.d.ts.map +1 -1
- package/dist/spine/cert-corpus-seed.js +52 -1
- package/dist/spine/cert-corpus-seed.js.map +1 -1
- package/dist/spine/cert-corpus-seed.test.js +76 -0
- package/dist/spine/cert-corpus-seed.test.js.map +1 -1
- package/dist/spine/frozen-split.d.ts +541 -0
- package/dist/spine/frozen-split.d.ts.map +1 -0
- package/dist/spine/frozen-split.js +205 -0
- package/dist/spine/frozen-split.js.map +1 -0
- package/dist/spine/frozen-split.test.d.ts +2 -0
- package/dist/spine/frozen-split.test.d.ts.map +1 -0
- package/dist/spine/frozen-split.test.js +89 -0
- package/dist/spine/frozen-split.test.js.map +1 -0
- package/dist/spine/gate2-eligibility.d.ts +33 -0
- package/dist/spine/gate2-eligibility.d.ts.map +1 -0
- package/dist/spine/gate2-eligibility.js +54 -0
- package/dist/spine/gate2-eligibility.js.map +1 -0
- package/dist/spine/gate2-eligibility.test.d.ts +2 -0
- package/dist/spine/gate2-eligibility.test.d.ts.map +1 -0
- package/dist/spine/gate2-eligibility.test.js +252 -0
- package/dist/spine/gate2-eligibility.test.js.map +1 -0
- package/dist/spine/ledgers.d.ts +24 -0
- package/dist/spine/ledgers.d.ts.map +1 -1
- package/dist/spine/split.d.ts +16 -0
- package/dist/spine/split.d.ts.map +1 -1
- package/dist/spine/split.js +13 -0
- package/dist/spine/split.js.map +1 -1
- package/dist/spine/windtunnel-firing.d.ts +6 -2
- package/dist/spine/windtunnel-firing.d.ts.map +1 -1
- package/dist/spine/windtunnel-firing.js.map +1 -1
- package/dist/spine/windtunnel-lock.d.ts +4 -4
- package/dist/spine/windtunnel-scorer-authored.d.ts +70 -0
- package/dist/spine/windtunnel-scorer-authored.d.ts.map +1 -0
- package/dist/spine/windtunnel-scorer-authored.js +204 -0
- package/dist/spine/windtunnel-scorer-authored.js.map +1 -0
- package/dist/spine/windtunnel-scorer-authored.test.d.ts +2 -0
- package/dist/spine/windtunnel-scorer-authored.test.d.ts.map +1 -0
- package/dist/spine/windtunnel-scorer-authored.test.js +455 -0
- package/dist/spine/windtunnel-scorer-authored.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// ─── ADR-112 §5.1/§8 R1 — the FROZEN SPLIT artifact (pre-authoring freeze) ────
|
|
2
|
+
//
|
|
3
|
+
// The real-set slice R1 (strategy 0102Z contract, Option A mechanism-first): the
|
|
4
|
+
// tamper-evident, TRACKED-PUBLIC artifact a `totem spine freeze-split` run writes
|
|
5
|
+
// BEFORE any rule is authored. It is the single home (Tenet-20) for the split
|
|
6
|
+
// facts an authored cert run consumes — membership, `frozenAt`, the derivation
|
|
7
|
+
// pins — superseding the D5 shape where materialize resolved the split from seed
|
|
8
|
+
// params (that resolution is demoted to a verification ASSERT, spine-authored-
|
|
9
|
+
// materialize fold-5).
|
|
10
|
+
//
|
|
11
|
+
// Tamper-evidence is (a)+(b) COMPOSED (the ruled construction):
|
|
12
|
+
// (a) commit-anchored — the artifact is committed to shared history BEFORE
|
|
13
|
+
// authoring; the proof is TOPOLOGY (introducing commit on origin/main
|
|
14
|
+
// ancestry, ledger entries strictly later by ancestry), never a timestamp —
|
|
15
|
+
// `GIT_COMMITTER_DATE` is settable, ancestry rewrites are observable
|
|
16
|
+
// (codex fold-1; the git plumbing lives cli-side in spine-freeze-proof.ts).
|
|
17
|
+
// (b) hash-commitment — `freezeCommitment = sha256(splitRef · frozenAt ·
|
|
18
|
+
// corpusIntegrity)`; every subsequent authoring-ledger entry carries it
|
|
19
|
+
// INSIDE its `authoringContentHash` material, so a re-freeze flips every
|
|
20
|
+
// downstream entry to would-revise, never "unchanged" (codex fold-2).
|
|
21
|
+
//
|
|
22
|
+
// `splitRef` is the CONTENT ADDRESS of the canonical artifact payload —
|
|
23
|
+
// `split:<sha256>` over every field EXCEPT `splitRef` / `freezeCommitment` /
|
|
24
|
+
// `label` (the commitment cannot be inside its own preimage; the label is a
|
|
25
|
+
// human handle, never load-bearing — codex fold-3). This module is PURE
|
|
26
|
+
// (schema + derivations); IO and git live in the cli layer.
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
import { z } from 'zod';
|
|
29
|
+
import { canonicalStringify } from '../compile-manifest.js';
|
|
30
|
+
import { SplitArtifactSchema } from './split.js';
|
|
31
|
+
const nonBlank = (label) => z.string().refine((s) => s.trim().length > 0, { message: `${label} must be non-empty` });
|
|
32
|
+
/** Lowercase 40-hex git commit SHA (canonical git form). */
|
|
33
|
+
const COMMIT_SHA_RE = /^[0-9a-f]{40}$/;
|
|
34
|
+
/** Full sha256 hex — the commitment / integrity stamp width. */
|
|
35
|
+
const SHA256_RE = /^[0-9a-f]{64}$/;
|
|
36
|
+
/** The content-addressed frozen-split reference form (codex fold-3). */
|
|
37
|
+
export const SPLIT_REF_RE = /^split:[0-9a-f]{64}$/;
|
|
38
|
+
/** Filename of the frozen split artifact inside its gate dir (the tracked-public home). */
|
|
39
|
+
export const FROZEN_SPLIT_FILE = 'frozen-split.json';
|
|
40
|
+
/**
|
|
41
|
+
* The selection-rule derivation PINS — the exact inputs the freeze derived the
|
|
42
|
+
* corpus from, recorded so materialize can RE-DERIVE the split and assert byte
|
|
43
|
+
* equality (detect-never-repair; re-derivation is a detector, never a source).
|
|
44
|
+
*/
|
|
45
|
+
export const SelectionPinsSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
predicate: nonBlank('selectionPins.predicate'),
|
|
48
|
+
window: z.discriminatedUnion('type', [
|
|
49
|
+
z.object({ type: z.literal('all') }),
|
|
50
|
+
z.object({ type: z.literal('bounded'), n: z.number().int().positive() }),
|
|
51
|
+
]),
|
|
52
|
+
codePathClassifier: z.object({
|
|
53
|
+
includeGlobs: z.array(z.string()),
|
|
54
|
+
excludeGlobs: z.array(z.string()),
|
|
55
|
+
}),
|
|
56
|
+
excludeRevertPairs: z.boolean(),
|
|
57
|
+
excludeBotPrs: z.boolean(),
|
|
58
|
+
})
|
|
59
|
+
.strict();
|
|
60
|
+
/**
|
|
61
|
+
* The `totem spine freeze-split` INPUT (the curated freeze decisions). Everything
|
|
62
|
+
* else — `asOfCommit` (lc HEAD at freeze, Q3 derived-at-freeze), the corpus, the
|
|
63
|
+
* split membership, `frozenAt`, the stamps — is DERIVED at freeze and pinned in
|
|
64
|
+
* the artifact. `cutIndex` is the recorded build-choice under the held-out ≥ 0.5
|
|
65
|
+
* floor (#804).
|
|
66
|
+
*/
|
|
67
|
+
export const FreezeSplitParamsSchema = z
|
|
68
|
+
.object({
|
|
69
|
+
gate: nonBlank('gate').refine((s) => /^[a-z0-9-]+$/.test(s), {
|
|
70
|
+
message: 'gate must be a kebab slug — it names the tracked freeze home directory',
|
|
71
|
+
}),
|
|
72
|
+
repo: nonBlank('repo'),
|
|
73
|
+
selectionRule: SelectionPinsSchema,
|
|
74
|
+
split: z.object({
|
|
75
|
+
cutIndex: z.number().int().nonnegative(),
|
|
76
|
+
excludedPrs: z.array(z.number().int().positive()).default([]),
|
|
77
|
+
}),
|
|
78
|
+
label: z.string().optional(),
|
|
79
|
+
})
|
|
80
|
+
.strict();
|
|
81
|
+
/**
|
|
82
|
+
* The frozen split artifact. `.strict()` — an unknown field is tamper/corruption,
|
|
83
|
+
* never silently carried. `split.frozenAt` is REQUIRED here (the freeze IS the
|
|
84
|
+
* event that mints it) even though `SplitArtifactSchema` keeps it optional for
|
|
85
|
+
* the mined/legacy shape.
|
|
86
|
+
*/
|
|
87
|
+
export const FrozenSplitArtifactSchema = z
|
|
88
|
+
.object({
|
|
89
|
+
schemaVersion: z.literal(1),
|
|
90
|
+
gate: nonBlank('gate'),
|
|
91
|
+
repo: nonBlank('repo'),
|
|
92
|
+
selectionPins: SelectionPinsSchema,
|
|
93
|
+
/** The resolved split (single home for membership + `frozenAt`; `asOfCommit` lives inside). */
|
|
94
|
+
split: SplitArtifactSchema,
|
|
95
|
+
/**
|
|
96
|
+
* The LAST train PR's merge commit — the §5.4 author-sandbox root derives
|
|
97
|
+
* from THIS (an lc worktree at the cut boundary), never from an
|
|
98
|
+
* author-supplied knob (the judgedBy≠author independence axiom applied to
|
|
99
|
+
* config — codex sandbox note).
|
|
100
|
+
*/
|
|
101
|
+
cutBoundarySha: z.string().regex(COMMIT_SHA_RE),
|
|
102
|
+
/** sha256 over the canonical corpus enumeration (PRs + merge commits) — the (b) commitment leg. */
|
|
103
|
+
corpusIntegrity: z.string().regex(SHA256_RE),
|
|
104
|
+
/** Content address of the canonical payload (all fields except splitRef/freezeCommitment/label). */
|
|
105
|
+
splitRef: z.string().regex(SPLIT_REF_RE),
|
|
106
|
+
/** `sha256(splitRef · frozenAt · corpusIntegrity)` — chained into every ledger entry's material. */
|
|
107
|
+
freezeCommitment: z.string().regex(SHA256_RE),
|
|
108
|
+
/** Optional human handle — NEVER load-bearing (resolution goes by `splitRef`). */
|
|
109
|
+
label: z.string().optional(),
|
|
110
|
+
})
|
|
111
|
+
.strict()
|
|
112
|
+
.superRefine((a, ctx) => {
|
|
113
|
+
if (a.split.frozenAt === undefined) {
|
|
114
|
+
ctx.addIssue({
|
|
115
|
+
code: z.ZodIssueCode.custom,
|
|
116
|
+
message: 'a frozen split artifact must carry split.frozenAt (the freeze mints the instant)',
|
|
117
|
+
path: ['split', 'frozenAt'],
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* sha256 over the canonical corpus enumeration: the sorted PR list + each PR's
|
|
123
|
+
* merge commit. Deterministic from the pinned inputs; a post-freeze change to
|
|
124
|
+
* either (t7 content injection) breaks the commitment chain loudly.
|
|
125
|
+
*/
|
|
126
|
+
export function computeCorpusIntegrity(corpus, mergeCommitByPr) {
|
|
127
|
+
const rows = [...corpus]
|
|
128
|
+
.sort((a, b) => a - b)
|
|
129
|
+
.map((pr) => ({ pr, mergeCommit: mergeCommitByPr.get(pr) ?? null }));
|
|
130
|
+
return createHash('sha256').update(canonicalStringify(rows)).digest('hex');
|
|
131
|
+
}
|
|
132
|
+
/** The content-address preimage: every artifact field EXCEPT splitRef / freezeCommitment / label. */
|
|
133
|
+
function refPayload(a) {
|
|
134
|
+
return {
|
|
135
|
+
schemaVersion: 1,
|
|
136
|
+
gate: a.gate,
|
|
137
|
+
repo: a.repo,
|
|
138
|
+
selectionPins: a.selectionPins,
|
|
139
|
+
split: a.split,
|
|
140
|
+
cutBoundarySha: a.cutBoundarySha,
|
|
141
|
+
corpusIntegrity: a.corpusIntegrity,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** `split:<sha256(canonical payload)>` — codex fold-3: the ref is the artifact's content address. */
|
|
145
|
+
export function computeFrozenSplitRef(a) {
|
|
146
|
+
return `split:${createHash('sha256')
|
|
147
|
+
.update(canonicalStringify(refPayload(a)))
|
|
148
|
+
.digest('hex')}`;
|
|
149
|
+
}
|
|
150
|
+
/** The (b) hash-commitment: `sha256(splitRef · frozenAt · corpusIntegrity)` over the canonical tuple. */
|
|
151
|
+
export function computeFreezeCommitment(splitRef, frozenAt, corpusIntegrity) {
|
|
152
|
+
return createHash('sha256').update(`${splitRef}\n${frozenAt}\n${corpusIntegrity}`).digest('hex');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Derive splitRef + freezeCommitment from the freeze-time inputs and return the
|
|
156
|
+
* validated artifact. Throws (Zod) if the assembly is malformed — a malformed
|
|
157
|
+
* freeze never produces an artifact (Tenet 4).
|
|
158
|
+
*/
|
|
159
|
+
export function assembleFrozenSplitArtifact(a) {
|
|
160
|
+
if (a.split.frozenAt === undefined) {
|
|
161
|
+
// Pre-empt the schema superRefine with the operative message: the freeze
|
|
162
|
+
// mints the instant; an assembly without one is a caller contract violation.
|
|
163
|
+
throw new Error('[Totem Error] assembleFrozenSplitArtifact: split.frozenAt is absent — the freeze mints the instant; assemble AFTER stamping it');
|
|
164
|
+
}
|
|
165
|
+
const splitRef = computeFrozenSplitRef(a);
|
|
166
|
+
const freezeCommitment = computeFreezeCommitment(splitRef, a.split.frozenAt, a.corpusIntegrity);
|
|
167
|
+
return FrozenSplitArtifactSchema.parse({
|
|
168
|
+
schemaVersion: 1,
|
|
169
|
+
gate: a.gate,
|
|
170
|
+
repo: a.repo,
|
|
171
|
+
selectionPins: a.selectionPins,
|
|
172
|
+
split: a.split,
|
|
173
|
+
cutBoundarySha: a.cutBoundarySha,
|
|
174
|
+
corpusIntegrity: a.corpusIntegrity,
|
|
175
|
+
splitRef,
|
|
176
|
+
freezeCommitment,
|
|
177
|
+
...(a.label !== undefined ? { label: a.label } : {}),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Recompute splitRef + freezeCommitment from the artifact's own payload and
|
|
182
|
+
* compare — the pure half of tamper detection (any in-place edit to a pinned
|
|
183
|
+
* field breaks one or both). Consumers throw a distinct fail-loud row on `!ok`;
|
|
184
|
+
* this stays a sensor (Tenet 13).
|
|
185
|
+
*/
|
|
186
|
+
export function verifyFreezeIntegrity(artifact) {
|
|
187
|
+
const assembly = {
|
|
188
|
+
gate: artifact.gate,
|
|
189
|
+
repo: artifact.repo,
|
|
190
|
+
selectionPins: artifact.selectionPins,
|
|
191
|
+
split: artifact.split,
|
|
192
|
+
cutBoundarySha: artifact.cutBoundarySha,
|
|
193
|
+
corpusIntegrity: artifact.corpusIntegrity,
|
|
194
|
+
};
|
|
195
|
+
const expectedSplitRef = computeFrozenSplitRef(assembly);
|
|
196
|
+
// `frozenAt` presence is schema-enforced (superRefine); the fallback keeps the
|
|
197
|
+
// recompute total rather than throwing inside a sensor.
|
|
198
|
+
const expectedCommitment = computeFreezeCommitment(artifact.splitRef, artifact.split.frozenAt ?? '', artifact.corpusIntegrity);
|
|
199
|
+
return {
|
|
200
|
+
ok: expectedSplitRef === artifact.splitRef && expectedCommitment === artifact.freezeCommitment,
|
|
201
|
+
expectedSplitRef,
|
|
202
|
+
expectedCommitment,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=frozen-split.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frozen-split.js","sourceRoot":"","sources":["../../src/spine/frozen-split.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frozen-split.test.d.ts","sourceRoot":"","sources":["../../src/spine/frozen-split.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { assembleFrozenSplitArtifact, computeCorpusIntegrity, computeFreezeCommitment, computeFrozenSplitRef, FrozenSplitArtifactSchema, SPLIT_REF_RE, verifyFreezeIntegrity, } from './frozen-split.js';
|
|
3
|
+
const sha = (n) => String(n).padStart(40, '0');
|
|
4
|
+
const FROZEN_AT = '2026-06-01T00:00:00.000Z';
|
|
5
|
+
function split(overrides) {
|
|
6
|
+
return {
|
|
7
|
+
asOfCommit: sha(999),
|
|
8
|
+
trainPrs: [1, 2],
|
|
9
|
+
heldOutPrs: [3, 4, 5],
|
|
10
|
+
excludedPrs: [],
|
|
11
|
+
positiveControlPrs: [],
|
|
12
|
+
negativeControlPrs: [],
|
|
13
|
+
splitRule: { predicate: 'code-touching non-bot', cutIndex: 2 },
|
|
14
|
+
frozenAt: FROZEN_AT,
|
|
15
|
+
...overrides,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function assembly(overrides) {
|
|
19
|
+
return {
|
|
20
|
+
gate: 'gate-1',
|
|
21
|
+
repo: 'mmnto-ai/liquid-city',
|
|
22
|
+
selectionPins: {
|
|
23
|
+
predicate: 'code-touching non-bot',
|
|
24
|
+
window: { type: 'all' },
|
|
25
|
+
codePathClassifier: { includeGlobs: ['packages/**'], excludeGlobs: ['**/*.md'] },
|
|
26
|
+
excludeRevertPairs: true,
|
|
27
|
+
excludeBotPrs: true,
|
|
28
|
+
},
|
|
29
|
+
split: split(),
|
|
30
|
+
cutBoundarySha: sha(2),
|
|
31
|
+
corpusIntegrity: computeCorpusIntegrity([1, 2, 3, 4, 5], new Map([1, 2, 3, 4, 5].map((pr) => [pr, sha(pr)]))),
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
describe('frozen-split derivations (R1 — codex fold-2/fold-3)', () => {
|
|
36
|
+
it('derives a deterministic content-addressed splitRef', () => {
|
|
37
|
+
const a = assembleFrozenSplitArtifact(assembly());
|
|
38
|
+
const b = assembleFrozenSplitArtifact(assembly());
|
|
39
|
+
expect(a.splitRef).toBe(b.splitRef);
|
|
40
|
+
expect(a.splitRef).toMatch(SPLIT_REF_RE);
|
|
41
|
+
});
|
|
42
|
+
it('the splitRef preimage EXCLUDES the commitment and the label (no circularity)', () => {
|
|
43
|
+
const base = assembleFrozenSplitArtifact(assembly());
|
|
44
|
+
const labeled = assembleFrozenSplitArtifact(assembly({ label: 'cert-1 freeze' }));
|
|
45
|
+
expect(labeled.splitRef).toBe(base.splitRef);
|
|
46
|
+
expect(labeled.freezeCommitment).toBe(base.freezeCommitment);
|
|
47
|
+
});
|
|
48
|
+
it('a pinned-field change moves the splitRef AND the commitment', () => {
|
|
49
|
+
const base = assembleFrozenSplitArtifact(assembly());
|
|
50
|
+
const moved = assembleFrozenSplitArtifact(assembly({ cutBoundarySha: sha(3) }));
|
|
51
|
+
expect(moved.splitRef).not.toBe(base.splitRef);
|
|
52
|
+
expect(moved.freezeCommitment).not.toBe(base.freezeCommitment);
|
|
53
|
+
});
|
|
54
|
+
it('a re-stamped frozenAt moves the commitment (the t1 orphaning preimage)', () => {
|
|
55
|
+
const base = assembleFrozenSplitArtifact(assembly());
|
|
56
|
+
const restamped = assembleFrozenSplitArtifact(assembly({ split: split({ frozenAt: '2026-06-02T00:00:00.000Z' }) }));
|
|
57
|
+
expect(restamped.freezeCommitment).not.toBe(base.freezeCommitment);
|
|
58
|
+
// Direct tuple check: the commitment is exactly sha256(splitRef · frozenAt · corpusIntegrity).
|
|
59
|
+
expect(base.freezeCommitment).toBe(computeFreezeCommitment(base.splitRef, FROZEN_AT, base.corpusIntegrity));
|
|
60
|
+
});
|
|
61
|
+
it('corpusIntegrity is order-insensitive over the corpus but merge-commit-sensitive', () => {
|
|
62
|
+
const map = new Map([1, 2, 3].map((pr) => [pr, sha(pr)]));
|
|
63
|
+
expect(computeCorpusIntegrity([3, 1, 2], map)).toBe(computeCorpusIntegrity([1, 2, 3], map));
|
|
64
|
+
const moved = new Map(map);
|
|
65
|
+
moved.set(3, sha(33));
|
|
66
|
+
expect(computeCorpusIntegrity([1, 2, 3], moved)).not.toBe(computeCorpusIntegrity([1, 2, 3], map));
|
|
67
|
+
});
|
|
68
|
+
it('verifyFreezeIntegrity detects an in-place membership edit (t7 sensor)', () => {
|
|
69
|
+
const artifact = assembleFrozenSplitArtifact(assembly());
|
|
70
|
+
const tampered = { ...artifact, split: { ...artifact.split, heldOutPrs: [3, 4] } };
|
|
71
|
+
const check = verifyFreezeIntegrity(tampered);
|
|
72
|
+
expect(check.ok).toBe(false);
|
|
73
|
+
expect(check.expectedSplitRef).not.toBe(tampered.splitRef);
|
|
74
|
+
expect(verifyFreezeIntegrity(artifact).ok).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
it('rejects an assembly whose split carries no frozenAt (the freeze mints the instant)', () => {
|
|
77
|
+
expect(() => assembleFrozenSplitArtifact(assembly({ split: split({ frozenAt: undefined }) }))).toThrow(/freeze mints the instant/);
|
|
78
|
+
});
|
|
79
|
+
it('schema rejects an unknown field (tamper/corruption, never silently carried)', () => {
|
|
80
|
+
const artifact = assembleFrozenSplitArtifact(assembly());
|
|
81
|
+
expect(FrozenSplitArtifactSchema.safeParse({ ...artifact, injected: true }).success).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
it('computeFrozenSplitRef is stable across assembly-object key order', () => {
|
|
84
|
+
const a = assembly();
|
|
85
|
+
const reordered = JSON.parse(JSON.stringify(a));
|
|
86
|
+
expect(computeFrozenSplitRef(reordered)).toBe(computeFrozenSplitRef(a));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=frozen-split.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frozen-split.test.js","sourceRoot":"","sources":["../../src/spine/frozen-split.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AuthoredWindtunnelVerdict } from './windtunnel-scorer-authored.js';
|
|
2
|
+
/** Per-survivor Gate-2 legibility — one row per surviving (non-culled) authored rule. */
|
|
3
|
+
export interface Gate2SurvivorRecord {
|
|
4
|
+
ruleId: string;
|
|
5
|
+
/** Held-out non-control activation count (the §5.3 O3 metric). Absent-from-map is reported as 0. */
|
|
6
|
+
heldOutActivations: number;
|
|
7
|
+
/** Eligible ⟺ NOT window-disqualified AND `heldOutActivations > 0` (§1(k)). */
|
|
8
|
+
gate2Eligible: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** The Gate-2 eligibility emission — a report field sibling to `heldOutActivationsByRule` (Q2). */
|
|
11
|
+
export interface Gate2Eligibility {
|
|
12
|
+
/** The eligible rule ids — survivors with > 0 held-out activations in a non-disqualified window. */
|
|
13
|
+
eligibleRuleIds: string[];
|
|
14
|
+
/** Every survivor considered (the audit trail; (k) exclusions are legible here, not silent). */
|
|
15
|
+
survivors: Gate2SurvivorRecord[];
|
|
16
|
+
/** True ⟺ `authoredControlGate.illegitimate > 0` — the whole window is FAIL-equivalent (Q4). */
|
|
17
|
+
windowDisqualified: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Derive the Gate-2-eligible set from a scored authored verdict + the run's minted
|
|
21
|
+
* rule ids. Pure + deterministic (Tenet-15): output preserves `mintedRuleIds` order;
|
|
22
|
+
* no Set/Map iteration leaks into the result.
|
|
23
|
+
*
|
|
24
|
+
* `mintedRuleIds` is NOT a verdict field (the verdict carries only `mintedRuleCount`)
|
|
25
|
+
* — it is the run's minted set, threaded from the engine result. Keys align: an
|
|
26
|
+
* authored rule's `mintedRuleId` IS its `heldOutActivationsByRule` key (both are the
|
|
27
|
+
* persisted `ruleId`; C2a `firingLabelId ← ruleId`).
|
|
28
|
+
*/
|
|
29
|
+
export declare function deriveGate2Eligibility(input: {
|
|
30
|
+
mintedRuleIds: readonly string[];
|
|
31
|
+
verdict: Pick<AuthoredWindtunnelVerdict, 'cullLedger' | 'heldOutActivationsByRule' | 'authoredControlGate'>;
|
|
32
|
+
}): Gate2Eligibility;
|
|
33
|
+
//# sourceMappingURL=gate2-eligibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate2-eligibility.d.ts","sourceRoot":"","sources":["../../src/spine/gate2-eligibility.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAEjF,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oGAAoG;IACpG,kBAAkB,EAAE,MAAM,CAAC;IAC3B,+EAA+E;IAC/E,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,mGAAmG;AACnG,MAAM,WAAW,gBAAgB;IAC/B,oGAAoG;IACpG,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gGAAgG;IAChG,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,gGAAgG;IAChG,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,OAAO,EAAE,IAAI,CACX,yBAAyB,EACzB,YAAY,GAAG,0BAA0B,GAAG,qBAAqB,CAClE,CAAC;CACH,GAAG,gBAAgB,CAwBnB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ─── ADR-112 §5.3 — the Gate-2-eligible set (slice D4) ───────────────────────
|
|
2
|
+
//
|
|
3
|
+
// A DOWNSTREAM, verdict-inert derivation over the authored window-tunnel verdict.
|
|
4
|
+
// It answers one question per surviving authored rule: is it eligible for Gate 2
|
|
5
|
+
// (hard-enforce)? Per strategy's D4 Q1/Q2 ruling (2026-07-01, couple-on-merge):
|
|
6
|
+
//
|
|
7
|
+
// • Q2 — this lives DOWNSTREAM of the scorer, NOT inside it (gemini/codex/agy +
|
|
8
|
+
// strategy unanimous, Tenet-20 join-back-once): `scoreAuthoredWindtunnel`
|
|
9
|
+
// emits the raw signal (`heldOutActivationsByRule` + `cullLedger`); THIS fn
|
|
10
|
+
// performs the intersection. It never re-scores and never touches the Gate-1
|
|
11
|
+
// verdict — the eligible set "affects only Gate-2 eligibility" (§5.3).
|
|
12
|
+
// • §1(k) is enforced HERE: a survivor with ZERO held-out activations is
|
|
13
|
+
// EXCLUDED — never admitted "as if held-out-certified". Absent-from-map ≡ an
|
|
14
|
+
// explicit `0` ≡ ineligible (codex: D3 records a non-culled rule with a valid
|
|
15
|
+
// positive control + zero held-out firings as an explicit `0`, not absent).
|
|
16
|
+
// • Q4 — a window with `authoredControlGate.illegitimate > 0` is FAIL-equivalent:
|
|
17
|
+
// NO survivor is eligible, EVEN WHEN `effect === 'none'` (a co-severe mined FP
|
|
18
|
+
// FAIL masked the illegitimate control — the equal-severity demote-only branch,
|
|
19
|
+
// `1a80655e`). We disqualify on the `illegitimate` COUNT, NEVER on `.effect`.
|
|
20
|
+
//
|
|
21
|
+
// Emitted, not hidden (§5.3, Tenet-4): a computed-but-hidden set is a silent
|
|
22
|
+
// artifact. The per-survivor `survivors[]` record shows every survivor
|
|
23
|
+
// considered + why excluded, so the (k) exclusion is auditable, not inferred.
|
|
24
|
+
/**
|
|
25
|
+
* Derive the Gate-2-eligible set from a scored authored verdict + the run's minted
|
|
26
|
+
* rule ids. Pure + deterministic (Tenet-15): output preserves `mintedRuleIds` order;
|
|
27
|
+
* no Set/Map iteration leaks into the result.
|
|
28
|
+
*
|
|
29
|
+
* `mintedRuleIds` is NOT a verdict field (the verdict carries only `mintedRuleCount`)
|
|
30
|
+
* — it is the run's minted set, threaded from the engine result. Keys align: an
|
|
31
|
+
* authored rule's `mintedRuleId` IS its `heldOutActivationsByRule` key (both are the
|
|
32
|
+
* persisted `ruleId`; C2a `firingLabelId ← ruleId`).
|
|
33
|
+
*/
|
|
34
|
+
export function deriveGate2Eligibility(input) {
|
|
35
|
+
const { mintedRuleIds, verdict } = input;
|
|
36
|
+
// Q4: window-level disqualifier — keyed on the COUNT, never `.effect` (a co-severe
|
|
37
|
+
// mined FP FAIL can mask an illegitimate control: `effect: 'none'` while `illegitimate > 0`).
|
|
38
|
+
const windowDisqualified = verdict.authoredControlGate.illegitimate > 0;
|
|
39
|
+
const culledRuleIds = new Set(verdict.cullLedger.map((entry) => entry.ruleId));
|
|
40
|
+
// Survivors = minted \ culled. A culled rule is never Gate-2-eligible, even if it
|
|
41
|
+
// had held-out activations (codex case (d)).
|
|
42
|
+
const survivors = mintedRuleIds
|
|
43
|
+
.filter((ruleId) => !culledRuleIds.has(ruleId))
|
|
44
|
+
.map((ruleId) => {
|
|
45
|
+
const heldOutActivations = verdict.heldOutActivationsByRule[ruleId] ?? 0;
|
|
46
|
+
const gate2Eligible = !windowDisqualified && heldOutActivations > 0;
|
|
47
|
+
return { ruleId, heldOutActivations, gate2Eligible };
|
|
48
|
+
});
|
|
49
|
+
const eligibleRuleIds = survivors
|
|
50
|
+
.filter((survivor) => survivor.gate2Eligible)
|
|
51
|
+
.map((survivor) => survivor.ruleId);
|
|
52
|
+
return { eligibleRuleIds, survivors, windowDisqualified };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=gate2-eligibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate2-eligibility.js","sourceRoot":"","sources":["../../src/spine/gate2-eligibility.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,EAAE;AACF,kFAAkF;AAClF,iFAAiF;AACjF,gFAAgF;AAChF,EAAE;AACF,kFAAkF;AAClF,8EAA8E;AAC9E,gFAAgF;AAChF,iFAAiF;AACjF,2EAA2E;AAC3E,2EAA2E;AAC3E,iFAAiF;AACjF,kFAAkF;AAClF,gFAAgF;AAChF,oFAAoF;AACpF,mFAAmF;AACnF,oFAAoF;AACpF,kFAAkF;AAClF,EAAE;AACF,6EAA6E;AAC7E,uEAAuE;AACvE,8EAA8E;AAuB9E;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAMtC;IACC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAEzC,mFAAmF;IACnF,8FAA8F;IAC9F,MAAM,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,YAAY,GAAG,CAAC,CAAC;IAExE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAE/E,kFAAkF;IAClF,6CAA6C;IAC7C,MAAM,SAAS,GAA0B,aAAa;SACnD,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;SAC9C,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QACd,MAAM,kBAAkB,GAAG,OAAO,CAAC,wBAAwB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzE,MAAM,aAAa,GAAG,CAAC,kBAAkB,IAAI,kBAAkB,GAAG,CAAC,CAAC;QACpE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;IAEL,MAAM,eAAe,GAAG,SAAS;SAC9B,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;SAC5C,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEtC,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate2-eligibility.test.d.ts","sourceRoot":"","sources":["../../src/spine/gate2-eligibility.test.ts"],"names":[],"mappings":""}
|