@astrosheep/square 0.3.106 → 0.4.1

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 (206) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +6 -12
  5. package/dist/artifact.js +43 -344
  6. package/dist/attention-presentation.d.ts +13 -7
  7. package/dist/attention-presentation.js +29 -68
  8. package/dist/boundary-presentation.d.ts +3 -16
  9. package/dist/boundary-presentation.js +29 -109
  10. package/dist/card-presentation.d.ts +1 -0
  11. package/dist/card-presentation.js +2 -0
  12. package/dist/catch-decisions.d.ts +2 -2
  13. package/dist/catch-decisions.js +7 -3
  14. package/dist/catch-selection.d.ts +8 -0
  15. package/dist/catch-selection.js +5 -0
  16. package/dist/claude-delivery.d.ts +14 -12
  17. package/dist/claude-delivery.js +42 -142
  18. package/dist/claude-mcp-context.js +2 -2
  19. package/dist/claude-mod.js +28 -140
  20. package/dist/claude-transport.d.ts +5 -0
  21. package/dist/claude-transport.js +34 -0
  22. package/dist/cli/context.d.ts +2 -2
  23. package/dist/cli/context.js +7 -10
  24. package/dist/cli/maintenance-commands.d.ts +0 -4
  25. package/dist/cli/maintenance-commands.js +2 -29
  26. package/dist/cli/observation-commands.d.ts +2 -0
  27. package/dist/cli/observation-commands.js +12 -2
  28. package/dist/cli/program.js +1 -1
  29. package/dist/cli/registry.js +4 -3
  30. package/dist/codex-address.d.ts +2 -2
  31. package/dist/codex-boundary-state.d.ts +1 -5
  32. package/dist/codex-boundary-state.js +0 -4
  33. package/dist/codex-hook.d.ts +7 -10
  34. package/dist/codex-hook.js +69 -33
  35. package/dist/codex-queue.d.ts +10 -11
  36. package/dist/codex-queue.js +32 -34
  37. package/dist/codex-watch.d.ts +2 -0
  38. package/dist/codex-watch.js +100 -0
  39. package/dist/decisions.js +3 -3
  40. package/dist/delivery-deferral.d.ts +1 -19
  41. package/dist/delivery-deferral.js +1 -29
  42. package/dist/delivery-frame.d.ts +7 -0
  43. package/dist/delivery-frame.js +11 -0
  44. package/dist/delivery-ledger-sqlite.d.ts +97 -0
  45. package/dist/delivery-ledger-sqlite.js +496 -0
  46. package/dist/delivery-ledger.d.ts +196 -0
  47. package/dist/delivery-ledger.js +268 -0
  48. package/dist/delivery-lifetime.d.ts +6 -0
  49. package/dist/delivery-lifetime.js +45 -0
  50. package/dist/harness-links.js +61 -7
  51. package/dist/harness.js +12 -17
  52. package/dist/help.js +3 -6
  53. package/dist/http-delivery.d.ts +5 -9
  54. package/dist/http-delivery.js +20 -79
  55. package/dist/inbox.d.ts +6 -57
  56. package/dist/inbox.js +24 -380
  57. package/dist/index.d.ts +7 -4
  58. package/dist/index.js +5 -2
  59. package/dist/mcp.d.ts +2 -1
  60. package/dist/mcp.js +40 -70
  61. package/dist/message.d.ts +7 -0
  62. package/dist/message.js +23 -0
  63. package/dist/model.d.ts +15 -91
  64. package/dist/model.js +2 -140
  65. package/dist/neutral.d.ts +72 -0
  66. package/dist/neutral.js +27 -0
  67. package/dist/notifications.d.ts +24 -26
  68. package/dist/notifications.js +39 -143
  69. package/dist/operation-control.d.ts +4 -0
  70. package/dist/operation-control.js +20 -0
  71. package/dist/operation-presentation.d.ts +1 -2
  72. package/dist/operation-presentation.js +1 -7
  73. package/dist/owner.d.ts +2 -2
  74. package/dist/owner.js +1 -1
  75. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  76. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  77. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  78. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  79. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  80. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  81. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  82. package/dist/packages/agent-delivery/src/codex.js +41 -4
  83. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  84. package/dist/packages/agent-delivery/src/index.js +1 -1
  85. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  86. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  87. package/dist/paseo-delivery.d.ts +5 -5
  88. package/dist/paseo-delivery.js +19 -99
  89. package/dist/paseo.d.ts +1 -1
  90. package/dist/paseo.js +1 -1
  91. package/dist/plugins/opencode/index.d.ts +5 -3
  92. package/dist/plugins/opencode/index.js +99 -21
  93. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  94. package/dist/plugins/opencode-shared/delivery.js +109 -0
  95. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  96. package/dist/plugins/opencode-v1/index.js +41 -0
  97. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  98. package/dist/plugins/opencode-v1/runtime.js +152 -0
  99. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  100. package/dist/plugins/opencode-v1/transport.js +50 -0
  101. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  102. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  103. package/dist/ports.d.ts +33 -214
  104. package/dist/presentation.d.ts +5 -5
  105. package/dist/presentation.js +1 -0
  106. package/dist/runtime.js +2 -2
  107. package/dist/search.js +1 -1
  108. package/dist/session-lifecycle.d.ts +8 -7
  109. package/dist/session-lifecycle.js +40 -42
  110. package/dist/sqlite-framing.d.ts +8 -0
  111. package/dist/sqlite-framing.js +31 -0
  112. package/dist/sqlite-square.d.ts +16 -26
  113. package/dist/sqlite-square.js +199 -262
  114. package/dist/sqlite-values.d.ts +21 -0
  115. package/dist/sqlite-values.js +78 -0
  116. package/dist/square-api.d.ts +1 -1
  117. package/dist/square-api.js +5 -8
  118. package/dist/square-core.d.ts +0 -1
  119. package/dist/square-domain.d.ts +12 -10
  120. package/dist/square-domain.js +105 -223
  121. package/dist/square-facade.d.ts +25 -76
  122. package/dist/square-location.d.ts +6 -1
  123. package/dist/square-location.js +1 -1
  124. package/dist/square-post.d.ts +53 -0
  125. package/dist/square-post.js +4 -0
  126. package/dist/square-posting.d.ts +13 -0
  127. package/dist/square-posting.js +174 -0
  128. package/dist/square-producer-sqlite.d.ts +11 -0
  129. package/dist/square-producer-sqlite.js +210 -0
  130. package/dist/square-receive.d.ts +2 -3
  131. package/dist/square-sqlite.d.ts +33 -42
  132. package/dist/square-sqlite.js +236 -768
  133. package/dist/square-tools.d.ts +8 -0
  134. package/dist/square-tools.js +39 -0
  135. package/dist/square-values.d.ts +27 -0
  136. package/dist/square-values.js +90 -0
  137. package/dist/views.d.ts +6 -6
  138. package/dist/views.js +14 -9
  139. package/dist/wake-sink.d.ts +4 -11
  140. package/dist/wake-sink.js +8 -32
  141. package/dist/wake-transports.d.ts +4 -0
  142. package/dist/wake-transports.js +11 -0
  143. package/examples/symmetric-domains.mjs +29 -249
  144. package/guides/change-waits.md +13 -53
  145. package/guides/claude-native.md +19 -172
  146. package/guides/codex-delivery.md +55 -78
  147. package/guides/delivery-primitives.md +48 -207
  148. package/guides/operation-cancellation.md +2 -5
  149. package/guides/participant-ownership.md +25 -89
  150. package/guides/pi-native-delivery.md +19 -56
  151. package/guides/pi-tools.md +6 -8
  152. package/guides/row-runtime-delivery-evidence.md +2 -0
  153. package/guides/row-storage-design.md +19 -44
  154. package/guides/square-api.md +113 -538
  155. package/package.json +8 -8
  156. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  157. package/plugins/claude/hooks/hooks.json +1 -1
  158. package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
  159. package/plugins/claude/types.d.ts +1 -1
  160. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  161. package/plugins/codex/hooks/hooks.json +11 -23
  162. package/plugins/opencode/index.ts +91 -23
  163. package/plugins/opencode-shared/delivery.ts +91 -0
  164. package/plugins/opencode-v1/index.ts +36 -0
  165. package/plugins/opencode-v1/runtime.ts +134 -0
  166. package/plugins/opencode-v1/transport.ts +41 -0
  167. package/plugins/paseo/OVERVIEW.md +5 -5
  168. package/plugins/paseo/README.md +7 -6
  169. package/plugins/paseo/client/markdown.tsx +5 -5
  170. package/plugins/paseo/client/square-message.tsx +28 -24
  171. package/plugins/paseo/client/transform.ts +8 -5
  172. package/plugins/paseo/shared/card-presentation.ts +16 -0
  173. package/plugins/paseo/shared/message.ts +6 -11
  174. package/plugins/pi/square-pi.js +167 -451
  175. package/dist/channel.d.ts +0 -74
  176. package/dist/channel.js +0 -92
  177. package/dist/claude-delivery-status.d.ts +0 -13
  178. package/dist/claude-delivery-status.js +0 -46
  179. package/dist/cmd/notify-once.d.ts +0 -2
  180. package/dist/cmd/notify-once.js +0 -29
  181. package/dist/delivery-attempts.d.ts +0 -52
  182. package/dist/delivery-attempts.js +0 -73
  183. package/dist/delivery-effects.d.ts +0 -139
  184. package/dist/delivery-effects.js +0 -163
  185. package/dist/delivery-health.d.ts +0 -51
  186. package/dist/delivery-health.js +0 -173
  187. package/dist/delivery-operations.d.ts +0 -9
  188. package/dist/delivery-operations.js +0 -168
  189. package/dist/delivery-primitives.d.ts +0 -5
  190. package/dist/delivery-primitives.js +0 -3
  191. package/dist/delivery.d.ts +0 -45
  192. package/dist/delivery.js +0 -106
  193. package/dist/plugins/opencode/delivery.d.ts +0 -19
  194. package/dist/plugins/opencode/delivery.js +0 -238
  195. package/dist/presentation-operations.d.ts +0 -18
  196. package/dist/presentation-operations.js +0 -86
  197. package/dist/square-index.d.ts +0 -16
  198. package/dist/square-index.js +0 -106
  199. package/dist/square-legacy-framing.d.ts +0 -11
  200. package/dist/square-legacy-framing.js +0 -64
  201. package/dist/square-migration-host-ledger.d.ts +0 -26
  202. package/dist/square-migration-host-ledger.js +0 -169
  203. package/dist/square-migration.d.ts +0 -30
  204. package/dist/square-migration.js +0 -599
  205. package/guides/migration.md +0 -156
  206. package/plugins/opencode/delivery.ts +0 -211
@@ -1,37 +1,18 @@
1
1
  # Square and Participant API
2
2
 
3
- `Square` opens the concrete local SQLite resource shared by SDK, CLI, MCP and Pi. `square-api.ts`
4
- exports the public `{at, build, inMemory}` entrypoints and `type Square`; `square.ts` remains the
5
- executable CLI entrypoint.
6
- For audience computation without opening a square, see the [attention primitive](attention-primitive.md).
7
- For transaction-scoped attempt custody and the storage-independent dispatch/prepared presentation
8
- lifecycles used by Square's actual adapters, see the [delivery primitives](delivery-primitives.md).
9
- Full presentation settlement atomically completes its captured attempt and full-consumption receipts;
10
- clipped previews record no full receipt. Cancellation/deferral is pre-effect policy, never a veto of a
11
- successful sink. The public primitive distinguishes sink observations from refused/failed persistence;
12
- Square retains its existing result shapes and explicit failed classification for rejected sinks.
13
- For caller-owned item admission and sparse consumption, see the [receive primitive](receive-primitive.md).
14
- `square-facade.ts` contains only public types. There is no separate operation factory, policy
15
- dispatcher or `{data, projection}` envelope.
3
+ `Square` is the concrete resource shared by SDK, CLI, MCP and Pi; `square.ts` is the CLI
4
+ entrypoint. `square-facade.ts` declares public operation types, not a second execution path. See
5
+ [attention-primitive.md](attention-primitive.md) for audience computation and
6
+ [delivery-primitives.md](delivery-primitives.md) for the host delivery ledger.
16
7
 
17
- ## Open, select, join, close
18
-
19
- Membership is explicit: no harness joins a square based on its filename or working directory.
20
- `PUBLIC.square` has exactly the same behavior as any other square file. Membership (join/done,
21
- read horizon and listens) is separate from host attachment (whether an exact owner is currently
22
- executing for that participant). Host shutdown detaches; it does not make the participant leave.
23
- An owner is an opaque, non-secret string compared only for equality. It is not a participant name
24
- or authentication credential: one owner may control several names. The SDK requires it explicitly;
25
- `localUserOwner()` supplies `user:<OS username>@<hostname>` when that is the intended actor.
8
+ ## Open and join
26
9
 
27
10
  ```ts
28
11
  import { Square } from '@astrosheep/square';
29
12
 
30
- const owner = 'example:alice-process';
31
13
  const square = await Square.at({ path: './discussion.square' });
32
14
  try {
33
- const joined = await square.join('Alice', { owner });
34
- const alice = joined.participant;
15
+ const { participant: alice } = await square.join('Alice', { owner: 'example:alice' });
35
16
  const sent = await alice.express('Hello @Bob', { force: true });
36
17
  const page = await square.history();
37
18
  console.log(sent.activity.id, page.activities);
@@ -40,516 +21,110 @@ try {
40
21
  }
41
22
  ```
42
23
 
43
- - `Square.build({path, markdown, hardCap?, throttlePerMinute?, template?, force?, ...})` returns
44
- the same open resource. Public `Square.at` and `Square.build` resolve an omitted `wakeTransport`
45
- once from `env ?? process.env` to the product's Claude/Codex/Paseo/HTTP defaults; if creation fails,
46
- opening still succeeds without proactive wake. A supplied port is used unchanged; `false`
47
- disables proactive wake. Activities still land normally without a transport.
48
- - Synchronous `Square.inMemory` uses only an explicitly supplied port; omitted or `false` means
49
- no wake. An injected transport runs the same claim, deferral and final currentness checks as
50
- file delivery. Each memory resource has a unique `memory://` identity; this is not a remotely
51
- openable address, is not indexed, and does not enable file-based hooks.
52
- - `Square.at` accepts `path`, optional `cwd`, `env`, `clock`, `wakeTransport` and a caller-owned
53
- connection via `resources`. At entry, `env` resolves the path, clock and discovery index path
54
- once. `Square.at`, `Square.build` and `Square.inMemory` do not capture a
55
- `owner`, `wakeAddress` or default `participant`. The same resource can serve multiple owners;
56
- identity is supplied at each join or participant selection. `wakeTransport` injection is retained
57
- for both file-backed build/open and memory resources.
58
- - Local artifact paths resolve against `cwd`; an omitted path uses `SQUARE_LOCATION` at entry.
59
- - `await square.participant(name, {owner})` selects an **attached, standing** seat held by that exact
60
- owner without joining, claiming or reentering. It never manufactures a ghost handle: a never-joined,
61
- done, foreign or detached seat is refused with `already_joined`, `reason: 'needs_seat'` and a `join`
62
- recovery fact.
63
- Foreign custody still requires explicit takeover. The returned handle captures the current owner
64
- and generation; subsequent mutations recheck custody. A name alone never authorizes writes.
65
- - `await square.seatsOf(owner)` returns the owner's standing seats as
66
- `[{name, generation, attachment}]`, including attached and detached seats but not done participants.
67
- It is read-only and neither joins nor restores a seat. `selectOwnSeat(square, owner, name?)` is the
68
- adapter helper: an explicit name is checked through `participant`; otherwise exactly one attached
69
- seat is required. For an overview-capable call,
70
- `selectOwnSeat(square, owner, undefined, {optional: true})` instead returns `undefined` for a missing
71
- owner or zero/multiple attached seats. Selection does not change membership.
72
- - Join with an explicit name and owner: `square.join('Alice', {owner, wakeAddress?, ...options}, control?)`
73
- returns one `JoinResult`: `{participant, kind, activity, observation?}`. The name is required even
74
- when the owner holds exactly one seat. `participant` is a handle, not a string. `takeover: true`
75
- atomically replaces a standing seat; a rejoin after done establishes a new join generation.
76
- Same-owner reconnection retains its generation. A detached seat reattaches with kind `reconnected`,
77
- no activity and no change to its read horizon or listens; the operation supplies any wake address.
78
- A foreign owner still needs explicit takeover, including for an away participant. Old handles remain
79
- fenced after takeover or rejoin, including when the same owner reacquires the name. There is no
80
- separate `Participant.join()` or public `takeover()` API.
81
- - Owner selection prevents accidental custody reuse, not malicious impersonation; never let untrusted
82
- input choose another caller's owner. A Square owns its connection unless
83
- `resources: createSquareResourceOwner()` lends it one. Closing a borrowing resource leaves that
84
- owner open. Request signals never become the borrowed connection's lifetime. The MCP server drains
85
- cancelled/in-flight work before closing its owner; explicit location overrides get independent
86
- request-owned connections.
87
-
88
- The rebuildable HOME discovery index is only a source of candidate paths, never custody authority.
89
- File-backed successful joins and non-null reattachments attempt best-effort publication of their
90
- owner and path; opening, building, selecting a participant or expressing does not publish. Memory
91
- resources are not indexed. Losing the index may delay discovery, but cannot transfer a seat.
92
-
93
- ## Adapter owner resolution
94
-
95
- Owners are nonempty strings without control characters, at most 512 UTF-8 bytes. The core stores
96
- and compares them without interpreting harnesses. One adapter codec, `harnessOwner`, escapes each
97
- coordinate segment with `encodeURIComponent`: `<channel>:<session>[:agent:<agent>]`.
98
- `decodeHarnessOwner` is for adapters only; custom owner strings need no harness spelling.
99
-
100
- CLI uses `--owner` or `SQUARE_OWNER` exactly when supplied, then one complete native harness
101
- coordinate (Paseo is auxiliary), otherwise `localUserOwner()`. Multiple native identities or an
102
- incomplete Claude-child identity refuse rather than using the human's owner. Plain terminal owners
103
- have no automatic host lifecycle or wake address: join explicitly and use `catch --idle`.
104
- MCP uses the call-local harness coordinate, else its configured owner, never tool arguments. Pi and
105
- native adapters encode their own coordinate; the Slack bridge uses its local configured owner.
106
- `inbox(input, owner)` and `square inbox --for-owner <owner>` select exact opaque equality.
107
-
108
- ## Host attachment lifecycle
109
-
110
- `square.detach(name, {owner, generation})` checks the exact owner and generation inside the artifact
111
- transaction. It returns `true` when it detaches, or `null` for stale, replaced, foreign, done or
112
- already-detached custody. It lands no activity, preserves membership/read horizon/listens, and
113
- clears wake readiness, catch ownership and delivery deferrals. Detached handles cannot write,
114
- including an empty catch; reattachment or same-owner rejoin restores access. Proactive claims,
115
- observers and final send/presentation checks exclude detached seats. Existing receipts and delivery
116
- evidence survive. A detached participant stays away indefinitely; no timer manufactures a departure.
117
-
118
- `square.reattach(name, {owner, wakeAddress?}, control?)` restores a standing seat of that exact owner
119
- and returns a `Participant`, or `null` if the seat is absent, foreign or done; it never creates
120
- membership. It preserves the generation, read horizon and listens. On an already attached seat it
121
- returns a handle and can refresh the supplied wake address, subject to native-route preference.
122
- Host start/resume restores detached seats found with `seatsOf(owner)` before arming delivery,
123
- independently across seats and locations. Concurrent departure or takeover remains fenced; a foreign
124
- takeover between the initial seat read and transaction can refuse instead of returning `null`.
125
- New/forked owners restore nothing, and hosts do not recover from historical done coordinates.
126
-
127
- There are three endings: participant `done` ends membership; Pi shutdown, Codex SessionEnd and
128
- Claude session end detach only; host-confirmed permanent deletion uses
129
- `square.endHeld(name, {owner, generation})` to land a fenced `done` on an attached or detached seat,
130
- returning its lifecycle result or `null` when custody no longer matches. OpenCode `session.deleted`
131
- is permanent deletion. Takeover replaces either attachment state with `done -> join`; late cleanup
132
- or restoration by the old owner cannot reclaim it.
133
-
134
- `square.cancelDeliveries(name, {owner, generation})` is also generation-fenced: for an attached
135
- current seat it advances the suppression boundary through the current activity head, returning
136
- whether it changed. A mismatched seat returns `false`; a detached or concurrently replaced seat may
137
- be refused. It does not consume activity or recall an external send already past its final check.
138
-
139
- ## HTTP(S) wake notifications
140
-
141
- File-backed public opens include a built-in `http` wake transport for both HTTP and HTTPS.
142
- Register its address through SDK join or same-owner `reattach`:
143
-
144
- ```ts
145
- const { participant: bob } = await square.join('Bob', {
146
- owner: 'example:bob-process',
147
- wakeAddress: { kind: 'http', address: {
148
- url: 'https://example.com/square/wake',
149
- tokenEnv: 'BOB_WAKE_TOKEN', // optional; sender process environment variable
150
- } },
151
- });
152
- ```
153
-
154
- The URL must have an HTTP(S) scheme and hostname, with no embedded username/password or fragment.
155
- Local/private endpoints are allowed. HTTPS uses normal certificate validation. The sender POSTs
156
- `Content-Type: application/json` to that URL without following redirects. When `tokenEnv` is
157
- configured, the sender resolves it in its own environment (`env` at open, otherwise `process.env`),
158
- not the receiver's. A missing, blank or invalid bearer token refuses locally without HTTP I/O.
159
- Keep the secret in that environment variable, never in the stored address. Diagnostics do not echo
160
- URLs or raw network errors; URL query strings may themselves be sensitive and remain stored config.
161
-
162
- The JSON body contains only:
163
-
164
- ```json
165
- {
166
- "location": "/sender/path/discussion.square",
167
- "participant": "Bob",
168
- "activity": "act/12",
169
- "actor": "Alice",
170
- "attemptN": 0,
171
- "preview": "<existing rendered Square wake reminder>"
172
- }
173
- ```
174
-
175
- `attemptN` is omitted when unavailable. The preview is the existing rendered awareness reminder,
176
- not a new payload template or full artifact. Owner, route and claim token are never included. `location` is the
177
- **sender's artifact locator**, not a downloadable URL. HTTP adds wake notification, not a remote
178
- Square backend: the receiver owns how it queues/wakes its agent and gives it access to Square.
179
-
180
- Return 2xx promptly only after accepting wake custody. A 2xx records an accepted wake, **not** catch
181
- or presentation; activity remains unread until normal consumption. Redirects and 4xx are rejected,
182
- 5xx are transient failures, and other non-2xx statuses are rejected. A network error or timeout
183
- after sending starts is **unknown**: the receiver may already have accepted. Unknown/accepted wakes
184
- are not automatically replayed. There is no retry loop in the HTTP adapter; existing delivery policy
185
- owns future dispatch. Local configuration, absent preview, expired pre-send budget or a refused final
186
- gate sends nothing. One deadline covers preparation, the final gate and waiting for response headers;
187
- response bodies are cancelled rather than read. Claims, per-owner ordering, deferral, attachment and
188
- cancellation checks are shared with every transport. `wakeTransport: false` still disables wakes,
189
- and an injected transport remains authoritative.
190
-
191
- The enclosing dispatcher's safety deadline is transport-neutral: if an attempt is still unsettled,
192
- it records conservative **unknown**, even when HTTP has not started (for example, while the final
193
- send gate is stalled). A local pre-send expiry is known no-send only when the adapter's result arrives
194
- before that outer fallback. HTTP still must not start I/O after a stalled gate resumes past its
195
- deadline; the persisted unknown remains and prevents automatic replay. This does not change the
196
- shared dispatcher's timeout or retry policy.
197
-
198
- ## One set of operations and defaults
199
-
200
- Square owns `history`, `status`, `participants`, `stream`, and recovery observations.
201
- A file-backed `stream` follows the path with a fresh artifact read for each page, so atomic file
202
- replacement is visible even while the resource holds its original connection. Its iterator owns
203
- its observation lifetime: `return()` interrupts a pending pull; closing a local Square does not close
204
- that independent path observation.
205
- Internal readers that explicitly borrow an artifact port leave its lifetime with the lender and
206
- propagate port failures rather than waiting for path repair.
207
-
208
- `Activity` is `{id, at, kind, actor, body?, metadata?, mentions, reach?, target?, reply?}` throughout
209
- the SDK. Stream without a recipient yields these activities; with a recipient it selects
210
- their landed directed activities and adds `perception` and the landing route `via`
211
- (`mention`, `reply`, `attention`, or `bell`). Perception follows the viewer's actual landed audience;
212
- `via` is absent when no directed route exists. Stream starts at a bounded tail or after an `ActivityId`
213
- like `act/12`; it never consumes activity.
214
- Participant owns `express`, `catch`, `pending`, `ack`, `listen`, `unlisten`, `ignore`, `listening`, `done`,
215
- `deferDelivery`, `renewDeliveryDeferral`, and `releaseDeliveryDeferral`. History belongs only to
216
- Square; there is no `Participant.history`.
217
-
218
- - Express waits when throttled unless `noWait: true`, for **every** caller. `ExpressResult`
219
- includes the committed `activity`, optional landing-time warnings (`unmatchedMentions`,
220
- `replyAuthorNotHere`), `hint` and `observation`. Warnings are not stored on the activity.
221
- - A say may carry `metadata`, an immutable JSON object with nested JSON values and a maximum serialized
222
- UTF-8 size of 16 KiB (`ACTIVITY_METADATA_MAX_BYTES`). `alice.express({body?, metadata?, ...options}, control?)`
223
- is the object form; the existing `express(body, options?, control?)` form remains valid. A non-empty
224
- body may carry `{}`; an omitted, empty or whitespace-only body requires non-empty metadata. Metadata-only
225
- says omit `body` from public activity records. Invalid objects, cycles, unsupported values and oversized
226
- metadata refuse before landing with `activityUnsent`. Metadata is carried unchanged by express, history,
227
- catch, status/latest, unread projections and stream records through the SDK, CLI and stream.
228
- MCP and Pi tools are not a metadata surface: their `express` requires a body and its schema rejects a
229
- `metadata` argument. Tool history/catch return rendered text with a structured-activity placeholder,
230
- not metadata JSON or activity objects. Metadata does not affect mentions, replies, listening, audience,
231
- ordering, consumption or permissions. A current participant may temporarily defer proactive delivery
232
- by exact metadata match through the SDK lease API below. Metadata may nest up to 256 levels; the bound is enforced before
233
- persistence and public reads. Human text uses a concise structured-activity placeholder and never prints
234
- metadata JSON. SDK history and pending queries accept exact metadata matches; there is no query
235
- language or metadata index. CLI `--metadata <json>` remains an express input flag.
236
- - Catch returns `{activities, remaining, reason, hint?, observation?}`. Each activity includes
237
- viewer-relative `perception` and `via`. Reasons are `drained`, `idle`, `capped`, `quorum`,
238
- `replaced`, or `already-active`. Omitted/zero idle is immediate; positive idle uses the single
239
- leased quiet wait. Receipt selection, perception, routes, counts and terminal facts settle
240
- in the receipt transaction. Cancellation/replacement releases only that waiter's lease and never
241
- consumes future activity.
242
- - All history callers return a `HistoryResult` object with `activities`. Default page size is ten;
243
- `limit: null` explicitly requests an unbounded archive. Numeric limits must be 1–100. Contextual
244
- queries still reject an implicit merged window over 100 activities; an explicit numeric limit
245
- bounds it. CLI/tool schemas accept numeric limits only. History never writes receipts; history
246
- observations retain lifetime say ordinals for CLI `history --format number`.
247
- - `participants()` returns `{participants, observation?}` and `listening()` returns
248
- `{listening, observation?}`, not arrays carrying metadata. Participant summaries use `presence`
249
- (`never-joined`, `active`, `watching`, `away`, or `done`). Away participants remain members,
250
- count toward in-square totals and keep receiving normal bell, mention and listened audience. Its roster
251
- supplies the participant counts. Core `square.status({viewer})` uses only the explicitly supplied viewer; with
252
- no viewer it returns an unfiltered overview without viewer-specific `listening`. Core status does
253
- not select or authorize a seat. CLI and tools check an explicit `--as`/`as` with `selectOwnSeat`;
254
- otherwise exactly one attached seat of the caller's owner becomes the viewer, while zero or multiple
255
- seats (or no owner) produce the overview. Snapshot participants use the same `name`, `presence` and
256
- `listening` facts, excluding never-joined people; its detached `delivered(name, id)` predicate
257
- remains available.
258
- - `unlisten(target)` removes only that participant's current listen edge to the target, without
259
- leaving or blocking future mentions/replies. It returns the same `ListenerChangeResult` as listen
260
- and ignore, with `activity: null` when no listen exists. Existing ignores are unchanged. Earlier
261
- landed audiences and receipts are never rewritten; listening again remains available.
262
- - Lifecycle operations return `{activity, observation?}` and use the same artifact custody gate;
263
- observations do not repeat the activity. Join entry uses public activities with settled perception,
264
- and status observations use public join activities. Done accepts no message and retires only its
265
- attached, captured owner/join generation; it never implicitly rejoins. A done participant is not
266
- recoverable authority for any host path.
267
- - Recovery summarizes unread directed activity with per-sender count, latest age and the single
268
- greatest-index preview. Age uses the greatest timestamp, which may belong to another activity.
269
-
270
- Operations that accept an `OperationControl` use a final argument with `signal` and optional typed
271
- `onProgress`. Listener/lifecycle operations take optional observation options before that argument;
272
- participant selection, seat discovery and host cleanup methods do not take an `OperationControl`.
273
-
274
- ## Shared history and pending filters
275
-
276
- `HistoryQuery` and `PendingOptions` extend the same exported `ActivityQuery`:
277
-
278
- ```ts
279
- interface ActivityQuery {
280
- at?: readonly ActivityId[];
281
- from?: readonly string[];
282
- match?: MetadataMatch;
283
- search?: { text: string; mode: 'literal' | 'regex' };
284
- cursor?: { id: ActivityId; direction: 'before' | 'after' };
285
- }
286
- ```
287
-
288
- - Filters combine with AND. IDs in `at` and senders in `from` each combine with OR; an absent array
289
- is unrestricted, while an explicitly empty array matches nothing. `at` is an exact ID selection;
290
- only history can expand those IDs with `contextBefore` / `contextAfter`.
291
- - Sender names use case-insensitive identity matching and need not belong to a joined participant.
292
- Direct senders such as `keiyaku-notice` can be queried without inventing membership. A valid but
293
- unmatched sender returns no activities, not an unknown-participant error.
294
- - `match` uses the same nested own-property, exact type-sensitive scalar equalities as deferrals.
295
- `search` tests each activity's ID, sender and body independently, case-insensitively. Literal mode
296
- treats regex characters as plain text; regex mode uses JavaScript's built-in regular expressions,
297
- without a ReDoS timeout. Search text must be nonempty and regex syntax must be valid. History
298
- searches says and departures, not joins; pending always selects addressed, unconsumed says.
299
- - A cursor is an exclusive activity-ID boundary, independent of `limit`: before excludes the anchor
300
- and everything newer; after excludes the anchor and everything older. No consumption is involved.
301
- Storage applies the shared filters before limiting and uses the same predicates for counts.
302
- - History keeps its archive-specific `order`, `mention`, `since` and context options. By default it
303
- selects the newest page and returns it oldest first; an after cursor selects the oldest following
304
- page. `order: 'desc'` reverses the selected page, not which activities belong to it. Numeric limits
305
- stay 1–100, default ten; `limit: null` reads all matches. Exact/context history keeps its existing
306
- implicit full-window selection with a 100-activity bound unless a numeric limit is supplied.
307
- - Pending always selects oldest first, including with a before cursor. Its default and maximum page
308
- size remain 100, and `remaining` counts matching unconsumed activities beyond that page within
309
- the supplied cursor boundary. It has no history context, ordering or audience override.
310
-
311
- The SDK accepts only `search` and `cursor`, not the removed `grep` / `fixed` / `before` / `after`
312
- fields. CLI flags and tool arguments keep those familiar spellings and translate at the adapter
313
- boundary, e.g. `--fixed text` becomes `search: {text, mode: 'literal'}` and `--after act/12` becomes
314
- `cursor: {id: 'act/12', direction: 'after'}`. The artifact format and stored activities do not change.
315
-
316
- ## Pending query and exact acknowledgement
317
-
318
- `Participant.pending(options?, control?)` and `Participant.ack(ids, control?)` separate a read-only
319
- query from explicit consumption. `ChannelEndpoint` from `@astrosheep/square/channel` exposes the
320
- identical methods by forwarding to its captured Participant; it has no independent inbox or storage.
321
-
322
- `Channel.send` / `Square.send` address joined members, including detached members. Attachment gates
323
- proactive wake, not landing: a detached recipient retains its unread addressed activity without a
324
- wake and can query/ack it after reattachment. Recipient validation remains all-or-nothing; a missing
325
- or departed recipient rejects the entire send without landing activity or waking other recipients.
326
-
327
- ```ts
328
- import type { ChannelEndpoint, MetadataMatch } from '@astrosheep/square/channel';
329
-
330
- async function drain(endpoint: ChannelEndpoint, match: MetadataMatch) {
331
- for (;;) {
332
- const page = await endpoint.pending({ match });
333
- if (page.activities.length === 0) break;
334
- // Process the entire page successfully before acknowledging its exact IDs.
335
- for (const activity of page.activities) await processNotice(activity);
336
- const { consumed } = await endpoint.ack(page.activities.map(activity => activity.id));
337
- // consumed may be smaller if another consumer acknowledged some IDs first.
338
- }
339
- }
340
- ```
341
-
342
- - `pending(query: PendingOptions = {}, control?)` returns
343
- `{activities: readonly PerceivedActivity[], remaining: number}`. It selects only landed,
344
- addressed, unconsumed says, oldest first. Body, metadata, delivery information, perception and
345
- route use the same public records as catch; bodies are full stored bodies, not clipped previews.
346
- - Limit defaults to 100 and must be an integer from 1 through 100. The shared filters above are
347
- available alongside that limit. Storage applies addressedness, consumption and all supplied
348
- filters **before** limiting. `remaining` counts matching unconsumed activities outside the returned
349
- page in the same read transaction. It is not the global unread count or a durable cursor.
350
- - A pending query consumes nothing, changes no read horizon and writes no delivery/presentation
351
- evidence. Deferrals, catch leases, cancellation and prior clipped presentation do not hide these
352
- explicit reads. Like catch, pending includes historical addressed receipt holes before a rejoin;
353
- it is not the proactive delivery eligibility query.
354
- - `ack(ids: readonly ActivityId[], control?)` returns `{consumed: number}`, the count of newly
355
- consumed exact IDs. It validates that **every** supplied ID is addressed to this participant before
356
- recording any receipts. Malformed, nonexistent or unaddressed IDs reject the whole batch with
357
- `invalid_args` (`reason: 'not_addressed'` for valid but unaddressed IDs). There is no implicit prefix
358
- acknowledgement, no match reselection and no requirement for an earlier pending call.
359
- - Duplicate IDs, repeated acknowledgement and an empty array are idempotent. Existing receipt
360
- timestamps remain unchanged. Acknowledgement uses the same sparse receipts as catch/full
361
- presentation, so it removes those IDs from pending and ordinary catch, but exact catch remains
362
- repeatable. It does **not** assert wake acceptance or presentation and writes no such evidence.
363
- - Both operations require their captured current owner, generation and attached seat; even an empty
364
- query/ack cannot reuse a detached, retired or replaced handle. No storage format change is needed.
365
- Cancellation before the receipt transaction prevents consumption; a committed ack is not undone.
366
- - To drain all matching notices, keep querying and acknowledging pages until an **empty** page.
367
- Acknowledging one page exposes the next without a cursor. For read-only pagination instead, pass
368
- `cursor: {id: page.activities.at(-1)!.id, direction: 'after'}` with the same filters; earlier pages
369
- stay unconsumed. Do not silently stop after the default page or treat a snapshot `remaining: 0`
370
- as proof that concurrent producers have stopped. Query and
371
- ack are separate operations, not an exclusive claim or an exactly-once external-effect guarantee.
372
-
373
- ## Temporary proactive delivery deferral
374
-
375
- SDK Participants expose three operations. There are no CLI/MCP/Pi authoring
376
- commands or listing operation in this delivery. Existing proactive adapters still obey deferrals.
377
-
378
- ```ts
379
- const lease = await alice.deferDelivery({
380
- match: { fields: [
381
- { path: ['job', 'kind'], equals: 'outcome' },
382
- { path: ['job', 'id'], equals: 7 },
383
- ] },
384
- durationMs: 60_000,
385
- });
386
- try {
387
- // Explicit history and catch remain available, including catch({id}).
388
- const renewed = await alice.renewDeliveryDeferral(lease.id, 60_000);
389
- // null means absent, expired, or not owned by this current generation.
390
- } finally {
391
- await alice.releaseDeliveryDeferral(lease.id);
392
- }
393
- ```
394
-
395
- - `MetadataScalar` is `null | boolean | number | string`; numbers must be finite. `MetadataMatch`
396
- has nonempty `fields`, each `{path: readonly string[], equals: MetadataScalar}`. Paths have one
397
- or more nonempty string segments, with no duplicates. Only JSON object own properties are
398
- traversed: arrays, missing/nonobject intermediates and inherited properties do not match.
399
- Equalities are exact and type-sensitive. A dot inside one segment is a literal key, not path
400
- syntax. All fields combine with AND; any matching live lease blocks delivery (OR across leases).
401
- - Each acquire returns a fresh UUID and an operation-clock ISO `expiresAt`: `{id, expiresAt}`.
402
- Match objects are not returned. `durationMs` must be a positive safe integer and is capped at
403
- 24 hours. Invalid matches, durations and IDs refuse with `invalid_args` before mutation.
404
- - Renewal extends that same live lease from the renewal operation's clock; expired/missing IDs
405
- return `null`, and release returns `false`. Independent overlapping leases are never merged;
406
- releasing one cannot end another. A captured current owner/join generation is required for every
407
- mutation. Stale handles retain the ownership refusal; done/takeover/rejoin retire old leases.
408
- - Lease writes change delivery revision, not conversation history. They do not create a receipt,
409
- change audience or unread counts, hide an archive record, or prohibit explicit catch/pending/ack.
410
- Catch and ack consume normally; a consumed record is not replayed when a deferral ends. The catch
411
- lease is a separate, single quiet-wait lease with its existing filters and lifecycle.
412
- - Producer/sweep, atomic claims, session/native projections, final pre-send checks and proactive
413
- presentation all share current-generation unexpired matching. Suppressed items get no seen or
414
- presented receipt. An active pending observer rechecks on release and the earliest relevant
415
- expiry, including renewed deadlines, without needing a new conversation activity. A one-shot
416
- producer skips and returns promptly. With no active observer, eligibility is reconsidered at
417
- the next normal opportunity; no daemon or per-message timer is created.
418
- - Acquiring a lease is not recall. An external send already past its final freshness check can race
419
- with acquisition and remain dispatched; a sink effect already written cannot be withdrawn.
420
- Presentation eligibility is checked before the effect, not again as a veto after success. A batch
421
- claims and filters its items before its one sink call, then records exactly the successfully rendered
422
- set under each captured owner/join generation. A lease acquired while the sink returns or before a
423
- later item's evidence commit does not erase success or allow replay after release. Cancellation
424
- likewise stops pre-effect work, not truthful completion after a successful sink: evidence settles
425
- on the original connection with an independent bounded storage lifetime and the same seat fences.
426
- A rejected/cancelled native landing is not a successful sink. Items filtered
427
- out before the sink receive no success receipt.
428
- Cancellation before commit prevents the lease mutation; after commit it remains until explicit
429
- release, expiry, or seat retirement. See [operation-cancellation.md](operation-cancellation.md).
430
-
431
- ## Optional observations, not another execution path
432
-
433
- Request `observe: true` on the **same method** when rendering needs additional facts. Each operation
434
- has a concrete optional observation type, without conditional generics or a generic outcome wrapper.
435
- Data-only calls do not hydrate join history, the pre-expression feed, catch presence enrichment,
436
- or history/status presentation annotations.
437
-
438
- Express captures optional unread context before the attempt: the latest five directed,
439
- unseen activities after the current join, each preview capped at 400 Unicode characters,
440
- plus the count outside that preview. It hydrates only that page, not the entire cursor suffix.
441
- This is a nonconsuming preview even when a short body fits in full; consumption requires `catch`,
442
- explicit `ack`, or the normal full-notification presentation path. Count and time facts come
443
- from the committed transaction. Listener and lifecycle observations likewise use their mutation's
444
- facts, with no postcommit context read. Join uses its mutation's essential facts and treats optional
445
- entry/history enrichment as best-effort: `observation.enrichmentUnavailable` is explicit and the
446
- successful receipt survives. Catch preserves its committed page even if optional nearby presence
447
- fails. A cancellation or failed clock after commit cannot turn these receipts into an unsent retry.
448
-
449
- Delivery readiness is participant-specific, not a property of the Square resource.
450
- `ParticipantSummary.deliveryAvailable`, `JoinObservation.deliveryAvailable` and
451
- `CatchObservation.deliveryAvailable` reflect whether the participant's seat has a stored wake
452
- address. Catch captures this fact with its selection. Join and catch observations require
453
- `observe: true`. These flags do not prove that a receiver is live or that delivery succeeded.
454
-
455
- `OperationError.operation` supplements authoritative error facts with caller/filter/recipient and
456
- express certainty. Selected-row/execution failures remain unknown unless admission proved the
457
- expression unsent. CLI saves raw drafts even when opening the resource fails; its initial open
458
- receives the express abort signal, while recovery reopening is deliberately uncancelled.
459
-
460
- ## Adapters and standalone functions
461
-
462
- CLI and tools only adapt input, identity, resource lifetime and output. They do not import private
463
- actions, storage, admission, scanners or query execution. CLI `--json` for join, express, catch,
464
- history, listen, ignore, unlisten, listening, done, status, participants and inbox
465
- serializes the same SDK result without requesting `observe: true`. Only `observation` is omitted and
466
- a live Participant handle becomes `{name}`. History returns one JSON document, including an empty
467
- `activities` array; `history --format` remains human-oriented. `participants --limit` limits only
468
- human text, not the SDK result. Stream `--ndjson` remains one SDK stream record per line.
469
-
470
- MCP/Pi use the shared `square-tools.ts` definitions. All seven tools return only the CLI's rendered text, with no `outputSchema`
471
- or `structuredContent`. Join text includes its context and recent activity; express text
472
- includes landing ID/reach, warnings and bounded unread previews, never the sent body echo
473
- or internal observation tables. Failures remain failed tool results with readable recovery
474
- text. The SDK and CLI JSON keep their full domain receipts. The optional express observation
475
- contains `context: {activities: [{id, actor, preview, truncated}], remaining}`, not the old
476
- unbounded `pendingPublic`/ordinal/perception tables.
477
-
478
- Express still finishes attempted notification delivery before releasing its resource.
479
- It overlaps at most four receiving sessions per dispatch, preserving order within each
480
- session and draining started work even on failure. Claims, final freshness checks and
481
- uncertain-delivery suppression remain unchanged. Paseo inventory discovery is asynchronous;
482
- overlapping attempts share only the in-flight inventory read, never a cached idle status.
483
- The standalone `discoverPaseoAgents` export now returns a promise.
484
-
485
- `discover(input, options?, control?)`, `inbox(input, owner, options?, control?)`,
486
- `diagnoseSquare(input, control?)`, and `migrateSquare(input, control?)` are ordinary public exports.
487
- They are not methods on a synthetic cross-square object. Diagnose/migrate resolve a path without
488
- opening a valid current-format Square first.
489
-
490
- ## Artifact format and explicit migration
491
-
492
- The current artifact format is 7. It stores opaque owners rather than harness coordinates and
493
- preserves format 6's attached/detached distinction. Seats, delivery evidence, cancellation and
494
- independent delivery deferrals remain separate from conversation activities and catch ownership.
495
- Ordinary opens refuse older formats with migration guidance and never convert them implicitly.
496
-
497
- Stop all sessions and services using the artifact, make an offline backup, then run
498
- `square --location <path.square> migrate` and `doctor` before reopening with upgraded runtimes.
499
- Supported formats 2–6 convert to 7 in one transaction. Format 6's Body coordinates become encoded
500
- owners while attachment, generations, wakes, evidence and revisions survive. Formats 4/5 first gain
501
- attached seats; format 4 also gains empty deferral support. Formats 4–6 never consult HostLedger.
502
- Historic host-cleanup departures cannot be distinguished from voluntary `done` and stay done.
503
- For formats 2/3, also freeze and back up the legacy HostLedger used for verified owner/evidence
504
- import; no authority is manufactured for done participants. Replay-critical native ambiguity
505
- refuses rather than disappears, including any nonempty legacy token-only tombstones.
506
- Current-format migration is a byte-preserving no-op. See [migration.md](migration.md) for the
507
- narrow metadata policy, source-preserving refusal and filesystem requirements. Updating a package
508
- does not migrate files or install a new runtime.
509
-
510
- ## SDK breaking migration: opaque owners
511
-
512
- Artifact format 7 introduced opaque owners; format-6 artifacts required explicit migration before
513
- opening with that format. Current artifacts use format 8 and require explicit migration from format 7.
514
- `Body`, `sameBody` and `validateBody` are removed from the core and public API. Replace identity
515
- objects with an explicit owner at each identity-bearing operation; do not put identity on a resource:
516
-
517
- ```ts
518
- import { Square } from '@astrosheep/square';
519
-
520
- const aliceOwner = 'example:alice-process';
521
- const bobOwner = 'example:bob-process';
522
- const square = await Square.at({ path: './discussion.square' });
523
- try {
524
- // Previously: pass body/wakeAddress/participant to Square.at and join(name).
525
- const { participant: alice } = await square.join('Alice', { owner: aliceOwner }); // wakeAddress is optional
526
- const { participant: bob } = await square.join('Bob', { owner: bobOwner }); // same resource, another owner
527
- // For an already attached seat, select it without joining:
528
- const selected = await square.participant('Alice', { owner: aliceOwner });
529
- const seats = await square.seatsOf(aliceOwner);
530
- const { activities } = await square.history(); // not alice.history()
531
- const status = await square.status({ viewer: selected.name });
532
- console.log(alice.name, bob.name, seats, activities, status);
533
- } finally {
534
- await square.close();
535
- }
536
- ```
537
-
538
- Replace `recognize()`/`bodySeats()` with `seatsOf(owner)`, followed by explicit attached-seat
539
- selection as needed; replace `implicitJoin` with `reattach(name, {owner, wakeAddress?})`. Replace
540
- `detachOwnedSession`/`endOwnedSession` with the generation-fenced `detach`/`endHeld` methods above.
541
- `forBody` is removed: use the same Square with operation-local owners. Read readiness from
542
- participant summaries or join/catch observations, not the removed resource-level
543
- `automaticDeliveryAvailable`. Core status no longer infers a viewer. Participant selection no longer
544
- returns a handle for a missing, done, foreign or detached seat.
545
-
546
- ## Other deliberate migration breaks
547
-
548
- The independent `createSquareApplication` API, its options/results/aliases, and `square-application.ts`
549
- are deleted. Consumers must open/close an actual Square and select a Participant. SDK joins return
550
- `(await square.join(name, {owner})).participant`; history callers must use `.activities` and opt into
551
- full archive reads with `limit: null`. SDK express now waits by default. MCP constructor configuration
552
- uses `path`, consistent with `Square.at`, rather than `squarePath`.
553
-
554
- External consumers must migrate before taking this source version. Editing source does not update
555
- an installed plugin, build, or running extension.
24
+ `Square.at`, `Square.build` and `Square.inMemory` open resources; an owner is supplied per join or
25
+ selection, never captured as the resource's default participant. Owners are opaque non-secret strings,
26
+ not authorization credentials. Adapters resolve their own harness identity without rewriting process
27
+ environment. Membership is explicit; `PUBLIC.square` is an ordinary filename. A participant handle
28
+ captures owner and join generation; rejoin or takeover fences old handles. `done` ends membership;
29
+ host attachment is separate, as described in [participant-ownership.md](participant-ownership.md).
30
+
31
+ ## Activity and delivery
32
+
33
+ `Participant.express` lands activity in Square, with optional immutable JSON `metadata` (maximum
34
+ 16 KiB). Mentions, replies, bells, listening and ignores determine the landed audience. Activity IDs
35
+ are `act/N`. `Square.history` is the read-only archive; `Participant.catch` consumes directed activity,
36
+ and `Participant.pending` reads addressed unconsumed says without consuming them. `listen`, `unlisten`,
37
+ `ignore`, `listening` and `done` operate on the participant. History, status and participants are
38
+ Square operations. `OperationControl` can carry cancellation; a committed mutation is not undone by
39
+ later cancellation.
40
+
41
+ A live idle catch owns matching activity notifications before producer export ownership is acquired.
42
+ Matching uses its sender and directed-attention filters against the activity's landed delivery route;
43
+ other activities and direct notices remain eligible. Release or expiry permits still-unconsumed activity
44
+ at the next ordinary posting opportunity; caught or ACKed activity is not exported. Starting a catch
45
+ cannot recall or retroactively suppress export ownership already committed.
46
+
47
+ After landing, Square renders and posts a card per audience member to the host delivery ledger using
48
+ `square:<canonical-path>#act/N/recipient/<encodeURIComponent(nameKey(recipient))>/join/<encodeURIComponent(capturedJoinGeneration)>`.
49
+ Notices use the analogous `notice/N` key. The recipient and participant join generation are frozen at
50
+ landing; ledger attachment rotation does not change the key. Post, receipt lookup and suppression use
51
+ the same captured coordinates. A later mutating operation retries unposted work idempotently. Directed
52
+ activity cards bound the body to 200 characters and point to `square_catch` when shortened. Square records which cards contain
53
+ the full activity. Unread means after the read horizon, not caught, and without a ledger receipt for
54
+ a full card. Square folds retained receipts lazily during privileged producer operations; catch permanently suppresses
55
+ the corresponding ledger cards when possible. Ledger receipt evidence has finite retention: after proof
56
+ expires, locally unproved activity remains unread until explicit catch or ACK. Missing evidence proves
57
+ neither consumption nor non-delivery. A shortened card cannot consume the full activity. Handoff
58
+ acceptance is not presentation; a boundary receipt is not proof of comprehension.
59
+
60
+ `Square.send({from, to, body, metadata?}, control?)` creates a producer-owned notice, not a say or a
61
+ membership. `to` is a participant name or readonly array of names. It returns Square-owned
62
+ `SendResult` `{id, at, from, to, body, metadata?}` with `id: notice/N`, distinct from `act/N`.
63
+ Recipients are frozen when the notice lands. The delivered notice text is exactly the supplied body,
64
+ including its original newlines; no sender label or metadata is added to that text. Sender and metadata
65
+ remain producer facts for queries and exact ACK. There is no sender truncate or preview option.
66
+ Notices use the same producer export ownership path as activity cards.
67
+
68
+ ## Queries and acknowledgement
69
+
70
+ `Square.participants({local: true, observe?: true}, control?)` reads only World-known participant
71
+ facts, including known departure and attachment state. It needs no host ledger access and does not
72
+ prove current host liveness; `deliveryAvailable` is false in this mode. The default query still overlays
73
+ host attachment and route availability. Local discovery can distinguish known names before selection;
74
+ it does not grant custody or allow fallback over a departed, foreign or detached identity.
75
+
76
+ `Square.history` returns `{activities}` without consuming; `Participant.pending` returns
77
+ `{activities, remaining}` oldest first. Activity queries support exact IDs, senders, metadata matches,
78
+ search and exclusive `act/N` cursors. Filters combine with AND. History defaults to ten records;
79
+ `limit: null` requests the complete archive. Pending defaults to 100 and accepts limits 1–100.
80
+ A pending read does not claim work or change the read horizon.
81
+
82
+ `Participant.ack(ids: readonly ActivityId[], control?)` remains the exact-ID consumption operation,
83
+ returning `{consumed}`. All IDs must be addressed to the participant; invalid or unaddressed IDs
84
+ reject the batch. Duplicate and already consumed IDs are idempotent. It does not assert presentation.
85
+
86
+ For producer waiting, the explicit opt-in
87
+ `Participant.ack({from: string, match: MetadataMatch, future: 'exact'}, control?)` returns
88
+ `Promise<{consumed: number}>` and records a durable recipient-scoped sender and metadata predicate,
89
+ including for a matching notice inserted later. Zero consumed still means the future consumption fact
90
+ committed. This is exact equality of each specified nested scalar field, not equality of the whole
91
+ metadata object, nor a prefix or wildcard match. Supply the full outcome identity; unrelated recipients
92
+ and outcomes remain unaffected. The ordinary ID form does not create a future predicate.
93
+
94
+ Insertion, ACK and producer export ownership serialize in World storage. Once export ownership commits,
95
+ the asynchronous ledger post runs outside that transaction: a later ACK cannot promise recall. The
96
+ fact remains until the producer can prove the corresponding source cannot be inserted or exported
97
+ again; no TTL or first-admission cutoff is required. Producer bookkeeping records the immutable
98
+ `firstPossibleExport` at initial export ownership using actual host time, on the ledger's clock basis.
99
+ A claim is recoverable when its process dies or its 60-second ownership expires; settlement is fenced
100
+ to the captured token. An abandoned uncertain export at or beyond the ledger's minimum 30-day identity
101
+ retention window is not automatically reinserted. Clock or boundary uncertainty is treated conservatively.
102
+ This limit is not a source TTL: it does not reject or expire never-exported sources, and catch and ACK
103
+ remain available. A crash between export and bookkeeping leaves honest uncertainty. A token-fenced
104
+ initial attempt proved not to have invoked insertion, or given a definite insertion refusal, releases
105
+ its export bookkeeping while keeping the source pending. Recovering a prior uncertain attempt never
106
+ erases its original export time merely because the current retry was unsent.
107
+
108
+ `MetadataMatch` has nonempty `fields` with nonempty path segments and exact, type-sensitive scalar
109
+ `equals` values (`null`, boolean, finite number or string). Only own object properties are traversed;
110
+ arrays and missing intermediates do not match. Fields combine with AND.
111
+
112
+ Sandbox participant selection and exact ACK check captured owner, join generation and locally
113
+ known lifecycle in World storage only. They do not need ledger access and do not prove host liveness.
114
+ Ledger-only detach or delete is learned at the next privileged producer reconciliation; actual export
115
+ is gated by the ledger's current state. Local controls cannot grant delivery authority or resurrect a
116
+ deleted session. Known stale, departed or replaced custody refuses.
117
+
118
+ ## Storage and lifecycle
119
+
120
+ Format 12 is a breaking fresh-store format. Square owns its activities, audience, participants, read
121
+ state and posting/full-card bookkeeping in the `.square` SQLite artifact. The per-user host delivery
122
+ ledger owns producer-neutral cards, attachment, routes, attempts and receipts; there is no discovery
123
+ index. Older artifacts are refused read-only: no migration, conversion or deletion. Host start/resume
124
+ and end attach/detach the ledger session, not Square seats. Host-confirmed deletion is reconciled to a
125
+ fenced `done` lazily by the next mutating operation, never by history, status or participants.
126
+
127
+ The package exports `DeliveryLedger` and its types from both the root and
128
+ `@astrosheep/square/delivery-ledger`. External producers post finished text and own their content;
129
+ the ledger does not match producer metadata. Proactive delivery suppression uses the ledger's exact
130
+ or prefix `suppress` operation, with an optional expiry; see [delivery-primitives.md](delivery-primitives.md).