@astrosheep/square 0.3.69 → 0.3.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/activity-feed.d.ts +4 -5
  2. package/dist/activity-feed.js +14 -10
  3. package/dist/artifact.d.ts +26 -33
  4. package/dist/artifact.js +346 -317
  5. package/dist/attention-presentation.js +2 -9
  6. package/dist/automatic-session.js +6 -6
  7. package/dist/boundary-presentation.js +3 -3
  8. package/dist/catch-decisions.d.ts +19 -12
  9. package/dist/catch-decisions.js +32 -68
  10. package/dist/claude-delivery.js +7 -7
  11. package/dist/claude-mod.js +18 -7
  12. package/dist/cli/catch-presentation.d.ts +7 -0
  13. package/dist/cli/catch-presentation.js +55 -0
  14. package/dist/cli/context.d.ts +1 -1
  15. package/dist/cli/context.js +5 -23
  16. package/dist/cli/express-presentation.d.ts +10 -0
  17. package/dist/cli/express-presentation.js +196 -0
  18. package/dist/cli/maintenance-commands.d.ts +4 -0
  19. package/dist/cli/maintenance-commands.js +29 -6
  20. package/dist/cli/observation-commands.d.ts +7 -5
  21. package/dist/cli/observation-commands.js +275 -262
  22. package/dist/cli/program.d.ts +1 -1
  23. package/dist/cli/program.js +43 -56
  24. package/dist/cli/registry.js +2 -1
  25. package/dist/cli/square-commands.d.ts +3 -9
  26. package/dist/cli/square-commands.js +134 -255
  27. package/dist/codex-queue.js +4 -1
  28. package/dist/decisions.d.ts +26 -74
  29. package/dist/decisions.js +190 -335
  30. package/dist/delivery-health.js +13 -22
  31. package/dist/delivery-operations.d.ts +0 -11
  32. package/dist/delivery-operations.js +44 -33
  33. package/dist/delivery.d.ts +33 -32
  34. package/dist/delivery.js +70 -81
  35. package/dist/harness-claude.js +1 -1
  36. package/dist/harness-codex.js +1 -1
  37. package/dist/harness-pi.js +1 -1
  38. package/dist/help.js +6 -1
  39. package/dist/inbox.d.ts +27 -0
  40. package/dist/inbox.js +55 -18
  41. package/dist/index.d.ts +2 -4
  42. package/dist/index.js +1 -1
  43. package/dist/list.d.ts +7 -1
  44. package/dist/list.js +49 -122
  45. package/dist/mcp.d.ts +1 -1
  46. package/dist/mcp.js +7 -37
  47. package/dist/model.d.ts +4 -44
  48. package/dist/model.js +1 -3
  49. package/dist/notifications.d.ts +1 -1
  50. package/dist/notifications.js +13 -7
  51. package/dist/open-square.d.ts +16 -2
  52. package/dist/open-square.js +40 -1
  53. package/dist/perception-projection.d.ts +6 -15
  54. package/dist/perception-projection.js +6 -26
  55. package/dist/{opencode-delivery.d.ts → plugins/opencode/delivery.d.ts} +1 -1
  56. package/dist/{opencode-delivery.js → plugins/opencode/delivery.js} +18 -18
  57. package/dist/{opencode.d.ts → plugins/opencode/index.d.ts} +2 -2
  58. package/dist/{opencode.js → plugins/opencode/index.js} +9 -9
  59. package/dist/ports.d.ts +156 -11
  60. package/dist/presentation-operations.d.ts +1 -1
  61. package/dist/presentation-operations.js +35 -44
  62. package/dist/presentation.d.ts +12 -19
  63. package/dist/presentation.js +33 -54
  64. package/dist/routes.d.ts +7 -14
  65. package/dist/routes.js +33 -62
  66. package/dist/runtime.d.ts +12 -39
  67. package/dist/runtime.js +33 -164
  68. package/dist/square-actions.d.ts +11 -21
  69. package/dist/square-actions.js +171 -271
  70. package/dist/square-application.d.ts +31 -55
  71. package/dist/square-application.js +637 -141
  72. package/dist/square-core.d.ts +3 -94
  73. package/dist/square-core.js +8 -353
  74. package/dist/square-domain.d.ts +24 -0
  75. package/dist/square-domain.js +77 -0
  76. package/dist/square-facade.d.ts +360 -9
  77. package/dist/square-migration.d.ts +24 -0
  78. package/dist/square-migration.js +211 -0
  79. package/dist/square-projections.d.ts +2 -42
  80. package/dist/square-projections.js +1 -37
  81. package/dist/square-sqlite.d.ts +59 -0
  82. package/dist/square-sqlite.js +702 -0
  83. package/dist/stream.d.ts +3 -10
  84. package/dist/stream.js +81 -52
  85. package/dist/views.d.ts +27 -95
  86. package/dist/views.js +217 -87
  87. package/dist/wake-eligibility.d.ts +15 -5
  88. package/dist/wake-eligibility.js +32 -16
  89. package/dist/wakes.d.ts +3 -3
  90. package/dist/wakes.js +31 -27
  91. package/guides/application-facade-delivery-evidence.md +200 -0
  92. package/guides/application-facade-design.md +95 -0
  93. package/guides/change-waits.md +4 -4
  94. package/guides/migration.md +77 -0
  95. package/guides/pi-native-delivery.md +1 -1
  96. package/guides/primitive-catch-consolidation.md +85 -0
  97. package/guides/primitive-history-consolidation.md +76 -0
  98. package/guides/primitive-status-consolidation.md +87 -0
  99. package/guides/primitive-storage-consolidation.md +128 -0
  100. package/guides/row-runtime-delivery-evidence.md +160 -0
  101. package/guides/row-storage-design.md +567 -0
  102. package/package.json +5 -7
  103. package/{claude-plugin → plugins/claude}/.claude-plugin/plugin.json +1 -1
  104. package/{codex-plugin → plugins/codex}/.codex-plugin/plugin.json +1 -1
  105. package/plugins/opencode/delivery.ts +149 -0
  106. package/plugins/opencode/index.ts +136 -0
  107. package/plugins/paseo/OVERVIEW.md +14 -0
  108. package/plugins/paseo/README.md +54 -0
  109. package/plugins/paseo/client/square-message.tsx +106 -0
  110. package/plugins/paseo/client/transform.ts +24 -0
  111. package/plugins/paseo/index.client.tsx +19 -0
  112. package/plugins/paseo/package.json +19 -0
  113. package/plugins/paseo/paseo-plugin.json +7 -0
  114. package/plugins/paseo/shared/message.ts +29 -0
  115. package/{extensions → plugins/pi}/square-pi.js +8 -5
  116. package/dist/activity.d.ts +0 -14
  117. package/dist/activity.js +0 -248
  118. package/dist/square-file-adapter.d.ts +0 -26
  119. package/dist/square-file-adapter.js +0 -85
  120. package/dist/square-state.d.ts +0 -6
  121. package/dist/square-state.js +0 -138
  122. package/dist/square-storage.d.ts +0 -14
  123. package/dist/square-storage.js +0 -188
  124. package/dist/square-wiring.d.ts +0 -33
  125. package/dist/square-wiring.js +0 -109
  126. package/dist/watch.d.ts +0 -8
  127. package/dist/watch.js +0 -252
  128. /package/{claude-plugin → plugins/claude}/hooks/hooks.json +0 -0
  129. /package/{claude-plugin → plugins/claude}/hooks/register.js +0 -0
  130. /package/{claude-plugin → plugins/claude}/skills/square/SKILL.md +0 -0
  131. /package/{claude-plugin → plugins/claude}/types.d.ts +0 -0
  132. /package/{codex-plugin → plugins/codex}/hooks/hooks.json +0 -0
@@ -1,5 +1,11 @@
1
1
  import type { ActivityId, Perception, Reach } from './square-core.js';
2
+ import type { StoredAct } from './model.js';
2
3
  import type { HostLedgerPort, WakeTransportPort, DeliveryResult } from './ports.js';
4
+ import type { DiagnoseResult } from './artifact.js';
5
+ import type { MigrationOutcome } from './square-migration.js';
6
+ export type { ActivityId, Perception, Reach } from './square-core.js';
7
+ /** The existing indexed domain event, including read and historical actorless lifecycle records. */
8
+ export type ArchiveRecord = Readonly<StoredAct>;
3
9
  export interface Activity {
4
10
  readonly id: ActivityId;
5
11
  readonly at: number;
@@ -24,12 +30,13 @@ export interface ExpressOptions {
24
30
  export interface ExpressResult {
25
31
  readonly activity: Activity;
26
32
  readonly delivery?: DeliveryResult;
27
- /** The replied-to author was not standing when the reply landed, so no notification reached them. */
33
+ /** The replied-to author was not standing when the reply landed. */
28
34
  readonly replyAuthorNotHere?: string;
29
35
  }
30
- export interface ListenerChangeResult {
36
+ export interface ListenerChangeData {
31
37
  readonly activity: Activity | null;
32
38
  }
39
+ export type ListenerChangeResult = ListenerChangeData;
33
40
  export interface CatchOptions {
34
41
  readonly id?: ActivityId;
35
42
  readonly idle?: number;
@@ -43,13 +50,21 @@ export interface CatchResult {
43
50
  readonly idleExpired: boolean;
44
51
  readonly remaining: number;
45
52
  }
46
- export interface OperationProgress {
53
+ export interface CatchWaitStart {
54
+ readonly idleMs: number;
55
+ readonly replacementRequested: boolean;
56
+ readonly replaced: boolean;
57
+ }
58
+ export type OperationProgress = {
47
59
  readonly kind: 'waiting';
48
60
  readonly reason: 'held' | 'throttled';
49
61
  readonly delayMs?: number;
50
62
  readonly holder?: string;
51
63
  readonly holdReason?: string;
52
- }
64
+ } | ({
65
+ readonly kind: 'catch-started';
66
+ readonly participant: string;
67
+ } & CatchWaitStart);
53
68
  export interface OperationControl {
54
69
  readonly signal?: AbortSignal;
55
70
  readonly onProgress?: (progress: OperationProgress) => void;
@@ -94,14 +109,10 @@ export interface OpenOptions {
94
109
  }
95
110
  export interface SquareAtInput extends SquareSource, OpenOptions {
96
111
  }
97
- export interface SquareBuildInput extends SquareSource {
112
+ export interface SquareBuildInput extends SquareSource, OpenOptions {
98
113
  markdown: string;
99
114
  hardCap?: number | null;
100
115
  throttlePerMinute?: number;
101
- clock?: () => number;
102
- hostLedger?: HostLedgerPort;
103
- wakeTransport?: WakeTransportPort;
104
- env?: NodeJS.ProcessEnv;
105
116
  }
106
117
  /** Optional fence for owner-scoped lifecycle actions. */
107
118
  export interface OwnershipFenceOptions {
@@ -120,3 +131,343 @@ export interface Participant {
120
131
  /** Leave without a message. Only an omitted or empty body is accepted. */
121
132
  done(body?: string, control?: OperationControl): Promise<ExpressResult>;
122
133
  }
134
+ export interface SquareApplicationContext extends Readonly<OpenOptions> {
135
+ readonly cwd: string;
136
+ readonly env: NodeJS.ProcessEnv;
137
+ readonly squarePath?: string;
138
+ readonly participant?: string;
139
+ }
140
+ export interface ApplicationOutcome<Data, Projection> {
141
+ readonly data: Data;
142
+ readonly projection: Projection;
143
+ }
144
+ export interface ApplicationFailureDetail {
145
+ readonly operation: 'express' | 'join' | 'catch' | 'history' | 'other';
146
+ readonly subject?: 'caller' | 'filter' | 'recipient';
147
+ readonly participant?: string;
148
+ readonly expressEffect?: 'unsent' | 'landed' | 'unknown';
149
+ readonly landedId?: ActivityId;
150
+ readonly historyBounds?: {
151
+ readonly maxLimit: number;
152
+ readonly totalMatches: number;
153
+ };
154
+ }
155
+ export interface ApplicationError extends Error {
156
+ readonly operation?: ApplicationFailureDetail;
157
+ }
158
+ export interface SquareContextProjection {
159
+ readonly location: string;
160
+ readonly standingCount: number;
161
+ readonly held: boolean;
162
+ readonly now: number;
163
+ readonly participant?: string;
164
+ readonly automaticDeliveryAvailable: boolean;
165
+ }
166
+ export interface BuildApplicationInput {
167
+ readonly markdown: string;
168
+ readonly hardCap?: number | null;
169
+ readonly throttlePerMinute?: number;
170
+ readonly template?: string;
171
+ readonly force?: boolean;
172
+ }
173
+ export interface BuildApplicationData {
174
+ readonly location: string;
175
+ readonly hardCap: number | null;
176
+ readonly throttlePerMinute?: number;
177
+ }
178
+ export interface JoinApplicationOptions {
179
+ readonly takeover?: boolean;
180
+ readonly last?: number;
181
+ }
182
+ export interface JoinApplicationData {
183
+ readonly participant: string;
184
+ readonly activity: Activity | null;
185
+ readonly kind: 'joined' | 'reconnected' | 'taken-over';
186
+ /** Known joined-or-done participants, NOT the current standing count. */
187
+ readonly participantCount: number;
188
+ }
189
+ export interface ExpressApplicationOptions extends ExpressOptions {
190
+ readonly noWait?: boolean;
191
+ readonly requireExplicitReach?: boolean;
192
+ }
193
+ export interface ExpressApplicationData {
194
+ readonly activity: Activity;
195
+ readonly waited: boolean;
196
+ readonly replyAuthorNotHere?: string;
197
+ }
198
+ export interface CatchApplicationOptions extends CatchOptions {
199
+ readonly replace?: boolean;
200
+ }
201
+ export type CatchTerminalStatus = 'already-active' | 'replaced' | 'capped' | 'quorum';
202
+ export type CatchStopStatus = CatchTerminalStatus | 'empty-now' | 'stale';
203
+ export interface CatchApplicationData extends CatchResult {
204
+ readonly status?: CatchTerminalStatus;
205
+ }
206
+ export interface HistoryApplicationQuery extends HistoryQuery {
207
+ readonly since?: number;
208
+ readonly at?: readonly ActivityId[];
209
+ readonly contextBefore?: number;
210
+ readonly contextAfter?: number;
211
+ }
212
+ export interface HistoryApplicationData {
213
+ readonly activities: readonly Activity[];
214
+ }
215
+ export interface ListeningData {
216
+ readonly listening: readonly string[];
217
+ }
218
+ export interface LifecycleData {
219
+ readonly activity: Activity;
220
+ }
221
+ export type ParticipantPresence = 'never-joined' | 'active' | 'watching' | 'done';
222
+ /** Canonical public participant fields; status excludes never-joined roster entries. */
223
+ export interface ParticipantSummary {
224
+ readonly name: string;
225
+ readonly state: 'active' | 'done' | 'not joined';
226
+ readonly listening: readonly string[];
227
+ readonly activityCount: number;
228
+ readonly lastActiveAt?: number;
229
+ readonly presence: ParticipantPresence;
230
+ readonly unreadActivityCount: number;
231
+ readonly pendingMentionCount: number;
232
+ }
233
+ export interface StatusApplicationData {
234
+ readonly hardCap: number | null;
235
+ readonly activeCount: number;
236
+ readonly doneCount: number;
237
+ readonly participants: readonly ParticipantSummary[];
238
+ readonly now: number;
239
+ readonly throttlePerMinute?: number;
240
+ readonly hold: {
241
+ readonly by?: string;
242
+ readonly reason?: string;
243
+ readonly at?: number;
244
+ } | null;
245
+ readonly latestActivity?: Activity;
246
+ }
247
+ export interface ParticipantsApplicationData {
248
+ readonly participants: readonly ParticipantSummary[];
249
+ }
250
+ export type DiagnosisApplicationData = DiagnoseResult;
251
+ export type MigrationApplicationData = MigrationOutcome;
252
+ export interface DiscoveryApplicationOptions {
253
+ readonly depth?: number;
254
+ readonly limit?: number;
255
+ readonly after?: string;
256
+ }
257
+ export interface SquareListItem {
258
+ readonly path: string;
259
+ readonly lastActiveAt: number;
260
+ readonly context: readonly string[];
261
+ readonly participants: readonly string[];
262
+ readonly activities: number;
263
+ }
264
+ export interface DiscoveryApplicationData {
265
+ readonly items: readonly SquareListItem[];
266
+ readonly stopped: boolean;
267
+ /** All discovered squares before applying the cursor or page limit. */
268
+ readonly total: number;
269
+ readonly remaining: number;
270
+ readonly next?: string;
271
+ }
272
+ export interface DiscoveryProjection {
273
+ readonly cwd: string;
274
+ readonly now: number;
275
+ readonly depth: number;
276
+ readonly limit: number;
277
+ }
278
+ export interface InboxApplicationOptions {
279
+ readonly limit?: number;
280
+ }
281
+ export interface InboxMembershipSummary {
282
+ readonly name: string;
283
+ readonly squarePath: string;
284
+ readonly pending: number;
285
+ }
286
+ export interface InboxApplicationData {
287
+ readonly rows: readonly InboxMembershipSummary[];
288
+ readonly total: number;
289
+ }
290
+ export interface InboxProjection {
291
+ readonly sessionId: string;
292
+ readonly now: number;
293
+ readonly limit: number;
294
+ }
295
+ export interface RecoveryApplicationData {
296
+ readonly participant?: string;
297
+ readonly standingCount?: number;
298
+ readonly held?: boolean;
299
+ readonly holdReason?: string;
300
+ readonly ownActivityCount?: number;
301
+ readonly hardCap?: number | null;
302
+ readonly roster?: readonly string[];
303
+ readonly activitySummaries?: readonly UnreadActivitySummary[];
304
+ }
305
+ export type RecoveryProjection = RecoveryApplicationData & Partial<SquareContextProjection>;
306
+ export type StreamStart = {
307
+ readonly kind: 'tail';
308
+ readonly last: number;
309
+ } | {
310
+ readonly kind: 'after';
311
+ readonly after: number;
312
+ };
313
+ export interface StreamApplicationOptions {
314
+ readonly recipient?: string;
315
+ readonly start?: StreamStart;
316
+ }
317
+ export type StreamApplicationRecord = ArchiveRecord & {
318
+ readonly seq: number;
319
+ readonly square: string;
320
+ readonly route?: string;
321
+ };
322
+ export interface UnreadActivityPreview {
323
+ readonly number: number;
324
+ readonly act: Extract<ArchiveRecord, {
325
+ kind: 'say';
326
+ }>;
327
+ readonly perception: Perception;
328
+ }
329
+ export interface UnreadActivitySummary {
330
+ readonly name: string;
331
+ readonly count: number;
332
+ readonly latestActivityAgeMs: number;
333
+ readonly previews: readonly UnreadActivityPreview[];
334
+ }
335
+ /** Selected-row observations. participantCount in these existing concern DTOs is a standing count. */
336
+ export interface RecoveryObservation {
337
+ readonly name: string;
338
+ readonly roster: readonly string[];
339
+ readonly activitySummaries: readonly UnreadActivitySummary[];
340
+ readonly participantCount: number;
341
+ readonly held: boolean;
342
+ readonly holdReason?: string;
343
+ readonly ownActivityCount: number;
344
+ readonly hardCap: number | null;
345
+ }
346
+ export interface EntryPresentation {
347
+ readonly joined: boolean;
348
+ readonly joinedBefore: boolean;
349
+ readonly scene: string;
350
+ readonly context: string;
351
+ readonly joinContext: string;
352
+ readonly recentActivities: readonly ArchiveRecord[];
353
+ readonly perceptions: Readonly<Record<number, Perception>>;
354
+ readonly sayNumbers: Readonly<Record<number, number>>;
355
+ readonly participantCount: number;
356
+ readonly held: boolean;
357
+ }
358
+ export interface HistoryContinuation {
359
+ readonly direction: 'before' | 'after';
360
+ readonly cursor: ActivityId;
361
+ readonly limit: number;
362
+ }
363
+ export interface ListPresentation {
364
+ readonly context: readonly string[];
365
+ readonly participants: readonly string[];
366
+ readonly activities: number;
367
+ }
368
+ export interface PresenceClassification {
369
+ /** Names in canonical data, watching first, then recent signs of life; no presentation clipping. */
370
+ readonly orderedHereNames: readonly string[];
371
+ readonly lingeringCount: number;
372
+ readonly classificationByName: Readonly<Record<string, 'here' | 'lingering' | 'done' | 'not-joined'>>;
373
+ }
374
+ export interface CatchPresence {
375
+ readonly participants: readonly ParticipantSummary[];
376
+ readonly now: number;
377
+ }
378
+ export interface StreamProjection {
379
+ readonly activities: readonly {
380
+ readonly activity: ArchiveRecord;
381
+ readonly route?: string;
382
+ }[];
383
+ readonly cursor: number;
384
+ readonly hasMore: boolean;
385
+ }
386
+ export interface EntryProjection extends SquareContextProjection, EntryPresentation {
387
+ }
388
+ export interface ExpressionObservation extends Pick<SquareContextProjection, 'standingCount' | 'held' | 'now'> {
389
+ readonly ownActivityCount: number;
390
+ readonly hardCap: number | null;
391
+ }
392
+ export interface ExpressProjection extends SquareContextProjection, ExpressionObservation {
393
+ readonly participant: string;
394
+ readonly pendingPublic: readonly ArchiveRecord[];
395
+ readonly sayNumbers: Readonly<Record<number, number>>;
396
+ readonly perceptions: Readonly<Record<number, Perception>>;
397
+ readonly reentry?: Activity;
398
+ readonly delivery?: DeliveryResult;
399
+ }
400
+ export interface CatchProjection extends SquareContextProjection, ExpressionObservation {
401
+ readonly participant: string;
402
+ readonly sayNumbers: Readonly<Record<number, number>>;
403
+ /** Optional nearby presence is not a commit-time selection fact. */
404
+ readonly presence?: CatchPresence;
405
+ readonly stop?: CatchStopStatus;
406
+ readonly waitStarted?: boolean;
407
+ readonly replaced?: boolean;
408
+ readonly enrichmentUnavailable?: true;
409
+ }
410
+ export interface HistoryProjection extends SquareContextProjection {
411
+ readonly activities: readonly ArchiveRecord[];
412
+ readonly sayNumbers: Readonly<Record<number, number>>;
413
+ readonly presenceAnchors: Readonly<Record<number, readonly string[]>>;
414
+ readonly totalMatches: number;
415
+ /** Ordinary terminal history visibility and stable continuation are owner policy. */
416
+ readonly visibleActivities: readonly ArchiveRecord[];
417
+ readonly continuation?: HistoryContinuation;
418
+ }
419
+ export interface ListenerProjection extends SquareContextProjection {
420
+ readonly participant: string;
421
+ readonly target: string;
422
+ readonly requestedTarget: string;
423
+ }
424
+ export interface EventPresentation {
425
+ readonly activity: ArchiveRecord;
426
+ readonly participantCount: number;
427
+ readonly held: boolean;
428
+ }
429
+ export interface LifecycleProjection extends SquareContextProjection, EventPresentation {
430
+ readonly delivery?: DeliveryResult;
431
+ }
432
+ export interface StatusProjection extends SquareContextProjection, PresenceClassification {
433
+ /** Full roster size, including never-joined names omitted from status data. */
434
+ readonly knownParticipantCount: number;
435
+ readonly latestActNumber?: number;
436
+ readonly latestPerception?: Perception;
437
+ readonly joins: readonly {
438
+ readonly activity: ArchiveRecord;
439
+ readonly rejoined: boolean;
440
+ }[];
441
+ readonly joinCount: number;
442
+ readonly attentionNames: readonly string[];
443
+ readonly viewer: string;
444
+ }
445
+ export interface ParticipantsProjection extends SquareContextProjection, PresenceClassification {
446
+ }
447
+ export interface SquareApplication {
448
+ now(): number;
449
+ build(input: BuildApplicationInput, control?: OperationControl): Promise<ApplicationOutcome<BuildApplicationData, SquareContextProjection>>;
450
+ join(options?: JoinApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<JoinApplicationData, EntryProjection>>;
451
+ express(body: string, options?: ExpressApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<ExpressApplicationData, ExpressProjection>>;
452
+ catch(options?: CatchApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<CatchApplicationData, CatchProjection>>;
453
+ history(query?: HistoryApplicationQuery, control?: OperationControl): Promise<ApplicationOutcome<HistoryApplicationData, HistoryProjection>>;
454
+ listen(target: string, control?: OperationControl): Promise<ApplicationOutcome<ListenerChangeData, ListenerProjection>>;
455
+ ignore(target: string, control?: OperationControl): Promise<ApplicationOutcome<ListenerChangeData, ListenerProjection>>;
456
+ listening(control?: OperationControl): Promise<ApplicationOutcome<ListeningData, SquareContextProjection>>;
457
+ hold(reason?: string, control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection>>;
458
+ resume(control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection>>;
459
+ done(body?: string, control?: OperationControl): Promise<ApplicationOutcome<LifecycleData, LifecycleProjection>>;
460
+ status(viewer?: string, control?: OperationControl): Promise<ApplicationOutcome<StatusApplicationData, StatusProjection>>;
461
+ participants(control?: OperationControl): Promise<ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection>>;
462
+ diagnose(control?: OperationControl): Promise<ApplicationOutcome<DiagnosisApplicationData, Partial<SquareContextProjection>>>;
463
+ migrate(control?: OperationControl): Promise<ApplicationOutcome<MigrationApplicationData, Partial<SquareContextProjection>>>;
464
+ discover(options: DiscoveryApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<DiscoveryApplicationData, DiscoveryProjection>>;
465
+ inbox(sessionId: string, options?: InboxApplicationOptions, control?: OperationControl): Promise<ApplicationOutcome<InboxApplicationData, InboxProjection>>;
466
+ recovery(control?: OperationControl): Promise<ApplicationOutcome<RecoveryApplicationData, RecoveryProjection>>;
467
+ stream(options: StreamApplicationOptions, control?: OperationControl): AsyncIterable<StreamApplicationRecord>;
468
+ }
469
+ export type JoinApplicationResult = JoinApplicationData;
470
+ export type ExpressApplicationResult = ExpressApplicationData;
471
+ export type ApplicationStatus = StatusApplicationData;
472
+ export type ApplicationStatusProjection = StatusProjection;
473
+ export type ApplicationParticipants = ParticipantsApplicationData;
@@ -0,0 +1,24 @@
1
+ import { type SquareState } from './square-sqlite.js';
2
+ /** Validate persisted v1 JSON; row codecs share activity and lease acceptance. */
3
+ export declare function validateSquareState(value: unknown): SquareState;
4
+ export interface MigrationCounts {
5
+ readonly activities: number;
6
+ readonly receipts: number;
7
+ readonly leases: number;
8
+ readonly routes: number;
9
+ }
10
+ export interface MigrationOutcome {
11
+ readonly status: 'migrated' | 'already-format-2';
12
+ /** Participants standing after the conversion, for the CLI header. */
13
+ readonly standing: number;
14
+ readonly counts?: MigrationCounts;
15
+ }
16
+ /**
17
+ * Explicit in-place conversion of a normally persisted version 1 artifact.
18
+ *
19
+ * The legacy snapshot is read, validated and written here and nowhere else in
20
+ * production; the row schema, codec and landing transition come from the driver.
21
+ * One transaction: preflight framing, the same recheck under the lock, the
22
+ * preserved authority, the derived facts, the format flip and a single COMMIT.
23
+ */
24
+ export declare function migrateSquareFile(location: string, signal?: AbortSignal): Promise<MigrationOutcome>;
@@ -0,0 +1,211 @@
1
+ import { inSqliteTransaction, openExistingSquareDatabase } from './artifact.js';
2
+ import { canonicalPath } from './canonical-path.js';
3
+ import { isWakeRouteKind } from './model.js';
4
+ import { parseActivityId } from './square-core.js';
5
+ import { APPLICATION_ID, V1_SNAPSHOT_TABLE, createSqliteDriver, initializeSqlite, invalidArtifact, validateLegacyFraming, validateSqliteHeader, validateStoredActivity, validateWatchLease, } from './square-sqlite.js';
6
+ function isObject(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ function hasExactKeys(value, required, optional = []) {
10
+ const allowed = new Set([...required, ...optional]);
11
+ return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => allowed.has(key));
12
+ }
13
+ function isFiniteNumber(value) {
14
+ return typeof value === 'number' && Number.isFinite(value);
15
+ }
16
+ function isNonNegativeInteger(value) {
17
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
18
+ }
19
+ function isStringArray(value) {
20
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
21
+ }
22
+ function validateRecord(value, item) {
23
+ return isObject(value) && Object.entries(value).every(([key, candidate]) => key.length > 0 && item(candidate));
24
+ }
25
+ function validateRuntime(value) {
26
+ return isObject(value) && hasExactKeys(value, ['nextActIndex', 'observations', 'leases'])
27
+ && isNonNegativeInteger(value.nextActIndex)
28
+ && validateRecord(value.observations, (candidate) => isObject(candidate)
29
+ && Object.entries(candidate).every(([id, observation]) => parseActivityId(id) !== undefined && isObject(observation) && hasExactKeys(observation, ['state', 'at']) && observation.state === 'seen' && isFiniteNumber(observation.at)))
30
+ && validateRecord(value.leases, (candidate) => {
31
+ try {
32
+ validateWatchLease(candidate);
33
+ return true;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ });
39
+ }
40
+ function validateAssignedRuntimeReferences(runtime) {
41
+ for (const observations of Object.values(runtime.observations)) {
42
+ for (const id of Object.keys(observations)) {
43
+ const index = parseActivityId(id);
44
+ if (index === undefined)
45
+ return 'malformed';
46
+ if (index >= runtime.nextActIndex)
47
+ return 'future';
48
+ }
49
+ }
50
+ return 'ok';
51
+ }
52
+ function validateActs(value) {
53
+ if (!Array.isArray(value))
54
+ return false;
55
+ let previous = -1;
56
+ try {
57
+ for (const candidate of value) {
58
+ const act = validateStoredActivity(candidate);
59
+ if (act.index <= previous)
60
+ return false;
61
+ previous = act.index;
62
+ }
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ return true;
68
+ }
69
+ function nonFiniteRoutePath(value, path) {
70
+ if (typeof value === 'number')
71
+ return Number.isFinite(value) ? undefined : path;
72
+ if (Array.isArray(value)) {
73
+ for (let index = 0; index < value.length; index += 1) {
74
+ const found = nonFiniteRoutePath(value[index], `${path}[${index}]`);
75
+ if (found !== undefined)
76
+ return found;
77
+ }
78
+ return undefined;
79
+ }
80
+ if (isObject(value)) {
81
+ for (const [key, item] of Object.entries(value)) {
82
+ const found = nonFiniteRoutePath(item, `${path}.${key}`);
83
+ if (found !== undefined)
84
+ return found;
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ /** Refuse exponent overflow with its legacy path before JSON could coerce it. */
90
+ function assertFiniteRouteNumbers(routes) {
91
+ for (let index = 0; index < routes.length; index += 1) {
92
+ const found = nonFiniteRoutePath(routes[index], `routes[${index}]`);
93
+ if (found !== undefined)
94
+ throw invalidArtifact(`${found} is not a finite number.`);
95
+ }
96
+ }
97
+ /** Validate persisted v1 JSON; row codecs share activity and lease acceptance. */
98
+ export function validateSquareState(value) {
99
+ if (isObject(value) && Array.isArray(value.routes))
100
+ assertFiniteRouteNumbers(value.routes);
101
+ if (!isObject(value) || !hasExactKeys(value, ['hardCap', 'preamble', 'warmup', 'acts', 'runtime'], ['throttlePerMinute', 'routes'])
102
+ || !(value.hardCap === null || (Number.isSafeInteger(value.hardCap) && value.hardCap > 0))
103
+ || (value.throttlePerMinute !== undefined && (!Number.isSafeInteger(value.throttlePerMinute) || value.throttlePerMinute <= 0))
104
+ || !isStringArray(value.preamble) || !isStringArray(value.warmup) || !validateActs(value.acts)
105
+ || (value.routes !== undefined && (!Array.isArray(value.routes) || !value.routes.every((route) => isObject(route) && isWakeRouteKind(route.kind)
106
+ && typeof route.location === 'string' && typeof route.participant === 'string' && typeof route.sessionId === 'string'
107
+ && typeof route.channel === 'string' && isObject(route.address) && Object.values(route.address).every((item) => typeof item === 'string')
108
+ && typeof route.updatedAt === 'number')))
109
+ || !validateRuntime(value.runtime))
110
+ throw invalidArtifact('snapshot schema is malformed.');
111
+ const historyBoundary = value.acts.at(-1)?.index ?? -1;
112
+ if (value.runtime.nextActIndex <= historyBoundary)
113
+ throw invalidArtifact('nextActIndex is behind the activity history.');
114
+ const references = validateAssignedRuntimeReferences(value.runtime);
115
+ if (references === 'malformed')
116
+ throw invalidArtifact('snapshot schema is malformed.');
117
+ if (references === 'future')
118
+ throw invalidArtifact('runtime references an unassigned activity index.');
119
+ return value;
120
+ }
121
+ function identity(database) {
122
+ const row = database.prepare('SELECT application_id, user_version FROM pragma_application_id, pragma_user_version').get();
123
+ return { applicationId: row.application_id, userVersion: row.user_version };
124
+ }
125
+ function standing(database) {
126
+ return createSqliteDriver(database).roster().filter((participant) => participant.joined).length;
127
+ }
128
+ /** The snapshot row, decoded only by the legacy validator. */
129
+ function decodeLegacySnapshot(database) {
130
+ const rows = database.prepare(`SELECT id, revision, state FROM ${V1_SNAPSHOT_TABLE}`).all();
131
+ if (rows.length !== 1 || rows[0].id !== 1 || !Number.isSafeInteger(rows[0].revision) || rows[0].revision < 0)
132
+ throw invalidArtifact('snapshot row is malformed.');
133
+ if (typeof rows[0].state !== 'string')
134
+ throw invalidArtifact('snapshot state is malformed.');
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(rows[0].state);
138
+ }
139
+ catch {
140
+ throw invalidArtifact('snapshot state is not valid JSON.');
141
+ }
142
+ return { revision: rows[0].revision, state: validateSquareState(parsed) };
143
+ }
144
+ function counts(state) {
145
+ let receipts = 0;
146
+ for (const observations of Object.values(state.runtime.observations))
147
+ receipts += Object.keys(observations).length;
148
+ return {
149
+ activities: state.acts.length,
150
+ receipts,
151
+ leases: Object.keys(state.runtime.leases).length,
152
+ routes: state.routes?.length ?? 0,
153
+ };
154
+ }
155
+ /**
156
+ * Explicit in-place conversion of a normally persisted version 1 artifact.
157
+ *
158
+ * The legacy snapshot is read, validated and written here and nowhere else in
159
+ * production; the row schema, codec and landing transition come from the driver.
160
+ * One transaction: preflight framing, the same recheck under the lock, the
161
+ * preserved authority, the derived facts, the format flip and a single COMMIT.
162
+ */
163
+ export async function migrateSquareFile(location, signal) {
164
+ const squarePath = await canonicalPath(location);
165
+ const database = openExistingSquareDatabase(squarePath);
166
+ try {
167
+ // Owned framing, read-only, before any pragma or DDL touches the artifact.
168
+ // Every preflight statement reads one snapshot taken in the helper's
169
+ // retryable stage: a transient writer lock is waited out like any other
170
+ // acquisition, and a migrator that commits while we look cannot move the
171
+ // artifact between the identity read and the schema reads.
172
+ let alreadyConverted;
173
+ const preflight = await inSqliteTransaction(database, false, () => {
174
+ const before = identity(database);
175
+ if (before.applicationId === APPLICATION_ID && before.userVersion === 1) {
176
+ validateLegacyFraming(database, APPLICATION_ID, squarePath);
177
+ return;
178
+ }
179
+ // A row artifact means another migrator finished here; anything else is
180
+ // refused by the row header itself.
181
+ validateSqliteHeader(database, squarePath);
182
+ alreadyConverted = { status: 'already-format-2', standing: standing(database) };
183
+ }, () => alreadyConverted, signal);
184
+ if (preflight !== undefined)
185
+ return preflight;
186
+ database.exec('PRAGMA busy_timeout = 0; PRAGMA synchronous = FULL');
187
+ const result = {};
188
+ await inSqliteTransaction(database, true, () => {
189
+ // Another migration may have finished between preflight and this lock.
190
+ const locked = identity(database);
191
+ if (locked.applicationId !== APPLICATION_ID || locked.userVersion !== 1) {
192
+ // A valid row artifact is done already; anything else is refused here.
193
+ validateSqliteHeader(database, squarePath);
194
+ return;
195
+ }
196
+ validateLegacyFraming(database, APPLICATION_ID, squarePath);
197
+ result.converted = true;
198
+ }, () => {
199
+ if (result.converted !== true)
200
+ return;
201
+ const legacy = decodeLegacySnapshot(database);
202
+ initializeSqlite(database, legacy.state, legacy.revision);
203
+ database.exec(`DROP TABLE ${V1_SNAPSHOT_TABLE}; PRAGMA user_version = 2`);
204
+ result.outcome = { status: 'migrated', standing: standing(database), counts: counts(legacy.state) };
205
+ }, signal);
206
+ return result.outcome ?? { status: 'already-format-2', standing: standing(database) };
207
+ }
208
+ finally {
209
+ database.close();
210
+ }
211
+ }