@astrosheep/square 0.3.106 → 0.4.1

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 (206) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +6 -12
  5. package/dist/artifact.js +43 -344
  6. package/dist/attention-presentation.d.ts +13 -7
  7. package/dist/attention-presentation.js +29 -68
  8. package/dist/boundary-presentation.d.ts +3 -16
  9. package/dist/boundary-presentation.js +29 -109
  10. package/dist/card-presentation.d.ts +1 -0
  11. package/dist/card-presentation.js +2 -0
  12. package/dist/catch-decisions.d.ts +2 -2
  13. package/dist/catch-decisions.js +7 -3
  14. package/dist/catch-selection.d.ts +8 -0
  15. package/dist/catch-selection.js +5 -0
  16. package/dist/claude-delivery.d.ts +14 -12
  17. package/dist/claude-delivery.js +42 -142
  18. package/dist/claude-mcp-context.js +2 -2
  19. package/dist/claude-mod.js +28 -140
  20. package/dist/claude-transport.d.ts +5 -0
  21. package/dist/claude-transport.js +34 -0
  22. package/dist/cli/context.d.ts +2 -2
  23. package/dist/cli/context.js +7 -10
  24. package/dist/cli/maintenance-commands.d.ts +0 -4
  25. package/dist/cli/maintenance-commands.js +2 -29
  26. package/dist/cli/observation-commands.d.ts +2 -0
  27. package/dist/cli/observation-commands.js +12 -2
  28. package/dist/cli/program.js +1 -1
  29. package/dist/cli/registry.js +4 -3
  30. package/dist/codex-address.d.ts +2 -2
  31. package/dist/codex-boundary-state.d.ts +1 -5
  32. package/dist/codex-boundary-state.js +0 -4
  33. package/dist/codex-hook.d.ts +7 -10
  34. package/dist/codex-hook.js +69 -33
  35. package/dist/codex-queue.d.ts +10 -11
  36. package/dist/codex-queue.js +32 -34
  37. package/dist/codex-watch.d.ts +2 -0
  38. package/dist/codex-watch.js +100 -0
  39. package/dist/decisions.js +3 -3
  40. package/dist/delivery-deferral.d.ts +1 -19
  41. package/dist/delivery-deferral.js +1 -29
  42. package/dist/delivery-frame.d.ts +7 -0
  43. package/dist/delivery-frame.js +11 -0
  44. package/dist/delivery-ledger-sqlite.d.ts +97 -0
  45. package/dist/delivery-ledger-sqlite.js +496 -0
  46. package/dist/delivery-ledger.d.ts +196 -0
  47. package/dist/delivery-ledger.js +268 -0
  48. package/dist/delivery-lifetime.d.ts +6 -0
  49. package/dist/delivery-lifetime.js +45 -0
  50. package/dist/harness-links.js +61 -7
  51. package/dist/harness.js +12 -17
  52. package/dist/help.js +3 -6
  53. package/dist/http-delivery.d.ts +5 -9
  54. package/dist/http-delivery.js +20 -79
  55. package/dist/inbox.d.ts +6 -57
  56. package/dist/inbox.js +24 -380
  57. package/dist/index.d.ts +7 -4
  58. package/dist/index.js +5 -2
  59. package/dist/mcp.d.ts +2 -1
  60. package/dist/mcp.js +40 -70
  61. package/dist/message.d.ts +7 -0
  62. package/dist/message.js +23 -0
  63. package/dist/model.d.ts +15 -91
  64. package/dist/model.js +2 -140
  65. package/dist/neutral.d.ts +72 -0
  66. package/dist/neutral.js +27 -0
  67. package/dist/notifications.d.ts +24 -26
  68. package/dist/notifications.js +39 -143
  69. package/dist/operation-control.d.ts +4 -0
  70. package/dist/operation-control.js +20 -0
  71. package/dist/operation-presentation.d.ts +1 -2
  72. package/dist/operation-presentation.js +1 -7
  73. package/dist/owner.d.ts +2 -2
  74. package/dist/owner.js +1 -1
  75. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  76. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  77. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  78. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  79. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  80. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  81. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  82. package/dist/packages/agent-delivery/src/codex.js +41 -4
  83. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  84. package/dist/packages/agent-delivery/src/index.js +1 -1
  85. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  86. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  87. package/dist/paseo-delivery.d.ts +5 -5
  88. package/dist/paseo-delivery.js +19 -99
  89. package/dist/paseo.d.ts +1 -1
  90. package/dist/paseo.js +1 -1
  91. package/dist/plugins/opencode/index.d.ts +5 -3
  92. package/dist/plugins/opencode/index.js +99 -21
  93. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  94. package/dist/plugins/opencode-shared/delivery.js +109 -0
  95. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  96. package/dist/plugins/opencode-v1/index.js +41 -0
  97. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  98. package/dist/plugins/opencode-v1/runtime.js +152 -0
  99. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  100. package/dist/plugins/opencode-v1/transport.js +50 -0
  101. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  102. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  103. package/dist/ports.d.ts +33 -214
  104. package/dist/presentation.d.ts +5 -5
  105. package/dist/presentation.js +1 -0
  106. package/dist/runtime.js +2 -2
  107. package/dist/search.js +1 -1
  108. package/dist/session-lifecycle.d.ts +8 -7
  109. package/dist/session-lifecycle.js +40 -42
  110. package/dist/sqlite-framing.d.ts +8 -0
  111. package/dist/sqlite-framing.js +31 -0
  112. package/dist/sqlite-square.d.ts +16 -26
  113. package/dist/sqlite-square.js +199 -262
  114. package/dist/sqlite-values.d.ts +21 -0
  115. package/dist/sqlite-values.js +78 -0
  116. package/dist/square-api.d.ts +1 -1
  117. package/dist/square-api.js +5 -8
  118. package/dist/square-core.d.ts +0 -1
  119. package/dist/square-domain.d.ts +12 -10
  120. package/dist/square-domain.js +105 -223
  121. package/dist/square-facade.d.ts +25 -76
  122. package/dist/square-location.d.ts +6 -1
  123. package/dist/square-location.js +1 -1
  124. package/dist/square-post.d.ts +53 -0
  125. package/dist/square-post.js +4 -0
  126. package/dist/square-posting.d.ts +13 -0
  127. package/dist/square-posting.js +174 -0
  128. package/dist/square-producer-sqlite.d.ts +11 -0
  129. package/dist/square-producer-sqlite.js +210 -0
  130. package/dist/square-receive.d.ts +2 -3
  131. package/dist/square-sqlite.d.ts +33 -42
  132. package/dist/square-sqlite.js +236 -768
  133. package/dist/square-tools.d.ts +8 -0
  134. package/dist/square-tools.js +39 -0
  135. package/dist/square-values.d.ts +27 -0
  136. package/dist/square-values.js +90 -0
  137. package/dist/views.d.ts +6 -6
  138. package/dist/views.js +14 -9
  139. package/dist/wake-sink.d.ts +4 -11
  140. package/dist/wake-sink.js +8 -32
  141. package/dist/wake-transports.d.ts +4 -0
  142. package/dist/wake-transports.js +11 -0
  143. package/examples/symmetric-domains.mjs +29 -249
  144. package/guides/change-waits.md +13 -53
  145. package/guides/claude-native.md +19 -172
  146. package/guides/codex-delivery.md +55 -78
  147. package/guides/delivery-primitives.md +48 -207
  148. package/guides/operation-cancellation.md +2 -5
  149. package/guides/participant-ownership.md +25 -89
  150. package/guides/pi-native-delivery.md +19 -56
  151. package/guides/pi-tools.md +6 -8
  152. package/guides/row-runtime-delivery-evidence.md +2 -0
  153. package/guides/row-storage-design.md +19 -44
  154. package/guides/square-api.md +113 -538
  155. package/package.json +8 -8
  156. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  157. package/plugins/claude/hooks/hooks.json +1 -1
  158. package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
  159. package/plugins/claude/types.d.ts +1 -1
  160. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  161. package/plugins/codex/hooks/hooks.json +11 -23
  162. package/plugins/opencode/index.ts +91 -23
  163. package/plugins/opencode-shared/delivery.ts +91 -0
  164. package/plugins/opencode-v1/index.ts +36 -0
  165. package/plugins/opencode-v1/runtime.ts +134 -0
  166. package/plugins/opencode-v1/transport.ts +41 -0
  167. package/plugins/paseo/OVERVIEW.md +5 -5
  168. package/plugins/paseo/README.md +7 -6
  169. package/plugins/paseo/client/markdown.tsx +5 -5
  170. package/plugins/paseo/client/square-message.tsx +28 -24
  171. package/plugins/paseo/client/transform.ts +8 -5
  172. package/plugins/paseo/shared/card-presentation.ts +16 -0
  173. package/plugins/paseo/shared/message.ts +6 -11
  174. package/plugins/pi/square-pi.js +167 -451
  175. package/dist/channel.d.ts +0 -74
  176. package/dist/channel.js +0 -92
  177. package/dist/claude-delivery-status.d.ts +0 -13
  178. package/dist/claude-delivery-status.js +0 -46
  179. package/dist/cmd/notify-once.d.ts +0 -2
  180. package/dist/cmd/notify-once.js +0 -29
  181. package/dist/delivery-attempts.d.ts +0 -52
  182. package/dist/delivery-attempts.js +0 -73
  183. package/dist/delivery-effects.d.ts +0 -139
  184. package/dist/delivery-effects.js +0 -163
  185. package/dist/delivery-health.d.ts +0 -51
  186. package/dist/delivery-health.js +0 -173
  187. package/dist/delivery-operations.d.ts +0 -9
  188. package/dist/delivery-operations.js +0 -168
  189. package/dist/delivery-primitives.d.ts +0 -5
  190. package/dist/delivery-primitives.js +0 -3
  191. package/dist/delivery.d.ts +0 -45
  192. package/dist/delivery.js +0 -106
  193. package/dist/plugins/opencode/delivery.d.ts +0 -19
  194. package/dist/plugins/opencode/delivery.js +0 -238
  195. package/dist/presentation-operations.d.ts +0 -18
  196. package/dist/presentation-operations.js +0 -86
  197. package/dist/square-index.d.ts +0 -16
  198. package/dist/square-index.js +0 -106
  199. package/dist/square-legacy-framing.d.ts +0 -11
  200. package/dist/square-legacy-framing.js +0 -64
  201. package/dist/square-migration-host-ledger.d.ts +0 -26
  202. package/dist/square-migration-host-ledger.js +0 -169
  203. package/dist/square-migration.d.ts +0 -30
  204. package/dist/square-migration.js +0 -599
  205. package/guides/migration.md +0 -156
  206. package/plugins/opencode/delivery.ts +0 -211
@@ -0,0 +1,21 @@
1
+ import { SquareError, type FactPage, type IndexPage } from './neutral.js';
2
+ export type Row = Record<string, string | number | null>;
3
+ export declare const encode: {
4
+ (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string;
5
+ (value: any, replacer?: (number | string)[] | null, space?: string | number): string;
6
+ };
7
+ export declare const key: (name: string) => string;
8
+ export declare function invalidArtifact(detail: string): SquareError;
9
+ export declare function requireFact(ok: unknown, detail?: string): asserts ok;
10
+ export declare function integer(value: unknown, minimum?: number): number;
11
+ export declare function finite(value: unknown): number;
12
+ export declare function string(value: unknown, nonempty?: boolean): string;
13
+ export declare function json(value: unknown): unknown;
14
+ export declare function object(value: unknown): Record<string, unknown>;
15
+ export declare function shape(value: unknown, required: readonly string[], optional?: readonly string[]): Record<string, unknown>;
16
+ export declare function strings(value: unknown): string[];
17
+ export declare function decoded(value: unknown, nonempty?: boolean): string;
18
+ export declare function immutable<T>(value: T): T;
19
+ export declare function stableJson(value: unknown): string;
20
+ export declare function pageRows<T>(rows: Row[], page: IndexPage, decode: (row: Row) => T): FactPage<T>;
21
+ export declare function pageLimit(page: IndexPage): number;
@@ -0,0 +1,78 @@
1
+ import { SquareError, nameKey } from './neutral.js';
2
+ export const encode = JSON.stringify;
3
+ export const key = (name) => encode(nameKey(name));
4
+ export function invalidArtifact(detail) {
5
+ return new SquareError('invalid_args', `Invalid square artifact: ${detail}`);
6
+ }
7
+ export function requireFact(ok, detail = 'row is malformed.') {
8
+ if (!ok)
9
+ throw invalidArtifact(detail);
10
+ }
11
+ export function integer(value, minimum = 0) {
12
+ requireFact(Number.isSafeInteger(value) && value >= minimum);
13
+ return value;
14
+ }
15
+ export function finite(value) {
16
+ requireFact(typeof value === 'number' && Number.isFinite(value));
17
+ return value;
18
+ }
19
+ export function string(value, nonempty = false) {
20
+ requireFact(typeof value === 'string' && (!nonempty || value.length > 0));
21
+ return value;
22
+ }
23
+ export function json(value) {
24
+ try {
25
+ return JSON.parse(string(value));
26
+ }
27
+ catch {
28
+ throw invalidArtifact('row contains invalid JSON.');
29
+ }
30
+ }
31
+ export function object(value) {
32
+ requireFact(typeof value === 'object' && value !== null && !Array.isArray(value));
33
+ return value;
34
+ }
35
+ export function shape(value, required, optional = []) {
36
+ const result = object(value);
37
+ requireFact(required.every(k => Object.hasOwn(result, k)) && Object.keys(result).every(k => required.includes(k) || optional.includes(k)));
38
+ return result;
39
+ }
40
+ export function strings(value) {
41
+ requireFact(Array.isArray(value) && value.every(v => typeof v === 'string'));
42
+ return value;
43
+ }
44
+ export function decoded(value, nonempty = false) {
45
+ const result = string(json(value), nonempty);
46
+ requireFact(encode(result) === value, 'string encoding is malformed.');
47
+ return result;
48
+ }
49
+ export function immutable(value) {
50
+ if (value !== null && typeof value === 'object') {
51
+ for (const item of Object.values(value))
52
+ immutable(item);
53
+ Object.freeze(value);
54
+ }
55
+ return value;
56
+ }
57
+ // Sorted object keys express small-value structural equality, never whole-state comparison.
58
+ export function stableJson(value) {
59
+ if (value === null || typeof value !== 'object') {
60
+ requireFact(value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value), 'value contains a non-finite or unsupported JSON value.');
61
+ return encode(value);
62
+ }
63
+ if (Array.isArray(value))
64
+ return '[' + value.map(stableJson).join(',') + ']';
65
+ return '{' + Object.keys(value).sort().map(k => encode(k) + ':' + stableJson(value[k])).join(',') + '}';
66
+ }
67
+ export function pageRows(rows, page, decode) {
68
+ const hasMore = rows.length > page.limit;
69
+ if (hasMore)
70
+ rows.pop();
71
+ if (page.edge === 'newest')
72
+ rows.reverse();
73
+ return immutable({ items: rows.map(decode), hasMore });
74
+ }
75
+ export function pageLimit(page) {
76
+ requireFact(page.edge === 'oldest' || page.edge === 'newest');
77
+ return Math.min(integer(page.limit, 1) + 1, Number.MAX_SAFE_INTEGER);
78
+ }
@@ -16,4 +16,4 @@ export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' |
16
16
  }): Promise<Participant | undefined>;
17
17
  export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
18
18
  export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
19
- export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
19
+ export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, discover, inbox } from './sqlite-square.js';
@@ -1,12 +1,8 @@
1
1
  import { SqliteSquare } from './sqlite-square.js';
2
2
  import { SquareError } from './model.js';
3
3
  import { requireOwner } from './seats.js';
4
- import { createDefaultWakeTransport } from './notifications.js';
5
- export const Square = { at: async (input, control) => SqliteSquare.at({ ...input, wakeTransport: await resolveWakeTransport(input) }, control),
6
- build: async (input, control) => SqliteSquare.build({ ...input, wakeTransport: await resolveWakeTransport(input) }, control), inMemory: SqliteSquare.inMemory };
7
- const resolveWakeTransport = (input) => input.wakeTransport !== undefined
8
- ? Promise.resolve(input.wakeTransport)
9
- : createDefaultWakeTransport(input.env ?? process.env).catch(() => undefined);
4
+ export const Square = { at: (input, control) => SqliteSquare.at(input, control),
5
+ build: (input, control) => SqliteSquare.build(input, control), inMemory: SqliteSquare.inMemory };
10
6
  export { requireOwner, requireJoinName } from './seats.js';
11
7
  export { squareLocation } from './square-location.js';
12
8
  export async function selectOwnSeat(square, owner, name, options) {
@@ -15,7 +11,8 @@ export async function selectOwnSeat(square, owner, name, options) {
15
11
  const caller = requireOwner(owner);
16
12
  if (name !== undefined)
17
13
  return square.participant(name, { owner: caller });
18
- const seats = (await square.seatsOf(caller)).filter(seat => seat.attachment === 'attached');
14
+ // Selection is local producer custody, not a ledger attachment check.
15
+ const seats = await square.seatsOf(caller);
19
16
  if (seats.length !== 1) {
20
17
  if (options?.optional)
21
18
  return undefined;
@@ -27,4 +24,4 @@ export async function selectOwnSeat(square, owner, name, options) {
27
24
  }
28
25
  export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
29
26
  export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
30
- export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
27
+ export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, discover, inbox } from './sqlite-square.js';
@@ -26,7 +26,6 @@ export type Act = {
26
26
  at?: number;
27
27
  body?: string;
28
28
  metadata?: import('./model.js').ActivityMetadata;
29
- delivery?: import('./model.js').ActivityDelivery;
30
29
  mentions?: readonly Participant[];
31
30
  reach?: Reach;
32
31
  reply?: number;
@@ -1,7 +1,10 @@
1
- import { type Seat, type StoredAct } from './model.js';
1
+ import { type ActDraft, type Seat } from './model.js';
2
2
  import type { ArtifactWriteAuthority, AttentionFact, LandedRecipient, ParticipantFact, SeatRef, SquareRead, SquareWrite } from './ports.js';
3
+ import type { ProducerWrite } from './square-post.js';
3
4
  export interface LandingEffect {
4
- readonly activity: StoredAct;
5
+ readonly activity: ActDraft & {
6
+ readonly index: number;
7
+ };
5
8
  readonly participant?: ParticipantFact;
6
9
  readonly removeListeningOf?: string;
7
10
  readonly removeListening?: {
@@ -14,24 +17,23 @@ export interface LandingEffect {
14
17
  export interface LandingFacts {
15
18
  readonly actor?: ParticipantFact;
16
19
  readonly standing: readonly ParticipantFact[];
20
+ readonly seats: ReadonlyMap<string, Seat>;
17
21
  readonly incoming: readonly AttentionFact[];
18
22
  readonly outgoing: readonly AttentionFact[];
19
23
  readonly replyAuthor?: string;
20
24
  }
21
- /** The single landing transition, shared by both adapters and explicit rebuild. */
22
- export declare function deriveLanding(activity: StoredAct, facts: LandingFacts): LandingEffect;
23
- export declare function landingFacts(read: SquareRead, activity: StoredAct): LandingFacts;
24
- export interface LandingWriter extends Pick<SquareWrite, 'markSeen' | 'putLease' | 'removeLease' | 'updateWakeAddress' | 'reattachSeat' | 'detachSeat' | 'cancelThrough' | 'putDeliveryDeferral' | 'renewDeliveryDeferral' | 'releaseDeliveryDeferral' | 'claimDelivery' | 'prepareDelivery' | 'completeDelivery' | 'observeNativeDelivery'> {
25
- /** Artifact-private seat persistence used only by this gate's lifecycle transition. */
25
+ export declare function deriveLanding(activity: ActDraft & {
26
+ readonly index: number;
27
+ }, facts: LandingFacts): LandingEffect;
28
+ export declare function landingFacts(read: SquareRead, activity: ActDraft): LandingFacts;
29
+ export interface LandingWriter extends Pick<SquareWrite, 'putLease' | 'removeLease' | 'markSeen' | 'setSeatAttachment'>, ProducerWrite {
26
30
  storeSeat(seat: Seat): boolean;
27
- removeSeat(seat: import('./ports.js').SeatRef): boolean;
31
+ removeSeat(seat: SeatRef): boolean;
28
32
  applyLanding(effect: LandingEffect): void;
29
33
  }
30
34
  export interface SquareWriteGate {
31
35
  readonly facts: SquareWrite;
32
36
  complete(): void;
33
37
  }
34
- /** Read probes and writes share the same captured-coordinate and attachment fence. */
35
38
  export declare function assertCurrentSeat(read: SquareRead, expected: SeatRef, allowDetached?: boolean): void;
36
- /** The artifact transaction supplies one captured authority; every mutation checks it here. */
37
39
  export declare function wrapSquareWrite(read: SquareRead, writes: LandingWriter, authority: ArtifactWriteAuthority): SquareWriteGate;
@@ -1,61 +1,47 @@
1
- import { SquareError, formatActivityId, parseActivityId, sameName, validateActivityDelivery, validateOwner, validateName } from './model.js';
1
+ import { SquareError, nameKey, sameName, validateOwner, validateName } from './model.js';
2
+ import { newEndpointGeneration, validateEndpointGeneration } from './message.js';
2
3
  import { resolveAudience } from './attention.js';
3
- /** The single landing transition, shared by both adapters and explicit rebuild. */
4
+ import { freshWatchLease } from './runtime.js';
5
+ import { leaseOwnsNotification } from './catch-selection.js';
4
6
  export function deriveLanding(activity, facts) {
5
- if (activity.kind === 'say' && activity.delivery?.mode === 'direct') {
6
- const audience = [];
7
- for (const mention of activity.mentions ?? []) {
8
- const recipient = facts.standing.find(item => sameName(item.name, mention))?.name;
9
- if (recipient !== undefined && !audience.some(item => sameName(item.recipient, recipient)))
10
- audience.push({ recipient, route: 'mention' });
11
- }
12
- return { activity, audience };
13
- }
14
7
  const audience = activity.kind === 'say' ? resolveAudience({
15
- actor: activity.actor, recipients: facts.standing.map((item) => item.name),
16
- mentions: activity.mentions, replyAuthor: facts.replyAuthor, reach: activity.reach,
17
- incoming: facts.incoming, sameIdentity: sameName,
8
+ actor: activity.actor, recipients: facts.standing.map(item => item.name), mentions: activity.mentions,
9
+ replyAuthor: facts.replyAuthor, reach: activity.reach, incoming: facts.incoming, sameIdentity: sameName,
10
+ }).map(recipient => {
11
+ const seat = facts.seats.get(nameKey(recipient.recipient));
12
+ return { ...recipient, ...(seat === undefined ? {} : { generation: seat.generation, owner: seat.owner }) };
18
13
  }) : [];
19
- const actor = activity.actor;
20
- const previous = facts.actor ?? { name: actor, firstIndex: activity.index, joined: false, done: false, sayCount: 0 };
14
+ const previous = facts.actor ?? { name: activity.actor, firstIndex: activity.index, joined: false, done: false, sayCount: 0 };
21
15
  const participant = {
22
- ...previous,
23
- lastActiveAt: Math.max(previous.lastActiveAt ?? -Infinity, activity.at),
16
+ ...previous, lastActiveAt: Math.max(previous.lastActiveAt ?? -Infinity, activity.at),
24
17
  ...(activity.kind === 'join' ? { joined: true, done: false, lastJoinIndex: activity.index } : {}),
25
18
  ...(activity.kind === 'done' ? { joined: false, done: true } : {}),
26
19
  ...(activity.kind === 'say' ? { sayCount: previous.sayCount + 1 } : {}),
27
20
  };
28
21
  let replaceAttention;
29
22
  if (activity.kind === 'listen' || activity.kind === 'ignore') {
30
- const existing = facts.outgoing.find((item) => sameName(item.target, activity.target));
31
- if (existing?.kind !== activity.kind) {
23
+ const existing = facts.outgoing.find(item => sameName(item.target, activity.target));
24
+ if (existing?.kind !== activity.kind)
32
25
  replaceAttention = { listener: participant.name, target: activity.target, kind: activity.kind, sinceIndex: activity.index };
33
- }
34
26
  }
35
- const removeListening = activity.kind === 'unlisten'
36
- && facts.outgoing.some((item) => item.kind === 'listen' && sameName(item.target, activity.target))
27
+ const removeListening = activity.kind === 'unlisten' && facts.outgoing.some(item => item.kind === 'listen' && sameName(item.target, activity.target))
37
28
  ? { listener: participant.name, target: activity.target } : undefined;
38
- return {
39
- activity, participant, audience,
40
- ...(activity.kind === 'done' ? { removeListeningOf: participant.name } : {}),
41
- ...(removeListening === undefined ? {} : { removeListening }),
42
- ...(replaceAttention === undefined ? {} : { replaceAttention }),
43
- };
29
+ return { activity, participant, audience, ...(activity.kind === 'done' ? { removeListeningOf: participant.name } : {}),
30
+ ...(removeListening === undefined ? {} : { removeListening }), ...(replaceAttention === undefined ? {} : { replaceAttention }) };
44
31
  }
45
32
  export function landingFacts(read, activity) {
46
- return {
47
- actor: read.participant(activity.actor),
48
- standing: activity.kind === 'say' ? read.roster().filter((item) => item.joined) : [],
33
+ const standing = activity.kind === 'say' ? read.roster().filter(item => item.joined) : [];
34
+ const seats = new Map();
35
+ for (const participant of standing) {
36
+ const seat = read.seat(participant.name);
37
+ if (seat !== undefined)
38
+ seats.set(nameKey(participant.name), seat);
39
+ }
40
+ return { actor: read.participant(activity.actor), standing, seats,
49
41
  incoming: activity.kind === 'say' ? read.incomingAttention(activity.actor) : [],
50
42
  outgoing: activity.kind === 'listen' || activity.kind === 'ignore' || activity.kind === 'unlisten' ? read.attention(activity.actor) : [],
51
- replyAuthor: activity.kind === 'say' && activity.reply !== undefined ? read.activity(activity.reply)?.actor : undefined,
52
- };
53
- }
54
- function validateGeneration(value) {
55
- if (typeof value !== 'string' || parseActivityId(value) === undefined)
56
- throw new SquareError('invalid_args', 'Artifact authority requires a valid join generation.');
43
+ replyAuthor: activity.kind === 'say' && activity.reply !== undefined ? read.activity(activity.reply)?.actor : undefined };
57
44
  }
58
- /** Read probes and writes share the same captured-coordinate and attachment fence. */
59
45
  export function assertCurrentSeat(read, expected, allowDetached = false) {
60
46
  const current = read.seat(expected.participant);
61
47
  if (current === undefined || current.owner !== expected.owner || current.generation !== expected.generation)
@@ -63,234 +49,130 @@ export function assertCurrentSeat(read, expected, allowDetached = false) {
63
49
  if (!allowDetached && current.attachment !== 'attached')
64
50
  throw new SquareError('already_joined', 'This participant is away; join again before writing.', { reason: 'needs_seat', activityUnsent: true });
65
51
  }
66
- /** The artifact transaction supplies one captured authority; every mutation checks it here. */
67
52
  export function wrapSquareWrite(read, writes, authority) {
68
- if (authority === undefined || typeof authority !== 'object' || authority === null)
53
+ if (!authority || typeof authority !== 'object')
69
54
  throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
70
- const { applyLanding, ...mutations } = writes;
55
+ const denied = () => { throw new SquareError('invalid_args', 'This authority cannot perform that write.'); };
56
+ const deniedWrites = { land: denied, putLease: denied, removeLease: denied, markSeen: denied, setSeatAttachment: denied, insertNotice: denied, ackMatching: denied, acquirePost: denied, settlePost: denied, foldReceipt: denied };
57
+ if (authority.kind === 'producer') {
58
+ return { facts: Object.assign({}, read, deniedWrites, {
59
+ acquirePost(post, token, pid, now, exportAt) {
60
+ if (post.kind === 'activity') {
61
+ const lease = freshWatchLease(read, post.recipient, now);
62
+ const arrival = read.directedAt(post.recipient, post.index);
63
+ if (lease && arrival?.generation === post.generation && leaseOwnsNotification(lease, arrival))
64
+ return false;
65
+ }
66
+ return writes.acquirePost(post, token, pid, now, exportAt);
67
+ },
68
+ settlePost: writes.settlePost, foldReceipt: writes.foldReceipt, setSeatAttachment: writes.setSeatAttachment,
69
+ }), complete() { } };
70
+ }
71
+ if (authority.kind === 'send') {
72
+ validateName(authority.actor);
73
+ return { facts: Object.assign({}, read, deniedWrites, { insertNotice(input, at) {
74
+ if (!sameName(input.from, authority.actor))
75
+ throw new SquareError('invalid_args', 'Notice sender does not match its authority.');
76
+ return writes.insertNotice(input, at);
77
+ } }), complete() { } };
78
+ }
71
79
  const seated = authority.kind === 'participant' || authority.kind === 'host-cleanup';
72
- const expectedName = seated ? authority.seat?.participant : authority.kind === 'send' ? authority.actor : authority.participant;
73
- if (!['participant', 'host-cleanup', 'join', 'takeover', 'send'].includes(authority.kind) || typeof expectedName !== 'string')
74
- throw new SquareError('invalid_args', 'Artifact write authority is malformed.');
75
- validateName(expectedName);
80
+ const name = seated ? authority.seat.participant : authority.participant;
81
+ validateName(name);
76
82
  if (seated) {
77
83
  validateOwner(authority.seat.owner);
78
- validateGeneration(authority.seat.generation);
84
+ validateEndpointGeneration(authority.seat.generation);
79
85
  }
80
- else if (authority.kind !== 'send') {
86
+ else {
81
87
  validateOwner(authority.owner);
82
88
  if (authority.kind === 'takeover')
83
- validateGeneration(authority.expectedGeneration);
89
+ validateEndpointGeneration(authority.expectedGeneration);
84
90
  }
85
- const assertSeat = (seat, expected = authority.kind === 'participant' ? authority.seat : undefined, allowDetached = false) => {
86
- if (expected === undefined || !sameName(expected.participant, seat.participant)
87
- || seat.generation !== expected.generation || seat.owner !== expected.owner)
88
- throw new SquareError('already_joined', 'This write needs the current participant seat.');
89
- assertCurrentSeat(read, expected, allowDetached);
90
- };
91
- const assertParticipant = (name, generation) => {
92
- if (authority.kind !== 'participant')
91
+ if (authority.kind === 'participant')
92
+ assertCurrentSeat(read, authority.seat);
93
+ if (authority.kind === 'join' && read.participant(name)?.joined) {
94
+ const seat = read.seat(name);
95
+ if (seat === undefined || seat.owner !== authority.owner)
96
+ throw new SquareError('already_joined', `${name} is held by another owner; take it over explicitly`);
97
+ }
98
+ if (authority.kind === 'takeover' && read.seat(name)?.generation !== authority.expectedGeneration)
99
+ throw new SquareError('already_joined', 'The participant seat changed before takeover.');
100
+ let takeoverPhase = 'ready';
101
+ let establishedSeat;
102
+ const participant = (requested, generation) => {
103
+ if (authority.kind !== 'participant' || !sameName(requested, name) || generation !== undefined && generation !== authority.seat.generation)
93
104
  throw new SquareError('already_joined', 'This write needs the current participant seat.');
94
- assertSeat({ ...authority.seat, participant: name, generation: generation === undefined ? authority.seat.generation : generation });
105
+ assertCurrentSeat(read, authority.seat);
95
106
  };
96
- let reconnectSeat;
97
- let takeoverPhase = authority.kind === 'takeover' ? 'ready' : undefined;
98
- if (authority.kind === 'participant') {
99
- assertSeat(authority.seat);
100
- }
101
- else if (authority.kind === 'host-cleanup') {
102
- // A late host cleanup is allowed to inspect a stale coordinate and return a no-op.
103
- }
104
- else if (authority.kind === 'join') {
105
- const participant = read.participant(expectedName), seat = read.seat(expectedName);
106
- if (participant?.joined) {
107
- if (seat === undefined || seat.owner !== authority.owner)
108
- throw new SquareError('already_joined', `${expectedName} is held by another owner; take it over explicitly`);
109
- reconnectSeat = seat;
110
- }
111
- }
112
- else if (authority.kind === 'takeover') {
113
- const current = read.seat(expectedName);
114
- if (current === undefined || current.generation !== authority.expectedGeneration)
115
- throw new SquareError('already_joined', 'The participant seat changed before takeover.');
116
- }
117
- let lastActivity;
118
- const facts = Object.assign({}, read, {
107
+ const facts = Object.assign({}, read, deniedWrites, {
119
108
  land(activity) {
120
- const actor = 'actor' in activity ? activity.actor : undefined;
121
- if (actor === undefined)
122
- throw new SquareError('invalid_args', 'Participant activities require a named actor.');
123
- validateName(actor);
124
- if (!sameName(actor, expectedName))
109
+ if (!sameName(activity.actor, name))
125
110
  throw new SquareError('already_joined', 'Activity actor does not match the artifact authority.');
126
- if (activity.kind === 'say')
127
- validateActivityDelivery(activity.delivery);
128
- if (authority.kind === 'send') {
129
- if (activity.kind !== 'say' || activity.delivery?.mode !== 'direct' || activity.reach !== undefined || activity.reply !== undefined)
130
- throw new SquareError('invalid_args', 'Send authority may only land direct messages without bell or reply.');
131
- }
132
- else if (authority.kind === 'participant') {
133
- assertSeat(authority.seat);
111
+ if (authority.kind === 'participant') {
112
+ assertCurrentSeat(read, authority.seat);
134
113
  if (activity.kind === 'join')
135
- throw new SquareError('invalid_args', 'A participant seat can only be re-established by join.');
114
+ throw new SquareError('invalid_args', 'A participant seat can only be established by join.');
136
115
  }
137
116
  else if (authority.kind === 'host-cleanup') {
138
- assertSeat(authority.seat, authority.seat, true);
117
+ assertCurrentSeat(read, authority.seat, true);
139
118
  if (activity.kind !== 'done')
140
119
  throw new SquareError('invalid_args', 'Host cleanup may only end membership.');
141
120
  }
142
121
  else if (authority.kind === 'join') {
143
- if (activity.kind !== 'join')
144
- throw new SquareError('invalid_args', 'A new seat must be established by its join activity.');
145
- if (read.participant(expectedName)?.joined)
146
- throw new SquareError('already_joined', `${expectedName} is already standing; join cannot replace an existing seat.`);
122
+ if (activity.kind !== 'join' || read.participant(name)?.joined)
123
+ throw new SquareError('already_joined', 'A new seat requires a new join.');
147
124
  }
148
125
  else if (takeoverPhase === 'ready') {
149
- const current = read.seat(expectedName);
150
- if (activity.kind !== 'done' || current === undefined || current.generation !== authority.expectedGeneration)
126
+ if (activity.kind !== 'done' || read.seat(name)?.generation !== authority.expectedGeneration)
151
127
  throw new SquareError('already_joined', 'Takeover must first complete the captured participant.');
152
128
  }
153
- else if (authority.kind === 'takeover' && takeoverPhase === 'done') {
154
- if (activity.kind !== 'join' || !read.participant(expectedName)?.done)
129
+ else if (takeoverPhase === 'done') {
130
+ if (activity.kind !== 'join' || !read.participant(name)?.done)
155
131
  throw new SquareError('already_joined', 'Takeover must join after its captured done activity.');
156
132
  }
157
- else {
133
+ else
158
134
  throw new SquareError('invalid_args', 'Takeover permits only one done followed by one join.');
159
- }
160
135
  const index = read.header().nextActIndex;
161
136
  if (!Number.isSafeInteger(index) || index < 0 || index >= Number.MAX_SAFE_INTEGER)
162
137
  throw new SquareError('invalid_args', 'Square activity index is exhausted.');
163
- const captured = authority.kind === 'takeover' && activity.kind === 'done'
164
- ? read.seat(expectedName) : undefined;
138
+ const captured = authority.kind === 'takeover' && activity.kind === 'done' ? read.seat(name) : undefined;
165
139
  const stored = { ...activity, index };
166
- applyLanding(deriveLanding(stored, landingFacts(read, stored)));
140
+ writes.applyLanding(deriveLanding(stored, landingFacts(read, activity)));
167
141
  if (stored.kind === 'join') {
168
142
  if (authority.kind !== 'join' && authority.kind !== 'takeover')
169
143
  throw new SquareError('invalid_args', 'Seat authority cannot establish another seat.');
170
- const seat = {
171
- participant: stored.actor,
172
- owner: authority.owner,
173
- generation: formatActivityId(stored.index),
174
- attachment: 'attached',
175
- ...(authority.wakeAddress === undefined ? {} : { wakeAddress: authority.wakeAddress }),
176
- };
177
- mutations.storeSeat(seat);
178
- const established = read.seat(seat.participant);
179
- if (established === undefined || established.owner !== seat.owner || established.generation !== seat.generation)
180
- throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
144
+ const seat = { participant: stored.actor, owner: authority.owner, generation: newEndpointGeneration(), attachment: 'attached', ...(authority.wakeAddress === undefined ? {} : { wakeAddress: authority.wakeAddress }) };
145
+ writes.storeSeat(seat);
146
+ establishedSeat = seat;
181
147
  if (authority.kind === 'takeover')
182
148
  takeoverPhase = 'joined';
183
149
  }
184
150
  else if (stored.kind === 'done' && seated) {
185
- if (!mutations.removeSeat(authority.seat))
186
- throw new SquareError('already_joined', 'The participant seat changed while it was being completed.');
151
+ if (!writes.removeSeat(authority.seat))
152
+ throw new SquareError('already_joined', 'The participant seat changed while completing it.');
187
153
  }
188
154
  else if (stored.kind === 'done' && authority.kind === 'takeover') {
189
- if (captured === undefined || !mutations.removeSeat({ participant: expectedName, owner: captured.owner, generation: captured.generation }))
190
- throw new SquareError('already_joined', 'The participant seat changed while takeover was completing it.');
155
+ if (captured === undefined || !writes.removeSeat(captured))
156
+ throw new SquareError('already_joined', 'The participant seat changed while takeover completed it.');
191
157
  takeoverPhase = 'done';
192
158
  }
193
- lastActivity = read.activity(index);
194
- return lastActivity;
195
- },
196
- updateWakeAddress(seat, wakeAddress) {
197
- if (authority.kind !== 'join' && authority.kind !== 'participant')
198
- throw new SquareError('already_joined', 'This write needs the current participant seat.');
199
- assertSeat(seat, authority.kind === 'join' ? reconnectSeat : authority.seat);
200
- return mutations.updateWakeAddress(seat, wakeAddress);
201
- },
202
- reattachSeat(seat, wakeAddress) {
203
- if (authority.kind !== 'join')
204
- throw new SquareError('already_joined', 'Reattachment requires a same-owner join.');
205
- assertSeat(seat, reconnectSeat, true);
206
- return mutations.reattachSeat(seat, wakeAddress);
207
- },
208
- detachSeat(seat) {
209
- if (authority.kind !== 'host-cleanup')
210
- throw new SquareError('already_joined', 'Detach requires captured host cleanup authority.');
211
- const current = read.seat(seat.participant), expected = authority.seat;
212
- if (!sameName(seat.participant, expected.participant) || seat.owner !== expected.owner || seat.generation !== expected.generation)
213
- throw new SquareError('already_joined', 'Host cleanup does not match its captured seat.');
214
- if (current === undefined || current.attachment !== 'attached' || current.owner !== expected.owner
215
- || current.generation !== expected.generation)
216
- return false;
217
- return mutations.detachSeat(seat);
218
- },
219
- cancelThrough(seat, through) {
220
- assertSeat(seat);
221
- if (!Number.isSafeInteger(through) || through < 0 || through >= read.header().nextActIndex)
222
- throw new SquareError('invalid_args', 'Cancellation boundary must be an assigned activity index below the allocator.');
223
- return mutations.cancelThrough(seat, through);
224
- },
225
- markSeen(name, indexes, at) {
226
- assertParticipant(name);
227
- return mutations.markSeen(name, indexes, at);
228
- },
229
- putLease(name, lease, generation) {
230
- assertParticipant(name, generation);
231
- return mutations.putLease(name, lease, generation);
232
- },
233
- removeLease(name, leaseId, generation) {
234
- assertParticipant(name, generation);
235
- return mutations.removeLease(name, leaseId, generation);
236
- },
237
- putDeliveryDeferral(...args) {
238
- assertSeat(args[0]);
239
- return mutations.putDeliveryDeferral(...args);
240
- },
241
- renewDeliveryDeferral(...args) {
242
- assertSeat(args[0]);
243
- return mutations.renewDeliveryDeferral(...args);
244
- },
245
- releaseDeliveryDeferral(...args) {
246
- assertSeat(args[0]);
247
- return mutations.releaseDeliveryDeferral(...args);
248
- },
249
- claimDelivery(input) {
250
- assertSeat(input.seat);
251
- const evidence = read.deliveryEvidence(input.seat.participant, input.seat.generation, input.activity);
252
- if (input.observationOf !== undefined) {
253
- // Receiver adapters validate native identity/content before entering this transaction.
254
- // This path records that existing message, rather than acquiring a second send.
255
- if (input.kind !== 'presentation' || !evidence.some(row => row.kind === 'wake'
256
- && row.claimToken === input.observationOf && ['dispatching', 'accepted', 'unknown'].includes(row.outcome)))
257
- return { status: 'stale' };
258
- }
259
- else {
260
- const blocker = evidence.find(row => row.kind !== input.kind
261
- && row.outcome !== 'failed' && row.outcome !== 'released');
262
- if (blocker !== undefined) {
263
- if (blocker.outcome === 'dispatching' && blocker.expiresAt !== undefined && blocker.expiresAt <= input.at) {
264
- // Normalize abandoned custody to unknown in the same transaction, never steal it.
265
- return mutations.claimDelivery({ ...input, kind: blocker.kind });
266
- }
267
- return { status: blocker.outcome === 'dispatching' ? 'busy' : 'terminal', evidence: blocker };
268
- }
269
- }
270
- return mutations.claimDelivery(input);
271
- },
272
- prepareDelivery(input) {
273
- assertSeat(input.seat);
274
- return mutations.prepareDelivery(input);
275
- },
276
- completeDelivery(input) {
277
- assertSeat(input.seat);
278
- return mutations.completeDelivery(input);
279
- },
280
- observeNativeDelivery(input) {
281
- assertSeat(input.seat);
282
- return mutations.observeNativeDelivery(input);
283
- },
159
+ // Return the persisted, detached fact rather than freezing the caller's draft.
160
+ const landed = read.activity(index);
161
+ if (landed === undefined)
162
+ throw new SquareError('invalid_args', 'The activity did not land.');
163
+ return landed;
164
+ },
165
+ putLease(name, lease, generation) { participant(name, generation); return writes.putLease(name, lease, generation); },
166
+ removeLease(name, leaseId, generation) { participant(name, generation); return writes.removeLease(name, leaseId, generation); },
167
+ markSeen(name, indexes, at) { participant(name); return writes.markSeen(name, indexes, at); },
168
+ ackMatching(seat, input, at) { participant(seat.participant, seat.generation); if (authority.kind !== 'participant' || authority.seat.owner !== seat.owner)
169
+ throw new SquareError('already_joined', 'Seat owner changed.'); return writes.ackMatching(seat, input, at); },
284
170
  });
285
- return {
286
- facts,
287
- complete() {
171
+ return { facts, complete() {
288
172
  if (authority.kind !== 'takeover')
289
173
  return;
290
- const seat = read.seat(expectedName);
291
- if (takeoverPhase !== 'joined' || lastActivity?.kind !== 'join' || seat === undefined
292
- || seat.owner !== authority.owner || seat.generation !== formatActivityId(lastActivity.index))
174
+ const seat = read.seat(name);
175
+ if (takeoverPhase !== 'joined' || seat === undefined || establishedSeat === undefined || seat.owner !== establishedSeat.owner || seat.generation !== establishedSeat.generation)
293
176
  throw new SquareError('already_joined', 'Takeover did not complete its replacement join.');
294
- },
295
- };
177
+ } };
296
178
  }