@astrosheep/square 0.3.94 → 0.3.96

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 (99) hide show
  1. package/dist/artifact.js +1 -1
  2. package/dist/attention-presentation.d.ts +5 -4
  3. package/dist/attention-presentation.js +18 -8
  4. package/dist/attention.d.ts +19 -0
  5. package/dist/attention.js +30 -0
  6. package/dist/boundary-presentation.js +50 -48
  7. package/dist/catch-decisions.d.ts +1 -2
  8. package/dist/catch-decisions.js +41 -21
  9. package/dist/channel.d.ts +72 -0
  10. package/dist/channel.js +86 -0
  11. package/dist/claude-delivery.js +2 -1
  12. package/dist/cli/context.js +0 -2
  13. package/dist/cli/express-presentation.js +3 -3
  14. package/dist/cli/json.js +1 -1
  15. package/dist/cli/program.js +1 -1
  16. package/dist/cli/registry.js +1 -3
  17. package/dist/cli/square-commands.d.ts +0 -2
  18. package/dist/cli/square-commands.js +21 -27
  19. package/dist/decisions.d.ts +0 -5
  20. package/dist/decisions.js +0 -13
  21. package/dist/delivery-attempts.d.ts +52 -0
  22. package/dist/delivery-attempts.js +73 -0
  23. package/dist/delivery-effects.d.ts +139 -0
  24. package/dist/delivery-effects.js +163 -0
  25. package/dist/delivery-operations.js +43 -68
  26. package/dist/delivery-primitives.d.ts +5 -0
  27. package/dist/delivery-primitives.js +3 -0
  28. package/dist/delivery.d.ts +1 -1
  29. package/dist/delivery.js +8 -7
  30. package/dist/help.js +4 -6
  31. package/dist/http-delivery.d.ts +16 -0
  32. package/dist/http-delivery.js +101 -0
  33. package/dist/inbox.js +3 -1
  34. package/dist/mcp.js +65 -63
  35. package/dist/model.d.ts +10 -6
  36. package/dist/model.js +20 -1
  37. package/dist/notifications.js +2 -0
  38. package/dist/operation-presentation.d.ts +1 -2
  39. package/dist/operation-presentation.js +12 -20
  40. package/dist/ports.d.ts +8 -14
  41. package/dist/presentation-operations.d.ts +15 -19
  42. package/dist/presentation-operations.js +47 -57
  43. package/dist/presentation.d.ts +2 -9
  44. package/dist/presentation.js +13 -48
  45. package/dist/receive.d.ts +66 -0
  46. package/dist/receive.js +54 -0
  47. package/dist/runtime.d.ts +0 -1
  48. package/dist/runtime.js +2 -3
  49. package/dist/sqlite-square.d.ts +3 -2
  50. package/dist/sqlite-square.js +83 -57
  51. package/dist/square-api.d.ts +1 -1
  52. package/dist/square-api.js +1 -1
  53. package/dist/square-core.d.ts +1 -9
  54. package/dist/square-domain.js +29 -36
  55. package/dist/square-facade.d.ts +25 -23
  56. package/dist/square-legacy-framing.d.ts +2 -0
  57. package/dist/square-legacy-framing.js +8 -0
  58. package/dist/square-migration.d.ts +3 -1
  59. package/dist/square-migration.js +46 -13
  60. package/dist/square-receive.d.ts +6 -0
  61. package/dist/square-receive.js +18 -0
  62. package/dist/square-sqlite.d.ts +5 -3
  63. package/dist/square-sqlite.js +100 -92
  64. package/dist/square-tools.d.ts +1 -0
  65. package/dist/square-tools.js +12 -12
  66. package/dist/views.d.ts +2 -3
  67. package/dist/views.js +14 -19
  68. package/dist/wakes.d.ts +0 -3
  69. package/dist/wakes.js +3 -5
  70. package/examples/symmetric-domains.mjs +250 -0
  71. package/guides/application-facade-delivery-evidence.md +5 -5
  72. package/guides/attention-primitive.md +32 -0
  73. package/guides/change-waits.md +2 -2
  74. package/guides/codex-delivery.md +1 -1
  75. package/guides/delivery-primitives.md +180 -0
  76. package/guides/mcp.md +1 -1
  77. package/guides/migration.md +12 -6
  78. package/guides/participant-ownership.md +1 -1
  79. package/guides/pi-tools.md +2 -2
  80. package/guides/primitive-catch-consolidation.md +5 -5
  81. package/guides/primitive-history-consolidation.md +2 -2
  82. package/guides/primitive-status-consolidation.md +3 -3
  83. package/guides/primitive-storage-consolidation.md +1 -1
  84. package/guides/receive-primitive.md +90 -0
  85. package/guides/row-runtime-delivery-evidence.md +1 -1
  86. package/guides/row-storage-design.md +17 -23
  87. package/guides/square-api.md +77 -10
  88. package/guides/symmetric-domains.md +92 -0
  89. package/package.json +18 -1
  90. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  91. package/plugins/claude/skills/square/SKILL.md +2 -6
  92. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  93. package/plugins/paseo/client/marble.ts +7 -0
  94. package/plugins/paseo/client/markdown.tsx +65 -9
  95. package/plugins/paseo/client/palette.ts +43 -20
  96. package/plugins/paseo/client/square-message.tsx +57 -48
  97. package/plugins/paseo/shared/message.ts +1 -2
  98. package/plugins/pi/square-pi.js +6 -4
  99. package/skills/square/SKILL.md +2 -6
package/dist/runtime.d.ts CHANGED
@@ -4,7 +4,6 @@ export declare const WATCH_HEARTBEAT_MS: number;
4
4
  export declare const WATCH_STALE_MS: number;
5
5
  export declare const THROTTLE_WINDOW_MS: number;
6
6
  export declare const UNREAD_BLOCK_GRACE_MS: number;
7
- export declare const HELD_WAIT_BUDGET_MS: number;
8
7
  export declare const FACT_PAGE_SIZE = 100;
9
8
  export declare function inSquareCount(facts: SquareRead): number;
10
9
  export declare function terminalStatusFor(current: ParticipantFact | undefined, hardCap: number | null, counts: ParticipantCounts): 'capped' | 'quorum' | undefined;
package/dist/runtime.js CHANGED
@@ -12,7 +12,6 @@ export const WATCH_HEARTBEAT_MS = parseIntegerEnv('SQUARE_WATCH_HEARTBEAT_MS', 6
12
12
  export const WATCH_STALE_MS = parseIntegerEnv('SQUARE_WATCH_STALE_MS', 180000);
13
13
  export const THROTTLE_WINDOW_MS = parseIntegerEnv('SQUARE_THROTTLE_WINDOW_MS', 60000);
14
14
  export const UNREAD_BLOCK_GRACE_MS = parseIntegerEnv('SQUARE_UNREAD_BLOCK_GRACE_MS', 90000);
15
- export const HELD_WAIT_BUDGET_MS = parseIntegerEnv('SQUARE_HELD_WAIT_BUDGET_MS', 10 * 60 * 1000);
16
15
  export const FACT_PAGE_SIZE = 100;
17
16
  export function inSquareCount(facts) { return facts.participantCounts().joined; }
18
17
  export function terminalStatusFor(current, hardCap, counts) {
@@ -33,8 +32,8 @@ export function readCursor(facts, name) {
33
32
  const boundary = facts.participant(name)?.lastJoinIndex ?? -1;
34
33
  const block = facts.directed(name, { afterIndex: boundary, unseenOnly: true }, { edge: 'oldest', limit: 1 }).items[0]?.index;
35
34
  const last = facts.activityHeads({
36
- afterIndex: boundary, ...(block === undefined ? {} : { beforeIndex: block }), actorRequired: true,
37
- kinds: ['join', 'done', 'say', 'hold', 'resume', 'listen', 'ignore', 'unlisten'],
35
+ afterIndex: boundary, ...(block === undefined ? {} : { beforeIndex: block }),
36
+ kinds: ['join', 'done', 'say', 'listen', 'ignore', 'unlisten'],
38
37
  }, { edge: 'newest', limit: 1 }).items[0]?.index;
39
38
  return Math.max(boundary, last ?? boundary);
40
39
  }
@@ -1,10 +1,9 @@
1
1
  import { type Owner } from './model.js';
2
- import type { HistoryQuery, HistoryResult, JoinOptions, JoinResult, LifecycleResult, ObservationOptions, OperationControl, Participant, SquareAtInput, SquareBuildInput, SquareLocation, StatusOptions, StatusResult, ParticipantsResult, RecoveryResult, DiscoveryOptions, InboxOptions, InboxResult, StreamOptions, StreamRecord, Activity, PerceivedActivity } from './square-facade.js';
2
+ import type { HistoryQuery, HistoryResult, JoinOptions, JoinResult, LifecycleResult, ObservationOptions, OperationControl, Participant, SquareAtInput, SquareBuildInput, SquareLocation, StatusOptions, StatusResult, ParticipantsResult, RecoveryResult, DiscoveryOptions, InboxOptions, InboxResult, StreamOptions, StreamRecord, Activity, PerceivedActivity, ChannelSendInput, ChannelSendResult } from './square-facade.js';
3
3
  export type * from './square-facade.js';
4
4
  export { createSquareResourceOwner } from './artifact.js';
5
5
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
6
6
  export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
7
- export { HELD_WAIT_BUDGET_MS } from './runtime.js';
8
7
  export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
9
8
  export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
10
9
  export declare function squareClock(env: NodeJS.ProcessEnv, clock?: () => number): () => number;
@@ -27,6 +26,8 @@ export declare class SqliteSquare {
27
26
  private contextObservation;
28
27
  private handle;
29
28
  private static readonly ParticipantHandle;
29
+ /** Direct messages use explicit recipients, not participant expression admission or body attention. */
30
+ send(input: ChannelSendInput, control?: OperationControl): Promise<ChannelSendResult>;
30
31
  participant(name: string, { owner }: {
31
32
  readonly owner: Owner;
32
33
  }): Promise<Participant>;
@@ -1,16 +1,17 @@
1
- import { formatActivityId } from './square-core.js';
1
+ import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
2
2
  import { assertCurrentSeat } from './square-domain.js';
3
3
  import { canonicalPath } from './canonical-path.js';
4
4
  import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
5
5
  import { squareLocation } from './square-location.js';
6
6
  import crypto from 'node:crypto';
7
7
  import { setTimeout as sleep } from 'node:timers/promises';
8
- import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.js';
9
- import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, coreUnlisten, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
10
- import { HELD_WAIT_BUDGET_MS, WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
8
+ import { activityMetadataHasContent, InternalSquareError, SquareError, isSquareError, nameKey, sameName, validateActivityMetadata, validateName } from './model.js';
9
+ import { coreDone, coreIgnore, coreListen, coreListening, coreUnlisten, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
10
+ import { WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
11
11
  import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
12
12
  import { expressionContext } from './activity-feed.js';
13
13
  import { expressHint } from './hints.js';
14
+ import { ATTENTION_BODY_MAX } from './attention-presentation.js';
14
15
  import { asSeatRef, participantAuthority, requireOwner, requireJoinName } from './seats.js';
15
16
  import { recoveryObservation, entryPresentation, snapshot, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, publicActivities, selectHistory, NON_READ, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
16
17
  import { sessionInboxCounts } from './inbox.js';
@@ -26,7 +27,6 @@ import { streamSquare } from './stream.js';
26
27
  export { createSquareResourceOwner } from './artifact.js';
27
28
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
28
29
  export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
29
- export { HELD_WAIT_BUDGET_MS } from './runtime.js';
30
30
  export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
31
31
  export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
32
32
  function checkControl(control) {
@@ -115,12 +115,12 @@ export class SqliteSquare {
115
115
  if (this.indexPath !== undefined)
116
116
  await rememberSquarePath(this.location, owner, this.indexPath).catch(() => undefined);
117
117
  }
118
- observation(standingCount, held, participant, now = this.clock()) {
119
- return { location: this.location, standingCount, held, now,
118
+ observation(standingCount, participant, now = this.clock()) {
119
+ return { location: this.location, standingCount, now,
120
120
  ...(participant === undefined ? {} : { participant }) };
121
121
  }
122
122
  async contextObservation(participant, control) {
123
- return (await this.artifact.read(facts => this.observation(inSquareCount(facts), facts.hold().active, participant), control?.signal)).value;
123
+ return (await this.artifact.read(facts => this.observation(inSquareCount(facts), participant), control?.signal)).value;
124
124
  }
125
125
  handle(seat) { return new SqliteSquare.ParticipantHandle(seat.participant, this, seat); }
126
126
  // The nested handle uses its resource's private dependencies without copying
@@ -134,6 +134,7 @@ export class SqliteSquare {
134
134
  this.owner = owner;
135
135
  this.seat = seat;
136
136
  }
137
+ get generation() { return this.seat.generation; }
137
138
  async express(bodyOrInput, optionsOrControl = {}, operationControl) {
138
139
  const square = this.owner;
139
140
  const input = typeof bodyOrInput === 'object' && bodyOrInput !== null ? bodyOrInput : undefined;
@@ -142,7 +143,6 @@ export class SqliteSquare {
142
143
  const control = input === undefined ? operationControl : optionsOrControl;
143
144
  let name = this.name, effect = 'unsent';
144
145
  let landedId;
145
- let heldWaitedMs = 0;
146
146
  try {
147
147
  checkControl(control);
148
148
  name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
@@ -167,12 +167,9 @@ export class SqliteSquare {
167
167
  // Decoding selected rows is storage work, not proof of safe admission refusal.
168
168
  const proposed = decideAct(facts, { name, body, force: options.force ?? false, metadata: options.metadata, now, mentions: options.mentions,
169
169
  ...(options.reach === undefined ? {} : { reach: options.reach }), ...(options.noMention === undefined ? {} : { noMention: options.noMention }), ...(reply === undefined ? {} : { reply }) });
170
- const holder = proposed.type === 'held' ? facts.hold().actor : undefined;
171
170
  const decision = rejectUnsentActivity(() => {
172
171
  if (proposed.type === 'blocked')
173
172
  throw new SquareError('behind', `${participantIdentity(name)} has pending activity`, { pending: proposed.activitySummaries.reduce((count, summary) => count + summary.count, 0) });
174
- if (proposed.type === 'held')
175
- throw new SquareError('held', 'The square is held', { ...(holder === undefined ? {} : { holder }), ...(proposed.reason === undefined ? {} : { holdReason: proposed.reason }) });
176
173
  if (proposed.type === 'capped')
177
174
  throw new SquareError('capped', `${participantIdentity(name)} reached the activity cap`);
178
175
  if (proposed.type === 'throttled')
@@ -181,7 +178,7 @@ export class SqliteSquare {
181
178
  throw new SquareError('bell_quota', `${participantIdentity(name)} cannot ring the bell yet`, { retryAfterMs: Math.max(1, proposed.nextAt - now) });
182
179
  return proposed;
183
180
  });
184
- return { stored: facts.land(decision.act), standingCount: inSquareCount(facts), held: facts.hold().active,
181
+ return { stored: facts.land(decision.act), standingCount: inSquareCount(facts),
185
182
  ownActivityCount: facts.participant(name).sayCount, hardCap: facts.header().hardCap,
186
183
  ...(decision.unmatchedMentions === undefined ? {} : { unmatchedMentions: decision.unmatchedMentions }),
187
184
  ...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) };
@@ -190,33 +187,20 @@ export class SqliteSquare {
190
187
  effect = 'landed';
191
188
  if (square.wakeTransport !== undefined)
192
189
  await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.deliveryLocation, activity: committed.stored.index, now }).catch(() => undefined);
193
- const { stored, standingCount, held, ownActivityCount, hardCap, ...metadata } = committed;
190
+ const { stored, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
194
191
  const hint = expressHint(ownActivityCount);
195
192
  return { activity: toPublicActivity(stored), ...metadata,
196
193
  ...(hint === undefined ? {} : { hint }), ...(context === undefined ? {} : { observation: {
197
- ...square.observation(standingCount, held, name, now), ownActivityCount, hardCap, context, participant: name,
194
+ ...square.observation(standingCount, name, now), ownActivityCount, hardCap, context, participant: name,
198
195
  } }) };
199
196
  }
200
197
  catch (error) {
201
198
  if (effect !== 'landed' && isSquareError(error) && error.facts?.activityUnsent === true)
202
199
  effect = 'unsent';
203
- if (effect === 'landed' || !isSquareError(error) || options.noWait || (error.code !== 'held' && error.code !== 'throttled'))
200
+ if (effect === 'landed' || !isSquareError(error) || options.noWait || error.code !== 'throttled')
204
201
  throw error;
205
- if (error.code === 'held' && heldWaitedMs >= HELD_WAIT_BUDGET_MS)
206
- throw error;
207
- control?.onProgress?.({ kind: 'waiting', reason: error.code,
208
- ...(error.code === 'throttled' ? { delayMs: error.facts?.retryAfterMs ?? 1 } : {}),
209
- ...(error.facts?.holder === undefined ? {} : { holder: error.facts.holder }),
210
- ...(error.facts?.holdReason === undefined ? {} : { holdReason: error.facts.holdReason }) });
211
- if (error.code === 'throttled')
212
- await sleep(error.facts?.retryAfterMs ?? 1, undefined, { signal: control?.signal });
213
- else {
214
- const started = Date.now();
215
- const observation = await square.artifact.read(facts => facts.hold().active, control?.signal);
216
- if (observation.value)
217
- await square.artifact.changed(observation.version, Math.max(1, HELD_WAIT_BUDGET_MS - heldWaitedMs), control?.signal);
218
- heldWaitedMs += Date.now() - started;
219
- }
202
+ control?.onProgress?.({ kind: 'waiting', reason: 'throttled', delayMs: error.facts?.retryAfterMs ?? 1 });
203
+ await sleep(error.facts?.retryAfterMs ?? 1, undefined, { signal: control?.signal });
220
204
  }
221
205
  }
222
206
  }
@@ -235,7 +219,7 @@ export class SqliteSquare {
235
219
  const act = verb === 'listen' ? coreListen(facts, name, target, now) : verb === 'ignore' ? coreIgnore(facts, name, target, now) : coreUnlisten(facts, name, target, now);
236
220
  const activity = act === undefined ? null : toPublicActivity(facts.land(act));
237
221
  return { activity, ...(!options.observe ? {} : { observation: {
238
- ...square.observation(inSquareCount(facts), facts.hold().active, name, now), participant: name,
222
+ ...square.observation(inSquareCount(facts), name, now), participant: name,
239
223
  target: activity?.target ?? target,
240
224
  } }) };
241
225
  }, control?.signal)).value;
@@ -247,7 +231,7 @@ export class SqliteSquare {
247
231
  const square = this.owner;
248
232
  return (await square.artifact.read(facts => {
249
233
  const name = resolveKnownName(facts, this.name);
250
- return { listening: coreListening(facts, name), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), facts.hold().active, name) }) };
234
+ return { listening: coreListening(facts, name), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name) }) };
251
235
  }, control?.signal)).value;
252
236
  }
253
237
  /** Receipt selection rechecks the lease inside the writer, not at its last heartbeat. */
@@ -283,7 +267,7 @@ export class SqliteSquare {
283
267
  }
284
268
  return { activities: view.activities, remaining: view.remaining, reason,
285
269
  ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
286
- ...square.observation(view.standingCount, view.held, view.participant, view.now), participant: view.participant,
270
+ ...square.observation(view.standingCount, view.participant, view.now), participant: view.participant,
287
271
  deliveryAvailable: view.deliveryAvailable,
288
272
  ownActivityCount: view.ownActivityCount, hardCap: view.hardCap,
289
273
  ...(presence === undefined ? { enrichmentUnavailable: true } : { presence }),
@@ -325,7 +309,8 @@ export class SqliteSquare {
325
309
  return await complete(undefined, 'already-active');
326
310
  leaseId = start.leaseId;
327
311
  control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced: start.replaced });
328
- let quietSince = square.clock(), wasHeld = false, nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
312
+ const quietSince = square.clock();
313
+ let nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
329
314
  while (true) {
330
315
  checkControl(control);
331
316
  // Arm before selecting: a commit during selection must not be missed.
@@ -337,21 +322,16 @@ export class SqliteSquare {
337
322
  return await complete(undefined, pulse.status);
338
323
  if (pulse.heartbeatAt !== undefined)
339
324
  nextHeartbeat = pulse.heartbeatAt + WATCH_HEARTBEAT_MS;
340
- if (pulse.type === 'sleep') {
341
- const caught = await this.catchPage(name, seat, selection, control, leaseId);
342
- if (caught === null)
343
- return await complete(undefined, 'replaced');
344
- if (caught.activities.length > 0)
345
- return await complete(caught);
346
- }
347
- if (pulse.type === 'held' || wasHeld)
348
- quietSince = square.clock();
349
- wasHeld = pulse.type === 'held';
350
- if (!wasHeld && square.clock() - quietSince >= idle) {
325
+ const caught = await this.catchPage(name, seat, selection, control, leaseId);
326
+ if (caught === null)
327
+ return await complete(undefined, 'replaced');
328
+ if (caught.activities.length > 0)
329
+ return await complete(caught);
330
+ if (square.clock() - quietSince >= idle) {
351
331
  const owns = await ownsWatchLease(square.artifact, seat, square.clock(), leaseId, control?.signal);
352
332
  return await complete(undefined, owns ? 'idle' : 'replaced');
353
333
  }
354
- const deadline = Math.min(nextHeartbeat, wasHeld ? Infinity : quietSince + idle);
334
+ const deadline = Math.min(nextHeartbeat, quietSince + idle);
355
335
  await square.artifact.changed(baseline, Math.max(0, deadline - square.clock()), control?.signal);
356
336
  }
357
337
  }
@@ -392,22 +372,68 @@ export class SqliteSquare {
392
372
  const square = this.owner, seat = this.seat;
393
373
  return (await square.artifact.transactAs(participantAuthority(seat), facts => facts.releaseDeliveryDeferral(asSeatRef(seat), id, square.clock()), control?.signal)).value;
394
374
  }
395
- async lifecycle(verb, body, options, control) {
396
- if (verb === 'done')
397
- validateDoneBody(body);
375
+ async done(body, options = {}, control) {
376
+ validateDoneBody(body);
398
377
  const square = this.owner;
399
378
  const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
400
379
  const seat = this.seat, now = square.clock();
401
380
  return (await square.artifact.transactAs(participantAuthority(seat), facts => {
402
- const stored = facts.land(verb === 'done' ? coreDone(facts, name, body ?? '', now) : verb === 'hold' ? coreHold(facts, name, body ?? '', now) : coreResume(facts, name, now));
403
- return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), facts.hold().active, name, now),
381
+ const stored = facts.land(coreDone(facts, name, body ?? '', now));
382
+ return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name, now),
404
383
  }) };
405
384
  }, control?.signal)).value;
406
385
  }
407
- hold(reason, options = {}, control) { return this.lifecycle('hold', reason, options, control); }
408
- resume(options = {}, control) { return this.lifecycle('resume', undefined, options, control); }
409
- done(body, options = {}, control) { return this.lifecycle('done', body, options, control); }
410
386
  };
387
+ /** Direct messages use explicit recipients, not participant expression admission or body attention. */
388
+ async send(input, control) {
389
+ checkControl(control);
390
+ const { from, to, body, truncate = ATTENTION_BODY_MAX } = input;
391
+ if (typeof from !== 'string')
392
+ throw new SquareError('invalid_args', 'Channel sender must be a name.');
393
+ validateName(from);
394
+ const targets = typeof to === 'string' ? [to] : to;
395
+ if (!Array.isArray(targets) || targets.length < 1 || targets.length > MAX_IDENTITY_SET_SIZE)
396
+ throw new SquareError('invalid_args', `Channel needs between 1 and ${MAX_IDENTITY_SET_SIZE} recipients.`);
397
+ const unique = [];
398
+ for (const target of targets) {
399
+ if (typeof target !== 'string')
400
+ throw new SquareError('invalid_args', 'Channel recipients must be names.');
401
+ validateName(target);
402
+ if (!unique.some(name => sameName(name, target)))
403
+ unique.push(target);
404
+ }
405
+ if (typeof body !== 'string' || body.trim() === '')
406
+ throw new SquareError('invalid_args', 'Channel body must be non-empty.');
407
+ if (truncate !== false && (!Number.isSafeInteger(truncate) || truncate < 1))
408
+ throw new SquareError('invalid_args', 'Channel truncate must be false or a positive integer.');
409
+ const fullBody = body.replace(/\r\n/g, '\n');
410
+ const preview = truncate !== false && fullBody.length > truncate
411
+ ? `${fullBody.slice(0, truncate).trimEnd()}… (truncated ${fullBody.length - truncate} chars)`
412
+ : undefined;
413
+ const delivery = preview === undefined ? { mode: 'direct' } : { mode: 'direct', preview };
414
+ const metadata = validateActivityMetadata(input.metadata);
415
+ const { stored, resolvedTo } = (await this.artifact.transactAs({ kind: 'send', actor: from }, facts => {
416
+ const standing = facts.roster().filter(p => p.joined), resolvedTo = [], missing = [];
417
+ for (const target of unique) {
418
+ const recipient = standing.find(p => sameName(p.name, target));
419
+ if (recipient === undefined || facts.seat(recipient.name)?.attachment !== 'attached')
420
+ missing.push(target);
421
+ else
422
+ resolvedTo.push(recipient.name);
423
+ }
424
+ if (missing.length > 0)
425
+ throw new SquareError('invalid_args', `Channel recipients are not standing: ${missing.join(', ')}`, {
426
+ reason: 'not_standing', targets: missing, activityUnsent: true,
427
+ });
428
+ return { stored: facts.land({ kind: 'say', actor: from, at: this.clock(), body: fullBody,
429
+ ...(activityMetadataHasContent(metadata) ? { metadata } : {}), delivery, mentions: resolvedTo }), resolvedTo };
430
+ }, control?.signal)).value;
431
+ if (this.wakeTransport !== undefined)
432
+ await deliverPending({ artifact: this.artifact, transport: this.wakeTransport, location: this.deliveryLocation,
433
+ activity: stored.index, now: stored.at }).catch(() => undefined);
434
+ return { id: formatActivityId(stored.index), at: stored.at, from: stored.actor, to: resolvedTo, body: preview ?? fullBody,
435
+ ...(stored.kind !== 'say' || stored.metadata === undefined ? {} : { metadata: stored.metadata }) };
436
+ }
411
437
  async participant(name, { owner }) {
412
438
  validateName(name);
413
439
  requireOwner(owner);
@@ -467,7 +493,7 @@ export class SqliteSquare {
467
493
  if (seat === undefined)
468
494
  throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
469
495
  return { participant: this.handle(seat), activity, kind,
470
- ...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), facts.hold().active, seat.participant, now),
496
+ ...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
471
497
  deliveryAvailable: seat.wakeAddress !== undefined } }) };
472
498
  }, control?.signal)).value;
473
499
  await this.publishSeat(owner);
@@ -535,7 +561,7 @@ export class SqliteSquare {
535
561
  if (indexes.includes(anchor))
536
562
  (presenceAnchors[anchor] ??= []).push(participant.name);
537
563
  }
538
- return { activities, observation: { ...this.observation(inSquareCount(facts), facts.hold().active), ...selected,
564
+ return { activities, observation: { ...this.observation(inSquareCount(facts)), ...selected,
539
565
  sayNumbers: facts.sayOrdinals(indexes), presenceAnchors } };
540
566
  }, control?.signal)).value;
541
567
  }
@@ -16,4 +16,4 @@ export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' |
16
16
  }): Promise<Participant | undefined>;
17
17
  export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
18
18
  export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
19
- export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HELD_WAIT_BUDGET_MS, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
19
+ export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
@@ -27,4 +27,4 @@ export async function selectOwnSeat(square, owner, name, options) {
27
27
  }
28
28
  export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
29
29
  export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
30
- export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HELD_WAIT_BUDGET_MS, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
30
+ export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
@@ -26,18 +26,10 @@ export type Act = {
26
26
  at?: number;
27
27
  body?: string;
28
28
  metadata?: import('./model.js').ActivityMetadata;
29
+ delivery?: import('./model.js').ActivityDelivery;
29
30
  mentions?: readonly Participant[];
30
31
  reach?: Reach;
31
32
  reply?: number;
32
- } | {
33
- kind: 'hold';
34
- actor?: Participant;
35
- at?: number;
36
- body?: string;
37
- } | {
38
- kind: 'resume';
39
- actor?: Participant;
40
- at?: number;
41
33
  } | {
42
34
  kind: 'read';
43
35
  actor: Participant;
@@ -1,35 +1,22 @@
1
- import { SquareError, formatActivityId, parseActivityId, sameName, validateOwner, validateName } from './model.js';
1
+ import { SquareError, formatActivityId, parseActivityId, sameName, validateActivityDelivery, validateOwner, validateName } from './model.js';
2
+ import { resolveAudience } from './attention.js';
2
3
  /** The single landing transition, shared by both adapters and explicit rebuild. */
3
4
  export function deriveLanding(activity, facts) {
4
- const audience = [];
5
- if (activity.kind === 'say') {
6
- const add = (requested, route) => {
7
- const recipient = facts.standing.find((item) => sameName(item.name, requested))?.name;
8
- if (recipient === undefined || sameName(recipient, activity.actor)
9
- || audience.some((item) => sameName(item.recipient, recipient)))
10
- return;
11
- if (activity.reach !== 'bell' && facts.incoming.some((item) => item.kind === 'ignore' && sameName(item.listener, recipient)))
12
- return;
13
- audience.push({ recipient, route });
14
- };
15
- if (activity.reach === 'bell') {
16
- for (const peer of facts.standing)
17
- add(peer.name, 'bell');
18
- }
19
- else {
20
- for (const mention of activity.mentions ?? [])
21
- add(mention, 'mention');
22
- if (facts.replyAuthor !== undefined)
23
- add(facts.replyAuthor, 'reply');
24
- for (const peer of facts.standing) {
25
- if (facts.incoming.some((item) => item.kind === 'listen' && sameName(item.listener, peer.name)))
26
- add(peer.name, 'attention');
27
- }
5
+ if (activity.kind === 'say' && activity.delivery?.mode === 'direct') {
6
+ const audience = [];
7
+ for (const mention of activity.mentions ?? []) {
8
+ const recipient = facts.standing.find(item => sameName(item.name, mention))?.name;
9
+ if (recipient !== undefined && !audience.some(item => sameName(item.recipient, recipient)))
10
+ audience.push({ recipient, route: 'mention' });
28
11
  }
12
+ return { activity, audience };
29
13
  }
14
+ const audience = activity.kind === 'say' ? resolveAudience({
15
+ actor: activity.actor, recipients: facts.standing.map((item) => item.name),
16
+ mentions: activity.mentions, replyAuthor: facts.replyAuthor, reach: activity.reach,
17
+ incoming: facts.incoming, sameIdentity: sameName,
18
+ }) : [];
30
19
  const actor = activity.actor;
31
- if (actor === undefined)
32
- return { activity, audience };
33
20
  const previous = facts.actor ?? { name: actor, firstIndex: activity.index, joined: false, done: false, sayCount: 0 };
34
21
  const participant = {
35
22
  ...previous,
@@ -57,7 +44,7 @@ export function deriveLanding(activity, facts) {
57
44
  }
58
45
  export function landingFacts(read, activity) {
59
46
  return {
60
- actor: activity.actor === undefined ? undefined : read.participant(activity.actor),
47
+ actor: read.participant(activity.actor),
61
48
  standing: activity.kind === 'say' ? read.roster().filter((item) => item.joined) : [],
62
49
  incoming: activity.kind === 'say' ? read.incomingAttention(activity.actor) : [],
63
50
  outgoing: activity.kind === 'listen' || activity.kind === 'ignore' || activity.kind === 'unlisten' ? read.attention(activity.actor) : [],
@@ -82,15 +69,15 @@ export function wrapSquareWrite(read, writes, authority) {
82
69
  throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
83
70
  const { applyLanding, ...mutations } = writes;
84
71
  const seated = authority.kind === 'participant' || authority.kind === 'host-cleanup';
85
- const expectedName = seated ? authority.seat?.participant : authority.participant;
86
- if (!['participant', 'host-cleanup', 'join', 'takeover'].includes(authority.kind) || typeof expectedName !== 'string')
72
+ const expectedName = seated ? authority.seat?.participant : authority.kind === 'send' ? authority.actor : authority.participant;
73
+ if (!['participant', 'host-cleanup', 'join', 'takeover', 'send'].includes(authority.kind) || typeof expectedName !== 'string')
87
74
  throw new SquareError('invalid_args', 'Artifact write authority is malformed.');
88
75
  validateName(expectedName);
89
76
  if (seated) {
90
77
  validateOwner(authority.seat.owner);
91
78
  validateGeneration(authority.seat.generation);
92
79
  }
93
- else {
80
+ else if (authority.kind !== 'send') {
94
81
  validateOwner(authority.owner);
95
82
  if (authority.kind === 'takeover')
96
83
  validateGeneration(authority.expectedGeneration);
@@ -122,7 +109,7 @@ export function wrapSquareWrite(read, writes, authority) {
122
109
  reconnectSeat = seat;
123
110
  }
124
111
  }
125
- else {
112
+ else if (authority.kind === 'takeover') {
126
113
  const current = read.seat(expectedName);
127
114
  if (current === undefined || current.generation !== authority.expectedGeneration)
128
115
  throw new SquareError('already_joined', 'The participant seat changed before takeover.');
@@ -136,7 +123,13 @@ export function wrapSquareWrite(read, writes, authority) {
136
123
  validateName(actor);
137
124
  if (!sameName(actor, expectedName))
138
125
  throw new SquareError('already_joined', 'Activity actor does not match the artifact authority.');
139
- if (authority.kind === 'participant') {
126
+ if (activity.kind === 'say')
127
+ validateActivityDelivery(activity.delivery);
128
+ if (authority.kind === 'send') {
129
+ if (activity.kind !== 'say' || activity.delivery?.mode !== 'direct' || activity.reach !== undefined || activity.reply !== undefined)
130
+ throw new SquareError('invalid_args', 'Send authority may only land direct messages without bell or reply.');
131
+ }
132
+ else if (authority.kind === 'participant') {
140
133
  assertSeat(authority.seat);
141
134
  if (activity.kind === 'join')
142
135
  throw new SquareError('invalid_args', 'A participant seat can only be re-established by join.');
@@ -172,7 +165,7 @@ export function wrapSquareWrite(read, writes, authority) {
172
165
  const stored = { ...activity, index };
173
166
  applyLanding(deriveLanding(stored, landingFacts(read, stored)));
174
167
  if (stored.kind === 'join') {
175
- if (seated)
168
+ if (authority.kind !== 'join' && authority.kind !== 'takeover')
176
169
  throw new SquareError('invalid_args', 'Seat authority cannot establish another seat.');
177
170
  const seat = {
178
171
  participant: stored.actor,
@@ -225,8 +218,8 @@ export function wrapSquareWrite(read, writes, authority) {
225
218
  },
226
219
  cancelThrough(seat, through) {
227
220
  assertSeat(seat);
228
- if (!Number.isSafeInteger(through) || through < 0 || through > read.header().lastActIndex || read.activity(through) === undefined)
229
- throw new SquareError('invalid_args', 'Cancellation boundary must name an existing activity at or below the current head.');
221
+ if (!Number.isSafeInteger(through) || through < 0 || through >= read.header().nextActIndex)
222
+ throw new SquareError('invalid_args', 'Cancellation boundary must be an assigned activity index below the allocator.');
230
223
  return mutations.cancelThrough(seat, through);
231
224
  },
232
225
  markSeen(name, indexes, at) {
@@ -1,19 +1,20 @@
1
1
  import type { DeliveryDeferral, MetadataMatch } from './delivery-deferral.js';
2
2
  export type { DeliveryDeferral, MetadataMatch, MetadataScalar } from './delivery-deferral.js';
3
3
  import type { ActivityId, Perception, Reach } from './square-core.js';
4
- import type { Owner, StoredAct, WakeAddress, ActivityMetadata, DirectedNotificationRoute } from './model.js';
4
+ import type { Owner, StoredAct, WakeAddress, ActivityMetadata, ActivityDelivery, DirectedNotificationRoute } from './model.js';
5
5
  import type { WakeTransportPort, SquareResourceOwner } from './ports.js';
6
6
  export type { ActivityId, Perception, Reach } from './square-core.js';
7
- export type { Owner, WakeAddress, ActivityMetadata, JsonValue, DirectedNotificationRoute } from './model.js';
8
- /** Indexed archive event, including read and historical actorless lifecycle records. */
7
+ export type { Owner, WakeAddress, ActivityMetadata, ActivityDelivery, JsonValue, DirectedNotificationRoute } from './model.js';
8
+ /** Indexed archive event, including read bookkeeping. */
9
9
  export type ArchiveRecord = Readonly<StoredAct>;
10
10
  export interface Activity {
11
11
  readonly id: ActivityId;
12
12
  readonly at: number;
13
- readonly kind: 'join' | 'say' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore' | 'unlisten';
13
+ readonly kind: 'join' | 'say' | 'done' | 'listen' | 'ignore' | 'unlisten';
14
14
  readonly actor: string;
15
15
  readonly body?: string;
16
16
  readonly metadata?: ActivityMetadata;
17
+ readonly delivery?: ActivityDelivery;
17
18
  readonly mentions: readonly string[];
18
19
  readonly reach?: Reach;
19
20
  readonly target?: string;
@@ -49,6 +50,22 @@ export interface ExpressResult {
49
50
  readonly hint?: string;
50
51
  readonly observation?: ExpressObservation;
51
52
  }
53
+ export type ChannelTruncate = number | false;
54
+ export interface ChannelSendInput {
55
+ readonly from: string;
56
+ readonly to: string | readonly string[];
57
+ readonly body: string;
58
+ readonly truncate?: ChannelTruncate;
59
+ readonly metadata?: ActivityMetadata;
60
+ }
61
+ export interface ChannelSendResult {
62
+ readonly id: ActivityId;
63
+ readonly at: number;
64
+ readonly from: string;
65
+ readonly to: readonly string[];
66
+ readonly body: string;
67
+ readonly metadata?: ActivityMetadata;
68
+ }
52
69
  export interface ListenerChangeResult {
53
70
  readonly activity: Activity | null;
54
71
  readonly observation?: ListenerObservation;
@@ -85,10 +102,8 @@ export interface CatchWaitStart {
85
102
  }
86
103
  export type OperationProgress = {
87
104
  readonly kind: 'waiting';
88
- readonly reason: 'held' | 'throttled';
105
+ readonly reason: 'throttled';
89
106
  readonly delayMs?: number;
90
- readonly holder?: string;
91
- readonly holdReason?: string;
92
107
  } | ({
93
108
  readonly kind: 'catch-started';
94
109
  readonly participant: string;
@@ -135,10 +150,6 @@ export interface SquareSnapshot {
135
150
  readonly actCount: number;
136
151
  readonly hardCap: number | null;
137
152
  readonly throttlePerMinute?: number;
138
- readonly held: {
139
- readonly by: string;
140
- readonly reason?: string;
141
- } | null;
142
153
  readonly participants: readonly Pick<ParticipantSummary, 'name' | 'presence' | 'listening'>[];
143
154
  delivered(name: string, id: ActivityId): boolean;
144
155
  }
@@ -176,6 +187,7 @@ export interface JoinResult {
176
187
  }
177
188
  export interface Participant {
178
189
  readonly name: string;
190
+ readonly generation: ActivityId;
179
191
  express(body: string | undefined, options?: ExpressOptions, control?: OperationControl): Promise<ExpressResult>;
180
192
  express(input: ExpressInput, control?: OperationControl): Promise<ExpressResult>;
181
193
  listen(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
@@ -183,8 +195,6 @@ export interface Participant {
183
195
  unlisten(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
184
196
  listening(options?: ObservationOptions, control?: OperationControl): Promise<ListeningResult>;
185
197
  catch(options?: CatchOptions, control?: OperationControl): Promise<CatchResult>;
186
- hold(reason?: string, options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
187
- resume(options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
188
198
  /** Leave without a message. Only an omitted or empty body is accepted. */
189
199
  done(body?: string, options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
190
200
  deferDelivery(input: Readonly<{
@@ -220,12 +230,6 @@ export interface StatusResult {
220
230
  readonly participants: readonly ParticipantSummary[];
221
231
  readonly now: number;
222
232
  readonly throttlePerMinute?: number;
223
- /** Non-null whenever held; by is present with an actor and omitted only for historical actorless holds. */
224
- readonly held: {
225
- readonly by?: string;
226
- readonly reason?: string;
227
- readonly at?: number;
228
- } | null;
229
233
  readonly latestActivity?: Activity;
230
234
  readonly observation?: StatusObservation;
231
235
  }
@@ -267,7 +271,6 @@ export interface InboxResult {
267
271
  export interface RecoveryResult {
268
272
  readonly participant?: string;
269
273
  readonly standingCount?: number;
270
- readonly held?: boolean;
271
274
  readonly ownActivityCount?: number;
272
275
  readonly hardCap?: number | null;
273
276
  readonly activitySummaries?: readonly UnreadActivitySummary[];
@@ -310,7 +313,6 @@ export interface CatchPresence {
310
313
  export interface SquareObservation {
311
314
  readonly location: string;
312
315
  readonly standingCount: number;
313
- readonly held: boolean;
314
316
  readonly now: number;
315
317
  readonly participant?: string;
316
318
  }
@@ -320,7 +322,7 @@ export interface JoinObservation extends SquareObservation {
320
322
  /** The receipt succeeded, but optional recent-history/context enrichment was unavailable. */
321
323
  readonly enrichmentUnavailable?: true;
322
324
  }
323
- export interface ExpressionObservation extends Pick<SquareObservation, 'standingCount' | 'held' | 'now'> {
325
+ export interface ExpressionObservation extends Pick<SquareObservation, 'standingCount' | 'now'> {
324
326
  readonly ownActivityCount: number;
325
327
  readonly hardCap: number | null;
326
328
  }
@@ -361,7 +363,7 @@ export interface ListenerObservation extends SquareObservation {
361
363
  readonly participant: string;
362
364
  readonly target: string;
363
365
  }
364
- export interface StatusObservation extends Omit<SquareObservation, 'now' | 'held'>, PresenceClassification {
366
+ export interface StatusObservation extends Omit<SquareObservation, 'now'>, PresenceClassification {
365
367
  readonly knownParticipantCount: number;
366
368
  readonly latestPerception?: Perception;
367
369
  readonly joins: readonly {
@@ -1,6 +1,8 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
2
  export declare const FORMAT4_ADDED_OBJECTS: ReadonlySet<string>;
3
3
  export declare const FORMAT4_REPLACED_OBJECTS: ReadonlySet<string>;
4
+ /** Historical activity framing only; no runtime activity decoder accepts these kinds. */
5
+ export declare function legacyActivitySchema(currentSchemaSql: string): string;
4
6
  /**
5
7
  * Read-only recognition of the exact row framing accepted by explicit migration.
6
8
  * It inspects identity, owned schema and journal mode only; it never reads row