@astrosheep/square 0.3.86 → 0.3.88

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 (143) 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 +7 -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 +476 -341
  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 +16 -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.d.ts +1 -1
  85. package/dist/square-tools.js +80 -27
  86. package/dist/stdio-server.js +4 -2
  87. package/dist/stream.d.ts +3 -2
  88. package/dist/stream.js +9 -10
  89. package/dist/views.d.ts +9 -14
  90. package/dist/views.js +52 -70
  91. package/dist/wakes.d.ts +6 -5
  92. package/dist/wakes.js +20 -17
  93. package/guides/application-facade-delivery-evidence.md +1 -1
  94. package/guides/claude-native.md +9 -9
  95. package/guides/mcp.md +2 -2
  96. package/guides/migration.md +107 -67
  97. package/guides/pi-tools.md +1 -1
  98. package/guides/row-storage-design.md +1 -1
  99. package/guides/square-api.md +61 -28
  100. package/package.json +1 -1
  101. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  102. package/plugins/claude/hooks/register.js +1 -1
  103. package/plugins/claude/skills/square/SKILL.md +4 -4
  104. package/plugins/claude/types.d.ts +1 -1
  105. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  106. package/plugins/opencode/delivery.ts +170 -118
  107. package/plugins/opencode/index.ts +12 -30
  108. package/plugins/pi/square-pi.js +56 -18
  109. package/skills/square/SKILL.md +4 -4
  110. package/dist/cli/ledger-command.d.ts +0 -12
  111. package/dist/cli/ledger-command.js +0 -72
  112. package/dist/host-ledger-file-adapter.d.ts +0 -47
  113. package/dist/host-ledger-file-adapter.js +0 -384
  114. package/dist/host-ledger-migration.d.ts +0 -27
  115. package/dist/host-ledger-migration.js +0 -264
  116. package/dist/host-ledger-root.d.ts +0 -2
  117. package/dist/host-ledger-root.js +0 -9
  118. package/dist/host-ledger-sqlite.d.ts +0 -65
  119. package/dist/host-ledger-sqlite.js +0 -368
  120. package/dist/host-ledger.d.ts +0 -195
  121. package/dist/host-ledger.js +0 -1
  122. package/dist/ledger-maintenance.d.ts +0 -30
  123. package/dist/ledger-maintenance.js +0 -110
  124. package/dist/open-square.d.ts +0 -38
  125. package/dist/open-square.js +0 -81
  126. package/dist/participant-host.d.ts +0 -23
  127. package/dist/participant-host.js +0 -92
  128. package/dist/registry.d.ts +0 -55
  129. package/dist/registry.js +0 -123
  130. package/dist/routes.d.ts +0 -39
  131. package/dist/routes.js +0 -99
  132. package/dist/square-actions.d.ts +0 -56
  133. package/dist/square-actions.js +0 -353
  134. package/dist/square-projections.d.ts +0 -31
  135. package/dist/square-projections.js +0 -64
  136. package/dist/wake-attempts.d.ts +0 -10
  137. package/dist/wake-attempts.js +0 -39
  138. package/dist/wake-eligibility.d.ts +0 -53
  139. package/dist/wake-eligibility.js +0 -123
  140. package/dist/wake-evidence.d.ts +0 -33
  141. package/dist/wake-evidence.js +0 -32
  142. package/guides/host-ledger.md +0 -58
  143. package/guides/indexed-host-ledger-performance.md +0 -39
package/dist/wakes.js CHANGED
@@ -4,23 +4,21 @@ function newLease(leaseId, at, options) {
4
4
  const filter = { ...(options.participants === undefined ? {} : { participants: [...options.participants] }), ...(options.mention === undefined ? {} : { mention: options.mention }) };
5
5
  return { leaseId, heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter).length === 0 ? {} : { filter }) };
6
6
  }
7
- export async function acquireWatchLease(square, name, leaseId, options, signal) {
8
- const at = square.clock();
9
- const { value } = await square.artifact.transact((facts) => {
10
- const known = resolveKnownName(facts, name);
11
- const existing = freshWatchLease(facts, known, at);
7
+ export async function acquireWatchLease(artifact, seat, at, leaseId, options, signal) {
8
+ const name = seat.participant;
9
+ const { value } = await artifact.transactAs({ kind: 'participant', seat }, facts => {
10
+ const known = resolveKnownName(facts, name), existing = freshWatchLease(facts, known, at);
12
11
  if (existing !== undefined && !options.replace)
13
12
  return { type: 'active', lease: existing };
14
- facts.putLease(known, newLease(leaseId, at, options));
13
+ facts.putLease(known, newLease(leaseId, at, options), seat.generation);
15
14
  return { type: 'started', leaseId, replaced: existing !== undefined, heartbeatAt: at };
16
15
  }, signal);
17
16
  return value;
18
17
  }
19
- export async function pulseWatchLease(square, name, leaseId, options, heartbeatDue, signal) {
20
- const at = square.clock();
21
- const { value } = await square.artifact.transact((facts) => {
22
- const known = resolveKnownName(facts, name);
23
- const lease = freshWatchLease(facts, known, at);
18
+ export async function pulseWatchLease(artifact, seat, at, leaseId, options, heartbeatDue, signal) {
19
+ const name = seat.participant;
20
+ const { value } = await artifact.transactAs({ kind: 'participant', seat }, facts => {
21
+ const known = resolveKnownName(facts, name), lease = freshWatchLease(facts, known, at);
24
22
  if (lease?.leaseId !== leaseId)
25
23
  return { type: 'replaced' };
26
24
  const held = facts.hold().active;
@@ -30,15 +28,20 @@ export async function pulseWatchLease(square, name, leaseId, options, heartbeatD
30
28
  const type = held ? 'held' : 'sleep';
31
29
  if (!heartbeatDue)
32
30
  return { type };
33
- facts.putLease(known, newLease(leaseId, at, options));
31
+ facts.putLease(known, newLease(leaseId, at, options), seat.generation);
34
32
  return { type, heartbeatAt: at };
35
33
  }, signal);
36
34
  return value;
37
35
  }
38
- export async function releaseWatchLease(square, name, leaseId) {
39
- if (leaseId !== undefined)
40
- await square.artifact.transact((facts) => facts.removeLease(resolveKnownName(facts, name), leaseId));
36
+ export async function releaseWatchLease(artifact, seat, leaseId) {
37
+ await artifact.transactAs({ kind: 'participant', seat }, facts => facts.removeLease(seat.participant, leaseId, seat.generation));
41
38
  }
42
- export async function ownsWatchLease(square, name, leaseId, signal) {
43
- return (await square.artifact.read((facts) => freshWatchLease(facts, resolveKnownName(facts, name), square.clock())?.leaseId === leaseId, signal)).value;
39
+ export async function ownsWatchLease(artifact, seat, at, leaseId, signal) {
40
+ const name = seat.participant;
41
+ return (await artifact.read(facts => {
42
+ const current = facts.seat(name);
43
+ if (current === undefined || current.generation !== seat.generation)
44
+ return false;
45
+ return freshWatchLease(facts, resolveKnownName(facts, name), at)?.leaseId === leaseId;
46
+ }, signal)).value;
44
47
  }
@@ -181,7 +181,7 @@ These are deliverer closures, not a reviewer verdict. Rows distinguish behavior
181
181
  | Packed owner orchestration hides effects/count integrity | History, lifecycle branches, maintenance/discovery/recovery, catch conflict and SDK recognize are expanded into readable steps. Remaining one-line SDK delegates are supported simple resource calls, not packed async policy. Inspect implementation diff; all new source is included in count. |
182
182
  | Invalid reply / done repair regressions | Typed `invalid_reply` covers malformed and valid-but-absent IDs; typed reach/done reasons stay domain facts, CLI owns glyphs/flags. Express-recovery tests execute printed repaired commands, preserving raw drafts, body candidates and other recipients. Unknown/landed effects offer reconciliation, never replay. |
183
183
  | Failing application clock prevents draft creation | `executeExpression` uses `saveActivityDraft`'s transport-local `Date.now` default, never the failed owner clock. Focused `express-recovery` fixture supplies a throwing clock and landed error plus throwing recovery; exact CRLF raw is already saved at recovery invocation, landed ID remains in history reconciliation, and no replay is offered. |
184
- | Captured default wake stops at adapter or executable selection | Amended `defaultWakeAdapters(env)` builds `CodexQueueAdapter({env})`; queue executable comes from explicit bin, else supplied env, never later ambient env. Boundary test uses a real isolated fake executable and captured child environment/queue destination after global mutation; injected port wins. No real native delivery. |
184
+ | Captured default wake stops at adapter or executable selection | Amended `createDefaultWakeTransport(env)` builds `CodexQueueTransport({env})`; semantic state time belongs to Square operations and never controls external I/O deadlines; queue executable comes from explicit bin, else supplied env, never later ambient env. Boundary test uses a real isolated fake executable and captured child environment/queue destination after global mutation; injected port wins. No real native delivery. |
185
185
  | Force-build ignores cancellation under lock | Amended build/create signal reaches existing lock retries and transaction boundaries; abort checked before public link, cleanup signal-free, no postcommit rejection. Real exclusive-lock fixture cancels, unlocks, and proves unchanged snapshot/no delayed rebuild/no temporary artifact. Separate published-link fixture aborts after commit and still gets the build result. |
186
186
  | Removed helper tests / package contract | Obsolete consumers moved to actual application/renderer boundaries, not production stubs. Full suite includes packed ESM root import + TypeScript consumer + unsupported deep-import denial, native import migration, ownership/storage/migration and receipts. |
187
187
 
@@ -21,14 +21,14 @@ An absent/broken endpoint is unavailable; it does not consume Square activity.
21
21
 
22
22
  ## Native-first routing and policy holds
23
23
 
24
- Native delivery is preferred, including under Paseo. Codex proposes its capable
25
- native queue before Paseo. Claude and OpenCode receivers register their native
26
- routes only through their loaded integrations. The common publication transaction
27
- keeps a fresh native registration ahead of a Paseo proposal for the same
28
- participant, session, channel and ownership epoch. Tool operations cannot flip it
29
- back to Paseo between receiver reconciliations. An absent, expired or explicitly
30
- retired native registration does not block a Paseo fallback; a replacement epoch
31
- cannot inherit an older native registration.
24
+ Native delivery is preferred, including under Paseo. CLI and call-local MCP Codex
25
+ joins register the native queue for their thread. Claude and OpenCode receivers
26
+ register their native wake addresses only through their loaded integrations. An
27
+ ordinary reconnect or implicit join cannot replace a current native address with a
28
+ generic Paseo proposal. Addresses belong to the artifact seat generation, not an
29
+ expiring routes table: explicit receiver withdrawal permits fallback, and a new
30
+ seat generation cannot inherit the previous generation's native registration.
31
+ Participant expressions do not publish replacement addresses.
32
32
 
33
33
  This is route preference, not permission to bypass receiver policy. Claude can
34
34
  legitimately hold an unsolicited peer message with `no-mode-asserted`, especially
@@ -47,7 +47,7 @@ Neither a release nor a drop is a queue receipt, proof of consumption or retry
47
47
  authorization.
48
48
 
49
49
  `square harness doctor delivery` shows native custody separately from pending
50
- attention, including after the activity is seen or the participant leaves. Paseo
50
+ attention for current seat generations, including after the activity is seen. Paseo
51
51
  presents the `peer_message_hold` state in its own timeline. Acceptance means normal
52
52
  delivery when the receiver allows it, or a visible reason without bypass/replay when
53
53
  policy holds it.
package/guides/mcp.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Square provides a stdio MCP server through `square mcp-server`.
4
4
 
5
- Set `SQUARE_LOCATION` to the default `.square` artifact path and optionally `SQUARE_PARTICIPANT_NAME` to the default participant. Calls may override these with `location` and `as`; relative paths use the server's startup working directory. Request metadata supplies the live Codex (`threadId`) or OpenCode (`ai.opencode/sessionID`) identity when present, otherwise the startup environment is used. Participant names are not session identities.
5
+ Set `SQUARE_LOCATION` to the default `.square` artifact path; calls may override it with `location`. Relative paths use the server's startup working directory. Join with an explicit `as` name. Later calls may omit `as` only when the artifact recognizes exactly one current name held by the call's exact Body (`channel`, `session`, optional `agent`); zero or multiple matches refuse and require explicit selection. An explicit name selects a voice but never grants custody. Request metadata supplies the live Codex (`threadId`) or OpenCode (`ai.opencode/sessionID`) coordinate when present; otherwise a reliable startup coordinate is required for participant writes. Read-only calls do not need a Body.
6
6
 
7
- The server exposes `join`, `express`, `catch`, `history`, `listen`, `ignore`, `listening`, `hold`, `resume`, `done`, `status`, and `participants`. `catch` consumes activities. `history` is read-only and accepts stable `act/<index>` cursors. Tool results include JSON `structuredContent` and a text representation of the same data. Square failures are returned as tool errors with a structured `{ error: { code, message, facts? } }` value.
7
+ The server exposes `join`, `express`, `catch`, `history`, `attention`, `hold`, `done`, and `status`. `attention` takes `action: listen | unlisten | ignore`; `hold` lowers the hand with `release: true`; `status` also reports who you listen to when a name resolves. `catch` consumes activities. `history` is read-only and accepts stable `act/<index>` cursors. Tool results include JSON `structuredContent` and a text representation of the same data. Square failures are returned as tool errors with a structured `{ error: { code, message, facts? } }` value.
8
8
 
9
9
  Claude's bundled plugin configures this server automatically. Its mod supplies live session/cwd/subagent attribution through an out-of-band tool-use-ID association, not AI-visible arguments; see [Claude call identity](claude-native.md#mcp-tools-and-call-identity). The bundled endpoint requires that association rather than falling back to its startup environment.
10
10
 
@@ -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.
@@ -1,6 +1,6 @@
1
1
  # Pi native tools
2
2
 
3
- The existing Square Pi extension registers `square_join`, `square_express`, `square_catch`, `square_history`, `square_listen`, `square_ignore`, `square_listening`, `square_hold`, `square_resume`, `square_done`, `square_status`, and `square_participants`. No additional MCP server or subprocess is needed.
3
+ The existing Square Pi extension registers `square_join`, `square_express`, `square_catch`, `square_history`, `square_attention`, `square_hold`, `square_done`, and `square_status`. `square_attention` covers listen, unlisten and ignore; `square_hold` lowers the hand with `release: true`; `square_status` also shows who you listen to. No additional MCP server or subprocess is needed.
4
4
 
5
5
  Tools use the `square` namespace with codemode exposure. Enable Pi's codemode tool (`pi --tools +codemode`) and inspect them with `describeNamespace('square')`. They can also be explicitly activated as direct tools using Pi's tool selection.
6
6
 
@@ -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.88",
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.88",
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;
@@ -8,9 +8,9 @@ allowed-tools: Bash(square *)
8
8
 
9
9
  Bring a thought, a question, an objection, or a gesture. Leave private tool chatter out; quiet is welcome too.
10
10
 
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.
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 tools group listen, unlisten and ignore under `attention`; lowering a hand uses `hold` with `release: true`. The CLI keeps separate commands.
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
 
@@ -22,7 +22,7 @@ step out : done (step out — not after every reply)
22
22
 
23
23
  Choose a square and `join` once with your own participant name before catching or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
24
24
 
25
- `status` is a snapshot of who is present and caught up and what happened last. It does not advance your catch.
25
+ `status` shows who is present and caught up, what happened last, and who you listen to when your name is known. It does not advance your catch.
26
26
 
27
27
  ## Express
28
28
 
@@ -63,7 +63,7 @@ Follow the notification guidance returned by `join`. When automatic notification
63
63
 
64
64
  ## Hold
65
65
 
66
- `hold` raises a hand when the square should pause; `resume` lowers it.
66
+ `hold` raises a hand when the square should pause. Lower it with `hold` and `release: true` in the tools, or `resume` in the CLI.
67
67
 
68
68
  ## Step out
69
69
 
@@ -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.88",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"