@astrosheep/square 0.3.93 → 0.3.95

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 (146) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +3 -3
  4. package/dist/attention.d.ts +19 -0
  5. package/dist/attention.js +30 -0
  6. package/dist/boundary-presentation.d.ts +2 -2
  7. package/dist/boundary-presentation.js +33 -40
  8. package/dist/catch-decisions.d.ts +5 -8
  9. package/dist/catch-decisions.js +45 -25
  10. package/dist/claude-delivery-status.js +10 -4
  11. package/dist/claude-delivery.js +19 -11
  12. package/dist/claude-mod.js +24 -53
  13. package/dist/cli/context.d.ts +9 -4
  14. package/dist/cli/context.js +30 -27
  15. package/dist/cli/express-presentation.d.ts +3 -1
  16. package/dist/cli/express-presentation.js +28 -16
  17. package/dist/cli/json.d.ts +3 -0
  18. package/dist/cli/json.js +14 -0
  19. package/dist/cli/mcp-server-command.js +2 -2
  20. package/dist/cli/observation-commands.d.ts +1 -3
  21. package/dist/cli/observation-commands.js +43 -56
  22. package/dist/cli/program.js +3 -3
  23. package/dist/cli/registry.js +7 -6
  24. package/dist/cli/square-commands.d.ts +0 -2
  25. package/dist/cli/square-commands.js +30 -44
  26. package/dist/codex-hook.d.ts +3 -2
  27. package/dist/codex-hook.js +13 -4
  28. package/dist/codex-queue.js +4 -4
  29. package/dist/decisions.d.ts +5 -16
  30. package/dist/decisions.js +9 -33
  31. package/dist/delivery-attempts.d.ts +52 -0
  32. package/dist/delivery-attempts.js +73 -0
  33. package/dist/delivery-deferral.js +2 -2
  34. package/dist/delivery-effects.d.ts +139 -0
  35. package/dist/delivery-effects.js +163 -0
  36. package/dist/delivery-health.d.ts +0 -1
  37. package/dist/delivery-health.js +1 -3
  38. package/dist/delivery-operations.d.ts +2 -2
  39. package/dist/delivery-operations.js +76 -101
  40. package/dist/delivery-primitives.d.ts +5 -0
  41. package/dist/delivery-primitives.js +3 -0
  42. package/dist/delivery.d.ts +1 -1
  43. package/dist/delivery.js +7 -6
  44. package/dist/help.js +14 -14
  45. package/dist/http-delivery.d.ts +16 -0
  46. package/dist/http-delivery.js +101 -0
  47. package/dist/inbox.d.ts +9 -22
  48. package/dist/inbox.js +23 -27
  49. package/dist/index.d.ts +1 -3
  50. package/dist/index.js +1 -1
  51. package/dist/mcp.d.ts +3 -0
  52. package/dist/mcp.js +23 -24
  53. package/dist/model.d.ts +12 -21
  54. package/dist/model.js +5 -8
  55. package/dist/notifications.d.ts +1 -1
  56. package/dist/notifications.js +8 -5
  57. package/dist/operation-presentation.d.ts +6 -6
  58. package/dist/operation-presentation.js +55 -58
  59. package/dist/owner.d.ts +11 -0
  60. package/dist/owner.js +32 -0
  61. package/dist/paseo-delivery.d.ts +4 -1
  62. package/dist/paseo-delivery.js +7 -12
  63. package/dist/paseo-state.d.ts +2 -2
  64. package/dist/paseo-state.js +6 -4
  65. package/dist/plugins/opencode/delivery.js +27 -15
  66. package/dist/ports.d.ts +18 -47
  67. package/dist/presentation-operations.d.ts +14 -19
  68. package/dist/presentation-operations.js +51 -61
  69. package/dist/presentation.d.ts +7 -17
  70. package/dist/presentation.js +46 -91
  71. package/dist/receive.d.ts +66 -0
  72. package/dist/receive.js +54 -0
  73. package/dist/runtime.d.ts +1 -8
  74. package/dist/runtime.js +2 -17
  75. package/dist/seats.d.ts +2 -3
  76. package/dist/seats.js +7 -15
  77. package/dist/session-lifecycle.d.ts +6 -2
  78. package/dist/session-lifecycle.js +39 -11
  79. package/dist/sqlite-square.d.ts +36 -23
  80. package/dist/sqlite-square.js +162 -211
  81. package/dist/square-api.d.ts +16 -11
  82. package/dist/square-api.js +27 -23
  83. package/dist/square-core.d.ts +0 -14
  84. package/dist/square-core.js +0 -9
  85. package/dist/square-domain.d.ts +4 -2
  86. package/dist/square-domain.js +65 -52
  87. package/dist/square-facade.d.ts +47 -105
  88. package/dist/square-index.d.ts +5 -13
  89. package/dist/square-index.js +13 -38
  90. package/dist/square-legacy-framing.d.ts +2 -0
  91. package/dist/square-legacy-framing.js +8 -0
  92. package/dist/square-location.d.ts +1 -14
  93. package/dist/square-location.js +4 -70
  94. package/dist/square-migration-host-ledger.d.ts +2 -2
  95. package/dist/square-migration-host-ledger.js +6 -5
  96. package/dist/square-migration.d.ts +4 -1
  97. package/dist/square-migration.js +93 -38
  98. package/dist/square-receive.d.ts +6 -0
  99. package/dist/square-receive.js +18 -0
  100. package/dist/square-sqlite.d.ts +9 -3
  101. package/dist/square-sqlite.js +139 -114
  102. package/dist/square-tools.d.ts +7 -10
  103. package/dist/square-tools.js +21 -61
  104. package/dist/stream.js +4 -5
  105. package/dist/views.d.ts +23 -13
  106. package/dist/views.js +47 -53
  107. package/dist/wakes.d.ts +1 -5
  108. package/dist/wakes.js +4 -6
  109. package/examples/symmetric-domains.mjs +250 -0
  110. package/guides/application-facade-delivery-evidence.md +6 -6
  111. package/guides/attention-primitive.md +32 -0
  112. package/guides/change-waits.md +2 -2
  113. package/guides/claude-native.md +18 -9
  114. package/guides/codex-delivery.md +17 -6
  115. package/guides/delivery-primitives.md +180 -0
  116. package/guides/mcp-connection-lifetime.md +1 -1
  117. package/guides/mcp.md +3 -3
  118. package/guides/migration.md +148 -151
  119. package/guides/operation-cancellation.md +1 -1
  120. package/guides/participant-ownership.md +78 -47
  121. package/guides/pi-tools.md +34 -5
  122. package/guides/primitive-catch-consolidation.md +5 -5
  123. package/guides/primitive-history-consolidation.md +2 -2
  124. package/guides/primitive-status-consolidation.md +3 -3
  125. package/guides/primitive-storage-consolidation.md +1 -1
  126. package/guides/receive-primitive.md +90 -0
  127. package/guides/row-runtime-delivery-evidence.md +1 -1
  128. package/guides/row-storage-design.md +22 -28
  129. package/guides/square-api.md +314 -91
  130. package/guides/symmetric-domains.md +92 -0
  131. package/package.json +14 -1
  132. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  133. package/plugins/claude/skills/square/SKILL.md +2 -6
  134. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  135. package/plugins/opencode/delivery.ts +27 -17
  136. package/plugins/paseo/client/palette.ts +18 -18
  137. package/plugins/paseo/client/square-message.tsx +17 -23
  138. package/plugins/pi/square-pi.js +17 -17
  139. package/skills/square/SKILL.md +2 -6
  140. package/dist/square-backend.d.ts +0 -13
  141. package/dist/square-backend.js +0 -1
  142. package/dist/square-stdio.d.ts +0 -22
  143. package/dist/square-stdio.js +0 -235
  144. package/dist/stdio-server.d.ts +0 -3
  145. package/dist/stdio-server.js +0 -212
  146. package/guides/backends.md +0 -193
@@ -1,8 +1,10 @@
1
1
  import { DatabaseSync as MemoryDatabase } from 'node:sqlite';
2
- import { sameBody, SquareError, nameKey, isWakeRouteKind, validateBody, validateActivityMetadata, activityMetadataHasContent } from './model.js';
2
+ import { SquareError, nameKey, isWakeRouteKind, validateOwner, validateActivityMetadata, activityMetadataHasContent } from './model.js';
3
3
  import { validateMetadataMatch, matchingDeferrals, validateDeferralId } from './delivery-deferral.js';
4
4
  import { deriveLanding, landingFacts } from './square-domain.js';
5
- import { legacyRowFramingIssue } from './square-legacy-framing.js';
5
+ import { claimAttempt, prepareAttempt, settleAttempt } from './delivery-attempts.js';
6
+ import { legacyRowFramingIssue, legacyActivitySchema } from './square-legacy-framing.js';
7
+ import { receive, recordConsumed } from './receive.js';
6
8
  export function emptyRuntimeState(nextActIndex = 0) {
7
9
  return { nextActIndex, observations: {}, leases: {} };
8
10
  }
@@ -11,7 +13,10 @@ const V1_USER_VERSION = 1;
11
13
  export const V2_USER_VERSION = 2;
12
14
  export const V3_USER_VERSION = 3;
13
15
  export const V4_USER_VERSION = 4;
14
- export const CURRENT_USER_VERSION = 5;
16
+ export const V5_USER_VERSION = 5;
17
+ export const V6_USER_VERSION = 6;
18
+ export const V7_USER_VERSION = 7;
19
+ export const CURRENT_USER_VERSION = 8;
15
20
  export const V1_SNAPSHOT_TABLE = 'square_snapshot';
16
21
  const V1_SNAPSHOT_DDL = `CREATE TABLE ${V1_SNAPSHOT_TABLE} (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL CHECK(revision>=0), state TEXT NOT NULL)`;
17
22
  const V1_SNAPSHOT_COLUMNS = [
@@ -56,18 +61,16 @@ export function validateLegacyFraming(database, applicationId, location = '') {
56
61
  function normalizedLegacySql(sql) {
57
62
  return sql.trim().replace(/\s+/g, ' ').replace(/\s*(>=|[(),=])\s*/g, '$1');
58
63
  }
59
- const CURRENT_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')";
64
+ const CURRENT_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','read','listen','ignore','unlisten')";
60
65
  export const ACTIVITIES_TABLE_SQL = `CREATE TABLE activities (
61
66
  act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
62
67
  at REAL NOT NULL,
63
68
  kind TEXT NOT NULL CHECK (${CURRENT_ACTIVITY_KIND_CHECK}),
64
- actor_json TEXT COLLATE BINARY,
65
- actor_key_json TEXT COLLATE BINARY,
69
+ actor_json TEXT COLLATE BINARY NOT NULL,
70
+ actor_key_json TEXT COLLATE BINARY NOT NULL,
66
71
  reach TEXT CHECK (reach = 'bell'),
67
72
  reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),
68
73
  payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
69
- CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),
70
- CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),
71
74
  CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))
72
75
  );`;
73
76
  export const ACTIVITY_INDEXES_SQL = [
@@ -87,6 +90,7 @@ export const DELIVERY_DEFERRALS_SQL = `CREATE TABLE delivery_deferrals (
87
90
  PRIMARY KEY (participant_key_json,generation_json,deferral_id_json)
88
91
  ) WITHOUT ROWID;
89
92
  CREATE INDEX delivery_deferral_expiry ON delivery_deferrals(participant_key_json,generation_json,expires_at);`;
93
+ const ATTACHMENT_COLUMN_SQL = "attachment TEXT NOT NULL DEFAULT 'attached' CHECK (attachment IN ('attached','detached'))";
90
94
  export const SCHEMA_SQL = `CREATE TABLE square_meta (
91
95
  id INTEGER PRIMARY KEY CHECK (id = 1),
92
96
  revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991),
@@ -119,13 +123,12 @@ CREATE TABLE catch_leases (
119
123
  CREATE TABLE seats (
120
124
  participant_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
121
125
  participant_json TEXT NOT NULL,
122
- channel_json TEXT NOT NULL,
123
- session_json TEXT NOT NULL,
124
- agent_json TEXT,
126
+ owner_json TEXT NOT NULL,
125
127
  generation_json TEXT NOT NULL,
126
- wake_address_json TEXT CHECK (wake_address_json IS NULL OR json_valid(wake_address_json))
128
+ wake_address_json TEXT CHECK (wake_address_json IS NULL OR json_valid(wake_address_json)),
129
+ ${ATTACHMENT_COLUMN_SQL}
127
130
  ) WITHOUT ROWID;
128
- CREATE INDEX seat_body ON seats(channel_json, session_json, agent_json);
131
+ CREATE INDEX seat_owner ON seats(owner_json);
129
132
  CREATE TABLE delivery_evidence (
130
133
  participant_key_json TEXT COLLATE BINARY NOT NULL,
131
134
  participant_json TEXT NOT NULL,
@@ -180,7 +183,13 @@ CREATE TABLE landed_audience (
180
183
  UNIQUE (act_index, recipient_order)
181
184
  ) WITHOUT ROWID;
182
185
  CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);`;
183
- export const FORMAT4_SCHEMA_SQL = SCHEMA_SQL.replace(DELIVERY_DEFERRALS_SQL, '');
186
+ export const FORMAT7_SCHEMA_SQL = legacyActivitySchema(SCHEMA_SQL);
187
+ export const FORMAT6_SCHEMA_SQL = FORMAT7_SCHEMA_SQL.replace(` owner_json TEXT NOT NULL,\n generation_json TEXT NOT NULL,`, ` channel_json TEXT NOT NULL,\n session_json TEXT NOT NULL,\n agent_json TEXT,\n generation_json TEXT NOT NULL,`).replace('CREATE INDEX seat_owner ON seats(owner_json);', 'CREATE INDEX seat_body ON seats(channel_json, session_json, agent_json);');
188
+ export const FORMAT5_SCHEMA_SQL = FORMAT6_SCHEMA_SQL.replace(`,\n ${ATTACHMENT_COLUMN_SQL}`, '');
189
+ export const FORMAT4_SCHEMA_SQL = FORMAT5_SCHEMA_SQL.replace(DELIVERY_DEFERRALS_SQL, '');
190
+ const FORMAT7_SCHEMA = FORMAT7_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
191
+ const FORMAT6_SCHEMA = FORMAT6_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
192
+ const FORMAT5_SCHEMA = FORMAT5_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
184
193
  const EXPECTED_SCHEMA_SQL = SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
185
194
  const FORMAT4_SCHEMA = FORMAT4_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
186
195
  const encode = JSON.stringify;
@@ -238,17 +247,17 @@ function immutable(value) {
238
247
  }
239
248
  return value;
240
249
  }
241
- const kinds = ['join', 'done', 'say', 'hold', 'resume', 'read', 'listen', 'ignore', 'unlisten'];
250
+ const kinds = ['join', 'done', 'say', 'read', 'listen', 'ignore', 'unlisten'];
242
251
  function head(row) {
243
252
  const index = integer(row.act_index), at = finite(row.at);
244
253
  requireFact(kinds.includes(string(row.kind)));
245
- const actor = row.actor_json === null ? undefined : decoded(row.actor_json, true);
246
- requireFact(actor === undefined ? row.actor_key_json === null && ['hold', 'resume'].includes(row.kind) : row.actor_key_json === key(actor));
254
+ const actor = decoded(row.actor_json, true);
255
+ requireFact(row.actor_key_json === key(actor));
247
256
  requireFact(row.reach === null || row.kind === 'say' && row.reach === 'bell');
248
257
  if (row.reply_index !== null)
249
258
  requireFact(row.kind === 'say' && integer(row.reply_index) < index);
250
259
  return immutable({
251
- index, at, kind: row.kind, ...(actor === undefined ? {} : { actor }), ...(row.reach === null ? {} : { reach: 'bell' }), ...(row.reply_index === null ? {} : { reply: row.reply_index })
260
+ index, at, kind: row.kind, actor, ...(row.reach === null ? {} : { reach: 'bell' }), ...(row.reply_index === null ? {} : { reply: row.reply_index })
252
261
  });
253
262
  }
254
263
  export function validateStoredActivity(value) {
@@ -256,13 +265,8 @@ export function validateStoredActivity(value) {
256
265
  integer(a.index);
257
266
  finite(a.at);
258
267
  requireFact(kinds.includes(string(a.kind)));
259
- const common = ['kind', 'at', 'index'];
260
- if (a.kind !== 'hold' && a.kind !== 'resume') {
261
- common.push('actor');
262
- string(a.actor, true);
263
- }
264
- else if (Object.hasOwn(a, 'actor'))
265
- string(a.actor, true);
268
+ const common = ['kind', 'at', 'index', 'actor'];
269
+ string(a.actor, true);
266
270
  switch (a.kind) {
267
271
  case 'say':
268
272
  shape(a, [...common], ['body', 'mentions', 'reach', 'reply', 'metadata']);
@@ -286,8 +290,7 @@ export function validateStoredActivity(value) {
286
290
  requireFact(integer(a.reply) < a.index);
287
291
  break;
288
292
  case 'done':
289
- case 'hold':
290
- shape(a, common, ['body', ...(a.kind === 'hold' ? ['actor'] : [])]);
293
+ shape(a, common, ['body']);
291
294
  if (Object.hasOwn(a, 'body'))
292
295
  string(a.body);
293
296
  break;
@@ -301,14 +304,14 @@ export function validateStoredActivity(value) {
301
304
  shape(a, [...common, 'target']);
302
305
  string(a.target, true);
303
306
  break;
304
- default: shape(a, common, a.kind === 'resume' ? ['actor'] : []);
307
+ default: shape(a, common);
305
308
  }
306
309
  return value;
307
310
  }
308
311
  function activity(row) {
309
312
  const h = head(row), payload = object(json(row.payload_json));
310
313
  // Payload cannot shadow indexed structural columns.
311
- const allowed = h.kind === 'say' ? ['body', 'mentions', 'metadata'] : h.kind === 'done' || h.kind === 'hold' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
314
+ const allowed = h.kind === 'say' ? ['body', 'mentions', 'metadata'] : h.kind === 'done' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
312
315
  requireFact(Object.keys(payload).every(k => allowed.includes(k)));
313
316
  return immutable(validateStoredActivity({ ...h, ...payload }));
314
317
  }
@@ -372,17 +375,19 @@ function wakeAddress(value) {
372
375
  stableJson(entries);
373
376
  return immutable({ kind: address.kind, address: entries });
374
377
  }
375
- function bodyFrom(row) {
376
- const body = { channel: decoded(row.channel_json, true), session: decoded(row.session_json, true), ...(row.agent_json === null ? {} : { agent: decoded(row.agent_json, true) }) };
377
- validateBody(body);
378
- return immutable(body);
378
+ function ownerFrom(row) {
379
+ const owner = decoded(row.owner_json, true);
380
+ validateOwner(owner);
381
+ return owner;
379
382
  }
380
383
  function seat(row) {
381
384
  const participant = decoded(row.participant_json, true);
382
385
  requireFact(row.participant_key_json === key(participant));
383
386
  const generation = activityId(decoded(row.generation_json, true));
384
387
  const parsedWake = row.wake_address_json === null ? undefined : wakeAddress(json(row.wake_address_json));
385
- return immutable({ participant, body: bodyFrom(row), generation, ...(parsedWake === undefined ? {} : { wakeAddress: parsedWake }) });
388
+ requireFact(row.attachment === 'attached' || row.attachment === 'detached', 'seat attachment is malformed.');
389
+ requireFact(row.attachment !== 'detached' || parsedWake === undefined, 'a detached seat has a wake address.');
390
+ return immutable({ participant, owner: ownerFrom(row), generation, attachment: row.attachment, ...(parsedWake === undefined ? {} : { wakeAddress: parsedWake }) });
386
391
  }
387
392
  function nativeCorrelation(value) {
388
393
  const item = shape(value, ['harness'], ['token', 'messageId', 'payloadHash']);
@@ -395,8 +400,7 @@ function nativeCorrelation(value) {
395
400
  ...(item.messageId === undefined ? {} : { messageId: item.messageId }),
396
401
  ...(item.payloadHash === undefined ? {} : { payloadHash: item.payloadHash }) });
397
402
  }
398
- function deliveryEvidence(row) {
399
- const payload = object(json(row.payload_json));
403
+ function deliveryEvidence(row, payload = object(json(row.payload_json))) {
400
404
  const participant = decoded(row.participant_json, true);
401
405
  requireFact(row.participant_key_json === key(participant));
402
406
  const generation = activityId(decoded(row.generation_json, true));
@@ -453,7 +457,7 @@ export function validateRowFraming(db, version, location = '') {
453
457
  const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
454
458
  requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
455
459
  requireFact(identity.user_version === version, 'unsupported SQLite format version.');
456
- validateOwnedSchema(db, version === V4_USER_VERSION ? FORMAT4_SCHEMA : EXPECTED_SCHEMA_SQL, location);
460
+ validateOwnedSchema(db, version === V4_USER_VERSION ? FORMAT4_SCHEMA : version === V5_USER_VERSION ? FORMAT5_SCHEMA : version === V6_USER_VERSION ? FORMAT6_SCHEMA : version === V7_USER_VERSION ? FORMAT7_SCHEMA : EXPECTED_SCHEMA_SQL, location);
457
461
  }
458
462
  export function validateSqliteHeader(db, location = '') {
459
463
  const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
@@ -462,7 +466,7 @@ export function validateSqliteHeader(db, location = '') {
462
466
  validateLegacyFraming(db, APPLICATION_ID, location);
463
467
  throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
464
468
  }
465
- if (identity.user_version === V2_USER_VERSION || identity.user_version === V3_USER_VERSION || identity.user_version === V4_USER_VERSION) {
469
+ if (identity.user_version === V2_USER_VERSION || identity.user_version === V3_USER_VERSION || identity.user_version === V4_USER_VERSION || identity.user_version === V5_USER_VERSION || identity.user_version === V6_USER_VERSION || identity.user_version === V7_USER_VERSION) {
466
470
  validateRowFraming(db, identity.user_version, location);
467
471
  throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
468
472
  }
@@ -486,8 +490,6 @@ function activitySelection(filter, alias = 'a') {
486
490
  add(`${alias}.act_index <= ?`, integer(filter.throughIndex, -1));
487
491
  if (filter.afterAt !== undefined)
488
492
  add(`${alias}.at > ?`, finite(filter.afterAt));
489
- if (filter.actorRequired)
490
- terms.push(`${alias}.actor_json IS NOT NULL`);
491
493
  for (const [column, values] of [['act_index', filter.indexes], ['kind', filter.kinds], [filter.actorMatch === 'exact' ? 'actor_json' : 'actor_key_json', filter.actors?.map(name => filter.actorMatch === 'exact' ? encode(name) : key(name))]]) {
492
494
  if (values !== undefined) {
493
495
  if (column === 'act_index')
@@ -532,7 +534,7 @@ export function createSqliteDriver(db) {
532
534
  }
533
535
  function directedHead(row, recipient) {
534
536
  const h = head(row);
535
- requireFact(h.kind === 'say' && h.actor !== undefined);
537
+ requireFact(h.kind === 'say');
536
538
  return immutable({
537
539
  index: h.index, at: h.at, actor: h.actor, recipient, route: routeKind(row.route)
538
540
  });
@@ -543,23 +545,22 @@ export function createSqliteDriver(db) {
543
545
  const latestSeats = `seats s JOIN participants p ON p.name_key_json=s.participant_key_json
544
546
  JOIN activities a ON a.act_index=p.last_join_index AND a.kind='join' AND a.actor_key_json=p.name_key_json
545
547
  AND s.generation_json='"act/'||a.act_index||'"'`;
546
- function bodySeats(body, lifecycle) {
547
- validateBody(body);
548
- return immutable(all(`SELECT s.* FROM ${latestSeats} WHERE p.${lifecycle}=1 AND s.channel_json=? AND s.session_json=? AND s.agent_json IS ? ORDER BY s.participant_key_json`, [encode(body.channel), encode(body.session), body.agent === undefined ? null : encode(body.agent)]).map(seat));
548
+ function ownerSeats(owner) {
549
+ validateOwner(owner);
550
+ return immutable(all(`SELECT s.* FROM ${latestSeats} WHERE p.joined=1 AND s.owner_json=? ORDER BY s.participant_key_json`, [encode(owner)]).map(seat));
549
551
  }
550
552
  const read = {
551
553
  seat(name) {
552
554
  const r = one(`SELECT s.* FROM ${latestSeats} WHERE p.joined=1 AND s.participant_key_json=?`, [key(name)]);
553
555
  return r ? seat(r) : undefined;
554
556
  },
555
- seatsForBody: body => bodySeats(body, 'joined'),
556
- completedSeatsForBody: body => bodySeats(body, 'done'),
557
+ seatsForOwner: ownerSeats,
557
558
  deliveryEvidence(participant, generation, activityIndex) {
558
559
  const args = [key(participant), encode(generation)];
559
560
  const filter = activityIndex === undefined ? '' : ' AND act_index=?';
560
561
  if (activityIndex !== undefined)
561
562
  args.push(integer(Number(activityIndex.slice(4))));
562
- return immutable(all(`SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=?${filter} ORDER BY act_index,kind,attempt_n`, args).map(deliveryEvidence));
563
+ return immutable(all(`SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=?${filter} ORDER BY act_index,kind,attempt_n`, args).map(row => deliveryEvidence(row)));
563
564
  },
564
565
  deliveryByToken(claimToken) {
565
566
  const row = one('SELECT * FROM delivery_evidence WHERE claim_token_json=?', [encode(claimToken)]);
@@ -589,18 +590,6 @@ export function createSqliteDriver(db) {
589
590
  roster: () => immutable(all('SELECT * FROM participants ORDER BY first_index').map(participant)),
590
591
  attention: name => immutable(attentionRows('t.listener_key_json=?', [key(name)])),
591
592
  incomingAttention: name => immutable(attentionRows('t.target_key_json=?', [key(name)])),
592
- hold() {
593
- const r = one("SELECT * FROM activities WHERE kind IN ('hold','resume') ORDER BY act_index DESC LIMIT 1");
594
- if (!r)
595
- return immutable({ active: false });
596
- const a = activity(r);
597
- if (a.kind === 'resume')
598
- return immutable({ active: false });
599
- requireFact(a.kind === 'hold');
600
- return immutable({
601
- active: true, at: a.at, ...(a.body === undefined ? {} : { reason: a.body }), ...(a.actor === undefined ? {} : { actor: a.actor })
602
- });
603
- },
604
593
  activity(index) {
605
594
  const r = one('SELECT * FROM activities WHERE act_index=?', [integer(index)]);
606
595
  return r ? activity(r) : undefined;
@@ -678,7 +667,7 @@ export function createSqliteDriver(db) {
678
667
  const groups = indices.map(r => {
679
668
  const rows = all(`SELECT a.act_index,a.at,a.kind,a.actor_json,a.actor_key_json,a.reach,a.reply_index,p.name_json,d.route FROM ${pendingFrom} WHERE ${pendingWhere} AND d.act_index=? ORDER BY d.recipient_order`, [integer(r.act_index)]);
680
669
  const h = head(rows[0]);
681
- requireFact(h.kind === 'say' && h.actor !== undefined);
670
+ requireFact(h.kind === 'say');
682
671
  return {
683
672
  index: h.index, at: h.at, actor: h.actor, recipients: rows.map(v => ({ recipient: decoded(v.name_json, true), route: routeKind(v.route) }))
684
673
  };
@@ -713,14 +702,14 @@ export function createSqliteDriver(db) {
713
702
  };
714
703
  const writes = {
715
704
  storeSeat(value) {
716
- validateBody(value.body);
705
+ validateOwner(value.owner);
717
706
  activityId(value.generation);
718
707
  string(value.participant, true);
719
708
  const existing = read.seat(value.participant);
720
709
  if (existing !== undefined && stableJson(existing) === stableJson(value))
721
710
  return false;
722
711
  const wake = value.wakeAddress === undefined ? null : encode(value.wakeAddress);
723
- run('INSERT INTO seats(participant_key_json,participant_json,channel_json,session_json,agent_json,generation_json,wake_address_json) VALUES(?,?,?,?,?,?,?) ON CONFLICT(participant_key_json) DO UPDATE SET participant_json=excluded.participant_json,channel_json=excluded.channel_json,session_json=excluded.session_json,agent_json=excluded.agent_json,generation_json=excluded.generation_json,wake_address_json=excluded.wake_address_json', [key(value.participant), encode(value.participant), encode(value.body.channel), encode(value.body.session), value.body.agent === undefined ? null : encode(value.body.agent), encode(value.generation), wake]);
712
+ run('INSERT INTO seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?) ON CONFLICT(participant_key_json) DO UPDATE SET participant_json=excluded.participant_json,owner_json=excluded.owner_json,generation_json=excluded.generation_json,wake_address_json=excluded.wake_address_json,attachment=excluded.attachment', [key(value.participant), encode(value.participant), encode(value.owner), encode(value.generation), wake, value.attachment]);
724
713
  run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
725
714
  run('DELETE FROM delivery_deferrals WHERE participant_key_json=?', [key(value.participant)]);
726
715
  deliveryChanged = true;
@@ -739,12 +728,28 @@ export function createSqliteDriver(db) {
739
728
  deliveryChanged = true;
740
729
  return count > 0;
741
730
  },
731
+ reattachSeat(value, address) {
732
+ const current = read.seat(value.participant);
733
+ if (current?.attachment !== 'detached')
734
+ return false;
735
+ run("UPDATE seats SET attachment='attached',wake_address_json=? WHERE participant_key_json=? AND generation_json=?", [address === undefined ? null : encode(wakeAddress(address)), key(value.participant), encode(value.generation)]);
736
+ deliveryChanged = true;
737
+ return true;
738
+ },
739
+ detachSeat(value) {
740
+ const count = run("UPDATE seats SET attachment='detached',wake_address_json=NULL WHERE participant_key_json=? AND generation_json=? AND attachment='attached'", [key(value.participant), encode(value.generation)]);
741
+ if (!count)
742
+ return false;
743
+ run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
744
+ run('DELETE FROM delivery_deferrals WHERE participant_key_json=? AND generation_json=?', [key(value.participant), encode(value.generation)]);
745
+ deliveryChanged = true;
746
+ return true;
747
+ },
742
748
  removeSeat(value) {
743
- // Done hides current authority; retain only its latest coordinate for exact-body resume.
744
749
  const row = one(`SELECT s.* FROM ${latestSeats} WHERE p.done=1 AND s.participant_key_json=?`, [key(value.participant)]);
745
- if (row === undefined || decoded(row.generation_json) !== value.generation)
750
+ if (row === undefined || decoded(row.generation_json) !== value.generation || ownerFrom(row) !== value.owner)
746
751
  return false;
747
- run('UPDATE seats SET wake_address_json=NULL WHERE participant_key_json=?', [key(value.participant)]);
752
+ run('DELETE FROM seats WHERE participant_key_json=?', [key(value.participant)]);
748
753
  run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
749
754
  run('DELETE FROM delivery_deferrals WHERE participant_key_json=?', [key(value.participant)]);
750
755
  changed = true;
@@ -765,7 +770,10 @@ export function createSqliteDriver(db) {
765
770
  const a = validateStoredActivity(effect.activity);
766
771
  requireFact(a.index >= read.header().nextActIndex && a.index < Number.MAX_SAFE_INTEGER, 'activity index cannot advance.');
767
772
  const { index, at, kind, actor, reach, reply, ...payload } = a;
768
- run('INSERT INTO activities(act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json) VALUES(?,?,?,?,?,?,?,?)', [index, at, kind, actor === undefined ? null : encode(actor), actor === undefined ? null : key(actor), reach ?? null, reply ?? null, encode(payload)]);
773
+ receive({
774
+ insertItem: () => { run('INSERT INTO activities(act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json) VALUES(?,?,?,?,?,?,?,?)', [index, at, kind, encode(actor), key(actor), reach ?? null, reply ?? null, encode(payload)]); },
775
+ insertAddress: (item, address, order) => { run('INSERT INTO landed_audience(act_index,recipient_key_json,recipient_order,route) VALUES(?,?,?,?)', [item, key(address.recipient), order, address.via]); },
776
+ }, { item: { key: index, data: a }, audience: effect.audience.map(r => ({ recipient: r.recipient, via: r.route })) });
769
777
  if (effect.participant) {
770
778
  const p = effect.participant;
771
779
  string(p.name, true);
@@ -794,7 +802,6 @@ export function createSqliteDriver(db) {
794
802
  requireFact(t.kind === 'listen' || t.kind === 'ignore');
795
803
  run('INSERT INTO attention(listener_key_json,target_key_json,target_json,kind,since_index) VALUES(?,?,?,?,?) ON CONFLICT(listener_key_json,target_key_json) DO UPDATE SET target_json=excluded.target_json,kind=excluded.kind,since_index=excluded.since_index', [key(t.listener), key(t.target), encode(t.target), t.kind, t.sinceIndex]);
796
804
  }
797
- effect.audience.forEach((r, order) => run('INSERT INTO landed_audience(act_index,recipient_key_json,recipient_order,route) VALUES(?,?,?,?)', [index, key(r.recipient), order, r.route]));
798
805
  run('UPDATE square_meta SET next_act_index=? WHERE id=1', [index + 1]);
799
806
  changed = true;
800
807
  },
@@ -802,11 +809,11 @@ export function createSqliteDriver(db) {
802
809
  finite(at);
803
810
  const next = read.header().nextActIndex;
804
811
  indexes.forEach(index => requireFact(integer(index) < next, 'receipt references an unassigned activity index.'));
805
- let count = 0;
806
- const encoded = encode(owner(name));
807
- string(owner(name), true);
808
- for (const index of indexes)
809
- count += run('INSERT OR IGNORE INTO receipts(owner_json,act_index,at) VALUES(?,?,?)', [encoded, index, at]);
812
+ const canonical = owner(name);
813
+ string(canonical, true);
814
+ const count = recordConsumed({
815
+ insertReceipt: (ref, time) => run('INSERT OR IGNORE INTO receipts(owner_json,act_index,at) VALUES(?,?,?)', [encode(ref.recipient), ref.item, time]) > 0,
816
+ }, indexes.map(item => ({ recipient: canonical, item })), at);
810
817
  if (count)
811
818
  changed = true;
812
819
  return count;
@@ -861,6 +868,40 @@ export function createSqliteDriver(db) {
861
868
  return count > 0;
862
869
  },
863
870
  };
871
+ function attempt(row) {
872
+ const data = object(json(row.payload_json)), evidence = deliveryEvidence(row, data);
873
+ return { purpose: evidence.kind === 'wake' ? 'dispatch' : 'presentation', token: evidence.claimToken ?? '',
874
+ ordinal: evidence.attemptN, outcome: evidence.outcome, at: evidence.at, expiresAt: evidence.expiresAt, data };
875
+ }
876
+ function attemptRows(seat, index, kind, known) {
877
+ const scope = [key(seat.participant), encode(seat.generation), index, kind];
878
+ let sources = [];
879
+ return {
880
+ list() {
881
+ sources = all('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? ORDER BY attempt_n DESC', scope);
882
+ return sources.map(attempt);
883
+ },
884
+ evidence(value) {
885
+ const source = sources.find(row => row.attempt_n === value.ordinal);
886
+ return deliveryEvidence({ ...source, outcome: value.outcome, at: value.at, expires_at: value.expiresAt ?? null }, value.data);
887
+ },
888
+ byToken(token) {
889
+ if (known?.token === token)
890
+ return known;
891
+ const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? AND claim_token_json=?', [...scope, encode(token)]);
892
+ return row === undefined ? undefined : attempt(row);
893
+ },
894
+ insert(value) {
895
+ run('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(?,?,?,?,?,?,?,?,?,?,?)', [scope[0], encode(seat.participant), scope[1], index, kind, value.ordinal, value.outcome, value.at, value.expiresAt ?? null, encode(value.token), encode(value.data)]);
896
+ deliveryChanged = true;
897
+ },
898
+ replace(value) {
899
+ const count = run('UPDATE delivery_evidence SET outcome=?,at=?,expires_at=?,payload_json=? WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? AND attempt_n=? AND claim_token_json IS ?', [value.outcome, value.at, value.expiresAt ?? null, encode(value.data), ...scope, value.ordinal, value.token === '' ? null : encode(value.token)]);
900
+ if (count)
901
+ deliveryChanged = true;
902
+ },
903
+ };
904
+ }
864
905
  const delivery = {
865
906
  claimDelivery(input) {
866
907
  requireFact(input.kind === 'wake' || input.kind === 'presentation');
@@ -872,7 +913,7 @@ export function createSqliteDriver(db) {
872
913
  wakeAddress(input.route);
873
914
  const currentSeat = read.seat(input.seat.participant);
874
915
  const item = read.activity(index);
875
- if (currentSeat === undefined || currentSeat.generation !== input.seat.generation || !sameBody(currentSeat.body, input.seat.body)
916
+ if (currentSeat === undefined || currentSeat.attachment !== 'attached' || currentSeat.generation !== input.seat.generation || currentSeat.owner !== input.seat.owner
876
917
  || item?.kind !== 'say' || read.directedAt(input.seat.participant, index) === undefined)
877
918
  return { status: 'stale' };
878
919
  if (matchingDeferrals(read.deliveryDeferrals(currentSeat.participant, currentSeat.generation), item.metadata, input.at).length > 0)
@@ -880,42 +921,24 @@ export function createSqliteDriver(db) {
880
921
  const cancelledThrough = read.cancelledThrough(input.seat.participant, input.seat.generation);
881
922
  if (cancelledThrough !== undefined && index <= cancelledThrough)
882
923
  return { status: 'stale' };
883
- const rows = all('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? ORDER BY attempt_n DESC', [key(input.seat.participant), encode(input.seat.generation), index, input.kind]);
884
- const decoded = rows.map(deliveryEvidence);
885
- const terminal = decoded.find((row) => input.kind === 'wake'
886
- ? row.outcome === 'accepted' || row.outcome === 'unknown'
887
- : row.outcome === 'presented' || row.outcome === 'unknown');
888
- if (terminal !== undefined)
889
- return { status: 'terminal', evidence: terminal };
890
- const dispatching = decoded.find((row) => row.outcome === 'dispatching');
891
- if (dispatching !== undefined && (dispatching.expiresAt === undefined || dispatching.expiresAt > input.at))
892
- return { status: 'busy', evidence: dispatching };
893
- if (dispatching !== undefined) {
894
- const updated = { ...dispatching, outcome: 'unknown', at: input.at, expiresAt: undefined, diagnostic: 'interrupted dispatch' };
895
- const source = rows.find((row) => row.attempt_n === dispatching.attemptN);
896
- run('UPDATE delivery_evidence SET outcome=?,at=?,expires_at=NULL,payload_json=? WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? AND attempt_n=?', ['unknown', input.at, encode({ ...object(json(source.payload_json)), diagnostic: 'interrupted dispatch' }), key(input.seat.participant), encode(input.seat.generation), index, input.kind, dispatching.attemptN]);
897
- deliveryChanged = true;
898
- return { status: 'terminal', evidence: updated };
899
- }
900
- const attemptN = (decoded[0]?.attemptN ?? -1) + 1;
901
- const payload = input.route === undefined ? {} : { route: input.route };
902
- const expiry = finite(input.at + leaseMs);
903
- run('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(?,?,?,?,?,?,?,?,?,?,?)', [key(input.seat.participant), encode(input.seat.participant), encode(input.seat.generation), index, input.kind, attemptN, 'dispatching', input.at, expiry, encode(input.claimToken), encode(payload)]);
904
- deliveryChanged = true;
905
- return { status: 'acquired', claimToken: input.claimToken, attemptN };
924
+ const rows = attemptRows(input.seat, index, input.kind);
925
+ const result = claimAttempt({ ...rows, replace(value) {
926
+ rows.replace({ ...value, data: { ...value.data, diagnostic: 'interrupted dispatch' } });
927
+ } }, { purpose: input.kind === 'wake' ? 'dispatch' : 'presentation', token: input.claimToken,
928
+ at: input.at, leaseMs, data: input.route === undefined ? {} : { route: input.route } });
929
+ if (result.status === 'acquired')
930
+ return { status: 'acquired', claimToken: result.token, attemptN: result.ordinal };
931
+ const evidence = result.interrupted ? { ...result.evidence, data: { ...result.evidence.data, diagnostic: 'interrupted dispatch' } } : result.evidence;
932
+ return { status: result.status, evidence: rows.evidence(evidence) };
906
933
  },
907
934
  prepareDelivery(input) {
908
935
  const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND claim_token_json=?', [key(input.seat.participant), encode(input.seat.generation), encode(input.claimToken)]);
909
936
  if (row === undefined || row.outcome !== 'dispatching')
910
937
  return false;
911
- const correlation = nativeCorrelation(input.correlation);
912
- const payload = object(json(row.payload_json));
938
+ const previous = attempt(row), correlation = nativeCorrelation(input.correlation), payload = previous.data;
913
939
  if (stableJson(payload.correlation ?? null) === stableJson(correlation))
914
940
  return true;
915
- const count = run('UPDATE delivery_evidence SET payload_json=? WHERE participant_key_json=? AND generation_json=? AND claim_token_json=?', [encode({ ...payload, correlation }), key(input.seat.participant), encode(input.seat.generation), encode(input.claimToken)]);
916
- if (count)
917
- deliveryChanged = true;
918
- return count > 0;
941
+ return prepareAttempt(attemptRows(input.seat, integer(row.act_index), row.kind, previous), { token: input.claimToken, data: { ...payload, correlation } });
919
942
  },
920
943
  completeDelivery(input) {
921
944
  const index = activityIndex(input.activity);
@@ -925,16 +948,9 @@ export function createSqliteDriver(db) {
925
948
  const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND claim_token_json=?', [key(input.seat.participant), encode(input.seat.generation), index, encode(input.claimToken)]);
926
949
  if (row === undefined)
927
950
  return false;
928
- if (row.outcome === 'accepted' && input.outcome === 'accepted')
929
- return true;
930
- if (row.outcome !== 'dispatching' && !(row.outcome === 'unknown' && input.outcome === 'accepted'))
931
- return false;
932
- const previous = object(json(row.payload_json));
933
- const payload = { ...previous, ...(input.signature === undefined ? {} : { signature: input.signature }), ...(input.message === undefined ? {} : { message: input.message }), ...(input.diagnostic === undefined ? {} : { diagnostic: input.diagnostic }), ...(input.correlation === undefined ? {} : { correlation: input.correlation }) };
934
- const count = run('UPDATE delivery_evidence SET outcome=?,at=?,expires_at=NULL,payload_json=? WHERE participant_key_json=? AND generation_json=? AND act_index=? AND claim_token_json=?', [input.outcome, input.at, encode(payload), key(input.seat.participant), encode(input.seat.generation), index, encode(input.claimToken)]);
935
- if (count)
936
- deliveryChanged = true;
937
- return count > 0;
951
+ const previous = attempt(row);
952
+ const payload = { ...previous.data, ...(input.signature === undefined ? {} : { signature: input.signature }), ...(input.message === undefined ? {} : { message: input.message }), ...(input.diagnostic === undefined ? {} : { diagnostic: input.diagnostic }), ...(input.correlation === undefined ? {} : { correlation: input.correlation }) };
953
+ return settleAttempt(attemptRows(input.seat, index, row.kind, previous), { token: input.claimToken, outcome: input.outcome, at: input.at, data: payload });
938
954
  },
939
955
  observeNativeDelivery(input) {
940
956
  const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND claim_token_json=?', [key(input.seat.participant), encode(input.seat.generation), encode(input.claimToken)]);
@@ -1023,15 +1039,18 @@ export function exportSqliteState(db) {
1023
1039
  }
1024
1040
  /** Full read-only integrity inspection, including maintained-index replay comparison. */
1025
1041
  export function inspectSqlite(db) {
1042
+ return inspectRows(db, seat);
1043
+ }
1044
+ function inspectRows(db, inspectSeat) {
1026
1045
  const check = db.prepare('PRAGMA integrity_check').all();
1027
1046
  requireFact(check.length === 1 && check[0].integrity_check === 'ok', 'SQLite integrity check failed.');
1028
1047
  const state = exportSqliteState(db), d = createSqliteDriver(db);
1029
1048
  d.roster();
1030
1049
  for (const row of db.prepare('SELECT * FROM seats').all())
1031
- seat(row);
1050
+ inspectSeat(row);
1032
1051
  for (const row of db.prepare('SELECT * FROM delivery_evidence').all())
1033
1052
  deliveryEvidence(row);
1034
- if (db.prepare('PRAGMA user_version').get().user_version === CURRENT_USER_VERSION) {
1053
+ if (Number(db.prepare('PRAGMA user_version').get().user_version) >= V5_USER_VERSION) {
1035
1054
  for (const row of db.prepare('SELECT * FROM delivery_deferrals').all())
1036
1055
  deliveryDeferral(row);
1037
1056
  }
@@ -1045,6 +1064,12 @@ export function inspectSqlite(db) {
1045
1064
  const rebuilt = new MemoryDatabase(':memory:');
1046
1065
  try {
1047
1066
  initializeSqlite(rebuilt, state, d.header().revision);
1067
+ // A migration may remove the activity that established canonical casing.
1068
+ // Retained participant spelling stays authoritative while replay checks
1069
+ // every derived lifecycle/count fact against surviving activities.
1070
+ const retainName = rebuilt.prepare('UPDATE participants SET name_json=? WHERE name_key_json=?');
1071
+ for (const p of d.roster())
1072
+ retainName.run(encode(p.name), key(p.name));
1048
1073
  for (const table of ['participants', 'attention', 'landed_audience']) {
1049
1074
  const original = db.prepare(`SELECT * FROM ${table} ORDER BY 1,2`).all();
1050
1075
  const expected = rebuilt.prepare(`SELECT * FROM ${table} ORDER BY 1,2`).all();
@@ -1,20 +1,17 @@
1
1
  import { z } from 'zod/v4';
2
2
  import { type Square } from './square-api.js';
3
- import type { OperationControl } from './square-facade.js';
3
+ import type { Owner, WakeAddress, OperationControl } from './square-facade.js';
4
+ export interface ToolIdentity {
5
+ readonly owner?: Owner;
6
+ readonly wakeAddress?: WakeAddress;
7
+ }
4
8
  type JsonObject = Record<string, unknown>;
5
- type Outcome = Promise<object>;
6
- type ToolRender = (result: JsonObject, args: JsonObject) => string | undefined;
7
9
  interface ToolDefinition {
8
10
  name: string;
9
11
  description: string;
10
12
  inputSchema: z.ZodObject<z.ZodRawShape>;
11
- run(square: Square, args: JsonObject, control: ToolControl): Outcome;
12
- /** Human text identical to what the CLI prints; transports show this to people. */
13
- render: ToolRender;
14
- }
15
- /** A transport that shows people the square asks the same operation for its rendering facts. */
16
- export interface ToolControl extends OperationControl {
17
- readonly observe?: boolean;
13
+ /** Human text identical to what the CLI prints; domain receipts stay inside the adapter. */
14
+ run(square: Square, args: JsonObject, control: OperationControl, identity: ToolIdentity): Promise<string>;
18
15
  }
19
16
  export declare const SQUARE_TOOL_INSTRUCTIONS = "Join once. Then express to speak, and catch to take in what reached you. History looks back without taking anything in.";
20
17
  export declare const SQUARE_TOOLS: readonly ToolDefinition[];