@astrosheep/square 0.3.105 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +8 -12
  5. package/dist/artifact.js +66 -343
  6. package/dist/attention-presentation.d.ts +33 -7
  7. package/dist/attention-presentation.js +33 -69
  8. package/dist/boundary-presentation.d.ts +0 -2
  9. package/dist/boundary-presentation.js +17 -22
  10. package/dist/catch-decisions.d.ts +2 -2
  11. package/dist/catch-decisions.js +14 -4
  12. package/dist/catch-selection.d.ts +8 -0
  13. package/dist/catch-selection.js +5 -0
  14. package/dist/channel-artifact.d.ts +10 -0
  15. package/dist/channel-artifact.js +55 -0
  16. package/dist/channel-delivery.d.ts +11 -0
  17. package/dist/channel-delivery.js +51 -0
  18. package/dist/channel-domain.d.ts +3 -0
  19. package/dist/channel-domain.js +83 -0
  20. package/dist/channel-facade.d.ts +76 -0
  21. package/dist/channel-facade.js +1 -0
  22. package/dist/channel-internal.d.ts +126 -0
  23. package/dist/channel-internal.js +440 -0
  24. package/dist/channel-metadata.d.ts +11 -0
  25. package/dist/channel-metadata.js +1 -0
  26. package/dist/channel-ports.d.ts +241 -0
  27. package/dist/channel-ports.js +1 -0
  28. package/dist/channel-presentation.d.ts +17 -0
  29. package/dist/channel-presentation.js +22 -0
  30. package/dist/channel-sqlite-schema.d.ts +13 -0
  31. package/dist/channel-sqlite-schema.js +81 -0
  32. package/dist/channel-transport.d.ts +62 -0
  33. package/dist/channel-transport.js +1 -0
  34. package/dist/channel-values.d.ts +27 -0
  35. package/dist/channel-values.js +90 -0
  36. package/dist/channel.d.ts +31 -21
  37. package/dist/channel.js +42 -74
  38. package/dist/claude-delivery-status.js +1 -1
  39. package/dist/claude-delivery.d.ts +2 -5
  40. package/dist/claude-delivery.js +56 -103
  41. package/dist/claude-mod.js +4 -4
  42. package/dist/claude-transport.d.ts +5 -0
  43. package/dist/claude-transport.js +58 -0
  44. package/dist/cli/context.d.ts +2 -2
  45. package/dist/cli/context.js +0 -1
  46. package/dist/cli/maintenance-commands.d.ts +0 -4
  47. package/dist/cli/maintenance-commands.js +2 -29
  48. package/dist/cli/program.js +1 -1
  49. package/dist/cli/registry.js +1 -2
  50. package/dist/codex-address.d.ts +1 -1
  51. package/dist/codex-queue.d.ts +2 -2
  52. package/dist/codex-queue.js +1 -3
  53. package/dist/decisions.js +3 -3
  54. package/dist/delivery-deferral.d.ts +9 -18
  55. package/dist/delivery-deferral.js +4 -4
  56. package/dist/delivery-health.d.ts +4 -4
  57. package/dist/delivery-health.js +11 -11
  58. package/dist/delivery-operations.d.ts +15 -2
  59. package/dist/delivery-operations.js +60 -30
  60. package/dist/delivery.d.ts +44 -33
  61. package/dist/delivery.js +49 -75
  62. package/dist/help.js +1 -6
  63. package/dist/http-delivery.d.ts +2 -2
  64. package/dist/http-delivery.js +2 -4
  65. package/dist/inbox.d.ts +10 -18
  66. package/dist/inbox.js +44 -53
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/mcp.d.ts +2 -1
  70. package/dist/mcp.js +40 -70
  71. package/dist/message.d.ts +7 -0
  72. package/dist/message.js +23 -0
  73. package/dist/model.d.ts +41 -77
  74. package/dist/model.js +7 -139
  75. package/dist/neutral.d.ts +72 -0
  76. package/dist/neutral.js +27 -0
  77. package/dist/notifications.d.ts +2 -8
  78. package/dist/notifications.js +15 -50
  79. package/dist/operation-presentation.d.ts +1 -2
  80. package/dist/operation-presentation.js +1 -7
  81. package/dist/owner.d.ts +2 -2
  82. package/dist/owner.js +1 -1
  83. package/dist/paseo-delivery.d.ts +1 -1
  84. package/dist/paseo-delivery.js +2 -4
  85. package/dist/paseo-timeline.js +1 -0
  86. package/dist/plugins/opencode/delivery.js +27 -42
  87. package/dist/ports.d.ts +46 -168
  88. package/dist/presentation-operations.d.ts +1 -14
  89. package/dist/presentation-operations.js +16 -16
  90. package/dist/presentation.d.ts +5 -5
  91. package/dist/presentation.js +1 -0
  92. package/dist/runtime.js +2 -2
  93. package/dist/search.js +1 -1
  94. package/dist/sqlite-channel.d.ts +15 -0
  95. package/dist/sqlite-channel.js +600 -0
  96. package/dist/sqlite-framing.d.ts +8 -0
  97. package/dist/sqlite-framing.js +31 -0
  98. package/dist/sqlite-square.d.ts +12 -13
  99. package/dist/sqlite-square.js +192 -248
  100. package/dist/sqlite-values.d.ts +21 -0
  101. package/dist/sqlite-values.js +78 -0
  102. package/dist/square-api.d.ts +1 -1
  103. package/dist/square-api.js +1 -1
  104. package/dist/square-core.d.ts +1 -1
  105. package/dist/square-domain.d.ts +21 -7
  106. package/dist/square-domain.js +93 -94
  107. package/dist/square-facade.d.ts +12 -66
  108. package/dist/square-index.d.ts +1 -1
  109. package/dist/square-index.js +1 -1
  110. package/dist/square-location.d.ts +6 -1
  111. package/dist/square-location.js +1 -1
  112. package/dist/square-receive.d.ts +2 -3
  113. package/dist/square-sqlite.d.ts +45 -44
  114. package/dist/square-sqlite.js +308 -764
  115. package/dist/square-tools.d.ts +8 -0
  116. package/dist/square-tools.js +39 -0
  117. package/dist/views.d.ts +6 -6
  118. package/dist/views.js +15 -9
  119. package/dist/wake-preview.d.ts +7 -0
  120. package/dist/wake-preview.js +15 -0
  121. package/dist/wake-sink.d.ts +2 -2
  122. package/dist/wake-sink.js +1 -1
  123. package/dist/wake-transports.d.ts +7 -0
  124. package/dist/wake-transports.js +42 -0
  125. package/guides/claude-native.md +13 -10
  126. package/guides/codex-delivery.md +3 -1
  127. package/guides/participant-ownership.md +1 -1
  128. package/guides/row-runtime-delivery-evidence.md +2 -0
  129. package/guides/row-storage-design.md +19 -44
  130. package/guides/square-api.md +137 -60
  131. package/package.json +3 -3
  132. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  133. package/plugins/claude/types.d.ts +1 -1
  134. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  135. package/plugins/opencode/delivery.ts +28 -41
  136. package/plugins/pi/square-pi.js +28 -51
  137. package/dist/square-legacy-framing.d.ts +0 -11
  138. package/dist/square-legacy-framing.js +0 -64
  139. package/dist/square-migration-host-ledger.d.ts +0 -26
  140. package/dist/square-migration-host-ledger.js +0 -169
  141. package/dist/square-migration.d.ts +0 -30
  142. package/dist/square-migration.js +0 -599
  143. package/guides/migration.md +0 -156
@@ -1,599 +0,0 @@
1
- import { DatabaseSync } from 'node:sqlite';
2
- import { createHash } from 'node:crypto';
3
- import { openExistingSquareDatabase } from './artifact.js';
4
- import { inSqliteTransaction } from './sqlite-transaction.js';
5
- import { canonicalPath, canonicalPathSync } from './canonical-path.js';
6
- import { harnessOwner, decodeHarnessOwner } from './owner.js';
7
- import { formatActivityId, isWakeRouteKind, nameKey } from './model.js';
8
- import { parseActivityId } from './square-core.js';
9
- import { readLegacyHostData } from './square-migration-host-ledger.js';
10
- import { legacyRowFramingIssue, FORMAT4_ADDED_OBJECTS } from './square-legacy-framing.js';
11
- import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V3_USER_VERSION, V4_USER_VERSION, V5_USER_VERSION, V6_USER_VERSION, V7_USER_VERSION, SCHEMA_SQL, FORMAT4_SCHEMA_SQL, DELIVERY_DEFERRALS_SQL, validateRowFraming, inspectSqlite, createSqliteDriver, invalidArtifact, validateStoredActivity, exportSqliteState, initializeSqlite } from './square-sqlite.js';
12
- function isObject(value) {
13
- return typeof value === 'object' && value !== null && !Array.isArray(value);
14
- }
15
- function hasExactKeys(value, required, optional = []) {
16
- const allowed = new Set([...required, ...optional]);
17
- return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
18
- }
19
- function isFiniteNumber(value) {
20
- return typeof value === 'number' && Number.isFinite(value);
21
- }
22
- function isNonNegativeInteger(value) {
23
- return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
24
- }
25
- function isStringArray(value) {
26
- return Array.isArray(value) && value.every((item) => typeof item === 'string');
27
- }
28
- function sameCoordinate(left, right) {
29
- return left.channel === right.channel && left.session === right.session && left.agent === right.agent;
30
- }
31
- function validateRecord(value, item) {
32
- return isObject(value) && Object.entries(value).every(([key, candidate]) => key.length > 0 && item(candidate));
33
- }
34
- function validateRuntime(value) {
35
- return isObject(value) && hasExactKeys(value, ['nextActIndex', 'observations', 'leases'])
36
- && isNonNegativeInteger(value.nextActIndex)
37
- && validateRecord(value.observations, (candidate) => isObject(candidate)
38
- && Object.entries(candidate).every(([id, observation]) => parseActivityId(id) !== undefined && isObject(observation) && hasExactKeys(observation, ['state', 'at']) && observation.state === 'seen' && isFiniteNumber(observation.at)))
39
- && validateRecord(value.leases, (candidate) => {
40
- try {
41
- validateLegacyLease(candidate);
42
- return true;
43
- }
44
- catch {
45
- return false;
46
- }
47
- });
48
- }
49
- function validateAssignedRuntimeReferences(runtime) {
50
- for (const observations of Object.values(runtime.observations)) {
51
- for (const id of Object.keys(observations)) {
52
- const index = parseActivityId(id);
53
- if (index === undefined)
54
- return 'malformed';
55
- if (index >= runtime.nextActIndex)
56
- return 'future';
57
- }
58
- }
59
- return 'ok';
60
- }
61
- function validateActs(value) {
62
- if (!Array.isArray(value))
63
- return false;
64
- let previous = -1;
65
- try {
66
- for (const candidate of value) {
67
- const act = validateStoredActivity(candidate);
68
- if (act.kind === 'unlisten' || act.index <= previous)
69
- return false;
70
- previous = act.index;
71
- }
72
- }
73
- catch {
74
- return false;
75
- }
76
- return true;
77
- }
78
- function nonFiniteRoutePath(value, path) {
79
- if (typeof value === 'number')
80
- return Number.isFinite(value) ? undefined : path;
81
- if (Array.isArray(value)) {
82
- for (let index = 0; index < value.length; index += 1) {
83
- const found = nonFiniteRoutePath(value[index], `${path}[${index}]`);
84
- if (found !== undefined)
85
- return found;
86
- }
87
- return undefined;
88
- }
89
- if (isObject(value)) {
90
- for (const [key, item] of Object.entries(value)) {
91
- const found = nonFiniteRoutePath(item, `${path}.${key}`);
92
- if (found !== undefined)
93
- return found;
94
- }
95
- }
96
- return undefined;
97
- }
98
- /** Refuse exponent overflow with its legacy path before JSON could coerce it. */
99
- function assertFiniteRouteNumbers(routes) {
100
- for (let index = 0; index < routes.length; index += 1) {
101
- const found = nonFiniteRoutePath(routes[index], `routes[${index}]`);
102
- if (found !== undefined)
103
- throw invalidArtifact(`${found} is not a finite number.`);
104
- }
105
- }
106
- /** Validate persisted v1 JSON; row codecs share activity and lease acceptance. */
107
- export function validateSquareState(value) {
108
- if (isObject(value) && Array.isArray(value.routes))
109
- assertFiniteRouteNumbers(value.routes);
110
- if (!isObject(value) || !hasExactKeys(value, ['hardCap', 'preamble', 'warmup', 'acts', 'runtime'], ['throttlePerMinute', 'routes'])
111
- || !(value.hardCap === null || (Number.isSafeInteger(value.hardCap) && value.hardCap > 0))
112
- || (value.throttlePerMinute !== undefined && (!Number.isSafeInteger(value.throttlePerMinute) || value.throttlePerMinute <= 0))
113
- || !isStringArray(value.preamble) || !isStringArray(value.warmup) || !validateActs(value.acts)
114
- || (value.routes !== undefined && (!Array.isArray(value.routes) || !value.routes.every((route) => isObject(route) && isWakeRouteKind(route.kind)
115
- && typeof route.location === 'string' && typeof route.participant === 'string' && typeof route.sessionId === 'string'
116
- && typeof route.channel === 'string' && isObject(route.address) && Object.values(route.address).every((item) => typeof item === 'string')
117
- && typeof route.updatedAt === 'number')))
118
- || !validateRuntime(value.runtime))
119
- throw invalidArtifact('snapshot schema is malformed.');
120
- const historyBoundary = value.acts.at(-1)?.index ?? -1;
121
- if (value.runtime.nextActIndex <= historyBoundary)
122
- throw invalidArtifact('nextActIndex is behind the activity history.');
123
- const references = validateAssignedRuntimeReferences(value.runtime);
124
- if (references === 'malformed')
125
- throw invalidArtifact('snapshot schema is malformed.');
126
- if (references === 'future')
127
- throw invalidArtifact('runtime references an unassigned activity index.');
128
- return value;
129
- }
130
- function identity(database) {
131
- const row = database.prepare('SELECT application_id, user_version FROM pragma_application_id, pragma_user_version').get();
132
- return { applicationId: row.application_id, userVersion: row.user_version };
133
- }
134
- function standing(database) {
135
- return createSqliteDriver(database).roster().filter((participant) => participant.joined).length;
136
- }
137
- const ACTIVITY_INDEX_NAMES = ['activity_kind_index', 'activity_actor_index', 'activity_say_actor_index', 'activity_say_time', 'activity_bell_actor_time'];
138
- function migrateActivities(database) {
139
- // Only activity identity references are deleted. Range watermarks (read.through and
140
- // delivery cancellation boundaries) remain meaningful even when their endpoint is gone.
141
- for (const table of ['landed_audience', 'receipts', 'delivery_evidence']) {
142
- database.exec(`DELETE FROM ${table} WHERE act_index IN (SELECT act_index FROM activities WHERE kind IN ('hold','resume'));`);
143
- }
144
- database.exec([
145
- "UPDATE activities SET reply_index=NULL WHERE reply_index IN (SELECT act_index FROM activities WHERE kind IN ('hold','resume'));",
146
- ...ACTIVITY_INDEX_NAMES.map((name) => `DROP INDEX ${name};`),
147
- 'ALTER TABLE activities RENAME TO activities_legacy;',
148
- ACTIVITIES_TABLE_SQL,
149
- "INSERT INTO activities(act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json) SELECT act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json FROM activities_legacy WHERE kind NOT IN ('hold','resume') ORDER BY act_index;",
150
- 'DROP TABLE activities_legacy;',
151
- ...ACTIVITY_INDEXES_SQL.map((sql) => `${sql};`),
152
- ].join('\n'));
153
- // Actor-bearing deleted activities and cleared replies can change maintained indices.
154
- // Replay surviving facts only, without lowering the authoritative next allocator.
155
- const names = database.prepare('SELECT name_key_json,name_json FROM participants').all();
156
- const rebuilt = new DatabaseSync(':memory:');
157
- try {
158
- initializeSqlite(rebuilt, exportSqliteState(database));
159
- // Canonical spelling is an established identity fact, not the spelling of
160
- // whichever actor activity happens to survive this migration first.
161
- const retainName = rebuilt.prepare('UPDATE participants SET name_json=? WHERE name_key_json=?');
162
- for (const row of names)
163
- retainName.run(row.name_json, row.name_key_json);
164
- for (const table of ['participants', 'attention', 'landed_audience']) {
165
- database.exec(`DELETE FROM ${table};`);
166
- for (const row of rebuilt.prepare(`SELECT * FROM ${table}`).all()) {
167
- const columns = Object.keys(row);
168
- database.prepare(`INSERT INTO ${table}(${columns.join(',')}) VALUES(${columns.map(() => '?').join(',')})`)
169
- .run(...Object.values(row));
170
- }
171
- }
172
- }
173
- finally {
174
- rebuilt.close();
175
- }
176
- }
177
- function validateCurrentFraming(database, location) {
178
- validateRowFraming(database, CURRENT_USER_VERSION, location);
179
- inspectSqlite(database);
180
- }
181
- function assertSource(condition, detail = 'row is malformed.') {
182
- if (!condition)
183
- throw invalidArtifact(detail);
184
- }
185
- function parseJson(value, detail) {
186
- assertSource(typeof value === 'string', detail);
187
- try {
188
- return JSON.parse(value);
189
- }
190
- catch {
191
- throw invalidArtifact(detail);
192
- }
193
- }
194
- function decodeJsonString(value, nonempty = false) {
195
- const parsed = parseJson(value, 'row contains invalid JSON.');
196
- assertSource(typeof parsed === 'string' && (!nonempty || parsed.length > 0) && JSON.stringify(parsed) === value, 'string encoding is malformed.');
197
- return parsed;
198
- }
199
- function sourceInteger(value, minimum = 0) {
200
- assertSource(typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum);
201
- return value;
202
- }
203
- function sourceFinite(value) {
204
- assertSource(typeof value === 'number' && Number.isFinite(value));
205
- return value;
206
- }
207
- function sourceStringArray(value) {
208
- const parsed = parseJson(value, 'header contains invalid JSON.');
209
- assertSource(Array.isArray(parsed) && parsed.every((item) => typeof item === 'string'));
210
- assertSource(JSON.stringify(parsed) === value, 'header string encoding is malformed.');
211
- return parsed;
212
- }
213
- function decodeLegacyRoute(row) {
214
- assertSource(sourceInteger(row.position) >= 0);
215
- const parsed = parseJson(row.payload_json, 'route payload is malformed.');
216
- assertSource(isObject(parsed)
217
- && hasExactKeys(parsed, ['location', 'participant', 'channel', 'address', 'extra'], ['epoch']), 'route payload is malformed.');
218
- assertSource(typeof parsed.location === 'string' && typeof parsed.participant === 'string'
219
- && typeof parsed.channel === 'string' && isObject(parsed.address)
220
- && Object.values(parsed.address).every((value) => typeof value === 'string')
221
- && isObject(parsed.extra), 'route payload is malformed.');
222
- assertSource(isWakeRouteKind(row.kind), 'route kind is malformed.');
223
- const participant = parsed.participant;
224
- assertSource(row.participant_key_json === JSON.stringify(nameKey(participant)), 'route participant key is malformed.');
225
- const sessionId = decodeJsonString(row.session_json);
226
- sourceFinite(row.updated_at);
227
- const coordinate = { channel: parsed.channel, session: sessionId, ...(parsed.extra.agent === undefined ? {} : { agent: parsed.extra.agent }) };
228
- harnessOwner(coordinate);
229
- const address = parsed.address;
230
- if (row.kind === 'claude-native')
231
- assertSource(address.sessionId === undefined || address.sessionId === sessionId, 'native route session conflicts with its body.');
232
- return { location: parsed.location, participant, coordinate,
233
- wakeAddress: { kind: row.kind, address: { ...address, ...(row.kind === 'claude-native' ? { sessionId } : {}) } } };
234
- }
235
- function validateLegacyLease(value) {
236
- assertSource(isObject(value) && hasExactKeys(value, ['leaseId', 'heartbeatAt', 'expiresAt'], ['filter']), 'legacy lease is malformed.');
237
- assertSource(typeof value.leaseId === 'string' && value.leaseId.length > 0
238
- && typeof value.heartbeatAt === 'number' && Number.isFinite(value.heartbeatAt)
239
- && typeof value.expiresAt === 'number' && Number.isFinite(value.expiresAt)
240
- && value.expiresAt >= value.heartbeatAt, 'legacy lease is malformed.');
241
- if (!Object.hasOwn(value, 'filter'))
242
- return;
243
- const filter = value.filter;
244
- assertSource(isObject(filter) && hasExactKeys(filter, [], ['participants', 'mention']), 'legacy lease filter is malformed.');
245
- assertSource((!Object.hasOwn(filter, 'participants') || Array.isArray(filter.participants) && filter.participants.every((item) => typeof item === 'string'))
246
- && (!Object.hasOwn(filter, 'mention') || typeof filter.mention === 'string'), 'legacy lease filter is malformed.');
247
- }
248
- function readLegacyRoutes(database) {
249
- return database.prepare('SELECT position,participant_key_json,session_json,kind,updated_at,payload_json FROM routes ORDER BY position').all()
250
- .map(decodeLegacyRoute);
251
- }
252
- function uniqueSeatAddress(routes) {
253
- const addresses = new Map();
254
- for (const route of routes) {
255
- const entries = Object.entries(route.wakeAddress.address).sort(([left], [right]) => left.localeCompare(right));
256
- addresses.set(JSON.stringify([route.wakeAddress.kind, entries]), route.wakeAddress);
257
- }
258
- return addresses.size === 1 ? [...addresses.values()][0] : undefined;
259
- }
260
- function establishingJoin(driver, participant) {
261
- const join = driver.activityHeads({ actors: [participant.name], kinds: ['join'] }, { edge: 'newest', limit: 1 }).items[0];
262
- assertSource(join?.kind === 'join' && join.index === participant.lastJoinIndex, `participant ${participant.name} has no verifiable establishing join.`);
263
- return join.index;
264
- }
265
- function planSeats(location, roster, routes, owners, driver) {
266
- const seats = [];
267
- const localRoutes = routes.filter(route => canonicalPathSync(route.location) === location);
268
- for (const participant of roster.filter((participant) => participant.joined)) {
269
- const joined = establishingJoin(driver, participant);
270
- const candidates = localRoutes.filter((route) => nameKey(route.participant) === nameKey(participant.name));
271
- const owner = owners.find(row => nameKey(row.participant) === nameKey(participant.name));
272
- const coordinates = [...candidates.map(route => route.coordinate), ...(owner === undefined ? [] : [owner.coordinate])];
273
- if (coordinates.length === 0 || !coordinates.every(coordinate => sameCoordinate(coordinate, coordinates[0])))
274
- continue;
275
- const address = uniqueSeatAddress(candidates);
276
- seats.push({
277
- participant: participant.name,
278
- owner: harnessOwner(coordinates[0]),
279
- generation: formatActivityId(joined),
280
- attachment: 'attached',
281
- ...(address === undefined ? {} : { wakeAddress: address }),
282
- });
283
- }
284
- return seats;
285
- }
286
- function tableToken(database, table, order) {
287
- const rows = database.prepare(`SELECT * FROM ${table} ORDER BY ${order}`).all();
288
- return JSON.stringify(rows.map((row) => Object.entries(row).sort(([left], [right]) => left.localeCompare(right))));
289
- }
290
- function legacySourceToken(database) {
291
- return JSON.stringify([
292
- ['square_meta', tableToken(database, 'square_meta', 'id')],
293
- ['activities', tableToken(database, 'activities', 'act_index')],
294
- ['participants', tableToken(database, 'participants', 'name_key_json')],
295
- ['attention', tableToken(database, 'attention', 'listener_key_json,target_key_json')],
296
- ['landed_audience', tableToken(database, 'landed_audience', 'act_index,recipient_order')],
297
- ['receipts', tableToken(database, 'receipts', 'owner_json,act_index')],
298
- ['catch_leases', tableToken(database, 'catch_leases', 'owner_json')],
299
- ['routes', tableToken(database, 'routes', 'position')],
300
- ]);
301
- }
302
- function inspectLegacyRowArtifact(database, location, host) {
303
- const metaRows = database.prepare('SELECT * FROM square_meta').all();
304
- assertSource(metaRows.length === 1 && metaRows[0].id === 1, 'header row is malformed.');
305
- const meta = metaRows[0];
306
- sourceInteger(meta.revision);
307
- const nextActIndex = sourceInteger(meta.next_act_index);
308
- assertSource(meta.hard_cap === null || (typeof meta.hard_cap === 'number' && Number.isSafeInteger(meta.hard_cap) && meta.hard_cap > 0), 'header row is malformed.');
309
- 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.');
310
- sourceStringArray(meta.preamble_json);
311
- sourceStringArray(meta.warmup_json);
312
- const driver = createSqliteDriver(database);
313
- let activityCount = 0;
314
- let previous = -1;
315
- for (const row of database.prepare("SELECT act_index FROM activities WHERE kind NOT IN ('hold','resume') ORDER BY act_index").iterate()) {
316
- const index = sourceInteger(row.act_index);
317
- assertSource(index > previous && index < nextActIndex, 'activity index is malformed or unassigned.');
318
- const activity = driver.activity(index);
319
- assertSource(activity !== undefined);
320
- validateStoredActivity(activity);
321
- activityCount++;
322
- previous = index;
323
- }
324
- assertSource(nextActIndex > previous, 'nextActIndex is behind the activity history.');
325
- const roster = driver.roster();
326
- const routes = readLegacyRoutes(database);
327
- for (const row of database.prepare('SELECT owner_json,act_index,at FROM receipts').iterate()) {
328
- decodeJsonString(row.owner_json, true);
329
- assertSource(sourceInteger(row.act_index) < nextActIndex, 'receipt references an unassigned activity index.');
330
- sourceFinite(row.at);
331
- }
332
- const leaseGenerations = [];
333
- const leaseOwners = [];
334
- for (const row of database.prepare('SELECT owner_json,lease_id_json,heartbeat_at,expires_at,filter_json FROM catch_leases').iterate()) {
335
- const owner = decodeJsonString(row.owner_json, true);
336
- leaseOwners.push(row.owner_json);
337
- const leaseId = decodeJsonString(row.lease_id_json, true);
338
- const lease = {
339
- leaseId,
340
- heartbeatAt: sourceFinite(row.heartbeat_at),
341
- expiresAt: sourceFinite(row.expires_at),
342
- ...(row.filter_json === null ? {} : { filter: parseJson(row.filter_json, 'lease filter is malformed.') }),
343
- };
344
- validateLegacyLease(lease);
345
- const ownerParticipant = roster.find((participant) => participant.joined && participant.name === owner);
346
- if (ownerParticipant?.lastJoinIndex !== undefined)
347
- leaseGenerations.push({ ownerJson: row.owner_json, generation: formatActivityId(ownerParticipant.lastJoinIndex) });
348
- }
349
- const seats = planSeats(location, roster, routes, host.owners, driver);
350
- const standingCount = roster.filter(participant => participant.joined).length;
351
- const deliveries = planLegacyDeliveries(host.evidence, seats, driver);
352
- const counts = {
353
- activities: activityCount,
354
- receipts: Number(database.prepare('SELECT COUNT(*) AS n FROM receipts').get().n),
355
- leases: leaseOwners.length,
356
- routes: routes.length,
357
- recoveredSeats: seats.length,
358
- unownedStanding: standingCount - seats.length,
359
- sharedBodySeats: seats.filter(seat => seats.some(other => other !== seat && other.owner === seat.owner)).length,
360
- skippedEvidence: host.evidence.length - deliveries.length,
361
- };
362
- const sourceToken = legacySourceToken(database);
363
- return { standing: standingCount, seats, leaseGenerations, sourceToken, counts, deliveries };
364
- }
365
- function legacyPayloadHash(payload) {
366
- return createHash('sha256').update(payload).digest('hex');
367
- }
368
- function legacyDeliveryPayload(source, current) {
369
- const native = source.nativeDelivery;
370
- if (native === undefined)
371
- return {};
372
- assertSource(isObject(native) && typeof native.payload === 'string' && native.payload.length > 0, 'native delivery correlation is malformed.');
373
- if (native.harness === 'claude') {
374
- assertSource(typeof source.claimToken === 'string' && /^[a-zA-Z0-9-]+$/.test(source.claimToken)
375
- && typeof native.endpoint === 'string' && native.endpoint.length > 0
376
- && (native.messageId === undefined || typeof native.messageId === 'string' && native.messageId.length > 0), 'Claude native correlation is malformed.');
377
- const address = current?.kind === 'claude-native' && current.address.endpoint === native.endpoint
378
- ? current.address : { endpoint: native.endpoint, loaded: 'true' };
379
- return { correlation: { harness: 'claude', token: source.claimToken,
380
- ...(native.messageId === undefined ? {} : { messageId: native.messageId }), payloadHash: legacyPayloadHash(native.payload) },
381
- route: { kind: 'claude-native', address: { ...address, sessionId: source.session } } };
382
- }
383
- assertSource(native.harness === 'opencode' && typeof native.inputId === 'string' && native.inputId.length > 0, 'OpenCode native correlation is malformed.');
384
- return { correlation: { harness: 'opencode', token: native.inputId, payloadHash: legacyPayloadHash(native.payload) },
385
- route: current?.kind === 'opencode-server' && current.address.sessionId === source.session
386
- ? current : { kind: 'opencode-server', address: { sessionId: source.session } } };
387
- }
388
- function planLegacyDeliveries(records, seats, driver) {
389
- const byGroup = new Map();
390
- const completedBodies = new Map();
391
- for (const source of records) {
392
- // Validate structure even when no surviving owner can accept this row.
393
- assertSource(source.kind === 'wake' || source.kind === 'presentation', 'delivery evidence kind is unsupported.');
394
- assertSource(['dispatching', 'accepted', 'failed', 'unknown', 'released', 'presented', 'clipped'].includes(source.outcome), 'delivery evidence outcome is unsupported.');
395
- assertSource(source.session.length > 0
396
- && (source.channel === undefined || typeof source.channel === 'string' && source.channel.length > 0)
397
- && (source.agent === undefined || typeof source.agent === 'string' && source.agent.length > 0), 'delivery evidence body is malformed.');
398
- const index = parseActivityId(source.activity);
399
- assertSource(index !== undefined, 'delivery evidence activity id is malformed.');
400
- const at = sourceFinite(source.at);
401
- const expiresAt = source.expiresAt === undefined ? undefined : sourceFinite(source.expiresAt);
402
- const claimToken = source.claimToken;
403
- assertSource(claimToken === undefined || typeof claimToken === 'string' && claimToken.length > 0, 'delivery claim token is malformed.');
404
- assertSource(source.attemptN === undefined || Number.isSafeInteger(source.attemptN) && source.attemptN >= 0, 'delivery attempt number is malformed.');
405
- const participant = driver.participant(source.participant);
406
- const seat = seats.find(seat => nameKey(seat.participant) === nameKey(source.participant));
407
- const payload = legacyDeliveryPayload(source, seat?.wakeAddress);
408
- const native = source.kind === 'wake' && source.nativeDelivery !== undefined;
409
- if (!participant?.joined && !(participant?.done && native))
410
- continue;
411
- const seatCoordinate = seat === undefined ? undefined : decodeHarnessOwner(seat.owner);
412
- if (participant.joined && (seatCoordinate === undefined || seatCoordinate.session !== source.session
413
- || seatCoordinate.agent !== source.agent || source.channel !== undefined && seatCoordinate.channel !== source.channel))
414
- continue;
415
- const harness = source.nativeDelivery?.harness;
416
- if (native && source.channel !== undefined && source.channel !== (harness === 'claude' ? 'claude-code' : 'opencode'))
417
- continue;
418
- if (native && seat !== undefined && harness !== undefined && seat.owner !== harnessOwner({ channel: harness === 'claude' ? 'claude-code' : 'opencode', session: source.session, ...(source.agent === undefined ? {} : { agent: source.agent }) }))
419
- continue;
420
- const joined = establishingJoin(driver, participant);
421
- const generation = formatActivityId(joined);
422
- if (index <= joined || driver.activityHeads({ indexes: [index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items.length === 0 || driver.directedAt(participant.name, index) === undefined)
423
- continue;
424
- if (participant.done) {
425
- const done = driver.activityHeads({ actors: [participant.name], kinds: ['done'] }, { edge: 'newest', limit: 1 }).items[0];
426
- if (done === undefined || index >= done.index)
427
- continue;
428
- const owner = harnessOwner({ channel: harness === 'claude' ? 'claude-code' : 'opencode', session: source.session,
429
- ...(source.agent === undefined ? {} : { agent: source.agent }) });
430
- const coordinate = JSON.stringify([owner]);
431
- const key = nameKey(participant.name), previous = completedBodies.get(key);
432
- completedBodies.set(key, previous === undefined || previous === coordinate ? coordinate : null);
433
- }
434
- const group = JSON.stringify([nameKey(participant.name), generation, index, source.kind]);
435
- const row = {
436
- participant: participant.name,
437
- generation,
438
- index,
439
- kind: source.kind,
440
- outcome: source.outcome,
441
- at,
442
- ...(expiresAt === undefined ? {} : { expiresAt }),
443
- ...(claimToken === undefined ? {} : { claimToken }),
444
- payload,
445
- ...(source.attemptN === undefined ? {} : { attemptN: source.attemptN }),
446
- };
447
- const rows = byGroup.get(group) ?? [];
448
- rows.push(row);
449
- byGroup.set(group, rows);
450
- }
451
- const deliveries = [];
452
- for (const rows of byGroup.values()) {
453
- if (completedBodies.get(nameKey(rows[0].participant)) === null)
454
- continue;
455
- const explicit = rows.flatMap(row => row.attemptN === undefined ? [] : [row.attemptN]);
456
- if (new Set(explicit).size !== explicit.length || explicit.length !== rows.length && rows.length !== 1)
457
- continue;
458
- rows.forEach((row, index) => deliveries.push({ ...row, attemptN: explicit.length === 0 ? 0 : explicit[index] }));
459
- }
460
- const tokens = new Map();
461
- for (const row of deliveries) {
462
- if (row.claimToken !== undefined)
463
- tokens.set(row.claimToken, (tokens.get(row.claimToken) ?? 0) + 1);
464
- }
465
- return deliveries.filter(row => row.claimToken === undefined || tokens.get(row.claimToken) === 1);
466
- }
467
- function tableDefinition(name) {
468
- const definition = SCHEMA_SQL.split(';').map((statement) => statement.trim()).find((statement) => statement.startsWith(`CREATE TABLE ${name} (`));
469
- if (definition === undefined)
470
- throw new Error(`Missing current table definition for ${name}.`);
471
- return `${definition};`;
472
- }
473
- function currentAdditionStatements() {
474
- return SCHEMA_SQL.split(';').map((statement) => statement.trim()).filter((statement) => {
475
- const match = /^(?:CREATE (?:UNIQUE )?TABLE|CREATE (?:UNIQUE )?INDEX)\s+([a-z_]+)/i.exec(statement);
476
- return match !== null && (FORMAT4_ADDED_OBJECTS.has(match[1]) || match[1] === 'seat_owner');
477
- }).map((statement) => `${statement};`);
478
- }
479
- function migrateSeatOwnership(database, source) {
480
- const rows = database.prepare('SELECT * FROM seats ORDER BY participant_key_json').all();
481
- database.exec('ALTER TABLE seats RENAME TO seats_legacy;');
482
- database.exec(tableDefinition('seats'));
483
- database.exec('CREATE INDEX seat_owner ON seats(owner_json);');
484
- const insert = database.prepare('INSERT INTO seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?)');
485
- for (const row of rows) {
486
- const channel = decodeJsonString(row.channel_json, true), session = decodeJsonString(row.session_json, true);
487
- const agent = row.agent_json === null ? undefined : decodeJsonString(row.agent_json, true);
488
- const owner = harnessOwner({ channel, session, ...(agent === undefined ? {} : { agent }) });
489
- const attachment = source === 6 ? row.attachment : 'attached';
490
- assertSource(attachment === 'attached' || attachment === 'detached', 'seat attachment is malformed.');
491
- insert.run(row.participant_key_json, row.participant_json, JSON.stringify(owner), row.generation_json, row.wake_address_json, attachment);
492
- }
493
- database.exec('DROP TABLE seats_legacy;');
494
- }
495
- function importOwnershipAndEvidence(database, plan) {
496
- for (const generation of plan.leaseGenerations) {
497
- database.prepare('UPDATE catch_leases SET generation_json=? WHERE owner_json=?').run(JSON.stringify(generation.generation), generation.ownerJson);
498
- }
499
- const insertSeat = database.prepare('INSERT INTO seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?)');
500
- for (const seat of plan.seats) {
501
- insertSeat.run(JSON.stringify(nameKey(seat.participant)), JSON.stringify(seat.participant), JSON.stringify(seat.owner), JSON.stringify(seat.generation), seat.wakeAddress === undefined ? null : JSON.stringify(seat.wakeAddress), seat.attachment);
502
- }
503
- 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(?,?,?,?,?,?,?,?,?,?,?)');
504
- for (const evidence of plan.deliveries) {
505
- 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));
506
- }
507
- }
508
- function upgradeRowArtifact(database, location, plan) {
509
- database.exec('ALTER TABLE square_meta RENAME TO square_meta_v3;');
510
- database.exec(tableDefinition('square_meta'));
511
- 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;');
512
- database.exec('DROP TABLE square_meta_v3;');
513
- database.exec('ALTER TABLE catch_leases RENAME TO catch_leases_v3;');
514
- database.exec(tableDefinition('catch_leases'));
515
- 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;');
516
- database.exec('DROP TABLE catch_leases_v3;');
517
- currentAdditionStatements().forEach((statement) => database.exec(statement));
518
- importOwnershipAndEvidence(database, plan);
519
- database.exec('DROP TABLE routes;');
520
- database.exec(DELIVERY_DEFERRALS_SQL);
521
- migrateActivities(database);
522
- database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION};`);
523
- validateCurrentFraming(database, location);
524
- }
525
- function validateMigrationSource(database, location) {
526
- const before = identity(database);
527
- if (before.applicationId !== APPLICATION_ID)
528
- throw invalidArtifact('unrelated SQLite database.');
529
- if (before.userVersion === V2_USER_VERSION || before.userVersion === V3_USER_VERSION) {
530
- const issue = legacyRowFramingIssue(database, APPLICATION_ID, before.userVersion, location, FORMAT4_SCHEMA_SQL);
531
- if (issue !== undefined)
532
- throw invalidArtifact(issue);
533
- return before.userVersion;
534
- }
535
- if (before.userVersion === V4_USER_VERSION || before.userVersion === V5_USER_VERSION || before.userVersion === V6_USER_VERSION || before.userVersion === V7_USER_VERSION) {
536
- validateRowFraming(database, before.userVersion, location);
537
- return before.userVersion;
538
- }
539
- if (before.userVersion === CURRENT_USER_VERSION) {
540
- validateCurrentFraming(database, location);
541
- return undefined;
542
- }
543
- throw invalidArtifact('unsupported SQLite format version.');
544
- }
545
- /**
546
- * Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
547
- * preflighted read-only, then receive ownership/evidence and deferral storage in one
548
- * format-8 transaction. Formats 4/5 gain attachment; format 4 also gains empty deferrals.
549
- * All legacy seats are encoded as opaque owners, preserving format-6 attachment.
550
- * Every source drops obsolete activities and their identity references, clears dangling
551
- * replies and rebuilds derived indices; surviving ids and allocation watermarks remain stable.
552
- * Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
553
- * unique; no source record is retired. Version 1 is not a supported source for this format transition.
554
- */
555
- export async function migrateSquareFile(location, signal, env = process.env) {
556
- const squarePath = await canonicalPath(location);
557
- const database = openExistingSquareDatabase(squarePath);
558
- try {
559
- const source = await inSqliteTransaction(database, false, () => validateMigrationSource(database, squarePath), value => value, signal);
560
- if (source === undefined)
561
- return { status: 'already-current', standing: standing(database) };
562
- if (source === V4_USER_VERSION || source === V5_USER_VERSION || source === V6_USER_VERSION || source === V7_USER_VERSION) {
563
- return await inSqliteTransaction(database, true, () => {
564
- assertSource(validateMigrationSource(database, squarePath) === source, 'source format changed during migration preflight.');
565
- }, () => {
566
- if (source === V4_USER_VERSION)
567
- database.exec(DELIVERY_DEFERRALS_SQL);
568
- if (source !== V7_USER_VERSION)
569
- migrateSeatOwnership(database, source);
570
- migrateActivities(database);
571
- database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION};`);
572
- validateCurrentFraming(database, squarePath);
573
- return { status: 'migrated', standing: standing(database) };
574
- }, signal);
575
- }
576
- const host = await readLegacyHostData(squarePath, env, signal);
577
- const plan = await inSqliteTransaction(database, false, () => {
578
- assertSource(validateMigrationSource(database, squarePath) === source, 'source format changed during migration preflight.');
579
- return inspectLegacyRowArtifact(database, squarePath, host);
580
- }, value => value, signal);
581
- database.exec('PRAGMA busy_timeout = 0; PRAGMA synchronous = FULL');
582
- const result = {};
583
- await inSqliteTransaction(database, true, () => {
584
- assertSource(validateMigrationSource(database, squarePath) === source, 'source format changed during migration preflight.');
585
- // Resolve the same frozen host input again against the locked artifact before any DDL.
586
- const lockedPlan = inspectLegacyRowArtifact(database, squarePath, host);
587
- assertSource(lockedPlan.sourceToken === plan.sourceToken, 'source changed during migration preflight.');
588
- result.plan = lockedPlan;
589
- }, () => {
590
- const migratedPlan = result.plan;
591
- upgradeRowArtifact(database, squarePath, migratedPlan);
592
- result.outcome = { status: 'migrated', standing: migratedPlan.standing, counts: migratedPlan.counts };
593
- }, signal);
594
- return result.outcome;
595
- }
596
- finally {
597
- database.close();
598
- }
599
- }