@astrosheep/square 0.4.0 → 0.4.2

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 (196) hide show
  1. package/dist/artifact.d.ts +1 -3
  2. package/dist/artifact.js +12 -36
  3. package/dist/attention-presentation.d.ts +2 -22
  4. package/dist/attention-presentation.js +9 -12
  5. package/dist/boundary-presentation.d.ts +3 -14
  6. package/dist/boundary-presentation.js +29 -104
  7. package/dist/card-presentation.d.ts +1 -0
  8. package/dist/card-presentation.js +2 -0
  9. package/dist/catch-decisions.js +1 -7
  10. package/dist/claude-boundary-state.d.ts +20 -0
  11. package/dist/claude-boundary-state.js +84 -0
  12. package/dist/claude-hook.d.ts +2 -0
  13. package/dist/claude-hook.js +231 -0
  14. package/dist/claude-mcp-context.d.ts +1 -1
  15. package/dist/claude-mcp-context.js +12 -3
  16. package/dist/cli/context.js +7 -9
  17. package/dist/cli/observation-commands.d.ts +4 -1
  18. package/dist/cli/observation-commands.js +37 -4
  19. package/dist/cli/registry.js +5 -2
  20. package/dist/codex-address.d.ts +2 -2
  21. package/dist/codex-boundary-state.d.ts +1 -5
  22. package/dist/codex-boundary-state.js +0 -4
  23. package/dist/codex-hook.d.ts +7 -10
  24. package/dist/codex-hook.js +69 -33
  25. package/dist/codex-queue.d.ts +10 -11
  26. package/dist/codex-queue.js +32 -32
  27. package/dist/codex-watch.d.ts +2 -0
  28. package/dist/codex-watch.js +100 -0
  29. package/dist/decisions.js +2 -2
  30. package/dist/delivery-deferral.d.ts +8 -17
  31. package/dist/delivery-deferral.js +0 -28
  32. package/dist/delivery-frame.d.ts +7 -0
  33. package/dist/delivery-frame.js +11 -0
  34. package/dist/delivery-ledger-sqlite.d.ts +97 -0
  35. package/dist/delivery-ledger-sqlite.js +496 -0
  36. package/dist/delivery-ledger.d.ts +196 -0
  37. package/dist/delivery-ledger.js +268 -0
  38. package/dist/delivery-lifetime.d.ts +6 -0
  39. package/dist/delivery-lifetime.js +45 -0
  40. package/dist/harness-claude.js +3 -4
  41. package/dist/harness-links.js +63 -7
  42. package/dist/harness.js +12 -17
  43. package/dist/help.js +4 -1
  44. package/dist/http-delivery.d.ts +5 -9
  45. package/dist/http-delivery.js +20 -77
  46. package/dist/inbox.d.ts +6 -49
  47. package/dist/inbox.js +24 -371
  48. package/dist/index.d.ts +6 -3
  49. package/dist/index.js +4 -1
  50. package/dist/mcp.js +2 -3
  51. package/dist/model.d.ts +7 -47
  52. package/dist/model.js +1 -7
  53. package/dist/notifications.d.ts +24 -20
  54. package/dist/notifications.js +39 -108
  55. package/dist/operation-control.d.ts +4 -0
  56. package/dist/operation-control.js +20 -0
  57. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  58. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  59. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  60. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  61. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  62. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  63. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  64. package/dist/packages/agent-delivery/src/codex.js +41 -4
  65. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  66. package/dist/packages/agent-delivery/src/index.js +1 -1
  67. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  68. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  69. package/dist/paseo-delivery.d.ts +5 -5
  70. package/dist/paseo-delivery.js +19 -97
  71. package/dist/paseo.d.ts +1 -1
  72. package/dist/paseo.js +1 -1
  73. package/dist/plugins/opencode/index.d.ts +5 -3
  74. package/dist/plugins/opencode/index.js +99 -21
  75. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  76. package/dist/plugins/opencode-shared/delivery.js +109 -0
  77. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  78. package/dist/plugins/opencode-v1/index.js +41 -0
  79. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  80. package/dist/plugins/opencode-v1/runtime.js +152 -0
  81. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  82. package/dist/plugins/opencode-v1/transport.js +50 -0
  83. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  84. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  85. package/dist/ports.d.ts +31 -90
  86. package/dist/session-lifecycle.d.ts +8 -7
  87. package/dist/session-lifecycle.js +40 -42
  88. package/dist/sqlite-square.d.ts +14 -23
  89. package/dist/sqlite-square.js +132 -139
  90. package/dist/square-api.js +4 -7
  91. package/dist/square-core.d.ts +0 -1
  92. package/dist/square-domain.d.ts +5 -17
  93. package/dist/square-domain.js +104 -221
  94. package/dist/square-facade.d.ts +18 -15
  95. package/dist/square-post.d.ts +53 -0
  96. package/dist/square-post.js +4 -0
  97. package/dist/square-posting.d.ts +13 -0
  98. package/dist/square-posting.js +174 -0
  99. package/dist/square-producer-sqlite.d.ts +11 -0
  100. package/dist/square-producer-sqlite.js +210 -0
  101. package/dist/square-sqlite.d.ts +15 -25
  102. package/dist/square-sqlite.js +150 -226
  103. package/dist/views.d.ts +2 -2
  104. package/dist/views.js +0 -1
  105. package/dist/wake-sink.d.ts +4 -11
  106. package/dist/wake-sink.js +8 -32
  107. package/dist/wake-transports.d.ts +4 -7
  108. package/dist/wake-transports.js +7 -39
  109. package/examples/symmetric-domains.mjs +29 -249
  110. package/guides/change-waits.md +13 -53
  111. package/guides/claude-native.md +7 -171
  112. package/guides/codex-delivery.md +55 -80
  113. package/guides/delivery-primitives.md +48 -207
  114. package/guides/mcp.md +1 -1
  115. package/guides/operation-cancellation.md +2 -5
  116. package/guides/participant-ownership.md +25 -89
  117. package/guides/pi-native-delivery.md +19 -56
  118. package/guides/pi-tools.md +6 -8
  119. package/guides/square-api.md +113 -615
  120. package/package.json +8 -8
  121. package/plugins/claude/.claude-plugin/plugin.json +2 -3
  122. package/plugins/claude/hooks/hooks.json +11 -1
  123. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  124. package/plugins/codex/hooks/hooks.json +11 -23
  125. package/plugins/opencode/index.ts +91 -23
  126. package/plugins/opencode-shared/delivery.ts +91 -0
  127. package/plugins/opencode-v1/index.ts +36 -0
  128. package/plugins/opencode-v1/runtime.ts +134 -0
  129. package/plugins/opencode-v1/transport.ts +41 -0
  130. package/plugins/paseo/OVERVIEW.md +5 -5
  131. package/plugins/paseo/README.md +7 -6
  132. package/plugins/paseo/client/markdown.tsx +5 -5
  133. package/plugins/paseo/client/square-message.tsx +28 -24
  134. package/plugins/paseo/client/transform.ts +8 -5
  135. package/plugins/paseo/shared/card-presentation.ts +16 -0
  136. package/plugins/paseo/shared/message.ts +6 -11
  137. package/plugins/pi/square-pi.js +156 -417
  138. package/dist/channel-artifact.d.ts +0 -10
  139. package/dist/channel-artifact.js +0 -55
  140. package/dist/channel-delivery.d.ts +0 -11
  141. package/dist/channel-delivery.js +0 -51
  142. package/dist/channel-domain.d.ts +0 -3
  143. package/dist/channel-domain.js +0 -83
  144. package/dist/channel-facade.d.ts +0 -76
  145. package/dist/channel-facade.js +0 -1
  146. package/dist/channel-internal.d.ts +0 -126
  147. package/dist/channel-internal.js +0 -440
  148. package/dist/channel-metadata.d.ts +0 -11
  149. package/dist/channel-metadata.js +0 -1
  150. package/dist/channel-ports.d.ts +0 -241
  151. package/dist/channel-ports.js +0 -1
  152. package/dist/channel-presentation.d.ts +0 -17
  153. package/dist/channel-presentation.js +0 -22
  154. package/dist/channel-sqlite-schema.d.ts +0 -13
  155. package/dist/channel-sqlite-schema.js +0 -81
  156. package/dist/channel-transport.d.ts +0 -62
  157. package/dist/channel-transport.js +0 -1
  158. package/dist/channel.d.ts +0 -84
  159. package/dist/channel.js +0 -60
  160. package/dist/claude-delivery-status.d.ts +0 -13
  161. package/dist/claude-delivery-status.js +0 -46
  162. package/dist/claude-delivery.d.ts +0 -12
  163. package/dist/claude-delivery.js +0 -108
  164. package/dist/claude-mod.d.ts +0 -8
  165. package/dist/claude-mod.js +0 -172
  166. package/dist/claude-transport.d.ts +0 -5
  167. package/dist/claude-transport.js +0 -58
  168. package/dist/cmd/notify-once.d.ts +0 -2
  169. package/dist/cmd/notify-once.js +0 -29
  170. package/dist/delivery-attempts.d.ts +0 -52
  171. package/dist/delivery-attempts.js +0 -73
  172. package/dist/delivery-effects.d.ts +0 -139
  173. package/dist/delivery-effects.js +0 -163
  174. package/dist/delivery-health.d.ts +0 -51
  175. package/dist/delivery-health.js +0 -173
  176. package/dist/delivery-operations.d.ts +0 -22
  177. package/dist/delivery-operations.js +0 -198
  178. package/dist/delivery-primitives.d.ts +0 -5
  179. package/dist/delivery-primitives.js +0 -3
  180. package/dist/delivery.d.ts +0 -56
  181. package/dist/delivery.js +0 -80
  182. package/dist/plugins/opencode/delivery.d.ts +0 -19
  183. package/dist/plugins/opencode/delivery.js +0 -223
  184. package/dist/presentation-operations.d.ts +0 -5
  185. package/dist/presentation-operations.js +0 -86
  186. package/dist/sqlite-channel.d.ts +0 -15
  187. package/dist/sqlite-channel.js +0 -600
  188. package/dist/square-index.d.ts +0 -16
  189. package/dist/square-index.js +0 -106
  190. package/dist/wake-preview.d.ts +0 -7
  191. package/dist/wake-preview.js +0 -15
  192. package/plugins/claude/hooks/register.js +0 -132
  193. package/plugins/claude/types.d.ts +0 -7
  194. package/plugins/opencode/delivery.ts +0 -198
  195. /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
  196. /package/dist/{channel-values.js → square-values.js} +0 -0
@@ -1,177 +1,13 @@
1
- # Claude native inbox
1
+ # Claude Code delivery
2
2
 
3
- Square's Claude plugin is a mod, not a shell-hook injector. Its `hooks/hooks.json`
4
- contains only `{"modules":["./register.js"]}`. Install with `square install claude`
5
- and load/approve it in a trusted interactive Claude terminal. Installation is
6
- not evidence of a live receiver.
3
+ The Square Claude plugin uses ordinary native command hooks. It has no mod, native inbox, or producer socket route. `SessionStart` attaches the exact native session to the host delivery ledger; `SessionEnd` fences and detaches that captured attachment without changing Square membership.
7
4
 
8
- Public `Square.at` and `Square.build` use the default Claude/Codex/Paseo wake transport when
9
- `wakeTransport` is omitted. Supply a port to replace it or `false` to disable proactive wake;
10
- synchronous `Square.inMemory` uses only an explicit port. See [square-api.md](square-api.md).
5
+ At `UserPromptSubmit`, Square records Claude's native `prompt_id` before presenting pending cards through event-correct `additionalContext`. Tool boundaries (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`) use the same boundary presenter for the exact native main/child owner, so cards arriving during a running turn need not wait for another user prompt. Autonomous hook-generated prompts are boundaries too, not evidence of human approval. A later prompt records presentation only when its input contains the exact complete prepared frame, including the ledger token and stored full-frame hash. That establishes observation before the model processes the prompt, not comprehension. The async frame uses a plain warning and token delimiters; Square never decodes entities or accepts changed/truncated text as an exact observation.
11
6
 
12
- ## Validated capability
7
+ At a normal `Stop`, one per-owner OS-locked observer waits for ledger changes for at most 86,400 seconds. It rechecks the exact captured `prompt_id`, attachment generation, Stop epoch and route after acquiring the lock and immediately before output. A new prompt disarms old observers. The watcher claims one card at a time and uses the existing handoff token/hash correlation. Invoked stderr output is `unknown` until that exact frame is observed at a later prompt boundary; there is no automatic fallback replay. Missing prompt IDs fail closed.
13
8
 
14
- The supported baseline is **macOS Claude Code with the mod actually loaded**;
15
- no build number is pinned. The receiver reads its real session ID, cwd, version, and
16
- `CLAUDE_CODE_MESSAGING_SOCKET`. A session ID or socket alone is not capability.
17
- Missing/disabled/unapproved mods, the normal unapproved headless `-p` path,
18
- and other platforms are unavailable. No legacy fallback exists.
19
- Square never changes inbound policy, forwards a child token, or impersonates a
20
- peer/plugin authority. Native OS-user socket permissions and Claude policy apply.
9
+ This is a finite, boundary-driven observer, not a promise of continuous coverage. Missing Stop, timeout, process exit, headless teardown, or a busy Escape with no later normal Stop may leave cards pending. Idle Escape has no classic hook and may not disarm an already waiting observer; already emitted asyncRewake notifications cannot be recalled. No Square inbox fallback is retained for these cases.
21
10
 
22
- `square harness doctor claude` explains installation and these limits.
23
- `square harness doctor delivery` derives current routes/evidence without sending.
24
- An absent/broken endpoint is unavailable; it does not consume Square activity.
11
+ The bundled MCP server gets call-local session, cwd, and agent attribution from native `PreToolUse`/terminal tool hooks keyed by Claude's `claudecode/toolUseId` metadata. Duplicate or incomplete call identity is denied by the event-specific PreToolUse permission response; unrelated tool hooks do not block. Cleanup matches tool-use ID, session and agent. Interrupted calls without a terminal hook expire after ten minutes. Models cannot supply identity as tool arguments.
25
12
 
26
- ## Native-first routing and policy holds
27
-
28
- Native delivery is preferred, including under Paseo. CLI and call-local MCP Codex
29
- joins register the native queue for their thread. Claude and OpenCode receivers
30
- register their native wake addresses only through their loaded integrations. An
31
- ordinary reconnect or reattachment cannot replace a current native address with a
32
- generic Paseo proposal. Addresses belong to the artifact seat generation, not an
33
- expiring routes table: explicit receiver withdrawal permits fallback, and a new
34
- seat generation cannot inherit the previous generation's native registration. Endpoint generations
35
- are opaque strings, compared for equality rather than interpreted.
36
- Participant expressions do not publish replacement addresses.
37
-
38
- This is route preference, not permission to bypass receiver policy. Claude can
39
- legitimately hold an unsolicited peer message with `no-mode-asserted`, especially
40
- under `bypassPermissions`. Square neither asserts a made-up permission mode nor
41
- changes `crossSessionInbound`. After bytes may have been sent, `unknown` remains
42
- attention-wide and prevents another send through Paseo, even if routes later change.
43
-
44
- Each native send persists a message ID before writing; the wire `uuid` and
45
- `msg_id` carry that ID. Square records no native custody telemetry from Paseo:
46
- official Paseo exposes no delivery-status lifecycle event, so a held message stays
47
- `unknown` with `native_admission_unconfirmed`. Recording a `held`, `released` or
48
- `dropped` observation would require a Paseo-provided event; Square does not patch
49
- Paseo to invent one. Even when such an observation exists, the event ID is
50
- deduplicated and the native reason and observation time remain diagnostic only.
51
- Neither a release nor a drop is a queue receipt, proof of consumption or retry
52
- authorization.
53
-
54
- `square harness doctor delivery` shows native custody separately from pending
55
- attention for current seat generations, including after the activity is seen. Paseo
56
- presents the `peer_message_hold` state in its own timeline. Acceptance means normal
57
- delivery when the receiver allows it, or a visible reason without bypass/replay when
58
- policy holds it.
59
-
60
- ## MCP tools and call identity
61
-
62
- The plugin's `.mcp.json` starts the existing Square MCP server. The mod no longer
63
- registers an argv wrapper or executes participant CLI commands. MCP and Pi native
64
- tools share their operation definitions and facade calls in `square-tools.ts`.
65
-
66
- Before a bundled MCP call, the mod uses the existing `square claude-mod` helper
67
- to publish `{toolUseId, sessionId, cwd, agentId?}`. It then calls `next(e)` unchanged.
68
- The server correlates `_meta["claudecode/toolUseId"]` with that association: no
69
- session, agent, token or other transport field is added to the model's schema or
70
- arguments. Claude's native **session ID plus child flag** semantics are preserved;
71
- the child loop's `agentId` is retained for attribution, not substituted as a session ID.
72
- Calls use the live cwd and session even if the MCP process started earlier.
73
-
74
- `SQUARE_CLAUDE_MCP_CONTEXT=1` is set only by the bundled MCP configuration. That
75
- endpoint fails closed if an association is missing or expired; it never silently
76
- uses the startup parent identity. Generic MCP configurations retain their existing
77
- identity handling. The hook targets `mcp__plugin_square_square__*`, not unrelated
78
- servers with similar operation names.
79
-
80
- The mod owns publication and token-matched removal in `finally`; the server only
81
- reads. Associations are private, transient transport files under
82
- `<host-ledger-root>/claude-mcp-calls`, not Square domain state or another artifact.
83
- Normal completion/errors remove them. If abrupt cancellation, unload or a crash
84
- prevents cleanup, they expire after ten minutes and the next publication prunes
85
- them. Expiry gates lookup, not an operation that already captured its context.
86
- MCP owns operation cancellation and connection shutdown. No second execution
87
- protocol, resident service or ledger schema migration is introduced.
88
-
89
- ## Membership and lifecycle
90
-
91
- Participants join explicitly. The mod never discovers or joins a square from its
92
- filename; `PUBLIC.square` has no special behavior. Existing memberships reconcile
93
- on startup. Main-conversation tool completion (including failed/aborted tools) and
94
- conversation start reconcile late memberships: the receiver itself publishes
95
- routes; the CLI cannot infer mod capability from a socket or session environment.
96
- This reconciliation injects no activity and does not poll. Its recovery sweep
97
- only sends previously unsent eligible attention, never unknown/admitted attempts.
98
-
99
- Initial activation and hot reload use `session.start`. New conversations use
100
- `classic.SessionStart`: clear reads the new API coordinate; **resume's event
101
- `session_id` is already the resumed coordinate while `$.session.id()` still reads
102
- the ending one (observed in 2.1.295)**. The event coordinate is used at that boundary, with
103
- API cwd/version/socket. Modern `session.end` detaches only its pinned `e.sessionId`
104
- and captured owner/join generations, within the event budget. Late committed joins
105
- are discovered at the pinned end timestamp. A process crash can omit end.
106
-
107
- Host end is not a participant departure: no `done` lands, and membership, listens,
108
- read horizon and generation remain intact. Catch ownership, deferrals and wake readiness
109
- are cleared; the participant appears away and keeps accumulating unread audience without
110
- proactive delivery. Before arming a receiver, start/resume discovers the exact owner's
111
- standing seats through `seatsOf(owner)` and restores only detached ones with
112
- `reattach(name, {owner, wakeAddress?})`. The adapter obtains that owner with
113
- `harnessOwner({channel: 'claude-code', session, agent?})`. One failed seat or location does not block the
114
- others. New/forked harness owners restore nothing. Neither a voluntary `done` nor a takeover is
115
- revived; there is no completed-seat recovery path or timeout that turns away into done.
116
- See [participant-ownership.md](participant-ownership.md).
117
-
118
- ## Body, admission, and presentation
119
-
120
- The coordinator uses Square's canonical audience projection and bounded body
121
- preview. Agora notifications identify the square, receiving participant and activity before its
122
- event and body. A clipped preview describes catching that activity to read it in full; it never
123
- embeds shell commands or tool-call syntax. Direct Channel notifications carry their stored preview
124
- or body instead. Channel custody and consumption use `msg/N`; Agora relations use `act/N`.
125
- Opaque text correlation resolves through bound memberships and the existing
126
- attempt ledger, never a path or asserted source from incoming text.
127
-
128
- Transport is the Node-only leaf published as `@astrosheep/agent-delivery/claude-native`,
129
- sourced from `packages/agent-delivery/src/claude-native.ts`. Square imports that
130
- source directly; its root package ships the compiled leaf without loading the
131
- OpenCode SDK or requiring a separate agent-delivery installation. There is no
132
- second socket sender. Receipt semantics (`written` is local bytes only, never
133
- admission), the plain-peer use, and the standalone client API are documented in
134
- the package `README.md`; the leaf accepts only target/session endpoint, text, and
135
- bounded deadline/cancellation control.
136
-
137
- `session.receive` runs before native policy; Square owns this admission gate and
138
- the presentation writer, not the leaf. `await next(e)` confirms queue admission
139
- only when the returned text is unchanged. Hold/refuse reject that gate;
140
- consumed/rewritten results cannot confirm the original. Unknown peer messages
141
- pass through. Recognized stale/cancelled deliveries can be consumed before queueing.
142
- No ambiguous send is blindly retried.
143
-
144
- The sole presentation writer observes **returned stored main user-role text**
145
- after `session.append.next`. The native attempt persists the payload hash and full/clipped coverage
146
- before sending. Stored ACK matches the entire framed payload against that persisted hash and checks
147
- the captured custody and current eligibility; it does not depend on rendering the payload again with
148
- the current renderer. Persisted full coverage records seen + presented; clipped coverage records
149
- clipped only. This means stored in model context, not processed or understood. Catch/presentation
150
- races are idempotent and revalidated immediately before send and inside the observation transaction.
151
-
152
- ## Cancellation and custody
153
-
154
- Explicit plugin `turn.abort` and terminal `turn.complete(reason:'aborted')`
155
- suppress the outstanding local batch without recording seen. Ordinary answer,
156
- refusal, and error completion are not user cancellation. Separate engine-owned
157
- family state survives hot reload, containing the abort timestamp and frozen
158
- membership generations. The artifact stores a generation-scoped monotonic message-index
159
- cutoff; replacement generations cannot inherit the old abort.
160
-
161
- Engine state reads are snapshots of one dispatch. Confirmation therefore uses
162
- one immediate fresh `clock.after` dispatch to read cancellation, not a repeated
163
- read in the old dispatch. It is not polling. If reload cancels a pending callback,
164
- its evidence stays unknown: reload does not replay it. A failed cancel bridge
165
- can be repaired by the fresh confirmation/reconciliation dispatch from retained
166
- engine state. If all bridge/state operations are unavailable, Square cannot
167
- promise a receipt or suppression; it fails boundedly and preserves unrelated
168
- Claude behavior.
169
-
170
- **Already queued OR natively held messages cannot be retracted.** Approval may
171
- release a held message without another receive guard. Append is observational,
172
- not a veto. Square suppresses local retries and stale evidence but cannot erase
173
- native custody or promise that remote work will not run. New activity after the
174
- abort remains eligible. Protocol design and runtime evidence live in the package
175
- `VALIDATION.md` and the commission artifacts it cites; the repository fixtures
176
- cover the same coordinator and packaging boundaries without requiring a Claude
177
- installation.
13
+ The installed and tested native runtime evidence is macOS Claude Code 2.1.299 with isolated HOME, plugin settings, delivery ledger and a dummy-key loopback provider. This does not certify other platforms, versions, or headless behavior. The final native prompt/generation check and async hook completion are not atomic. `node --import ./test/sandbox-env.js test/claude-hook-native.mjs` with `SQUARE_CLAUDE_HOOK_NATIVE_SMOKE=1` runs the shipped manifest and candidate CLI through a scratch PATH shim and preserves hook/provider evidence in `/tmp`. It proves repeated idle wake, prompt/attachment fencing, duplicate suppression, active prompt/tool context, native main/child MCP attribution and identity-failure denial.
@@ -1,92 +1,67 @@
1
1
  # Codex delivery
2
2
 
3
- Square uses the shared Codex leaves in `packages/agent-delivery/src`, rather than
4
- maintaining a second hook protocol, boundary store or CLI sender. The standalone
5
- package's [README](../packages/agent-delivery/README.md) describes both its native
6
- app-server capability and its host-owned hook integration.
3
+ Producers post neutral cards to DeliveryLedger; neither default dispatch nor public `postAndDispatch`
4
+ queues Codex input. A native receiver owns both synchronous presentation and idle queue admission.
5
+ It never reads Square artifacts or producer content stores.
7
6
 
8
- Public `Square.at` and `Square.build` use the default Claude/Codex/Paseo wake transport when
9
- `wakeTransport` is omitted. Supply a port to replace it or `false` to disable proactive wake;
10
- synchronous `Square.inMemory` uses only an explicit port. See [square-api.md](square-api.md).
7
+ The shipped hooks for Codex 0.166.0 are synchronous SessionStart, UserPromptSubmit, PreToolUse,
8
+ PostToolUse, Stop, Interrupt and SessionEnd, plus an async Stop command with `timeout: 86400` seconds.
9
+ SessionStart disarms and attaches the exact native session. Active prompt/tool boundaries disarm and
10
+ present eligible cards as event-correct `additionalContext`, using neutral reminder framing and the
11
+ original card text. The output sink must accept the response before presentation evidence is recorded;
12
+ a later suppression does not erase a real output effect. SessionEnd disarms and detaches. None of these
13
+ host lifecycle events restores or detaches Square seats. SessionResume is not a verified native event
14
+ and is not registered. A resume path without a real SessionStart/Stop has no inferred idle authority.
11
15
 
12
- ## Square's hook path
16
+ Each synchronous Stop stores a unique token and native turn ID in the existing boundary store. One
17
+ receiver-owned `square codex-watch` command holds a separate private OS-backed lock for its native
18
+ session coordinate and observes the ledger, with a one-second bounded rescan for boundary changes or
19
+ missed notifications. Duplicate async Stop processes exit; the existing watcher follows newer Stop
20
+ tokens. The lock does not prove idle state. Observation never reserves cards while waiting.
13
21
 
14
- `plugins/codex/hooks/hooks.json` invokes `square codex-hook`:
22
+ Before claiming each card, the watcher reads the hosting endpoint's `thread/read(includeTurns:true)`.
23
+ It requires loaded idle state and the newest live turn to be completed with exactly the captured Stop
24
+ turn ID. Newest-last history ordering is verified for 0.166.0; other versions fail closed until verified.
25
+ After claiming, the receiver repeats that native read, checks the exact epoch again, then checks ledger
26
+ currentness, suppression, cancellation, attachment generation, signal and deadline immediately before
27
+ CLI invocation. Queue always uses the existing hosting endpoint explicitly, without starting a service.
28
+ Missing, malformed, ephemeral, notLoaded, active, interrupted or unreadable history cannot authorize a
29
+ queue. A delayed stale Stop cannot authorize sending merely by arriving after an active callback.
15
30
 
16
- - `SessionStart`, `SessionResume` and `UserPromptSubmit` invalidate the previous
17
- Stop observation. `SessionEnd` marks the host session ended and detaches its exact
18
- owner's seats without landing `done`, ending membership, dropping listens or resetting
19
- the read horizon. Later Stop/tool callbacks cannot re-enable queue until a new
20
- `SessionStart` or `SessionResume`. Before enabling delivery, those start paths use
21
- `seatsOf(owner)` and `reattach(name, {owner, wakeAddress?})` to restore only detached
22
- standing seats of that exact owner; voluntary departures and takeovers are never revived.
23
- A new/forked session restores nothing.
24
- - `PostToolUse` asks Square for current pending activity and returns the existing
25
- bounded `additionalContext` presentation.
26
- - `Stop` records queue eligibility and sweeps pending work, but does not itself
27
- claim that a preview was presented.
31
+ `square codex-interrupt` handles only a native Interrupt payload and cancels the captured attachment's
32
+ pending through-sequence batch, then future cards remain available after a later verified Stop or active
33
+ boundary. Replacement attachments are fenced by generation. Before-effect epoch/suppression/cancel
34
+ failures release their claims where the ledger permits. Attachment rotation already normalizes an
35
+ in-flight ledger attempt to unknown: that custody remains unknown even if the final receiver check
36
+ then proves no queue was launched. The watcher exits on detach, deletion or generation change and
37
+ retains its ledger connection only for already-captured late settlements.
28
38
 
29
- The shared handler owns hook framing, lifecycle sequencing, its cancellation
30
- budget and bounded sweeps. Square supplies activity selection, formatting,
31
- artifact-owned presentation claims, cancellation, attachment cleanup and
32
- pending-work sweeps. The handler cooperatively awaits Square's pull: the signal
33
- cancels work before presentation, but a successful presentation's `settleAs`
34
- finishes independently of request cancellation. Even when that settlement crosses
35
- the internal budget, the hook waits for it and returns the associated context;
36
- it never abandons settlement that can mark activity seen without returning text.
37
- After expiry, no sweep starts, and a sweep timeout cannot erase successful pull
38
- output. Settlement must still finish within Codex's external hook timeout. Hook inbox lookup uses
39
- an exact owner from `harnessOwner({ channel: 'codex', session })`, not a bare session id. The shared
40
- boundary store uses Square's caller-selected `SQUARE_CODEX_BOUNDARIES` file,
41
- defaulting to `~/.square/codex-boundaries.json`; its JSON observations are not
42
- conversation storage and are not proof of current native execution or consumption.
43
- Square's format-11 artifact remains authoritative for membership, attachment and delivery evidence.
44
- Message custody uses `msg/N`; Agora activity relations use `act/N`. Endpoint generations are opaque
45
- and are compared for equality when fencing custody.
46
- Detached members appear away indefinitely and keep accumulating unread audience, but have no
47
- write or proactive delivery eligibility until same-owner reattachment. Only a host-confirmed
48
- permanent deletion uses fenced membership-ending cleanup; Codex SessionEnd is not that proof.
39
+ Queue admission is custody, not presentation or model comprehension. Accepted/unknown outbox custody
40
+ has no remaining card content and cannot be automatically replayed. Invoked timeout or ambiguous
41
+ results remain unknown. Native idle reads and external queue are not atomic: a new turn can start after
42
+ the final check and a raced queue may arrive later. Interrupt cannot recall input already admitted by
43
+ Codex, and hook failure/timeout cannot prove that cancellation happened. Native callbacks have no
44
+ cross-incarnation event identity; SessionEnd can fence only the attachment observed when it runs.
49
45
 
50
- `CodexQueueTransport` implements the existing `WakeTransportPort` and uses the public
51
- `connect({ harness: 'codex', transport: 'cli', sessionId, boundaries, ... })`
52
- and `agent.queue(text)` interface, just like independent hosts. Square reads its
53
- stored pending preview and finishes its currentness gate first; the bound client
54
- checks boundary eligibility once inside `queue()`, then launches the CLI. No
55
- caller-side lifecycle check is duplicated. Outcomes continue through the existing
56
- artifact-owned delivery path; no host ledger or new route kind is introduced.
46
+ There is no watcher before the first Stop. Synchronous boundaries can present; otherwise cards wait.
47
+ The async budget is finite 24 hours, not a promise of 24-hour observation. After timeout, crash, host
48
+ termination or missed spawn while idle, cards wait for the next Stop/user/native boundary. There is no
49
+ producer fallback or autonomous crash-idle recovery. During attachment replacement an old watcher
50
+ may briefly retain the coordinate lock while the new Stop's duplicate exits; the next Stop restores
51
+ observation after the old process releases it.
57
52
 
58
- Both CLI coordinate creation and MCP call-local Codex coordinates capture the
59
- recipient's `CODEX_HOME` (or home default), so later senders use the recipient's
60
- CLI configuration rather than their own. Only an explicitly configured
61
- `SQUARE_CODEX_ENDPOINT` adds a remote endpoint. `SQUARE_CODEX_BIN` remains
62
- Square's executable selection; it is not an environment convention of the
63
- independent package.
53
+ Reproduce the isolated native smoke from this checkout after a worktree-local build:
64
54
 
65
- By default the sender runs `codex queue --thread ... --message ...`, allowing the
66
- CLI's normal connection/startup behavior. Only an explicit endpoint adds
67
- `--remote`. This does not make another host's private stdio pipe attachable.
68
- After an observed `SessionEnd`, Square does not dispatch queue; the bound lifecycle
69
- check also catches an end observed while the currentness gate was running.
70
- It cannot retract a submission already launched or infer a missed end hook.
71
- Queue success means native admission only; its receipt id is a local attempt
72
- UUID, not a server message id. The child runs asynchronously and respects the
73
- send's abort signal/deadline. Timeout, killed child and unexplained post-launch
74
- failures remain unknown, not a safe reason to resend. A receiver with no tool
75
- calls waits for Stop; this path is not instantaneous native steer.
55
+ ```sh
56
+ env -u SQUARE_HOST_LEDGER_ROOT SQUARE_CODEX_NATIVE_SMOKE=1 \
57
+ node --import ./test/sandbox-env.js test/codex-watch-native.mjs
58
+ ```
76
59
 
77
- ## Optional native app-server capability
60
+ The probe creates a temporary HOME/CODEX_HOME, installs no plugin, and uses the actual shipped hook
61
+ manifest/CLI with an isolated native app-server and loopback fixture provider. Its JSON evidence names
62
+ the temporary directory. It verifies native Stop startup, idle wake, busy fencing, singleton reuse,
63
+ Interrupt RPC cancellation, real UserPromptSubmit presentation and process-group shutdown. Physical
64
+ TUI Escape-to-Interrupt is not verified; neither are 24-hour operation or every resume path.
78
65
 
79
- Independent consumers can use `connect({ harness: 'codex', sessionId, endpoint })`
80
- from `@astrosheep/agent-delivery` for fenced busy steer and native queue.
81
- `transport: 'app-server'` is the explicit spelling and the default when omitted;
82
- it exposes both methods. `transport: 'cli'` exposes queue only and requires the
83
- host's hook lifecycle source at connection time. There is no automatic fallback
84
- between transports. The app-server transport never starts a service or calls
85
- `thread/resume`. This does not replace Square's hook policy or add a new persisted
86
- Square route kind. A loaded thread's owner remains its existing host, not the
87
- external sender.
88
-
89
- Neither path implements a read-receipt roundtrip. Admission and runtime
90
- observations must not be presented as model comprehension or completion. An
91
- unknown outcome stays unknown. Installing, restarting or changing a real Codex
92
- server is outside this integration.
66
+ The shared package documents its transport leaves in [its README](../packages/agent-delivery/README.md).
67
+ See [participant ownership](participant-ownership.md) for host attachment and Square membership.
@@ -1,207 +1,48 @@
1
- # Delivery custody primitives
2
-
3
- `@astrosheep/square/delivery` provides storage-independent attempt transitions, suppression predicates,
4
- and four bound effect functions: `dispatchEffect`, `preparePresentation`, `presentPrepared`, and
5
- `presentPreparedBatch`. It does not open a square, interpret identities, own transactions, or schedule
6
- retries. Square's actual dispatch, single presentation and boundary batch adapters compose these same
7
- lifecycles; its SQLite adapter uses the attempt transitions with existing tables. Ownership, membership,
8
- cancellation and delivery-deferral policy remain in the bound application capability.
9
-
10
- ## Bind a series inside a transaction
11
-
12
- `AttemptRows<D>` is a capability for **one logical item / recipient / authority / purpose series**.
13
- The caller chooses those coordinates, including arbitrary opaque strings, and binds the store to them.
14
- No Square name, seat, generation, activity-id codec or composite-key format is required.
15
-
16
- - `list()` returns that series newest ordinal first.
17
- - `byToken(token)` returns only a matching attempt in that series.
18
- - `insert(attempt)` and `replace(attempt)` write within the caller's current transaction.
19
- - Replacements preserve purpose, token, ordinal and the caller's external series coordinates.
20
-
21
- The caller guarantees isolation against competing claims, token uniqueness within its token lookup
22
- scope, atomic commit/rollback, and durable storage where needed. Run each operation and its reads and
23
- writes inside that boundary; an asynchronous read followed later by an unguarded write is not custody.
24
- Stores must not retain mutable references that allow a caller to change durable rows without a write.
25
- `D` belongs entirely to the caller. The primitive does not merge, serialize or interpret it.
26
-
27
- ```ts
28
- import { claimAttempt, prepareAttempt, settleAttempt, type AttemptRows } from '@astrosheep/square/delivery';
29
-
30
- type Payload = { route?: string; nativeToken?: string; ack?: string };
31
- // Supplied by the application, already scoped and transaction-bound:
32
- declare const rows: AttemptRows<Payload>;
33
-
34
- const claim = claimAttempt(rows, {
35
- purpose: 'dispatch', token: 'application-generated-unique-token',
36
- at: 1000, leaseMs: 30_000, data: { route: 'opaque endpoint' },
37
- });
38
- if (claim.status === 'acquired') {
39
- prepareAttempt(rows, { token: claim.token, data: { route: 'opaque endpoint', nativeToken: 'correlation' } });
40
- }
41
- // In a later transaction, only after external proof of acceptance:
42
- settleAttempt(rows, {
43
- token: 'application-generated-unique-token', outcome: 'accepted', at: 1100,
44
- data: { route: 'opaque endpoint', nativeToken: 'correlation', ack: 'external proof' },
45
- });
46
- ```
47
-
48
- The example shows only transaction-scoped transitions. Commit a successful claim before external I/O;
49
- settle its exact token in a new transaction after observing the result. The bound effect capability
50
- below connects those operations without introducing a generic transaction runtime. Authority fencing,
51
- currentness policy and the meaning of external proof remain the application's responsibility.
52
- An `accepted` dispatch is not proof of presentation or consumption. Only the responsible presentation
53
- boundary may assert `presented`; consuming an item is a separate application operation.
54
-
55
- ## Square content custody
56
-
57
- Dispatch and presentation remain separate evidence series, but they are not independent permission
58
- to inject content. Square's transaction-scoped domain gate checks both series for the same artifact,
59
- recipient, seat generation and activity before admitting a new injection. A claim in either series
60
- blocks the other; accepted, presented, clipped and unknown evidence also block it. Only failed or
61
- released evidence permits another injection. An expired opposing claim is normalized to unknown in
62
- that transaction rather than stolen. No lock is held over host or network I/O.
63
-
64
- A native receiver that has verified the exact existing wake may pass its claim token as
65
- `observationOf` to the presentation adapter. The gate verifies the wake belongs to the same captured
66
- recipient/generation/activity and remains dispatching, accepted or unknown. This records observation
67
- of that message; its sink must not inject another message. Claude and OpenCode retain their exact
68
- native correlation/content checks. A successful wake alone never creates a consumption receipt.
69
- Paseo currently has no corresponding native Pi observation correlation: its accepted/unknown attempt
70
- stays unconsumed, available to explicit catch, without automatic native reinjection.
71
-
72
- Session pending projections used for automatic injection suppress claimed, accepted and uncertain
73
- items as well as presented ones. Failed/released attempts make them eligible again through delivery
74
- revision observation. Native payload construction and exact-token observation read their captured
75
- activity directly, not this injection queue. Cancellation and seat replacement retain their existing
76
- fences. Uncertainty deliberately favors no automatic duplicate over automatic recovery; no
77
- exactly-once processing or guaranteed eventual delivery is claimed.
78
-
79
- These checks use the existing artifact evidence and transactions; no new registry or artifact format
80
- is introduced. All running injectors must load the updated code before the guarantee applies.
81
-
82
- ## Transitions
83
-
84
- An `Attempt<D>` has `purpose: 'dispatch' | 'presentation'`, `token`, zero-based `ordinal`, `outcome`,
85
- finite `at`, optional `expiresAt`, and `data`.
86
-
87
- - `claimAttempt` first returns terminal evidence: dispatch `accepted`/`unknown`, presentation
88
- `presented`/`unknown`. Otherwise a live `dispatching` attempt is `busy`; missing expiry is live.
89
- - At exact expiry or later, an interrupted `dispatching` attempt becomes `unknown`, keeps its original
90
- payload, loses expiry, and returns `terminal` with `interrupted: true`. There is no new attempt and
91
- **no automatic replay of uncertainty**. Square maps that flag to its interruption diagnostic.
92
- - With neither terminal nor dispatching evidence, claim inserts `dispatching` at the next ordinal.
93
- Failed/released attempts can therefore be retried. Purpose-specific terminal meanings are not
94
- inferred from other completion labels.
95
- - `prepareAttempt` replaces only the data of the exact `dispatching` token. Pass the complete merged
96
- payload. It returns false for an absent or no-longer-dispatching token. Callers that already know
97
- the payload is unchanged may skip the replacement; core performs no JSON equality checks.
98
- - `settleAttempt` allows `dispatching` to any defined completion, and late `unknown` to `accepted`.
99
- It clears expiry and sets time/outcome/data. `accepted` to `accepted` returns true **without any
100
- write or payload/time mutation**. All other transitions return false, including `unknown` to
101
- `presented`. Missing or foreign tokens cannot settle another series.
102
-
103
- Invalid purpose/completion/token/time, non-positive or unsafe lease durations, non-finite expiry, and
104
- unsafe next ordinals reject before mutation with `TypeError` or `RangeError`, not `SquareError`.
105
- The caller supplies valid stored attempts and consistently scoped, ordered rows.
106
-
107
- ## Suppression without payloads
108
-
109
- `presentationBlocksDispatch(attempts, now)` and `automaticDispatchEligible(attempts, now)` accept only
110
- `purpose`, `outcome`, and optional `expiresAt`; no dummy payload or identity is needed. These predicates
111
- may see both purposes for the same logical delivery.
112
-
113
- Presentation `presented`, `clipped`, `unknown`, or `dispatching` blocks dispatch, including an
114
- expired presentation claim: loss of its lease is uncertainty, not permission to send again. Automatic dispatch additionally requires no dispatch
115
- `accepted`, `unknown`, or `dispatching`; even expired dispatch custody blocks automatic replay.
116
- These are preselection predicates, not substitutes for a transactional claim or application gates.
117
-
118
-
119
- ## Bound effects: one captured capability, four functions
120
-
121
- `EffectPort<C>` is bound by the caller to **one captured work item / recipient / authority / purpose**.
122
- It exposes only:
123
-
124
- - `claim(signal?)`: the application's existing atomic claim, returning `{ token, ordinal }` or
125
- `undefined` when busy, terminal, stale or deferred. The runner does not claim the attempt twice.
126
- - `current(signal?)`: pre-effect policy; a rejected check is unverifiable and treated as false.
127
- - `settle(claim, completion)`: one captured settlement operation, with no request signal. Return true
128
- only when it commits, false when not recorded (not necessarily stale), or throw on persistence error.
129
-
130
- The application owns tokens, leases, semantic timestamps, authority, proof/diagnostic payloads and
131
- resource lifetimes. There is no public transaction type, series-key codec or extra persisted lifecycle.
132
- `Settlement<C>` keeps the selected completion separate from persistence: `recorded`, `not-recorded`,
133
- or `error` with the original error. Observations are never erased by persistence refusal/error.
134
-
135
- ### Dispatch
136
-
137
- ```ts
138
- import { dispatchEffect, type EffectPort, type DispatchCompletion } from '@astrosheep/square/delivery';
139
-
140
- declare const port: EffectPort<DispatchCompletion>; // bound claim/current/settle
141
- // A sender must check its absolute deadline/cancellation immediately before external mutation.
142
- const report = await dispatchEffect(port, async context => {
143
- if (!await context.beforeSend()) return { outcome: 'not-capable' };
144
- if (context.signal?.aborted || Date.now() >= context.deadline) return { outcome: 'unknown' };
145
- // Call the application's transport here, passing the remaining budget and captured claim.
146
- return { outcome: 'accepted', data: { proof: 'application acknowledgement' } };
147
- }, { timeoutMs: 5000 });
148
- ```
149
-
150
- `dispatchEffect` owns claim → initial currentness → send → settlement. Cancellation suppresses
151
- pre-effect work. It starts its timer immediately before invoking the sender, not during claim,
152
- initial policy reads or persistence. Exactly one returned/thrown/timeout decision is frozen before
153
- settlement. A sender exception defaults to `unknown`, not failed; returned `not-capable` releases
154
- custody. A denied final gate releases with `not-current / final` under the sender's obligation to
155
- obey it. No automatic retry occurs.
156
-
157
- `beforeSend()` is **policy currentness, not an elapsed-budget gate**. A stalled policy check may
158
- return true after the outer timeout. The sender must recheck `deadline` and `signal` after that await
159
- and immediately before I/O; true does not authorize a late send. Outer timeout stays unknown even
160
- when no I/O is known to have started. Late gates and late promise results cannot rewrite the frozen
161
- settlement. Separately correlated native proof may still use the attempt transition's permitted late
162
- unknown → accepted path; the timed-out runner does not reconcile itself.
163
-
164
- ### Prepared presentation
165
-
166
- `preparePresentation({ payload, coverage, port }, { signal? })` acquires custody without running a
167
- sink. A returned `PreparedPresentation<P>` exposes `payload`, pre-effect `eligible(signal?)`, and
168
- promise-cached `release()` for abandonment. It is a local one-shot capability, not a durable row and
169
- not a public success writer.
170
-
171
- `presentPrepared(handle, sink, control?)` specializes the batch lifecycle to one item.
172
- `presentPreparedBatch(handles, render, sink, control?)` takes all supplied custody before its first
173
- await. It rejects duplicate, foreign or used handles before any effect, checks eligibility, then calls
174
- synchronous side-effect-free `render(eligible)` returning `{ payload, included }`. Included handles
175
- must be a unique exact subset of those eligible handles; invalid rendering releases custody and
176
- rejects without presentation evidence. No included items means no sink call.
177
-
178
- Cancellation is checked immediately before the one sink invocation. There is **no presentation timer**
179
- and no post-effect cancellation, deferral or currentness veto. The runner awaits the sink and marks
180
- **all included handles** effect-observed before starting asynchronous settlements. Full coverage
181
- settles `presented`; clipped coverage settles `clipped`. A thrown sink defaults to `unknown`;
182
- `classifyError` may explicitly return `failed` (classifier failure stays unknown). Unused handles are
183
- released, all sibling settlements and cleanup drain even on persistence failure, and later cleanup
184
- cannot overwrite success or uncertainty.
185
-
186
- ### Mandatory atomic full-consumption settlement
187
-
188
- The presentation binding's single `settle` operation must enforce:
189
-
190
- | Completion | Atomic persistence |
191
- | --- | --- |
192
- | `presented` | Complete the captured attempt **and all captured full-consumption receipts in the same transaction**. Return true only when the combined operation commits. |
193
- | `clipped` | Complete the attempt; no full-consumption receipt. |
194
- | `failed`, `unknown`, `released` | Complete the attempt; no success consumption receipt. |
195
-
196
- If receipt writing throws, both receipt writes and attempt completion must roll back. Do not reselect
197
- items or split `complete` and `recordConsumed` into separately awaited callbacks. Captured ownership
198
- still fences settlement; request cancellation and new delivery deferrals do not. TypeScript cannot
199
- supply isolation for a caller, so this is a mandatory port contract, tested in Square's real transaction.
200
-
201
- Presentation reports distinguish observed sink behavior from durable persistence. A returned sink
202
- with `not-recorded` or `error` still returned successfully; that alone proves only the supplied sink's
203
- documented effect, not model consumption or human display. Square retains throwing sinks as unknown and rethrows the original exception after draining.
204
- A missing native landing event or a canceled wait cannot prove the message never entered the host. Its single result stays
205
- `{ presented: true, activity }` only on recorded success, false on ownership refusal; unexpected
206
- success-settlement errors propagate. Batch returns its original sink value after all settlements,
207
- with the same error propagation. No schema, storage format or public Square result changes are needed.
1
+ # Delivery ledger
2
+
3
+ `@astrosheep/square/delivery-ledger` exports `DeliveryLedger` and its types, also available from the
4
+ package root. The per-user ledger defaults to `~/.square/delivery.v3.sqlite` and stores producer-neutral,
5
+ finished text cards, session attachment, routes, attempts and receipts. Producers own content and
6
+ recipient selection; adapters own transport behavior. Routes contain only `{kind, address}`.
7
+ A card may also carry producer-neutral presentation `details`:
8
+ `{kind: 'presentation', origin, author?, ref?, badge?, body}`. `text` remains the bounded model-facing
9
+ card; `body` holds the full Markdown for display. Receivers validate these details and render them
10
+ without opening producer storage. Cards without valid presentation details remain plain text.
11
+
12
+ Posting is idempotent by owner and key. Producer-push handoff and receiver-pull presentation have
13
+ separate attempt tracks, with cross-track exclusion based on ledger attempt state. Accepted or unknown
14
+ handoff does not establish presentation; uncertain effects must not be blindly replayed. Presentation
15
+ receipts record only the cards confirmed by the receiving boundary. A receipt means observed at that
16
+ boundary, not model comprehension. An observer may not detect a later plugin removing a card.
17
+ Receipt retention is finite: once evidence expires, Square retains unproved local unread until catch
18
+ or ACK, rather than inventing consumption or reinserting an uncertain old card.
19
+
20
+ The ledger is an outbox. Accepted and unknown attempts drop card text and details in the same
21
+ transaction, retaining only custody so an exact late observation can still write a receipt. A presented
22
+ presentation attempt deletes its card and attempts once no attempt is dispatching; a permanent exact
23
+ suppression prevents reposting the key. Unconfirmed accepted/unknown custody expires only after
24
+ 30 days from its latest settlement. Failed and released attempts keep content for retry. Receipts and
25
+ permanent suppressions expire in bounded batches after 30 days. Deleted sessions lose cards, attempts
26
+ and suppressions; sessions detached longer than 30 days lose cards and attempts. Session rows remain:
27
+ a deleted row is the authority for producer reconciliation and cannot be registered again.
28
+
29
+ Square folds receipts lazily into its own read state only for full activity cards; a shortened activity
30
+ card points to `square_catch` and cannot consume the full activity. Catch permanently suppresses the
31
+ corresponding ledger card where possible. Cancellation suppresses outstanding work without claiming it was seen and cannot
32
+ recall an external send already past its final check. Producers can call
33
+ `ledger.suppress({owner, key, scope: 'exact' | 'prefix', expiresAt?})` to suppress one key or a key
34
+ prefix. Omit `expiresAt` for permanent suppression within ledger retention, or supply an expiry for
35
+ temporary suppression. This does not consume Square activity; producer-owned exact ACK is described
36
+ in [square-api.md](square-api.md). The ledger does not match producer metadata.
37
+
38
+ Square posting, the Codex Stop watcher and OpenCode receivers retain their original ledger connection while
39
+ an already-invoked handoff has a pending `lateSettlement`. Timeout or cancellation returns promptly;
40
+ the connection closes after the last captured effect settles. It holds no open SQLite transaction and
41
+ grants no further send authority after the caller stops. An effect that never settles may retain an
42
+ idle connection until process exit. Late persistence failures produce redacted diagnostics rather than
43
+ reopening the ledger or retrying the effect. Pi presentation and Claude's independent exact-reference
44
+ confirmation callbacks retain their existing lifetimes.
45
+
46
+ Codex producers only post. Its native async Stop receiver observes the ledger without holding claims
47
+ while waiting; active UserPromptSubmit, PreToolUse and PostToolUse boundaries present synchronously.
48
+ See [Codex delivery](codex-delivery.md) for exact idle-epoch fencing, cancellation and recovery limits.