@junghanacs/entwurf 0.41.1 → 0.45.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 (102) hide show
  1. package/AGENTS.md +2 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +344 -0
  4. package/DELIVERY.md +37 -20
  5. package/ENTWURF.json +236 -0
  6. package/README.md +75 -65
  7. package/VERIFY.md +9 -9
  8. package/docs/external-mcp-host.md +1 -1
  9. package/docs/setup-clean-host.md +59 -33
  10. package/mcp/entwurf-bridge/dist/ENTWURF.json +236 -0
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +32 -190
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/agent-guidance.js +299 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +23 -24
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +15 -11
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +29 -24
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +18 -16
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +101 -32
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  29. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +23 -45
  30. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  31. package/mcp/entwurf-bridge/src/index.ts +126 -321
  32. package/native/win32-x64/entwurf_win.node +0 -0
  33. package/native/win32-x64/manifest.json +2 -2
  34. package/package.json +5 -4
  35. package/pi-extensions/lib/agent-guidance.ts +341 -0
  36. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  37. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  38. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  39. package/pi-extensions/lib/entwurf-v2-surface.ts +27 -30
  40. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +15 -16
  41. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  42. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  43. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  44. package/pi-extensions/lib/mux-fresh-call.ts +29 -30
  45. package/pi-extensions/lib/mux-resume-call.ts +18 -19
  46. package/pi-extensions/lib/native-push/register.ts +17 -2
  47. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  48. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  49. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  50. package/pi-extensions/lib/remote-pair-send.ts +124 -34
  51. package/pi-extensions/meta-bridge-omp.ts +69 -7
  52. package/pi-extensions/meta-bridge-pi-durable.ts +23 -64
  53. package/run.sh +182 -22
  54. package/scripts/build-bridge.sh +10 -0
  55. package/scripts/check-agy-sender-identity.ts +6 -4
  56. package/scripts/check-capability-bundle-reach.ts +86 -0
  57. package/scripts/check-entwurf-self-address.ts +37 -6
  58. package/scripts/check-entwurf-v2-contract.ts +24 -2
  59. package/scripts/check-entwurf-v2-production.ts +45 -31
  60. package/scripts/check-entwurf-v2-surface.ts +94 -98
  61. package/scripts/check-gate-qualification.ts +17 -13
  62. package/scripts/check-mux-resume-call.ts +20 -3
  63. package/scripts/check-native-push-adapter.ts +4 -0
  64. package/scripts/check-native-push-register.ts +32 -82
  65. package/scripts/check-remote-pair-installed.ts +2 -2
  66. package/scripts/dev-bin.sh +112 -1
  67. package/scripts/lib/chain-terminus-publication.ts +58 -0
  68. package/scripts/lib/codex-fresh-live-protocol.ts +30 -0
  69. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  70. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  71. package/scripts/mutants/agent-guidance.json +160 -0
  72. package/scripts/mutants/bridge-boot-resume.json +6 -8
  73. package/scripts/mutants/capability-bundle-reach.json +124 -0
  74. package/scripts/mutants/chain-terminus-publication.json +17 -0
  75. package/scripts/mutants/codex-native.json +98 -0
  76. package/scripts/mutants/control-send-receipt.json +23 -6
  77. package/scripts/mutants/mux-fresh-call.json +30 -4
  78. package/scripts/mutants/mux-resume-call.json +28 -3
  79. package/scripts/mutants/native-push-paused.json +17 -0
  80. package/scripts/mutants/omp-fresh.json +13 -2
  81. package/scripts/mutants/pack-install.json +46 -0
  82. package/scripts/mutants/pi-durable.json +17 -5
  83. package/scripts/mutants/remote-pair-display.json +3 -3
  84. package/scripts/mutants/remote-pair-receive.json +41 -11
  85. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  86. package/scripts/mutants/remote-pair-send.json +177 -19
  87. package/scripts/mutants/remote-pair-transport.json +4 -4
  88. package/scripts/mutants/self-address.json +10 -10
  89. package/scripts/mutants/source-install.json +66 -0
  90. package/scripts/mutants/v2-surface.json +50 -64
  91. package/scripts/mutants/v2-visible-resume.json +11 -0
  92. package/scripts/remote-pair-receive.ts +12 -12
  93. package/scripts/smoke-agy-install-state.sh +121 -0
  94. package/scripts/smoke-agy-native-push-live.ts +13 -0
  95. package/scripts/smoke-codex-fresh-live.ts +50 -27
  96. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  97. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  98. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  99. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  100. package/scripts/smoke-omp-fresh-live.ts +5 -4
  101. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  102. package/scripts/smoke-setup-verdict.sh +27 -2
Binary file
@@ -3,7 +3,7 @@
3
3
  "platform": "win32",
4
4
  "arch": "x64",
5
5
  "file": "entwurf_win.node",
6
- "sha256": "9837893521ec1d033913ca6cd1b705cb6e942e8d5c288948a1ed0ca0e28655e5",
6
+ "sha256": "0864b2753a8c22831874dd00959febf597616315670130910aa45147aaa61d17",
7
7
  "napiVersion": 8,
8
8
  "builtWith": {
9
9
  "node": "24.18.1",
@@ -12,7 +12,7 @@
12
12
  "windowsTargetPlatformVersion": "10.0.26100.0",
13
13
  "visualStudio": {
14
14
  "displayName": "Visual Studio Enterprise 2026",
15
- "installationVersion": "18.10.12224.181"
15
+ "installationVersion": "18.11.12315.225"
16
16
  }
17
17
  },
18
18
  "source": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junghanacs/entwurf",
3
- "version": "0.41.1",
3
+ "version": "0.45.0",
4
4
  "description": "Visible sibling sessions across independent agent harnesses: launch in Herdr or tmux, retire on request and reopen pi or Claude Code in tmux, and deliver messages by garden id; native bridges and Claude/Cortex ACP adapters.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -39,6 +39,7 @@
39
39
  ],
40
40
  "type": "module",
41
41
  "files": [
42
+ "ENTWURF.json",
42
43
  "protocol.js",
43
44
  "pi-extensions/",
44
45
  "native/win32-x64/",
@@ -196,10 +197,10 @@
196
197
  "check:full": "bash scripts/check-elapsed.sh check:full check:core check:hermetic check:package",
197
198
  "check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
198
199
  "check:toolchain": "pnpm lint && pnpm typecheck",
199
- "check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-pi-queue-oracle && ./run.sh check-tests-beside-behavior && ./run.sh check-remote-pair-installed-audit",
200
- "check:contracts": "./run.sh check-demo-reproducibility && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-control-retire && ./run.sh check-pi-retire-request && ./run.sh check-cc-retire-pidfd && ./run.sh check-cc-retire-request && ./run.sh check-retirement-event && ./run.sh check-entwurf-v2-retire && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-win32-preview && ./run.sh check-pack-win32-carrier && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-fresh-call-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-set-model-readback && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-pi-durable-beside && ./run.sh check-pi-durable-carrier && ./run.sh check-pi-durable-native-module",
200
+ "check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-pi-queue-oracle && ./run.sh check-tests-beside-behavior && ./run.sh check-remote-pair-installed-audit && ./run.sh check-chain-terminus-publication",
201
+ "check:contracts": "./run.sh check-demo-reproducibility && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-control-retire && ./run.sh check-pi-retire-request && ./run.sh check-cc-retire-pidfd && ./run.sh check-cc-retire-request && ./run.sh check-retirement-event && ./run.sh check-entwurf-v2-retire && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-win32-preview && ./run.sh check-pack-win32-carrier && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-fresh-call-provider && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-native-push-paused && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-set-model-readback && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-agent-guidance-beside && ./run.sh check-pi-durable-beside && ./run.sh check-pi-durable-carrier && ./run.sh check-pi-durable-native-module && ./run.sh check-remote-pair-beside",
201
202
  "check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-meta-hook-session-switch && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-pi-launch && ./run.sh check-codex-app-server-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-birth-hook && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-peer-facts && ./run.sh check-herdr-plugin && ./run.sh check-herdr-plugin-profile && ./run.sh check-herdr-plugin-build && ./run.sh check-herdr-runtime-bootstrap && ./run.sh check-herdr-activation && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-usage-accounting && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-typing-call-fence && ./run.sh check-gate-manifests && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex && ./run.sh check-codex-bridge-identity && ./run.sh check-pi-bridge-sender && ./run.sh check-pi-mcp-register && ./run.sh check-pi-mcp-bridge && ./run.sh check-codex-birth-hook && ./run.sh smoke-codex-config-state && ./run.sh smoke-codex-birth && ./run.sh check-herdr-sandbox",
202
- "check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-pack-consumer-roots && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
203
+ "check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-pack-argv-matcher && ./run.sh check-pack-consumer-roots && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
203
204
  },
204
205
  "pi": {
205
206
  "extensions": [
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Product-owned natural-language logic, authored only in root ENTWURF.json (#154).
3
+ * One module-relative file, read once. The compiled layout carries an exact copy at
4
+ * dist/ENTWURF.json; no cwd/HOME search, override, old-text fallback or hot reload.
5
+ * Existing operator engraving and user-configuration contracts are unrelated.
6
+ */
7
+ import { readFileSync } from "node:fs";
8
+
9
+ const TOOL_SPEC = {
10
+ entwurf_v2: { durable: true, parameters: ["target", "intent", "message", "mode", "wants_reply"] },
11
+ entwurf_self: { durable: true, parameters: [] },
12
+ entwurf_peers: { durable: true, parameters: [] },
13
+ entwurf_inbox_read: { durable: true, parameters: ["gardenId"] },
14
+ entwurf_register_native: { durable: false, parameters: ["backend", "nativeSessionId", "cwd"] },
15
+ entwurf_fresh_call: {
16
+ durable: true,
17
+ parameters: ["backend", "model", "task", "cwd", "placement.tmuxSession", "placement"],
18
+ },
19
+ entwurf_resume_call: { durable: true, parameters: ["target"] },
20
+ entwurf_retire_call: { durable: true, parameters: ["target"] },
21
+ entwurf_callback: { durable: true, parameters: [] },
22
+ } as const;
23
+
24
+ /** Frozen entwurf_v2 reject wire ids whose static next-step hint text lives in the root file. */
25
+ const REJECT_HINT_REASONS = [
26
+ "dormant-fire-forget-unsupported",
27
+ "indeterminate-no-spawn",
28
+ "native-push-target-dead",
29
+ "native-push-probe-indeterminate",
30
+ "native-push-paused",
31
+ ] as const;
32
+
33
+ export type RejectHintReason = (typeof REJECT_HINT_REASONS)[number];
34
+
35
+ /** Frozen entwurf_resume_call record/liveness reject wire ids whose static hint text lives in the root file. */
36
+ const RESUME_REJECT_HINT_REASONS = [
37
+ "target-invalid",
38
+ "target-backend-unsupported",
39
+ "target-locked",
40
+ "target-live",
41
+ "target-indeterminate",
42
+ "target-receiver-absent",
43
+ "target-receiver-corrupt",
44
+ "target-receiver-mismatch",
45
+ "target-receiver-unbound",
46
+ "target-address-conflict",
47
+ "launch-refused",
48
+ ] as const;
49
+
50
+ export type ResumeRejectHintReason = (typeof RESUME_REJECT_HINT_REASONS)[number];
51
+
52
+ /** Frozen entwurf_fresh_call tmux launch-stage reject wire ids whose static hint text lives in the root file. */
53
+ const FRESH_LAUNCH_REJECT_HINT_REASONS = [
54
+ "no-tmux-context",
55
+ "anchor-malformed",
56
+ "anchor-unresolved",
57
+ "anchor-mismatch",
58
+ "caller-identity-unavailable",
59
+ "cwd-not-absolute",
60
+ "cwd-format-token",
61
+ "cwd-missing",
62
+ "cwd-not-directory",
63
+ "tmux-session-name-invalid",
64
+ "tmux-session-missing",
65
+ "model-empty",
66
+ "task-empty",
67
+ "runtime-unresolved",
68
+ "runtime-not-absolute",
69
+ "runtime-path-whitespace",
70
+ "runtime-missing",
71
+ "runtime-not-regular-file",
72
+ "runtime-not-executable",
73
+ ] as const;
74
+
75
+ export type FreshLaunchRejectHintReason = (typeof FRESH_LAUNCH_REJECT_HINT_REASONS)[number];
76
+
77
+ /** Frozen entwurf_resume_call tmux launch-stage reject wire ids whose static hint text lives in the root file. */
78
+ const RESUME_LAUNCH_REJECT_HINT_REASONS = [
79
+ "no-tmux-context",
80
+ "anchor-malformed",
81
+ "anchor-unresolved",
82
+ "anchor-mismatch",
83
+ "cwd-not-absolute",
84
+ "cwd-format-token",
85
+ "cwd-missing",
86
+ "cwd-not-directory",
87
+ "herdr-resume-unsupported",
88
+ "runtime-unresolved",
89
+ "runtime-not-absolute",
90
+ "runtime-path-whitespace",
91
+ "runtime-missing",
92
+ "runtime-not-regular-file",
93
+ "runtime-not-executable",
94
+ ] as const;
95
+
96
+ export type ResumeLaunchRejectHintReason = (typeof RESUME_LAUNCH_REJECT_HINT_REASONS)[number];
97
+
98
+ /** Frozen keys of the static delivery-result clauses whose text lives in the root file; the
99
+ * receipts around them (syntax, branch conditions, slots, punctuation) stay code-owned. */
100
+ const DELIVERY_RESULT_CLAUSE_KEYS = [
101
+ "finalized-rejected",
102
+ "dirty-lock",
103
+ "remote-accepted",
104
+ "remote-reported-error",
105
+ "remote-indeterminate",
106
+ ] as const;
107
+
108
+ export type DeliveryResultClauseKey = (typeof DELIVERY_RESULT_CLAUSE_KEYS)[number];
109
+
110
+ /** Frozen wire ids of the reject hints whose authored text sits on either side of ONE code-owned
111
+ * slot, and each one's exact segment keys. The slot value, the reason branch and the
112
+ * concatenation stay code-owned; the root file holds only the text around the slot. */
113
+ const SLOTTED_REJECT_HINT_SEGMENTS = {
114
+ "record-less-socket": ["beforeFreshCutPrescription", "afterFreshCutPrescription"],
115
+ } as const;
116
+
117
+ export type SlottedRejectHintReason = keyof typeof SLOTTED_REJECT_HINT_SEGMENTS;
118
+ type SlottedRejectHints = {
119
+ readonly [R in SlottedRejectHintReason]: Readonly<Record<(typeof SLOTTED_REJECT_HINT_SEGMENTS)[R][number], string>>;
120
+ };
121
+
122
+ type ToolName = keyof typeof TOOL_SPEC;
123
+ type ToolGuidance<K extends ToolName> = {
124
+ readonly descriptions: (typeof TOOL_SPEC)[K]["durable"] extends true
125
+ ? { readonly mcp: string; readonly durable: string }
126
+ : { readonly mcp: string };
127
+ readonly parameters: Readonly<Record<(typeof TOOL_SPEC)[K]["parameters"][number], string>>;
128
+ };
129
+
130
+ export interface EntwurfGuidance {
131
+ readonly _about: Readonly<Record<"purpose" | "ownership" | "scope" | "editing", string>>;
132
+ readonly schemaVersion: 6;
133
+ readonly revision: number;
134
+ readonly history: readonly {
135
+ readonly revision: number;
136
+ readonly source: string;
137
+ readonly issue: string;
138
+ readonly change: string;
139
+ }[];
140
+ readonly tools: { readonly [K in ToolName]: ToolGuidance<K> };
141
+ readonly rejectHints: Readonly<Record<RejectHintReason, string>>;
142
+ readonly resumeRejectHints: Readonly<Record<ResumeRejectHintReason, string>>;
143
+ readonly freshLaunchRejectHints: Readonly<Record<FreshLaunchRejectHintReason, string>>;
144
+ readonly resumeLaunchRejectHints: Readonly<Record<ResumeLaunchRejectHintReason, string>>;
145
+ readonly deliveryResultClauses: Readonly<Record<DeliveryResultClauseKey, string>>;
146
+ readonly slottedRejectHints: SlottedRejectHints;
147
+ }
148
+
149
+ function record(value: unknown, at: string): asserts value is Record<string, unknown> {
150
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
151
+ throw new Error(`ENTWURF.json ${at}: expected an object`);
152
+ }
153
+ }
154
+
155
+ function keys(value: Record<string, unknown>, expected: readonly string[], at: string): void {
156
+ const actual = Object.keys(value).sort();
157
+ const wanted = [...expected].sort();
158
+ if (actual.length !== wanted.length || actual.some((key, i) => key !== wanted[i])) {
159
+ throw new Error(`ENTWURF.json ${at}: keys must be exactly ${wanted.join(", ")}`);
160
+ }
161
+ }
162
+
163
+ function text(value: unknown, at: string): void {
164
+ if (typeof value !== "string" || value.length === 0) {
165
+ throw new Error(`ENTWURF.json ${at}: expected a non-empty string`);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * JSON.parse owns strict grammar, but its last-key-wins value is never enough.
171
+ * Walk the ORIGINAL, already grammar-checked tokens and reject duplicate DECODED
172
+ * object keys before returning data. Object scopes are independent; strings that
173
+ * are values (including punctuation and escaped quotes) are never key tokens.
174
+ */
175
+ function rejectDuplicateKeys(source: string): void {
176
+ let pos = 0;
177
+ const space = (): void => {
178
+ while (pos < source.length && /[\x20\t\r\n]/.test(source[pos])) pos++;
179
+ };
180
+ const string = (): string => {
181
+ const start = pos++;
182
+ while (pos < source.length) {
183
+ if (source[pos] === "\\") pos += 2;
184
+ else if (source[pos++] === '"') return JSON.parse(source.slice(start, pos)) as string;
185
+ }
186
+ throw new Error("ENTWURF.json: unterminated string");
187
+ };
188
+ const value = (): void => {
189
+ space();
190
+ if (source[pos] === "{") {
191
+ pos++;
192
+ space();
193
+ const seen = new Set<string>();
194
+ if (source[pos] !== "}") {
195
+ for (;;) {
196
+ space();
197
+ const key = string();
198
+ if (seen.has(key)) throw new Error(`ENTWURF.json: duplicate object key ${JSON.stringify(key)}`);
199
+ seen.add(key);
200
+ space();
201
+ pos++; // colon; JSON.parse already certified the grammar
202
+ value();
203
+ space();
204
+ if (source[pos] !== ",") break;
205
+ pos++;
206
+ }
207
+ }
208
+ pos++;
209
+ } else if (source[pos] === "[") {
210
+ pos++;
211
+ space();
212
+ if (source[pos] !== "]") {
213
+ for (;;) {
214
+ value();
215
+ space();
216
+ if (source[pos] !== ",") break;
217
+ pos++;
218
+ }
219
+ }
220
+ pos++;
221
+ } else if (source[pos] === '"') {
222
+ string();
223
+ } else {
224
+ while (pos < source.length && !/[\x20\t\r\n,}\]]/.test(source[pos])) pos++;
225
+ }
226
+ };
227
+ value();
228
+ }
229
+
230
+ function freeze(value: unknown): void {
231
+ if (value === null || typeof value !== "object") return;
232
+ for (const child of Object.values(value)) freeze(child);
233
+ Object.freeze(value);
234
+ }
235
+
236
+ /** Pure data-validation seam; production always reads the one fixed module URL below. */
237
+ export function parseEntwurfGuidance(source: string): EntwurfGuidance {
238
+ const data: unknown = JSON.parse(source);
239
+ rejectDuplicateKeys(source);
240
+ record(data, "root");
241
+ if (data.schemaVersion !== 6) throw new Error("ENTWURF.json: unsupported schemaVersion");
242
+ keys(
243
+ data,
244
+ [
245
+ "_about",
246
+ "schemaVersion",
247
+ "revision",
248
+ "history",
249
+ "tools",
250
+ "rejectHints",
251
+ "resumeRejectHints",
252
+ "freshLaunchRejectHints",
253
+ "resumeLaunchRejectHints",
254
+ "deliveryResultClauses",
255
+ "slottedRejectHints",
256
+ ],
257
+ "root",
258
+ );
259
+ if (!Number.isSafeInteger(data.revision) || (data.revision as number) < 1) {
260
+ throw new Error("ENTWURF.json: revision must be a positive safe integer");
261
+ }
262
+ record(data._about, "_about");
263
+ keys(data._about, ["purpose", "ownership", "scope", "editing"], "_about");
264
+ for (const [key, value] of Object.entries(data._about)) text(value, `_about.${key}`);
265
+ if (!Array.isArray(data.history) || data.history.length === 0) {
266
+ throw new Error("ENTWURF.json: history must be a non-empty array");
267
+ }
268
+ for (const entry of data.history) {
269
+ record(entry, "history entry");
270
+ keys(entry, ["revision", "source", "issue", "change"], "history entry");
271
+ if (
272
+ !Number.isSafeInteger(entry.revision) ||
273
+ (entry.revision as number) < 1 ||
274
+ (entry.revision as number) > (data.revision as number)
275
+ ) {
276
+ throw new Error("ENTWURF.json: history revision must name an existing payload revision");
277
+ }
278
+ for (const key of ["source", "issue", "change"] as const) text(entry[key], `history.${key}`);
279
+ }
280
+ record(data.tools, "tools");
281
+ keys(data.tools, Object.keys(TOOL_SPEC), "tools");
282
+ for (const name of Object.keys(TOOL_SPEC) as ToolName[]) {
283
+ const tool = data.tools[name];
284
+ record(tool, name);
285
+ keys(tool, ["descriptions", "parameters"], name);
286
+ record(tool.descriptions, `${name}.descriptions`);
287
+ keys(tool.descriptions, TOOL_SPEC[name].durable ? ["mcp", "durable"] : ["mcp"], `${name}.descriptions`);
288
+ for (const [rail, value] of Object.entries(tool.descriptions)) text(value, `${name}.descriptions.${rail}`);
289
+ record(tool.parameters, `${name}.parameters`);
290
+ keys(tool.parameters, TOOL_SPEC[name].parameters, `${name}.parameters`);
291
+ for (const [field, value] of Object.entries(tool.parameters)) text(value, `${name}.parameters.${field}`);
292
+ }
293
+ record(data.rejectHints, "rejectHints");
294
+ keys(data.rejectHints, REJECT_HINT_REASONS, "rejectHints");
295
+ for (const [reason, value] of Object.entries(data.rejectHints)) text(value, `rejectHints.${reason}`);
296
+ record(data.resumeRejectHints, "resumeRejectHints");
297
+ keys(data.resumeRejectHints, RESUME_REJECT_HINT_REASONS, "resumeRejectHints");
298
+ for (const [reason, value] of Object.entries(data.resumeRejectHints)) text(value, `resumeRejectHints.${reason}`);
299
+ record(data.freshLaunchRejectHints, "freshLaunchRejectHints");
300
+ keys(data.freshLaunchRejectHints, FRESH_LAUNCH_REJECT_HINT_REASONS, "freshLaunchRejectHints");
301
+ for (const [reason, value] of Object.entries(data.freshLaunchRejectHints))
302
+ text(value, `freshLaunchRejectHints.${reason}`);
303
+ record(data.resumeLaunchRejectHints, "resumeLaunchRejectHints");
304
+ keys(data.resumeLaunchRejectHints, RESUME_LAUNCH_REJECT_HINT_REASONS, "resumeLaunchRejectHints");
305
+ for (const [reason, value] of Object.entries(data.resumeLaunchRejectHints))
306
+ text(value, `resumeLaunchRejectHints.${reason}`);
307
+ record(data.deliveryResultClauses, "deliveryResultClauses");
308
+ keys(data.deliveryResultClauses, DELIVERY_RESULT_CLAUSE_KEYS, "deliveryResultClauses");
309
+ for (const [key, value] of Object.entries(data.deliveryResultClauses)) text(value, `deliveryResultClauses.${key}`);
310
+ record(data.slottedRejectHints, "slottedRejectHints");
311
+ keys(data.slottedRejectHints, Object.keys(SLOTTED_REJECT_HINT_SEGMENTS), "slottedRejectHints");
312
+ for (const reason of Object.keys(SLOTTED_REJECT_HINT_SEGMENTS) as SlottedRejectHintReason[]) {
313
+ const segments = data.slottedRejectHints[reason];
314
+ record(segments, `slottedRejectHints.${reason}`);
315
+ keys(segments, SLOTTED_REJECT_HINT_SEGMENTS[reason], `slottedRejectHints.${reason}`);
316
+ for (const [key, value] of Object.entries(segments)) text(value, `slottedRejectHints.${reason}.${key}`);
317
+ }
318
+ freeze(data);
319
+ return data as unknown as EntwurfGuidance;
320
+ }
321
+
322
+ const guidance = parseEntwurfGuidance(readFileSync(new URL("../../ENTWURF.json", import.meta.url), "utf8"));
323
+
324
+ /** Only these exact payload strings enter tool schemas; _about/history never do. */
325
+ export const ENTWURF_TOOL_GUIDANCE = guidance.tools;
326
+ /** Static next-step text appended to these entwurf_v2 rejections; the decider and wire ids stay code-owned. */
327
+ export const ENTWURF_REJECT_HINTS = guidance.rejectHints;
328
+ /** Static next-step text in these entwurf_resume_call record/liveness rejections; the verb and the shared ACP model hint stay code-owned. */
329
+ export const ENTWURF_RESUME_REJECT_HINTS = guidance.resumeRejectHints;
330
+ /** Static tmux launch-stage hints of entwurf_fresh_call; slot-bearing, ACP and preflight-leaf hints stay code-owned. */
331
+ export const ENTWURF_FRESH_LAUNCH_REJECT_HINTS = guidance.freshLaunchRejectHints;
332
+ /** Static tmux launch-stage hints of entwurf_resume_call, kept apart from the fresh verb even where the text matches. */
333
+ export const ENTWURF_RESUME_LAUNCH_REJECT_HINTS = guidance.resumeLaunchRejectHints;
334
+ /** Static delivery-result clauses of the entwurf_v2 dirty-lock and remote-pair receipts; the receipt syntax around them stays code-owned. */
335
+ export const ENTWURF_DELIVERY_RESULT_CLAUSES = guidance.deliveryResultClauses;
336
+ /** Text on either side of one code-owned slot per reject reason; the slot value and the concatenation stay with the consumer. */
337
+ export const ENTWURF_SLOTTED_REJECT_HINTS = guidance.slottedRejectHints;
338
+ export const ENTWURF_GUIDANCE_VERSION = Object.freeze({
339
+ schemaVersion: guidance.schemaVersion,
340
+ revision: guidance.revision,
341
+ });
@@ -77,6 +77,8 @@ export interface SelfAddressabilityFacts {
77
77
  watchArmed?: boolean;
78
78
  /** meta-session (native-push): an adapter probe found this citizen's live native conversation. */
79
79
  probeAlive?: boolean;
80
+ /** meta-session (native-push): #152 — this backend's native-push is paused, so no probe ran. */
81
+ nativePushPaused?: boolean;
80
82
  }
81
83
 
82
84
  export interface SelfAddressabilityResult {
@@ -118,6 +120,17 @@ export function computeSelfAddressability(facts: SelfAddressabilityFacts): SelfA
118
120
  // default fail-closed row).
119
121
  switch (facts.metaDeliveryDomain) {
120
122
  case "native-push": {
123
+ // #152 policy, decided BEFORE the pure record+probe predicate and kept out of it:
124
+ // only a record-backed citizen can be "paused" (a record-less one keeps its own
125
+ // reason), and no probe was run for this answer, so none is reported.
126
+ if (facts.recordBacked === true && facts.nativePushPaused === true) {
127
+ return {
128
+ replyable: false,
129
+ socketState: "none",
130
+ reason:
131
+ "native-push unreachable — native-push for this backend is paused (#152); no probe ran for this answer",
132
+ };
133
+ }
121
134
  const push = nativePushDeliverable({ recordBacked: facts.recordBacked, probeAlive: facts.probeAlive });
122
135
  return {
123
136
  replyable: push.deliverable,
@@ -134,6 +134,26 @@ export function nativePushSupported(backend: string): backend is NativePushBacke
134
134
  return (NATIVE_PUSH_BACKENDS as readonly string[]).includes(backend);
135
135
  }
136
136
 
137
+ // ── Paused native-push backends (#152) ─────────────────────────────────────
138
+ // A native-push backend whose SEND is paused by product policy. On the external send path
139
+ // entwurf measured, Antigravity's language server refused the `agentapi` call with
140
+ // `Unauthenticated: missing CSRF token` (agy 1.2.16 in #128; agy 1.4.6 again for #152).
141
+ // Whether a first-party own-tool shell holds a token entwurf could legitimately present is
142
+ // UNMEASURED, not ruled out. So instead of probing a port that refused on that path and
143
+ // reporting it as an inconclusive liveness, the three public entries that would reach the agy LS —
144
+ // v2 dispatch, a sender's own `replyable`, and `entwurf_register_native` — refuse by name
145
+ // BEFORE any probe. This is a fixed constant, not a flag: no env, config or runtime toggle
146
+ // can lift it. The backend stays in NATIVE_PUSH_BACKENDS, so existing agy records, the V3
147
+ // reader and the peers listing are unchanged; only the native-push traffic stops.
148
+ // The one probe that is NOT behind this guard is the fresh-cut quiescence check
149
+ // (scripts/meta-bridge-fresh-cut.ts), which protects an operator-named archive and must keep
150
+ // failing closed rather than read the pause as a departed host.
151
+ export const NATIVE_PUSH_PAUSED_BACKENDS: readonly NativePushBackend[] = ["antigravity"];
152
+
153
+ export function nativePushPaused(backend: string): boolean {
154
+ return (NATIVE_PUSH_PAUSED_BACKENDS as readonly string[]).includes(backend);
155
+ }
156
+
137
157
  // NativePushLiveness = the 3-value liveness the native-push adapter probe yields.
138
158
  // The SAME three values as SocketLiveness, reused so there is ONE 3-value liveness
139
159
  // vocabulary — but these are NOT socket-bound (봉인 2: "주석만 socket-전용 오독 정정"):
@@ -173,6 +193,7 @@ export const ENTWURF_V2_REJECT_REASONS = [
173
193
  "mailbox-undeliverable", // F-mailbox: fire-and-forget to an unsupported citizen whose mailbox is not deliverable (fail-closed; future pi-backend non-drainable mailbox)
174
194
  "native-push-target-dead", // 봉인 1: fire-and-forget to a native-push (agy) target whose adapter probe found NO live conversation. Post-probe; observedLiveness = dead.
175
195
  "native-push-probe-indeterminate", // 봉인 1: fire-and-forget to a native-push target whose adapter probe was inconclusive (agy alive but no port served the conv, or a probe error). Post-probe; observedLiveness = indeterminate. Never coerced to dead.
196
+ "native-push-paused", // #152: fire-and-forget to a native-push backend whose send is paused by product policy (NATIVE_PUSH_PAUSED_BACKENDS — antigravity). Pre-probe, observedLiveness = null: nothing was probed, so no liveness is claimed — never coerced to dead or indeterminate.
176
197
  "bad-target", // R2: absent/typo garden-id (no existing citizen); fresh creation out of v2 scope
177
198
  "record-less-socket", // #50 C4: a gid-shaped non-symlink control socket exists but NO meta-record claims it. The record is the sole address authority (목표 ②), so a bare socket is not an addressable citizen — it is a diagnostic state, refused for EVERY intent before any lock/probe (pre-probe, observedLiveness=null). NOT `bad-target`: something real answers to this gid, and mislabeling it absent would hide the very state the reject exists to surface.
178
199
  "target-locked", // R5 pre-claim for bucket B F2 per-gid lockfile conflict
@@ -181,12 +202,13 @@ export const ENTWURF_V2_REJECT_REASONS = [
181
202
  export type EntwurfV2RejectReason = (typeof ENTWURF_V2_REJECT_REASONS)[number];
182
203
 
183
204
  // ── Pre-probe reject reasons (?6 — observedLiveness = null) ────────────────
184
- // These four rejects are decided BEFORE any liveness probe runs, so there is no
205
+ // These five rejects are decided BEFORE any liveness probe runs, so there is no
185
206
  // honest 4-value FactLiveness to stamp: `bad-target` (no citizen/backend),
186
207
  // `target-locked` (5a lock conflict, before lstat/connect), `target-address-conflict`
187
208
  // (address-subject conflict → probing is forbidden), `record-less-socket` (#50 C4:
188
209
  // no record ⇒ not a citizen ⇒ no in-domain probe ever runs — the presence hint is
189
- // a single lstat, not a liveness measurement). `indeterminate` means an
210
+ // a single lstat, not a liveness measurement), `native-push-paused` (#152: the backend's
211
+ // native-push send is paused by policy, so its adapter is never probed). `indeterminate` means an
190
212
  // in-domain probe was inconclusive (≠ "not looked yet"); `unsupported` means the
191
213
  // backend has no predicate (≠ "pre-probe"). So a pre-probe reject's
192
214
  // observedLiveness is `null`, NOT one of the four values. Every OTHER reject —
@@ -203,6 +225,7 @@ export const PRE_PROBE_REJECT_REASONS = [
203
225
  "target-locked",
204
226
  "target-address-conflict",
205
227
  "record-less-socket",
228
+ "native-push-paused",
206
229
  ] as const satisfies readonly EntwurfV2RejectReason[];
207
230
  export type PreProbeRejectReason = (typeof PRE_PROBE_REJECT_REASONS)[number];
208
231
 
@@ -52,6 +52,7 @@ import {
52
52
  isLivenessSupported,
53
53
  makeRejectReceipt,
54
54
  type NativePushBackend,
55
+ nativePushPaused,
55
56
  nativePushSupported,
56
57
  resolveDispatch,
57
58
  resolveNativePushDispatch,
@@ -306,6 +307,12 @@ export async function decideDispatch(input: DispatchInput, deps: DispatchDecider
306
307
  // branch is LOCK-FREE (봉인 4): the pi in-domain lock closes a socket TOCTOU, but a
307
308
  // volatile probe route has no lock meaning (a duplicate-send idempotency is a D8 future).
308
309
  if (nativePushSupported(identity.backend)) {
310
+ // #152: a paused native-push backend is refused by name BEFORE its adapter is probed —
311
+ // on the measured external path that language server refused the call (missing CSRF token),
312
+ // and a refusal must not be reported as a measured liveness. Pre-probe, observedLiveness null.
313
+ if (nativePushPaused(identity.backend)) {
314
+ return reject(makeRejectReceipt("native-push-paused", null));
315
+ }
309
316
  const probe = await deps.nativePushProbe(identity);
310
317
  const receipt = resolveNativePushDispatch(input.intent, probe.status);
311
318
  if (!receipt.ok) return reject(receipt);
@@ -18,6 +18,11 @@
18
18
  */
19
19
 
20
20
  import { basename } from "node:path";
21
+ import {
22
+ ENTWURF_DELIVERY_RESULT_CLAUSES,
23
+ ENTWURF_REJECT_HINTS,
24
+ ENTWURF_SLOTTED_REJECT_HINTS,
25
+ } from "./agent-guidance.ts";
21
26
  import type { SenderEnvelope } from "./entwurf-control-rpc.ts";
22
27
  import type { DispatchInput, EntwurfV2Mode } from "./entwurf-v2-decider.ts";
23
28
  import { makeProductionEntwurfV2Deps, type ProductionEntwurfV2Opts } from "./entwurf-v2-production.ts";
@@ -65,7 +70,9 @@ export function toDispatchInput(params: SurfaceEntwurfV2Params): DispatchInput {
65
70
  * Detour B (B-a) — actionable rendering of an honest reject. The decider is UNCHANGED:
66
71
  * a reject stays a reject (Hard Rule 3). This only appends a one-line "what to do instead"
67
72
  * to the reject TEXT, so an honest reject stops reading as "delivery impossible". Returns
68
- * undefined for rejects with no useful next step.
73
+ * undefined for rejects with no useful next step. Hint English is authored in root ENTWURF.json
74
+ * (#154); the reason switch stays here, and so do the record-less-socket slot value and its
75
+ * concatenation (#154 E1b).
69
76
  */
70
77
  export function actionableRejectHint(reason: string): string | undefined {
71
78
  switch (reason) {
@@ -74,13 +81,7 @@ export function actionableRejectHint(reason: string): string | undefined {
74
81
  // state — the citizen exists and is not running — matters more than ever now that
75
82
  // there is no verb behind it: a caller that reads "reject" as "wrong id" goes
76
83
  // looking in the wrong place.
77
- return (
78
- "this citizen's record is intact but its session is not running, and delivery has no way to wake it. " +
79
- "The resume that used to answer here launched a hidden background child and was withdrawn under the " +
80
- "visible-first rule; the visible same-id resume is the separate lifecycle verb entwurf_resume_call. " +
81
- "Only on an explicit operator request, use entwurf_resume_call {target} for Pi or Claude Code; " +
82
- "it submits no prompt. This capability hint is not permission to resume automatically."
83
- );
84
+ return ENTWURF_REJECT_HINTS["dormant-fire-forget-unsupported"];
84
85
  case "indeterminate-no-spawn":
85
86
  // The socket probe did not settle, so liveness is UNKNOWN — an unestablished probe
86
87
  // is not a measured death, and dispatching into it could double-deliver. Say
@@ -91,32 +92,27 @@ export function actionableRejectHint(reason: string): string | undefined {
91
92
  // (never dispatch into an indeterminate target), not a capability that still
92
93
  // exists. So the hint's job is to say plainly what did NOT happen: nothing
93
94
  // delivered, nothing started.
94
- return (
95
- "the control-socket probe was inconclusive, so the target's liveness is UNKNOWN — this is not a " +
96
- "measured death. NOTHING was delivered and NO process was started (the reason id keeps its frozen " +
97
- "'-no-spawn' wire spelling from an era when one could be; entwurf_v2 starts nothing on any rail). " +
98
- "Re-run entwurf_peers to re-probe; if it stays indeterminate, check for a stale socket file left at " +
99
- "that garden id by a session that died without cleaning up."
100
- );
95
+ return ENTWURF_REJECT_HINTS["indeterminate-no-spawn"];
101
96
  case "native-push-target-dead":
102
97
  // The adapter probe found no live native host serving this conversation/thread. Stays
103
98
  // backend-neutral: agy speaks conversations over an LS port, Codex threads over an
104
99
  // app-server socket, and this hint is read by callers of both.
105
- return "the native-push conversation/thread is not live — no native host (harness process or app-server) answered the probe for it. Re-open that conversation/thread in its own harness, then retry; there is nothing to inject into.";
100
+ return ENTWURF_REJECT_HINTS["native-push-target-dead"];
106
101
  case "native-push-probe-indeterminate":
107
102
  // Native host up, but nothing served this conversation/thread — inconclusive, not a
108
103
  // hard dead. Backend-neutral for the same reason as the dead cell above.
109
- return "the native host (harness process or app-server) is up, but nothing served this conversation/thread, so the probe is INCONCLUSIVE — not a measured death. Retry once it is loaded, or verify the conversation/thread id.";
110
- case "record-less-socket":
104
+ return ENTWURF_REJECT_HINTS["native-push-probe-indeterminate"];
105
+ case "native-push-paused":
106
+ // #152: a policy refusal, not a liveness verdict — this dispatch probed and sent nothing.
107
+ return ENTWURF_REJECT_HINTS["native-push-paused"];
108
+ case "record-less-socket": {
111
109
  // #50 C4: name the true cause AND the fix — a bare socket is a diagnostic
112
- // state, not an addressable citizen (the record is the address).
113
- return (
114
- "a control socket exists at this garden id but NO meta-record claims it — the record is the " +
115
- "sole address authority, so a bare socket is not an addressable citizen. If a pre-record-era " +
116
- "resident owns that socket, restart it under the current runtime (session_start births its " +
117
- `record); if the store predates the current generation, quiesce and archive it with ` +
118
- `${FRESH_CUT_PRESCRIPTION}. A stale/forged socket should be removed.`
119
- );
110
+ // state, not an addressable citizen (the record is the address). The text on either
111
+ // side of the fix command is authored in root ENTWURF.json; the command itself stays
112
+ // with its owner (meta-session.ts), so the root file never copies it.
113
+ const hint = ENTWURF_SLOTTED_REJECT_HINTS["record-less-socket"];
114
+ return `${hint.beforeFreshCutPrescription}${FRESH_CUT_PRESCRIPTION}${hint.afterFreshCutPrescription}`;
115
+ }
120
116
  default:
121
117
  return undefined;
122
118
  }
@@ -218,17 +214,18 @@ export function renderEntwurfV2Result(result: EntwurfV2RunResult): EntwurfV2Surf
218
214
  // the same acceptance vocabulary the ordinary receipt uses is named here too — a
219
215
  // dirty lock is a reason to be MORE precise about what happened, not less, because
220
216
  // the operator is about to decide by hand whether anything needs re-sending.
217
+ // The two static clauses are authored in root ENTWURF.json (#154 E1a); this receipt's
218
+ // syntax, branches and slots stay here.
221
219
  const what =
222
220
  result.finalizedOutcome === "rejected"
223
- ? `rejected${result.finalizedRejectReason ? ` (${result.finalizedRejectReason})` : ""} — the receiver refused it; nothing was accepted`
221
+ ? `rejected${result.finalizedRejectReason ? ` (${result.finalizedRejectReason})` : ""} — ${ENTWURF_DELIVERY_RESULT_CLAUSES["finalized-rejected"]}`
224
222
  : `${result.finalizedBoundary ?? "mailbox-enqueued"}${
225
223
  result.finalizedMessagePath ? ` (enqueued ${basename(result.finalizedMessagePath)})` : ""
226
224
  }`;
227
225
  return {
228
226
  text:
229
- `entwurf_v2 ${result.transport} finalized as ${what}, and the lock is DIRTY ` +
230
- `(release failed) — do NOT retry, a re-send would double-deliver. Clear the lock by hand.` +
231
- `\n error: ${result.error}`,
227
+ `entwurf_v2 ${result.transport} finalized as ${what}, and ` +
228
+ `${ENTWURF_DELIVERY_RESULT_CLAUSES["dirty-lock"]}\n error: ${result.error}`,
232
229
  isError: true,
233
230
  };
234
231
  }