@junghanacs/entwurf 0.17.2 → 0.18.1
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/AGENTS.md +6 -5
- package/CHANGELOG.md +299 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +9 -5
- package/VERIFY.md +3 -3
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +46 -12
- package/docs/setup-clean-host.md +35 -3
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +76 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +9 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +11 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +66 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +10 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +8 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +25 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +6 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +38 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +64 -4
- package/mcp/entwurf-bridge/src/index.ts +16 -8
- package/package.json +10 -9
- package/pi-extensions/entwurf-control.ts +12 -2
- package/pi-extensions/lib/acp/acp-client.ts +3 -3
- package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
- package/pi-extensions/lib/acp/backend.ts +3 -3
- package/pi-extensions/lib/acp/event-mapper.ts +5 -5
- package/pi-extensions/lib/entwurf-deliverability.ts +104 -0
- package/pi-extensions/lib/entwurf-fact-provider.ts +20 -6
- package/pi-extensions/lib/entwurf-facts.ts +52 -3
- package/pi-extensions/lib/entwurf-peer-observe.ts +77 -0
- package/pi-extensions/lib/entwurf-peers-render.ts +12 -1
- package/pi-extensions/lib/entwurf-v2-decider.ts +26 -8
- package/pi-extensions/lib/entwurf-v2-production.ts +32 -12
- package/pi-extensions/lib/entwurf-v2-surface.ts +6 -0
- package/pi-extensions/lib/meta-session.ts +44 -0
- package/pi-extensions/meta-bridge-hook.ts +72 -3
- package/run.sh +146 -56
- package/scripts/check-acp-usage-accounting.ts +9 -9
- package/scripts/check-bridge-delivery.ts +42 -1
- package/scripts/check-entwurf-fact-provider.ts +22 -1
- package/scripts/check-entwurf-facts.ts +17 -2
- package/scripts/check-entwurf-peers-surface.ts +20 -2
- package/scripts/check-entwurf-self-address.ts +20 -2
- package/scripts/check-entwurf-v2-decider.ts +6 -3
- package/scripts/check-entwurf-v2-matrix.ts +10 -5
- package/scripts/check-entwurf-v2-production.ts +72 -1
- package/scripts/check-gate-qualification.ts +63 -12
- package/scripts/check-meta-hook-session-switch.ts +560 -0
- package/scripts/check-release-gate-outcomes.ts +160 -3
- package/scripts/meta-bridge-doctor.sh +29 -4
- package/scripts/mutants/bridge-command-boot.json +3 -3
- package/scripts/mutants/gate-qualification.json +28 -0
- package/scripts/mutants/meta-hook-session-switch.json +223 -0
- package/scripts/mutants/pack-install.json +14 -3
- package/scripts/mutants/release-gate.json +22 -0
- package/scripts/raw-acp-compaction-measure/README.md +77 -0
- package/scripts/raw-acp-compaction-measure/probe.ts +196 -0
- package/scripts/raw-claude-session-switch/README.md +170 -0
- package/scripts/smoke-acp-cortex-live.ts +22 -2
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-v2-send-live.ts +23 -2
- package/scripts/smoke-entwurf-chain-live.ts +85 -13
- package/scripts/smoke-entwurf-v2-matrix-live.ts +23 -1
- package/scripts/smoke-mux-fresh-call-live.ts +13 -3
|
@@ -469,8 +469,26 @@ const metaBody = functionBody("buildTrustedMetaSenderEnvelope");
|
|
|
469
469
|
ok("buildTrustedMetaSenderEnvelope calls computeSelfAddressability", /computeSelfAddressability\s*\(/.test(metaBody));
|
|
470
470
|
ok("buildTrustedMetaSenderEnvelope no longer hardcodes `replyable: true`", !/replyable:\s*true/.test(metaBody));
|
|
471
471
|
ok(
|
|
472
|
-
|
|
473
|
-
|
|
472
|
+
// #101: the identity match alone is no longer the whole answer. `entwurf_self` composes
|
|
473
|
+
// the SAME `resolveMailboxReceiverFacts` the v2 dispatch seam uses — reading the receiver
|
|
474
|
+
// marker AND, where the watch owner is the sender-marker process, the join that says that
|
|
475
|
+
// owner is still serving this garden. A citizen's self-reported replyability and what
|
|
476
|
+
// dispatch decides about it come from one measurement, so they cannot disagree.
|
|
477
|
+
"buildTrustedMetaSenderEnvelope derives active-receiver from the SHARED receiver composition (both markers)",
|
|
478
|
+
/resolveMailboxReceiverFacts\s*\(/.test(metaBody) &&
|
|
479
|
+
/readMetaReceiverMarker\s*\(/.test(metaBody) &&
|
|
480
|
+
/readMetaSenderMarker\s*\(/.test(metaBody),
|
|
481
|
+
);
|
|
482
|
+
ok(
|
|
483
|
+
"buildTrustedMetaSenderEnvelope no longer copies one match into both receiver facts",
|
|
484
|
+
!/ownerAlive:\s*active/.test(metaBody) && !/watchArmed:\s*active/.test(metaBody),
|
|
485
|
+
);
|
|
486
|
+
ok(
|
|
487
|
+
// #101 (cross-review): both markers are read with their liveness guards ON. A reader that
|
|
488
|
+
// opted out would accept a dead session's leftover file as "which garden this pid serves
|
|
489
|
+
// now", and every fixture pid in a gate is live, so nothing dynamic here could tell.
|
|
490
|
+
"buildTrustedMetaSenderEnvelope reads neither marker with the owner guard disabled",
|
|
491
|
+
!/verifyOwner:\s*false/.test(metaBody),
|
|
474
492
|
);
|
|
475
493
|
ok(
|
|
476
494
|
"buildTrustedMetaSenderEnvelope keeps meta identity + derived replyable (inactive → not null)",
|
|
@@ -207,11 +207,13 @@ async function main(): Promise<void> {
|
|
|
207
207
|
ok("target-locked: diagnostic carried", d.kind === "reject" && d.diagnostic?.kind === "target-locked");
|
|
208
208
|
ok(
|
|
209
209
|
"target-locked: diagnostic holder pid preserved",
|
|
210
|
-
d.kind === "reject" && d.diagnostic?.conflict.holder?.pid === 4242,
|
|
210
|
+
d.kind === "reject" && d.diagnostic?.kind === "target-locked" && d.diagnostic.conflict.holder?.pid === 4242,
|
|
211
211
|
);
|
|
212
212
|
ok(
|
|
213
213
|
"target-locked: diagnostic lockPath preserved",
|
|
214
|
-
d.kind === "reject" &&
|
|
214
|
+
d.kind === "reject" &&
|
|
215
|
+
d.diagnostic?.kind === "target-locked" &&
|
|
216
|
+
d.diagnostic.conflict.lockPath === `/fake/locks/${GID}.lock`,
|
|
215
217
|
);
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -253,7 +255,8 @@ async function main(): Promise<void> {
|
|
|
253
255
|
ok(
|
|
254
256
|
"target-locked(corrupt): diagnostic detail preserved",
|
|
255
257
|
d.kind === "reject" &&
|
|
256
|
-
d.diagnostic?.
|
|
258
|
+
d.diagnostic?.kind === "target-locked" &&
|
|
259
|
+
d.diagnostic.conflict.detail === "lockfile is empty, corrupt, or holds a different garden id",
|
|
257
260
|
);
|
|
258
261
|
}
|
|
259
262
|
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type DispatchDecision,
|
|
43
43
|
decideDispatch,
|
|
44
44
|
type ExecutionPlan,
|
|
45
|
+
type RejectDiagnostic,
|
|
45
46
|
type TargetResolution,
|
|
46
47
|
} from "../pi-extensions/lib/entwurf-v2-decider.ts";
|
|
47
48
|
import type { AcquireLockResult, LockClaim } from "../pi-extensions/lib/entwurf-v2-lock.ts";
|
|
@@ -156,7 +157,10 @@ type LockClass = "none" | "held" | "mailbox-null" | "released" | "acquire-fail";
|
|
|
156
157
|
|
|
157
158
|
type Expect =
|
|
158
159
|
| { decision: "execute"; transport: EntwurfV2Transport; lock: LockClass }
|
|
159
|
-
|
|
160
|
+
// `diagnostic` names the KIND a reject must carry, not a boolean: two reject cells now
|
|
161
|
+
// carry machine-readable evidence, and the axis has to say which — a reject that carried
|
|
162
|
+
// the wrong diagnostic would pass a yes/no cell (#101 갭 C).
|
|
163
|
+
| { decision: "reject"; reason: string; lock: LockClass; diagnostic?: RejectDiagnostic["kind"] };
|
|
160
164
|
|
|
161
165
|
/**
|
|
162
166
|
* Which IO seams the decider is ALLOWED to touch — the "어느 축을 만지면 안 되는지"
|
|
@@ -211,7 +215,7 @@ const ROWS: Row[] = [
|
|
|
211
215
|
targetKind: "pi, lock held by another",
|
|
212
216
|
intent: "fire-and-forget",
|
|
213
217
|
scenario: { resolution: { identity: identity("pi"), preProbeAddressConflict: false }, lock: "conflict" },
|
|
214
|
-
expect: { decision: "reject", reason: "target-locked", lock: "acquire-fail", diagnostic:
|
|
218
|
+
expect: { decision: "reject", reason: "target-locked", lock: "acquire-fail", diagnostic: "target-locked" },
|
|
215
219
|
},
|
|
216
220
|
{
|
|
217
221
|
name: "unsupported self-fetch active → meta-mailbox",
|
|
@@ -231,7 +235,8 @@ const ROWS: Row[] = [
|
|
|
231
235
|
resolution: { identity: identity("claude-code"), preProbeAddressConflict: false },
|
|
232
236
|
mailboxDeliverable: false,
|
|
233
237
|
},
|
|
234
|
-
|
|
238
|
+
// #101 갭 C: an undeliverable mailbox reject carries the receiver axis that failed.
|
|
239
|
+
expect: { decision: "reject", reason: "mailbox-undeliverable", lock: "none", diagnostic: "mailbox-undeliverable" },
|
|
235
240
|
},
|
|
236
241
|
{
|
|
237
242
|
name: "in-domain live ff → control-socket",
|
|
@@ -338,8 +343,8 @@ async function runRow(row: Row): Promise<void> {
|
|
|
338
343
|
}
|
|
339
344
|
if (row.expect.diagnostic) {
|
|
340
345
|
ok(
|
|
341
|
-
`${row.name}: carries
|
|
342
|
-
d.kind === "reject" && d.diagnostic?.kind ===
|
|
346
|
+
`${row.name}: carries the ${row.expect.diagnostic} diagnostic`,
|
|
347
|
+
d.kind === "reject" && d.diagnostic?.kind === row.expect.diagnostic,
|
|
343
348
|
);
|
|
344
349
|
} else {
|
|
345
350
|
ok(`${row.name}: no diagnostic`, d.kind === "reject" && d.diagnostic === undefined);
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* E. Q3 + Q5 — a dead control send re-resolves (claude-code citizen) to the mailbox and
|
|
14
14
|
* enqueues through the SAME sendViaMailbox instance (same enqueue spy) on the SAME dirs
|
|
15
15
|
* the direct hand used — direct send and fallback send never drift.
|
|
16
|
+
* E4. #101 — the deliverability seam reads BOTH markers: a live receiver marker whose
|
|
17
|
+
* owner's sender marker names ANOTHER garden is undeliverable. The SAME closure serves
|
|
18
|
+
* the direct send and the dead-control fallback, so neither can be fooled alone.
|
|
16
19
|
*
|
|
17
20
|
* No real IO — every seam is a spy; the factory's COMPOSITION is what is under test.
|
|
18
21
|
*/
|
|
@@ -26,7 +29,7 @@ import {
|
|
|
26
29
|
} from "../pi-extensions/lib/entwurf-v2-production.ts";
|
|
27
30
|
import { runEntwurfV2 } from "../pi-extensions/lib/entwurf-v2-runner.ts";
|
|
28
31
|
import type { ControlSocketPlan, MetaMailboxPlan } from "../pi-extensions/lib/entwurf-v2-send.ts";
|
|
29
|
-
import type { MetaIdentity, MetaReceiverMarker } from "../pi-extensions/lib/meta-session.ts";
|
|
32
|
+
import type { MetaIdentity, MetaReceiverMarker, MetaSenderMarker } from "../pi-extensions/lib/meta-session.ts";
|
|
30
33
|
import type { NativePushAdapter, NativePushProbeResult } from "../pi-extensions/lib/native-push/adapter.ts";
|
|
31
34
|
import type { TargetSocketInspection } from "../pi-extensions/lib/socket-discovery.ts";
|
|
32
35
|
|
|
@@ -38,6 +41,8 @@ function ok(label: string, cond: boolean): void {
|
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
const GID = "20260613T100000-aaaaaa";
|
|
44
|
+
/** The garden a switched-away owner serves instead (#101). */
|
|
45
|
+
const OTHER_GID = "20260613T100000-bbbbbb";
|
|
41
46
|
const LOCK_DIR = "/fake/locks";
|
|
42
47
|
const SESSIONS_DIR = "/fake/sessions";
|
|
43
48
|
const MAILBOX_DIR = "/fake/mailbox";
|
|
@@ -74,6 +79,19 @@ function receiverMarker(gid: string, backend: string, nativeSessionId = "n"): Me
|
|
|
74
79
|
};
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
/** The receiver owner's sender marker — "which garden does this pid serve NOW?" (#101). */
|
|
83
|
+
function senderMarker(gardenId: string, backend: string, ownerPid: number): MetaSenderMarker {
|
|
84
|
+
return {
|
|
85
|
+
gardenId,
|
|
86
|
+
backend: backend as MetaSenderMarker["backend"],
|
|
87
|
+
nativeSessionId: "n",
|
|
88
|
+
cwd: "/cwd",
|
|
89
|
+
ownerPid,
|
|
90
|
+
ownerStartKey: "x",
|
|
91
|
+
updatedAt: "t",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
77
95
|
function lockClaim(gardenId = GID): LockClaim {
|
|
78
96
|
return {
|
|
79
97
|
gardenId,
|
|
@@ -133,6 +151,11 @@ function makeSpiedFactory(over: {
|
|
|
133
151
|
/** SE-2 2d-3 — the target's receiver presence marker: "active" (matches identity,
|
|
134
152
|
* default), "absent" (terminated/never-armed), or "mismatch" (drifted native id). */
|
|
135
153
|
receiverMarker?: "active" | "absent" | "mismatch";
|
|
154
|
+
/** #101 — the receiver owner's SENDER marker, i.e. which garden that pid serves NOW:
|
|
155
|
+
* "same" (still this one, default), "other-garden" (the owner switched sessions in
|
|
156
|
+
* place — a live marker whose watch is retired), or "absent" (no sender marker at all).
|
|
157
|
+
* Injected rather than defaulted so this gate never stats the operator's real roots. */
|
|
158
|
+
senderMarker?: "same" | "other-garden" | "absent";
|
|
136
159
|
}) {
|
|
137
160
|
const spies: Spies = {
|
|
138
161
|
acquire: [],
|
|
@@ -161,6 +184,10 @@ function makeSpiedFactory(over: {
|
|
|
161
184
|
const nsid = over.receiverMarker === "mismatch" ? "DRIFT" : "n";
|
|
162
185
|
return receiverMarker(gid, over.backend ?? "pi", nsid);
|
|
163
186
|
},
|
|
187
|
+
readSenderMarker: (backend, ownerPid) => {
|
|
188
|
+
if (over.senderMarker === "absent") return null;
|
|
189
|
+
return senderMarker(over.senderMarker === "other-garden" ? OTHER_GID : GID, backend, ownerPid);
|
|
190
|
+
},
|
|
164
191
|
inspectPath: async (socketPath) => {
|
|
165
192
|
spies.inspectPath.push({ socketPath });
|
|
166
193
|
if (over.inspectKind === "indeterminate") {
|
|
@@ -388,6 +415,50 @@ async function main(): Promise<void> {
|
|
|
388
415
|
ok("E3: drifted marker → enqueue NEVER called (presence ≠ identity match)", spies.enqueue.length === 0);
|
|
389
416
|
}
|
|
390
417
|
|
|
418
|
+
// ── E4: #101 — a LIVE receiver marker whose owner switched gardens. Every axis the
|
|
419
|
+
// pre-#101 seam looked at says "active": the marker exists, its owner is live, and it
|
|
420
|
+
// matches this identity exactly. Only the owner's sender marker — which garden that pid
|
|
421
|
+
// serves NOW — says the watch is retired. This is the cell where a real message was
|
|
422
|
+
// enqueued into a mailbox nobody was draining (oracle, 2026-09-04). ─────────────────
|
|
423
|
+
{
|
|
424
|
+
const { deps, spies } = makeSpiedFactory({
|
|
425
|
+
backend: "claude-code",
|
|
426
|
+
rpc: "dead-throw",
|
|
427
|
+
classifyDead: true,
|
|
428
|
+
receiverMarker: "active",
|
|
429
|
+
senderMarker: "other-garden",
|
|
430
|
+
});
|
|
431
|
+
const res = await deps.executor.sendControl(CONTROL_PLAN, lockClaim());
|
|
432
|
+
ok(
|
|
433
|
+
"E4: owner switched gardens → rejected (mailbox-undeliverable), never fallback-sent",
|
|
434
|
+
res.outcome === "rejected" && res.rejectReason === "mailbox-undeliverable",
|
|
435
|
+
);
|
|
436
|
+
ok("E4: nothing enqueued into the retired garden's mailbox", spies.enqueue.length === 0);
|
|
437
|
+
// …and the same wiring still DELIVERS when that owner is serving this garden, so the
|
|
438
|
+
// join is a measurement and not a blanket refusal.
|
|
439
|
+
const served = makeSpiedFactory({
|
|
440
|
+
backend: "claude-code",
|
|
441
|
+
rpc: "dead-throw",
|
|
442
|
+
classifyDead: true,
|
|
443
|
+
receiverMarker: "active",
|
|
444
|
+
senderMarker: "same",
|
|
445
|
+
});
|
|
446
|
+
const ok2 = await served.deps.executor.sendControl(CONTROL_PLAN, lockClaim());
|
|
447
|
+
ok("E4: the served garden still falls back to a real mailbox enqueue", ok2.outcome === "fallback-sent");
|
|
448
|
+
ok(
|
|
449
|
+
"E4: a MISSING sender marker is fail-closed, not optimistic",
|
|
450
|
+
(
|
|
451
|
+
await makeSpiedFactory({
|
|
452
|
+
backend: "claude-code",
|
|
453
|
+
rpc: "dead-throw",
|
|
454
|
+
classifyDead: true,
|
|
455
|
+
receiverMarker: "active",
|
|
456
|
+
senderMarker: "absent",
|
|
457
|
+
}).deps.executor.sendControl(CONTROL_PLAN, lockClaim())
|
|
458
|
+
).outcome === "rejected",
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
391
462
|
// ── F: #50 C4 — record-LESS control socket → pre-probe record-less-socket reject ──
|
|
392
463
|
// resolveTarget finds no meta-record, does ONE record-side lstat (inspectPath), sees a
|
|
393
464
|
// non-symlink socket → recordLessSocket. EVERY intent then rejects pre-probe as
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* check-gate-qualification — kill-proof qualification of the shipped gates.
|
|
3
3
|
*
|
|
4
|
+
* TWO ENTRYPOINTS, ONE FILE. `--manifests-only` (shipped as `run.sh
|
|
5
|
+
* check-gate-manifests`) stops after the HEAD — the runner self-test plus the real
|
|
6
|
+
* manifests' validation and lane inventory — having executed ZERO mutants from
|
|
7
|
+
* `scripts/mutants/` and having made NO snapshot of this repo. The default
|
|
8
|
+
* entrypoint is unchanged: it runs the same head first and then the body. The head
|
|
9
|
+
* is separable because of what it has actually caught: across 549 CI runs the
|
|
10
|
+
* qualification step went red five times, and three of those died in the head in
|
|
11
|
+
* 4-5 seconds, before a single mutant gate was invoked (#99 B-3). The head costs
|
|
12
|
+
* ~8s, so it belongs in the deterministic floor every push already pays for; the
|
|
13
|
+
* body's contract — which mutants run, how they are classified, what it prints —
|
|
14
|
+
* is untouched by the split.
|
|
15
|
+
*
|
|
4
16
|
* Two phases, in a fixed order:
|
|
5
17
|
*
|
|
6
18
|
* 1. RUNNER SELF-TEST. The runner is itself a SUT: a qualification harness that
|
|
@@ -58,6 +70,10 @@ import {
|
|
|
58
70
|
const REPO_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
59
71
|
const MUTANTS_DIR = path.join(REPO_DIR, "scripts", "mutants");
|
|
60
72
|
|
|
73
|
+
/** Head-only mode: validate, count, and stop — no mutant is executed, no repo snapshot is made. */
|
|
74
|
+
const MANIFESTS_ONLY = process.argv.includes("--manifests-only");
|
|
75
|
+
const SURFACE = MANIFESTS_ONLY ? "check-gate-manifests" : "gate-qualification";
|
|
76
|
+
|
|
61
77
|
let passed = 0;
|
|
62
78
|
function ok(label: string, cond: boolean): void {
|
|
63
79
|
assert.ok(cond, label);
|
|
@@ -775,10 +791,16 @@ const quiet = (): void => {};
|
|
|
775
791
|
}
|
|
776
792
|
}
|
|
777
793
|
|
|
778
|
-
console.log(`\n[
|
|
794
|
+
console.log(`\n[${SURFACE}] self-test: ${passed} checks passed`);
|
|
779
795
|
|
|
780
|
-
// ═══ Phase
|
|
796
|
+
// ═══ Phase 1d — the real manifests, VALIDATED but not yet run ═══════════════
|
|
797
|
+
//
|
|
798
|
+
// The HEAD ends here. Everything above and in this block is pure reading and
|
|
799
|
+
// checking: no mutant from `scripts/mutants/` is executed and no snapshot of THIS
|
|
800
|
+
// repo exists yet. `--manifests-only` returns from exactly this point.
|
|
781
801
|
|
|
802
|
+
let selected: MutantSpec[];
|
|
803
|
+
let manifestCount: number;
|
|
782
804
|
{
|
|
783
805
|
const manifests: MutantManifest[] = fs
|
|
784
806
|
.readdirSync(MUTANTS_DIR)
|
|
@@ -813,21 +835,23 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
|
|
|
813
835
|
"copilot-launch": 14,
|
|
814
836
|
"copilot-receive": 18,
|
|
815
837
|
"fresh-cut": 1,
|
|
838
|
+
"gate-qualification": 2,
|
|
816
839
|
"meta-facts": 4,
|
|
840
|
+
"meta-hook-session-switch": 17,
|
|
817
841
|
"meta-identity": 4,
|
|
818
842
|
"meta-retire": 3,
|
|
819
843
|
"mux-boundary": 14,
|
|
820
844
|
"mux-fresh-call": 37,
|
|
821
845
|
"mux-launcher-fence": 7,
|
|
822
846
|
"mux-parent-artifact": 3,
|
|
823
|
-
"pack-install":
|
|
847
|
+
"pack-install": 2,
|
|
824
848
|
"pi-package-ownership": 6,
|
|
825
849
|
"mux-resume-call": 12,
|
|
826
850
|
"omp-birth": 11,
|
|
827
851
|
"omp-fresh": 24,
|
|
828
852
|
"omp-receive": 11,
|
|
829
853
|
"probe-ordering": 1,
|
|
830
|
-
"release-gate":
|
|
854
|
+
"release-gate": 14,
|
|
831
855
|
"resume-args": 6,
|
|
832
856
|
"resume-launch-identity": 6,
|
|
833
857
|
"self-address": 5,
|
|
@@ -838,17 +862,44 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
|
|
|
838
862
|
};
|
|
839
863
|
const laneTally: Record<string, number> = {};
|
|
840
864
|
for (const man of manifests) laneTally[man.lane] = (laneTally[man.lane] ?? 0) + man.mutants.length;
|
|
841
|
-
|
|
842
|
-
laneTally,
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
865
|
+
try {
|
|
866
|
+
assert.deepEqual(laneTally, EXPECTED_LANE_MUTANTS);
|
|
867
|
+
} catch {
|
|
868
|
+
assert.fail(
|
|
869
|
+
"[QK:LANE-INVENTORY-DECLARED] the mutant lane inventory drifted from the declared contract — extend " +
|
|
870
|
+
"EXPECTED_LANE_MUTANTS and the manifests together, never silently. A lane that lost its manifest in a " +
|
|
871
|
+
"merge, or gained mutants nobody declared, would otherwise pass as `n/n killed` over whatever set " +
|
|
872
|
+
`happened to be on disk. declared=${JSON.stringify(EXPECTED_LANE_MUTANTS)} onDisk=${JSON.stringify(laneTally)}`,
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
try {
|
|
876
|
+
selected = validateManifestSet(manifests, makeOriginChecks(REPO_DIR));
|
|
877
|
+
} catch (err) {
|
|
878
|
+
assert.fail(
|
|
879
|
+
"[QK:MANIFEST-SET-INTEGRITY-REFUSED] the committed manifest set broke its cross-manifest contract (global " +
|
|
880
|
+
"claim uniqueness, subject tracked + lstat-regular in the ORIGIN index, signatureSource on the origin work " +
|
|
881
|
+
"surface, claim token present exactly once in its gate source). Every one of those is what makes a KILLED " +
|
|
882
|
+
"verdict mean something, so the set is refused rather than run. Refusal: " +
|
|
883
|
+
(err instanceof Error ? err.message : String(err)),
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
manifestCount = manifests.length;
|
|
887
|
+
console.log(`[${SURFACE}] ${selected.length} mutants across ${manifestCount} lanes (no tiers — full set every run)`);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (MANIFESTS_ONLY) {
|
|
848
891
|
console.log(
|
|
849
|
-
`[gate-
|
|
892
|
+
`[check-gate-manifests] ok — runner self-test green, ${selected.length} committed mutants across ` +
|
|
893
|
+
`${manifestCount} lanes validated against the origin index, and the lane inventory matches its declared ` +
|
|
894
|
+
"contract. ZERO mutants were executed and this repo was never snapshotted: the body " +
|
|
895
|
+
"(check-gate-qualification) owns that, unchanged.",
|
|
850
896
|
);
|
|
897
|
+
process.exit(0);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// ═══ Phase 2 — the real manifests against a snapshot of THIS repo ═══════════
|
|
851
901
|
|
|
902
|
+
{
|
|
852
903
|
const headBefore = originHead(REPO_DIR);
|
|
853
904
|
const workSurfaceBefore = originWorkSurfaceSha(REPO_DIR);
|
|
854
905
|
|