@astrosheep/square 0.3.70 → 0.3.71

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 (115) hide show
  1. package/dist/activity-feed.d.ts +4 -5
  2. package/dist/activity-feed.js +14 -10
  3. package/dist/artifact.d.ts +26 -33
  4. package/dist/artifact.js +346 -317
  5. package/dist/attention-presentation.js +2 -9
  6. package/dist/automatic-session.js +6 -6
  7. package/dist/boundary-presentation.js +3 -3
  8. package/dist/catch-decisions.d.ts +19 -12
  9. package/dist/catch-decisions.js +32 -68
  10. package/dist/claude-delivery.js +7 -7
  11. package/dist/claude-mod.js +18 -7
  12. package/dist/cli/catch-presentation.d.ts +7 -0
  13. package/dist/cli/catch-presentation.js +55 -0
  14. package/dist/cli/context.d.ts +1 -1
  15. package/dist/cli/context.js +5 -23
  16. package/dist/cli/express-presentation.d.ts +10 -0
  17. package/dist/cli/express-presentation.js +196 -0
  18. package/dist/cli/maintenance-commands.d.ts +4 -0
  19. package/dist/cli/maintenance-commands.js +29 -6
  20. package/dist/cli/observation-commands.d.ts +7 -5
  21. package/dist/cli/observation-commands.js +275 -262
  22. package/dist/cli/program.d.ts +1 -1
  23. package/dist/cli/program.js +43 -56
  24. package/dist/cli/registry.js +2 -1
  25. package/dist/cli/square-commands.d.ts +3 -9
  26. package/dist/cli/square-commands.js +134 -255
  27. package/dist/codex-queue.js +4 -1
  28. package/dist/decisions.d.ts +26 -74
  29. package/dist/decisions.js +190 -335
  30. package/dist/delivery-health.js +13 -22
  31. package/dist/delivery-operations.d.ts +0 -11
  32. package/dist/delivery-operations.js +44 -33
  33. package/dist/delivery.d.ts +33 -32
  34. package/dist/delivery.js +70 -81
  35. package/dist/help.js +6 -1
  36. package/dist/inbox.d.ts +27 -0
  37. package/dist/inbox.js +55 -18
  38. package/dist/index.d.ts +2 -4
  39. package/dist/index.js +1 -1
  40. package/dist/list.d.ts +7 -1
  41. package/dist/list.js +49 -122
  42. package/dist/mcp.d.ts +1 -1
  43. package/dist/mcp.js +7 -37
  44. package/dist/model.d.ts +4 -44
  45. package/dist/model.js +1 -3
  46. package/dist/notifications.d.ts +1 -1
  47. package/dist/notifications.js +13 -7
  48. package/dist/open-square.d.ts +16 -2
  49. package/dist/open-square.js +40 -1
  50. package/dist/perception-projection.d.ts +6 -15
  51. package/dist/perception-projection.js +6 -26
  52. package/dist/plugins/opencode/delivery.js +9 -9
  53. package/dist/plugins/opencode/index.js +3 -3
  54. package/dist/ports.d.ts +156 -11
  55. package/dist/presentation-operations.d.ts +1 -1
  56. package/dist/presentation-operations.js +35 -44
  57. package/dist/presentation.d.ts +12 -19
  58. package/dist/presentation.js +33 -54
  59. package/dist/routes.d.ts +7 -14
  60. package/dist/routes.js +33 -62
  61. package/dist/runtime.d.ts +12 -39
  62. package/dist/runtime.js +33 -164
  63. package/dist/square-actions.d.ts +11 -21
  64. package/dist/square-actions.js +171 -271
  65. package/dist/square-application.d.ts +31 -55
  66. package/dist/square-application.js +637 -141
  67. package/dist/square-core.d.ts +3 -94
  68. package/dist/square-core.js +8 -353
  69. package/dist/square-domain.d.ts +24 -0
  70. package/dist/square-domain.js +77 -0
  71. package/dist/square-facade.d.ts +360 -9
  72. package/dist/square-migration.d.ts +24 -0
  73. package/dist/square-migration.js +211 -0
  74. package/dist/square-projections.d.ts +2 -42
  75. package/dist/square-projections.js +1 -37
  76. package/dist/square-sqlite.d.ts +59 -0
  77. package/dist/square-sqlite.js +702 -0
  78. package/dist/stream.d.ts +3 -10
  79. package/dist/stream.js +81 -52
  80. package/dist/views.d.ts +27 -95
  81. package/dist/views.js +217 -87
  82. package/dist/wake-eligibility.d.ts +15 -5
  83. package/dist/wake-eligibility.js +32 -16
  84. package/dist/wakes.d.ts +3 -3
  85. package/dist/wakes.js +31 -27
  86. package/guides/application-facade-delivery-evidence.md +200 -0
  87. package/guides/application-facade-design.md +95 -0
  88. package/guides/change-waits.md +4 -4
  89. package/guides/migration.md +77 -0
  90. package/guides/primitive-catch-consolidation.md +85 -0
  91. package/guides/primitive-history-consolidation.md +76 -0
  92. package/guides/primitive-status-consolidation.md +87 -0
  93. package/guides/primitive-storage-consolidation.md +128 -0
  94. package/guides/row-runtime-delivery-evidence.md +160 -0
  95. package/guides/row-storage-design.md +567 -0
  96. package/package.json +1 -1
  97. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  98. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  99. package/plugins/opencode/delivery.ts +11 -11
  100. package/plugins/opencode/index.ts +3 -3
  101. package/plugins/paseo/client/square-message.tsx +70 -52
  102. package/plugins/paseo/shared/message.ts +15 -0
  103. package/plugins/pi/square-pi.js +3 -0
  104. package/dist/activity.d.ts +0 -14
  105. package/dist/activity.js +0 -248
  106. package/dist/square-file-adapter.d.ts +0 -26
  107. package/dist/square-file-adapter.js +0 -85
  108. package/dist/square-state.d.ts +0 -6
  109. package/dist/square-state.js +0 -138
  110. package/dist/square-storage.d.ts +0 -14
  111. package/dist/square-storage.js +0 -188
  112. package/dist/square-wiring.d.ts +0 -33
  113. package/dist/square-wiring.js +0 -109
  114. package/dist/watch.d.ts +0 -8
  115. package/dist/watch.js +0 -252
@@ -0,0 +1,567 @@
1
+ # Row-oriented artifact design
2
+
3
+ **Status: design closed; row runtime candidate implemented and measured, awaiting independent review/acceptance.**
4
+ Source baseline: `99679eb` (0.3.70). This document specifies the design; [runtime delivery evidence](row-runtime-delivery-evidence.md) records implementation verification and actual capacity measurements. Explicit migration remains a separately accepted delivery.
5
+
6
+ ## Decision in brief
7
+
8
+ - Keep one versioned SQLite `.square`; replace whole-history JSON reads/writes with transaction-scoped typed domain reads and transitions.
9
+ - Domain code owns behavior; the artifact adapter owns SQLite. Keep the public operation facade; any future daemon runs those operations and their transactions server-side, rather than transmitting callback-based storage ports.
10
+ - Persist authority once: activities, sparse receipts, leases, ordered routes and configuration. Maintain participant/attention/audience indices atomically; do not add an inbox, cursor, eligible/notified flags, or a sidecar.
11
+ - Keep receiver-owned discovery without guaranteed push. Background traversal must progress despite unrelated writes and revisit earlier withheld candidates on change/expiry.
12
+ - Explicit `square --location <path.square> migrate` converts v1 in place in one transaction. No runtime dual format, automatic migration, daemon or socket is added.
13
+ - Two acceptance boundaries: row-backed runtime, then explicit migration. Release both together. No claim of 300-process capacity until the implementation is measured.
14
+
15
+ This document supersedes the exploratory inbox/snapshot sketches and temporary design reports. The domain specification below fixes interfaces/schema/algorithms; the migration and delivery terms fix failure handling and verification. The AGENTS.md amendment is proposed here and lands with runtime implementation, not as a claim about today's authority.
16
+
17
+ # Domain specification
18
+
19
+ The interfaces below are the settled private interfaces; public behavior remains unchanged. Migration and acceptance are specified later in this document.
20
+
21
+ ## 1. Boundary and authority
22
+
23
+ Replace AGENTS.md's `All behavior operates on SquareState ...` sentence, with the runtime change, by:
24
+
25
+ > Behavior operates on typed Square domain facts obtained through transaction-scoped reads and domain transitions. SQLite schema, SQL, and storage framing remain private to the artifact boundary. Partial fact reads must not masquerade as a complete SquareState.
26
+
27
+ One typed domain representation: activities, sparse seen receipts, catch leases, and ordered published routes, plus small configuration facts. Current participants/attention and historical landed audience are reconstructible indices. No inbox, continuous cursor, pending flag, eligibility, persisted ordinal, or receiver acknowledgement watermark. Hold is read from the latest hold/resume activity, not another authoritative fact.
28
+
29
+ Three layers:
30
+ - Domain actions validate admission and produce `Act` values; domain projections combine the read capabilities below. Host/network work is outside artifact transactions.
31
+ - A domain transaction wrapper implements `SquareRead`/`SquareWrite`. `land()` allocates an index and runs the single shared landing transition. This wrapper owns interpretation of domain facts, not SQL.
32
+ - The private SQLite driver decodes typed rows, translates the finite selectors below, and applies typed landing effects. Only this driver performs artifact I/O. No SQL, row objects, statement handles, SQLite names, or transaction capabilities reach renderers/harnesses. The memory adapter implements the same capabilities and uses the same landing transition.
33
+
34
+ There is no whole-state runtime fallback. Full integrity inspection, legacy migration, and an explicit test/export projection are exceptional bounded-by-request operations; hot callers must not invoke them.
35
+
36
+ ## 2. Exact private domain port
37
+
38
+ These types are private to the Square library. Public facade Activity/CatchResult/HistoryQuery/SquareSnapshot remain unchanged. Existing `Act`, `StoredAct`, `WatchLease`, `HardCap`, `Perception`, and `WakeRouteKind` keep their meanings; `StoredAct` decoding permits exactly the v1 optional-field distinctions, including absent say.mentions.
39
+
40
+ ```ts
41
+ type ActivityIndex = number; // validated nonnegative safe integer
42
+ interface SquareHeader {
43
+ readonly revision: number;
44
+ readonly nextActIndex: number; // assigned coordinate boundary, NOT max(index)+1
45
+ readonly lastActIndex: number; // greatest existing row, or -1
46
+ readonly hardCap: HardCap;
47
+ readonly throttlePerMinute?: number;
48
+ readonly preamble: readonly string[];
49
+ readonly warmup: readonly string[];
50
+ }
51
+ interface ParticipantFact {
52
+ readonly name: string; // first actor spelling encountered by replay
53
+ readonly firstIndex: number; // roster order
54
+ readonly joined: boolean;
55
+ readonly done: boolean;
56
+ readonly sayCount: number; // lifetime, including before any join
57
+ readonly lastActiveAt?: number; // max timestamp of actor's activities
58
+ readonly lastJoinIndex?: number;
59
+ }
60
+ interface AttentionFact {
61
+ readonly listener: string; // canonical actor spelling
62
+ readonly target: string; // spelling retained when relation was inserted
63
+ readonly kind: 'listen' | 'ignore';
64
+ readonly sinceIndex: number;
65
+ }
66
+ interface ActivityHead {
67
+ readonly index: ActivityIndex;
68
+ readonly at: number;
69
+ readonly kind: StoredAct['kind'];
70
+ readonly actor?: string; // actual activity spelling, not roster spelling
71
+ readonly reach?: 'bell';
72
+ readonly reply?: ActivityIndex;
73
+ }
74
+ interface ActivityFilter {
75
+ readonly afterIndex?: number; // exclusive; -1 permitted
76
+ readonly beforeIndex?: number; // exclusive
77
+ readonly throughIndex?: number; // inclusive scan fence
78
+ readonly indexes?: readonly ActivityIndex[]; // context union or exact batch
79
+ readonly kinds?: readonly StoredAct['kind'][];
80
+ readonly actors?: readonly string[];
81
+ readonly actorMatch?: 'identity' | 'exact'; // default identity
82
+ readonly actorRequired?: true;
83
+ readonly afterAt?: number; // exclusive; no implicit upper-time bound
84
+ }
85
+ interface IndexPage {
86
+ readonly edge: 'oldest' | 'newest';
87
+ readonly limit: number; // positive safe integer, internal storage batch only
88
+ }
89
+ interface FactPage<T> {
90
+ readonly items: readonly T[]; // ascending index, even when edge=newest
91
+ readonly hasMore: boolean; // more matching rows at the chosen edge
92
+ }
93
+ interface DirectedFilter {
94
+ readonly afterIndex?: number;
95
+ readonly beforeIndex?: number;
96
+ readonly throughIndex?: number;
97
+ readonly unseenOnly?: true;
98
+ readonly senders?: readonly string[];
99
+ readonly attentionOnly?: true; // route in mention/reply/bell; excludes listener-only
100
+ }
101
+ interface DirectedHead {
102
+ readonly index: ActivityIndex;
103
+ readonly at: number;
104
+ readonly actor: string;
105
+ readonly recipient: string;
106
+ readonly route: 'mention' | 'reply' | 'attention' | 'bell';
107
+ }
108
+ interface LandedRecipient {
109
+ readonly recipient: string;
110
+ readonly route: DirectedHead['route'];
111
+ }
112
+ interface ReceiptFacts {
113
+ readonly latestAt?: number; // ALL exact-owner receipts, including assigned gaps
114
+ readonly latestActorActivityIndex?: number;
115
+ // largest receipt coordinate > afterIndex which has an actor-bearing activity
116
+ }
117
+ interface ReceiptAssignment {
118
+ readonly owner: string; // original, EXACT outer key, not nameKey(owner)
119
+ readonly index: ActivityIndex;
120
+ }
121
+ type JsonValue = null | boolean | number | string
122
+ | readonly JsonValue[] | { readonly [key: string]: JsonValue };
123
+ interface RouteFact {
124
+ readonly location: string;
125
+ readonly participant: string;
126
+ readonly sessionId: string;
127
+ readonly channel: string;
128
+ readonly kind: WakeRouteKind;
129
+ readonly address: Readonly<Record<string, string>>;
130
+ readonly updatedAt: number;
131
+ readonly epoch?: JsonValue; // v1 validator did not constrain epoch
132
+ readonly extra: Readonly<Record<string, JsonValue>>; // other v1 route properties
133
+ }
134
+ // Normal publication produces numeric/absent epoch and extra={}.
135
+ // Eligibility narrows epoch to number/undefined before creating WakeRoute requests.
136
+
137
+ interface SquareRead {
138
+ header(): SquareHeader;
139
+ participant(requestedName: string): ParticipantFact | undefined;
140
+ roster(): readonly ParticipantFact[]; // firstIndex ascending
141
+ attention(listener: string): readonly AttentionFact[]; // sinceIndex ascending
142
+ incomingAttention(sender: string): readonly AttentionFact[];
143
+ // incoming matches nameKey(target); includes listen and ignore, not only joined actors
144
+ hold(): { readonly active: boolean; readonly at?: number;
145
+ readonly reason?: string; readonly actor?: string };
146
+
147
+ activity(index: ActivityIndex): StoredAct | undefined;
148
+ activityHeads(filter: ActivityFilter, page: IndexPage): FactPage<ActivityHead>;
149
+ activities(indexes: readonly ActivityIndex[]): readonly StoredAct[];
150
+ // existing requested rows only, ascending index; no implicit complete-history load
151
+ activityCount(filter: ActivityFilter): number;
152
+ contextIndexes(centers: readonly ActivityIndex[], before: number, after: number): readonly ActivityIndex[];
153
+ // union of neighboring EXISTING ACTIVITY POSITIONS, before other filters
154
+ sayOrdinals(indexes: readonly ActivityIndex[]): Readonly<Record<number, number>>;
155
+ // only existing says; count same-name says <= target index, over lifetime
156
+ recentSayTimes(afterExclusive: number, takeLatest: number,
157
+ actor?: string, bellOnly?: true): readonly number[];
158
+ // newest takeLatest timestamps, ascending by timestamp; future timestamps included
159
+
160
+ audience(index: ActivityIndex): readonly LandedRecipient[]; // landing recipient order
161
+ directed(recipient: string, filter: DirectedFilter, page: IndexPage): FactPage<DirectedHead>;
162
+ directedAt(recipient: string, index: ActivityIndex): DirectedHead | undefined;
163
+ directedCount(recipient: string, filter: DirectedFilter): number;
164
+ pendingAudience(afterExclusive: number, throughInclusive: number, activityLimit: number): {
165
+ readonly groups: readonly {
166
+ readonly index: ActivityIndex;
167
+ readonly at: number;
168
+ readonly actor: string;
169
+ readonly recipients: readonly LandedRecipient[];
170
+ }[];
171
+ readonly hasMore: boolean;
172
+ };
173
+ // oldest DISTINCT activity indices; each group contains ALL currently joined,
174
+ // post-latest-join, individually unseen recipients, in landing order. No body.
175
+
176
+ receipt(requestedName: string, index: ActivityIndex): { readonly at: number } | undefined;
177
+ receiptFacts(requestedName: string, afterIndex: number): ReceiptFacts;
178
+ receiptAssignments(): readonly ReceiptAssignment[]; // explicit SDK snapshot ONLY
179
+ lease(requestedName: string): WatchLease | undefined; // raw, freshness is domain policy
180
+ routes(): readonly RouteFact[]; // complete ordered route array, including duplicates
181
+ }
182
+ interface SquareWrite extends SquareRead {
183
+ land(activity: Act & { readonly at: number }): StoredAct;
184
+ markSeen(requestedName: string, indexes: readonly ActivityIndex[], at: number): number;
185
+ // count newly inserted receipts; first receipt timestamp is immutable
186
+ putLease(requestedName: string, lease: WatchLease): boolean;
187
+ removeLease(requestedName: string, expectedLeaseId: string): boolean;
188
+ replaceRoutes(routes: readonly RouteFact[]): boolean;
189
+ // structural equality including order/duplicates; no implicit route normalization
190
+ }
191
+ interface SquareReadResult<R> { readonly value: R; readonly version: number; }
192
+ interface SquareCommitResult<R> extends SquareReadResult<R> { readonly changed: boolean; }
193
+ interface SquareArtifactPort {
194
+ read<R>(select: (facts: SquareRead) => R, signal?: AbortSignal): Promise<SquareReadResult<R>>;
195
+ transact<R>(change: (facts: SquareWrite) => R, signal?: AbortSignal): Promise<SquareCommitResult<R>>;
196
+ revision(signal?: AbortSignal): Promise<number>; // header-only observation, no domain payload
197
+ changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
198
+ close(): Promise<void>;
199
+ }
200
+ ```
201
+
202
+ Selector semantics are fixed: fields AND; list elements OR; an absent list means no constraint; an explicitly empty list matches nothing. Name comparisons use JS domain `nameKey`, except explicitly exact actor matching. Unknown recipient produces no directed rows; resolution/validation and existing error wording belong to action code. No regex/SQL-expression/predicate injection interface. Pages are finite relational reads, not a new query language. `activities()` is the only multi-body hydration operation.
203
+
204
+ `pendingAudience` is a domain-defined relation: the domain owns its joined/post-join/exact-receipt predicate and memory equivalent; SQL mechanically implements it. Host eligibility, lease freshness, cancellation, ownership, grace periods, and route/effect authorization are not SQL policy. `activityLimit` bounds complete candidate groups before host eligibility, never the final sweep result. It is the only global pending traversal: required by sweep/dispatch/doctor. Receiver reads never use it. Its limit counts activity groups, not recipients, so a large bell cannot split or lose recipients. Audience hydration explicitly orders by recipient_order (mention order, then reply, then listeners in roster order), never by the recipient primary key. Existing deliverPending dispatch remains roster-major then activity-index-major: collect bodyless groups and iterate per recipient in captured roster order; sweep alone selects ascending distinct activity indices. `routes()` deliberately keeps one ordered value rather than introducing a route query framework or exposing surrogate row ids; route count is not activity-history size.
205
+
206
+ `participant()` normalizes identity but does not validate the requested string. Receipt and lease methods first resolve to `participant(name)?.name ?? name`, then use **exact owner spelling**. This is runtime.ts/perception-projection.ts behavior; it is NOT lowercase receipt lookup. `receiptAssignments()` returns raw owner/index values (also dormant keys and orphan coordinates). No timestamp map is needed for the SDK closure.
207
+
208
+ All returned values are detached immutable facts; no `SquareRead`, statement, lazy generator, or function closing over a transaction may survive its callback. Guard every transaction capability after callback completion. Callbacks are synchronous, cannot nest operations on the same artifact, and must not return a thenable. No automatic callback replay after it starts.
209
+
210
+ ### Landing effect and exclusive writers
211
+
212
+ Private driver write surface, inaccessible to production actions:
213
+
214
+ ```ts
215
+ interface LandingEffect {
216
+ readonly activity: StoredAct;
217
+ readonly participant?: ParticipantFact; // actor's new current fact
218
+ readonly removeListeningOf?: string; // done: listen only, ignore survives
219
+ readonly replaceAttention?: AttentionFact;
220
+ readonly audience: readonly LandedRecipient[]; // empty for non-say
221
+ }
222
+ interface LandingFacts {
223
+ readonly actor?: ParticipantFact;
224
+ readonly standing: readonly ParticipantFact[]; // roster order, only needed for say
225
+ readonly incoming: readonly AttentionFact[]; // only needed for say's sender
226
+ readonly outgoing: readonly AttentionFact[]; // only needed for listen/ignore
227
+ readonly replyAuthor?: string;
228
+ }
229
+ function deriveLanding(activity: StoredAct, facts: LandingFacts): LandingEffect;
230
+ ```
231
+
232
+ `deriveLanding` is pure and shared by runtime and rebuild. `land()` reads the necessary narrow facts, validates structural invariants (not action admission again), allocates **header.nextActIndex**, calls this transition, and applies the effect plus nextActIndex increment atomically. Migration supplies the preserved assigned indices; it never calls the allocator or admission validation. Earlier reply author lookup uses the existing activity row, including join/done/read/listen/actor-bearing hold; missing earlier coordinate means no author. Valid legacy replay never requires a future author because reply < own index.
233
+
234
+ `markSeen` accepts any nonnegative index below nextActIndex, even if no activity/participant exists. It refuses unassigned coordinates and non-finite timestamps; it inserts only missing exact-owner receipts. Actions—not a receipt FK—ensure catch/native writes concern the intended activities. Lease writers similarly retain unknown/raw owner fallback. Only receipt insertion marks seen; presentation outcome, cancellation, and wake evidence do not.
235
+
236
+ `putLease`/`replaceRoutes` compare detached values and return false for semantic no-op; changed revision is not driven by a whole-state comparison. Multiple writes have read-your-writes. Takeover's done then join therefore sees the new participant state in the same transaction.
237
+
238
+ ## 3. Actual v2 table/column/index shape
239
+
240
+ `application_id=0x53515245`, `user_version=2`, DELETE journal, synchronous FULL. The exact owned schema is these tables and indices; no triggers, views, FTS, runtime sidecar, or additional table. All key and string columns below use **JSON.stringify(string) with BINARY collation**, not raw JS strings. This preserves lone surrogates/NUL, including malformed legacy actor/target/owner/route strings and their derived keys. Equal JSON-string encodings mean exact JS-string equality. Normalized keys encode the result of JS `nameKey` (never SQLite LOWER). Decode at the artifact boundary.
241
+
242
+ Body/mentions/through/target and other non-indexed fields are stored once in typed payload JSON. Indexed structural fields are NOT also present in activity payload. Schema checks are storage framing; per-kind payload validation and cross-row assigned-coordinate checks are boundary validation.
243
+
244
+ ```sql
245
+ CREATE TABLE square_meta (
246
+ id INTEGER PRIMARY KEY CHECK (id = 1),
247
+ revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991),
248
+ next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),
249
+ hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),
250
+ throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),
251
+ preamble_json TEXT NOT NULL CHECK (json_valid(preamble_json)),
252
+ warmup_json TEXT NOT NULL CHECK (json_valid(warmup_json))
253
+ );
254
+ CREATE TABLE activities (
255
+ act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
256
+ at REAL NOT NULL,
257
+ kind TEXT NOT NULL CHECK (kind IN ('join','done','say','hold','resume','read','listen','ignore')),
258
+ actor_json TEXT COLLATE BINARY,
259
+ actor_key_json TEXT COLLATE BINARY,
260
+ reach TEXT CHECK (reach = 'bell'),
261
+ reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),
262
+ payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
263
+ CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),
264
+ CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),
265
+ CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))
266
+ );
267
+ CREATE INDEX activity_kind_index ON activities(kind, act_index);
268
+ CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index);
269
+ CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say';
270
+ CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say';
271
+ CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell';
272
+
273
+ CREATE TABLE receipts (
274
+ owner_json TEXT COLLATE BINARY NOT NULL,
275
+ act_index INTEGER NOT NULL CHECK (act_index BETWEEN 0 AND 9007199254740991),
276
+ at REAL NOT NULL,
277
+ PRIMARY KEY (owner_json, act_index)
278
+ ) WITHOUT ROWID;
279
+ CREATE INDEX receipt_owner_time ON receipts(owner_json, at);
280
+ CREATE TABLE catch_leases (
281
+ owner_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
282
+ lease_id_json TEXT NOT NULL,
283
+ heartbeat_at REAL NOT NULL,
284
+ expires_at REAL NOT NULL CHECK (expires_at >= heartbeat_at),
285
+ filter_json TEXT CHECK (filter_json IS NULL OR json_valid(filter_json))
286
+ ) WITHOUT ROWID;
287
+
288
+ CREATE TABLE routes (
289
+ position INTEGER PRIMARY KEY CHECK (position >= 0),
290
+ participant_key_json TEXT COLLATE BINARY NOT NULL,
291
+ session_json TEXT COLLATE BINARY NOT NULL,
292
+ kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','claude-native','paseo')),
293
+ updated_at REAL NOT NULL,
294
+ payload_json TEXT NOT NULL CHECK (json_valid(payload_json))
295
+ );
296
+ CREATE INDEX route_participant ON routes(participant_key_json, position);
297
+ CREATE INDEX route_session ON routes(session_json, position);
298
+
299
+ CREATE TABLE participants (
300
+ name_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
301
+ name_json TEXT NOT NULL,
302
+ first_index INTEGER NOT NULL UNIQUE,
303
+ joined INTEGER NOT NULL CHECK (joined IN (0,1)),
304
+ done INTEGER NOT NULL CHECK (done IN (0,1)),
305
+ say_count INTEGER NOT NULL CHECK (say_count >= 0),
306
+ last_active_at REAL,
307
+ last_join_index INTEGER,
308
+ CHECK (NOT (joined=1 AND done=1))
309
+ ) WITHOUT ROWID;
310
+ CREATE TABLE attention (
311
+ listener_key_json TEXT COLLATE BINARY NOT NULL,
312
+ target_key_json TEXT COLLATE BINARY NOT NULL,
313
+ target_json TEXT NOT NULL,
314
+ kind TEXT NOT NULL CHECK (kind IN ('listen','ignore')),
315
+ since_index INTEGER NOT NULL,
316
+ PRIMARY KEY (listener_key_json, target_key_json)
317
+ ) WITHOUT ROWID;
318
+ CREATE INDEX attention_target ON attention(target_key_json, kind, listener_key_json);
319
+ CREATE TABLE landed_audience (
320
+ act_index INTEGER NOT NULL,
321
+ recipient_key_json TEXT COLLATE BINARY NOT NULL,
322
+ recipient_order INTEGER NOT NULL CHECK (recipient_order >= 0),
323
+ route TEXT NOT NULL CHECK (route IN ('mention','reply','attention','bell')),
324
+ PRIMARY KEY (act_index, recipient_key_json),
325
+ UNIQUE (act_index, recipient_order)
326
+ ) WITHOUT ROWID;
327
+ CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);
328
+ ```
329
+
330
+ No foreign keys. In particular receipts must not reference activities or participants; replies/through must not require existing rows; leases/routes must not require known names. Derived tables are maintained and checked against authority, not used to reinterpret permissible v1 authority as invalid. `next_act_index > greatest existing activity index` and receipts < next_act_index are checked by the boundary (all receipts only on full integrity/import; inserted receipts on writes). No contiguous-index constraint. Finite fractional activity/receipt/lease times remain REAL, not INTEGER. Number-safe limits are enforced in the codec and allocator.
331
+
332
+ Activity payload shapes, with optional-property presence preserved:
333
+ - join/resume: `{}`.
334
+ - say: `{body: string, mentions?: string[]}`; reach/reply live only in their columns.
335
+ - done/hold: `{body?: string}`.
336
+ - read: `{through: number}`; no reference check on through.
337
+ - listen/ignore: `{target: string}`; no participant reference check on target.
338
+ Actor's original spelling lives in actor_json; actor_key_json is its indexed identity derivative. Neither is repeated in payload. Indices store coordinates/keys, never duplicate bodies.
339
+
340
+ Route payload contains location, original participant spelling, channel, address, optional epoch, and all extra properties accepted by the v1 validator; sessionId/kind/updatedAt are reconstructed from columns. `participant_key_json` is only a derivative, not a replacement for original participant spelling. Routes have **no unique identity constraint**. Import positions are array positions; `replaceRoutes` can rewrite this small array to positions 0..n-1. Holes in positions from internal deletion are harmless provided order is retained, but the chosen runtime replacement implementation rewrites contiguous positions. No canonical-location column: legacy routes retain raw locations; current route policy canonicalizes when it currently does, and wake/native equality does not silently gain canonicalization.
341
+
342
+ Preserve finite route epoch/extra JSON values and empty strings; do not otherwise tighten their legacy shapes. Use ordinary JSON encoding, not a bespoke numeric codec. Migration explicitly refuses non-finite numbers anywhere in a route (updatedAt, epoch, or nested extras) before DDL/inserts and leaves the artifact unchanged; never silently coerce them to null. These exponent-overflow values can pass the old permissive validator on manually authored JSON, but are not non-finite values normally persisted by the v1 JSON.stringify writer. Runtime codecs likewise require finite route numeric values. Raw schema equality is not receipt or participant equality.
343
+
344
+ No receipt `state` column: all receipts are seen. No persisted hold table: `hold()` selects last hold/resume using activity_kind_index; resume returns inactive with no stale reason/actor/time. No additional name registry: a participant is first touched by ANY actor-bearing activity, not necessarily join.
345
+
346
+ ## 4. Exact derived transitions and routes
347
+
348
+ Landing uses pre-activity participant/attention facts:
349
+ 1. Compute a say audience first. Resolve deduplicated mentions against standing roster (bell: all standing); then standing reply author; then active listeners in roster order. Skip sender and duplicate recipients. Ignore blocks all non-bell reach; bell bypasses ignore. Store ordered unique recipients and priority **bell > mention > reply > attention**. Explicit self mention remains in activity metadata but creates no self audience edge. Missing/departed reply author creates no edge; action result retains existing replyAuthorNotHere semantics.
350
+ 2. Touch actor if new: name is first actor spelling, joined=false, done=false, sayCount=0. Update lastActiveAt with max(existing, activity.at), not latest-by-index time.
351
+ 3. join sets joined=true/done=false and lastJoinIndex=index. done sets joined=false/done=true and clears OUTGOING listen only. Ignore survives departure/rejoin; incoming attention is not cleared. Say increments lifetime count, even for unusual imported prejoin/postdone sequences.
352
+ 4. listen inserts listen only when not already listening; removes matching ignore. ignore removes matching listen and inserts ignore only when absent. Preserve target's first retained spelling and relation order, including unknown/self targets. A no-op legacy activity still touches actor time; replay never applies the current ten-name admission cap. hold/resume remain events; read does not acknowledge receipts or advance a persisted cursor.
353
+
354
+ There is one production transition, no SQL triggers and no separate migration fold. Test-only frozen old fold/replay supplies differential evidence. Index integrity checking can rebuild through the production transition but cannot replace differential tests with self-comparison.
355
+
356
+ Existing route policy remains domain code over `routes()` plus `replaceRoutes()`:
357
+ - Identity is canonical location + nameKey(participant) + EXACT sessionId where routes.ts currently uses routeIdentityKey. Applying a route removes **every** matching identity and appends one new row at the tail. Nonmatching order is preserved.
358
+ - Idempotent publish is no-op only with exactly ONE matching row, matching kind/channel/address/epoch, and existing.updatedAt > at - ROUTE_FRESH_MS. Equality compares address key/value sets, not property enumeration order. Duplicate matching rows force removal-and-tail-append even if one matches. A fresh future timestamp can therefore suppress refresh as today.
359
+ - requireCurrentSession refuses publication when participant is not joined or a foreign-session route exists at that canonical location. It does not claim host ownership in the artifact.
360
+ - Session/participant/ended-session retirement retains the current expectedEpoch/force/liveParticipants predicates. Unknown ledger evidence keeps a joined route where current code keeps it. Takeover removes participant routes across all locations as current square-actions does; ordinary join removes only at its supplied location.
361
+ - Wake eligibility joins routes with host ownership, selects maximal updatedAt per recipient **including ties**, and preserves array order of tied routes. Native preview still selects the FIRST matching array entry under its existing kind/session/exact epoch/name/address predicates. Do not replace either with a uniqueness constraint or timestamp-only arbitrary tie break.
362
+ - `RouteFact.extra` is a lossless boundary envelope; policy does not consult it. Epoch narrows only after exact ownership matching; malformed epoch cannot acquire an owner by coercion. Typed transport requests remain WakeRoute.
363
+
364
+ ## 5. Cursor, receipts, presence, catch — fixed algorithms
365
+
366
+ Let P be resolved participant, K=P.name (or requested raw spelling if unknown), J=P.lastJoinIndex ?? -1. All receipt membership queries use encoded **K exactly**.
367
+
368
+ ### Continuous cursor
369
+
370
+ Find B = smallest landed-audience index > J without receipt (K,index). Then find C = greatest EXISTING activity index > J and < B (no upper bound if no B), with actor present and kind != read. Return max(J,C), with missing C treated as J. Implementation uses `directed(... unseenOnly, oldest limit=1)` and `activityHeads(... actorRequired, kinds excluding read, newest limit=1)` in the SAME transaction. It does not read every intervening activity body.
371
+
372
+ Why this is exact: audience edges only exist on peer says. Before the first unseen addressed say, own and nonaddressed actor-bearing non-read events all advance readCursor. Read and actorless events do not. No cursor at B-1 is invented: if J=4, actor-bearing act/7 exists, act/9 is actorless, and unseen addressed act/12 blocks, cursor is 7, not 11 or 9. Seen act/13 beyond that hole does not advance it.
373
+
374
+ ### Evidence and presence
375
+
376
+ `receiptFacts(K,J).latestAt` is MAX timestamp over ALL exact-owner receipts, including orphan coordinates and prejoin receipts. Joined participants' lastActiveAt for status is max(participant lastActiveAt, this timestamp); done/nonjoined participants ignore receipt timestamp for status. This matches current status, even though notification pending has a latest-join cutoff.
377
+
378
+ Presence anchor: if no join, return -1. E=max(J, largest own actor-bearing activity >J, largest receipt coordinate >J that actually has an actor-bearing activity). Read is allowed as evidence here. Anchor is greatest say/done index >J and <=E; if none, -1. Orphan receipts do not anchor presence. Nonaddressed activity advances cursor but is NOT evidence of presence. Get own head by actor identity/newest, evidenced receipt coordinate by receiptFacts, and final say/done head by range/newest. No full-history hydration.
379
+
380
+ ### Counts and admission
381
+
382
+ - Suffix/behind/unreadActivityCount use directed rows > continuous cursor, **including individually seen rows beyond a hole**. Notification pending instead requires joined, >latest join, individually unseen. Do not substitute one count for the other.
383
+ - Hard cap uses lifetime sayCount. Quorum uses all known peers' done booleans (there must be at least one peer), not only currently standing peers.
384
+ - Throttle: request newest `limit` global say times with at > now-THROTTLE_WINDOW_MS; if that many exist, releaseAt is the oldest returned + window, retry=max(1, releaseAt-now). Bell: newest 1 actor bell timestamp > now-1h; that timestamp +1h is nextAt. Neither query excludes future timestamps.
385
+ - Behind summaries retain current per-sender counts/latest timestamp, last THREE suffix previews and their **suffix-relative** numbering. Lifetime sayOrdinals are for ordinary history/feed rendering; they are not substituted for unread-summary numbers. Block only when !force, suffix nonempty, and freshest summary age > UNREAD_BLOCK_GRACE_MS; max(0,now-at) remains the age. force bypasses behind only.
386
+
387
+ ### Catch transaction
388
+
389
+ Resolve known viewer, validate options in the existing order, use one write transaction. Ordinary catch queries historical directed unseen rows (NO lastJoin cutoff), with senders and attentionOnly as applicable, oldest limit (default 10/max 100). `from:[]` matches nothing. Attention-only includes mention/reply/bell routes but not listener-only. Since directed membership is already true, these route categories exactly match current catch's viewer mention/reply/bell filter. Fetch bodies for selected indices only; insert seen receipts; compute resulting continuous cursor. **Ordinary remaining is filtered unseen count after insertion**. Exact-id catch verifies activity existence and historical directed membership, permits repeat of already-seen activity, and computes remaining over ALL historical directed unseen rows; repeat receipt has no write/revision change. Retain current conflict of id with own-property idle/from/mention/limit, error facts, and no standing requirement for catch.
390
+
391
+ Catch and concurrent ordinary catches serialize under BEGIN IMMEDIATE; filtered/exact later catch does not cover an earlier hole. Lease freshness stays expiresAt>now AND now-heartbeatAt<=WATCH_STALE_MS. Boundary lease ownership covers every matching notification, not only a catch page. Matching boundary lease uses current route-priority representation exactly as leaseOwnsNotification does, not a new raw mention interpretation. Generic wake eligibility does not acquire a new catch-lease gate; native preview retains its existing lease gate.
392
+
393
+ ## 6. Paging, immutable pages, and independent discovery
394
+
395
+ Internal page size is **100**. It is not a presentation limit. Complete snapshot/history/catch operations assemble their result in ONE synchronous read/write transaction; internal pages all share that transaction. `sessionInbox` likewise assembles the COMPLETE artifact projection for each bound recipient in one synchronous read callback, then observes host eligibility outside it. Multiple artifacts and host evidence are not a distributed snapshot.
396
+
397
+ Background traversal across page transactions instead captures immutable upper bound H=lastActIndex, original lower bound L, and each relevant recipient's canonical name + lastJoinIndex + joined state, captured together in the first artifact read transaction. Capture the detached host binding coordinate (session/channel/epoch, cancellation scope) separately under subscribe-before-read; heartbeat/updatedAt changes alone are not a new ownership coordinate. Its ephemeral position is the last examined activity index. **Do not restart on unrelated revision changes**: doing so starves a scan under continuous writers. Activities and landed edges <=H remain immutable; page values already returned are detached immutable values, not live views. Receipts/leases/routes may change during the pass, so this background collection is not advertised as a single artifact/ledger snapshot.
398
+
399
+ Advance past each scanned candidate even when host evidence/local exclusions/lease withhold it; global traversal advances past the entire activity group. Continue until bounded relation exhausted or caller's EXISTING post-eligibility selection limit is full. Sweep limit counts distinct eligible activity coordinates; early suppressed rows never consume it. New landing >H waits for the next pass. A relevant join/binding change invalidates only that recipient's pass; discard its accumulated candidates and schedule its new pass, rather than restarting other recipients. In global pages keep only the initially captured recipients whose join coordinate still matches; newly bound/rejoined recipients belong to their next pass. Final per-recipient join/binding checks also cover departure with no later returned candidate.
400
+
401
+ Track changes observed during a pass as a retained request for a subsequent pass FROM L, not as an acknowledgement through the last-examined index; don't interrupt finite progress for unrelated changes. Consume that request only when the follow-up pass begins, not when the old pass finishes; never acknowledge a newer observer revision as scanned. Retain the earliest relevant expiry of withheld candidates. Revisit withheld earlier candidates on that next pass or expiry. Before any transport/presentation/receipt effect revalidate the relevant join/binding, pending receipt, route/lease/cancellation and host claims/evidence with fresh reads and existing fences. A stale candidate can be skipped without claiming receipt. No DB transaction spans awaits to ledger/network. This policy makes finite traversal progress despite unrelated writers without pretending mutable eligibility is frozen.
402
+
403
+ `sessionInbox` remains COMPLETE per bound recipient: in its one read callback accumulate every post-latest-join unseen directed candidate and every required body, not the first storage page. Boundary rendering still owns its existing 1200-character limit/clipping/omission decisions. Pi's initial observer keys and its fresh complete lookup/observed keys retain their existing roles. After a successful Pi batch, **all initial batch keys**, including entries omitted from rendered context, enter handledPending exactly as today. Storage pages do not create separate batches or shrink that key set. Selected preview bodies can be separated from bodyless eligibility summaries only if this complete-result and exact batch-key behavior stays unchanged. The initial implementation keeps complete sessionInbox bodies; paged domain reads already eliminate unrelated-history hydration.
404
+
405
+ Observe/capture ledger changes BEFORE discovering bindings; subscribe/capture each artifact BEFORE its projection. Cache only the version actually projected, never the observer's later revision. Artifact/relevant ledger changes and catch-lease/host-claim expiry schedule reconsideration without restarting an active finite pass; relevant join/binding change invalidates only that recipient's pass. Failed injection evidence alone must not generate a retry spin. Unreadable artifact is retried, not cached as an empty current inbox; one failure does not disable other bindings.
406
+
407
+ Keep filesystem hints plus the existing 2-second authoritative revision reconciliation; producers may push or fail to push. Each receiver reads only its own bound participant relation. Pi/OpenCode autonomous loops stay; Claude stays its existing lifecycle/tool/turn-boundary discovery with bounded recovery. This storage contract does NOT add a Claude resident loop or claim every harness currently has idle discovery.
408
+
409
+ ## 7. Complete caller replacement map (including render dependencies)
410
+
411
+ No production structure below carries `.state`, all `.activities`, or a deferred DB query. Renderers receive settled perceptions and numbers, never recompute audience from a partial SquareState.
412
+
413
+ | Current callers | Exact facts/replacement |
414
+ |---|---|
415
+ | openSquare/openSquareArtifact, wait baselines, watch headers, cli/program | header/revision only; remove preliminary whole-state validation/load. Every normal selected-row decode remains validated. |
416
+ | square-actions join/implicitJoin/takeover/done/endOwnedSession, registry-associated route cleanup | participant/roster as needed, routes, land/replaceRoutes; same host claim lock scopes and cleanup sequences. Takeover done+join is one txn. No full SDK snapshot. |
417
+ | createSquareApplication.join's before/after snapshot and CLI hold/resume/done count calls | new internal `joinFacts(name)` projection = requested participant, known joined-or-done count, standing count. Before conflict pending uses known joined-or-done count as today; application's participantCount keeps its existing known joined-or-done semantics. CLI header after hold/resume/done uses standing count. Wire private helpers on Square/context rather than calling public snapshot. |
418
+ | express/decideAct and behind rendering (activity.ts) | header, actor fact, roster for longest standing-body mention resolution, hold, reply head/existence, recentSayTimes, continuous cursor, directed suffix. Admission normalization/error order unchanged. A valid reply coordinate is <nextActIndex, even if a gap: preserve current acceptance of assigned-but-missing reply. ActivityPresentation supplies own count/cap/hold/roster, suffix items with lifetime ordinals + settled full perception, and behind summaries. No whole history used just to number previews. |
419
+ | listen/ignore/listening, watch leases and terminal checks | actor fact, outgoing attention; roster only for quorum; land or lease methods. Unknown valid attention target still allowed. Pulse keeps hold-before-terminal priority. |
420
+ | catch / native seen | directed selectors/activity exact, markSeen, cursor/counts in txn; native `current` becomes synchronous `(facts: SquareRead)=>boolean`, evaluated ONLY within reads/receipt txn. It cannot escape or call host I/O. |
421
+ | entryPresentation / cli/square-commands join | bounded newest say/done heads then bodies; selected sayOrdinals; explicit per-index perceptions for the selected viewer; header context/scene; actor's join count from activityCount(kind join, actor identity). Supply `joinedBefore` rather than replaying `.state.acts`. `lastN=null` remains an explicit complete history request, not normal join's default. |
422
+ | statusPresentation / cli/observation-commands status | roster+attention, hold, receiptFacts, continuous cursors and directed counts, fresh lease status; latest say/done + ordinal. Supply last THREE join events after latest public index, total number of such joins, and each `rejoined` boolean from existence of earlier done with EXACT actor spelling (current CLI equality). Latest line carries settled perception for context.name (including '' ambient viewer), not a SquareState. No receipts/history map hydrated. |
423
+ | participants/participantsPresentation/listPresentation | roster-derived status sorted by existing compareParticipantActivity; receipts/leases aggregates for participant statuses; list requires only active names, header preamble and say count. Change probeSquare to create a validated file-backed port, not full read then memory clone. |
424
+ | 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. |
425
+ | 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. |
426
+ | 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. |
427
+ | 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`. |
428
+ | 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. |
429
+ | 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. |
430
+ | 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. |
431
+ | public Square.snapshot().delivered(name,id) | explicit receiptAssignments + detached roster resolution + small header/status in ONE read txn, as below. Other production callers must not use this for count/standing. |
432
+ | doctor / explicit import/export tests | full selected-row validation and replay comparison of indices; detached result includes standing count, not state for CLI header. Legacy validator lives only in explicit migration/test oracle. No automatic repair. |
433
+
434
+ ### Public SDK snapshot closure
435
+
436
+ Keep synchronous arbitrary `delivered(name,id)`. `snapshot()` intentionally copies ALL raw receipt assignments into a private detached Set keyed by JSON.stringify([rawOwner,index]), along with an immutable nameKey->first-roster-spelling map. Closure validates/parses id with current parseRequiredActivityId, resolves `map[nameKey(name)] ?? name`, and tests that exact owner/index set. This covers dormant raw owner keys, orphan assigned coordinates, unknown-name fallback, and arbitrary later calls after port close. It does not query a DB or retain SquareRead. Later writes do not alter this snapshot. No receipt times/activity bodies are copied. This O(receipts) exception is necessary for the existing synchronous API, and is limited to explicit SDK snapshot calls; lifecycle/status/poll/render use narrower helpers.
437
+
438
+ ### History algorithm
439
+
440
+ 1. In one read txn resolve known participant filters and optional known audience recipient; establish context union by existing activity positions, then AND index/time/actor/audience filters. Audience filter uses landed edges, including attention, not body tokens or current listening. API history includes non-read activities; CLI ordinary history hides read; CLI --at/context/--since retains its current raw candidates. Plain CLI rendering hides listen/ignore only at rendering, so those rows still occupy a page slot.
441
+ Execution is fixed: with an audience filter, enumerate historical `directed` pages without unseenOnly or a join cutoff; intersect each page's indices with the context union and other index/time/actor/kind filters through `activityHeads`. Without an audience filter, enumerate `activityHeads` directly. Continue the source traversal even when an intersection is empty. Public limiting follows the remaining filters/search; storage page exhaustion is distinct from a filtered empty page. This composition needs no new audience selector or query framework.
442
+ 2. Nonempty regex/fixed search additionally restricts to say/done. Compile existing JS case-insensitive search once and test id/actor/body, not SQL LIKE/SQLite regexp. Iterate all relationally matching candidate body pages in the txn to count matches; maintain only the requested edge's selected page plus the needed existence/count metadata. Regex remains potentially expensive exactly as current engine; no new safety claim. Empty supplied pattern keeps current no-search semantics for facade.
443
+ 3. Default/before selects LAST N filtered matches; after selects FIRST N. Select chronological edge first; desc reverses ONLY selected page. A complete request remains explicit and follows existing CLI maximum refusal. Continuation availability is computed over the current archive-visible kind set and current public page min/max, preserving current text/JSON differences; it is not simply a raw-head hasMore flag. Return total search matches, selected rows, public-before/public-after existence, selected ordinals and all anchor names landing on selected coordinates.
444
+ 4. Presence anchors are the §5 evidence algorithm for all roster names; compute without full hydration and attach only selected-coordinate annotations in roster order. Context positions include read/actorless activities; coordinate arithmetic must not replace position selection. History across separate calls promises stable activity-id coordinates, not frozen mutable receipt/presence annotations.
445
+
446
+ ## 8. Error/transaction lifecycle (fixed)
447
+
448
+ - Open existing artifact create-disabled; validate owned header/schema before persistent pragmas, and validate again in the transaction. New runtime refuses v1 with existing invalid_args plus migration_required recovery per lead terms; no dual read or automatic migration. Missing/unsupported/corrupt refusal semantics stay; selected row corruption is reported on access. Full doctor checks unrelated rows explicitly, rather than every hot read scanning authority.
449
+ - `read` starts one SQLite read transaction and pins revision before callback. Results are detached. A busy acquisition before callback may retry after the existing 25ms cancellable yield. No callback replay once invoked. `transact` starts BEGIN IMMEDIATE, invokes once, performs domain writes/read-your-writes, and increments revision ONCE if any authoritative value changed. No-op transaction returns unchanged version/changed=false; no whole-state clone/deep-equality cycle.
450
+ - BUSY COMMIT retries the SAME transaction/callback result. SQL/codec/callback error, thenable result, expired capability use, close, or abort before successful commit rolls back; capability is invalidated on every exit. Callback throw retains the original error. Exhausting safe nextActIndex/revision refuses before irreversible commit. If callback catches an internal driver failure, transaction is marked failed and cannot commit partial changes. A harmless domain validation exception can be caught before a write (needed by endOwnedSession's already_done/not_joined handling).
451
+ - Per-port queued writes and reads-after-prior-writes retain current ordering; memory port has equivalent serialization/no-op/revision behavior. Close aborts busy waits, waits for queued operations, releases observers/handles, and is idempotent; changed() returns false on close. Explicit caller abort propagates its reason. No returned callback/result exposes DB lifetime.
452
+ - External side effects never occur within an artifact txn. Preserve join claim->artifact->presence/route cleanup, lifecycle claim locks, wake claim->fresh checks->adapter->terminal evidence, and presentation read->claim->reread->sink->receipt txn->evidence. `presentPending` may use its current predicate only on SquareRead; native payload/epoch/lease fence is recomputed there. Clipped presentation returns success without seen; exact/native markSeen preserves first receipt time.
453
+ - Only admission failures get activityUnsent=true. Storage errors and failures after commit do NOT claim safe resend; successful commit cannot be retroactively rolled back by route/ledger/network failures. No distributed transaction/exactly-once remote presentation is asserted. Existing claim recovery/cancellation scopes remain in host ledger/adapters, not moved into artifact tables.
454
+
455
+ ## 9. Closed decisions / handoff
456
+
457
+ No unresolved product decision remains for this storage design. Chosen cost tradeoffs are intentional: O(recipients) audience rows at landing (bells included), indexed exact counts/ordinals may scan keys, regex history still examines matching bodies, explicit SDK snapshot copies receipts, and complete sessionInbox remains complete. These are not promises of measured capacity. Host-ledger format/global NDJSON parsing/GC and harness scheduling improvements are excluded.
458
+
459
+ Migration implements the schema here using the single explicit in-place transaction below; it does not invent the schema/keys/domain transition. Verification, two independently acceptable deliveries, and capacity evidence are fixed in the migration and delivery terms below. Receipt lookup uses exact canonical roster spelling; route ordering/duplicates are retained; no mandatory backup or --check/--backup/max-workers configuration is introduced.
460
+
461
+ # Migration and delivery terms
462
+
463
+ These terms apply together with the domain specification above.
464
+
465
+ ## Migration protocol
466
+
467
+ - Only `square --location <path.square> migrate`. No --check, --backup, --force, auto-migrate, bulk mode, separate lock database, sidecar, daemon, or runtime dual reader. An operator can make an offline backup with ordinary tooling; the command does not invent a backup lifecycle.
468
+ - application_id remains 0x53515245; user_version 2 identifies rows. Normal new-format reads/writes refuse version 1 with invalid_args / reason migration_required and a shell-quoted full migration command. build --force does not bypass it. Unknown/unrelated/invalid formats are refused, not repaired.
469
+ - `doctor` remains read-only: recognizes legacy v1 and prints the migration recovery command (exit 2); validates v2 fully, including a replay comparison of derived indices. It must not imply that legacy v1 is damaged. Directory/list probing retains omission semantics for unsupported files; no new implicit migration/discovery subsystem.
470
+ - Open existing canonical path create-disabled. A read-only preflight establishes owned known schema/version before any persistent pragma or DDL. Require existing DELETE journal mode, refuse another mode without changing it; the shipped v1 creator/writer already establishes DELETE. Set connection busy_timeout=0 and synchronous=FULL. Acquire BEGIN IMMEDIATE, re-check format/schema under the lock, and validate the full v1 snapshot inside it. If v2 is already present and has valid framing/header, return already-format-2 without changing revision. A second migrator follows this same in-lock check.
471
+ - Use v1 validation retained ONLY in migration (test oracle may also retain it), plus one explicit restriction: non-finite numbers anywhere in a route (including nested extras) are unsupported input, refused before DDL/inserts with the artifact untouched. They are not normally persisted as non-finite values by the v1 JSON.stringify writer; do not add a bespoke overflow-number codec or silently coerce to null. Copy normally persisted authoritative facts losslessly, preserving revision and nextActIndex, activity indices/gaps and optional field distinctions, raw receipt/lease owner keys, route order/duplicates, strings and finite fractional timestamps. Absent routes and [] both become zero rows; empty receipt-owner maps are behaviorless and may become zero receipt rows. Do not canonicalize receipts or add foreign keys that reject accepted v1 facts.
472
+ - Actual receipt lookup is exact canonical roster spelling, not lowercase nameKey: preserve the raw owner-key relation and resolve lookup via the domain. Free-form JS strings must survive SQLite binding; JSON string encoding (including for key strings where necessary) preserves lone surrogates. All normalized lookup keys come from domain nameKey, never SQLite LOWER.
473
+ - Create v2 tables and insert authority. Build derived participant/hold/attention/audience facts with the SAME single-step domain transition used by runtime landing (admission checks are NOT replayed). Check insert/constraint failures normally; do not add a second full replay or a second in-memory copy just to validate the function against itself. Lossless authority equality and old-replay equivalence are migration tests; doctor can explicitly recompute indices for integrity checking. Drop square_snapshot. Set user_version=2 inside the same transaction. Commit once; nothing is published piecemeal.
474
+ - Busy acquisition/read retries yield using existing 25ms retry and operation cancellation. Busy COMMIT retries the same transaction; never rerun copy or domain callback. Error/abort before successful COMMIT rolls back. Process death before commit recovers v1 through SQLite journal; after commit leaves complete v2. Preserve path/inode/registry identity. No host-ledger mutation and no synthetic activity.
475
+ - Storage exclusion is guaranteed for the shipped SQLite v1 runtime because it validates again after BEGIN IMMEDIATE: its write commits before migration, or it sees v2 and refuses afterward. A pre-migration read can finish with its old snapshot. Migration does not revoke in-flight remote delivery or stop processes; recommend stopping/upgrading attached runtimes for an operationally quiet migration. Arbitrary external path replacement and pre-SQLite binaries are outside the artifact update protocol. Do not claim a distributed transaction or exactly-once remote delivery.
476
+
477
+ ## Independent discovery and concurrency
478
+
479
+ - Keep filesystem hints plus 2-second autonomous authoritative revision reconciliation. Producer push remains optional; each receiver asks for its own bound recipient facts, not everybody's inbox. Subscribe/capture before inspecting bindings/artifact; don't acknowledge a revision newer than the data actually read.
480
+ - Page scans use an immutable upper activity bound and ephemeral last-examined index. Apply eligibility after scanning, continuing beyond withheld rows. A page limit is NOT a presentation/catch limit. An exhausted, suppressed scan cannot permanently forget candidates: artifact/ledger changes schedule reconsideration without restarting the active pass; relevant join/binding changes invalidate that recipient's pass; relevant expiry schedules reconsideration even without a write. Existing local cancellation and Pi omitted-batch suppression are not receipts and keep their scope.
481
+ - Complete user-facing snapshot/history/catch operations assemble their result in one synchronous read/write transaction, paging internally if useful. Background discovery across page transactions instead captures immutable upper activity bound plus relevant join/binding coordinate. Unrelated revisions must NOT restart its progress (that would starve under continuous writes). Receipts/leases/routes can change during scanning; recheck relevant join/binding and freshness before effects. Capture upper bound and join coordinate together in the initial artifact transaction; capture host binding separately. Compare semantic coordinates, not heartbeat timestamps. A change during a pass remains pending until the follow-up pass begins from the original lower bound, not from last-examined as an acknowledgement; never mark a newer observer revision consumed at completion. Retain earliest relevant withheld-candidate expiry. Relevant join/binding changes invalidate that recipient's pass. Background artifact+ledger eligibility is not advertised as a single snapshot. No DB transaction spans awaits to ledger/network.
482
+ - Preserve existing complete-result APIs. The explicit full SDK snapshot may copy receipts needed by its synchronous delivered() closure, but join/status/poll/render hot paths must not call that full snapshot just for counts. No DB callback/query capability survives transaction completion.
483
+ - This storage delivery preserves Pi/OpenCode idle discovery and existing Claude lifecycle/boundary behavior. It does not quietly add a new Claude resident poller. Any stronger harness scheduling change is separately scoped.
484
+
485
+ ## Acceptance boundary and verification
486
+
487
+ Two independently acceptable implementations, not one aggregate:
488
+ 1. Row-backed runtime: new format creation, domain port and single transition, all production callers moved, old whole-state runtime path removed, v1 refusal/doctor recovery, public behavior preserved and cost evidence.
489
+ 2. Explicit v1 migration: depends on the settled v2 schema/transition; migration CLI and transactional preservation/recovery evidence. Core v2 behavior can be accepted on newly created artifacts without migration; migration is independently accepted on v1 fixtures. Release should ship both together so existing users have the recovery command.
490
+ Host-ledger format/GC, daemon/socket service, new notification delivery semantics, version pinning, and publication are excluded.
491
+
492
+ ### Mandatory deletion and simplification acceptance
493
+
494
+ This is a replacement, not an additive adapter layer. Acceptance MUST reject an implementation that leaves the unnecessary old machinery in place even if behavior tests pass.
495
+
496
+ - Remove the production whole-state read/transact/snapshot-replacement path, full-state clones/deep-equality write detection, replay-per-operation delivery/perception closures, obsolete `FromState`/delta bridges, and `.state`/whole-history renderer inputs replaced by the caller map. Delete dead callers, exports and tests of removed implementation details; retain behavioral coverage.
497
+ - One production landing transition and one definition of each domain predicate. No parallel old/new runtime paths, compatibility wrappers, generic query framework, duplicate conversion layers, or unsupported defensive branches. The v1 decoder exists ONLY for explicit migration; frozen legacy semantic oracles are test-only. Full doctor/export and explicit SDK snapshot exceptions are narrow capabilities, not runtime fallbacks.
498
+ - Hand-written production source must be net smaller than pre-change baseline `99679eb2905dde5f282025bc7f5494f7da065e54`, both for the runtime candidate and the final combined runtime+migration candidate. Migration adds only its necessary one-time conversion code and must fit that combined reduction; it need not be net-negative relative to the already reduced runtime candidate. Count tracked production `.ts`, `.js`, `.mjs` files; exclude tests/fixtures, scripts, documentation, generated output, vendored references and dependencies. No unrelated deletions, moving production code into excluded paths, code minification, formatting-only reductions or removing behavioral tests to game the result.
499
+ - Delivery evidence includes the deletion/replacement map (old mechanism → replacement or removal), remaining legacy-reference search with justified migration/test-only exceptions, and production additions/deletions/net delta against the fixed baseline. Independent review judges unnecessary abstraction and duplication in addition to correctness. A passing test suite or negative line count alone cannot waive this criterion; unexplained retained obsolete code or net growth is a rejection, not deferred cleanup.
500
+
501
+ Minimum meaningful evidence (not a Cartesian test matrix):
502
+ - Differential trace covering mention/reply/listen/ignore/bell/done/rejoin: maintained indices equal the frozen v1 replay oracle (test-only), including route precedence and roster order. Comparing the new transition to itself does not count as semantic evidence. One shared production transition, no SQL trigger policy.
503
+ - Sparse receipt scenario: earlier unseen hole, later filtered/exact catch, repeat exact catch no write, pre-rejoin catch versus post-rejoin notification, truthful remaining and cursor/behind/presence distinction. Filter before limit; history context uses existing activity positions.
504
+ - Two-process transaction race and short recovery tests: concurrent catches do not double-consume ordinary catch; atomic activity+audience; abort/rollback and commit-busy callback runs once. Existing native epoch/cancel/claim-before-send/after-send tests stay green; no stronger distributed guarantee claimed.
505
+ - Discovery scenario with hints disabled and push absent, including an early withheld candidate and a later eligible one; expiry alone re-enables the earlier candidate. Verify failed evidence does not busy-loop. Preserve Pi omitted notification behavior.
506
+ - Migration: one representative lossless legacy fixture including gaps/orphan receipts/case keys and optional fields; second run no-op; invalid input no writes; concurrent v1 writer/version gate; process death before commit rolls back. Use actual old artifact implementation or pinned installed binary for old-writer evidence, not v2 code pretending to be old.
507
+ - Capacity evidence: adapt existing scripts/benchmark-idle-wait.mjs rather than add a daemon or general benchmark framework. Permit 300 workers directly (no max-workers config). Same artifact, 300 independent processes and shared synthetic host ledger. Run idle, then active bell discovery with push absent/hints disabled, then simultaneous catches. Report actual readiness count, completions, revision/selected-row instrumentation with positive controls, hydrated rows/bytes, CPU, busy/commit duration, discovery latency and ledger work separately. Bound run and fail loudly on incomplete workers; do not silently lower count. Existing 20s discovery window is a probe deadline, not an SLA. Compare small/larger unrelated histories to establish no full-history hydration on recipient hot reads; SQL exact counts may still scan index entries. Update instrumentation: old square_snapshot regex otherwise falsely counts zero after v2. No capacity success claim before running on implemented code.
508
+ - Commands after implementation: npm test (already sandboxed); npm run test:cli-process; explicit benchmark via node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs. No live registry/session identities or native remote delivery during probes.
509
+
510
+ ## Authority amendment accompanying runtime implementation
511
+
512
+ Replace AGENTS.md's whole-state requirement with: “Behavior operates on typed Square domain facts obtained through transaction-scoped reads and domain transitions. SQLite schema, SQL, and storage framing remain private to the artifact boundary. Partial fact reads must not masquerade as a complete SquareState.”
513
+
514
+ This is an explicit proposed amendment authorized by the requested boundary redesign, not an assertion that today's text already says it. The design document records it now; runtime implementation changes AGENTS.md with the boundary, not before.
515
+
516
+ ## Binding plan
517
+
518
+ The current lead agent remains holder of both deliveries. Assign implementation hands independently from acceptance boundaries; the reviewer of a candidate must not be its deliverer. Use both `reviewed` and `verified` gates. Do not bind an aggregate storage-plus-migration contract.
519
+
520
+ **Runtime delivery Region:**
521
+ ```text
522
+ square-*.ts
523
+ artifact.ts
524
+ ports.ts
525
+ model.ts
526
+ runtime.ts
527
+ decisions.ts
528
+ catch-decisions.ts
529
+ activity.ts
530
+ activity-feed.ts
531
+ perception-projection.ts
532
+ presentation.ts
533
+ presentation-operations.ts
534
+ boundary-presentation.ts
535
+ views.ts
536
+ delivery.ts
537
+ delivery-operations.ts
538
+ delivery-health.ts
539
+ notifications.ts
540
+ inbox.ts
541
+ routes.ts
542
+ wake-eligibility.ts
543
+ wakes.ts
544
+ watch.ts
545
+ stream.ts
546
+ claude-delivery.ts
547
+ claude-mod.ts
548
+ cli/**
549
+ plugins/opencode/**
550
+ test/**
551
+ scripts/benchmark-idle-wait.mjs
552
+ AGENTS.md
553
+ guides/row-storage-design.md
554
+ guides/change-waits.md
555
+ ```
556
+ These are planning regions, not a requirement to edit every listed file. Host-ledger persistence and packages/agent-delivery transport internals are excluded. New private domain/artifact helper modules use the existing `square-` family; no generic query framework is introduced.
557
+
558
+ **Migration delivery Region:** `artifact.ts`, `square-storage.ts`, the new private migration module `square-migration.ts`, `square-state.ts` for isolated v1 validation, `model.ts`, `cli/maintenance-commands.ts`, `cli/registry.ts`, `cli/program.ts`, `help.ts`, `test/migration.test.js`, its `test/fixtures/**`, and migration documentation. Depends on the runtime delivery's v2 schema/codec/shared transition. Sharing files is coordination, not an extra dependency.
559
+
560
+ Verification declarations for both delivery snapshots include `npm test` and `npm run test:cli-process` (sandboxed by their scripts), with timeouts chosen from actual baseline duration rather than arbitrarily cutting off tests. Runtime evidence additionally runs the adapted benchmark:
561
+ ```sh
562
+ node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 300 --mode session
563
+ node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 300 --mode session --active --no-hints
564
+ ```
565
+ These commands are implemented by the runtime candidate; `--no-hints` and direct 300-worker support are benchmark capabilities. `--no-hints` disables fs.watch only inside benchmark workers, never through production configuration. Active session mode includes the simultaneous catch phase and comparison at 1,800 versus 18,000 unrelated history rows; preserve result/counter positive controls and report incomplete phases as failure. There is no new production performance knob. The baseline 20-second discovery deadline is retained as a finite probe bound, not a user-facing latency guarantee.
566
+
567
+ Independent design review found the architecture sound. Its concrete closure amendments are incorporated: no revision-driven pagination restart, retained follow-up/expiry obligations, explicit history audience-selector composition, domain ownership of pendingAudience, and refusal instead of a bespoke non-finite JSON codec. Design review is not implementation review, a test result, or a capacity result.