@junghanacs/entwurf 0.12.9 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/AGENTS.md +135 -248
  2. package/BASELINE.md +46 -7
  3. package/CHANGELOG.md +35 -0
  4. package/CONTRIBUTING.md +4 -2
  5. package/DELIVERY.md +5 -4
  6. package/README.md +46 -18
  7. package/VERIFY.md +10 -6
  8. package/demo/demo.sh +7 -1
  9. package/docs/setup-clean-host.md +10 -4
  10. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +159 -66
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +148 -5
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -3
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +26 -9
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +10 -6
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +15 -17
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +13 -13
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +31 -41
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +8 -9
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +32 -25
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +4 -4
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +5 -4
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +9 -11
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +3 -1
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +17 -20
  28. package/mcp/entwurf-bridge/src/index.ts +190 -82
  29. package/package.json +7 -3
  30. package/pi/settings.reference.json +1 -1
  31. package/pi-extensions/acp-provider.ts +20 -10
  32. package/pi-extensions/entwurf-control.ts +69 -27
  33. package/pi-extensions/lib/acp/augment.ts +60 -2
  34. package/pi-extensions/lib/acp/backend-adapter.ts +183 -8
  35. package/pi-extensions/lib/acp/backend.ts +5 -1
  36. package/pi-extensions/lib/acp/config.ts +19 -5
  37. package/pi-extensions/lib/acp/engraving.ts +3 -1
  38. package/pi-extensions/lib/acp/event-mapper.ts +10 -3
  39. package/pi-extensions/lib/acp/models.ts +69 -7
  40. package/pi-extensions/lib/acp/overlay.ts +234 -3
  41. package/pi-extensions/lib/acp/tool-surface.ts +12 -4
  42. package/pi-extensions/lib/entwurf-core.ts +26 -9
  43. package/pi-extensions/lib/entwurf-deliverability.ts +10 -6
  44. package/pi-extensions/lib/entwurf-fact-provider.ts +15 -17
  45. package/pi-extensions/lib/entwurf-facts.ts +13 -13
  46. package/pi-extensions/lib/entwurf-resume-args.ts +40 -46
  47. package/pi-extensions/lib/entwurf-self-address.ts +8 -9
  48. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +7 -2
  49. package/pi-extensions/lib/entwurf-v2-contract.ts +32 -25
  50. package/pi-extensions/lib/entwurf-v2-decider.ts +8 -8
  51. package/pi-extensions/lib/entwurf-v2-lock.ts +5 -4
  52. package/pi-extensions/lib/entwurf-v2-production.ts +14 -11
  53. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +3 -1
  54. package/pi-extensions/lib/meta-session.ts +17 -20
  55. package/run.sh +200 -62
  56. package/scripts/agy-bridge-config.py +249 -49
  57. package/scripts/agy-bridge.sh +59 -14
  58. package/scripts/check-acp-carrier-augment.ts +34 -2
  59. package/scripts/check-acp-cortex.ts +668 -0
  60. package/scripts/check-acp-provider-surface.ts +50 -6
  61. package/scripts/check-acp-sdk-surface.ts +22 -11
  62. package/scripts/check-acp-session-reuse.ts +64 -1
  63. package/scripts/check-agy-permission-matrix.py +655 -0
  64. package/scripts/check-entwurf-deliverability.ts +9 -6
  65. package/scripts/check-entwurf-fact-provider.ts +7 -7
  66. package/scripts/check-entwurf-facts.ts +12 -12
  67. package/scripts/check-entwurf-resume-args.ts +22 -60
  68. package/scripts/check-entwurf-self-address.ts +187 -4
  69. package/scripts/check-entwurf-v2-contract.ts +2 -2
  70. package/scripts/check-entwurf-v2-production.ts +5 -5
  71. package/scripts/check-entwurf-v2-spawn-production.ts +6 -2
  72. package/scripts/check-entwurf-v2-surface.ts +327 -14
  73. package/scripts/check-gate-qualification.ts +787 -0
  74. package/scripts/check-meta-identity-consumers.ts +1 -1
  75. package/scripts/check-probe-cli-shim.ts +879 -0
  76. package/scripts/check-probe-ordering.ts +2450 -0
  77. package/scripts/check-shell-quote.ts +5 -4
  78. package/scripts/fixtures/probe-cli-shim +20 -0
  79. package/scripts/fixtures/probe-mcp-server.ts +168 -12
  80. package/scripts/lib/mutation-qualify.ts +794 -0
  81. package/scripts/lib/probe-acp-turn.ts +207 -0
  82. package/scripts/lib/probe-cli-shim.ts +464 -0
  83. package/scripts/lib/probe-cli-target.ts +165 -0
  84. package/scripts/lib/probe-event-log.ts +383 -0
  85. package/scripts/lib/probe-verdict.ts +1213 -0
  86. package/scripts/mutants/acp-augment.json +30 -0
  87. package/scripts/mutants/acp-cortex.json +196 -0
  88. package/scripts/mutants/agy-permission.json +144 -0
  89. package/scripts/mutants/meta-identity.json +17 -0
  90. package/scripts/mutants/probe-ordering.json +1032 -0
  91. package/scripts/mutants/self-address.json +59 -0
  92. package/scripts/mutants/v2-surface.json +87 -0
  93. package/scripts/smoke-acp-cortex-live.ts +392 -0
  94. package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
  95. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  96. package/scripts/smoke-acp-v2-send-live.ts +33 -18
  97. package/scripts/smoke-agy-install-state.sh +205 -20
  98. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -2
  99. package/pi-extensions/lib/entwurf-mailbox-guard.ts +0 -100
  100. package/scripts/check-entwurf-mailbox-guard.ts +0 -266
@@ -1,266 +0,0 @@
1
- /**
2
- * check-entwurf-mailbox-guard — deterministic gate for the guarded mailbox enqueue
3
- * (SE-1/SE-2 slice 2d). The IO orchestration that conversational-reply sites use
4
- * instead of calling enqueueMetaMessage directly: it must NOT enqueue (no .msg, no
5
- * doorbell poke, no mailbox mutation) when the target is undeliverable, and must
6
- * enqueue exactly once when it is.
7
- *
8
- * Proves (GPT Q5 — both axes):
9
- * 1. PURE 0-call: with injected facts/readers, an undeliverable target leaves the
10
- * injected enqueue UNCALLED (dead receiver / direct-inject / absent record);
11
- * a deliverable target calls it exactly once and carries the result through.
12
- * 2. TMPDIR SNAPSHOT: with the REAL enqueueMetaMessage against a real mailbox dir,
13
- * a refused send leaves the mailbox tree byte-for-byte unchanged (file list +
14
- * content hash, not just mtime), and an accepted send writes exactly one .msg +
15
- * pokes the signal.
16
- * 3. fact gathering: recordBacked/wakeMode from the record+capability, ownerAlive/
17
- * watchArmed from the receiver marker (present→true, absent/dead→false).
18
- */
19
-
20
- import assert from "node:assert/strict";
21
- import { createHash } from "node:crypto";
22
- import { mkdirSync, mkdtempSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
23
- import { tmpdir } from "node:os";
24
- import * as path from "node:path";
25
- import { fileURLToPath } from "node:url";
26
- import { gatherMailboxDeliverabilityFacts, guardedMailboxEnqueue } from "../pi-extensions/lib/entwurf-mailbox-guard.ts";
27
- import {
28
- enqueueMetaMessage,
29
- type MetaIdentity,
30
- mintMetaIdentity,
31
- readMetaReceiverMarker,
32
- serializeMetaIdentity,
33
- writeMetaReceiverMarker,
34
- } from "../pi-extensions/lib/meta-session.ts";
35
-
36
- let passed = 0;
37
- function ok(label: string, cond: boolean): void {
38
- assert.ok(cond, label);
39
- console.log(` ok ${label}`);
40
- passed++;
41
- }
42
-
43
- const REPO_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
44
- const GARDEN = "20260614T120000-aaaaaa";
45
- const selfFetch = () => ({ wakeMode: "self-fetch" }) as never;
46
- const directInject = () => ({ wakeMode: "direct-inject" }) as never;
47
- // The record identity and the marker MUST agree on garden/backend/native id — that
48
- // agreement is what SE-2 2d-3 now requires (a present marker alone is not enough). Built
49
- // from a fixed garden id (NOT mintMetaIdentity, which would generate a random gardenId that
50
- // never matches GARDEN — the old presence-only test silently ignored that mismatch).
51
- const liveIdentity = (): MetaIdentity => ({
52
- schemaVersion: 3,
53
- gardenId: GARDEN,
54
- backend: "claude-code",
55
- nativeSessionId: "n-a",
56
- cwd: "/tmp",
57
- model: null,
58
- transcriptPath: null,
59
- createdAt: "2026-06-14T01:00:00.000Z",
60
- recordUpdatedAt: "2026-06-14T01:00:00.000Z",
61
- });
62
- // The owner pid is a plausible one on purpose: these markers are injected straight
63
- // into the pure predicate, which never reads the field — but a fixture carrying
64
- // `ownerPid: 1` would model a marker no writer can mint and no reader honors
65
- // (isPlausibleOwnerPid, #53 A), and fixtures are read as claims about the world.
66
- const liveMarker = () =>
67
- ({
68
- gardenId: GARDEN,
69
- backend: "claude-code",
70
- nativeSessionId: "n-a",
71
- ownerPid: 4242,
72
- ownerStartKey: "x",
73
- ownerKind: "claude-code-cli",
74
- armProvenance: "session-start",
75
- updatedAt: "t",
76
- }) as never;
77
- // Same shape as liveMarker but with a drifted native id → does NOT match the record
78
- // identity ("n-a") → SE-2 2d-3 treats it as a stale/foreign marker (inactive receiver).
79
- const driftMarker = () =>
80
- ({
81
- gardenId: GARDEN,
82
- backend: "claude-code",
83
- nativeSessionId: "n-OTHER",
84
- ownerPid: 4242,
85
- ownerStartKey: "x",
86
- ownerKind: "claude-code-cli",
87
- armProvenance: "session-start",
88
- updatedAt: "t",
89
- }) as never;
90
-
91
- // ── PURE 0-call (injected facts/readers) ────────────────────────────────────
92
- {
93
- let calls = 0;
94
- const enqueue = () => {
95
- calls++;
96
- return "ENQUEUED";
97
- };
98
-
99
- // deliverable: self-fetch record + live marker → enqueue once.
100
- const okOut = guardedMailboxEnqueue(
101
- GARDEN,
102
- { readIdentity: liveIdentity, readReceiverMarker: liveMarker, capabilityFor: selfFetch },
103
- enqueue,
104
- );
105
- ok("deliverable → enqueue called exactly once", calls === 1);
106
- ok("deliverable → outcome carries the enqueue result", okOut.delivered === true && okOut.result === "ENQUEUED");
107
-
108
- // dead receiver (marker null) → 0-call.
109
- calls = 0;
110
- const deadOut = guardedMailboxEnqueue(
111
- GARDEN,
112
- { readIdentity: liveIdentity, readReceiverMarker: () => null, capabilityFor: selfFetch },
113
- enqueue,
114
- );
115
- ok("dead/absent receiver → enqueue NOT called (0-call)", calls === 0);
116
- ok(
117
- "dead receiver → delivered:false with a reason",
118
- deadOut.delivered === false && !deadOut.delivered && deadOut.reason.length > 0,
119
- );
120
-
121
- // direct-inject backend (pi) → 0-call even with a live marker (SE-1).
122
- calls = 0;
123
- guardedMailboxEnqueue(
124
- GARDEN,
125
- { readIdentity: liveIdentity, readReceiverMarker: liveMarker, capabilityFor: directInject },
126
- enqueue,
127
- );
128
- ok("direct-inject backend → enqueue NOT called (SE-1, 0-call)", calls === 0);
129
-
130
- // SE-2 2d-3: a present marker whose identity has DRIFTED (native session id mismatch) is a
131
- // stale/foreign marker — not THIS receiver — so presence alone does not deliver. 0-call.
132
- calls = 0;
133
- const driftOut = guardedMailboxEnqueue(
134
- GARDEN,
135
- {
136
- readIdentity: liveIdentity,
137
- readReceiverMarker: driftMarker,
138
- capabilityFor: selfFetch,
139
- },
140
- enqueue,
141
- );
142
- ok("marker identity drift (native id) → enqueue NOT called (SE-2 2d-3, 0-call)", calls === 0);
143
- ok("marker identity drift → delivered:false", driftOut.delivered === false);
144
-
145
- // no backing record → 0-call.
146
- calls = 0;
147
- guardedMailboxEnqueue(
148
- GARDEN,
149
- {
150
- readIdentity: () => {
151
- throw new Error("no record");
152
- },
153
- readReceiverMarker: liveMarker,
154
- capabilityFor: selfFetch,
155
- },
156
- enqueue,
157
- );
158
- ok("absent record → enqueue NOT called (0-call)", calls === 0);
159
- }
160
-
161
- // ── fact gathering ──────────────────────────────────────────────────────────
162
- {
163
- const facts = gatherMailboxDeliverabilityFacts(GARDEN, {
164
- readIdentity: liveIdentity,
165
- readReceiverMarker: liveMarker,
166
- capabilityFor: selfFetch,
167
- });
168
- ok("facts: recordBacked from a present record", facts.recordBacked === true);
169
- ok("facts: wakeMode from the capability registry", facts.wakeMode === "self-fetch");
170
- ok("facts: ownerAlive+watchArmed true from a present marker", facts.ownerAlive === true && facts.watchArmed === true);
171
-
172
- const absent = gatherMailboxDeliverabilityFacts(GARDEN, {
173
- readIdentity: () => {
174
- throw new Error("none");
175
- },
176
- readReceiverMarker: () => null,
177
- capabilityFor: selfFetch,
178
- });
179
- ok(
180
- "facts: absent record → recordBacked false, wakeMode undefined",
181
- absent.recordBacked === false && absent.wakeMode === undefined,
182
- );
183
- ok("facts: absent marker → ownerAlive+watchArmed false", absent.ownerAlive === false && absent.watchArmed === false);
184
-
185
- // A present-but-drifted marker (native id mismatch) is recordBacked (the record is fine)
186
- // but the receiver is inactive — the SE-2 2d-3 identity-match gate, not presence alone.
187
- const drift = gatherMailboxDeliverabilityFacts(GARDEN, {
188
- readIdentity: liveIdentity,
189
- readReceiverMarker: driftMarker,
190
- capabilityFor: selfFetch,
191
- });
192
- ok(
193
- "facts: drifted marker → recordBacked true but ownerAlive+watchArmed false",
194
- drift.recordBacked === true && drift.ownerAlive === false && drift.watchArmed === false,
195
- );
196
- }
197
-
198
- // ── TMPDIR SNAPSHOT (real enqueueMetaMessage) ───────────────────────────────
199
- function snapshot(dir: string): string {
200
- const h = createHash("sha256");
201
- const walk = (d: string) => {
202
- let entries: string[];
203
- try {
204
- entries = readdirSync(d).sort();
205
- } catch {
206
- return;
207
- }
208
- for (const name of entries) {
209
- const p = path.join(d, name);
210
- const st = statSync(p);
211
- h.update(`${p}\n`);
212
- if (st.isDirectory()) walk(p);
213
- else h.update(readFileSync(p));
214
- }
215
- };
216
- walk(dir);
217
- return h.digest("hex");
218
- }
219
-
220
- {
221
- const TMP = mkdtempSync(path.join(tmpdir(), "psa-mbguard-"));
222
- const sessionsDir = path.join(TMP, "meta-sessions");
223
- const mailboxDir = path.join(TMP, "meta-mailbox");
224
- const receiversDir = path.join(TMP, "meta-receivers");
225
- for (const d of [sessionsDir, mailboxDir, receiversDir]) mkdirSync(d, { recursive: true });
226
-
227
- // Seed a real claude-code record (recordBacked + self-fetch), but NO receiver
228
- // marker → the target is a terminated/absent receiver. A guarded send must refuse.
229
- const id = mintMetaIdentity({ backend: "claude-code", nativeSessionId: "n-snap", cwd: "/tmp" });
230
- writeFileSync(path.join(sessionsDir, `${id.gardenId}.meta.json`), serializeMetaIdentity(id));
231
-
232
- const deps = {
233
- readIdentity: (g: string) =>
234
- JSON.parse(readFileSync(path.join(sessionsDir, `${g}.meta.json`), "utf8")) as MetaIdentity,
235
- readReceiverMarker: (g: string) => readMetaReceiverMarker({ gardenId: g, receiversDir }),
236
- };
237
- const realEnqueue = () => enqueueMetaMessage({ gardenId: id.gardenId, body: "SNAP", sessionsDir, mailboxDir });
238
-
239
- const before = snapshot(mailboxDir);
240
- const refused = guardedMailboxEnqueue(id.gardenId, deps, realEnqueue);
241
- const after = snapshot(mailboxDir);
242
- ok("refused send → delivered:false", refused.delivered === false);
243
- ok("refused send → mailbox tree byte-identical (no .msg, no signal poke, no state)", before === after);
244
-
245
- // Now arm a live receiver marker (this process is the live owner) → deliverable.
246
- writeMetaReceiverMarker({
247
- gardenId: id.gardenId,
248
- backend: "claude-code",
249
- nativeSessionId: "n-snap",
250
- ownerPid: process.pid,
251
- armProvenance: "session-start",
252
- receiversDir,
253
- });
254
- const accepted = guardedMailboxEnqueue(id.gardenId, deps, realEnqueue);
255
- ok("active receiver → delivered:true", accepted.delivered === true);
256
- const msgs = readdirSync(path.join(mailboxDir, id.gardenId)).filter((f) => f.endsWith(".msg"));
257
- ok("active receiver → exactly one .msg written", msgs.length === 1);
258
- }
259
-
260
- // ── V2-ONLY WIRING: legacy MCP entwurf_send fallback is gone ───────────────
261
- // v2 mailbox delivery is covered by check-entwurf-v2-mailbox + decider gates; this
262
- // file keeps the guard primitive pinned for remaining/future conversational enqueue sites.
263
- const mcpSrc = readFileSync(path.join(REPO_DIR, "mcp", "entwurf-bridge", "src", "index.ts"), "utf8");
264
- ok("legacy MCP entwurf_send tool removed", !mcpSrc.includes('server.tool(\n\t"entwurf_send"'));
265
-
266
- console.log(`\ncheck-entwurf-mailbox-guard: ${passed} checks passed`);