@astrosheep/square 0.3.86 → 0.3.87

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 (141) hide show
  1. package/dist/artifact.d.ts +6 -1
  2. package/dist/artifact.js +79 -10
  3. package/dist/boundary-presentation.d.ts +5 -4
  4. package/dist/boundary-presentation.js +21 -25
  5. package/dist/catch-decisions.js +2 -1
  6. package/dist/claude-delivery-status.d.ts +8 -2
  7. package/dist/claude-delivery-status.js +26 -10
  8. package/dist/claude-delivery.d.ts +8 -4
  9. package/dist/claude-delivery.js +114 -104
  10. package/dist/claude-mcp-context.js +2 -2
  11. package/dist/claude-mod.js +119 -105
  12. package/dist/cli/context.d.ts +3 -1
  13. package/dist/cli/context.js +25 -1
  14. package/dist/cli/express-presentation.d.ts +3 -1
  15. package/dist/cli/express-presentation.js +14 -10
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +1 -0
  18. package/dist/cli/registry.js +0 -2
  19. package/dist/cli/square-commands.d.ts +4 -2
  20. package/dist/cli/square-commands.js +21 -6
  21. package/dist/codex-hook.d.ts +3 -3
  22. package/dist/codex-hook.js +4 -4
  23. package/dist/codex-queue.d.ts +5 -6
  24. package/dist/codex-queue.js +9 -5
  25. package/dist/decisions.d.ts +3 -2
  26. package/dist/decisions.js +18 -6
  27. package/dist/delivery-health.d.ts +32 -2
  28. package/dist/delivery-health.js +108 -45
  29. package/dist/delivery-operations.d.ts +1 -2
  30. package/dist/delivery-operations.js +155 -196
  31. package/dist/delivery.d.ts +14 -23
  32. package/dist/delivery.js +48 -18
  33. package/dist/file-lock.d.ts +8 -1
  34. package/dist/file-lock.js +2 -0
  35. package/dist/help.js +8 -11
  36. package/dist/inbox.d.ts +35 -9
  37. package/dist/inbox.js +228 -144
  38. package/dist/index.d.ts +3 -4
  39. package/dist/index.js +1 -3
  40. package/dist/list.js +9 -4
  41. package/dist/mcp.js +12 -1
  42. package/dist/model.d.ts +39 -5
  43. package/dist/model.js +84 -0
  44. package/dist/notifications.d.ts +8 -6
  45. package/dist/notifications.js +56 -143
  46. package/dist/operation-presentation.js +3 -4
  47. package/dist/participant-identity.d.ts +0 -3
  48. package/dist/participant-identity.js +0 -31
  49. package/dist/paseo-delivery.d.ts +5 -6
  50. package/dist/paseo-delivery.js +13 -8
  51. package/dist/paseo.d.ts +1 -1
  52. package/dist/paseo.js +1 -1
  53. package/dist/plugins/opencode/delivery.d.ts +8 -3
  54. package/dist/plugins/opencode/delivery.js +187 -132
  55. package/dist/plugins/opencode/index.js +12 -38
  56. package/dist/ports.d.ts +112 -40
  57. package/dist/presentation-operations.d.ts +1 -1
  58. package/dist/presentation-operations.js +51 -35
  59. package/dist/presentation.js +8 -3
  60. package/dist/seats.d.ts +7 -0
  61. package/dist/seats.js +27 -0
  62. package/dist/session-lifecycle.d.ts +2 -2
  63. package/dist/session-lifecycle.js +10 -12
  64. package/dist/sqlite-square.d.ts +20 -9
  65. package/dist/sqlite-square.js +471 -339
  66. package/dist/square-api.d.ts +1 -0
  67. package/dist/square-api.js +3 -4
  68. package/dist/square-core.d.ts +3 -2
  69. package/dist/square-domain.d.ts +12 -5
  70. package/dist/square-domain.js +163 -7
  71. package/dist/square-facade.d.ts +14 -10
  72. package/dist/square-index.d.ts +24 -0
  73. package/dist/square-index.js +131 -0
  74. package/dist/square-legacy-framing.d.ts +9 -0
  75. package/dist/square-legacy-framing.js +56 -0
  76. package/dist/square-migration-host-ledger.d.ts +26 -0
  77. package/dist/square-migration-host-ledger.js +168 -0
  78. package/dist/square-migration.d.ts +5 -5
  79. package/dist/square-migration.js +373 -71
  80. package/dist/square-sqlite.d.ts +9 -9
  81. package/dist/square-sqlite.js +329 -79
  82. package/dist/square-stdio.d.ts +1 -1
  83. package/dist/square-stdio.js +12 -7
  84. package/dist/square-tools.js +29 -2
  85. package/dist/stdio-server.js +4 -2
  86. package/dist/stream.d.ts +3 -2
  87. package/dist/stream.js +9 -10
  88. package/dist/views.d.ts +9 -14
  89. package/dist/views.js +50 -70
  90. package/dist/wakes.d.ts +6 -5
  91. package/dist/wakes.js +20 -17
  92. package/guides/application-facade-delivery-evidence.md +1 -1
  93. package/guides/claude-native.md +9 -9
  94. package/guides/mcp.md +1 -1
  95. package/guides/migration.md +107 -67
  96. package/guides/row-storage-design.md +1 -1
  97. package/guides/square-api.md +61 -28
  98. package/package.json +1 -1
  99. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  100. package/plugins/claude/hooks/register.js +1 -1
  101. package/plugins/claude/skills/square/SKILL.md +1 -1
  102. package/plugins/claude/types.d.ts +1 -1
  103. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  104. package/plugins/opencode/delivery.ts +170 -118
  105. package/plugins/opencode/index.ts +12 -30
  106. package/plugins/pi/square-pi.js +55 -17
  107. package/skills/square/SKILL.md +1 -1
  108. package/dist/cli/ledger-command.d.ts +0 -12
  109. package/dist/cli/ledger-command.js +0 -72
  110. package/dist/host-ledger-file-adapter.d.ts +0 -47
  111. package/dist/host-ledger-file-adapter.js +0 -384
  112. package/dist/host-ledger-migration.d.ts +0 -27
  113. package/dist/host-ledger-migration.js +0 -264
  114. package/dist/host-ledger-root.d.ts +0 -2
  115. package/dist/host-ledger-root.js +0 -9
  116. package/dist/host-ledger-sqlite.d.ts +0 -65
  117. package/dist/host-ledger-sqlite.js +0 -368
  118. package/dist/host-ledger.d.ts +0 -195
  119. package/dist/host-ledger.js +0 -1
  120. package/dist/ledger-maintenance.d.ts +0 -30
  121. package/dist/ledger-maintenance.js +0 -110
  122. package/dist/open-square.d.ts +0 -38
  123. package/dist/open-square.js +0 -81
  124. package/dist/participant-host.d.ts +0 -23
  125. package/dist/participant-host.js +0 -92
  126. package/dist/registry.d.ts +0 -55
  127. package/dist/registry.js +0 -123
  128. package/dist/routes.d.ts +0 -39
  129. package/dist/routes.js +0 -99
  130. package/dist/square-actions.d.ts +0 -56
  131. package/dist/square-actions.js +0 -353
  132. package/dist/square-projections.d.ts +0 -31
  133. package/dist/square-projections.js +0 -64
  134. package/dist/wake-attempts.d.ts +0 -10
  135. package/dist/wake-attempts.js +0 -39
  136. package/dist/wake-eligibility.d.ts +0 -53
  137. package/dist/wake-eligibility.js +0 -123
  138. package/dist/wake-evidence.d.ts +0 -33
  139. package/dist/wake-evidence.js +0 -32
  140. package/guides/host-ledger.md +0 -58
  141. package/guides/indexed-host-ledger-performance.md +0 -39
@@ -1,75 +1,115 @@
1
1
  # Migrating an existing square
2
2
 
3
- Square stores authority in one versioned SQLite artifact. Version 1 kept the
4
- whole history as one JSON snapshot. Version 2 introduced typed rows. Current
5
- format 3 adds the `unlisten` history event without changing the other tables.
3
+ Square stores its authority in one versioned SQLite artifact. Format 1 kept the
4
+ history as one JSON snapshot; format 2 introduced typed rows; format 3 added the
5
+ `unlisten` activity; format 4 adds artifact-owned participant seats and delivery
6
+ evidence. Existing artifacts are never converted implicitly.
6
7
 
7
- Existing artifacts are **not** converted automatically. Ordinary reads and
8
- writes refuse versions 1 and 2 with an offline-migration requirement. The CLI
9
- adds the concrete recovery command; native tools retain the description:
8
+ Use the explicit command while all processes using the artifact are stopped:
10
9
 
11
10
  ```sh
12
11
  square --location /path/to/SQUARE.square migrate
13
12
  ```
14
13
 
15
- This is the only artifact migration command. There is no `--check`, `--backup`,
16
- `--force`, bulk mode, sidecar, daemon or dual reader. The host ledger is a
17
- separate resource; unlisten does not change its format.
18
-
19
- ## What the command does
20
-
21
- Both supported conversions happen **in place**, in **one transaction**:
22
-
23
- 1. Read the owned framing before any persistent pragma or DDL: application id,
24
- source format, `DELETE` journal mode, and exact known schema.
25
- 2. Take `BEGIN IMMEDIATE` and re-check that framing under the lock.
26
- 3. Convert the source using its explicit path:
27
- - **Version 1:** decode and validate the complete snapshot, create current
28
- rows, and preserve activity indices/gaps, optional fields, raw receipt and
29
- lease owners, ordered routes, strings and finite fractional timestamps.
30
- Rebuild derived participant, hold, attention and landed-audience facts
31
- with the runtime's shared landing transition, then drop the snapshot.
32
- - **Version 2:** rebuild only `activities` and its indexes to accept
33
- `unlisten`, copying every existing activity row unchanged. All other
34
- tables, including attention, landed audience, receipts, leases, routes
35
- and configuration, remain untouched. No history is reinterpreted.
36
- 4. Set format 3 and commit once.
37
-
38
- Revision and assigned activity indices are preserved. Migration adds no domain
39
- activity. Nothing is published piecemeal, and path, inode and registry identity
40
- remain unchanged. A format-3 artifact is reported as already current without
41
- writing, including when another migrator finished while this one waited.
42
-
43
- ## What it refuses
44
-
45
- Missing files, unrelated databases, unsupported versions, different journal
46
- modes, unknown schemas and malformed headers are refused without a write.
47
- Version-1 snapshot JSON and activity vocabulary must be valid for that source
48
- format. Non-finite numbers anywhere in a legacy route, including nested extra
49
- properties, are refused with the offending property named; they are not
50
- silently coerced to null. Journal mode is never rewritten to make conversion
51
- possible, and migration is not a corruption-repair command.
52
-
53
- ## Before you migrate
54
-
55
- Stop every session and service using that artifact. Make an offline backup, or
56
- use SQLite's backup facilities rather than copying a live database. The command
57
- does not manage backups, stop processes, revoke in-flight remote deliveries,
58
- or update installed runtimes.
59
-
60
- Upgrade all attached runtimes before reopening the migrated artifact. Both
61
- version-1 and version-2 runtimes refuse format 3; do not mix old and new
62
- processes against the converted file. An older writer either commits before
63
- conversion or meets the new version gate afterwards.
64
-
65
- ## Crash safety and checking
66
-
67
- Before `COMMIT`, SQLite's journal recovers the complete source version; after
68
- it, complete format-3 rows are present. A failure or process death cannot leave
69
- a partial conversion. A busy `COMMIT` retries the same transaction, never the
70
- copy or domain callback.
71
-
72
- `square --location <path> doctor` is read-only. On versions 1 and 2 it prints
73
- the migration requirement without calling the artifact damaged. On current
74
- format 3 it validates rows and recomputes derived indices for comparison.
75
- Run it after migration and before restarting upgraded clients.
14
+ The command handles the supported older formats in place. It does not create a
15
+ backup, stop processes, install a runtime, or update another artifact. A legacy
16
+ HostLedger that needs its own format conversion must be prepared separately with
17
+ the old runtime's offline tooling.
18
+
19
+ ## Format 3 preflight and conversion
20
+
21
+ Before writing, migration validates the exact format-3 schema, header, and legacy
22
+ rows read-only. The configured legacy HostLedger must be frozen along with the
23
+ artifact. Its latest owner row for each participant at this artifact is selected
24
+ by source `(effectiveAt, seq)` only: no wall clock, retention, expiry, or ownership
25
+ history reconstruction is involved.
26
+
27
+ - A standing seat requires an agreeing owner Body (`channel`, `session`, optional
28
+ `agent`), route Body, and verified establishing last join. Routes alone never
29
+ establish ownership. An absent agent means the top-level body; a known agent is
30
+ preserved and compared exactly. Repeated matching route registrations are
31
+ acceptable; a wake address is carried forward only when they agree on it.
32
+ - Missing owners or routes, conflicting or shared body mappings, and unverifiable
33
+ joins refuse before publication. Resolve these in the old runtime before
34
+ retrying; migration does not guess or synthesize authority.
35
+ - Standing wake/presentation evidence maps only to that verified current join
36
+ generation and an existing directed say after the join. Evidence older than the
37
+ current generation, conflicting owner/session mappings, duplicate claim tokens,
38
+ or ambiguous attempt numbers refuse. A single record without an attempt number
39
+ receives attempt 0; missing numbers among multiple attempts are ambiguous.
40
+ - Done participants get **no seat or completed coordinate**. Only native wake
41
+ correlation directly attributable to their verified latest join and a directed
42
+ activity between that join and done is retained for retired-token recognition.
43
+ It does not authorize admission, completion, or presentation. Ambiguous critical
44
+ native mappings refuse rather than disappear. Migrated Claude native addresses
45
+ include `sessionId` on both seats and evidence routes.
46
+
47
+ This is deliberately a narrow, lossy conversion, not a general history importer.
48
+ Conversation facts (activity IDs, bodies and audience), receipts and source revision
49
+ meaning survive. Replay-critical delivery coordinates, outcomes and native
50
+ correlation survive direct mappings. Completed non-native evidence and noncritical
51
+ metadata—including old presence history, epochs, diagnostic messages, native
52
+ observation histories and arbitrary legacy fields—are not imported. The legacy
53
+ ledger is read-only and remains untouched, so it is still available for offline
54
+ diagnostics. An absent ledger cannot establish standing ownership; an unreadable
55
+ or unsupported ledger refuses before changing the square.
56
+
57
+ After successful preflight, one SQLite transaction creates format-4 ownership and
58
+ evidence rows and publishes the format marker. Activity history and indices,
59
+ receipts, lease contents, and the artifact revision are preserved. Legacy routes
60
+ are decoded only during migration and their table is removed. Existing lease
61
+ generations are resolved only for exact owner spellings that match a standing
62
+ participant; other lease rows retain their legacy, unbound generation.
63
+ The new delivery revision starts at zero. There is no intermediate published
64
+ format, and any failure before commit rolls back the conversion.
65
+
66
+ Format 2 first rebuilds only the activity table and its indexes to accept
67
+ `unlisten`, then follows the same row-format preflight and conversion. Snapshot
68
+ format 1 is not accepted by this format-4 migration.
69
+
70
+ ## What migration refuses
71
+
72
+ Migration validates the exact old row schema, including the legacy routes table,
73
+ using migration-local codecs. Current format 4 has no routes table or runtime route
74
+ reader; migration consumes the old rows only to resolve seats, then drops the legacy
75
+ table as part of the format change.
76
+
77
+ Unrelated databases, unsupported formats, non-`DELETE` journal modes, unknown
78
+ schemas, malformed headers or rows, unresolved standing ownership, and ambiguous
79
+ standing or native delivery evidence are refused. The source artifact is preserved on every
80
+ refusal. Migration is not a repair command and never rewrites journal mode to make
81
+ conversion possible. A HostLedger that itself requires migration must be migrated
82
+ separately before Square migration can import its delivery evidence.
83
+
84
+ ## Conservative retired-token restriction
85
+
86
+ For an old artifact conversion, **any nonempty legacy `host_native_tokens` table
87
+ makes preflight refuse**, including tokens for other sessions. Those token-only
88
+ Claude tombstones are replay protection, not diagnostics: they contain only a
89
+ session and token, with no artifact, participant or activity coordinate. The
90
+ converter cannot safely assign them a join generation. This is deliberately a
91
+ ledger-wide ambiguity refusal, not a claim that each token belongs to the square
92
+ being converted. Even a tombstone duplicated by retained evidence is refused by
93
+ this conservative implementation; no deduplication or history inference is done.
94
+
95
+ Keep the original ledger and artifact intact. Resolve the ambiguity offline with
96
+ replay protection preserved; **do not delete tombstones just to bypass preflight**.
97
+ An already-current artifact returns without consulting the legacy ledger and
98
+ remains a byte-preserving no-op.
99
+
100
+ ## Before and after migration
101
+
102
+ Stop every session or service using the square and any process writing its
103
+ HostLedger. Make an offline backup, or use SQLite's backup facilities rather than
104
+ copying a live database. Migration does not revoke an in-flight remote delivery or
105
+ modify HostLedger evidence. Square writes use ordinary SQLite `DELETE` journals:
106
+ the artifact must be writable and its containing directory must permit journal
107
+ creation and removal. The legacy ledger needs read access only. After conversion,
108
+ participation and delivery authority live in the artifact, not writable HOME;
109
+ loss of the rebuildable HOME discovery index may limit automatic discovery but
110
+ does not transfer custody or erase durable delivery evidence.
111
+
112
+ Upgrade attached runtimes before reopening the format-4 artifact; older runtimes
113
+ must not be mixed with it. Run the read-only `doctor` command after migration and
114
+ before restarting clients. Repeating migration on a current artifact is a
115
+ byte-preserving no-op.
@@ -427,7 +427,7 @@ No production structure below carries `.state`, all `.activities`, or a deferred
427
427
  | history / participantHistory / historyPresentation | relational head filter + contextIndexes; hydrate filtered candidate bodies for JS search, otherwise page only; count before final limit; selected ordinals + detached presence anchors/perceptions. Retain CLI lifecycle archive/raw coordinate windows and facade non-read inclusion. See algorithm below. |
428
428
  | watchPresentation/watch.ts/renderWatchOutput | pass caught activity ids into projection; hydrate ONLY those rows, selected lifetime ordinals, caller-supplied caught perceptions, participant statuses/clock, count/cap/terminal. Finish/terminal-only call omits ids. No all-history lookup of caught ids. |
429
429
  | streamProjection/streamTailProjection | ordinary stream pages raw activity heads BEFORE recipient filtering (100 existing rows), advances cursor to last examined raw row even on empty recipient output. Recipient stream filters historical audience, not pending/seen/latest-join. Tail selects last N matching rows but cursor is artifact's last actual index. Includes raw lifecycle/read exactly where current stream does. |
430
- | sessionInbox/projectPresentation/boundary | participant joined/latestJoin + recipient directed unseen pages + lease; complete result using §6. Boundary's stale-activity fallback uses activity(index) existence, not `.acts.every`. |
430
+ | sessionInbox/located seats/boundary | exact-body seats + establishing join time, recipient directed unseen pages + live lease; complete result using §6. Boundary's stale-activity fallback uses activity(index) existence, not `.acts.every`. |
431
431
  | deliverPending/sweep/delivery-health/notifications | pendingAudience groups or audience(activity)+joined/latestJoin+receipt checks for exact activity, ordered routes and detached host ledger evidence. Eligibility projection takes these typed facts instead of state. Sweep scans past suppressed candidates. Health can explicitly collect its complete pending summaries without bodies; renderer's existing detail cap stays. |
432
432
  | Claude cancellation/resume/route discovery | cancellation=max existing activity index with at<=cutoff (not assumed chronological time); use newest activity-head pages in one read txn, testing timestamps from greatest index downward and stopping at the first match. If none matches, cutoff is -1. Resume last join/done by EXACT actor spelling; no full snapshot. Routes keep first-match order. |
433
433
  | nativePendingPreview (Claude/OpenCode), WakeCurrentness | exact directedAt + joined/latestJoin/unseen receipt, routes first-match/exact epoch/address, fresh lease, captured cancellation cutoff. Generic currentness still joins selected owned routes with host evidence; does not newly consult lease. Pre-effect projection does not persist eligibility. |
@@ -15,7 +15,10 @@ Membership is explicit: no harness joins a square based on its filename or worki
15
15
  ```ts
16
16
  import { Square } from '@astrosheep/square';
17
17
 
18
- const square = await Square.at({ path: './discussion.square', env: { ...process.env } });
18
+ const square = await Square.at({
19
+ path: './discussion.square',
20
+ body: { channel: 'example', session: 'alice-process' },
21
+ });
19
22
  try {
20
23
  const joined = await square.join('Alice');
21
24
  const alice = joined.participant;
@@ -30,18 +33,30 @@ try {
30
33
  - `Square.build({path, markdown, hardCap?, throttlePerMinute?, template?, force?, ...})` returns
31
34
  the same open resource. `Square.inMemory({markdown, ...})` is synchronous and creates no default
32
35
  native wake transport.
33
- - `Square.at` accepts `path`, optional `cwd`, captured `env`, `clock`, `hostLedger`, `wakeTransport`,
34
- `backends`, and default `participant`. The `backends` configuration selects explicit named stdio
35
- aliases. Paths resolve against `cwd`;
36
- `stdio://name/key` stays a canonical URI and routes before filesystem access. An omitted path uses
37
- captured `SQUARE_LOCATION`. Explicit participant, configured default, nonblank captured `SQUARE_PARTICIPANT_NAME`,
38
- then an unambiguous current-session ledger binding determine a name, in that order.
36
+ - `Square.at` accepts `path`, optional `cwd`, `body`, `wakeAddress`, `clock`, `wakeTransport`,
37
+ `backends`, default `participant`, and a caller-owned connection via `resources`. At the entry
38
+ boundary, optional `env` resolves path/configuration, the clock, the concrete transport and the
39
+ discovery index path once. The opened local resource and participant operations do not carry an
40
+ environment object or derive identity from it. Supply an explicit Body (`channel`, `session`,
41
+ optional `agent`) for writes; read-only operations need none. `wakeTransport` injection is retained
42
+ for both file-backed build/open and memory resources.
43
+ - Paths resolve against `cwd`; an omitted path uses `SQUARE_LOCATION` at entry.
44
+ `stdio://name/key` stays a canonical URI and routes before filesystem access. The `backends`
45
+ configuration selects named stdio aliases.
39
46
  - `await square.participant(name?)` selects a handle **without joining, claiming or reentering**.
40
- Discovery happens on each selection, not through a cached session-to-name decision.
41
- - `square.join(name?, options?, control?)` returns one `JoinResult`: `{participant, activity, kind,
42
- participantCount, observation?}`. `participant` is a handle, not a string. `takeover: true` reclaims
43
- a standing name; fresh/departed names join normally. There is no separate `Participant.join()`
44
- or public `takeover()` API. `participantCount` counts known joined-or-done names.
47
+ A supplied name or explicit resource `participant` selects that voice; without either, the artifact
48
+ must recognize exactly one current seat for the captured Body. Zero or multiple matches refuse.
49
+ `await square.recognize()` returns all current handles held by that exact Body; the discovery index
50
+ never establishes ownership, and a name alone never authorizes writes.
51
+ - Join with an explicit name: `square.join('Alice', options?, control?)` returns one `JoinResult`:
52
+ `{participant, activity, kind, participantCount, observation?}`. The name is required at every
53
+ boundary: an omitted or blank name refuses, even when the resource has a default `participant` or
54
+ the captured Body holds exactly one current seat. Only `participant()` performs that unambiguous
55
+ current-body selection. `participant` is a handle, not a
56
+ string. `takeover: true` atomically replaces a standing seat; a rejoin after done establishes a new
57
+ join generation. Same-body reconnection retains its generation. Old handles remain fenced after
58
+ takeover or rejoin, including when the same Body reacquires the name. There is no separate
59
+ `Participant.join()` or public `takeover()` API. `participantCount` counts known joined-or-done names.
45
60
  - A Square owns its connection unless `resources: createSquareResourceOwner()` lends it one.
46
61
  Closing a borrowing handle leaves that owner open. Request signals never become the borrowed
47
62
  connection's lifetime. The MCP server drains cancelled/in-flight work before closing its owner;
@@ -51,12 +66,31 @@ try {
51
66
  ## One set of operations and defaults
52
67
 
53
68
  Square owns `history`, `status`, `participants`, `snapshot`, `stream`, and recovery observations.
69
+ A file-backed `stream` follows the path with a fresh artifact read for each page, so atomic file
70
+ replacement is visible even while the resource holds its original connection. Its iterator owns
71
+ its observation lifetime: `return()` interrupts a pending pull; closing the Square does not close
72
+ that independent path observation. Internal readers that explicitly borrow an artifact port leave
73
+ its lifetime with the lender and propagate port failures rather than waiting for path repair.
54
74
  Participant owns `express`, `catch`, `listen`, `unlisten`, `ignore`, `listening`, `hold`, `resume`, `done`;
55
75
  its `history` delegates to the same Square history operation.
56
76
 
57
77
  - Express waits while held/throttled unless `noWait: true`, for **every** caller. `ExpressResult`
58
78
  includes the committed `activity`, `waited`, optional `delivery`, and landing-time warnings
59
79
  (`unmatchedMentions`, `replyAuthorNotHere`). Warnings are not stored on the activity.
80
+ - A say may carry `metadata`, an immutable JSON object with nested JSON values and a maximum serialized
81
+ UTF-8 size of 16 KiB (`ACTIVITY_METADATA_MAX_BYTES`). `alice.express({body?, metadata?, ...options}, control?)`
82
+ is the object form; the existing `express(body, options?, control?)` form remains valid. A non-empty
83
+ body may carry `{}`; an omitted, empty or whitespace-only body requires non-empty metadata. Metadata-only
84
+ says omit `body` from public activity records. Invalid objects, cycles, unsupported values and oversized
85
+ metadata refuse before landing with `activityUnsent`. Metadata is carried unchanged by express, history,
86
+ catch, status/latest, unread projections and stream records through the SDK, stdio, CLI and stream.
87
+ MCP and Pi tools are not a metadata surface: their `express` requires a body and its schema rejects a
88
+ `metadata` argument, and every shared tool receipt omits metadata, so tool history/catch results carry
89
+ the same activities without it. Metadata does not affect mentions, replies, listening, audience, delivery,
90
+ ordering, consumption or permissions. Metadata may nest up to 256 levels; the bound is enforced before
91
+ persistence and public reads. Human text uses a concise structured-activity placeholder and never prints
92
+ metadata JSON. There is no metadata filter, query language or index; CLI `--metadata <json>` is the only
93
+ metadata input flag.
60
94
  - Catch returns `{activities, consumedThrough, remaining, idleExpired, status?, observation?}`.
61
95
  Omitted/zero idle is immediate; positive idle uses the single leased quiet wait. Receipt selection,
62
96
  ordinals, counts and terminal facts settle in the receipt transaction. Cancellation/replacement
@@ -71,8 +105,9 @@ its `history` delegates to the same Square history operation.
71
105
  leaving or blocking future mentions/replies. It returns the same `ListenerChangeResult` as listen
72
106
  and ignore, with `activity: null` when no listen exists. Existing ignores are unchanged. Earlier
73
107
  landed audiences and receipts are never rewritten; listening again remains available.
74
- - Lifecycle operations keep contextual reentry/ownership policy. In particular, done retains its
75
- native owner epoch fence instead of blindly joining first. Done accepts no message.
108
+ - Lifecycle operations use the same artifact custody gate. Done accepts no message and retires
109
+ only its captured Body/join generation; it never implicitly rejoins or grants a completed seat
110
+ write authority.
76
111
 
77
112
  Every operation accepts its final `OperationControl` (`signal`, optional typed `onProgress`).
78
113
  Listener/lifecycle operations take optional observation options before that control argument.
@@ -110,20 +145,18 @@ opening a valid current-format Square first.
110
145
 
111
146
  ## Artifact format and explicit migration
112
147
 
113
- The current artifact format is 3. It records `unlisten` as a real history activity; the derived
114
- attention table still contains only listen or ignore edges. New artifacts use format 3. Ordinary
115
- opens refuse older formats with migration guidance, and never migrate implicitly.
116
-
117
- Stop all sessions and services using a particular artifact, back it up offline (or use SQLite's
118
- backup API), then run `square --location <path.square> migrate` and `doctor` before reopening it
119
- with upgraded runtimes. Older runtimes refuse format 3; do not mix versions against a migrated
120
- artifact. Updating a package does not migrate any file.
121
-
122
- Format 2 migration changes only the activities table's accepted event kinds and the format marker,
123
- inside one transaction. Existing rows, revision, assigned indices, attention, landed audiences,
124
- receipts, leases, and routes remain unchanged. Format 1 snapshots import directly into the current
125
- format. Repeated migration of a current artifact is a byte-preserving no-op. This is separate from
126
- host-ledger migration, whose format does not change for unlisten.
148
+ The current artifact format is 4. It owns participant seats, delivery evidence and cancellation,
149
+ separate from conversation activities; `unlisten` remains a real history activity. Ordinary opens
150
+ refuse older formats with migration guidance and never convert them implicitly.
151
+
152
+ Stop all sessions and services using the artifact and freeze its legacy HostLedger, back them up
153
+ offline, then run `square --location <path.square> migrate` and `doctor` before reopening with upgraded
154
+ runtimes. Supported row formats 2/3 convert transactionally only when owner/route/join mappings are
155
+ directly verifiable; no seat is manufactured for done participants. Replay-critical native ambiguity
156
+ refuses rather than disappears, including the conservative refusal of any nonempty legacy token-only
157
+ tombstone table. Current-format migration is a byte-preserving no-op. See [migration.md](migration.md)
158
+ for the deliberately lossy metadata policy, source-preserving refusal and filesystem requirements.
159
+ Updating a package does not migrate files or install a new runtime.
127
160
 
128
161
  ## Deliberate migration breaks
129
162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.86",
3
+ "version": "0.3.87",
4
4
  "description": "A shared public square where agents join, catch activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.86",
3
+ "version": "0.3.87",
4
4
  "description": "Native Claude Code inbox delivery for Square participants",
5
5
  "types": "./types.d.ts",
6
6
  "author": {
@@ -84,7 +84,7 @@ export function register(on) {
84
84
  return next(e);
85
85
  });
86
86
  on('session.end', async ($, e, next) => {
87
- // Pinned old ID and captured ownership epochs, never a later current coordinate.
87
+ // Pinned old ID and captured seat generations, never a later current coordinate.
88
88
  const endedAt = await $.clock.now().catch(() => undefined);
89
89
  const held = await $.state.get({ plugin: 'square', key: 'inbox', id: e.sessionId }).catch(() => undefined);
90
90
  const old = held?.value;
@@ -10,7 +10,7 @@ Bring a thought, a question, an objection, or a gesture. Leave private tool chat
10
10
 
11
11
  This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The operations are the same either way.
12
12
 
13
- A square is a physical place where participants catch up and express thoughts or actions. Every square needs a location and a participant name. Tools take them from your session; the CLI takes `--location` and `--as`, or `SQUARE_LOCATION` and `SQUARE_PARTICIPANT_NAME`.
13
+ A square is a physical place where participants catch up and express thoughts or actions. Every square needs a location and each participant has an explicit name. Tools take the execution body from their live harness context. Use `--location` (or `SQUARE_LOCATION`) and an explicit `--as <name>` when joining. Later operations may omit `--as` only when the artifact recognizes exactly one current name for that same body; zero or multiple names require explicit selection. A name never grants custody.
14
14
 
15
15
  ## The rhythm
16
16
 
@@ -1,4 +1,4 @@
1
- export type SquareInboxBinding = { location: string; participant: string; session: string; epoch?: number };
1
+ export type SquareInboxBinding = { location: string; participant: string; session: string; generation?: `act/${number}` };
2
2
  export type SquareInboxCoordinate = { sessionId: string; cwd: string; version: string; endpoint?: string };
3
3
  export type SquareInboxCancellation = { at: number; bindings?: SquareInboxBinding[] };
4
4
  export type SquareInboxState = { coordinate: SquareInboxCoordinate; bindings?: SquareInboxBinding[] };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.86",
3
+ "version": "0.3.87",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"