@junghanacs/entwurf 0.51.0 → 0.52.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.
@@ -82,8 +82,8 @@
82
82
  {
83
83
  "name": "harness-setup.js",
84
84
  "source": "pi/pi-durable/src/experimental/durable/harness-setup.ts",
85
- "blob": "e8561809321ce8833a13e3757c172f4e38faabcc",
86
- "sha256": "eb1158030a8ab95187a4fdd5e7b90816a7995e5f446d09a8184d4f5007e7ac5f"
85
+ "blob": "5a799bfbfbbedbec229761b26f639fbe2702b623",
86
+ "sha256": "70b50044ab14c1293a2ace53b375dc48ad940ea9eeacc131d498ccb1ce040617"
87
87
  },
88
88
  {
89
89
  "name": "prompt.js",
@@ -100,8 +100,8 @@
100
100
  {
101
101
  "name": "subagent.js",
102
102
  "source": "pi/pi-durable/src/experimental/durable/subagent.ts",
103
- "blob": "e842d8a6b8dc55825d0381d803b6d6496b205c19",
104
- "sha256": "c9931fabd60f5fbf75220e896a57e5c8b131b4936887bf6f9660d50d01709f3a"
103
+ "blob": "8c18e24a030f0b701167f15a33579195303f5ac5",
104
+ "sha256": "208618441e8e81813de2f44fa9ad5d5bdcdbb78a754679bb7a93d82480e7bece"
105
105
  },
106
106
  {
107
107
  "name": "session-name.js",
@@ -138,18 +138,18 @@
138
138
  ]
139
139
  },
140
140
  "sdk": {
141
- "comment": "The published library set the carrier runs against, derived from the 1.5.0 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare (1.5.0 adds pi-schema, declared by chord, pi-agent-core, pi-ai and pi-durable). direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.5.0 pins). Each is an exact production dependency in package.json. Since 1.3.0 the members' own edges are exact too (upstream #10865), and the installed physical edges are still what must equal these versions.",
141
+ "comment": "The published library set the carrier runs against, derived from the 1.5.2 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare (1.5.0 adds pi-schema, declared by chord, pi-agent-core, pi-ai and pi-durable). direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.5.2 pins). Each is an exact production dependency in package.json. Since 1.3.0 the members' own edges are exact too (upstream #10865), and the installed physical edges are still what must equal these versions.",
142
142
  "members": {
143
- "@earendil-works/chord": "1.5.0",
144
- "@earendil-works/pi-agent-core": "1.5.0",
145
- "@earendil-works/pi-ai": "1.5.0",
146
- "@earendil-works/pi-codemode": "1.5.0",
147
- "@earendil-works/pi-coding-agent": "1.5.0",
148
- "@earendil-works/pi-durable": "1.5.0",
149
- "@earendil-works/pi-mcp": "1.5.0",
150
- "@earendil-works/pi-schema": "1.5.0",
151
- "@earendil-works/pi-telemetry": "1.5.0",
152
- "@earendil-works/pi-tui": "1.5.0"
143
+ "@earendil-works/chord": "1.5.2",
144
+ "@earendil-works/pi-agent-core": "1.5.2",
145
+ "@earendil-works/pi-ai": "1.5.2",
146
+ "@earendil-works/pi-codemode": "1.5.2",
147
+ "@earendil-works/pi-coding-agent": "1.5.2",
148
+ "@earendil-works/pi-durable": "1.5.2",
149
+ "@earendil-works/pi-mcp": "1.5.2",
150
+ "@earendil-works/pi-schema": "1.5.2",
151
+ "@earendil-works/pi-telemetry": "1.5.2",
152
+ "@earendil-works/pi-tui": "1.5.2"
153
153
  },
154
154
  "direct": {
155
155
  "proper-lockfile": "4.1.2"
@@ -50,7 +50,11 @@ export interface SenderEnvelope {
50
50
  * renders nothing (etiquette marker, not transport contract).
51
51
  */
52
52
  export function formatSenderInfoBlock(sender: SenderEnvelope, wantsReply = false): string {
53
- return `\n\n<sender_info>${JSON.stringify({
53
+ const replyGuidance =
54
+ sender.replyable === true
55
+ ? "\n\n[entwurf: When a reply is requested, send the result via entwurf_v2 to the requested recipient (default: sender_info.sessionId); a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment.]"
56
+ : "";
57
+ return `${replyGuidance}\n\n<sender_info>${JSON.stringify({
54
58
  sessionId: sender.sessionId,
55
59
  agentId: sender.agentId,
56
60
  cwd: sender.cwd,
@@ -79,7 +79,7 @@ export function formatMetaMailboxBody(sender: MailboxSenderEnvelope, message: st
79
79
  // SENDING host, so it is neither abbreviated against this host's HOME nor shown as a local path.
80
80
  const remote = !replyable ? sender.remote : undefined;
81
81
  const sessionLine = replyable
82
- ? `${sender.sessionId} (${kind}replyable — reply via entwurf_v2 to this sessionId, intent=fire-and-forget)`
82
+ ? `${sender.sessionId} (${kind}replyable — when a reply is requested, send the result via entwurf_v2 (intent=fire-and-forget) to the requested recipient, defaulting to this sessionId; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment)`
83
83
  : remote
84
84
  ? `${sender.sessionId} (remote via ${remote.via}; ${remote.replyRoute === "declared" ? "reply route declared, not probed" : "no reply route declared"})`
85
85
  : isMeta
@@ -349,7 +349,7 @@ export interface PiDurableRoots {
349
349
  // ---------------------------------------------------------------------------
350
350
  // Durable surface, typed NARROWLY and locally — read at pi-durable 1.0.2 @cd32f77 (#129,
351
351
  // `harness/types.ts`; historical coordinates, not re-anchored here). The current supply is the
352
- // carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.5.0 SDK set
352
+ // carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.5.2 SDK set
353
353
  // `pi/pi-durable/overlay/upstream-pin.json` names (origin `abe508e`). `[read 2026-10-13]` at
354
354
  // v1.2.0 one shape typed below moved: `ToolExecutionResult.content` is now `output`, beside the new
355
355
  // `structuredOutput` (`harness/types.ts`, its CHANGELOG's breaking changes). An omitted `output`
package/run.sh CHANGED
@@ -1156,8 +1156,10 @@ check_control_send_receipt() {
1156
1156
  # test titles `run_vitest` emits. Narrow file filter, same reason as check-entwurf-v2-send.
1157
1157
  # It stays inside check:contracts even though the discovery door also runs this file — see
1158
1158
  # check-release-gate-outcomes: a gate a mutant names must be reachable outside qualification.
1159
- section "control-socket acceptance boundary (mutant execution coordinate)"
1160
- run_vitest pi-extensions/lib/control-send-receipt.test.ts
1159
+ # #159 adds the three delivery-input seams here as a shared mutant coordinate; discovery remains
1160
+ # the beside glob. This reuses the existing gate instead of adding a second help/catalog surface.
1161
+ section "control-socket acceptance and received guidance (mutant execution coordinate)"
1162
+ run_vitest pi-extensions/lib/control-send-receipt.test.ts pi-extensions/entwurf-control.test.ts pi-extensions/lib/entwurf-v2-mailbox.test.ts pi-extensions/lib/entwurf-v2-native-push.test.ts
1161
1163
  }
1162
1164
 
1163
1165
  check_control_retire() {
@@ -2502,15 +2504,16 @@ assert.deepEqual(floorGaps, [],
2502
2504
  // The published-scope regions' own oracle (see PUBLISHED-SCOPE REGIONS above). Checked after the
2503
2505
  // four surfaces so a stranded closure row is still attributed to the claim above.
2504
2506
  // PUBLISHED_ARTIFACT is the one published pair a region may name, copied from the artifact, not from
2505
- // this checkout. `[측정 2026-11-03]` the global install of @junghanacs/entwurf@0.50.0 whose bytes #155
2506
- // HOP1 accepted against the registry tarball declares exactly these nine @earendil-works dependencies
2507
- // in its package.json (sha256 20c572534fcfc03e8f89890a0853a25048166f7c998e433945289a5d20ee1234).
2507
+ // this checkout. Read 2026-11-11 from package/package.json in the preserved registry-accepted
2508
+ // 0.51.0 release TGZ (TGZ SHA256 04fcce26d0a6dec8b8bb9a7f14a843eee0defb313aedafa34080600853d36e4d;
2509
+ // raw manifest SHA256 b5043ba8edde6f2b9d13052a98c3cd808a3b30f1023da41b22ee022470a96120).
2510
+ // Its ten @earendil-works production dependencies are exact 1.5.0, independent of this checkout.
2508
2511
  const PUBLISHED_ARTIFACT = {
2509
- version: '0.50.0',
2512
+ version: '0.51.0',
2510
2513
  dependencies: {
2511
- '@earendil-works/chord': '1.4.0', '@earendil-works/pi-agent-core': '1.4.0', '@earendil-works/pi-ai': '1.4.0',
2512
- '@earendil-works/pi-codemode': '1.4.0', '@earendil-works/pi-coding-agent': '1.4.0', '@earendil-works/pi-durable': '1.4.0',
2513
- '@earendil-works/pi-mcp': '1.4.0', '@earendil-works/pi-telemetry': '1.4.0', '@earendil-works/pi-tui': '1.4.0',
2514
+ '@earendil-works/chord': '1.5.0', '@earendil-works/pi-agent-core': '1.5.0', '@earendil-works/pi-ai': '1.5.0',
2515
+ '@earendil-works/pi-codemode': '1.5.0', '@earendil-works/pi-coding-agent': '1.5.0', '@earendil-works/pi-durable': '1.5.0',
2516
+ '@earendil-works/pi-mcp': '1.5.0', '@earendil-works/pi-schema': '1.5.0', '@earendil-works/pi-telemetry': '1.5.0', '@earendil-works/pi-tui': '1.5.0',
2514
2517
  },
2515
2518
  };
2516
2519
  const publishedSdk = [...new Set(Object.values(PUBLISHED_ARTIFACT.dependencies))];
@@ -4129,7 +4132,7 @@ check_pack_win32_carrier() {
4129
4132
  # which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
4130
4133
  # below so a future suffix change cannot pass vacuously.)
4131
4134
  pack_install_leaked_pi() {
4132
- grep '^@earendil-works+' | grep -Ev '@1\.5\.0(_|$)' || true
4135
+ grep '^@earendil-works+' | grep -Ev '@1\.5\.2(_|$)' || true
4133
4136
  }
4134
4137
 
4135
4138
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
@@ -4146,18 +4149,18 @@ check_pack_pin_matcher() {
4146
4149
  # Cell 1 — the version boundary. Every measured peer-hash shape is a fixture: the
4147
4150
  # 0.84.4-era `_@modelcontextprotocol+sdk@…`, the `_ws@8.21.3` observed on a real
4148
4151
  # 0.85.1 tree, and `[측정 2026-09-20]` the combined suffix a 0.86.0 install carries.
4149
- # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`1.5.0-beta.1`,
4152
+ # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`1.5.2-beta.1`,
4150
4153
  # the prerelease shape that an unanchored match would bless) and an off-pin version.
4151
4154
  matcher_probe=$(printf '%s\n' \
4152
- '@earendil-works+pi-ai@1.5.0' \
4153
- '@earendil-works+pi-ai@1.5.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
4154
- '@earendil-works+pi-ai@1.5.0_ws@8.21.3' \
4155
- '@earendil-works+pi-ai@1.5.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
4156
- '@earendil-works+pi-ai@1.5.0-beta.1' \
4155
+ '@earendil-works+pi-ai@1.5.2' \
4156
+ '@earendil-works+pi-ai@1.5.2_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
4157
+ '@earendil-works+pi-ai@1.5.2_ws@8.21.3' \
4158
+ '@earendil-works+pi-ai@1.5.2_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
4159
+ '@earendil-works+pi-ai@1.5.2-beta.1' \
4157
4160
  '@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
4158
- if [ "$matcher_probe" != '@earendil-works+pi-ai@1.5.0-beta.1
4161
+ if [ "$matcher_probe" != '@earendil-works+pi-ai@1.5.2-beta.1
4159
4162
  @earendil-works+pi-agent-core@0.87.1' ]; then
4160
- fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.5.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.5.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
4163
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.5.2-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.5.2 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
4161
4164
  return 1
4162
4165
  fi
4163
4166
 
@@ -4165,9 +4168,9 @@ check_pack_pin_matcher() {
4165
4168
  # (0.85.0 onward, still true at 0.99.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
4166
4169
  # back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
4167
4170
  matcher_probe=$(printf '%s\n' \
4168
- '@earendil-works+chord@1.5.0' \
4171
+ '@earendil-works+chord@1.5.2' \
4169
4172
  '@earendil-works+chord@0.87.1' \
4170
- '@earendil-works+pi-ai@1.5.0' | pack_install_leaked_pi)
4173
+ '@earendil-works+pi-ai@1.5.2' | pack_install_leaked_pi)
4171
4174
  if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
4172
4175
  fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it was a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and pi-protocol — `[측정 2026-09-20]` at 0.86.0, re-measured `[측정 2026-09-22]` at 0.87.0, `[측정 2026-09-23]` at 0.87.1 and `[측정 2026-09-30]` at 0.99.1, where pi-coding-agent adds pi-codemode and pi-mcp beside them; `[측정 2026-10-13]` at 1.2.0 pi-durable alone declares it) — got: ${matcher_probe:-<nothing leaked>}"
4173
4176
  return 1
@@ -5089,16 +5092,16 @@ _check_pack_install_impl() {
5089
5092
  local install_log
5090
5093
  install_log=$(cd "$tmp" && pnpm add \
5091
5094
  "$tgz_path" \
5092
- "@earendil-works/pi-ai@1.5.0" \
5093
- "@earendil-works/pi-coding-agent@1.5.0" \
5094
- "@earendil-works/pi-tui@1.5.0" \
5095
- "@earendil-works/pi-agent-core@1.5.0" \
5096
- "@earendil-works/pi-telemetry@1.5.0" \
5097
- "@earendil-works/chord@1.5.0" \
5098
- "@earendil-works/pi-codemode@1.5.0" \
5099
- "@earendil-works/pi-mcp@1.5.0" \
5100
- "@earendil-works/pi-durable@1.5.0" \
5101
- "@earendil-works/pi-schema@1.5.0" \
5095
+ "@earendil-works/pi-ai@1.5.2" \
5096
+ "@earendil-works/pi-coding-agent@1.5.2" \
5097
+ "@earendil-works/pi-tui@1.5.2" \
5098
+ "@earendil-works/pi-agent-core@1.5.2" \
5099
+ "@earendil-works/pi-telemetry@1.5.2" \
5100
+ "@earendil-works/chord@1.5.2" \
5101
+ "@earendil-works/pi-codemode@1.5.2" \
5102
+ "@earendil-works/pi-mcp@1.5.2" \
5103
+ "@earendil-works/pi-durable@1.5.2" \
5104
+ "@earendil-works/pi-schema@1.5.2" \
5102
5105
  --ignore-workspace --ignore-scripts 2>&1) || {
5103
5106
  fail "[check-pack-install] pnpm add failed:"
5104
5107
  echo "$install_log" | tail -10 | sed 's/^/ /' >&2
@@ -5107,17 +5110,17 @@ _check_pack_install_impl() {
5107
5110
 
5108
5111
  # A pin is a wish until the resolved tree is read back. Assert it: EVERY
5109
5112
  # @earendil-works package present — direct or transitive, top level or nested,
5110
- # chord included — must be the pinned 0.86.0. Anything else means an unpinned caret
5113
+ # chord included — must be the pinned 1.5.2. Anything else means an unpinned caret
5111
5114
  # floated and the rest of this gate would be exercising a runtime nobody verified,
5112
- # while still printing "pinned pi 0.86.0". Fail loud instead of proving the wrong floor.
5115
+ # while still printing a pinned runtime. Fail loud instead of proving the wrong floor.
5113
5116
  local leaked_pi
5114
5117
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
5115
5118
  if [ -n "$leaked_pi" ]; then
5116
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.5.0):"
5119
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.5.2):"
5117
5120
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
5118
5121
  return 1
5119
5122
  fi
5120
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.5.0 (chord, pi-durable and pi-schema included)"
5123
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.5.2 (chord, pi-durable and pi-schema included)"
5121
5124
 
5122
5125
  # Resolve the installed package.json and confirm pi.extensions
5123
5126
  # arrived intact. If pi.extensions is empty or missing, the
@@ -92,7 +92,7 @@ function assertRailSemantics(tag: string, longDescRaw: string, intentDescRaw: st
92
92
  );
93
93
  ok(
94
94
  `${tag} — long description names the THIRD result for an unsupported citizen`,
95
- /mailbox-undeliverable/.test(longDesc) && /THIRD RESULT/.test(longDesc),
95
+ /mailbox-undeliverable/.test(longDesc) && /target without a live socket, the THIRD RESULT/.test(longDesc),
96
96
  );
97
97
  ok(
98
98
  `${tag} — intent param names a rail's reject, not only its allow cell`,
@@ -139,30 +139,16 @@ function assertRailSemantics(tag: string, longDescRaw: string, intentDescRaw: st
139
139
  * whose subject (two distinct OWNED rejects) left with the `owned-outcome` intent.
140
140
  *
141
141
  * After the visible-first cut a dormant socket-domain citizen rejects delivery by v2, and
142
- * these four strings are the only place a caller learns that BEFORE spending a dispatch. So each
143
- * one must, on its own:
144
- * - name the reason it will actually get back (`dormant-fire-forget-unsupported`);
145
- * - name the intent that used to answer there (`owned-outcome`) — silently deleting it leaves
146
- * an operator who read last week's docs with no way to find out what happened;
147
- * - say it was `withdrawn`, ADJACENTLY to that name. Presence alone is not enough: naming a
148
- * retired intent without the retirement verb beside it reads as an offer, which is exactly
149
- * the "advertised as selectable" failure this claim exists to forbid;
150
- * - carry NEITHER retired advertisement form (`owned-outcome = …` enum styling, or the old
151
- * "DORMANT socket-domain citizen … never auto-converted" sentence that described picking it).
152
- *
153
- * ORDER IS LOAD-BEARING, and not for readability. `ok` throws on the FIRST failure, and the kill
154
- * signature is read off the failing line — so the `[QK:…]` label must sit on the assertion this
155
- * claim's mutant reaches FIRST. That mutant drops the reason AND the withdrawal in one edit; with
156
- * the reason check leading, an UNLABELLED line would throw first and the kill would read as
157
- * WRONG-REASON. The adjacency check therefore goes first and carries the only token; the other two
158
- * run unlabelled behind it — still real assertions, just not this claim's signature.
142
+ * both caller-facing strings must independently name the actual dormant rejection and say that
143
+ * delivery never launches or resumes a process. Neither may advertise the retired offer. History
144
+ * belongs in the issue and literal amendment, not in the current caller contract. The labelled
145
+ * assertion comes first because qualification reads the first failing line as the kill signature.
159
146
  */
160
147
  function assertDormantHonesty(tag: string, rawText: string): void {
161
148
  const text = rawText;
162
- const at = text.indexOf("owned-outcome");
163
149
  ok(
164
- `${tag} — names the withdrawn intent owned-outcome and says "withdrawn" beside it (not as an offer) [QK:V2SURF-DORMANT-HONESTY]`,
165
- at >= 0 && /withdrawn/.test(text.slice(at, at + 200)),
150
+ `${tag} — delivery never launches or resumes a process and names the dormant rejection [QK:V2SURF-DORMANT-HONESTY]`,
151
+ /This delivery verb never launches or resumes a process/.test(text) && /dormant-fire-forget-unsupported/.test(text),
166
152
  );
167
153
  ok(`${tag} — names the reason a dormant target actually returns`, /dormant-fire-forget-unsupported/.test(text));
168
154
  ok(
@@ -3460,7 +3460,7 @@ let manifestCount: number;
3460
3460
  "pi-retire-request": 6,
3461
3461
  "retirement-event": 16,
3462
3462
  "control-retire": 9,
3463
- "control-send-receipt": 20,
3463
+ "control-send-receipt": 23,
3464
3464
  "codex-native": 95,
3465
3465
  "native-push-paused": 1,
3466
3466
  "chain-terminus-publication": 1,
@@ -900,7 +900,10 @@ async function cell(): Promise<void> {
900
900
  hostReceiver.ownerKind === "pi-durable-host";
901
901
  ok(
902
902
  `[QK:${QK}-SENDER-REPLYABILITY] the host's receiver marker is armed and owned by the spawned host, and the landed body says what that predicts: replyable (armed ${hostArmed})`,
903
- hostArmed && body.includes(`session: ${hostGid} (meta-session, replyable — reply via entwurf_v2`),
903
+ hostArmed &&
904
+ body.includes(
905
+ `session: ${hostGid} (meta-session, replyable — when a reply is requested, send the result via entwurf_v2`,
906
+ ),
904
907
  );
905
908
  const hostRecords = fs
906
909
  .readdirSync(dirs.sessions)
@@ -452,14 +452,20 @@ export function codexSourceToolReceipts(thread: unknown): SourceToolReceipt[] {
452
452
  }
453
453
 
454
454
  function exactSenderInfoSuffix(text: string, nonce: string): string | null {
455
- const prefix = `${nonce}\n\n<sender_info>`;
455
+ // Independent expected frame: never import the producer's guidance as the receipt oracle.
456
+ const guidance =
457
+ "\n\n[entwurf: When a reply is requested, send the result via entwurf_v2 to the requested recipient (default: sender_info.sessionId); a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment.]";
458
+ const open = "<sender_info>";
456
459
  const suffix = "</sender_info>";
457
- if (!text.startsWith(prefix) || !text.endsWith(suffix)) return null;
458
- const json = text.slice(prefix.length, -suffix.length);
459
- if (json.includes("\n") || json.includes("<sender_info>")) return null;
460
+ const at = text.lastIndexOf(open);
461
+ if (at < 0 || !text.endsWith(suffix)) return null;
462
+ const json = text.slice(at + open.length, -suffix.length);
463
+ if (json.includes("\n") || json.includes(open)) return null;
460
464
  try {
461
- const sender = JSON.parse(json) as { sessionId?: unknown };
462
- return typeof sender.sessionId === "string" ? sender.sessionId : null;
465
+ const sender = JSON.parse(json) as { sessionId?: unknown; replyable?: unknown };
466
+ const prefix = `${nonce}${sender.replyable === true ? guidance : ""}\n\n${open}`;
467
+ if (!text.startsWith(prefix) || !text.endsWith(suffix)) return null;
468
+ return at === prefix.length - open.length && typeof sender.sessionId === "string" ? sender.sessionId : null;
463
469
  } catch {
464
470
  return null;
465
471
  }
@@ -6,8 +6,8 @@
6
6
  "claim": "GUIDANCE-REVIEWED-REVISION",
7
7
  "title": "a valid Root revision advances beyond the independently reviewed payload revision while every history entry and decoded tool string remains valid",
8
8
  "subject": "ENTWURF.json",
9
- "find": ["\t\"revision\": 19,", "\t\"history\": ["],
10
- "replace": ["\t\"revision\": 20,", "\t\"history\": ["],
9
+ "find": ["\t\"revision\": 20,", "\t\"history\": ["],
10
+ "replace": ["\t\"revision\": 21,", "\t\"history\": ["],
11
11
  "gate": ["bash", "run.sh", "check-agent-guidance-beside"],
12
12
  "timeoutSeconds": 120,
13
13
  "signature": "[QK:GUIDANCE-REVIEWED-REVISION]",
@@ -715,9 +715,9 @@
715
715
  "claim": "CODEX-LIVE-RAW-CALLBACK-REQUIRED",
716
716
  "title": "a Pi callback with arbitrary bytes after its sender envelope is accepted as exact",
717
717
  "subject": "scripts/lib/codex-fresh-source-receipts.ts",
718
- "find": ["\tif (!text.startsWith(prefix) || !text.endsWith(suffix)) return null;"],
718
+ "find": ["\tconst suffix = \"</sender_info>\";"],
719
719
  "replace": [
720
- "\tif (!text.startsWith(prefix)) return null;\n\tconst suffixAt = text.indexOf(suffix, prefix.length);\n\tif (suffixAt < 0) return null;\n\ttext = text.slice(0, suffixAt + suffix.length); // mutant: discard callback suffix"
720
+ "\tconst suffix = \"</sender_info>\";\n\tconst suffixAt = text.indexOf(suffix);\n\tif (suffixAt >= 0) text = text.slice(0, suffixAt + suffix.length); // mutant: discard callback suffix"
721
721
  ],
722
722
  "gate": ["bash", "run.sh", "check-mux-fresh-call"],
723
723
  "timeoutSeconds": 180,
@@ -11,7 +11,8 @@
11
11
  "gate": ["bash", "run.sh", "check-control-send-receipt"],
12
12
  "timeoutSeconds": 600,
13
13
  "signature": "[QK:SEND-RECEIPT-IDLE-IS-SENT]",
14
- "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts"
14
+ "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts",
15
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
15
16
  },
16
17
  {
17
18
  "claim": "SEND-RECEIPT-BUSY-STEER-IS-QUEUED",
@@ -22,7 +23,8 @@
22
23
  "gate": ["bash", "run.sh", "check-control-send-receipt"],
23
24
  "timeoutSeconds": 600,
24
25
  "signature": "[QK:SEND-RECEIPT-BUSY-STEER-IS-QUEUED]",
25
- "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts"
26
+ "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts",
27
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
26
28
  },
27
29
  {
28
30
  "claim": "SEND-RECEIPT-BUSY-FOLLOWUP-IS-QUEUED",
@@ -33,7 +35,8 @@
33
35
  "gate": ["bash", "run.sh", "check-control-send-receipt"],
34
36
  "timeoutSeconds": 600,
35
37
  "signature": "[QK:SEND-RECEIPT-BUSY-FOLLOWUP-IS-QUEUED]",
36
- "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts"
38
+ "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts",
39
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
37
40
  },
38
41
  {
39
42
  "claim": "SEND-RECEIPT-UNKNOWN-IS-ACCEPTED-NOT-SENT",
@@ -44,7 +47,8 @@
44
47
  "gate": ["bash", "run.sh", "check-control-send-receipt"],
45
48
  "timeoutSeconds": 600,
46
49
  "signature": "[QK:SEND-RECEIPT-UNKNOWN-IS-ACCEPTED-NOT-SENT]",
47
- "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts"
50
+ "signatureSource": "pi-extensions/lib/control-send-receipt.test.ts",
51
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
48
52
  },
49
53
  {
50
54
  "claim": "SEND-BOUNDARY-CARRIED",
@@ -194,7 +198,7 @@
194
198
  "claim": "PI-CORE-PIN-TRACKS-FLOOR",
195
199
  "title": "the queue oracle's pi-agent-core pin drifts off the floor the rest of the pi runtime is pinned to, so the oracle keeps answering honestly — about a pi nobody in this repo runs",
196
200
  "subject": "package.json",
197
- "find": ["\"@earendil-works/pi-agent-core\": \"1.5.0\","],
201
+ "find": ["\"@earendil-works/pi-agent-core\": \"1.5.2\","],
198
202
  "replace": ["\"@earendil-works/pi-agent-core\": \"0.87.1\","],
199
203
  "gate": ["bash", "run.sh", "check-dep-versions"],
200
204
  "timeoutSeconds": 300,
@@ -240,15 +244,51 @@
240
244
  "title": "the caller-facing outcome list drops the native-push rail, so a caller reading the one blurb it reads before dispatching is told the control rail's vocabulary covers a rail that answers with a direct injection instead",
241
245
  "subject": "ENTWURF.json",
242
246
  "find": [
243
- "native-push INJECTION, a rejection, or lock-dirty. This receipt is not the colleague's reply or evidence of reading. EXISTING targets only; discover with "
247
+ "native-push INJECTION, a rejection, or lock-dirty. A receipt is not a reply or proof of reading. A local final is not delivery:"
244
248
  ],
245
249
  "replace": [
246
- "a rejection, or lock-dirty. This receipt is not the colleague's reply or evidence of reading. EXISTING targets only; discover with "
250
+ "a rejection, or lock-dirty. A receipt is not a reply or proof of reading. A local final is not delivery:"
247
251
  ],
248
252
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
249
253
  "timeoutSeconds": 300,
250
254
  "signature": "[QK:SEND-OUTCOME-LIST-NAMES-EVERY-RAIL]",
251
255
  "signatureSource": "scripts/check-entwurf-v2-surface.ts"
256
+ },
257
+ {
258
+ "claim": "PI-RECEIVED-REPLY-GUIDANCE",
259
+ "title": "the Pi receiver omits guidance for a replyable sender, so a local final can be mistaken for a reply",
260
+ "subject": "pi-extensions/lib/entwurf-control-rpc.ts",
261
+ "find": ["sender.replyable === true"],
262
+ "replace": ["sender.replyable === false"],
263
+ "gate": ["bash", "run.sh", "check-control-send-receipt"],
264
+ "timeoutSeconds": 300,
265
+ "signature": "[QK:PI-RECEIVED-REPLY-GUIDANCE]",
266
+ "signatureSource": "pi-extensions/entwurf-control.test.ts",
267
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
268
+ },
269
+ {
270
+ "claim": "MAILBOX-RECEIVED-REPLY-GUIDANCE",
271
+ "title": "the mailbox receiver's guidance drops the local-final boundary",
272
+ "subject": "pi-extensions/lib/meta-mailbox-body.ts",
273
+ "find": ["a local final is not delivery."],
274
+ "replace": ["a local final is delivery."],
275
+ "gate": ["bash", "run.sh", "check-control-send-receipt"],
276
+ "timeoutSeconds": 300,
277
+ "signature": "[QK:MAILBOX-RECEIVED-REPLY-GUIDANCE]",
278
+ "signatureSource": "pi-extensions/lib/entwurf-v2-mailbox.test.ts",
279
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
280
+ },
281
+ {
282
+ "claim": "NATIVE-PUSH-RECEIVED-REPLY-GUIDANCE",
283
+ "title": "the native-push receiver's guidance drops the no-send boundary",
284
+ "subject": "pi-extensions/lib/meta-mailbox-body.ts",
285
+ "find": ["Honor routing and no-send instructions;"],
286
+ "replace": ["Ignore routing and no-send instructions;"],
287
+ "gate": ["bash", "run.sh", "check-control-send-receipt"],
288
+ "timeoutSeconds": 300,
289
+ "signature": "[QK:NATIVE-PUSH-RECEIVED-REPLY-GUIDANCE]",
290
+ "signatureSource": "pi-extensions/lib/entwurf-v2-native-push.test.ts",
291
+ "build": { "argv": ["bash", "scripts/build-bridge.sh"], "output": "mcp/entwurf-bridge/dist" }
252
292
  }
253
293
  ]
254
294
  }
@@ -6,8 +6,8 @@
6
6
  "claim": "PACK-INSTALL-PIN-MATCHER-BOUNDED",
7
7
  "title": "the pin-leak matcher loses its version boundary and blesses substring lookalikes \u2014 @0.85.10 reads as the pinned 0.85.1 and the gate announces a verified tree it never verified (independent review, 2026-08-25)",
8
8
  "subject": "run.sh",
9
- "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.5\\.0(_|$)' || true"],
10
- "replace": [" grep '^@earendil-works+' | grep -v '@1\\.5\\.0' || true"],
9
+ "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.5\\.2(_|$)' || true"],
10
+ "replace": [" grep '^@earendil-works+' | grep -v '@1\\.5\\.2' || true"],
11
11
  "gate": ["bash", "run.sh", "check-pack-pin-matcher"],
12
12
  "timeoutSeconds": 120,
13
13
  "signature": "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED]",
@@ -17,8 +17,8 @@
17
17
  "claim": "PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE",
18
18
  "title": "the pin-leak matcher narrows back to the pi-* families and goes blind to a non-pi member of the same runtime closure \u2014 @earendil-works/chord (new in pi 0.85.0, a runtime dependency of pi-coding-agent/pi-agent-core/pi-client/pi-protocol) floats unpinned while the gate prints a verified pin (measured 2026-09-06 on a real 0.85.1 install tree)",
19
19
  "subject": "run.sh",
20
- "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.5\\.0(_|$)' || true"],
21
- "replace": [" grep '^@earendil-works+pi-' | grep -Ev '@1\\.5\\.0(_|$)' || true"],
20
+ "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.5\\.2(_|$)' || true"],
21
+ "replace": [" grep '^@earendil-works+pi-' | grep -Ev '@1\\.5\\.2(_|$)' || true"],
22
22
  "gate": ["bash", "run.sh", "check-pack-pin-matcher"],
23
23
  "timeoutSeconds": 120,
24
24
  "signature": "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE]",
@@ -7,10 +7,10 @@
7
7
  "title": "one row of the pnpm workspace closure is left on the previous floor while every other surface moved — the shape a version bump actually fails in. Nothing else reads that file, so the stranded row surfaces only at install time, in a different command, far from the change that stranded it",
8
8
  "subject": "pnpm-workspace.yaml",
9
9
  "find": [
10
- " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0 || 1.4.0 || 1.5.0'"
10
+ " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0 || 1.4.0 || 1.5.0 || 1.5.2'"
11
11
  ],
12
12
  "replace": [
13
- " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0 || 1.4.0'"
13
+ " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0 || 1.4.0 || 1.5.0'"
14
14
  ],
15
15
  "gate": ["bash", "run.sh", "check-dep-versions"],
16
16
  "timeoutSeconds": 300,
@@ -21,8 +21,8 @@
21
21
  "claim": "PI-SDK-PRODUCTION-SET",
22
22
  "title": "the durable library's production pin falls one release behind the SDK set the pin declares, so the carrier would bind a pi-durable nobody verified it against",
23
23
  "subject": "package.json",
24
- "find": ["\"@earendil-works/pi-durable\": \"1.5.0\","],
25
- "replace": ["\"@earendil-works/pi-durable\": \"1.4.0\","],
24
+ "find": ["\"@earendil-works/pi-durable\": \"1.5.2\","],
25
+ "replace": ["\"@earendil-works/pi-durable\": \"1.5.0\","],
26
26
  "gate": ["bash", "run.sh", "check-dep-versions"],
27
27
  "timeoutSeconds": 300,
28
28
  "signature": "[QK:PI-SDK-PRODUCTION-SET]",
@@ -33,7 +33,7 @@
33
33
  "title": "an @earendil-works package returns as a dev dependency beside its production pin, one name in two dependency classes",
34
34
  "subject": "package.json",
35
35
  "find": ["\"@biomejs/biome\": \"^2.4.13\","],
36
- "replace": ["\"@biomejs/biome\": \"^2.4.13\",\n\t\t\"@earendil-works/pi-ai\": \"1.5.0\","],
36
+ "replace": ["\"@biomejs/biome\": \"^2.4.13\",\n\t\t\"@earendil-works/pi-ai\": \"1.5.2\","],
37
37
  "gate": ["bash", "run.sh", "check-dep-versions"],
38
38
  "timeoutSeconds": 300,
39
39
  "signature": "[QK:PI-SDK-NO-DUAL-DECLARATION]",
@@ -41,15 +41,13 @@
41
41
  },
42
42
  {
43
43
  "claim": "PI-FLOOR-PUBLISHED-SCOPE",
44
- "title": "a published-scope region in an operator doc names a package the frozen published manifest does not record, so the 1.4 instructions inside it would be held to a pairing no artifact ships — the doc can no longer prove which published package its install steps belong to",
44
+ "title": "a published-scope region in an operator doc names a package the frozen published manifest does not record, so the 1.5.0 instructions inside it would be held to a pairing no artifact ships — the doc can no longer prove which published package its install steps belong to",
45
45
  "subject": "docs/setup-clean-host.md",
46
46
  "find": [
47
- "<!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->",
48
- "| Component | Requirement | Needed for |"
47
+ "| pi | optional-by-presence — published 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.51.0 pi 1.5.0 -->"
49
48
  ],
50
49
  "replace": [
51
- "<!-- entwurf:published @junghanacs/entwurf@0.49.0 pi 1.4.0 -->",
52
- "| Component | Requirement | Needed for |"
50
+ "| pi | optional-by-presence — published 0.51.0: <!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.5.0 -->"
53
51
  ],
54
52
  "gate": ["bash", "run.sh", "check-dep-versions"],
55
53
  "timeoutSeconds": 300,
@@ -22,7 +22,7 @@
22
22
  "title": "MCP long description reverts to the false \"socket paths take a per-target lock\" claim",
23
23
  "subject": "ENTWURF.json",
24
24
  "find": [
25
- "LOCK: taken for a control-socket-DOMAIN dispatch. The mailbox and native-push rails are lock-free — deliverability and the adapter probe guard them. mode "
25
+ "A control-socket-DOMAIN dispatch takes a target lock; mailbox and native-push rails are lock-free. mode "
26
26
  ],
27
27
  "replace": ["LOCK POLICY: socket paths take a per-target lock; the mailbox path does not. mode "],
28
28
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
@@ -81,7 +81,7 @@
81
81
  "title": "MCP long description advertises owned-outcome as a selectable dormant verb again and drops the reason a dormant target actually returns",
82
82
  "subject": "ENTWURF.json",
83
83
  "find": [
84
- "A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. "
84
+ "A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. This delivery verb never launches or resumes a process. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. "
85
85
  ],
86
86
  "replace": [
87
87
  "owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY and is never auto-converted. "
@@ -132,11 +132,9 @@
132
132
  "claim": "V2SURF-DESC-FITS-HOST-CAP",
133
133
  "title": "the v2 tool description grows back past the host's 2048-char cap — the INTENT contract is silently cut off before the model that must choose an intent ever reads it",
134
134
  "subject": "ENTWURF.json",
135
- "find": [
136
- "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id "
137
- ],
135
+ "find": ["Send, reply or hand off to an existing garden id; the decider selects the live rail "],
138
136
  "replace": [
139
- "CANONICAL DELIVERY SURFACE for garden ids: message, reply, or hand off to whoever an id names. The id PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING "
137
+ "Send, reply or hand off to an existing garden id; the decider selects the live rail PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING PADDING "
140
138
  ],
141
139
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
142
140
  "timeoutSeconds": 60,
@@ -354,7 +354,7 @@ async function main(): Promise<void> {
354
354
  ok(
355
355
  "the landed sender session IS the resident's own garden id, replyable (anchored envelope line)",
356
356
  new RegExp(
357
- `^ {2}session: {5}${rx(senderGid)} \\(replyable — reply via entwurf_v2 to this sessionId, intent=fire-and-forget\\)$`,
357
+ `^ {2}session: {5}${rx(senderGid)} ${rx("(replyable — when a reply is requested, send the result via entwurf_v2 (intent=fire-and-forget) to the requested recipient, defaulting to this sessionId; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment)")}$`,
358
358
  "m",
359
359
  ).test(header),
360
360
  );
@@ -345,7 +345,7 @@ async function main(): Promise<void> {
345
345
  ok(
346
346
  "the landed sender session IS the resident's own garden id, replyable (anchored envelope line)",
347
347
  new RegExp(
348
- `^ {2}session: {5}${rx(senderGid)} \\(replyable — reply via entwurf_v2 to this sessionId, intent=fire-and-forget\\)$`,
348
+ `^ {2}session: {5}${rx(senderGid)} ${rx("(replyable — when a reply is requested, send the result via entwurf_v2 (intent=fire-and-forget) to the requested recipient, defaulting to this sessionId; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment)")}$`,
349
349
  "m",
350
350
  ).test(header),
351
351
  );