@astrosheep/square 0.3.86 → 0.3.87

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 (141) hide show
  1. package/dist/artifact.d.ts +6 -1
  2. package/dist/artifact.js +79 -10
  3. package/dist/boundary-presentation.d.ts +5 -4
  4. package/dist/boundary-presentation.js +21 -25
  5. package/dist/catch-decisions.js +2 -1
  6. package/dist/claude-delivery-status.d.ts +8 -2
  7. package/dist/claude-delivery-status.js +26 -10
  8. package/dist/claude-delivery.d.ts +8 -4
  9. package/dist/claude-delivery.js +114 -104
  10. package/dist/claude-mcp-context.js +2 -2
  11. package/dist/claude-mod.js +119 -105
  12. package/dist/cli/context.d.ts +3 -1
  13. package/dist/cli/context.js +25 -1
  14. package/dist/cli/express-presentation.d.ts +3 -1
  15. package/dist/cli/express-presentation.js +14 -10
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +1 -0
  18. package/dist/cli/registry.js +0 -2
  19. package/dist/cli/square-commands.d.ts +4 -2
  20. package/dist/cli/square-commands.js +21 -6
  21. package/dist/codex-hook.d.ts +3 -3
  22. package/dist/codex-hook.js +4 -4
  23. package/dist/codex-queue.d.ts +5 -6
  24. package/dist/codex-queue.js +9 -5
  25. package/dist/decisions.d.ts +3 -2
  26. package/dist/decisions.js +18 -6
  27. package/dist/delivery-health.d.ts +32 -2
  28. package/dist/delivery-health.js +108 -45
  29. package/dist/delivery-operations.d.ts +1 -2
  30. package/dist/delivery-operations.js +155 -196
  31. package/dist/delivery.d.ts +14 -23
  32. package/dist/delivery.js +48 -18
  33. package/dist/file-lock.d.ts +8 -1
  34. package/dist/file-lock.js +2 -0
  35. package/dist/help.js +8 -11
  36. package/dist/inbox.d.ts +35 -9
  37. package/dist/inbox.js +228 -144
  38. package/dist/index.d.ts +3 -4
  39. package/dist/index.js +1 -3
  40. package/dist/list.js +9 -4
  41. package/dist/mcp.js +12 -1
  42. package/dist/model.d.ts +39 -5
  43. package/dist/model.js +84 -0
  44. package/dist/notifications.d.ts +8 -6
  45. package/dist/notifications.js +56 -143
  46. package/dist/operation-presentation.js +3 -4
  47. package/dist/participant-identity.d.ts +0 -3
  48. package/dist/participant-identity.js +0 -31
  49. package/dist/paseo-delivery.d.ts +5 -6
  50. package/dist/paseo-delivery.js +13 -8
  51. package/dist/paseo.d.ts +1 -1
  52. package/dist/paseo.js +1 -1
  53. package/dist/plugins/opencode/delivery.d.ts +8 -3
  54. package/dist/plugins/opencode/delivery.js +187 -132
  55. package/dist/plugins/opencode/index.js +12 -38
  56. package/dist/ports.d.ts +112 -40
  57. package/dist/presentation-operations.d.ts +1 -1
  58. package/dist/presentation-operations.js +51 -35
  59. package/dist/presentation.js +8 -3
  60. package/dist/seats.d.ts +7 -0
  61. package/dist/seats.js +27 -0
  62. package/dist/session-lifecycle.d.ts +2 -2
  63. package/dist/session-lifecycle.js +10 -12
  64. package/dist/sqlite-square.d.ts +20 -9
  65. package/dist/sqlite-square.js +471 -339
  66. package/dist/square-api.d.ts +1 -0
  67. package/dist/square-api.js +3 -4
  68. package/dist/square-core.d.ts +3 -2
  69. package/dist/square-domain.d.ts +12 -5
  70. package/dist/square-domain.js +163 -7
  71. package/dist/square-facade.d.ts +14 -10
  72. package/dist/square-index.d.ts +24 -0
  73. package/dist/square-index.js +131 -0
  74. package/dist/square-legacy-framing.d.ts +9 -0
  75. package/dist/square-legacy-framing.js +56 -0
  76. package/dist/square-migration-host-ledger.d.ts +26 -0
  77. package/dist/square-migration-host-ledger.js +168 -0
  78. package/dist/square-migration.d.ts +5 -5
  79. package/dist/square-migration.js +373 -71
  80. package/dist/square-sqlite.d.ts +9 -9
  81. package/dist/square-sqlite.js +329 -79
  82. package/dist/square-stdio.d.ts +1 -1
  83. package/dist/square-stdio.js +12 -7
  84. package/dist/square-tools.js +29 -2
  85. package/dist/stdio-server.js +4 -2
  86. package/dist/stream.d.ts +3 -2
  87. package/dist/stream.js +9 -10
  88. package/dist/views.d.ts +9 -14
  89. package/dist/views.js +50 -70
  90. package/dist/wakes.d.ts +6 -5
  91. package/dist/wakes.js +20 -17
  92. package/guides/application-facade-delivery-evidence.md +1 -1
  93. package/guides/claude-native.md +9 -9
  94. package/guides/mcp.md +1 -1
  95. package/guides/migration.md +107 -67
  96. package/guides/row-storage-design.md +1 -1
  97. package/guides/square-api.md +61 -28
  98. package/package.json +1 -1
  99. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  100. package/plugins/claude/hooks/register.js +1 -1
  101. package/plugins/claude/skills/square/SKILL.md +1 -1
  102. package/plugins/claude/types.d.ts +1 -1
  103. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  104. package/plugins/opencode/delivery.ts +170 -118
  105. package/plugins/opencode/index.ts +12 -30
  106. package/plugins/pi/square-pi.js +55 -17
  107. package/skills/square/SKILL.md +1 -1
  108. package/dist/cli/ledger-command.d.ts +0 -12
  109. package/dist/cli/ledger-command.js +0 -72
  110. package/dist/host-ledger-file-adapter.d.ts +0 -47
  111. package/dist/host-ledger-file-adapter.js +0 -384
  112. package/dist/host-ledger-migration.d.ts +0 -27
  113. package/dist/host-ledger-migration.js +0 -264
  114. package/dist/host-ledger-root.d.ts +0 -2
  115. package/dist/host-ledger-root.js +0 -9
  116. package/dist/host-ledger-sqlite.d.ts +0 -65
  117. package/dist/host-ledger-sqlite.js +0 -368
  118. package/dist/host-ledger.d.ts +0 -195
  119. package/dist/host-ledger.js +0 -1
  120. package/dist/ledger-maintenance.d.ts +0 -30
  121. package/dist/ledger-maintenance.js +0 -110
  122. package/dist/open-square.d.ts +0 -38
  123. package/dist/open-square.js +0 -81
  124. package/dist/participant-host.d.ts +0 -23
  125. package/dist/participant-host.js +0 -92
  126. package/dist/registry.d.ts +0 -55
  127. package/dist/registry.js +0 -123
  128. package/dist/routes.d.ts +0 -39
  129. package/dist/routes.js +0 -99
  130. package/dist/square-actions.d.ts +0 -56
  131. package/dist/square-actions.js +0 -353
  132. package/dist/square-projections.d.ts +0 -31
  133. package/dist/square-projections.js +0 -64
  134. package/dist/wake-attempts.d.ts +0 -10
  135. package/dist/wake-attempts.js +0 -39
  136. package/dist/wake-eligibility.d.ts +0 -53
  137. package/dist/wake-eligibility.js +0 -123
  138. package/dist/wake-evidence.d.ts +0 -33
  139. package/dist/wake-evidence.js +0 -32
  140. package/guides/host-ledger.md +0 -58
  141. package/guides/indexed-host-ledger-performance.md +0 -39
@@ -2,6 +2,7 @@ import type { SquareBackend } from './square-backend.js';
2
2
  import type { SquareAtInput, SquareBuildInput, OperationControl } from './square-facade.js';
3
3
  export type * from './square-facade.js';
4
4
  export { squareLocation } from './square-location.js';
5
+ export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
5
6
  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';
6
7
  /** The same structural resource regardless of persistence or transport. */
7
8
  export interface Square extends SquareBackend {
@@ -2,23 +2,22 @@ import { SqliteSquare } from './sqlite-square.js';
2
2
  import { squareLocation, backendConfiguration, parseSquareLocation } from './square-location.js';
3
3
  import { openStdioSquare } from './square-stdio.js';
4
4
  export { squareLocation } from './square-location.js';
5
+ export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
5
6
  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';
6
7
  export const Square = {
7
8
  async at(input, control) {
8
9
  const captured = { ...input, cwd: input.cwd ?? process.cwd(), env: { ...(input.env ?? process.env) } };
9
10
  const location = squareLocation(captured);
10
11
  const selected = parseSquareLocation(location);
11
- const backends = await backendConfiguration(captured);
12
12
  return selected.kind === 'local' ? SqliteSquare.at({ ...captured, path: location }, control)
13
- : openStdioSquare(location, selected, captured, backends, undefined, control);
13
+ : openStdioSquare(location, selected, captured, await backendConfiguration(captured), undefined, control);
14
14
  },
15
15
  async build(input, control) {
16
16
  const captured = { ...input, cwd: input.cwd ?? process.cwd(), env: { ...(input.env ?? process.env) } };
17
17
  const location = squareLocation(captured);
18
18
  const selected = parseSquareLocation(location);
19
- const backends = await backendConfiguration(captured);
20
19
  return selected.kind === 'local' ? SqliteSquare.build({ ...captured, path: location }, control)
21
- : openStdioSquare(location, selected, captured, backends, {
20
+ : openStdioSquare(location, selected, captured, await backendConfiguration(captured), {
22
21
  markdown: input.markdown, hardCap: input.hardCap, throttlePerMinute: input.throttlePerMinute,
23
22
  template: input.template, force: input.force,
24
23
  }, control);
@@ -24,7 +24,8 @@ export type Act = {
24
24
  kind: 'say';
25
25
  actor: Participant;
26
26
  at?: number;
27
- body: string;
27
+ body?: string;
28
+ metadata?: import('./model.js').ActivityMetadata;
28
29
  mentions?: readonly Participant[];
29
30
  reach?: Reach;
30
31
  reply?: number;
@@ -59,7 +60,7 @@ export type Act = {
59
60
  at?: number;
60
61
  };
61
62
  export declare function audienceOf(say: {
62
- body: string;
63
+ body?: string;
63
64
  mentions?: readonly Participant[];
64
65
  reach?: Reach;
65
66
  }): Audience;
@@ -1,5 +1,5 @@
1
- import { type StoredAct } from './model.js';
2
- import type { AttentionFact, LandedRecipient, ParticipantFact, SquareRead, SquareWrite } from './ports.js';
1
+ import { type Seat, type StoredAct } from './model.js';
2
+ import type { ArtifactWriteAuthority, AttentionFact, LandedRecipient, ParticipantFact, SquareRead, SquareWrite } from './ports.js';
3
3
  export interface LandingEffect {
4
4
  readonly activity: StoredAct;
5
5
  readonly participant?: ParticipantFact;
@@ -21,8 +21,15 @@ export interface LandingFacts {
21
21
  /** The single landing transition, shared by both adapters and explicit rebuild. */
22
22
  export declare function deriveLanding(activity: StoredAct, facts: LandingFacts): LandingEffect;
23
23
  export declare function landingFacts(read: SquareRead, activity: StoredAct): LandingFacts;
24
- export interface LandingWriter extends Pick<SquareWrite, 'markSeen' | 'putLease' | 'removeLease' | 'replaceRoutes'> {
24
+ export interface LandingWriter extends Pick<SquareWrite, 'markSeen' | 'putLease' | 'removeLease' | 'updateWakeAddress' | 'cancelThrough' | 'claimDelivery' | 'prepareDelivery' | 'completeDelivery' | 'observeNativeDelivery'> {
25
+ /** Artifact-private seat persistence used only by this gate's lifecycle transition. */
26
+ storeSeat(seat: Seat): boolean;
27
+ removeSeat(seat: import('./ports.js').SeatRef): boolean;
25
28
  applyLanding(effect: LandingEffect): void;
26
29
  }
27
- /** Driver-owned methods validate rows/writes and track changes; the domain owns allocation. */
28
- export declare function wrapSquareWrite(read: SquareRead, writes: LandingWriter): SquareWrite;
30
+ export interface SquareWriteGate {
31
+ readonly facts: SquareWrite;
32
+ complete(): void;
33
+ }
34
+ /** The artifact transaction supplies one captured authority; every mutation checks it here. */
35
+ export declare function wrapSquareWrite(read: SquareRead, writes: LandingWriter, authority: ArtifactWriteAuthority): SquareWriteGate;
@@ -1,4 +1,4 @@
1
- import { SquareError, sameName } from './model.js';
1
+ import { SquareError, formatActivityId, parseActivityId, sameBody, sameName, validateBody, validateName } from './model.js';
2
2
  /** The single landing transition, shared by both adapters and explicit rebuild. */
3
3
  export function deriveLanding(activity, facts) {
4
4
  const audience = [];
@@ -64,18 +64,174 @@ export function landingFacts(read, activity) {
64
64
  replyAuthor: activity.kind === 'say' && activity.reply !== undefined ? read.activity(activity.reply)?.actor : undefined,
65
65
  };
66
66
  }
67
- /** Driver-owned methods validate rows/writes and track changes; the domain owns allocation. */
68
- export function wrapSquareWrite(read, writes) {
67
+ function validateGeneration(value) {
68
+ if (typeof value !== 'string' || parseActivityId(value) === undefined)
69
+ throw new SquareError('invalid_args', 'Artifact authority requires a valid join generation.');
70
+ }
71
+ /** The artifact transaction supplies one captured authority; every mutation checks it here. */
72
+ export function wrapSquareWrite(read, writes, authority) {
73
+ if (authority === undefined || typeof authority !== 'object' || authority === null)
74
+ throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
69
75
  const { applyLanding, ...mutations } = writes;
70
- return Object.assign({}, read, mutations, {
76
+ const expectedName = authority.kind === 'participant' ? authority.seat?.participant : authority.participant;
77
+ if (!['participant', 'join', 'takeover'].includes(authority.kind) || typeof expectedName !== 'string')
78
+ throw new SquareError('invalid_args', 'Artifact write authority is malformed.');
79
+ validateName(expectedName);
80
+ if (authority.kind === 'participant') {
81
+ validateBody(authority.seat.body);
82
+ validateGeneration(authority.seat.generation);
83
+ }
84
+ else {
85
+ validateBody(authority.body);
86
+ if (authority.kind === 'takeover')
87
+ validateGeneration(authority.expectedGeneration);
88
+ }
89
+ const assertSeat = (seat, expected = authority.kind === 'participant' ? authority.seat : undefined) => {
90
+ if (expected === undefined || !sameName(expected.participant, seat.participant)
91
+ || seat.generation !== expected.generation || !sameBody(seat.body, expected.body))
92
+ throw new SquareError('already_joined', 'This write needs the current participant seat.');
93
+ const current = read.seat(seat.participant);
94
+ if (current === undefined || !sameBody(current.body, expected.body) || current.generation !== expected.generation)
95
+ throw new SquareError('already_joined', 'This participant seat has been replaced.');
96
+ };
97
+ const assertParticipant = (name, generation) => {
98
+ if (authority.kind !== 'participant')
99
+ throw new SquareError('already_joined', 'This write needs the current participant seat.');
100
+ assertSeat({ ...authority.seat, participant: name, generation: generation === undefined ? authority.seat.generation : generation });
101
+ };
102
+ let reconnectSeat;
103
+ let takeoverPhase = authority.kind === 'takeover' ? 'ready' : undefined;
104
+ if (authority.kind === 'participant') {
105
+ assertSeat(authority.seat);
106
+ }
107
+ else if (authority.kind === 'join') {
108
+ const participant = read.participant(expectedName), seat = read.seat(expectedName);
109
+ if (participant?.joined) {
110
+ if (seat === undefined || !sameBody(seat.body, authority.body))
111
+ throw new SquareError('already_joined', `${expectedName} is held by another body; take it over explicitly`);
112
+ reconnectSeat = seat;
113
+ }
114
+ }
115
+ else {
116
+ const current = read.seat(expectedName);
117
+ if (current === undefined || current.generation !== authority.expectedGeneration)
118
+ throw new SquareError('already_joined', 'The participant seat changed before takeover.');
119
+ }
120
+ let lastActivity;
121
+ const facts = Object.assign({}, read, {
71
122
  land(activity) {
123
+ const actor = 'actor' in activity ? activity.actor : undefined;
124
+ if (actor === undefined)
125
+ throw new SquareError('invalid_args', 'Participant activities require a named actor.');
126
+ validateName(actor);
127
+ if (!sameName(actor, expectedName))
128
+ throw new SquareError('already_joined', 'Activity actor does not match the artifact authority.');
129
+ if (authority.kind === 'participant') {
130
+ assertSeat(authority.seat);
131
+ if (activity.kind === 'join')
132
+ throw new SquareError('invalid_args', 'A participant seat can only be re-established by join.');
133
+ }
134
+ else if (authority.kind === 'join') {
135
+ if (activity.kind !== 'join')
136
+ throw new SquareError('invalid_args', 'A new seat must be established by its join activity.');
137
+ if (read.participant(expectedName)?.joined)
138
+ throw new SquareError('already_joined', `${expectedName} is already standing; join cannot replace an existing seat.`);
139
+ }
140
+ else if (takeoverPhase === 'ready') {
141
+ const current = read.seat(expectedName);
142
+ if (activity.kind !== 'done' || current === undefined || current.generation !== authority.expectedGeneration)
143
+ throw new SquareError('already_joined', 'Takeover must first complete the captured participant.');
144
+ }
145
+ else if (authority.kind === 'takeover' && takeoverPhase === 'done') {
146
+ if (activity.kind !== 'join' || !read.participant(expectedName)?.done)
147
+ throw new SquareError('already_joined', 'Takeover must join after its captured done activity.');
148
+ }
149
+ else {
150
+ throw new SquareError('invalid_args', 'Takeover permits only one done followed by one join.');
151
+ }
72
152
  const index = read.header().nextActIndex;
73
- if (!Number.isSafeInteger(index) || index < 0 || index >= Number.MAX_SAFE_INTEGER) {
153
+ if (!Number.isSafeInteger(index) || index < 0 || index >= Number.MAX_SAFE_INTEGER)
74
154
  throw new SquareError('invalid_args', 'Square activity index is exhausted.');
75
- }
155
+ const captured = authority.kind === 'takeover' && activity.kind === 'done'
156
+ ? read.seat(expectedName) : undefined;
76
157
  const stored = { ...activity, index };
77
158
  applyLanding(deriveLanding(stored, landingFacts(read, stored)));
78
- return read.activity(index);
159
+ if (stored.kind === 'join') {
160
+ if (authority.kind === 'participant')
161
+ throw new SquareError('invalid_args', 'Participant authority cannot establish a seat.');
162
+ const seat = {
163
+ participant: stored.actor,
164
+ body: authority.body,
165
+ generation: formatActivityId(stored.index),
166
+ ...(authority.wakeAddress === undefined ? {} : { wakeAddress: authority.wakeAddress }),
167
+ };
168
+ mutations.storeSeat(seat);
169
+ const established = read.seat(seat.participant);
170
+ if (established === undefined || !sameBody(established.body, seat.body) || established.generation !== seat.generation)
171
+ throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
172
+ if (authority.kind === 'takeover')
173
+ takeoverPhase = 'joined';
174
+ }
175
+ else if (stored.kind === 'done' && authority.kind === 'participant') {
176
+ if (!mutations.removeSeat(authority.seat))
177
+ throw new SquareError('already_joined', 'The participant seat changed while it was being completed.');
178
+ }
179
+ else if (stored.kind === 'done' && authority.kind === 'takeover') {
180
+ if (captured === undefined || !mutations.removeSeat({ participant: expectedName, body: captured.body, generation: captured.generation }))
181
+ throw new SquareError('already_joined', 'The participant seat changed while takeover was completing it.');
182
+ takeoverPhase = 'done';
183
+ }
184
+ lastActivity = read.activity(index);
185
+ return lastActivity;
186
+ },
187
+ updateWakeAddress(seat, wakeAddress) {
188
+ assertSeat(seat, authority.kind === 'join' ? reconnectSeat : undefined);
189
+ return mutations.updateWakeAddress(seat, wakeAddress);
190
+ },
191
+ cancelThrough(seat, through) {
192
+ assertSeat(seat);
193
+ if (!Number.isSafeInteger(through) || through < 0 || through > read.header().lastActIndex || read.activity(through) === undefined)
194
+ throw new SquareError('invalid_args', 'Cancellation boundary must name an existing activity at or below the current head.');
195
+ return mutations.cancelThrough(seat, through);
196
+ },
197
+ markSeen(name, indexes, at) {
198
+ assertParticipant(name);
199
+ return mutations.markSeen(name, indexes, at);
200
+ },
201
+ putLease(name, lease, generation) {
202
+ assertParticipant(name, generation);
203
+ return mutations.putLease(name, lease, generation);
204
+ },
205
+ removeLease(name, leaseId, generation) {
206
+ assertParticipant(name, generation);
207
+ return mutations.removeLease(name, leaseId, generation);
208
+ },
209
+ claimDelivery(input) {
210
+ assertSeat(input.seat);
211
+ return mutations.claimDelivery(input);
212
+ },
213
+ prepareDelivery(input) {
214
+ assertSeat(input.seat);
215
+ return mutations.prepareDelivery(input);
216
+ },
217
+ completeDelivery(input) {
218
+ assertSeat(input.seat);
219
+ return mutations.completeDelivery(input);
220
+ },
221
+ observeNativeDelivery(input) {
222
+ assertSeat(input.seat);
223
+ return mutations.observeNativeDelivery(input);
79
224
  },
80
225
  });
226
+ return {
227
+ facts,
228
+ complete() {
229
+ if (authority.kind !== 'takeover')
230
+ return;
231
+ const seat = read.seat(expectedName);
232
+ if (takeoverPhase !== 'joined' || lastActivity?.kind !== 'join' || seat === undefined
233
+ || !sameBody(seat.body, authority.body) || seat.generation !== formatActivityId(lastActivity.index))
234
+ throw new SquareError('already_joined', 'Takeover did not complete its replacement join.');
235
+ },
236
+ };
81
237
  }
@@ -1,8 +1,9 @@
1
1
  import type { SquareBackends } from './square-backend.js';
2
2
  import type { ActivityId, Perception, Reach } from './square-core.js';
3
- import type { StoredAct } from './model.js';
4
- import type { HostLedgerPort, WakeTransportPort, DeliveryResult, SquareResourceOwner } from './ports.js';
3
+ import type { Body, StoredAct, WakeAddress, ActivityMetadata } from './model.js';
4
+ import type { WakeTransportPort, DeliveryResult, SquareResourceOwner } from './ports.js';
5
5
  export type { ActivityId, Perception, Reach } from './square-core.js';
6
+ export type { Body, WakeAddress, ActivityMetadata, JsonValue } from './model.js';
6
7
  /** Indexed archive event, including read and historical actorless lifecycle records. */
7
8
  export type ArchiveRecord = Readonly<StoredAct>;
8
9
  export interface Activity {
@@ -11,6 +12,7 @@ export interface Activity {
11
12
  readonly kind: 'join' | 'say' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore' | 'unlisten';
12
13
  readonly actor: string;
13
14
  readonly body?: string;
15
+ readonly metadata?: ActivityMetadata;
14
16
  readonly mentions: readonly string[];
15
17
  readonly reach?: Reach;
16
18
  readonly target?: string;
@@ -24,6 +26,7 @@ export interface ObservationOptions {
24
26
  readonly observe?: boolean;
25
27
  }
26
28
  export interface ExpressOptions extends ObservationOptions {
29
+ readonly metadata?: ActivityMetadata;
27
30
  readonly force?: boolean;
28
31
  readonly mentions?: readonly string[];
29
32
  readonly reach?: Reach;
@@ -31,6 +34,9 @@ export interface ExpressOptions extends ObservationOptions {
31
34
  readonly noMention?: boolean;
32
35
  readonly noWait?: boolean;
33
36
  }
37
+ export interface ExpressInput extends ExpressOptions {
38
+ readonly body?: string;
39
+ }
34
40
  export interface ActivityResult {
35
41
  readonly activity: Activity;
36
42
  readonly delivery?: DeliveryResult;
@@ -147,9 +153,11 @@ export interface SquareSnapshot {
147
153
  }
148
154
  export interface OpenOptions {
149
155
  readonly clock?: () => number;
150
- readonly hostLedger?: HostLedgerPort;
151
156
  readonly wakeTransport?: WakeTransportPort;
152
- readonly env?: NodeJS.ProcessEnv;
157
+ /** Explicit execution coordinate resolved by an adapter. */
158
+ readonly body?: Body;
159
+ /** Optional wake address attached to this body. */
160
+ readonly wakeAddress?: WakeAddress;
153
161
  }
154
162
  export interface SquareLocation {
155
163
  readonly path?: string;
@@ -181,13 +189,10 @@ export interface JoinResult {
181
189
  readonly participantCount: number;
182
190
  readonly observation?: JoinObservation;
183
191
  }
184
- /** Optional fence for owner-scoped lifecycle actions. */
185
- export interface OwnershipFenceOptions {
186
- readonly expectedEpoch?: number;
187
- }
188
192
  export interface Participant {
189
193
  readonly name: string;
190
- express(body: string, options?: ExpressOptions, control?: OperationControl): Promise<ExpressResult>;
194
+ express(body: string | undefined, options?: ExpressOptions, control?: OperationControl): Promise<ExpressResult>;
195
+ express(input: ExpressInput, control?: OperationControl): Promise<ExpressResult>;
191
196
  listen(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
192
197
  ignore(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
193
198
  unlisten(target: string, options?: ObservationOptions, control?: OperationControl): Promise<ListenerChangeResult>;
@@ -356,7 +361,6 @@ export interface ExpressObservation extends SquareObservation, ExpressionObserva
356
361
  readonly pendingPublic: readonly ArchiveRecord[];
357
362
  readonly sayNumbers: Readonly<Record<number, number>>;
358
363
  readonly perceptions: Readonly<Record<number, Perception>>;
359
- readonly reentry?: Activity;
360
364
  }
361
365
  export interface CatchObservation extends SquareObservation, ExpressionObservation {
362
366
  readonly participant: string;
@@ -0,0 +1,24 @@
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. */
5
+ export interface SquareIndexEntry {
6
+ readonly location: string;
7
+ readonly body: Body;
8
+ }
9
+ export interface SquareIndexQuery {
10
+ readonly body?: Body;
11
+ readonly channel?: string;
12
+ readonly session?: string;
13
+ readonly agent?: string;
14
+ }
15
+ /** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
16
+ export declare function squareIndexPath(env?: NodeJS.ProcessEnv): string;
17
+ /** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
18
+ export declare function indexedSquareEntries(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly SquareIndexEntry[]>;
19
+ export declare function indexedSquarePaths(query?: SquareIndexQuery, env?: NodeJS.ProcessEnv): Promise<readonly string[]>;
20
+ /** 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. */
23
+ export declare function observeSquareIndex(env?: NodeJS.ProcessEnv): Promise<VersionObserver<string>>;
24
+ export declare function bodyKey(body: Body): string;
@@ -0,0 +1,131 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { withFileLock } from './file-lock.js';
6
+ import { canonicalPath } from './canonical-path.js';
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. */
10
+ export function squareIndexPath(env = process.env) {
11
+ const configured = env.SQUARE_INDEX?.trim();
12
+ return path.resolve(configured ?? path.join(os.homedir(), '.square', 'index.ndjsonl'));
13
+ }
14
+ function lockPath(index) {
15
+ return `${index}.lock`;
16
+ }
17
+ function validEntry(value) {
18
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
19
+ return undefined;
20
+ 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))
23
+ 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
+ try {
31
+ validateBody(body);
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ if (Object.keys(raw).some((key) => !['channel', 'session', 'agent'].includes(key)))
37
+ return undefined;
38
+ return { location: row.location, body };
39
+ }
40
+ async function readEntries(index) {
41
+ let content;
42
+ try {
43
+ content = await fs.readFile(index, 'utf8');
44
+ }
45
+ catch (error) {
46
+ if (error.code === 'ENOENT')
47
+ return [];
48
+ throw error;
49
+ }
50
+ const entries = [];
51
+ for (const line of content.split('\n')) {
52
+ if (line.trim() === '')
53
+ continue;
54
+ try {
55
+ const entry = validEntry(JSON.parse(line));
56
+ if (entry !== undefined)
57
+ entries.push(entry);
58
+ }
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
+ }
63
+ }
64
+ return entries;
65
+ }
66
+ 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;
76
+ }
77
+ /** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
78
+ export async function indexedSquareEntries(query = {}, env = process.env) {
79
+ const entries = await readEntries(squareIndexPath(env));
80
+ const result = [];
81
+ const seen = new Set();
82
+ for (const entry of entries) {
83
+ if (!matches(entry, query))
84
+ continue;
85
+ let location;
86
+ try {
87
+ location = await canonicalPath(entry.location);
88
+ }
89
+ catch {
90
+ continue;
91
+ }
92
+ const key = JSON.stringify([location, entry.body.channel, entry.body.session, entry.body.agent ?? null]);
93
+ if (seen.has(key))
94
+ continue;
95
+ seen.add(key);
96
+ result.push({ location, body: entry.body });
97
+ }
98
+ return result;
99
+ }
100
+ export async function indexedSquarePaths(query = {}, env = process.env) {
101
+ return [...new Set((await indexedSquareEntries(query, env)).map((entry) => entry.location))];
102
+ }
103
+ /** 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);
106
+ const canonical = await canonicalPath(location);
107
+ await fs.mkdir(path.dirname(index), { recursive: true });
108
+ return await withFileLock(lockPath(index), { retryMs: 10, skipIfBusy: true }, async () => {
109
+ const entries = await readEntries(index);
110
+ if (entries.some((entry) => entry.location === canonical && sameBody(entry.body, body)))
111
+ return false;
112
+ const next = [...entries, { location: canonical, body }];
113
+ const temporary = `${index}.${process.pid}.${randomUUID()}.tmp`;
114
+ try {
115
+ await fs.writeFile(temporary, next.map((entry) => JSON.stringify(entry)).join('\n') + '\n', { flag: 'wx', mode: 0o600 });
116
+ await fs.rename(temporary, index);
117
+ }
118
+ finally {
119
+ await fs.rm(temporary, { force: true });
120
+ }
121
+ return true;
122
+ }) ?? false;
123
+ }
124
+ /** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
125
+ export function observeSquareIndex(env = process.env) {
126
+ return observeFileMetadata(squareIndexPath(env));
127
+ }
128
+ export function bodyKey(body) {
129
+ validateBody(body);
130
+ return JSON.stringify([body.channel, body.session, body.agent ?? null]);
131
+ }
@@ -0,0 +1,9 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ export declare const FORMAT4_ADDED_OBJECTS: ReadonlySet<string>;
3
+ export declare const FORMAT4_REPLACED_OBJECTS: ReadonlySet<string>;
4
+ /**
5
+ * Read-only recognition of the exact row framing accepted by explicit migration.
6
+ * It inspects identity, owned schema and journal mode only; it never reads row
7
+ * contents (including legacy routes), changes pragmas, or performs DDL.
8
+ */
9
+ export declare function legacyRowFramingIssue(database: DatabaseSync, applicationId: number, version: 2 | 3, location: string, currentSchemaSql: string): string | undefined;
@@ -0,0 +1,56 @@
1
+ export const FORMAT4_ADDED_OBJECTS = new Set([
2
+ 'seats', 'seat_body', 'delivery_evidence', 'delivery_claim_token', 'delivery_activity', 'delivery_cancellations',
3
+ ]);
4
+ export const FORMAT4_REPLACED_OBJECTS = new Set(['routes', 'route_participant', 'route_session']);
5
+ const LEGACY_ROUTES_SQL = `CREATE TABLE routes (
6
+ position INTEGER PRIMARY KEY CHECK (position >= 0),
7
+ participant_key_json TEXT COLLATE BINARY NOT NULL,
8
+ session_json TEXT COLLATE BINARY NOT NULL,
9
+ kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','claude-native','paseo')),
10
+ updated_at REAL NOT NULL,
11
+ payload_json TEXT NOT NULL CHECK (json_valid(payload_json))
12
+ );
13
+ CREATE INDEX route_participant ON routes(participant_key_json, position);
14
+ CREATE INDEX route_session ON routes(session_json, position);`;
15
+ function normalizedSql(sql) {
16
+ return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1');
17
+ }
18
+ function legacyRowSchema(currentSchemaSql, version) {
19
+ const statements = currentSchemaSql.split(';').map((statement) => statement.trim()).filter(Boolean)
20
+ .filter((statement) => {
21
+ const match = /^(?:CREATE (?:UNIQUE )?TABLE|CREATE (?:UNIQUE )?INDEX)\s+([a-z_]+)/i.exec(statement);
22
+ return match === null || !FORMAT4_ADDED_OBJECTS.has(match[1]) && !FORMAT4_REPLACED_OBJECTS.has(match[1]);
23
+ });
24
+ const legacyStatements = statements.join(';\n')
25
+ .replace(' delivery_revision INTEGER NOT NULL CHECK (delivery_revision BETWEEN 0 AND 9007199254740991),\n', '')
26
+ .replace(' generation_json TEXT NOT NULL DEFAULT \'""\',\n', '');
27
+ const allStatements = [...legacyStatements.split(';').map((statement) => statement.trim()).filter(Boolean),
28
+ ...LEGACY_ROUTES_SQL.split(';').map((statement) => statement.trim()).filter(Boolean)];
29
+ let schema = allStatements.join(';\n');
30
+ if (version === 2) {
31
+ schema = schema.replace("kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')", "kind IN ('join','done','say','hold','resume','read','listen','ignore')");
32
+ }
33
+ return schema.split(';').map((statement) => normalizedSql(statement)).filter(Boolean).sort();
34
+ }
35
+ /**
36
+ * Read-only recognition of the exact row framing accepted by explicit migration.
37
+ * It inspects identity, owned schema and journal mode only; it never reads row
38
+ * contents (including legacy routes), changes pragmas, or performs DDL.
39
+ */
40
+ export function legacyRowFramingIssue(database, applicationId, version, location, currentSchemaSql) {
41
+ const identity = database.prepare('SELECT application_id, user_version FROM pragma_application_id, pragma_user_version').get();
42
+ if (identity.application_id !== applicationId)
43
+ return 'unrelated SQLite database.';
44
+ if (identity.user_version !== version)
45
+ return 'unsupported SQLite format version.';
46
+ const actual = database.prepare("SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' AND sql IS NOT NULL").all()
47
+ .map((row) => typeof row.sql === 'string' ? normalizedSql(row.sql) : '')
48
+ .sort();
49
+ const expected = legacyRowSchema(currentSchemaSql, version);
50
+ if (actual.length !== expected.length || actual.some((sql, index) => sql !== expected[index]))
51
+ return 'unsupported row schema.';
52
+ const journalMode = database.prepare('PRAGMA journal_mode').get().journal_mode;
53
+ if (journalMode !== (location === ':memory:' ? 'memory' : 'delete'))
54
+ return 'unsupported journal mode.';
55
+ return undefined;
56
+ }
@@ -0,0 +1,26 @@
1
+ import { type Body } from './model.js';
2
+ /** Offline conversion input only; never a runtime ownership or evidence reader. */
3
+ export interface LegacyOwner {
4
+ readonly participant: string;
5
+ readonly body: Body;
6
+ }
7
+ export interface LegacyHostData {
8
+ readonly owners: readonly LegacyOwner[];
9
+ readonly evidence: readonly LegacyDeliveryEvidence[];
10
+ }
11
+ export interface LegacyDeliveryEvidence {
12
+ readonly v: 1;
13
+ readonly location: string;
14
+ readonly participant: string;
15
+ readonly session: string;
16
+ readonly activity: string;
17
+ readonly kind: 'wake' | 'presentation' | 'lifecycle';
18
+ readonly outcome: string;
19
+ readonly at?: number;
20
+ readonly expiresAt?: number;
21
+ readonly attemptN?: number;
22
+ readonly claimToken?: string;
23
+ readonly [key: string]: unknown;
24
+ }
25
+ /** One frozen snapshot, latest owners only; no age, expiry, or ownership history inference. */
26
+ export declare function readLegacyHostData(location: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<LegacyHostData>;