@junghanacs/entwurf 0.17.2 → 0.18.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 (44) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +127 -0
  3. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -9
  4. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +76 -0
  5. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +9 -6
  6. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +11 -3
  7. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +66 -0
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +10 -1
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +8 -2
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +25 -13
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +6 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +38 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +64 -4
  14. package/mcp/entwurf-bridge/src/index.ts +16 -8
  15. package/package.json +3 -2
  16. package/pi-extensions/lib/entwurf-deliverability.ts +104 -0
  17. package/pi-extensions/lib/entwurf-fact-provider.ts +20 -6
  18. package/pi-extensions/lib/entwurf-facts.ts +52 -3
  19. package/pi-extensions/lib/entwurf-peer-observe.ts +77 -0
  20. package/pi-extensions/lib/entwurf-peers-render.ts +12 -1
  21. package/pi-extensions/lib/entwurf-v2-decider.ts +26 -8
  22. package/pi-extensions/lib/entwurf-v2-production.ts +32 -12
  23. package/pi-extensions/lib/entwurf-v2-surface.ts +6 -0
  24. package/pi-extensions/lib/meta-session.ts +44 -0
  25. package/pi-extensions/meta-bridge-hook.ts +72 -3
  26. package/run.sh +21 -0
  27. package/scripts/check-bridge-delivery.ts +42 -1
  28. package/scripts/check-entwurf-fact-provider.ts +22 -1
  29. package/scripts/check-entwurf-facts.ts +17 -2
  30. package/scripts/check-entwurf-peers-surface.ts +20 -2
  31. package/scripts/check-entwurf-self-address.ts +20 -2
  32. package/scripts/check-entwurf-v2-decider.ts +6 -3
  33. package/scripts/check-entwurf-v2-matrix.ts +10 -5
  34. package/scripts/check-entwurf-v2-production.ts +72 -1
  35. package/scripts/check-gate-qualification.ts +1 -0
  36. package/scripts/check-meta-hook-session-switch.ts +560 -0
  37. package/scripts/meta-bridge-doctor.sh +29 -4
  38. package/scripts/mutants/meta-hook-session-switch.json +223 -0
  39. package/scripts/raw-claude-session-switch/README.md +170 -0
  40. package/scripts/smoke-acp-cortex-live.ts +22 -2
  41. package/scripts/smoke-acp-v2-send-live.ts +23 -2
  42. package/scripts/smoke-entwurf-chain-live.ts +85 -13
  43. package/scripts/smoke-entwurf-v2-matrix-live.ts +23 -1
  44. package/scripts/smoke-mux-fresh-call-live.ts +13 -3
@@ -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
@@ -814,6 +814,7 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
814
814
  "copilot-receive": 18,
815
815
  "fresh-cut": 1,
816
816
  "meta-facts": 4,
817
+ "meta-hook-session-switch": 17,
817
818
  "meta-identity": 4,
818
819
  "meta-retire": 3,
819
820
  "mux-boundary": 14,