@astrosheep/square 0.3.85 → 0.3.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) 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 +15 -12
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +2 -1
  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 +12 -14
  36. package/dist/hints.js +2 -3
  37. package/dist/inbox.d.ts +35 -9
  38. package/dist/inbox.js +228 -144
  39. package/dist/index.d.ts +4 -4
  40. package/dist/index.js +1 -3
  41. package/dist/list.js +9 -4
  42. package/dist/mcp.js +12 -1
  43. package/dist/model.d.ts +39 -5
  44. package/dist/model.js +84 -0
  45. package/dist/notifications.d.ts +8 -6
  46. package/dist/notifications.js +56 -143
  47. package/dist/operation-presentation.js +12 -14
  48. package/dist/participant-identity.d.ts +0 -3
  49. package/dist/participant-identity.js +0 -31
  50. package/dist/paseo-delivery.d.ts +5 -6
  51. package/dist/paseo-delivery.js +13 -8
  52. package/dist/paseo.d.ts +1 -1
  53. package/dist/paseo.js +1 -1
  54. package/dist/plugins/opencode/delivery.d.ts +8 -3
  55. package/dist/plugins/opencode/delivery.js +187 -132
  56. package/dist/plugins/opencode/index.js +12 -38
  57. package/dist/ports.d.ts +112 -40
  58. package/dist/presentation-operations.d.ts +1 -1
  59. package/dist/presentation-operations.js +51 -35
  60. package/dist/presentation.d.ts +0 -1
  61. package/dist/presentation.js +18 -13
  62. package/dist/seats.d.ts +7 -0
  63. package/dist/seats.js +27 -0
  64. package/dist/session-lifecycle.d.ts +2 -2
  65. package/dist/session-lifecycle.js +10 -12
  66. package/dist/sqlite-square.d.ts +53 -0
  67. package/dist/sqlite-square.js +602 -0
  68. package/dist/square-api.d.ts +12 -41
  69. package/dist/square-api.js +26 -474
  70. package/dist/square-backend.d.ts +13 -0
  71. package/dist/square-core.d.ts +3 -2
  72. package/dist/square-domain.d.ts +12 -5
  73. package/dist/square-domain.js +163 -7
  74. package/dist/square-facade.d.ts +16 -10
  75. package/dist/square-index.d.ts +24 -0
  76. package/dist/square-index.js +131 -0
  77. package/dist/square-legacy-framing.d.ts +9 -0
  78. package/dist/square-legacy-framing.js +56 -0
  79. package/dist/square-location.d.ts +15 -0
  80. package/dist/square-location.js +76 -0
  81. package/dist/square-migration-host-ledger.d.ts +26 -0
  82. package/dist/square-migration-host-ledger.js +168 -0
  83. package/dist/square-migration.d.ts +5 -5
  84. package/dist/square-migration.js +373 -71
  85. package/dist/square-sqlite.d.ts +9 -9
  86. package/dist/square-sqlite.js +329 -79
  87. package/dist/square-stdio.d.ts +22 -0
  88. package/dist/square-stdio.js +222 -0
  89. package/dist/square-tools.js +30 -3
  90. package/dist/stdio-server.d.ts +3 -0
  91. package/dist/stdio-server.js +192 -0
  92. package/dist/stream.d.ts +3 -2
  93. package/dist/stream.js +9 -10
  94. package/dist/views.d.ts +9 -14
  95. package/dist/views.js +50 -70
  96. package/dist/wakes.d.ts +6 -5
  97. package/dist/wakes.js +20 -17
  98. package/guides/application-facade-delivery-evidence.md +1 -1
  99. package/guides/backends.md +180 -0
  100. package/guides/claude-native.md +9 -9
  101. package/guides/mcp.md +1 -1
  102. package/guides/migration.md +107 -67
  103. package/guides/row-storage-design.md +1 -1
  104. package/guides/square-api.md +69 -30
  105. package/package.json +1 -1
  106. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  107. package/plugins/claude/hooks/register.js +1 -1
  108. package/plugins/claude/skills/square/SKILL.md +7 -7
  109. package/plugins/claude/types.d.ts +1 -1
  110. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  111. package/plugins/opencode/delivery.ts +170 -118
  112. package/plugins/opencode/index.ts +12 -30
  113. package/plugins/pi/square-pi.js +58 -20
  114. package/skills/square/SKILL.md +7 -7
  115. package/dist/cli/ledger-command.d.ts +0 -12
  116. package/dist/cli/ledger-command.js +0 -72
  117. package/dist/host-ledger-file-adapter.d.ts +0 -47
  118. package/dist/host-ledger-file-adapter.js +0 -384
  119. package/dist/host-ledger-migration.d.ts +0 -27
  120. package/dist/host-ledger-migration.js +0 -264
  121. package/dist/host-ledger-root.d.ts +0 -2
  122. package/dist/host-ledger-root.js +0 -9
  123. package/dist/host-ledger-sqlite.d.ts +0 -65
  124. package/dist/host-ledger-sqlite.js +0 -368
  125. package/dist/host-ledger.d.ts +0 -195
  126. package/dist/ledger-maintenance.d.ts +0 -30
  127. package/dist/ledger-maintenance.js +0 -110
  128. package/dist/open-square.d.ts +0 -36
  129. package/dist/open-square.js +0 -81
  130. package/dist/participant-host.d.ts +0 -22
  131. package/dist/participant-host.js +0 -92
  132. package/dist/registry.d.ts +0 -55
  133. package/dist/registry.js +0 -123
  134. package/dist/routes.d.ts +0 -39
  135. package/dist/routes.js +0 -99
  136. package/dist/square-actions.d.ts +0 -56
  137. package/dist/square-actions.js +0 -353
  138. package/dist/square-projections.d.ts +0 -31
  139. package/dist/square-projections.js +0 -64
  140. package/dist/wake-attempts.d.ts +0 -10
  141. package/dist/wake-attempts.js +0 -39
  142. package/dist/wake-eligibility.d.ts +0 -53
  143. package/dist/wake-eligibility.js +0 -123
  144. package/dist/wake-evidence.d.ts +0 -33
  145. package/dist/wake-evidence.js +0 -32
  146. package/guides/host-ledger.md +0 -58
  147. package/guides/indexed-host-ledger-performance.md +0 -39
  148. /package/dist/{host-ledger.js → square-backend.js} +0 -0
@@ -1,9 +1,12 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { openExistingSquareDatabase } from './artifact.js';
2
3
  import { inSqliteTransaction } from './sqlite-transaction.js';
3
- import { canonicalPath } from './canonical-path.js';
4
- import { isWakeRouteKind } from './model.js';
4
+ import { canonicalPath, canonicalPathSync } from './canonical-path.js';
5
+ import { formatActivityId, isWakeRouteKind, nameKey, sameBody, validateBody } from './model.js';
5
6
  import { parseActivityId } from './square-core.js';
6
- import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V1_SNAPSHOT_TABLE, createSqliteDriver, initializeSqlite, invalidArtifact, validateLegacyFraming, validateRowFraming, validateSqliteHeader, validateStoredActivity, validateWatchLease, } from './square-sqlite.js';
7
+ import { readLegacyHostData } from './square-migration-host-ledger.js';
8
+ import { legacyRowFramingIssue, FORMAT4_ADDED_OBJECTS, FORMAT4_REPLACED_OBJECTS } from './square-legacy-framing.js';
9
+ import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V3_USER_VERSION, SCHEMA_SQL, createSqliteDriver, invalidArtifact, validateStoredActivity } from './square-sqlite.js';
7
10
  function isObject(value) {
8
11
  return typeof value === 'object' && value !== null && !Array.isArray(value);
9
12
  }
@@ -30,7 +33,7 @@ function validateRuntime(value) {
30
33
  && Object.entries(candidate).every(([id, observation]) => parseActivityId(id) !== undefined && isObject(observation) && hasExactKeys(observation, ['state', 'at']) && observation.state === 'seen' && isFiniteNumber(observation.at)))
31
34
  && validateRecord(value.leases, (candidate) => {
32
35
  try {
33
- validateWatchLease(candidate);
36
+ validateLegacyLease(candidate);
34
37
  return true;
35
38
  }
36
39
  catch {
@@ -126,37 +129,6 @@ function identity(database) {
126
129
  function standing(database) {
127
130
  return createSqliteDriver(database).roster().filter((participant) => participant.joined).length;
128
131
  }
129
- /** The snapshot row, decoded only by the legacy validator. */
130
- function decodeLegacySnapshot(database) {
131
- const rows = database.prepare(`SELECT id, revision, state FROM ${V1_SNAPSHOT_TABLE}`).all();
132
- if (rows.length !== 1 || rows[0].id !== 1 || !Number.isSafeInteger(rows[0].revision) || rows[0].revision < 0)
133
- throw invalidArtifact('snapshot row is malformed.');
134
- if (typeof rows[0].state !== 'string')
135
- throw invalidArtifact('snapshot state is malformed.');
136
- let parsed;
137
- try {
138
- parsed = JSON.parse(rows[0].state);
139
- }
140
- catch {
141
- throw invalidArtifact('snapshot state is not valid JSON.');
142
- }
143
- return { revision: rows[0].revision, state: validateSquareState(parsed) };
144
- }
145
- function counts(state) {
146
- let receipts = 0;
147
- for (const observations of Object.values(state.runtime.observations))
148
- receipts += Object.keys(observations).length;
149
- return {
150
- activities: state.acts.length,
151
- receipts,
152
- leases: Object.keys(state.runtime.leases).length,
153
- routes: state.routes?.length ?? 0,
154
- };
155
- }
156
- function rowCounts(database) {
157
- const count = (table) => Number(database.prepare(`SELECT COUNT(*) AS n FROM ${table}`).get().n);
158
- return { activities: count('activities'), receipts: count('receipts'), leases: count('catch_leases'), routes: count('routes') };
159
- }
160
132
  const ACTIVITY_INDEX_NAMES = ['activity_kind_index', 'activity_actor_index', 'activity_say_actor_index', 'activity_say_time', 'activity_bell_actor_time'];
161
133
  function rebuildV2Activities(database) {
162
134
  database.exec([
@@ -168,63 +140,393 @@ function rebuildV2Activities(database) {
168
140
  ...ACTIVITY_INDEXES_SQL.map((sql) => `${sql};`),
169
141
  ].join('\n'));
170
142
  }
143
+ function normalizedSql(sql) {
144
+ return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1');
145
+ }
146
+ function currentFormat4Schema() {
147
+ return SCHEMA_SQL.split(';').map((statement) => statement.trim()).filter(Boolean)
148
+ .filter((statement) => {
149
+ const match = /^(?:CREATE (?:UNIQUE )?TABLE|CREATE (?:UNIQUE )?INDEX)\s+([a-z_]+)/i.exec(statement);
150
+ return match === null || !FORMAT4_REPLACED_OBJECTS.has(match[1]);
151
+ }).map(normalizedSql).sort();
152
+ }
153
+ function validateFormat4Framing(database, location) {
154
+ const before = identity(database);
155
+ assertSource(before.applicationId === APPLICATION_ID, 'unrelated SQLite database.');
156
+ assertSource(before.userVersion === CURRENT_USER_VERSION, 'unsupported SQLite format version.');
157
+ const actual = database.prepare("SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' AND sql IS NOT NULL").all()
158
+ .map((row) => typeof row.sql === 'string' ? normalizedSql(row.sql) : '').sort();
159
+ const expected = currentFormat4Schema();
160
+ assertSource(actual.length === expected.length && actual.every((sql, index) => sql === expected[index]), 'unsupported row schema.');
161
+ const journalMode = database.prepare('PRAGMA journal_mode').get().journal_mode;
162
+ assertSource(journalMode === (location === ':memory:' ? 'memory' : 'delete'), 'unsupported journal mode.');
163
+ createSqliteDriver(database).header();
164
+ }
165
+ function assertSource(condition, detail = 'row is malformed.') {
166
+ if (!condition)
167
+ throw invalidArtifact(detail);
168
+ }
169
+ function parseJson(value, detail) {
170
+ assertSource(typeof value === 'string', detail);
171
+ try {
172
+ return JSON.parse(value);
173
+ }
174
+ catch {
175
+ throw invalidArtifact(detail);
176
+ }
177
+ }
178
+ function decodeJsonString(value, nonempty = false) {
179
+ const parsed = parseJson(value, 'row contains invalid JSON.');
180
+ assertSource(typeof parsed === 'string' && (!nonempty || parsed.length > 0) && JSON.stringify(parsed) === value, 'string encoding is malformed.');
181
+ return parsed;
182
+ }
183
+ function sourceInteger(value, minimum = 0) {
184
+ assertSource(typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum);
185
+ return value;
186
+ }
187
+ function sourceFinite(value) {
188
+ assertSource(typeof value === 'number' && Number.isFinite(value));
189
+ return value;
190
+ }
191
+ function sourceStringArray(value) {
192
+ const parsed = parseJson(value, 'header contains invalid JSON.');
193
+ assertSource(Array.isArray(parsed) && parsed.every((item) => typeof item === 'string'));
194
+ assertSource(JSON.stringify(parsed) === value, 'header string encoding is malformed.');
195
+ return parsed;
196
+ }
197
+ function decodeLegacyRoute(row) {
198
+ assertSource(sourceInteger(row.position) >= 0);
199
+ const parsed = parseJson(row.payload_json, 'route payload is malformed.');
200
+ assertSource(isObject(parsed)
201
+ && hasExactKeys(parsed, ['location', 'participant', 'channel', 'address', 'extra'], ['epoch']), 'route payload is malformed.');
202
+ assertSource(typeof parsed.location === 'string' && typeof parsed.participant === 'string'
203
+ && typeof parsed.channel === 'string' && isObject(parsed.address)
204
+ && Object.values(parsed.address).every((value) => typeof value === 'string')
205
+ && isObject(parsed.extra), 'route payload is malformed.');
206
+ assertSource(isWakeRouteKind(row.kind), 'route kind is malformed.');
207
+ const participant = parsed.participant;
208
+ assertSource(row.participant_key_json === JSON.stringify(nameKey(participant)), 'route participant key is malformed.');
209
+ const sessionId = decodeJsonString(row.session_json);
210
+ sourceFinite(row.updated_at);
211
+ const body = { channel: parsed.channel, session: sessionId, ...(parsed.extra.agent === undefined ? {} : { agent: parsed.extra.agent }) };
212
+ validateBody(body);
213
+ const address = parsed.address;
214
+ if (row.kind === 'claude-native')
215
+ assertSource(address.sessionId === undefined || address.sessionId === sessionId, 'native route session conflicts with its body.');
216
+ return { location: parsed.location, participant, body,
217
+ wakeAddress: { kind: row.kind, address: { ...address, ...(row.kind === 'claude-native' ? { sessionId } : {}) } } };
218
+ }
219
+ function validateLegacyLease(value) {
220
+ assertSource(isObject(value) && hasExactKeys(value, ['leaseId', 'heartbeatAt', 'expiresAt'], ['filter']), 'legacy lease is malformed.');
221
+ assertSource(typeof value.leaseId === 'string' && value.leaseId.length > 0
222
+ && typeof value.heartbeatAt === 'number' && Number.isFinite(value.heartbeatAt)
223
+ && typeof value.expiresAt === 'number' && Number.isFinite(value.expiresAt)
224
+ && value.expiresAt >= value.heartbeatAt, 'legacy lease is malformed.');
225
+ if (!Object.hasOwn(value, 'filter'))
226
+ return;
227
+ const filter = value.filter;
228
+ assertSource(isObject(filter) && hasExactKeys(filter, [], ['participants', 'mention']), 'legacy lease filter is malformed.');
229
+ assertSource((!Object.hasOwn(filter, 'participants') || Array.isArray(filter.participants) && filter.participants.every((item) => typeof item === 'string'))
230
+ && (!Object.hasOwn(filter, 'mention') || typeof filter.mention === 'string'), 'legacy lease filter is malformed.');
231
+ }
232
+ function readLegacyRoutes(database) {
233
+ return database.prepare('SELECT position,participant_key_json,session_json,kind,updated_at,payload_json FROM routes ORDER BY position').all()
234
+ .map(decodeLegacyRoute);
235
+ }
236
+ function uniqueSeatAddress(routes) {
237
+ const addresses = new Map();
238
+ for (const route of routes) {
239
+ const entries = Object.entries(route.wakeAddress.address).sort(([left], [right]) => left.localeCompare(right));
240
+ addresses.set(JSON.stringify([route.wakeAddress.kind, entries]), route.wakeAddress);
241
+ }
242
+ return addresses.size === 1 ? [...addresses.values()][0] : undefined;
243
+ }
244
+ function establishingJoin(driver, participant) {
245
+ const join = driver.activityHeads({ actors: [participant.name], kinds: ['join'] }, { edge: 'newest', limit: 1 }).items[0];
246
+ assertSource(join?.kind === 'join' && join.index === participant.lastJoinIndex, `participant ${participant.name} has no verifiable establishing join.`);
247
+ return join.index;
248
+ }
249
+ function planSeats(location, roster, routes, owners, driver) {
250
+ const standing = roster.filter((participant) => participant.joined);
251
+ return standing.map((participant) => {
252
+ const joined = establishingJoin(driver, participant);
253
+ const candidates = routes.filter((route) => canonicalPathSync(route.location) === location && nameKey(route.participant) === nameKey(participant.name));
254
+ const owner = owners.find(row => nameKey(row.participant) === nameKey(participant.name));
255
+ assertSource(owner !== undefined, `standing participant ${participant.name} has no legacy owner.`);
256
+ const { body } = owner;
257
+ assertSource(candidates.length > 0 && candidates.every(route => sameBody(route.body, body)), `standing participant ${participant.name} has no unique legacy body agreeing with its owner.`);
258
+ const shared = [...owners, ...routes.filter(route => canonicalPathSync(route.location) === location)]
259
+ .some(row => nameKey(row.participant) !== nameKey(participant.name) && sameBody(row.body, body));
260
+ assertSource(!shared, `standing participant ${participant.name} shares a legacy body with another owner.`);
261
+ const address = uniqueSeatAddress(candidates);
262
+ return {
263
+ participant: participant.name,
264
+ body,
265
+ generation: formatActivityId(joined),
266
+ ...(address === undefined ? {} : { wakeAddress: address }),
267
+ };
268
+ });
269
+ }
270
+ function tableToken(database, table, order) {
271
+ const rows = database.prepare(`SELECT * FROM ${table} ORDER BY ${order}`).all();
272
+ return JSON.stringify(rows.map((row) => Object.entries(row).sort(([left], [right]) => left.localeCompare(right))));
273
+ }
274
+ function legacySourceToken(database) {
275
+ return JSON.stringify([
276
+ ['square_meta', tableToken(database, 'square_meta', 'id')],
277
+ ['activities', tableToken(database, 'activities', 'act_index')],
278
+ ['participants', tableToken(database, 'participants', 'name_key_json')],
279
+ ['attention', tableToken(database, 'attention', 'listener_key_json,target_key_json')],
280
+ ['landed_audience', tableToken(database, 'landed_audience', 'act_index,recipient_order')],
281
+ ['receipts', tableToken(database, 'receipts', 'owner_json,act_index')],
282
+ ['catch_leases', tableToken(database, 'catch_leases', 'owner_json')],
283
+ ['routes', tableToken(database, 'routes', 'position')],
284
+ ]);
285
+ }
286
+ function inspectLegacyRowArtifact(database, location, host) {
287
+ const metaRows = database.prepare('SELECT * FROM square_meta').all();
288
+ assertSource(metaRows.length === 1 && metaRows[0].id === 1, 'header row is malformed.');
289
+ const meta = metaRows[0];
290
+ sourceInteger(meta.revision);
291
+ const nextActIndex = sourceInteger(meta.next_act_index);
292
+ assertSource(meta.hard_cap === null || (typeof meta.hard_cap === 'number' && Number.isSafeInteger(meta.hard_cap) && meta.hard_cap > 0), 'header row is malformed.');
293
+ assertSource(meta.throttle_per_minute === null || (typeof meta.throttle_per_minute === 'number' && Number.isSafeInteger(meta.throttle_per_minute) && meta.throttle_per_minute > 0), 'header row is malformed.');
294
+ sourceStringArray(meta.preamble_json);
295
+ sourceStringArray(meta.warmup_json);
296
+ const driver = createSqliteDriver(database);
297
+ let activityCount = 0;
298
+ let previous = -1;
299
+ for (const row of database.prepare('SELECT act_index FROM activities ORDER BY act_index').iterate()) {
300
+ const index = sourceInteger(row.act_index);
301
+ assertSource(index > previous && index < nextActIndex, 'activity index is malformed or unassigned.');
302
+ const activity = driver.activity(index);
303
+ assertSource(activity !== undefined);
304
+ validateStoredActivity(activity);
305
+ activityCount++;
306
+ previous = index;
307
+ }
308
+ assertSource(nextActIndex > previous, 'nextActIndex is behind the activity history.');
309
+ const roster = driver.roster();
310
+ const routes = readLegacyRoutes(database);
311
+ for (const row of database.prepare('SELECT owner_json,act_index,at FROM receipts').iterate()) {
312
+ decodeJsonString(row.owner_json, true);
313
+ assertSource(sourceInteger(row.act_index) < nextActIndex, 'receipt references an unassigned activity index.');
314
+ sourceFinite(row.at);
315
+ }
316
+ const leaseGenerations = [];
317
+ const leaseOwners = [];
318
+ for (const row of database.prepare('SELECT owner_json,lease_id_json,heartbeat_at,expires_at,filter_json FROM catch_leases').iterate()) {
319
+ const owner = decodeJsonString(row.owner_json, true);
320
+ leaseOwners.push(row.owner_json);
321
+ const leaseId = decodeJsonString(row.lease_id_json, true);
322
+ const lease = {
323
+ leaseId,
324
+ heartbeatAt: sourceFinite(row.heartbeat_at),
325
+ expiresAt: sourceFinite(row.expires_at),
326
+ ...(row.filter_json === null ? {} : { filter: parseJson(row.filter_json, 'lease filter is malformed.') }),
327
+ };
328
+ validateLegacyLease(lease);
329
+ const ownerParticipant = roster.find((participant) => participant.joined && participant.name === owner);
330
+ if (ownerParticipant?.lastJoinIndex !== undefined)
331
+ leaseGenerations.push({ ownerJson: row.owner_json, generation: formatActivityId(ownerParticipant.lastJoinIndex) });
332
+ }
333
+ const seats = planSeats(location, roster, routes, host.owners, driver);
334
+ const counts = {
335
+ activities: activityCount,
336
+ receipts: Number(database.prepare('SELECT COUNT(*) AS n FROM receipts').get().n),
337
+ leases: leaseOwners.length,
338
+ routes: routes.length,
339
+ };
340
+ const sourceToken = legacySourceToken(database);
341
+ return { standing: seats.length, seats, leaseGenerations, sourceToken, counts,
342
+ deliveries: planLegacyDeliveries(host.evidence, seats, driver) };
343
+ }
344
+ function legacyPayloadHash(payload) {
345
+ return createHash('sha256').update(payload).digest('hex');
346
+ }
347
+ function legacyDeliveryPayload(source, current) {
348
+ const native = source.nativeDelivery;
349
+ if (native === undefined)
350
+ return {};
351
+ assertSource(isObject(native) && typeof native.payload === 'string' && native.payload.length > 0, 'native delivery correlation is malformed.');
352
+ if (native.harness === 'claude') {
353
+ assertSource(typeof source.claimToken === 'string' && /^[a-zA-Z0-9-]+$/.test(source.claimToken)
354
+ && typeof native.endpoint === 'string' && native.endpoint.length > 0
355
+ && (native.messageId === undefined || typeof native.messageId === 'string' && native.messageId.length > 0), 'Claude native correlation is malformed.');
356
+ const address = current?.kind === 'claude-native' && current.address.endpoint === native.endpoint
357
+ ? current.address : { endpoint: native.endpoint, loaded: 'true' };
358
+ return { correlation: { harness: 'claude', token: source.claimToken,
359
+ ...(native.messageId === undefined ? {} : { messageId: native.messageId }), payloadHash: legacyPayloadHash(native.payload) },
360
+ route: { kind: 'claude-native', address: { ...address, sessionId: source.session } } };
361
+ }
362
+ assertSource(native.harness === 'opencode' && typeof native.inputId === 'string' && native.inputId.length > 0, 'OpenCode native correlation is malformed.');
363
+ return { correlation: { harness: 'opencode', token: native.inputId, payloadHash: legacyPayloadHash(native.payload) },
364
+ route: current?.kind === 'opencode-server' && current.address.sessionId === source.session
365
+ ? current : { kind: 'opencode-server', address: { sessionId: source.session } } };
366
+ }
367
+ function planLegacyDeliveries(records, seats, driver) {
368
+ const byGroup = new Map();
369
+ const tokens = new Set(), completedBodies = new Map();
370
+ for (const source of records) {
371
+ const participant = driver.participant(source.participant);
372
+ const native = source.kind === 'wake' && source.nativeDelivery !== undefined;
373
+ if (!participant?.joined && !native)
374
+ continue; // Noncritical completed diagnostics are not imported.
375
+ assertSource(participant?.joined || participant?.done, `native delivery evidence for ${source.participant} has no unique participant.`);
376
+ const joined = establishingJoin(driver, participant);
377
+ const generation = formatActivityId(joined);
378
+ const seat = seats.find(seat => nameKey(seat.participant) === nameKey(participant.name));
379
+ if (participant.joined)
380
+ assertSource(seat !== undefined && seat.body.session === source.session
381
+ && source.agent === seat.body.agent && (source.channel === undefined || source.channel === seat.body.channel), `delivery evidence for standing participant ${source.participant} has no unique owner.`);
382
+ if (native && seat !== undefined)
383
+ assertSource(source.nativeDelivery?.harness ===
384
+ (seat.body.channel === 'claude-code' ? 'claude' : seat.body.channel === 'opencode' ? 'opencode' : undefined), 'native delivery harness conflicts with its owner body.');
385
+ const index = parseActivityId(source.activity);
386
+ assertSource(index !== undefined && index > joined && driver.activity(index)?.kind === 'say'
387
+ && driver.directedAt(participant.name, index) !== undefined, `delivery evidence for ${source.participant} has no unique activity in its join generation.`);
388
+ const payload = legacyDeliveryPayload(source, seat?.wakeAddress);
389
+ if (participant.done) {
390
+ const done = driver.activityHeads({ actors: [participant.name], kinds: ['done'] }, { edge: 'newest', limit: 1 }).items[0];
391
+ assertSource(done !== undefined && index < done.index && source.session.length > 0, `native delivery evidence for ${source.participant} has no unique completed generation.`);
392
+ const harness = source.nativeDelivery.harness;
393
+ const body = { channel: harness === 'claude' ? 'claude-code' : 'opencode', session: source.session,
394
+ ...(source.agent === undefined ? {} : { agent: source.agent }) };
395
+ validateBody(body);
396
+ const coordinate = JSON.stringify([body.channel, body.session, body.agent]);
397
+ const previous = completedBodies.get(nameKey(participant.name));
398
+ assertSource(previous === undefined || previous === coordinate, 'completed native delivery owner mapping is ambiguous.');
399
+ completedBodies.set(nameKey(participant.name), coordinate);
400
+ }
401
+ assertSource(source.kind === 'wake' || source.kind === 'presentation', 'delivery evidence kind is unsupported.');
402
+ assertSource(['dispatching', 'accepted', 'failed', 'unknown', 'released', 'presented', 'clipped'].includes(source.outcome), 'delivery evidence outcome is unsupported.');
403
+ const at = sourceFinite(source.at);
404
+ const expiresAt = source.expiresAt === undefined ? undefined : sourceFinite(source.expiresAt);
405
+ const claimToken = source.claimToken;
406
+ assertSource(claimToken === undefined || typeof claimToken === 'string' && claimToken.length > 0, 'delivery claim token is malformed.');
407
+ if (claimToken !== undefined) {
408
+ assertSource(!tokens.has(claimToken), 'delivery claim token is ambiguous.');
409
+ tokens.add(claimToken);
410
+ }
411
+ const group = JSON.stringify([nameKey(participant.name), generation, index, source.kind]);
412
+ const row = {
413
+ participant: participant.name,
414
+ generation,
415
+ index,
416
+ kind: source.kind,
417
+ outcome: source.outcome,
418
+ at,
419
+ ...(expiresAt === undefined ? {} : { expiresAt }),
420
+ ...(claimToken === undefined ? {} : { claimToken }),
421
+ payload,
422
+ ...(source.attemptN === undefined ? {} : { attemptN: source.attemptN }),
423
+ };
424
+ const rows = byGroup.get(group) ?? [];
425
+ rows.push(row);
426
+ byGroup.set(group, rows);
427
+ }
428
+ const deliveries = [];
429
+ for (const rows of byGroup.values()) {
430
+ const attempts = rows.map(row => row.attemptN);
431
+ const explicit = attempts.filter((attempt) => attempt !== undefined);
432
+ assertSource(explicit.every((attempt) => Number.isSafeInteger(attempt) && attempt >= 0) && new Set(explicit).size === explicit.length, 'delivery attempt mapping is ambiguous.');
433
+ if (explicit.length !== rows.length)
434
+ assertSource(rows.length === 1 && explicit.length === 0, 'delivery attempt mapping is ambiguous.');
435
+ rows.forEach((row, index) => deliveries.push({ ...row, attemptN: explicit.length === 0 ? 0 : explicit[index] }));
436
+ }
437
+ return deliveries;
438
+ }
439
+ const V4_REBUILT_TABLES = ['square_meta', 'catch_leases'];
440
+ function tableDefinition(name) {
441
+ const definition = SCHEMA_SQL.split(';').map((statement) => statement.trim()).find((statement) => statement.startsWith(`CREATE TABLE ${name} (`));
442
+ if (definition === undefined)
443
+ throw new Error(`Missing current table definition for ${name}.`);
444
+ return `${definition};`;
445
+ }
446
+ function currentAdditionStatements() {
447
+ return SCHEMA_SQL.split(';').map((statement) => statement.trim()).filter((statement) => {
448
+ const match = /^(?:CREATE (?:UNIQUE )?TABLE|CREATE (?:UNIQUE )?INDEX)\s+([a-z_]+)/i.exec(statement);
449
+ return match !== null && FORMAT4_ADDED_OBJECTS.has(match[1]);
450
+ }).map((statement) => `${statement};`);
451
+ }
452
+ function importOwnershipAndEvidence(database, plan) {
453
+ for (const generation of plan.leaseGenerations) {
454
+ database.prepare('UPDATE catch_leases SET generation_json=? WHERE owner_json=?').run(JSON.stringify(generation.generation), generation.ownerJson);
455
+ }
456
+ const insertSeat = database.prepare('INSERT INTO seats(participant_key_json,participant_json,channel_json,session_json,agent_json,generation_json,wake_address_json) VALUES(?,?,?,?,?,?,?)');
457
+ for (const seat of plan.seats) {
458
+ insertSeat.run(JSON.stringify(nameKey(seat.participant)), JSON.stringify(seat.participant), JSON.stringify(seat.body.channel), JSON.stringify(seat.body.session), seat.body.agent === undefined ? null : JSON.stringify(seat.body.agent), JSON.stringify(seat.generation), seat.wakeAddress === undefined ? null : JSON.stringify(seat.wakeAddress));
459
+ }
460
+ const insertDelivery = database.prepare('INSERT INTO delivery_evidence(participant_key_json,participant_json,generation_json,act_index,kind,attempt_n,outcome,at,expires_at,claim_token_json,payload_json) VALUES(?,?,?,?,?,?,?,?,?,?,?)');
461
+ for (const evidence of plan.deliveries) {
462
+ insertDelivery.run(JSON.stringify(nameKey(evidence.participant)), JSON.stringify(evidence.participant), JSON.stringify(evidence.generation), evidence.index, evidence.kind, evidence.attemptN, evidence.outcome, evidence.at, evidence.expiresAt ?? null, evidence.claimToken === undefined ? null : JSON.stringify(evidence.claimToken), JSON.stringify(evidence.payload));
463
+ }
464
+ }
465
+ function upgradeRowArtifact(database, location, plan) {
466
+ database.exec('ALTER TABLE square_meta RENAME TO square_meta_v3;');
467
+ database.exec(tableDefinition('square_meta'));
468
+ database.exec('INSERT INTO square_meta(id,revision,delivery_revision,next_act_index,hard_cap,throttle_per_minute,preamble_json,warmup_json) SELECT id,revision,0,next_act_index,hard_cap,throttle_per_minute,preamble_json,warmup_json FROM square_meta_v3;');
469
+ database.exec('DROP TABLE square_meta_v3;');
470
+ database.exec('ALTER TABLE catch_leases RENAME TO catch_leases_v3;');
471
+ database.exec(tableDefinition('catch_leases'));
472
+ database.exec('INSERT INTO catch_leases(owner_json,generation_json,lease_id_json,heartbeat_at,expires_at,filter_json) SELECT owner_json, \'""\', lease_id_json,heartbeat_at,expires_at,filter_json FROM catch_leases_v3;');
473
+ database.exec('DROP TABLE catch_leases_v3;');
474
+ currentAdditionStatements().forEach((statement) => database.exec(statement));
475
+ importOwnershipAndEvidence(database, plan);
476
+ database.exec('DROP TABLE routes;');
477
+ database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION};`);
478
+ validateFormat4Framing(database, location);
479
+ }
171
480
  function validateMigrationSource(database, location) {
172
481
  const before = identity(database);
173
482
  if (before.applicationId !== APPLICATION_ID)
174
483
  throw invalidArtifact('unrelated SQLite database.');
175
- if (before.userVersion === 1) {
176
- validateLegacyFraming(database, APPLICATION_ID, location);
177
- return 1;
178
- }
179
- if (before.userVersion === V2_USER_VERSION) {
180
- validateRowFraming(database, V2_USER_VERSION, location);
181
- createSqliteDriver(database).header();
182
- return 2;
484
+ if (before.userVersion === V2_USER_VERSION || before.userVersion === V3_USER_VERSION) {
485
+ const issue = legacyRowFramingIssue(database, APPLICATION_ID, before.userVersion, location, SCHEMA_SQL);
486
+ if (issue !== undefined)
487
+ throw invalidArtifact(issue);
488
+ return before.userVersion;
183
489
  }
184
490
  if (before.userVersion === CURRENT_USER_VERSION) {
185
- validateSqliteHeader(database, location);
491
+ validateFormat4Framing(database, location);
186
492
  return undefined;
187
493
  }
188
494
  throw invalidArtifact('unsupported SQLite format version.');
189
495
  }
190
496
  /**
191
- * Explicit in-place conversion of a version 1 snapshot or version 2 row
192
- * artifact. Each source is validated before and under the write lock; v1 is
193
- * imported through the normal current row initializer, while v2 rebuilds only
194
- * activities and its indexes before publishing the new format version.
497
+ * Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
498
+ * preflighted read-only, then receive the format-4 ownership/evidence rows in one transaction.
499
+ * Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
500
+ * unique; no source record is retired. Version 1 is not a supported source for this format transition.
195
501
  */
196
- export async function migrateSquareFile(location, signal) {
502
+ export async function migrateSquareFile(location, signal, env = process.env) {
197
503
  const squarePath = await canonicalPath(location);
198
504
  const database = openExistingSquareDatabase(squarePath);
199
505
  try {
200
- let alreadyCurrent;
201
- const preflight = await inSqliteTransaction(database, false, () => {
202
- const source = validateMigrationSource(database, squarePath);
203
- if (source === undefined)
204
- alreadyCurrent = { status: 'already-current', standing: standing(database) };
205
- return source;
206
- }, (source) => source, signal);
207
- if (preflight === undefined)
208
- return alreadyCurrent;
506
+ const source = await inSqliteTransaction(database, false, () => validateMigrationSource(database, squarePath), value => value, signal);
507
+ if (source === undefined)
508
+ return { status: 'already-current', standing: standing(database) };
509
+ const host = await readLegacyHostData(squarePath, env, signal);
510
+ const plan = await inSqliteTransaction(database, false, () => {
511
+ assertSource(validateMigrationSource(database, squarePath) === source, 'source format changed during migration preflight.');
512
+ return inspectLegacyRowArtifact(database, squarePath, host);
513
+ }, value => value, signal);
209
514
  database.exec('PRAGMA busy_timeout = 0; PRAGMA synchronous = FULL');
210
515
  const result = {};
211
516
  await inSqliteTransaction(database, true, () => {
212
- const source = validateMigrationSource(database, squarePath);
213
- result.source = source;
517
+ assertSource(validateMigrationSource(database, squarePath) === source, 'source format changed during migration preflight.');
518
+ // Resolve the same frozen host input again against the locked artifact before any DDL.
519
+ const lockedPlan = inspectLegacyRowArtifact(database, squarePath, host);
520
+ assertSource(lockedPlan.sourceToken === plan.sourceToken, 'source changed during migration preflight.');
521
+ result.plan = lockedPlan;
214
522
  }, () => {
215
- if (result.source === 1) {
216
- const legacy = decodeLegacySnapshot(database);
217
- initializeSqlite(database, legacy.state, legacy.revision);
218
- database.exec(`DROP TABLE ${V1_SNAPSHOT_TABLE}; PRAGMA user_version = ${CURRENT_USER_VERSION}`);
219
- result.outcome = { status: 'migrated', standing: standing(database), counts: counts(legacy.state) };
220
- }
221
- else if (result.source === 2) {
523
+ if (source === 2)
222
524
  rebuildV2Activities(database);
223
- database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION}`);
224
- result.outcome = { status: 'migrated', standing: standing(database), counts: rowCounts(database) };
225
- }
525
+ const migratedPlan = result.plan;
526
+ upgradeRowArtifact(database, squarePath, migratedPlan);
527
+ result.outcome = { status: 'migrated', standing: migratedPlan.standing, counts: migratedPlan.counts };
226
528
  }, signal);
227
- return result.outcome ?? { status: 'already-current', standing: standing(database) };
529
+ return result.outcome;
228
530
  }
229
531
  finally {
230
532
  database.close();
@@ -1,6 +1,6 @@
1
1
  import { type DatabaseSync } from 'node:sqlite';
2
- import { SquareError, type ActivityObservation, type HardCap, type StoredAct, type WakeRoute, type WatchLease } from './model.js';
3
- import type { SquareHeader, SquareRead, RouteFact } from './ports.js';
2
+ import { SquareError, type ActivityObservation, type HardCap, type StoredAct, type WatchLease } from './model.js';
3
+ import type { SquareHeader, SquareRead, DeliveryClaimInput, DeliveryClaimResult, DeliveryCompletion, DeliveryPreparation } from './ports.js';
4
4
  import { type LandingWriter } from './square-domain.js';
5
5
  /** Explicit import/export DTOs, not a runtime transaction model. */
6
6
  export interface SquareRuntimeState {
@@ -14,13 +14,13 @@ export interface SquareState {
14
14
  preamble: string[];
15
15
  warmup: string[];
16
16
  acts: StoredAct[];
17
- routes?: WakeRoute[];
18
17
  runtime: SquareRuntimeState;
19
18
  }
20
19
  export declare function emptyRuntimeState(nextActIndex?: number): SquareRuntimeState;
21
20
  export declare const APPLICATION_ID = 1397838405;
22
21
  export declare const V2_USER_VERSION = 2;
23
- export declare const CURRENT_USER_VERSION = 3;
22
+ export declare const V3_USER_VERSION = 3;
23
+ export declare const CURRENT_USER_VERSION = 4;
24
24
  export declare const V1_SNAPSHOT_TABLE = "square_snapshot";
25
25
  export interface LegacyFraming {
26
26
  readonly applicationId: number;
@@ -37,21 +37,21 @@ export declare function validateLegacyFraming(database: DatabaseSync, applicatio
37
37
  export declare const ACTIVITIES_TABLE_SQL = "CREATE TABLE activities (\n act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),\n at REAL NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')),\n actor_json TEXT COLLATE BINARY,\n actor_key_json TEXT COLLATE BINARY,\n reach TEXT CHECK (reach = 'bell'),\n reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),\n CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),\n CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))\n);";
38
38
  export declare const ACTIVITY_INDEXES_SQL: readonly ["CREATE INDEX activity_kind_index ON activities(kind, act_index)", "CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index)", "CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say'", "CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say'", "CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell'"];
39
39
  export declare const SCHEMA_SQL: string;
40
- export declare const SCHEMA_SQL_V2: string;
41
40
  export declare function invalidArtifact(detail: string): SquareError;
42
41
  export declare function validateStoredActivity(value: unknown): StoredAct;
43
42
  export declare function validateWatchLease(value: unknown): WatchLease;
44
- export declare function validateRoute(value: unknown): RouteFact;
45
- export declare function validateRowFraming(db: DatabaseSync, version: 2 | 3, location?: string): void;
43
+ export declare function validateRowFraming(db: DatabaseSync, version: 2 | 3 | 4, location?: string): void;
46
44
  export declare function validateSqliteHeader(db: DatabaseSync, location?: string): SquareHeader;
47
45
  export interface SqliteDriver extends SquareRead, LandingWriter {
48
46
  readonly changed: boolean;
47
+ readonly deliveryChanged: boolean;
48
+ claimDelivery(input: DeliveryClaimInput): DeliveryClaimResult;
49
+ prepareDelivery(input: DeliveryPreparation): boolean;
50
+ completeDelivery(input: DeliveryCompletion): boolean;
49
51
  }
50
52
  export declare function createSqliteDriver(db: DatabaseSync): SqliteDriver;
51
- export declare function legacyRouteFact(value: Record<string, unknown>): RouteFact;
52
53
  /** Explicit import only. Caller owns transaction, legacy validation and format publication. */
53
54
  export declare function initializeSqlite(db: DatabaseSync, state: SquareState, revision?: number): void;
54
- /** Exceptional export projection for fixtures, never a runtime fallback. */
55
55
  export declare function exportSqliteState(db: DatabaseSync): SquareState;
56
56
  /** Full read-only integrity inspection, including maintained-index replay comparison. */
57
57
  export declare function inspectSqlite(db: DatabaseSync): {