@junghanacs/entwurf 0.12.8 → 0.12.10
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 +134 -248
- package/BASELINE.md +1 -1
- package/CHANGELOG.md +39 -1
- package/DELIVERY.md +5 -4
- package/README.md +148 -23
- package/VERIFY.md +3 -2
- package/demo/README.md +2 -2
- package/demo/demo.sh +7 -1
- package/docs/setup-clean-host.md +24 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +168 -81
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +12 -12
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +5 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +26 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +10 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +16 -18
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +13 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +31 -41
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +8 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +32 -25
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +4 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +5 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +14 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +11 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +15 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +500 -54
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +8 -3
- package/mcp/entwurf-bridge/dist/scripts/agy-imprint.js +14 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +155 -29
- package/mcp/entwurf-bridge/src/index.ts +200 -94
- package/package.json +11 -9
- package/pi-extensions/entwurf-control.ts +81 -39
- package/pi-extensions/lib/acp/models.ts +12 -12
- package/pi-extensions/lib/acp/overlay.ts +5 -3
- package/pi-extensions/lib/entwurf-core.ts +26 -9
- package/pi-extensions/lib/entwurf-deliverability.ts +10 -6
- package/pi-extensions/lib/entwurf-fact-provider.ts +24 -19
- package/pi-extensions/lib/entwurf-facts.ts +13 -13
- package/pi-extensions/lib/entwurf-resume-args.ts +40 -46
- package/pi-extensions/lib/entwurf-self-address.ts +8 -9
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +7 -2
- package/pi-extensions/lib/entwurf-v2-contract.ts +32 -25
- package/pi-extensions/lib/entwurf-v2-decider.ts +8 -8
- package/pi-extensions/lib/entwurf-v2-lock.ts +5 -4
- package/pi-extensions/lib/entwurf-v2-production.ts +22 -13
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +11 -3
- package/pi-extensions/lib/meta-sender-identity.ts +15 -5
- package/pi-extensions/lib/meta-session.ts +543 -58
- package/pi-extensions/meta-bridge-hook.ts +8 -2
- package/run.sh +78 -67
- package/scripts/agy-bridge-config.py +249 -49
- package/scripts/agy-bridge.sh +59 -14
- package/scripts/agy-imprint.ts +15 -1
- package/scripts/check-acp-carrier-augment.ts +34 -2
- package/scripts/check-acp-sdk-surface.ts +22 -11
- package/scripts/check-acp-session-reuse.ts +1 -1
- package/scripts/check-acp-session-store.ts +3 -3
- package/scripts/check-agy-permission-matrix.py +655 -0
- package/scripts/check-agy-sender-identity.ts +83 -0
- package/scripts/check-entwurf-control-rpc.ts +2 -2
- package/scripts/check-entwurf-deliverability.ts +9 -6
- package/scripts/check-entwurf-fact-provider.ts +16 -8
- package/scripts/check-entwurf-facts.ts +13 -13
- package/scripts/check-entwurf-resume-args.ts +25 -63
- package/scripts/check-entwurf-self-address.ts +187 -4
- package/scripts/check-entwurf-session-identity.ts +7 -6
- package/scripts/check-entwurf-v2-contract.ts +2 -2
- package/scripts/check-entwurf-v2-production.ts +9 -7
- package/scripts/check-entwurf-v2-spawn-production.ts +8 -4
- package/scripts/check-entwurf-v2-surface.ts +327 -14
- package/scripts/check-fresh-cut-gate.sh +305 -4
- package/scripts/check-gate-qualification.ts +785 -0
- package/scripts/check-meta-identity-consumers.ts +501 -1
- package/scripts/check-meta-listing.ts +91 -9
- package/scripts/check-meta-receiver-marker.ts +54 -0
- package/scripts/check-model-lock.ts +1 -1
- package/scripts/check-shell-quote.ts +2 -1
- package/scripts/lib/mutation-qualify.ts +794 -0
- package/scripts/meta-bridge-fresh-cut.ts +164 -28
- package/scripts/mutants/acp-augment.json +30 -0
- package/scripts/mutants/agy-permission.json +144 -0
- package/scripts/mutants/meta-identity.json +17 -0
- package/scripts/mutants/self-address.json +59 -0
- package/scripts/mutants/v2-surface.json +87 -0
- package/scripts/pi_settings_io.py +65 -0
- package/scripts/register-pi-package.py +183 -37
- package/scripts/register-pi-provider.py +68 -10
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-acp-v2-send-live.ts +33 -18
- package/scripts/smoke-agy-install-state.sh +205 -20
- package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -2
- package/scripts/smoke-meta-install-state.sh +1 -1
- package/scripts/smoke-pi-attach.ts +7 -2
- package/scripts/smoke-user-scope-citizen.sh +177 -0
- package/pi-extensions/lib/entwurf-mailbox-guard.ts +0 -100
- package/scripts/check-entwurf-mailbox-guard.ts +0 -262
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* - meta-sessions/ (identity records) → ARCHIVED (`<dir>.archive-<ts>`)
|
|
25
25
|
* - meta-mailbox/ (messages+receipts) → ARCHIVED (`<dir>.archive-<ts>`)
|
|
26
26
|
* - meta-senders/, meta-receivers/ markers → CLEARED (disposable process
|
|
27
|
-
* state; the quiesce gate has already proven every owner pid is gone
|
|
27
|
+
* state; the quiesce gate has already proven every owner pid is gone — or,
|
|
28
|
+
* for a refuted marker, that no such owner could ever have existed)
|
|
28
29
|
* - control sockets (*.sock) → CLEARED if probed dead (the GC
|
|
29
30
|
* rule: only a demonstrably dead socket may be unlinked)
|
|
30
31
|
* - native transcripts, andenken/embedding axes, install ownership state
|
|
@@ -50,6 +51,28 @@
|
|
|
50
51
|
* Only a marker whose named owner demonstrably no longer holds that pid is dead,
|
|
51
52
|
* and only a dead marker/socket is cleared.
|
|
52
53
|
*
|
|
54
|
+
* ONE class is neither live, uncertain nor dead: a marker whose `ownerPid` cannot
|
|
55
|
+
* own anything (`<= 1` — init, or a non-pid number). It is REFUTED BY CONSTRUCTION
|
|
56
|
+
* rather than proven dead: after #53 A no writer in this tree can mint one
|
|
57
|
+
* (`isPlausibleOwnerPid` at both writers and at the write boundary), so on a current
|
|
58
|
+
* install it is LEGACY or CORRUPT residue — a pre-fix writer whose parent had been
|
|
59
|
+
* reparented to init (the retired shell-form Claude hook; the agy imprint, which
|
|
60
|
+
* asked only `> 0` until this cut), or a foreign/damaged marker, which is the only
|
|
61
|
+
* way a non-integer pid can appear at all. The one file actually observed was a
|
|
62
|
+
* shell-form Claude hook reparented to init.
|
|
63
|
+
*
|
|
64
|
+
* Reading it as an owner is not merely wrong, it is a trap: init runs for the whole
|
|
65
|
+
* boot and its start-key does not change while it does, so `classifyMarkerOwner`
|
|
66
|
+
* answers `live` and THE ACTION THIS REFUSAL PRESCRIBES CANNOT CHANGE THAT — the
|
|
67
|
+
* operator quiesces every session, exactly as told, and the cut refuses again.
|
|
68
|
+
* (Deleting the marker removes the claim rather than refuting the verdict, and a
|
|
69
|
+
* reboot recomputes the key with no contract either way — neither is a remedy this
|
|
70
|
+
* cut may lean on.) Meanwhile 0.12.8 names this very cut as the one repair for a
|
|
71
|
+
* pre-v3 store, so the host was stuck until the file was deleted by hand (#53 A,
|
|
72
|
+
* measured 2026-07-25). Such a marker is therefore clearable residue, swept with the
|
|
73
|
+
* dead ones but COUNTED AND REPORTED APART from them, because invalidity and death
|
|
74
|
+
* are different findings.
|
|
75
|
+
*
|
|
53
76
|
* THE MARKER WALK IS NOT THE WHOLE WORLD. A native-push (agy) citizen is registered
|
|
54
77
|
* with NO marker of any kind and is dispatched straight off its record, so marker
|
|
55
78
|
* absence is its NORMAL deliverable state — a socket+marker scan would call such a host
|
|
@@ -69,6 +92,15 @@
|
|
|
69
92
|
* renames reports exactly what already moved (`archived so far:`) and a re-run
|
|
70
93
|
* finishes the cut with its own stamp.
|
|
71
94
|
* Running on a clean/empty host just opens a fresh generation and says so.
|
|
95
|
+
*
|
|
96
|
+
* EXIT CONTRACT (#54) — `FRESH_CUT_EXIT`, defined beside the verb's own name in
|
|
97
|
+
* meta-session.ts because a runbook and a gate must read the same numbers:
|
|
98
|
+
* `0` complete · `1` nothing moved · `2` usage · `3` cut transition incomplete ·
|
|
99
|
+
* `4` cut complete but marker/socket cleanup incomplete. The three failure states
|
|
100
|
+
* were all hidden behind one `1` before, which is why the documented `fresh-cut && setup` chain stopped
|
|
101
|
+
* identically on a refusal that changed nothing and on a cut that had already
|
|
102
|
+
* unblocked the install. Only `0` is success — #54 asked for the states to become
|
|
103
|
+
* DISTINGUISHABLE, not for a failed sweep to become a pass.
|
|
72
104
|
*/
|
|
73
105
|
|
|
74
106
|
import * as fs from "node:fs";
|
|
@@ -82,7 +114,10 @@ import {
|
|
|
82
114
|
defaultMetaReceiversDir,
|
|
83
115
|
defaultMetaSendersDir,
|
|
84
116
|
defaultMetaSessionsDir,
|
|
117
|
+
FRESH_CUT_EXIT,
|
|
118
|
+
isPlausibleOwnerPid,
|
|
85
119
|
type MetaIdentity,
|
|
120
|
+
midCutExit,
|
|
86
121
|
parseMetaIdentity,
|
|
87
122
|
probePidExistence,
|
|
88
123
|
processStartKey,
|
|
@@ -104,6 +139,20 @@ function usage(code: number): never {
|
|
|
104
139
|
"store = ENTWURF_META_SESSIONS_DIR || <PI_CODING_AGENT_DIR|~/.pi/agent>/meta-sessions",
|
|
105
140
|
"mailbox = ENTWURF_META_MAILBOX_DIR || <PI_CODING_AGENT_DIR|~/.pi/agent>/meta-mailbox",
|
|
106
141
|
"sockets = ENTWURF_DIR || ~/.pi/entwurf-control",
|
|
142
|
+
"",
|
|
143
|
+
"EXIT CONTRACT (#54) — what already moved, not how bad it was:",
|
|
144
|
+
` ${FRESH_CUT_EXIT.COMPLETE} cut complete: generation archived (or none existed), fresh v3 store open,`,
|
|
145
|
+
" residue cleared. `&& setup` is correct here.",
|
|
146
|
+
` ${FRESH_CUT_EXIT.NO_MOVE} NOTHING MOVED: a live/unprovable surface, an occupied archive destination,`,
|
|
147
|
+
" an unreadable surface, or a first rename that failed. The host is unchanged —",
|
|
148
|
+
" fix the named cause and re-run. Do NOT run setup: the store it refused is still there.",
|
|
149
|
+
` ${FRESH_CUT_EXIT.USAGE} usage.`,
|
|
150
|
+
` ${FRESH_CUT_EXIT.HALF_CUT} CUT TRANSITION INCOMPLETE: at least one archive move happened,`,
|
|
151
|
+
" but the fresh generation is not confirmed open. Inspect, or re-run to finish under a new stamp.",
|
|
152
|
+
` ${FRESH_CUT_EXIT.CLEANUP_INCOMPLETE} CUT COMPLETE, CLEANUP INCOMPLETE: the generation IS archived and the fresh`,
|
|
153
|
+
" generation IS open — install and citizen birth are unblocked, so setup may run —",
|
|
154
|
+
" but marker/socket residue survived. Prefer fixing it and re-running BEFORE setup; after new citizen birth,",
|
|
155
|
+
" remove the named residue manually instead of archiving the new generation with another cut.",
|
|
107
156
|
].join("\n"),
|
|
108
157
|
);
|
|
109
158
|
process.exit(code);
|
|
@@ -216,18 +265,22 @@ function classifySurfaceDir(dir: string): SurfaceDirState {
|
|
|
216
265
|
* address out from under it. An unreadable marker at REST is disposable residue; an
|
|
217
266
|
* unreadable marker as EVIDENCE OF QUIESCENCE is no evidence at all.
|
|
218
267
|
*/
|
|
219
|
-
function inspectMarkers(
|
|
268
|
+
function inspectMarkers(
|
|
269
|
+
dir: string,
|
|
270
|
+
label: string,
|
|
271
|
+
): { violations: QuiesceViolation[]; deadFiles: string[]; refutedFiles: string[] } {
|
|
220
272
|
const violations: QuiesceViolation[] = [];
|
|
221
273
|
const deadFiles: string[] = [];
|
|
274
|
+
const refutedFiles: string[] = [];
|
|
222
275
|
const markerFiles: { file: string; shown: string }[] = [];
|
|
223
276
|
const uncertain = (shown: string, why: string): void => {
|
|
224
277
|
violations.push({ surface: label, detail: `${shown} — ${why}`, kind: "uncertain" });
|
|
225
278
|
};
|
|
226
279
|
const dirState = classifySurfaceDir(dir);
|
|
227
|
-
if (dirState.state === "absent") return { violations, deadFiles };
|
|
280
|
+
if (dirState.state === "absent") return { violations, deadFiles, refutedFiles };
|
|
228
281
|
if (dirState.state !== "directory") {
|
|
229
282
|
uncertain(dir, `surface could not be inspected (${dirState.detail}): an unreadable surface is not a quiesced one`);
|
|
230
|
-
return { violations, deadFiles };
|
|
283
|
+
return { violations, deadFiles, refutedFiles };
|
|
231
284
|
}
|
|
232
285
|
// depth 0 = the marker root, depth 1 = a backend subdir. Nothing deeper is part
|
|
233
286
|
// of any marker layout, so it is inspected by hand rather than swept.
|
|
@@ -263,13 +316,28 @@ function inspectMarkers(dir: string, label: string): { violations: QuiesceViolat
|
|
|
263
316
|
uncertain(shown, `unreadable marker (${err instanceof Error ? err.message : String(err)}): owner unprovable`);
|
|
264
317
|
continue;
|
|
265
318
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
319
|
+
// Two different failures, deliberately kept apart. NO numeric `ownerPid` means the
|
|
320
|
+
// marker never NAMED an owner — unprovable, so it refuses the cut like every other
|
|
321
|
+
// unreadable marker. A numeric one that cannot own (`<= 1`, non-integer, unsafe)
|
|
322
|
+
// IS a claim, and it is refuted BY CONSTRUCTION: no writer in this tree can mint it
|
|
323
|
+
// any more, and on the axis this repo certifies "this session is owned by init" is
|
|
324
|
+
// false on its face (a container harness running AS pid 1 is a real but UNSUPPORTED
|
|
325
|
+
// shape — see isPlausibleOwnerPid; it fails closed at the writers). That is a
|
|
326
|
+
// proof of INVALIDITY — strictly stronger than the proof of death this loop already
|
|
327
|
+
// acts on — so it is clearable residue, never `live` and never `uncertain`. Without
|
|
328
|
+
// this row a single reparented-owner marker left quiescence unprovable by the ONE
|
|
329
|
+
// action the refusal prescribes, and the repair 0.12.8 names could not run until
|
|
330
|
+
// the file was deleted by hand (#53 A, measured 2026-07-25).
|
|
331
|
+
if (typeof raw.ownerPid !== "number") {
|
|
332
|
+
uncertain(shown, "no numeric `ownerPid`: owner unprovable");
|
|
271
333
|
continue;
|
|
272
334
|
}
|
|
335
|
+
if (!isPlausibleOwnerPid(raw.ownerPid)) {
|
|
336
|
+
refutedFiles.push(file);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const ownerPid = raw.ownerPid;
|
|
340
|
+
const ownerStartKey = typeof raw.ownerStartKey === "string" ? raw.ownerStartKey : "";
|
|
273
341
|
if (ownerStartKey === "") {
|
|
274
342
|
uncertain(shown, "no `ownerStartKey`: a bare pid cannot distinguish the owner from a reused pid");
|
|
275
343
|
continue;
|
|
@@ -294,7 +362,7 @@ function inspectMarkers(dir: string, label: string): { violations: QuiesceViolat
|
|
|
294
362
|
deadFiles.push(file);
|
|
295
363
|
}
|
|
296
364
|
}
|
|
297
|
-
return { violations, deadFiles };
|
|
365
|
+
return { violations, deadFiles, refutedFiles };
|
|
298
366
|
}
|
|
299
367
|
|
|
300
368
|
/**
|
|
@@ -319,7 +387,8 @@ function inspectMarkers(dir: string, label: string): { violations: QuiesceViolat
|
|
|
319
387
|
* record this walk skips, and the claim is deliberately narrow: NOT "that native session
|
|
320
388
|
* has exited", only "these bytes are not an address authority in the live runtime, so
|
|
321
389
|
* they front no current-generation garden surface". Every path that ADDRESSES a citizen
|
|
322
|
-
* goes through the live schema — `
|
|
390
|
+
* goes through the live schema — `readAddressableMetaIdentity` (v2 `resolveTarget` and
|
|
391
|
+
* the pi resume), `readMetaIdentityByGardenId` (the relay reads),
|
|
323
392
|
* the sender-marker trust — and each THROWS on a record this parser refuses, so nothing
|
|
324
393
|
* can dispatch to it. (`entwurf_peers` still LISTS it as a diagnostic; a facts surface
|
|
325
394
|
* reporting what it could not read is not an address.) A record we CAN read is the
|
|
@@ -402,10 +471,39 @@ async function inspectNativePushCitizens(storeDir: string): Promise<QuiesceViola
|
|
|
402
471
|
return violations;
|
|
403
472
|
}
|
|
404
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Remove the files a completed cut is contracted to clear, and report what it could
|
|
476
|
+
* not. ENOENT is the ONLY tolerated failure — the file raced away, which is the goal
|
|
477
|
+
* state. Every other errno is a real refusal to delete (EACCES, EPERM under a
|
|
478
|
+
* sticky-bit parent, EROFS, an immutable attribute, an LSM denial), and a bare
|
|
479
|
+
* `catch {}` here would launder all of them into "raced away" while the command's own
|
|
480
|
+
* output still said `cleared:`. That is the Crash-Don't-Warn shape this repo removes,
|
|
481
|
+
* not a shortcut it tolerates — the caller decides what to do with the failures,
|
|
482
|
+
* because by this point the archive has already moved and throwing would cost the
|
|
483
|
+
* operator the story of what DID happen.
|
|
484
|
+
*/
|
|
485
|
+
function clearFiles(files: string[]): { cleared: number; failures: { file: string; reason: string }[] } {
|
|
486
|
+
const failures: { file: string; reason: string }[] = [];
|
|
487
|
+
let cleared = 0;
|
|
488
|
+
for (const file of files) {
|
|
489
|
+
try {
|
|
490
|
+
fs.unlinkSync(file);
|
|
491
|
+
cleared += 1;
|
|
492
|
+
} catch (err) {
|
|
493
|
+
if ((err as { code?: unknown }).code === "ENOENT") continue; // raced away — already gone is the goal state
|
|
494
|
+
failures.push({ file, reason: err instanceof Error ? err.message : String(err) });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return { cleared, failures };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Surfaces this process has already renamed into an archive — see the rename loop. */
|
|
501
|
+
let archivedSoFar = 0;
|
|
502
|
+
|
|
405
503
|
async function main(): Promise<number> {
|
|
406
504
|
const args = process.argv.slice(2);
|
|
407
|
-
if (args.includes("-h") || args.includes("--help")) usage(
|
|
408
|
-
if (args.length > 0) usage(
|
|
505
|
+
if (args.includes("-h") || args.includes("--help")) usage(FRESH_CUT_EXIT.COMPLETE);
|
|
506
|
+
if (args.length > 0) usage(FRESH_CUT_EXIT.USAGE);
|
|
409
507
|
|
|
410
508
|
const storeDir = defaultMetaSessionsDir();
|
|
411
509
|
const mailboxDir = defaultMetaMailboxDir();
|
|
@@ -487,7 +585,7 @@ async function main(): Promise<number> {
|
|
|
487
585
|
"Quiesce those sessions (close them / let them exit), inspect anything listed UNCERTAIN, " +
|
|
488
586
|
"then re-run the same command. Nothing was moved.",
|
|
489
587
|
);
|
|
490
|
-
return
|
|
588
|
+
return FRESH_CUT_EXIT.NO_MOVE;
|
|
491
589
|
}
|
|
492
590
|
|
|
493
591
|
// ── the cut: plan the moves, preflight the whole plan, then rename ───────
|
|
@@ -536,9 +634,15 @@ async function main(): Promise<number> {
|
|
|
536
634
|
"taken (a cut in the same second, or leftovers from an interrupted one). Nothing was moved: " +
|
|
537
635
|
"wait a second and re-run, or move those directories aside first.",
|
|
538
636
|
);
|
|
539
|
-
return
|
|
637
|
+
return FRESH_CUT_EXIT.NO_MOVE;
|
|
540
638
|
}
|
|
541
639
|
const archived: string[] = [];
|
|
640
|
+
// Mirrored to module scope for the ONE reader that cannot see this array: the
|
|
641
|
+
// top-level rejection handler. Everything from here on can still throw — the
|
|
642
|
+
// `mkdirSync` that opens the fresh generation, most obviously — and a handler that
|
|
643
|
+
// answered a flat `1` there would tell a runbook "nothing moved" about a host whose
|
|
644
|
+
// generation is already in an archive. A single-shot CLI may hold one fact in module
|
|
645
|
+
// scope when the alternative is an exit status that lies (#54).
|
|
542
646
|
for (const { src, dest } of plan) {
|
|
543
647
|
try {
|
|
544
648
|
fs.renameSync(src, dest);
|
|
@@ -563,19 +667,20 @@ async function main(): Promise<number> {
|
|
|
563
667
|
"own stamp — or inspect by hand.",
|
|
564
668
|
);
|
|
565
669
|
}
|
|
566
|
-
|
|
670
|
+
// The prose above already distinguished these two; the STATUS did not, and a
|
|
671
|
+
// runbook reads the status (#54). `midCutExit` is the SSOT so the words and the
|
|
672
|
+
// number cannot drift apart.
|
|
673
|
+
return midCutExit(archived.length);
|
|
567
674
|
}
|
|
568
675
|
archived.push(dest);
|
|
676
|
+
archivedSoFar = archived.length;
|
|
569
677
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// raced away — already gone is the goal state
|
|
577
|
-
}
|
|
578
|
-
}
|
|
678
|
+
// Both sweeps go through ONE remover so they cannot drift to different meanings of
|
|
679
|
+
// "could not remove". Counted APART, because a dead marker is an owner we PROVED
|
|
680
|
+
// left while a refuted one never named an owner that could exist, and folding the
|
|
681
|
+
// two would dress a proof of invalidity up as a proof of death.
|
|
682
|
+
const dead = clearFiles([...senders.deadFiles, ...receivers.deadFiles, ...deadSockets]);
|
|
683
|
+
const refuted = clearFiles([...senders.refutedFiles, ...receivers.refutedFiles]);
|
|
579
684
|
fs.mkdirSync(storeDir, { recursive: true });
|
|
580
685
|
fs.mkdirSync(mailboxDir, { recursive: true });
|
|
581
686
|
|
|
@@ -584,13 +689,40 @@ async function main(): Promise<number> {
|
|
|
584
689
|
} else {
|
|
585
690
|
for (const dir of archived) console.log(`archived: ${dir}`);
|
|
586
691
|
}
|
|
587
|
-
if (cleared > 0) console.log(`cleared: ${cleared} dead marker/socket file(s) (disposable process state)`);
|
|
692
|
+
if (dead.cleared > 0) console.log(`cleared: ${dead.cleared} dead marker/socket file(s) (disposable process state)`);
|
|
693
|
+
if (refuted.cleared > 0) {
|
|
694
|
+
console.log(
|
|
695
|
+
`refuted: ${refuted.cleared} legacy/corrupt marker(s) named an owner pid that cannot own a session ` +
|
|
696
|
+
"(<= 1, or not a pid at all); REFUTED BY CONSTRUCTION, not proven dead, and cleared. The observed " +
|
|
697
|
+
"case was a hook reparented to init before it read its own parent.",
|
|
698
|
+
);
|
|
699
|
+
}
|
|
588
700
|
console.log(`fresh generation open: ${storeDir} (empty, v3-only)`);
|
|
589
701
|
console.log(
|
|
590
702
|
"untouched: native transcripts, andenken/embedding memory axes, install state. " +
|
|
591
703
|
"The archive is forensic only — no runtime reads it and there is no restore verb.",
|
|
592
704
|
);
|
|
593
|
-
|
|
705
|
+
|
|
706
|
+
// The generation IS cut by now, so a cleanup failure must not throw away that
|
|
707
|
+
// story — but it must not be swallowed either: this command's own report claims
|
|
708
|
+
// the residue was "cleared". Say exactly which files survived and why, keep the
|
|
709
|
+
// success lines above (this is not a half-cut), and exit nonzero so no caller
|
|
710
|
+
// reads a partial sweep as a clean one.
|
|
711
|
+
const cleanupFailures = [...dead.failures, ...refuted.failures];
|
|
712
|
+
if (cleanupFailures.length > 0) {
|
|
713
|
+
for (const { file, reason } of cleanupFailures) console.error(`FAIL post-cut cleanup: ${file} — ${reason}`);
|
|
714
|
+
console.error(
|
|
715
|
+
`FAIL post-cut cleanup: ${cleanupFailures.length} marker/socket file(s) above could NOT be removed. The ` +
|
|
716
|
+
"generation was archived and the fresh generation is open — this is not a half-cut, and install/citizen " +
|
|
717
|
+
"birth are no longer blocked by the store. What survived is disposable process state sitting in the new " +
|
|
718
|
+
"generation's surfaces, and it will refuse the NEXT cut. Fix the cause (permissions, a read-only mount, " +
|
|
719
|
+
`an immutable attribute) and re-run the same command BEFORE \`setup\`, or remove those files by hand. EXIT ` +
|
|
720
|
+
`${FRESH_CUT_EXIT.CLEANUP_INCOMPLETE}: the cut is DONE, so \`setup\` may proceed — but once a new citizen is ` +
|
|
721
|
+
"born, another fresh-cut would archive that new generation too; repair the named residue manually instead.",
|
|
722
|
+
);
|
|
723
|
+
return FRESH_CUT_EXIT.CLEANUP_INCOMPLETE;
|
|
724
|
+
}
|
|
725
|
+
return FRESH_CUT_EXIT.COMPLETE;
|
|
594
726
|
}
|
|
595
727
|
|
|
596
728
|
main().then(
|
|
@@ -598,6 +730,10 @@ main().then(
|
|
|
598
730
|
(err) => {
|
|
599
731
|
console.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
|
|
600
732
|
console.error("FAIL: fresh-cut did not complete — the message above names the cause; re-run after fixing it.");
|
|
601
|
-
|
|
733
|
+
// Not a flat 1: the ONE thing a caller must know from a crash is whether the host
|
|
734
|
+
// still holds its generation. Every refusal that reaches here from before the first
|
|
735
|
+
// rename really is a no-op (its own message says so); anything after it is a
|
|
736
|
+
// an incomplete cut transition and must not be mistaken for a no-op (#54).
|
|
737
|
+
process.exit(midCutExit(archivedSoFar));
|
|
602
738
|
},
|
|
603
739
|
);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "acp-augment",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "AUGMENT-BUDGET-FITS",
|
|
7
|
+
"title": "shrinking the augment cap re-opens the tail-cut that hid AGENTS rules + cwd from ACP siblings",
|
|
8
|
+
"subject": "pi-extensions/lib/acp/augment.ts",
|
|
9
|
+
"find": ["const MAX_AUGMENT_BYTES = 50 * 1024;"],
|
|
10
|
+
"replace": ["const MAX_AUGMENT_BYTES = 20 * 1024;"],
|
|
11
|
+
"gate": ["bash", "run.sh", "check-acp-carrier-augment"],
|
|
12
|
+
"timeoutSeconds": 60,
|
|
13
|
+
"signature": "[QK:AUGMENT-BUDGET-FITS]",
|
|
14
|
+
"signatureSource": "scripts/check-acp-carrier-augment.ts"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"claim": "AUGMENT-TRUNC-MARKER",
|
|
18
|
+
"title": "silent truncation: dropping the honest marker leaves a cut augment with no trace",
|
|
19
|
+
"subject": "pi-extensions/lib/acp/augment.ts",
|
|
20
|
+
"find": [
|
|
21
|
+
"\tconst marker = `\\n\\n[entwurf: context augment truncated to ${MAX_AUGMENT_BYTES} bytes; read AGENTS.md files directly if more detail is needed.]`;"
|
|
22
|
+
],
|
|
23
|
+
"replace": ["\tconst marker = \"\";"],
|
|
24
|
+
"gate": ["bash", "run.sh", "check-acp-carrier-augment"],
|
|
25
|
+
"timeoutSeconds": 60,
|
|
26
|
+
"signature": "[QK:AUGMENT-TRUNC-MARKER]",
|
|
27
|
+
"signatureSource": "scripts/check-acp-carrier-augment.ts"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "agy-permission",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "AGY-EXACT-RULE-SET",
|
|
7
|
+
"title": "over-grant: adding entwurf_inbox_read to ALLOW_RULES must die at the independent literal oracle",
|
|
8
|
+
"subject": "scripts/agy-bridge-config.py",
|
|
9
|
+
"find": [
|
|
10
|
+
"ALLOW_RULES = tuple(f\"mcp({SERVER_KEY}/{tool})\" for tool in (\"entwurf_v2\", \"entwurf_peers\", \"entwurf_self\"))"
|
|
11
|
+
],
|
|
12
|
+
"replace": [
|
|
13
|
+
"ALLOW_RULES = tuple(",
|
|
14
|
+
" f\"mcp({SERVER_KEY}/{tool})\" for tool in (\"entwurf_v2\", \"entwurf_peers\", \"entwurf_self\", \"entwurf_inbox_read\")",
|
|
15
|
+
")"
|
|
16
|
+
],
|
|
17
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
18
|
+
"timeoutSeconds": 180,
|
|
19
|
+
"signature": "[QK:AGY-EXACT-RULE-SET]",
|
|
20
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"claim": "AGY-INSTALL-MALFORMED-PRIOR",
|
|
24
|
+
"title": "install reverts to a shallow prior-state check (empty provenance map passes all()) and re-captures",
|
|
25
|
+
"subject": "scripts/agy-bridge-config.py",
|
|
26
|
+
"find": [
|
|
27
|
+
" try:",
|
|
28
|
+
" _prior_rules, prior_existed = _parse_permission_state(prior)",
|
|
29
|
+
" except _PermissionStateError as exc:",
|
|
30
|
+
" _die(4, f\"agy-bridge: prior permission install-state is unusable — {exc}. Refusing to \"",
|
|
31
|
+
" \"overwrite provenance this entwurf cannot read; inspect it by hand.\")",
|
|
32
|
+
" for rule in ALLOW_RULES:",
|
|
33
|
+
" if rule in prior_existed:",
|
|
34
|
+
" rules_existed[rule] = prior_existed[rule]"
|
|
35
|
+
],
|
|
36
|
+
"replace": [
|
|
37
|
+
" if prior.get(\"schemaVersion\") == 1:",
|
|
38
|
+
" legacy_rule = prior.get(\"rule\")",
|
|
39
|
+
" if isinstance(legacy_rule, str) and isinstance(prior.get(\"ruleExistedBefore\"), bool):",
|
|
40
|
+
" if legacy_rule in ALLOW_RULES:",
|
|
41
|
+
" rules_existed[legacy_rule] = prior[\"ruleExistedBefore\"]",
|
|
42
|
+
" else:",
|
|
43
|
+
" prior_existed = prior.get(\"rulesExistedBefore\")",
|
|
44
|
+
" if isinstance(prior_existed, dict) and all(isinstance(v, bool) for v in prior_existed.values()):",
|
|
45
|
+
" for rule in ALLOW_RULES:",
|
|
46
|
+
" if rule in prior_existed:",
|
|
47
|
+
" rules_existed[rule] = prior_existed[rule]"
|
|
48
|
+
],
|
|
49
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
50
|
+
"timeoutSeconds": 180,
|
|
51
|
+
"signature": "[QK:AGY-INSTALL-MALFORMED-PRIOR]",
|
|
52
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"claim": "AGY-UNINSTALL-VALIDATE-FIRST",
|
|
56
|
+
"title": "uninstall validates the state only inside the settings-exists branch (refusal after mutation)",
|
|
57
|
+
"subject": "scripts/agy-bridge-config.py",
|
|
58
|
+
"find": [
|
|
59
|
+
" ours = _owned_rules(state)",
|
|
60
|
+
"",
|
|
61
|
+
" if os.path.islink(settings_path):",
|
|
62
|
+
" _die(3, f\"agy-bridge: refusing to uninstall — {settings_path} became a symlink since install \"",
|
|
63
|
+
" f\"(someone else's SSOT now). Resolve by hand.\")",
|
|
64
|
+
"",
|
|
65
|
+
" if os.path.exists(settings_path):",
|
|
66
|
+
" data = _load_config(settings_path)"
|
|
67
|
+
],
|
|
68
|
+
"replace": [
|
|
69
|
+
" if os.path.islink(settings_path):",
|
|
70
|
+
" _die(3, f\"agy-bridge: refusing to uninstall — {settings_path} became a symlink since install \"",
|
|
71
|
+
" f\"(someone else's SSOT now). Resolve by hand.\")",
|
|
72
|
+
"",
|
|
73
|
+
" ours = []",
|
|
74
|
+
" if os.path.exists(settings_path):",
|
|
75
|
+
" data = _load_config(settings_path)",
|
|
76
|
+
" ours = _owned_rules(state)"
|
|
77
|
+
],
|
|
78
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
79
|
+
"timeoutSeconds": 180,
|
|
80
|
+
"signature": "[QK:AGY-UNINSTALL-VALIDATE-FIRST]",
|
|
81
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"claim": "AGY-DOCTOR-OWNERSHIP-AXIS",
|
|
85
|
+
"title": "the state-doctor rounds a path-correct but unreadable ownership record up to ok",
|
|
86
|
+
"subject": "scripts/agy-bridge-config.py",
|
|
87
|
+
"find": [
|
|
88
|
+
" try:",
|
|
89
|
+
" rules, _existed = _parse_permission_state(state)",
|
|
90
|
+
" except _PermissionStateError as exc:",
|
|
91
|
+
" sys.stdout.write(f\"corrupt {exc}\\n\")",
|
|
92
|
+
" return",
|
|
93
|
+
" sys.stdout.write(f\"ok {len(rules)}\\n\")"
|
|
94
|
+
],
|
|
95
|
+
"replace": [" sys.stdout.write(\"ok 0\\n\")"],
|
|
96
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
97
|
+
"timeoutSeconds": 180,
|
|
98
|
+
"signature": "[QK:AGY-DOCTOR-OWNERSHIP-AXIS]",
|
|
99
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"claim": "AGY-CROSS-LIST-BROAD-FIRST",
|
|
103
|
+
"title": "broad-most-covering runs only within each list, so a deny-exact shadows an ask-broad host wrongly",
|
|
104
|
+
"subject": "scripts/agy-bridge-config.py",
|
|
105
|
+
"find": [
|
|
106
|
+
" for rule_set, scope in ((BROAD_RULES, \"broad\"), (ALLOW_RULES, \"exact\")):",
|
|
107
|
+
" for list_name in SHADOWING_LISTS:",
|
|
108
|
+
" rules = perms.get(list_name)",
|
|
109
|
+
" if not isinstance(rules, list):",
|
|
110
|
+
" continue",
|
|
111
|
+
" for rule in rules:",
|
|
112
|
+
" if rule in rule_set:",
|
|
113
|
+
" sys.stdout.write(f\"shadowed-by-{list_name} {scope} {rule}\\n\")",
|
|
114
|
+
" return"
|
|
115
|
+
],
|
|
116
|
+
"replace": [
|
|
117
|
+
" for list_name in SHADOWING_LISTS:",
|
|
118
|
+
" rules = perms.get(list_name)",
|
|
119
|
+
" if not isinstance(rules, list):",
|
|
120
|
+
" continue",
|
|
121
|
+
" for rule_set, scope in ((BROAD_RULES, \"broad\"), (ALLOW_RULES, \"exact\")):",
|
|
122
|
+
" for rule in rules:",
|
|
123
|
+
" if rule in rule_set:",
|
|
124
|
+
" sys.stdout.write(f\"shadowed-by-{list_name} {scope} {rule}\\n\")",
|
|
125
|
+
" return"
|
|
126
|
+
],
|
|
127
|
+
"gate": ["bash", "run.sh", "check-agy-permission-matrix"],
|
|
128
|
+
"timeoutSeconds": 60,
|
|
129
|
+
"signature": "[QK:AGY-CROSS-LIST-BROAD-FIRST]",
|
|
130
|
+
"signatureSource": "scripts/check-agy-permission-matrix.py"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"claim": "AGY-SCHEMA-VERSIONS-APART",
|
|
134
|
+
"title": "re-merging the two state schema constants stamps a lying version onto the MCP install-state",
|
|
135
|
+
"subject": "scripts/agy-bridge-config.py",
|
|
136
|
+
"find": ["STATE_SCHEMA_VERSION = 1 # MCP install-state (server key + preimage); shape unchanged"],
|
|
137
|
+
"replace": ["STATE_SCHEMA_VERSION = 2 # re-merged with the permission version (the schema re-merge defect)"],
|
|
138
|
+
"gate": ["bash", "run.sh", "smoke-agy-install-state"],
|
|
139
|
+
"timeoutSeconds": 180,
|
|
140
|
+
"signature": "[QK:AGY-SCHEMA-VERSIONS-APART]",
|
|
141
|
+
"signatureSource": "scripts/smoke-agy-install-state.sh"
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "meta-identity",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "META-FIFO-NONBLOCK",
|
|
7
|
+
"title": "removing O_NONBLOCK makes a fifo-shaped record block the open — the 7/26 mutation evidence, committed",
|
|
8
|
+
"subject": "pi-extensions/lib/meta-session.ts",
|
|
9
|
+
"find": ["\tconst fd = fs.openSync(file, fs.constants.O_RDONLY | O_NOFOLLOW_FLAG | O_NONBLOCK_FLAG);"],
|
|
10
|
+
"replace": ["\tconst fd = fs.openSync(file, fs.constants.O_RDONLY | O_NOFOLLOW_FLAG);"],
|
|
11
|
+
"gate": ["bash", "run.sh", "check-meta-identity-consumers"],
|
|
12
|
+
"timeoutSeconds": 120,
|
|
13
|
+
"signature": "[QK:META-FIFO-NONBLOCK]",
|
|
14
|
+
"signatureSource": "scripts/check-meta-identity-consumers.ts"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "self-address",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "SELFADDR-RAIL-RENDER",
|
|
7
|
+
"title": "entwurf_self reverts to an empty self-fetch guard + unconditional mailbox render (ordering-not-containment)",
|
|
8
|
+
"subject": "mcp/entwurf-bridge/src/index.ts",
|
|
9
|
+
"find": [
|
|
10
|
+
"\t\t\t\tif (rail === \"self-fetch\") {",
|
|
11
|
+
"\t\t\t\t\tconst mailboxPath = path.join(defaultMetaMailboxDir(), sender.sessionId);",
|
|
12
|
+
"\t\t\t\t\textra.mailboxPath = mailboxPath;",
|
|
13
|
+
"\t\t\t\t\tlines.push(`mailboxPath: ${mailboxPath}`);",
|
|
14
|
+
"\t\t\t\t} else if (rail === \"native-push\") {"
|
|
15
|
+
],
|
|
16
|
+
"replace": [
|
|
17
|
+
"\t\t\t\tif (rail === \"self-fetch\") {",
|
|
18
|
+
"\t\t\t\t}",
|
|
19
|
+
"\t\t\t\tconst mailboxPath = path.join(defaultMetaMailboxDir(), sender.sessionId);",
|
|
20
|
+
"\t\t\t\textra.mailboxPath = mailboxPath;",
|
|
21
|
+
"\t\t\t\tlines.push(`mailboxPath: ${mailboxPath}`);",
|
|
22
|
+
"\t\t\t\tif (rail === \"native-push\") {"
|
|
23
|
+
],
|
|
24
|
+
"gate": ["bash", "run.sh", "check-entwurf-self-address"],
|
|
25
|
+
"timeoutSeconds": 60,
|
|
26
|
+
"signature": "[QK:SELFADDR-RAIL-RENDER]",
|
|
27
|
+
"signatureSource": "scripts/check-entwurf-self-address.ts"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"claim": "SELFADDR-NO-HARDCODED-REPLYABLE",
|
|
31
|
+
"title": "buildStrictPiSenderEnvelope hardcodes replyable: true again (env presence read as reachability)",
|
|
32
|
+
"subject": "mcp/entwurf-bridge/src/index.ts",
|
|
33
|
+
"find": ["\t\torigin: \"pi-session\",", "\t\treplyable: self.replyable,", "\t};", "}"],
|
|
34
|
+
"replace": ["\t\torigin: \"pi-session\",", "\t\treplyable: true,", "\t};", "}"],
|
|
35
|
+
"gate": ["bash", "run.sh", "check-entwurf-self-address"],
|
|
36
|
+
"timeoutSeconds": 60,
|
|
37
|
+
"signature": "[QK:SELFADDR-NO-HARDCODED-REPLYABLE]",
|
|
38
|
+
"signatureSource": "scripts/check-entwurf-self-address.ts"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"claim": "SELFADDR-RAIL-FACT-LEAK",
|
|
42
|
+
"title": "the native-push predicate lets a mailbox fact (watchArmed) buy replyability — behavioral, not text",
|
|
43
|
+
"subject": "pi-extensions/lib/entwurf-self-address.ts",
|
|
44
|
+
"find": [
|
|
45
|
+
"\t\t\t\t\tconst push = nativePushDeliverable({ recordBacked: facts.recordBacked, probeAlive: facts.probeAlive });"
|
|
46
|
+
],
|
|
47
|
+
"replace": [
|
|
48
|
+
"\t\t\t\t\tconst push = nativePushDeliverable({",
|
|
49
|
+
"\t\t\t\t\t\trecordBacked: facts.recordBacked,",
|
|
50
|
+
"\t\t\t\t\t\tprobeAlive: facts.probeAlive === true || facts.watchArmed === true,",
|
|
51
|
+
"\t\t\t\t\t});"
|
|
52
|
+
],
|
|
53
|
+
"gate": ["bash", "run.sh", "check-entwurf-self-address"],
|
|
54
|
+
"timeoutSeconds": 60,
|
|
55
|
+
"signature": "[QK:SELFADDR-RAIL-FACT-LEAK]",
|
|
56
|
+
"signatureSource": "scripts/check-entwurf-self-address.ts"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"lane": "v2-surface",
|
|
4
|
+
"mutants": [
|
|
5
|
+
{
|
|
6
|
+
"claim": "V2SURF-MCP-LOCK-DOMAIN",
|
|
7
|
+
"title": "MCP long description reverts to the false \"socket paths take a per-target lock\" claim",
|
|
8
|
+
"subject": "mcp/entwurf-bridge/src/index.ts",
|
|
9
|
+
"find": [
|
|
10
|
+
"\t\t\"(delivered / rejected / lock-retained / delivered-but-lock-dirty). LOCK POLICY (do not \" +",
|
|
11
|
+
"\t\t\"over-generalize it): the per-target lock is taken for a control-socket-DOMAIN dispatch, which is \" +",
|
|
12
|
+
"\t\t\"both the live send AND the dormant cell's spawn-bg resume — spawn-bg is a separate relaunch \" +",
|
|
13
|
+
"\t\t\"transport yet it still runs under that domain's lock. The mailbox and native-push rails are \" +",
|
|
14
|
+
"\t\t\"lock-free: the mailbox is guarded instead by active-receiver deliverability, and native-push by \" +",
|
|
15
|
+
"\t\t\"its adapter probe. The decider — not the \" +"
|
|
16
|
+
],
|
|
17
|
+
"replace": [
|
|
18
|
+
"\t\t\"(delivered / rejected / lock-retained / delivered-but-lock-dirty). LOCK POLICY: socket paths \" +",
|
|
19
|
+
"\t\t\"take a per-target lock; the mailbox path does not. The decider — not the \" +"
|
|
20
|
+
],
|
|
21
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
22
|
+
"timeoutSeconds": 60,
|
|
23
|
+
"signature": "[QK:V2SURF-MCP-LOCK-DOMAIN]",
|
|
24
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"claim": "V2SURF-PI-MODE-SCOPE",
|
|
28
|
+
"title": "pi-native mode param reverts to \"Delivery mode for a live send\" (false for native-push)",
|
|
29
|
+
"subject": "pi-extensions/entwurf-control.ts",
|
|
30
|
+
"find": [
|
|
31
|
+
"\t\t\t\tdescription:",
|
|
32
|
+
"\t\t\t\t\t\"Injection style for a CONTROL-SOCKET send only: steer (immediate) or follow_up (after task). \" +",
|
|
33
|
+
"\t\t\t\t\t\"The mailbox, native-push, and spawn-bg plans carry no mode, so it has no effect on those rails.\","
|
|
34
|
+
],
|
|
35
|
+
"replace": [
|
|
36
|
+
"\t\t\t\tdescription:",
|
|
37
|
+
"\t\t\t\t\t\"Delivery mode for a live send: steer (immediate, interrupt the current turn) or follow_up \" +",
|
|
38
|
+
"\t\t\t\t\t\"(queued after the current task finishes running to completion).\","
|
|
39
|
+
],
|
|
40
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
41
|
+
"timeoutSeconds": 60,
|
|
42
|
+
"signature": "[QK:V2SURF-PI-MODE-SCOPE]",
|
|
43
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"claim": "V2SURF-INBOX-SCOPE-HONESTY",
|
|
47
|
+
"title": "entwurf_inbox_read reverts to the false \"drains your own inbox\" access claim (SCOPE paragraph gone)",
|
|
48
|
+
"subject": "mcp/entwurf-bridge/src/index.ts",
|
|
49
|
+
"find": [
|
|
50
|
+
"\t\t\"doorbell is only a wake attempt, not a read. An empty inbox mutates nothing. \" +",
|
|
51
|
+
"\t\t\"SCOPE — read this literally: the garden id is CALLER-SUPPLIED and is NOT verified against \" +",
|
|
52
|
+
"\t\t\"your own identity (a host with no garden record of its own can call this too). So passing \" +",
|
|
53
|
+
"\t\t\"another citizen's garden id drains THEIR mail and stamps THEIR receipt, and they will never \" +",
|
|
54
|
+
"\t\t\"see those messages. Pass only the id from your own doorbell notice or your own meta-record; \" +",
|
|
55
|
+
"\t\t\"use entwurf_self if you need to confirm which id that is. \" +"
|
|
56
|
+
],
|
|
57
|
+
"replace": [
|
|
58
|
+
"\t\t\"doorbell is only a wake attempt, not a read. An empty inbox mutates nothing. \" +",
|
|
59
|
+
"\t\t\"This tool drains your own meta-bridge inbox and stamps the read receipt on your meta-record. \" +"
|
|
60
|
+
],
|
|
61
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
62
|
+
"timeoutSeconds": 60,
|
|
63
|
+
"signature": "[QK:V2SURF-INBOX-SCOPE-HONESTY]",
|
|
64
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"claim": "V2SURF-MERGED-REJECT",
|
|
68
|
+
"title": "MCP long description merges the two owned rejects back under backend-liveness-unsupported",
|
|
69
|
+
"subject": "mcp/entwurf-bridge/src/index.ts",
|
|
70
|
+
"find": [
|
|
71
|
+
"\t\t\"owned-outcome is ONLY for waking a DORMANT socket-domain citizen (spawn-bg resume, currently \" +",
|
|
72
|
+
"\t\t\"backend pi); on a live target it is rejected as owned-live-no-autosend. Neither self-fetch nor \" +",
|
|
73
|
+
"\t\t\"native-push has resume authority, but they reject under DIFFERENT reasons — self-fetch as \" +",
|
|
74
|
+
"\t\t\"backend-liveness-unsupported, native-push as native-push-no-resume-authority. \" +"
|
|
75
|
+
],
|
|
76
|
+
"replace": [
|
|
77
|
+
"\t\t\"owned-outcome is ONLY for waking a DORMANT socket-domain citizen (spawn-bg resume, currently \" +",
|
|
78
|
+
"\t\t\"backend pi); on a live target it is rejected as owned-live-no-autosend, and on self-fetch and \" +",
|
|
79
|
+
"\t\t\"native-push alike it is rejected as backend-liveness-unsupported. \" +"
|
|
80
|
+
],
|
|
81
|
+
"gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
|
|
82
|
+
"timeoutSeconds": 60,
|
|
83
|
+
"signature": "[QK:V2SURF-MERGED-REJECT]",
|
|
84
|
+
"signatureSource": "scripts/check-entwurf-v2-surface.ts"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|