@astrosheep/square 0.4.0 → 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 (187) 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-delivery.d.ts +13 -8
  11. package/dist/claude-delivery.js +40 -93
  12. package/dist/claude-mcp-context.js +2 -2
  13. package/dist/claude-mod.js +28 -140
  14. package/dist/claude-transport.d.ts +3 -3
  15. package/dist/claude-transport.js +20 -44
  16. package/dist/cli/context.js +7 -9
  17. package/dist/cli/observation-commands.d.ts +2 -0
  18. package/dist/cli/observation-commands.js +12 -2
  19. package/dist/cli/registry.js +3 -1
  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-links.js +61 -7
  41. package/dist/harness.js +12 -17
  42. package/dist/help.js +2 -0
  43. package/dist/http-delivery.d.ts +5 -9
  44. package/dist/http-delivery.js +20 -77
  45. package/dist/inbox.d.ts +6 -49
  46. package/dist/inbox.js +24 -371
  47. package/dist/index.d.ts +6 -3
  48. package/dist/index.js +4 -1
  49. package/dist/mcp.js +1 -1
  50. package/dist/model.d.ts +7 -47
  51. package/dist/model.js +1 -7
  52. package/dist/notifications.d.ts +24 -20
  53. package/dist/notifications.js +39 -108
  54. package/dist/operation-control.d.ts +4 -0
  55. package/dist/operation-control.js +20 -0
  56. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  57. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  58. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  59. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  60. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  61. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  62. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  63. package/dist/packages/agent-delivery/src/codex.js +41 -4
  64. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  65. package/dist/packages/agent-delivery/src/index.js +1 -1
  66. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  67. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  68. package/dist/paseo-delivery.d.ts +5 -5
  69. package/dist/paseo-delivery.js +19 -97
  70. package/dist/paseo.d.ts +1 -1
  71. package/dist/paseo.js +1 -1
  72. package/dist/plugins/opencode/index.d.ts +5 -3
  73. package/dist/plugins/opencode/index.js +99 -21
  74. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  75. package/dist/plugins/opencode-shared/delivery.js +109 -0
  76. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  77. package/dist/plugins/opencode-v1/index.js +41 -0
  78. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  79. package/dist/plugins/opencode-v1/runtime.js +152 -0
  80. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  81. package/dist/plugins/opencode-v1/transport.js +50 -0
  82. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  83. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  84. package/dist/ports.d.ts +31 -90
  85. package/dist/session-lifecycle.d.ts +8 -7
  86. package/dist/session-lifecycle.js +40 -42
  87. package/dist/sqlite-square.d.ts +14 -23
  88. package/dist/sqlite-square.js +132 -139
  89. package/dist/square-api.js +4 -7
  90. package/dist/square-core.d.ts +0 -1
  91. package/dist/square-domain.d.ts +5 -17
  92. package/dist/square-domain.js +104 -221
  93. package/dist/square-facade.d.ts +18 -15
  94. package/dist/square-post.d.ts +53 -0
  95. package/dist/square-post.js +4 -0
  96. package/dist/square-posting.d.ts +13 -0
  97. package/dist/square-posting.js +174 -0
  98. package/dist/square-producer-sqlite.d.ts +11 -0
  99. package/dist/square-producer-sqlite.js +210 -0
  100. package/dist/square-sqlite.d.ts +15 -25
  101. package/dist/square-sqlite.js +150 -226
  102. package/dist/views.d.ts +2 -2
  103. package/dist/views.js +0 -1
  104. package/dist/wake-sink.d.ts +4 -11
  105. package/dist/wake-sink.js +8 -32
  106. package/dist/wake-transports.d.ts +4 -7
  107. package/dist/wake-transports.js +8 -39
  108. package/examples/symmetric-domains.mjs +29 -249
  109. package/guides/change-waits.md +13 -53
  110. package/guides/claude-native.md +19 -175
  111. package/guides/codex-delivery.md +55 -80
  112. package/guides/delivery-primitives.md +48 -207
  113. package/guides/operation-cancellation.md +2 -5
  114. package/guides/participant-ownership.md +25 -89
  115. package/guides/pi-native-delivery.md +19 -56
  116. package/guides/pi-tools.md +6 -8
  117. package/guides/square-api.md +113 -615
  118. package/package.json +8 -8
  119. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  120. package/plugins/claude/hooks/hooks.json +1 -1
  121. package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
  122. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  123. package/plugins/codex/hooks/hooks.json +11 -23
  124. package/plugins/opencode/index.ts +91 -23
  125. package/plugins/opencode-shared/delivery.ts +91 -0
  126. package/plugins/opencode-v1/index.ts +36 -0
  127. package/plugins/opencode-v1/runtime.ts +134 -0
  128. package/plugins/opencode-v1/transport.ts +41 -0
  129. package/plugins/paseo/OVERVIEW.md +5 -5
  130. package/plugins/paseo/README.md +7 -6
  131. package/plugins/paseo/client/markdown.tsx +5 -5
  132. package/plugins/paseo/client/square-message.tsx +28 -24
  133. package/plugins/paseo/client/transform.ts +8 -5
  134. package/plugins/paseo/shared/card-presentation.ts +16 -0
  135. package/plugins/paseo/shared/message.ts +6 -11
  136. package/plugins/pi/square-pi.js +156 -417
  137. package/dist/channel-artifact.d.ts +0 -10
  138. package/dist/channel-artifact.js +0 -55
  139. package/dist/channel-delivery.d.ts +0 -11
  140. package/dist/channel-delivery.js +0 -51
  141. package/dist/channel-domain.d.ts +0 -3
  142. package/dist/channel-domain.js +0 -83
  143. package/dist/channel-facade.d.ts +0 -76
  144. package/dist/channel-facade.js +0 -1
  145. package/dist/channel-internal.d.ts +0 -126
  146. package/dist/channel-internal.js +0 -440
  147. package/dist/channel-metadata.d.ts +0 -11
  148. package/dist/channel-metadata.js +0 -1
  149. package/dist/channel-ports.d.ts +0 -241
  150. package/dist/channel-ports.js +0 -1
  151. package/dist/channel-presentation.d.ts +0 -17
  152. package/dist/channel-presentation.js +0 -22
  153. package/dist/channel-sqlite-schema.d.ts +0 -13
  154. package/dist/channel-sqlite-schema.js +0 -81
  155. package/dist/channel-transport.d.ts +0 -62
  156. package/dist/channel-transport.js +0 -1
  157. package/dist/channel.d.ts +0 -84
  158. package/dist/channel.js +0 -60
  159. package/dist/claude-delivery-status.d.ts +0 -13
  160. package/dist/claude-delivery-status.js +0 -46
  161. package/dist/cmd/notify-once.d.ts +0 -2
  162. package/dist/cmd/notify-once.js +0 -29
  163. package/dist/delivery-attempts.d.ts +0 -52
  164. package/dist/delivery-attempts.js +0 -73
  165. package/dist/delivery-effects.d.ts +0 -139
  166. package/dist/delivery-effects.js +0 -163
  167. package/dist/delivery-health.d.ts +0 -51
  168. package/dist/delivery-health.js +0 -173
  169. package/dist/delivery-operations.d.ts +0 -22
  170. package/dist/delivery-operations.js +0 -198
  171. package/dist/delivery-primitives.d.ts +0 -5
  172. package/dist/delivery-primitives.js +0 -3
  173. package/dist/delivery.d.ts +0 -56
  174. package/dist/delivery.js +0 -80
  175. package/dist/plugins/opencode/delivery.d.ts +0 -19
  176. package/dist/plugins/opencode/delivery.js +0 -223
  177. package/dist/presentation-operations.d.ts +0 -5
  178. package/dist/presentation-operations.js +0 -86
  179. package/dist/sqlite-channel.d.ts +0 -15
  180. package/dist/sqlite-channel.js +0 -600
  181. package/dist/square-index.d.ts +0 -16
  182. package/dist/square-index.js +0 -106
  183. package/dist/wake-preview.d.ts +0 -7
  184. package/dist/wake-preview.js +0 -15
  185. package/plugins/opencode/delivery.ts +0 -198
  186. /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
  187. /package/dist/{channel-values.js → square-values.js} +0 -0
@@ -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,593 +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, and
228
- by channel send/receive/history/pending messages.
229
- MCP and Pi tools are not a metadata surface: their `express` requires a body and its schema rejects a
230
- `metadata` argument. Tool history/catch return rendered text with a structured-activity placeholder,
231
- not metadata JSON or activity objects. Metadata does not affect mentions, replies, listening, audience,
232
- ordering, consumption or permissions. A current participant may temporarily defer proactive delivery
233
- by exact metadata match through the SDK lease API below. Metadata may nest up to 256 levels; the bound is enforced before
234
- persistence and public reads. Human text uses a concise structured-activity placeholder and never prints
235
- metadata JSON. SDK history and pending queries accept exact metadata matches; there is no query
236
- language or metadata index. CLI `--metadata <json>` remains an express input flag. Channel
237
- `send` accepts the same metadata and returns it on `ChannelMessage` records.
238
- - Catch returns `{activities, remaining, reason, hint?, observation?}`. Each activity includes
239
- viewer-relative `perception` and `via`. Reasons are `drained`, `idle`, `capped`, `quorum`,
240
- `replaced`, or `already-active`. Omitted/zero idle is immediate; positive idle uses the single
241
- leased quiet wait. Receipt selection, perception, routes, counts and terminal facts settle
242
- in the receipt transaction. Cancellation/replacement releases only that waiter's lease and never
243
- consumes future activity.
244
- - Agora history (`Square.history`) returns `{activities}`. Default page size is ten;
245
- `limit: null` explicitly requests an unbounded archive. Numeric limits must be 1–100. Contextual
246
- queries still reject an implicit merged window over 100 activities; an explicit numeric limit
247
- bounds it. CLI/tool schemas accept numeric limits only. History never writes receipts; history
248
- observations retain lifetime say ordinals for CLI `history --format number`.
249
- - `participants()` returns `{participants, observation?}` and `listening()` returns
250
- `{listening, observation?}`, not arrays carrying metadata. Participant summaries use `presence`
251
- (`never-joined`, `active`, `watching`, `away`, or `done`). Away participants remain members,
252
- count toward in-square totals and keep receiving normal bell, mention and listened audience. Its roster
253
- supplies the participant counts. Core `square.status({viewer})` uses only the explicitly supplied viewer; with
254
- no viewer it returns an unfiltered overview without viewer-specific `listening`. Core status does
255
- not select or authorize a seat. CLI and tools check an explicit `--as`/`as` with `selectOwnSeat`;
256
- otherwise exactly one attached seat of the caller's owner becomes the viewer, while zero or multiple
257
- seats (or no owner) produce the overview. Snapshot participants use the same `name`, `presence` and
258
- `listening` facts, excluding never-joined people; its detached `delivered(name, id)` predicate
259
- remains available.
260
- - `unlisten(target)` removes only that participant's current listen edge to the target, without
261
- leaving or blocking future mentions/replies. It returns the same `ListenerChangeResult` as listen
262
- and ignore, with `activity: null` when no listen exists. Existing ignores are unchanged. Earlier
263
- landed audiences and receipts are never rewritten; listening again remains available.
264
- - Lifecycle operations return `{activity, observation?}` and use the same artifact custody gate;
265
- observations do not repeat the activity. Join entry uses public activities with settled perception,
266
- and status observations use public join activities. Done accepts no message and retires only its
267
- attached, captured owner/join generation; it never implicitly rejoins. A done participant is not
268
- recoverable authority for any host path.
269
- - Recovery summarizes unread directed activity with per-sender count, latest age and the single
270
- greatest-index preview. Age uses the greatest timestamp, which may belong to another activity.
271
-
272
- Operations that accept an `OperationControl` use a final argument with `signal` and optional typed
273
- `onProgress`. Listener/lifecycle operations take optional observation options before that argument;
274
- participant selection, seat discovery and host cleanup methods do not take an `OperationControl`.
275
-
276
- ## Channel endpoints and point-to-point messages
277
-
278
- `@astrosheep/square/channel` exports the endpoint and messaging resource `Channel`; the Agora
279
- resource (`Square` / `Participant`) uses the same message substrate. `Channel` has static `open`,
280
- `create` and `inMemory`, a `location` property, and methods `attach`, `reattach`, `endpoint`,
281
- `endpointsOf`, `listEndpoints`, `detach`, `cancelDeliveries`, `remove`, `send`, `history` and
282
- `close`. Obtain a `ChannelEndpoint` from a Channel; it has `name` and `generation` properties and
283
- methods `pending`, `ack`, `receive`, `deferDelivery`, `renewDeliveryDeferral`,
284
- `releaseDeliveryDeferral`, `send` and `leave`. Endpoint generations are opaque strings: retain
285
- and compare them for equality, without interpreting their contents.
286
-
287
- The subpath exports the types `MessageId`, `MessageCursor`, `MessageQuery`, `MessageSearch`,
288
- `MessageMetadata`, `ChannelTruncate`, `ChannelSendInput`, `ChannelSendResult`, `ChannelMessage`,
289
- `ChannelPendingOptions`, `ChannelPendingResult`, `ChannelReceiveResult`, `ChannelHistoryQuery`,
290
- `ChannelHistoryResult`, `AckResult`, `ChannelEndpointSummary`, `ChannelEndpointHold`,
291
- `DeliveryDeferral`, `MetadataMatch`, `Owner`, `OperationControl`, `WakeAddress`, `WakeTransportPort`,
292
- `ChannelOpenInput`, `ChannelCreateInput`, `HarnessSessionChannel` and `ResolvedHarnessSession`.
293
- It also exports `harnessOwner`, `decodeHarnessOwner`, `localUserOwner` and `resolveHarnessSession`.
294
- The package root exports `MessageId`, `MessageCursor` and `MessageQuery`, but not `MessageMetadata`
295
- or `MessageSearch` under those names; its Agora equivalents are `ActivityMetadata` and `ActivitySearch`.
296
-
297
- Square and Participant expose their own Agora lifecycle operations; in particular,
298
- `square.endHeld` lands a fenced `done`, whereas `channel.remove` removes an endpoint and is not
299
- `Channel.endHeld`.
300
- `Square.history` and `Participant.pending` remain Agora projections, not channel reads: Agora
301
- history selects joins, says, departures and listener changes but never native messages, and Agora
302
- pending selects addressed, unconsumed says as perceived activities.
303
-
304
- - `Channel.send({from, to, body, truncate?, metadata?})` addresses standing endpoints explicitly.
305
- `truncate` defaults to `false`, so the full body is delivered and presentation records a receipt.
306
- An explicit positive integer at least as long as the body is likewise unclipped; when the body is
307
- longer, the landed message stores `preview`, so presentation shows that preview and records clipped
308
- coverage without consuming the full stored body. `pending`, `receive` and `ack` still read and
309
- consume that full body. Any other `truncate` value is refused before landing. `to` in the send
310
- result and in every `ChannelMessage` is the actual landed audience after address resolution. A
311
- stored message may have an empty body only when imported from a legacy metadata-only direct say;
312
- `Channel.send` itself always requires a non-empty body.
313
- - `endpoint.receive(options?, control?)` selects the oldest unconsumed addressed messages matching the
314
- shared filters and records their receipts in one transaction, returning
315
- `{messages, consumed, remaining}`. Each `ChannelMessage` is `{id, at, from, to, body, metadata?}`,
316
- symmetric with the send result. `remaining` counts matching unconsumed messages left after that
317
- transaction. Nothing is consumed by `pending` or `history`.
318
- - `Channel.history(query?, control?)` returns `{messages}` for native messages and says, selecting
319
- the newest page oldest-first by default; `order: 'desc'` reverses the page. It shares the
320
- `MessageQuery` filters (`at`, `from`, `match`, `search`, `cursor`) but has no Agora
321
- `contextBefore`/`contextAfter`, `since`, `mention` or `observe` options. Its default page size is
322
- 100; `limit` accepts 1–100 or `null` for all matches.
323
- - `listEndpoints(control?)` reports every standing endpoint without an owner argument as
324
- `{name, generation, attachment, deliveryAvailable, unreadCount, lastActiveAt?}`. `endpointsOf(owner)`
325
- remains the owner-scoped custody list, and neither operation joins, restores or reveals an address.
326
- - Direct notifications have no separate envelope: the model-facing text is the stored preview, or
327
- the body when untruncated, escaped inside the shared `<system-reminder>` frame; sender, recipients
328
- and ID are not printed. Body text that would read as `<channel-`, `<square-` or
329
- `<system-reminder` is escaped. A mixed boundary keeps notification order; its fixed budget and
330
- `N more activities not shown.` line apply only to Agora activity blocks, and the line trails the
331
- rendered batch.
332
- - An unconsumed direct message stays eligible for the proactive inbox and wake horizon across a seat
333
- takeover or rejoin. Earlier Agora activity still ends at the recipient's new read horizon.
334
-
335
- ## Shared history and pending filters
336
-
337
- Agora `HistoryQuery` and `PendingOptions` extend the exported `ActivityQuery`:
338
-
339
- ```ts
340
- interface ActivityQuery {
341
- at?: readonly ActivityId[];
342
- from?: readonly string[];
343
- match?: MetadataMatch;
344
- search?: { text: string; mode: 'literal' | 'regex' };
345
- cursor?: { id: ActivityId; direction: 'before' | 'after' };
346
- }
347
- ```
348
-
349
- Channel `ChannelHistoryQuery` and `ChannelPendingOptions` extend `MessageQuery`, whose `at` IDs
350
- and `MessageCursor.id` are `MessageId` (`msg/N`) rather than `ActivityId` (`act/N`). The message
351
- query types are exported from `@astrosheep/square/channel` and the package root. Channel history,
352
- pending, receive and ack use `msg/N`; Agora history, pending and ack use `act/N`. A say has both
353
- coordinates, which are not interchangeable.
354
-
355
- - Filters combine with AND. IDs in `at` and senders in `from` each combine with OR; an absent array
356
- is unrestricted, while an explicitly empty array matches nothing. `at` is an exact ID selection;
357
- only history can expand those IDs with `contextBefore` / `contextAfter`.
358
- - Sender names use case-insensitive identity matching and need not belong to a joined participant.
359
- Direct senders such as `keiyaku-notice` can be queried without inventing membership. A valid but
360
- unmatched sender returns no activities, not an unknown-participant error.
361
- - `match` uses the same nested own-property, exact type-sensitive scalar equalities as deferrals.
362
- `search` tests each activity's ID, sender and body independently, case-insensitively. Literal mode
363
- treats regex characters as plain text; regex mode uses JavaScript's built-in regular expressions,
364
- without a ReDoS timeout. Search text must be nonempty and regex syntax must be valid. Agora
365
- history searches says and departures, not joins. Participant `pending` selects addressed,
366
- unconsumed says; `ChannelEndpoint` `pending`/`receive` select addressed, unconsumed messages and
367
- says.
368
- - A cursor is an exclusive boundary in its own coordinate space, independent of `limit`: before excludes the anchor
369
- and everything newer; after excludes the anchor and everything older. No consumption is involved.
370
- Storage applies the shared filters before limiting and uses the same predicates for counts.
371
- - Agora history keeps its archive-specific `order`, `mention`, `since` and context options. By
372
- default it selects the newest page and returns it oldest first; an after cursor selects the oldest following
373
- page. `order: 'desc'` reverses the selected page, not which activities belong to it. Numeric limits
374
- stay 1–100, default ten; `limit: null` reads all matches. Exact/context history keeps its existing
375
- implicit full-window selection with a 100-activity bound unless a numeric limit is supplied.
376
- - Pending always selects oldest first, including with a before cursor. Its default and maximum page
377
- size remain 100, and `remaining` counts matching unconsumed activities beyond that page within
378
- the supplied cursor boundary. It has no history context, ordering or audience override.
379
-
380
- The SDK accepts only `search` and `cursor`, not the removed `grep` / `fixed` / `before` / `after`
381
- fields. CLI flags and tool arguments keep those familiar spellings and translate at the adapter
382
- boundary, e.g. `--fixed text` becomes `search: {text, mode: 'literal'}` and `--after act/12` becomes
383
- `cursor: {id: 'act/12', direction: 'after'}`. The query surface keeps these `act/N` and `msg/N` coordinates; format 11 is a separate fresh-store
384
- breaking upgrade rather than a query change.
385
-
386
- ## Pending query and exact acknowledgement
387
-
388
- `Participant.pending(options?, control?)` and `Participant.ack(ids, control?)` separate a read-only
389
- query from explicit consumption. Agora `pending` returns addressed, unconsumed `say` records as
390
- perceived activities, so native messages never appear there. `ChannelEndpoint` from
391
- `@astrosheep/square/channel` returns `{messages}` for both native messages and says; `ack` records
392
- receipts for the addressed message IDs. Neither layer keeps a second inbox, read horizon or storage.
393
-
394
- Agora publication is participant-scoped: `Participant.express(...)` publishes a say/activity for
395
- that standing participant. Standalone `Channel.send` addresses standing Channel endpoints, without
396
- requiring Agora membership, and lands a Channel message, not an Agora activity. Attachment gates
397
- proactive wake, not landing: a detached Channel recipient retains its unread addressed item and can
398
- query/ack it after reattachment. Recipient validation is all-or-nothing; a missing or departed
399
- recipient rejects the entire send without landing a message or waking other recipients. An omitted
400
- `truncate` sends the full body; truncation is an explicit per-message choice.
401
-
402
- ```ts
403
- import type { ChannelEndpoint, MetadataMatch } from '@astrosheep/square/channel';
404
-
405
- async function drain(endpoint: ChannelEndpoint, match: MetadataMatch) {
406
- for (;;) {
407
- const page = await endpoint.pending({ match });
408
- if (page.messages.length === 0) break;
409
- // Process the entire page successfully before acknowledging its exact IDs.
410
- for (const message of page.messages) await processNotice(message);
411
- const { consumed } = await endpoint.ack(page.messages.map(message => message.id));
412
- // consumed may be smaller if another consumer acknowledged some IDs first.
413
- }
414
- }
415
- ```
416
-
417
- - `Participant.pending(query: PendingOptions = {}, control?)` returns
418
- `{activities: readonly PerceivedActivity[], remaining: number}` and selects landed, addressed,
419
- unconsumed says oldest first, with viewer-relative `perception` and `via` as in catch.
420
- `ChannelEndpoint.pending` returns `{messages: readonly ChannelMessage[], remaining: number}` for
421
- addressed, unconsumed messages and says; each message is `{id, at, from, to, body, metadata?}`.
422
- Both read full stored bodies, never clipped previews or delivery remnants.
423
- - Limit defaults to 100 and must be an integer from 1 through 100. The shared filters above are
424
- available alongside that limit. Storage applies addressedness, consumption and all supplied
425
- filters **before** limiting. `remaining` counts matching unconsumed activities for Agora and
426
- messages for Channel outside the returned page in the same read transaction. It is not the global
427
- unread count or a durable cursor.
428
- - A pending query consumes nothing, changes no read horizon and writes no delivery/presentation
429
- evidence. Deferrals, catch leases, cancellation and prior clipped presentation do not hide these
430
- explicit reads. Agora catch and pending include historical addressed activity receipt holes from
431
- before a rejoin; they are not proactive delivery eligibility queries. Channel pending, receive and
432
- ack use only addresses valid for the current endpoint generation. Ordinary unfenced `Channel.send`
433
- messages remain addressed across takeover or rejoin until consumed.
434
- - Agora `ack(ids: readonly ActivityId[], control?)` and Channel `ack(ids: readonly MessageId[], control?)`
435
- return `{consumed: number}`, the count of newly
436
- consumed exact IDs. It validates that **every** supplied ID is addressed to this participant before
437
- recording any receipts; Channel also checks that the address is valid for the current generation. Malformed, nonexistent or unaddressed IDs reject the whole batch with
438
- `invalid_args` (`reason: 'not_addressed'` for valid but unaddressed IDs). There is no implicit prefix
439
- acknowledgement, no match reselection and no requirement for an earlier pending call.
440
- - Duplicate IDs, repeated acknowledgement and an empty array are idempotent. Existing receipt
441
- timestamps remain unchanged. Acknowledgement uses the same sparse receipts as catch/full
442
- presentation, so it removes those IDs from pending and ordinary catch, but exact catch remains
443
- repeatable. It does **not** assert wake acceptance or presentation and writes no such evidence.
444
- - Both operations require their captured current owner, generation and attached seat; even an empty
445
- query/ack cannot reuse a detached, retired or replaced handle. No storage format change is needed.
446
- Cancellation before the receipt transaction prevents consumption; a committed ack is not undone.
447
- - To drain all matching notices, keep querying and acknowledging pages until an **empty** page.
448
- Acknowledging one page exposes the next without a cursor. For read-only pagination instead, pass
449
- `cursor: {id: page.messages.at(-1)!.id, direction: 'after'}` on a channel endpoint (or
450
- `page.activities` on a Participant) with the same filters; earlier pages
451
- stay unconsumed. Do not silently stop after the default page or treat a snapshot `remaining: 0`
452
- as proof that concurrent producers have stopped. Query and
453
- ack are separate operations, not an exclusive claim or an exactly-once external-effect guarantee.
454
-
455
- ## Temporary proactive delivery deferral
456
-
457
- SDK Participants expose three operations. There are no CLI/MCP/Pi authoring
458
- commands or listing operation in this delivery. Existing proactive adapters still obey deferrals.
459
-
460
- ```ts
461
- const lease = await alice.deferDelivery({
462
- match: { fields: [
463
- { path: ['job', 'kind'], equals: 'outcome' },
464
- { path: ['job', 'id'], equals: 7 },
465
- ] },
466
- durationMs: 60_000,
467
- });
468
- try {
469
- // Explicit history and catch remain available, including catch({id}).
470
- const renewed = await alice.renewDeliveryDeferral(lease.id, 60_000);
471
- // null means absent, expired, or not owned by this current generation.
472
- } finally {
473
- await alice.releaseDeliveryDeferral(lease.id);
474
- }
475
- ```
476
-
477
- - `MetadataScalar` is `null | boolean | number | string`; numbers must be finite. `MetadataMatch`
478
- has nonempty `fields`, each `{path: readonly string[], equals: MetadataScalar}`. Paths have one
479
- or more nonempty string segments, with no duplicates. Only JSON object own properties are
480
- traversed: arrays, missing/nonobject intermediates and inherited properties do not match.
481
- Equalities are exact and type-sensitive. A dot inside one segment is a literal key, not path
482
- syntax. All fields combine with AND; any matching live lease blocks delivery (OR across leases).
483
- - Each acquire returns a fresh UUID and an operation-clock ISO `expiresAt`: `{id, expiresAt}`.
484
- Match objects are not returned. `durationMs` must be a positive safe integer and is capped at
485
- 24 hours. Invalid matches, durations and IDs refuse with `invalid_args` before mutation.
486
- - Renewal extends that same live lease from the renewal operation's clock; expired/missing IDs
487
- return `null`, and release returns `false`. Independent overlapping leases are never merged;
488
- releasing one cannot end another. A captured current owner/join generation is required for every
489
- mutation. Stale handles retain the ownership refusal; done/takeover/rejoin retire old leases.
490
- - Lease writes change delivery revision, not conversation history. They do not create a receipt,
491
- change audience or unread counts, hide an archive record, or prohibit explicit catch/pending/ack.
492
- Catch and ack consume normally; a consumed record is not replayed when a deferral ends. The catch
493
- lease is a separate, single quiet-wait lease with its existing filters and lifecycle.
494
- - Producer/sweep, atomic claims, session/native projections, final pre-send checks and proactive
495
- presentation all share current-generation unexpired matching. Suppressed items get no seen or
496
- presented receipt. An active pending observer rechecks on release and the earliest relevant
497
- expiry, including renewed deadlines, without needing a new conversation activity. A one-shot
498
- producer skips and returns promptly. With no active observer, eligibility is reconsidered at
499
- the next normal opportunity; no daemon or per-message timer is created.
500
- - Acquiring a lease is not recall. An external send already past its final freshness check can race
501
- with acquisition and remain dispatched; a sink effect already written cannot be withdrawn.
502
- Presentation eligibility is checked before the effect, not again as a veto after success. A batch
503
- claims and filters its items before its one sink call, then records exactly the successfully rendered
504
- set under each captured owner/join generation. A lease acquired while the sink returns or before a
505
- later item's evidence commit does not erase success or allow replay after release. Cancellation
506
- likewise stops pre-effect work, not truthful completion after a successful sink: evidence settles
507
- on the original connection with an independent bounded storage lifetime and the same seat fences.
508
- A rejected/cancelled native landing is not a successful sink. Items filtered
509
- out before the sink receive no success receipt.
510
- Cancellation before commit prevents the lease mutation; after commit it remains until explicit
511
- release, expiry, or seat retirement. See [operation-cancellation.md](operation-cancellation.md).
512
-
513
- ## Optional observations, not another execution path
514
-
515
- Request `observe: true` on the **same method** when rendering needs additional facts. Each operation
516
- has a concrete optional observation type, without conditional generics or a generic outcome wrapper.
517
- Data-only calls do not hydrate join history, the pre-expression feed, catch presence enrichment,
518
- or history/status presentation annotations.
519
-
520
- Express captures optional unread context before the attempt: the latest five directed,
521
- unseen activities after the current join, each preview capped at 400 Unicode characters,
522
- plus the count outside that preview. It hydrates only that page, not the entire cursor suffix.
523
- This is a nonconsuming preview even when a short body fits in full; consumption requires `catch`,
524
- explicit `ack`, or the normal full-notification presentation path. Count and time facts come
525
- from the committed transaction. Listener and lifecycle observations likewise use their mutation's
526
- facts, with no postcommit context read. Join uses its mutation's essential facts and treats optional
527
- entry/history enrichment as best-effort: `observation.enrichmentUnavailable` is explicit and the
528
- successful receipt survives. Catch preserves its committed page even if optional nearby presence
529
- fails. A cancellation or failed clock after commit cannot turn these receipts into an unsent retry.
530
-
531
- Delivery readiness is participant-specific, not a property of the Square resource.
532
- `ParticipantSummary.deliveryAvailable`, `JoinObservation.deliveryAvailable` and
533
- `CatchObservation.deliveryAvailable` reflect whether the participant's seat has a stored wake
534
- address. Catch captures this fact with its selection. Join and catch observations require
535
- `observe: true`. These flags do not prove that a receiver is live or that delivery succeeded.
536
-
537
- `OperationError.operation` supplements authoritative error facts with caller/filter/recipient and
538
- express certainty. Selected-row/execution failures remain unknown unless admission proved the
539
- expression unsent. CLI saves raw drafts even when opening the resource fails; its initial open
540
- receives the express abort signal, while recovery reopening is deliberately uncancelled.
541
-
542
- ## Adapters and standalone functions
543
-
544
- CLI and tools only adapt input, identity, resource lifetime and output. They do not import private
545
- actions, storage, admission, scanners or query execution. CLI `--json` for join, express, catch,
546
- history, listen, ignore, unlisten, listening, done, status, participants and inbox
547
- serializes the same SDK result without requesting `observe: true`. Only `observation` is omitted and
548
- a live Participant handle becomes `{name}`. History returns one JSON document, including an empty
549
- `activities` array; `history --format` remains human-oriented. `participants --limit` limits only
550
- human text, not the SDK result. Stream `--ndjson` remains one SDK stream record per line.
551
-
552
- MCP/Pi use the shared `square-tools.ts` definitions. All seven tools return only the CLI's rendered text, with no `outputSchema`
553
- or `structuredContent`. Join text includes its context and recent activity; express text
554
- includes landing ID/reach, warnings and bounded unread previews, never the sent body echo
555
- or internal observation tables. Failures remain failed tool results with readable recovery
556
- text. The SDK and CLI JSON keep their full domain receipts. The optional express observation
557
- contains `context: {activities: [{id, actor, preview, truncated}], remaining}`, not the old
558
- unbounded `pendingPublic`/ordinal/perception tables.
559
-
560
- Express still finishes attempted notification delivery before releasing its resource.
561
- It overlaps at most four receiving sessions per dispatch, preserving order within each
562
- session and draining started work even on failure. Claims, final freshness checks and
563
- uncertain-delivery suppression remain unchanged. Paseo inventory discovery is asynchronous;
564
- overlapping attempts share only the in-flight inventory read, never a cached idle status.
565
- Paseo inventory discovery returns a promise.
566
-
567
- `discover(input, options?, control?)`, `inbox(input, owner, options?, control?)`, and
568
- `diagnoseSquare(input, control?)` are ordinary public exports. They are not methods on a synthetic
569
- cross-square object. Diagnose resolves a path without opening a valid current-format Square first.
570
-
571
- ## Artifact format
572
-
573
- The current artifact format is 11. It is a breaking fresh-store format, not an upgrade of format
574
- 10. A full Square file combines three separately owned layers in one SQLite database and one
575
- transaction: the neutral storage revision header, the same Channel message/endpoint/delivery
576
- tables used by a standalone Channel, and the Agora social tables plus the activity allocator and
577
- square settings. Standalone Channel storage uses its own format identity and version and contains
578
- only the neutral and Channel structures; it never creates or reads Agora tables.
579
-
580
- Ordinary opens accept only the exact current identity, version and owned schema. Any other
581
- identity or version is refused read-only, with no conversion, no write and no migrate command.
582
- There is no automatic upgrade, compatibility decoder or recovery instruction. Stop any session
583
- using an older `.square` file and start a fresh square; the old bytes are never touched.
584
-
585
- ## SDK breaking migration: opaque owners
586
-
587
- Artifact format 7 introduced opaque owners, format 8 removed hold/resume activities, and format 9
588
- stores native messages. Format 10 unified message content while keeping Agora relations separate.
589
- Format 11 splits neutral storage, standalone Channel and Agora, and has no migration from any
590
- older format. Current artifacts must be created fresh at format 11.
591
- `Body`, `sameBody` and `validateBody` are removed from the core and public API. Replace identity
592
- objects with an explicit owner at each identity-bearing operation; do not put identity on a resource:
593
-
594
- ```ts
595
- import { Square } from '@astrosheep/square';
596
-
597
- const aliceOwner = 'example:alice-process';
598
- const bobOwner = 'example:bob-process';
599
- const square = await Square.at({ path: './discussion.square' });
600
- try {
601
- // Previously: pass body/wakeAddress/participant to Square.at and join(name).
602
- const { participant: alice } = await square.join('Alice', { owner: aliceOwner }); // wakeAddress is optional
603
- const { participant: bob } = await square.join('Bob', { owner: bobOwner }); // same resource, another owner
604
- // For an already attached seat, select it without joining:
605
- const selected = await square.participant('Alice', { owner: aliceOwner });
606
- const seats = await square.seatsOf(aliceOwner);
607
- const { activities } = await square.history(); // not alice.history()
608
- const status = await square.status({ viewer: selected.name });
609
- console.log(alice.name, bob.name, seats, activities, status);
610
- } finally {
611
- await square.close();
612
- }
613
- ```
614
-
615
- Replace `recognize()`/`bodySeats()` with `seatsOf(owner)`, followed by explicit attached-seat
616
- selection as needed; replace `implicitJoin` with `reattach(name, {owner, wakeAddress?})`. Replace
617
- `detachOwnedSession`/`endOwnedSession` with the generation-fenced `detach`/`endHeld` methods above.
618
- `forBody` is removed: use the same Square with operation-local owners. Read readiness from
619
- participant summaries or join/catch observations, not the removed resource-level
620
- `automaticDeliveryAvailable`. Core status no longer infers a viewer. Participant selection no longer
621
- returns a handle for a missing, done, foreign or detached seat.
622
-
623
- ## Other deliberate migration breaks
624
-
625
- The independent `createSquareApplication` API, its options/results/aliases, and `square-application.ts`
626
- are deleted. Consumers must open/close an actual Square and select a Participant. SDK joins return
627
- `(await square.join(name, {owner})).participant`; history callers must use `.activities` and opt into
628
- full archive reads with `limit: null`. SDK express now waits by default. MCP constructor configuration
629
- uses `path`, consistent with `Square.at`, rather than `squarePath`.
630
-
631
- External consumers must adapt their code before taking this source version. Editing source does not update
632
- 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).