@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,17 +1,16 @@
1
- import type { SquareBackends } from './square-backend.js';
2
1
  import type { DeliveryDeferral, MetadataMatch } from './delivery-deferral.js';
3
2
  export type { DeliveryDeferral, MetadataMatch, MetadataScalar } from './delivery-deferral.js';
4
3
  import type { ActivityId, Perception, Reach } from './square-core.js';
5
- import type { Body, StoredAct, WakeAddress, ActivityMetadata } from './model.js';
6
- import type { WakeTransportPort, DeliveryResult, SquareResourceOwner } from './ports.js';
4
+ import type { Owner, StoredAct, WakeAddress, ActivityMetadata, DirectedNotificationRoute } from './model.js';
5
+ import type { WakeTransportPort, SquareResourceOwner } from './ports.js';
7
6
  export type { ActivityId, Perception, Reach } from './square-core.js';
8
- export type { Body, WakeAddress, ActivityMetadata, JsonValue } from './model.js';
9
- /** Indexed archive event, including read and historical actorless lifecycle records. */
7
+ export type { Owner, WakeAddress, ActivityMetadata, JsonValue, DirectedNotificationRoute } from './model.js';
8
+ /** Indexed archive event, including read bookkeeping. */
10
9
  export type ArchiveRecord = Readonly<StoredAct>;
11
10
  export interface Activity {
12
11
  readonly id: ActivityId;
13
12
  readonly at: number;
14
- readonly kind: 'join' | 'say' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore' | 'unlisten';
13
+ readonly kind: 'join' | 'say' | 'done' | 'listen' | 'ignore' | 'unlisten';
15
14
  readonly actor: string;
16
15
  readonly body?: string;
17
16
  readonly metadata?: ActivityMetadata;
@@ -22,6 +21,7 @@ export interface Activity {
22
21
  }
23
22
  export interface PerceivedActivity extends Activity {
24
23
  readonly perception: Perception;
24
+ readonly via?: DirectedNotificationRoute;
25
25
  }
26
26
  /** Request extra facts for rendering, without changing operation semantics. */
27
27
  export interface ObservationOptions {
@@ -39,12 +39,8 @@ export interface ExpressOptions extends ObservationOptions {
39
39
  export interface ExpressInput extends ExpressOptions {
40
40
  readonly body?: string;
41
41
  }
42
- export interface ActivityResult {
42
+ export interface ExpressResult {
43
43
  readonly activity: Activity;
44
- readonly delivery?: DeliveryResult;
45
- }
46
- export interface ExpressResult extends ActivityResult {
47
- readonly waited: boolean;
48
44
  /** The replied-to author was not standing when the reply landed. */
49
45
  readonly replyAuthorNotHere?: string;
50
46
  /** Body @name tokens with no standing match at landing; never stored on the activity. */
@@ -61,8 +57,9 @@ export interface ListeningResult {
61
57
  readonly listening: readonly string[];
62
58
  readonly observation?: SquareObservation;
63
59
  }
64
- export interface LifecycleResult extends ActivityResult {
65
- readonly observation?: LifecycleObservation;
60
+ export interface LifecycleResult {
61
+ readonly activity: Activity;
62
+ readonly observation?: SquareObservation;
66
63
  }
67
64
  export interface CatchOptions extends ObservationOptions {
68
65
  readonly id?: ActivityId;
@@ -72,12 +69,11 @@ export interface CatchOptions extends ObservationOptions {
72
69
  readonly limit?: number;
73
70
  readonly replace?: boolean;
74
71
  }
72
+ export type CatchReason = 'drained' | 'idle' | 'capped' | 'quorum' | 'replaced' | 'already-active';
75
73
  export interface CatchResult {
76
74
  readonly activities: readonly PerceivedActivity[];
77
- readonly consumedThrough: ActivityId | null;
78
- readonly idleExpired: boolean;
79
75
  readonly remaining: number;
80
- readonly status?: CatchTerminalStatus;
76
+ readonly reason: CatchReason;
81
77
  /** Occasional participant-facing nudge; not part of any activity. */
82
78
  readonly hint?: string;
83
79
  readonly observation?: CatchObservation;
@@ -89,10 +85,8 @@ export interface CatchWaitStart {
89
85
  }
90
86
  export type OperationProgress = {
91
87
  readonly kind: 'waiting';
92
- readonly reason: 'held' | 'throttled';
88
+ readonly reason: 'throttled';
93
89
  readonly delayMs?: number;
94
- readonly holder?: string;
95
- readonly holdReason?: string;
96
90
  } | ({
97
91
  readonly kind: 'catch-started';
98
92
  readonly participant: string;
@@ -134,32 +128,17 @@ export interface HistoryResult {
134
128
  readonly activities: readonly Activity[];
135
129
  readonly observation?: HistoryObservation;
136
130
  }
137
- export interface ParticipantStatus {
138
- readonly name: string;
139
- readonly state: 'joined' | 'done';
140
- readonly consumedThrough: ActivityId | null;
141
- readonly watching: boolean;
142
- readonly listening: readonly string[];
143
- }
144
131
  export interface SquareSnapshot {
145
132
  readonly context: string;
146
133
  readonly actCount: number;
147
134
  readonly hardCap: number | null;
148
135
  readonly throttlePerMinute?: number;
149
- readonly held: {
150
- readonly by: string;
151
- readonly reason?: string;
152
- } | null;
153
- readonly participants: readonly ParticipantStatus[];
136
+ readonly participants: readonly Pick<ParticipantSummary, 'name' | 'presence' | 'listening'>[];
154
137
  delivered(name: string, id: ActivityId): boolean;
155
138
  }
156
139
  export interface OpenOptions {
157
140
  readonly clock?: () => number;
158
- readonly wakeTransport?: WakeTransportPort;
159
- /** Explicit execution coordinate resolved by an adapter. */
160
- readonly body?: Body;
161
- /** Optional wake address attached to this body. */
162
- readonly wakeAddress?: WakeAddress;
141
+ readonly wakeTransport?: WakeTransportPort | false;
163
142
  }
164
143
  export interface SquareLocation {
165
144
  readonly path?: string;
@@ -167,8 +146,6 @@ export interface SquareLocation {
167
146
  readonly env?: NodeJS.ProcessEnv;
168
147
  }
169
148
  export interface SquareAtInput extends SquareLocation, OpenOptions {
170
- readonly backends?: SquareBackends;
171
- readonly participant?: string;
172
149
  /** A caller-owned connection; closing this Square never closes its owner. */
173
150
  readonly resources?: SquareResourceOwner;
174
151
  }
@@ -180,6 +157,8 @@ export interface SquareBuildInput extends SquareAtInput {
180
157
  readonly force?: boolean;
181
158
  }
182
159
  export interface JoinOptions extends ObservationOptions {
160
+ readonly owner: Owner;
161
+ readonly wakeAddress?: WakeAddress;
183
162
  readonly takeover?: boolean;
184
163
  readonly last?: number;
185
164
  }
@@ -187,8 +166,6 @@ export interface JoinResult {
187
166
  readonly participant: Participant;
188
167
  readonly activity: Activity | null;
189
168
  readonly kind: 'joined' | 'reconnected' | 'taken-over';
190
- /** Known joined-or-done participants, not the current standing count. */
191
- readonly participantCount: number;
192
169
  readonly observation?: JoinObservation;
193
170
  }
194
171
  export interface Participant {
@@ -200,9 +177,6 @@ export interface Participant {
200
177
  unlisten(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
201
178
  listening(options?: ObservationOptions, control?: OperationControl): Promise<ListeningResult>;
202
179
  catch(options?: CatchOptions, control?: OperationControl): Promise<CatchResult>;
203
- history(query?: HistoryQuery, control?: OperationControl): Promise<HistoryResult>;
204
- hold(reason?: string, options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
205
- resume(options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
206
180
  /** Leave without a message. Only an omitted or empty body is accepted. */
207
181
  done(body?: string, options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
208
182
  deferDelivery(input: Readonly<{
@@ -212,12 +186,15 @@ export interface Participant {
212
186
  renewDeliveryDeferral(id: string, durationMs: number, control?: OperationControl): Promise<DeliveryDeferral | null>;
213
187
  releaseDeliveryDeferral(id: string, control?: OperationControl): Promise<boolean>;
214
188
  }
215
- export type CatchTerminalStatus = 'already-active' | 'replaced' | 'capped' | 'quorum';
216
- export type CatchStopStatus = CatchTerminalStatus | 'empty-now' | 'stale';
217
- export type ParticipantPresence = 'never-joined' | 'active' | 'watching' | 'done';
189
+ export interface HeldSeat {
190
+ readonly name: string;
191
+ readonly generation: ActivityId;
192
+ readonly attachment: 'attached' | 'detached';
193
+ }
194
+ export type ParticipantPresence = 'never-joined' | 'active' | 'watching' | 'away' | 'done';
218
195
  export interface ParticipantSummary {
219
196
  readonly name: string;
220
- readonly state: 'active' | 'done' | 'not joined';
197
+ readonly deliveryAvailable: boolean;
221
198
  readonly listening: readonly string[];
222
199
  readonly activityCount: number;
223
200
  readonly lastActiveAt?: number;
@@ -232,16 +209,9 @@ export interface StatusResult {
232
209
  /** Present when a viewer resolves; an empty list means they listen to nobody. */
233
210
  readonly listening?: readonly string[];
234
211
  readonly hardCap: number | null;
235
- readonly activeCount: number;
236
- readonly doneCount: number;
237
212
  readonly participants: readonly ParticipantSummary[];
238
213
  readonly now: number;
239
214
  readonly throttlePerMinute?: number;
240
- readonly hold: {
241
- readonly by?: string;
242
- readonly reason?: string;
243
- readonly at?: number;
244
- } | null;
245
215
  readonly latestActivity?: Activity;
246
216
  readonly observation?: StatusObservation;
247
217
  }
@@ -283,11 +253,8 @@ export interface InboxResult {
283
253
  export interface RecoveryResult {
284
254
  readonly participant?: string;
285
255
  readonly standingCount?: number;
286
- readonly held?: boolean;
287
- readonly holdReason?: string;
288
256
  readonly ownActivityCount?: number;
289
257
  readonly hardCap?: number | null;
290
- readonly roster?: readonly string[];
291
258
  readonly activitySummaries?: readonly UnreadActivitySummary[];
292
259
  }
293
260
  export type StreamStart = {
@@ -295,90 +262,70 @@ export type StreamStart = {
295
262
  readonly last: number;
296
263
  } | {
297
264
  readonly kind: 'after';
298
- readonly after: number;
265
+ readonly after: ActivityId;
299
266
  };
300
267
  export interface StreamOptions {
301
268
  readonly recipient?: string;
302
269
  readonly start?: StreamStart;
303
270
  }
304
- export type StreamRecord = ArchiveRecord & {
305
- readonly seq: number;
306
- readonly square: string;
307
- readonly route?: string;
308
- };
309
- export interface UnreadActivityPreview {
310
- readonly number: number;
311
- readonly act: Extract<ArchiveRecord, {
312
- kind: 'say';
313
- }>;
314
- readonly perception: Perception;
315
- }
271
+ export type StreamRecord = Activity | PerceivedActivity;
316
272
  export interface UnreadActivitySummary {
317
273
  readonly name: string;
318
274
  readonly count: number;
319
275
  readonly latestActivityAgeMs: number;
320
- readonly previews: readonly UnreadActivityPreview[];
276
+ readonly preview: Extract<ArchiveRecord, {
277
+ kind: 'say';
278
+ }>;
321
279
  }
322
280
  export interface EntryPresentation {
323
281
  readonly joinedBefore: boolean;
324
282
  readonly scene: string;
325
283
  readonly joinContext: string;
326
- readonly recentActivities: readonly ArchiveRecord[];
327
- readonly perceptions: Readonly<Record<number, Perception>>;
328
- readonly sayNumbers: Readonly<Record<number, number>>;
329
- }
330
- export interface ListPresentation {
331
- readonly context: readonly string[];
332
- readonly participants: readonly string[];
333
- readonly activities: number;
284
+ readonly recentActivities: readonly PerceivedActivity[];
334
285
  }
335
286
  export interface PresenceClassification {
336
287
  readonly orderedHereNames: readonly string[];
337
288
  readonly lingeringCount: number;
338
- readonly classificationByName: Readonly<Record<string, 'here' | 'lingering' | 'done' | 'not-joined'>>;
289
+ readonly classificationByName: Readonly<Record<string, 'here' | 'lingering' | 'away' | 'done' | 'not-joined'>>;
339
290
  }
340
291
  export interface CatchPresence {
341
292
  readonly participants: readonly ParticipantSummary[];
342
293
  readonly now: number;
343
294
  }
344
- export interface StreamProjection {
345
- readonly activities: readonly {
346
- readonly activity: ArchiveRecord;
347
- readonly route?: string;
348
- }[];
349
- readonly cursor: number;
350
- readonly hasMore: boolean;
351
- }
352
295
  export interface SquareObservation {
353
296
  readonly location: string;
354
297
  readonly standingCount: number;
355
- readonly held: boolean;
356
298
  readonly now: number;
357
299
  readonly participant?: string;
358
- readonly automaticDeliveryAvailable: boolean;
359
300
  }
360
301
  export interface JoinObservation extends SquareObservation {
302
+ readonly deliveryAvailable: boolean;
361
303
  readonly entry?: EntryPresentation;
362
304
  /** The receipt succeeded, but optional recent-history/context enrichment was unavailable. */
363
305
  readonly enrichmentUnavailable?: true;
364
306
  }
365
- export interface ExpressionObservation extends Pick<SquareObservation, 'standingCount' | 'held' | 'now'> {
307
+ export interface ExpressionObservation extends Pick<SquareObservation, 'standingCount' | 'now'> {
366
308
  readonly ownActivityCount: number;
367
309
  readonly hardCap: number | null;
368
310
  }
311
+ export interface ExpressContext {
312
+ readonly activities: readonly {
313
+ readonly id: ActivityId;
314
+ readonly actor: string;
315
+ readonly preview: string;
316
+ readonly truncated: boolean;
317
+ }[];
318
+ /** Unread activities outside this preview, not a consumption cursor. */
319
+ readonly remaining: number;
320
+ }
369
321
  export interface ExpressObservation extends SquareObservation, ExpressionObservation {
370
322
  readonly participant: string;
371
- readonly pendingPublic: readonly ArchiveRecord[];
372
- readonly sayNumbers: Readonly<Record<number, number>>;
373
- readonly perceptions: Readonly<Record<number, Perception>>;
323
+ readonly context: ExpressContext;
374
324
  }
375
325
  export interface CatchObservation extends SquareObservation, ExpressionObservation {
326
+ readonly deliveryAvailable: boolean;
376
327
  readonly participant: string;
377
- readonly sayNumbers: Readonly<Record<number, number>>;
378
328
  readonly presence?: CatchPresence;
379
- readonly stop?: CatchStopStatus;
380
- readonly waitStarted?: boolean;
381
- readonly replaced?: boolean;
382
329
  readonly enrichmentUnavailable?: true;
383
330
  }
384
331
  export interface HistoryContinuation {
@@ -397,17 +344,12 @@ export interface HistoryObservation extends SquareObservation {
397
344
  export interface ListenerObservation extends SquareObservation {
398
345
  readonly participant: string;
399
346
  readonly target: string;
400
- readonly requestedTarget: string;
401
- }
402
- export interface LifecycleObservation extends SquareObservation {
403
- readonly activity: ArchiveRecord;
404
347
  }
405
- export interface StatusObservation extends SquareObservation, PresenceClassification {
348
+ export interface StatusObservation extends Omit<SquareObservation, 'now'>, PresenceClassification {
406
349
  readonly knownParticipantCount: number;
407
- readonly latestActNumber?: number;
408
350
  readonly latestPerception?: Perception;
409
351
  readonly joins: readonly {
410
- readonly activity: ArchiveRecord;
352
+ readonly activity: Activity;
411
353
  readonly rejoined: boolean;
412
354
  }[];
413
355
  readonly joinCount: number;
@@ -1,24 +1,16 @@
1
1
  import { type VersionObserver } from './file-changes.js';
2
- import { type Body } from './model.js';
3
- /** A rebuildable discovery hint: the body that may have used an artifact and its path.
4
- * It is deliberately not a participant, generation, ownership, suppression, or evidence row. */
2
+ import { type Owner } from './model.js';
3
+ /** A rebuildable discovery hint: the owner that may have used an artifact and its path. */
5
4
  export interface SquareIndexEntry {
6
5
  readonly location: string;
7
- readonly body: Body;
6
+ readonly owner: Owner;
8
7
  }
9
8
  export interface SquareIndexQuery {
10
- readonly body?: Body;
11
- readonly channel?: string;
12
- readonly session?: string;
13
- readonly agent?: string;
9
+ readonly owner?: Owner;
14
10
  }
15
- /** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
16
11
  export declare function squareIndexPath(env?: NodeJS.ProcessEnv): string;
17
- /** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
18
12
  export declare function indexedSquareEntries(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly SquareIndexEntry[]>;
19
13
  export declare function indexedSquarePaths(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly string[]>;
20
14
  /** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
21
- export declare function rememberSquarePath(location: string, body: Body, index: string): Promise<boolean>;
22
- /** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
15
+ export declare function rememberSquarePath(location: string, owner: Owner, index: string): Promise<boolean>;
23
16
  export declare function observeSquareIndex(env?: NodeJS.ProcessEnv): Promise<VersionObserver<string>>;
24
- export declare function bodyKey(body: Body): string;
@@ -5,8 +5,7 @@ import { randomUUID } from 'node:crypto';
5
5
  import { withFileLock } from './file-lock.js';
6
6
  import { canonicalPath } from './canonical-path.js';
7
7
  import { observeFileMetadata } from './file-changes.js';
8
- import { sameBody, validateBody } from './model.js';
9
- /** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
8
+ import { validateOwner } from './model.js';
10
9
  export function squareIndexPath(env = process.env) {
11
10
  const configured = env.SQUARE_INDEX?.trim();
12
11
  return path.resolve(configured ?? path.join(os.homedir(), '.square', 'index.ndjsonl'));
@@ -18,24 +17,17 @@ function validEntry(value) {
18
17
  if (value === null || typeof value !== 'object' || Array.isArray(value))
19
18
  return undefined;
20
19
  const row = value;
21
- if (typeof row.location !== 'string' || !path.isAbsolute(row.location)
22
- || row.body === null || typeof row.body !== 'object' || Array.isArray(row.body))
20
+ if (typeof row.location !== 'string' || !path.isAbsolute(row.location) || typeof row.owner !== 'string')
23
21
  return undefined;
24
- const raw = row.body;
25
- const body = {
26
- channel: raw.channel,
27
- session: raw.session,
28
- ...(raw.agent === undefined ? {} : { agent: raw.agent }),
29
- };
30
22
  try {
31
- validateBody(body);
23
+ validateOwner(row.owner);
32
24
  }
33
25
  catch {
34
26
  return undefined;
35
27
  }
36
- if (Object.keys(raw).some((key) => !['channel', 'session', 'agent'].includes(key)))
28
+ if (Object.keys(row).some((key) => !['location', 'owner'].includes(key)))
37
29
  return undefined;
38
- return { location: row.location, body };
30
+ return { location: row.location, owner: row.owner };
39
31
  }
40
32
  async function readEntries(index) {
41
33
  let content;
@@ -56,25 +48,13 @@ async function readEntries(index) {
56
48
  if (entry !== undefined)
57
49
  entries.push(entry);
58
50
  }
59
- catch {
60
- // The index is disposable discovery input. A malformed line is ignored rather than
61
- // becoming a second authority or preventing access to valid entries beside it.
62
- }
51
+ catch { /* Disposable discovery input. */ }
63
52
  }
64
53
  return entries;
65
54
  }
66
55
  function matches(entry, query) {
67
- if (query.body !== undefined && !sameBody(entry.body, query.body))
68
- return false;
69
- if (query.channel !== undefined && entry.body.channel !== query.channel)
70
- return false;
71
- if (query.session !== undefined && entry.body.session !== query.session)
72
- return false;
73
- if (query.agent !== undefined && entry.body.agent !== query.agent)
74
- return false;
75
- return true;
56
+ return query.owner === undefined || entry.owner === query.owner;
76
57
  }
77
- /** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
78
58
  export async function indexedSquareEntries(query = {}, env = process.env) {
79
59
  const entries = await readEntries(squareIndexPath(env));
80
60
  const result = [];
@@ -89,11 +69,11 @@ export async function indexedSquareEntries(query = {}, env = process.env) {
89
69
  catch {
90
70
  continue;
91
71
  }
92
- const key = JSON.stringify([location, entry.body.channel, entry.body.session, entry.body.agent ?? null]);
72
+ const key = JSON.stringify([location, entry.owner]);
93
73
  if (seen.has(key))
94
74
  continue;
95
75
  seen.add(key);
96
- result.push({ location, body: entry.body });
76
+ result.push({ location, owner: entry.owner });
97
77
  }
98
78
  return result;
99
79
  }
@@ -101,15 +81,15 @@ export async function indexedSquarePaths(query = {}, env = process.env) {
101
81
  return [...new Set((await indexedSquareEntries(query, env)).map((entry) => entry.location))];
102
82
  }
103
83
  /** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
104
- export async function rememberSquarePath(location, body, index) {
105
- validateBody(body);
84
+ export async function rememberSquarePath(location, owner, index) {
85
+ validateOwner(owner);
106
86
  const canonical = await canonicalPath(location);
107
87
  await fs.mkdir(path.dirname(index), { recursive: true });
108
88
  return await withFileLock(lockPath(index), { retryMs: 10, skipIfBusy: true }, async () => {
109
89
  const entries = await readEntries(index);
110
- if (entries.some((entry) => entry.location === canonical && sameBody(entry.body, body)))
90
+ if (entries.some((entry) => entry.location === canonical && entry.owner === owner))
111
91
  return false;
112
- const next = [...entries, { location: canonical, body }];
92
+ const next = [...entries, { location: canonical, owner }];
113
93
  const temporary = `${index}.${process.pid}.${randomUUID()}.tmp`;
114
94
  try {
115
95
  await fs.writeFile(temporary, next.map((entry) => JSON.stringify(entry)).join('\n') + '\n', { flag: 'wx', mode: 0o600 });
@@ -121,11 +101,6 @@ export async function rememberSquarePath(location, body, index) {
121
101
  return true;
122
102
  }) ?? false;
123
103
  }
124
- /** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
125
104
  export function observeSquareIndex(env = process.env) {
126
105
  return observeFileMetadata(squareIndexPath(env));
127
106
  }
128
- export function bodyKey(body) {
129
- validateBody(body);
130
- return JSON.stringify([body.channel, body.session, body.agent ?? null]);
131
- }
@@ -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
@@ -15,6 +15,14 @@ CREATE INDEX route_session ON routes(session_json, position);`;
15
15
  function normalizedSql(sql) {
16
16
  return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1');
17
17
  }
18
+ /** Historical activity framing only; no runtime activity decoder accepts these kinds. */
19
+ export function legacyActivitySchema(currentSchemaSql) {
20
+ return currentSchemaSql
21
+ .replace("kind IN ('join','done','say','read','listen','ignore','unlisten')", "kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')")
22
+ .replace(' actor_json TEXT COLLATE BINARY NOT NULL,', ' actor_json TEXT COLLATE BINARY,')
23
+ .replace(' actor_key_json TEXT COLLATE BINARY NOT NULL,', ' actor_key_json TEXT COLLATE BINARY,')
24
+ .replace(" CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))", " CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),\n CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),\n CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))");
25
+ }
18
26
  function legacyRowSchema(currentSchemaSql, version) {
19
27
  const statements = currentSchemaSql.split(';').map((statement) => statement.trim()).filter(Boolean)
20
28
  .filter((statement) => {
@@ -1,15 +1,2 @@
1
- import type { SquareAtInput, SquareLocation } from './square-facade.js';
2
- import type { SquareBackends } from './square-backend.js';
3
- export type ParsedSquareLocation = {
4
- readonly kind: 'local';
5
- readonly path: string;
6
- } | {
7
- readonly kind: 'stdio';
8
- readonly backend: string;
9
- readonly key: string;
10
- readonly uri: string;
11
- };
12
- export declare function parseSquareLocation(value: string): ParsedSquareLocation;
1
+ import type { SquareLocation } from './square-facade.js';
13
2
  export declare function squareLocation(input: SquareLocation): string;
14
- export declare function localSquareLocation(input: SquareLocation): string;
15
- export declare function backendConfiguration(input: SquareAtInput): Promise<SquareBackends>;
@@ -1,77 +1,11 @@
1
1
  import path from 'node:path';
2
- import os from 'node:os';
3
- import { readFile } from 'node:fs/promises';
4
2
  import { SquareError } from './model.js';
5
- export function parseSquareLocation(value) {
6
- // Windows roots include drive-relative prefixes such as D:.
7
- if (path.win32.parse(value).root !== '' || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value))
8
- return { kind: 'local', path: value };
9
- // Parse the raw components, before URL dot-segment normalization or host lowercasing.
10
- const match = /^stdio:\/\/([A-Za-z0-9_-]+)\/([^/?#]+)$/.exec(value);
11
- if (!match)
12
- throw new SquareError('invalid_args', 'Expected a local path or stdio://<configured-name>/<encoded-square-key>; no credentials, port, query or fragment');
13
- let key;
14
- try {
15
- key = decodeURIComponent(match[2]);
16
- }
17
- catch {
18
- throw new SquareError('invalid_args', 'Invalid percent encoding in stdio square key');
19
- }
20
- if (!key || key === '.' || key === '..' || /[/\\\x00-\x1f\x7f]/.test(key))
21
- throw new SquareError('invalid_args', 'A stdio square key must be one nonempty name, not a path');
22
- return { kind: 'stdio', backend: match[1], key, uri: `stdio://${match[1]}/${encodeURIComponent(key)}` };
23
- }
24
3
  export function squareLocation(input) {
25
4
  const selected = input.path ?? (input.env ?? process.env).SQUARE_LOCATION;
26
5
  if (selected === undefined || selected.trim() === '')
27
6
  throw new SquareError('invalid_args', 'Square needs a location');
28
- const location = parseSquareLocation(selected);
29
- return location.kind === 'local' ? path.resolve(input.cwd ?? process.cwd(), location.path) : location.uri;
30
- }
31
- export function localSquareLocation(input) {
32
- const location = squareLocation(input);
33
- if (parseSquareLocation(location).kind !== 'local')
34
- throw new SquareError('invalid_args', 'This file operation requires a local square artifact; backend URIs are not supported');
35
- return location;
36
- }
37
- function validateBackends(value) {
38
- const object = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
39
- const invalid = () => new SquareError('invalid_args', 'Invalid backend configuration: expected {backends:{name:{command,args?,cwd?,env?}}}');
40
- if (!object(value))
41
- throw invalid();
42
- for (const [name, entry] of Object.entries(value)) {
43
- if (!/^[A-Za-z0-9_-]+$/.test(name) || !object(entry) || typeof entry.command !== 'string' || !entry.command.trim()
44
- || Object.keys(entry).some(key => !['command', 'args', 'cwd', 'env'].includes(key))
45
- || (entry.args !== undefined && (!Array.isArray(entry.args) || entry.args.some(arg => typeof arg !== 'string')))
46
- || (entry.cwd !== undefined && typeof entry.cwd !== 'string')
47
- || (entry.env !== undefined && (!object(entry.env) || Object.values(entry.env).some(v => typeof v !== 'string'))))
48
- throw invalid();
49
- }
50
- return value;
51
- }
52
- export async function backendConfiguration(input) {
53
- if (input.backends !== undefined)
54
- return validateBackends(input.backends);
55
- const env = input.env ?? process.env;
56
- const configured = env.SQUARE_BACKENDS_CONFIG;
57
- const file = configured === undefined ? path.join(env.HOME ?? os.homedir(), '.config/square/backends.json')
58
- : path.resolve(input.cwd ?? process.cwd(), configured);
59
- let text;
60
- try {
61
- text = await readFile(file, 'utf8');
62
- }
63
- catch (error) {
64
- if (configured === undefined && error.code === 'ENOENT')
65
- return {};
66
- throw new SquareError('invalid_args', 'Cannot read backend configuration; check SQUARE_BACKENDS_CONFIG');
67
- }
68
- try {
69
- const parsed = JSON.parse(text);
70
- if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object' || Object.keys(parsed).some(key => key !== 'backends') || parsed.backends === undefined)
71
- throw new Error();
72
- return validateBackends(parsed.backends);
73
- }
74
- catch {
75
- throw new SquareError('invalid_args', 'Invalid backend configuration; expected {backends:{name:{command,args?,cwd?,env?}}}');
76
- }
7
+ // Windows roots include drive-relative prefixes such as D:.
8
+ if (path.win32.parse(selected).root === '' && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(selected))
9
+ throw new SquareError('invalid_args', 'Expected a local square artifact path');
10
+ return path.resolve(input.cwd ?? process.cwd(), selected);
77
11
  }
@@ -1,8 +1,8 @@
1
- import { type Body } from './model.js';
1
+ import { type HarnessCoordinate } from './owner.js';
2
2
  /** Offline conversion input only; never a runtime ownership or evidence reader. */
3
3
  export interface LegacyOwner {
4
4
  readonly participant: string;
5
- readonly body: Body;
5
+ readonly coordinate: HarnessCoordinate;
6
6
  }
7
7
  export interface LegacyHostData {
8
8
  readonly owners: readonly LegacyOwner[];
@@ -5,7 +5,8 @@ import path from 'node:path';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import { canonicalPath } from './canonical-path.js';
7
7
  import { inSqliteTransaction } from './sqlite-transaction.js';
8
- import { nameKey, validateBody } from './model.js';
8
+ import { harnessOwner } from './owner.js';
9
+ import { nameKey } from './model.js';
9
10
  const LEDGER_FILENAME = 'host-ledger.sqlite';
10
11
  const LEDGER_APPLICATION_ID = 0x5351484c;
11
12
  const LEDGER_VERSION = 2;
@@ -144,11 +145,11 @@ export async function readLegacyHostData(location, env = process.env, signal) {
144
145
  if (value.v !== 1 || value.location !== canonical || typeof value.participant !== 'string'
145
146
  || !finite(at))
146
147
  throw invalid('owner fields are malformed.');
147
- const body = { channel: value.channel, session: value.session, ...(value.agent === undefined ? {} : { agent: value.agent }) };
148
- validateBody(body);
148
+ const coordinate = { channel: value.channel, session: value.session, ...(value.agent === undefined ? {} : { agent: value.agent }) };
149
+ harnessOwner(coordinate);
149
150
  projections(row, { location_key: recordKey(canonical), participant_key: recordKey(nameKey(value.participant)),
150
- channel_key: recordKey(body.channel), session_key: recordKey(body.session), effective_at: at });
151
- return { participant: value.participant, body };
151
+ channel_key: recordKey(coordinate.channel), session_key: recordKey(coordinate.session), effective_at: at });
152
+ return { participant: value.participant, coordinate };
152
153
  });
153
154
  const evidence = database.prepare("SELECT * FROM host_evidence WHERE location_key=? AND kind IN ('wake','presentation') ORDER BY seq").all(recordKey(canonical)).map(row => {
154
155
  const value = decodeEvidence(record(row));
@@ -20,7 +20,10 @@ export interface MigrationOutcome {
20
20
  /**
21
21
  * Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
22
22
  * preflighted read-only, then receive ownership/evidence and deferral storage in one
23
- * format-5 transaction. Format 4 adds only the empty deferral table and its index.
23
+ * format-8 transaction. Formats 4/5 gain attachment; format 4 also gains empty deferrals.
24
+ * All legacy seats are encoded as opaque owners, preserving format-6 attachment.
25
+ * Every source drops obsolete activities and their identity references, clears dangling
26
+ * replies and rebuilds derived indices; surviving ids and allocation watermarks remain stable.
24
27
  * Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
25
28
  * unique; no source record is retired. Version 1 is not a supported source for this format transition.
26
29
  */