@astrosheep/square 0.4.0 → 0.4.2

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 (196) hide show
  1. package/dist/artifact.d.ts +1 -3
  2. package/dist/artifact.js +12 -36
  3. package/dist/attention-presentation.d.ts +2 -22
  4. package/dist/attention-presentation.js +9 -12
  5. package/dist/boundary-presentation.d.ts +3 -14
  6. package/dist/boundary-presentation.js +29 -104
  7. package/dist/card-presentation.d.ts +1 -0
  8. package/dist/card-presentation.js +2 -0
  9. package/dist/catch-decisions.js +1 -7
  10. package/dist/claude-boundary-state.d.ts +20 -0
  11. package/dist/claude-boundary-state.js +84 -0
  12. package/dist/claude-hook.d.ts +2 -0
  13. package/dist/claude-hook.js +231 -0
  14. package/dist/claude-mcp-context.d.ts +1 -1
  15. package/dist/claude-mcp-context.js +12 -3
  16. package/dist/cli/context.js +7 -9
  17. package/dist/cli/observation-commands.d.ts +4 -1
  18. package/dist/cli/observation-commands.js +37 -4
  19. package/dist/cli/registry.js +5 -2
  20. package/dist/codex-address.d.ts +2 -2
  21. package/dist/codex-boundary-state.d.ts +1 -5
  22. package/dist/codex-boundary-state.js +0 -4
  23. package/dist/codex-hook.d.ts +7 -10
  24. package/dist/codex-hook.js +69 -33
  25. package/dist/codex-queue.d.ts +10 -11
  26. package/dist/codex-queue.js +32 -32
  27. package/dist/codex-watch.d.ts +2 -0
  28. package/dist/codex-watch.js +100 -0
  29. package/dist/decisions.js +2 -2
  30. package/dist/delivery-deferral.d.ts +8 -17
  31. package/dist/delivery-deferral.js +0 -28
  32. package/dist/delivery-frame.d.ts +7 -0
  33. package/dist/delivery-frame.js +11 -0
  34. package/dist/delivery-ledger-sqlite.d.ts +97 -0
  35. package/dist/delivery-ledger-sqlite.js +496 -0
  36. package/dist/delivery-ledger.d.ts +196 -0
  37. package/dist/delivery-ledger.js +268 -0
  38. package/dist/delivery-lifetime.d.ts +6 -0
  39. package/dist/delivery-lifetime.js +45 -0
  40. package/dist/harness-claude.js +3 -4
  41. package/dist/harness-links.js +63 -7
  42. package/dist/harness.js +12 -17
  43. package/dist/help.js +4 -1
  44. package/dist/http-delivery.d.ts +5 -9
  45. package/dist/http-delivery.js +20 -77
  46. package/dist/inbox.d.ts +6 -49
  47. package/dist/inbox.js +24 -371
  48. package/dist/index.d.ts +6 -3
  49. package/dist/index.js +4 -1
  50. package/dist/mcp.js +2 -3
  51. package/dist/model.d.ts +7 -47
  52. package/dist/model.js +1 -7
  53. package/dist/notifications.d.ts +24 -20
  54. package/dist/notifications.js +39 -108
  55. package/dist/operation-control.d.ts +4 -0
  56. package/dist/operation-control.js +20 -0
  57. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  58. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  59. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  60. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  61. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  62. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  63. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  64. package/dist/packages/agent-delivery/src/codex.js +41 -4
  65. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  66. package/dist/packages/agent-delivery/src/index.js +1 -1
  67. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  68. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  69. package/dist/paseo-delivery.d.ts +5 -5
  70. package/dist/paseo-delivery.js +19 -97
  71. package/dist/paseo.d.ts +1 -1
  72. package/dist/paseo.js +1 -1
  73. package/dist/plugins/opencode/index.d.ts +5 -3
  74. package/dist/plugins/opencode/index.js +99 -21
  75. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  76. package/dist/plugins/opencode-shared/delivery.js +109 -0
  77. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  78. package/dist/plugins/opencode-v1/index.js +41 -0
  79. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  80. package/dist/plugins/opencode-v1/runtime.js +152 -0
  81. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  82. package/dist/plugins/opencode-v1/transport.js +50 -0
  83. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  84. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  85. package/dist/ports.d.ts +31 -90
  86. package/dist/session-lifecycle.d.ts +8 -7
  87. package/dist/session-lifecycle.js +40 -42
  88. package/dist/sqlite-square.d.ts +14 -23
  89. package/dist/sqlite-square.js +132 -139
  90. package/dist/square-api.js +4 -7
  91. package/dist/square-core.d.ts +0 -1
  92. package/dist/square-domain.d.ts +5 -17
  93. package/dist/square-domain.js +104 -221
  94. package/dist/square-facade.d.ts +18 -15
  95. package/dist/square-post.d.ts +53 -0
  96. package/dist/square-post.js +4 -0
  97. package/dist/square-posting.d.ts +13 -0
  98. package/dist/square-posting.js +174 -0
  99. package/dist/square-producer-sqlite.d.ts +11 -0
  100. package/dist/square-producer-sqlite.js +210 -0
  101. package/dist/square-sqlite.d.ts +15 -25
  102. package/dist/square-sqlite.js +150 -226
  103. package/dist/views.d.ts +2 -2
  104. package/dist/views.js +0 -1
  105. package/dist/wake-sink.d.ts +4 -11
  106. package/dist/wake-sink.js +8 -32
  107. package/dist/wake-transports.d.ts +4 -7
  108. package/dist/wake-transports.js +7 -39
  109. package/examples/symmetric-domains.mjs +29 -249
  110. package/guides/change-waits.md +13 -53
  111. package/guides/claude-native.md +7 -171
  112. package/guides/codex-delivery.md +55 -80
  113. package/guides/delivery-primitives.md +48 -207
  114. package/guides/mcp.md +1 -1
  115. package/guides/operation-cancellation.md +2 -5
  116. package/guides/participant-ownership.md +25 -89
  117. package/guides/pi-native-delivery.md +19 -56
  118. package/guides/pi-tools.md +6 -8
  119. package/guides/square-api.md +113 -615
  120. package/package.json +8 -8
  121. package/plugins/claude/.claude-plugin/plugin.json +2 -3
  122. package/plugins/claude/hooks/hooks.json +11 -1
  123. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  124. package/plugins/codex/hooks/hooks.json +11 -23
  125. package/plugins/opencode/index.ts +91 -23
  126. package/plugins/opencode-shared/delivery.ts +91 -0
  127. package/plugins/opencode-v1/index.ts +36 -0
  128. package/plugins/opencode-v1/runtime.ts +134 -0
  129. package/plugins/opencode-v1/transport.ts +41 -0
  130. package/plugins/paseo/OVERVIEW.md +5 -5
  131. package/plugins/paseo/README.md +7 -6
  132. package/plugins/paseo/client/markdown.tsx +5 -5
  133. package/plugins/paseo/client/square-message.tsx +28 -24
  134. package/plugins/paseo/client/transform.ts +8 -5
  135. package/plugins/paseo/shared/card-presentation.ts +16 -0
  136. package/plugins/paseo/shared/message.ts +6 -11
  137. package/plugins/pi/square-pi.js +156 -417
  138. package/dist/channel-artifact.d.ts +0 -10
  139. package/dist/channel-artifact.js +0 -55
  140. package/dist/channel-delivery.d.ts +0 -11
  141. package/dist/channel-delivery.js +0 -51
  142. package/dist/channel-domain.d.ts +0 -3
  143. package/dist/channel-domain.js +0 -83
  144. package/dist/channel-facade.d.ts +0 -76
  145. package/dist/channel-facade.js +0 -1
  146. package/dist/channel-internal.d.ts +0 -126
  147. package/dist/channel-internal.js +0 -440
  148. package/dist/channel-metadata.d.ts +0 -11
  149. package/dist/channel-metadata.js +0 -1
  150. package/dist/channel-ports.d.ts +0 -241
  151. package/dist/channel-ports.js +0 -1
  152. package/dist/channel-presentation.d.ts +0 -17
  153. package/dist/channel-presentation.js +0 -22
  154. package/dist/channel-sqlite-schema.d.ts +0 -13
  155. package/dist/channel-sqlite-schema.js +0 -81
  156. package/dist/channel-transport.d.ts +0 -62
  157. package/dist/channel-transport.js +0 -1
  158. package/dist/channel.d.ts +0 -84
  159. package/dist/channel.js +0 -60
  160. package/dist/claude-delivery-status.d.ts +0 -13
  161. package/dist/claude-delivery-status.js +0 -46
  162. package/dist/claude-delivery.d.ts +0 -12
  163. package/dist/claude-delivery.js +0 -108
  164. package/dist/claude-mod.d.ts +0 -8
  165. package/dist/claude-mod.js +0 -172
  166. package/dist/claude-transport.d.ts +0 -5
  167. package/dist/claude-transport.js +0 -58
  168. package/dist/cmd/notify-once.d.ts +0 -2
  169. package/dist/cmd/notify-once.js +0 -29
  170. package/dist/delivery-attempts.d.ts +0 -52
  171. package/dist/delivery-attempts.js +0 -73
  172. package/dist/delivery-effects.d.ts +0 -139
  173. package/dist/delivery-effects.js +0 -163
  174. package/dist/delivery-health.d.ts +0 -51
  175. package/dist/delivery-health.js +0 -173
  176. package/dist/delivery-operations.d.ts +0 -22
  177. package/dist/delivery-operations.js +0 -198
  178. package/dist/delivery-primitives.d.ts +0 -5
  179. package/dist/delivery-primitives.js +0 -3
  180. package/dist/delivery.d.ts +0 -56
  181. package/dist/delivery.js +0 -80
  182. package/dist/plugins/opencode/delivery.d.ts +0 -19
  183. package/dist/plugins/opencode/delivery.js +0 -223
  184. package/dist/presentation-operations.d.ts +0 -5
  185. package/dist/presentation-operations.js +0 -86
  186. package/dist/sqlite-channel.d.ts +0 -15
  187. package/dist/sqlite-channel.js +0 -600
  188. package/dist/square-index.d.ts +0 -16
  189. package/dist/square-index.js +0 -106
  190. package/dist/wake-preview.d.ts +0 -7
  191. package/dist/wake-preview.js +0 -15
  192. package/plugins/claude/hooks/register.js +0 -132
  193. package/plugins/claude/types.d.ts +0 -7
  194. package/plugins/opencode/delivery.ts +0 -198
  195. /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
  196. /package/dist/{channel-values.js → square-values.js} +0 -0
@@ -1,91 +1,47 @@
1
1
  import { SquareError, nameKey, sameName, validateOwner, validateName } from './model.js';
2
- import { wrapChannelWrite } from './channel-domain.js';
3
- import { newEndpointGeneration } from './message.js';
2
+ import { newEndpointGeneration, validateEndpointGeneration } from './message.js';
4
3
  import { resolveAudience } from './attention.js';
5
- import { validateEndpointGeneration as endpointGeneration } from './message.js';
4
+ import { freshWatchLease } from './runtime.js';
6
5
  import { leaseOwnsNotification } from './catch-selection.js';
7
- /** Pending messages the live catch currently owns; the lower pipeline only needs this coverage. */
8
- export function catchCoverage(read, name, lease) {
9
- const covered = [];
10
- let cursor = -1;
11
- for (;;) {
12
- const page = read.channel.directed(name, { afterIndex: cursor, unseenOnly: true, currentGeneration: true }, { edge: 'oldest', limit: 100 });
13
- for (const head of page.items) {
14
- const relation = read.activityByMessage(head.index);
15
- const route = relation === undefined ? undefined : read.directedAt(name, relation.index)?.route;
16
- if (route !== undefined && leaseOwnsNotification(lease, { actor: head.from, route, recipient: name }))
17
- covered.push(head.index);
18
- }
19
- if (!page.hasMore || page.items.length === 0)
20
- break;
21
- cursor = page.items[page.items.length - 1].index;
22
- }
23
- return covered;
24
- }
25
- /** Artifact-private landing effect; sender capabilities never expose another recipient's claim. */
26
- export function extendLandingCatchCoverage(read, putClaim, activity, audience) {
27
- if (activity.kind !== 'say')
28
- return;
29
- const index = activity.messageIndex;
30
- for (const recipient of audience) {
31
- const claim = read.channel.claimLease(recipient.recipient, 'agora');
32
- if (claim === undefined || claim.expiresAt <= activity.at || recipient.generation !== undefined && claim.generation !== recipient.generation)
33
- continue;
34
- const lease = read.lease(recipient.recipient);
35
- if (lease === undefined || lease.leaseId !== claim.id
36
- || !leaseOwnsNotification(lease, { actor: activity.actor, route: recipient.route, recipient: recipient.recipient }))
37
- continue;
38
- if (!claim.suppressed.includes(index))
39
- putClaim({ ...claim, suppressed: [...claim.suppressed, index] });
40
- }
41
- }
42
- /** The single landing transition, shared by both adapters and explicit rebuild. */
43
6
  export function deriveLanding(activity, facts) {
44
7
  const audience = activity.kind === 'say' ? resolveAudience({
45
- actor: activity.actor, recipients: facts.standing.map((item) => item.name),
46
- mentions: activity.mentions, replyAuthor: facts.replyAuthor, reach: activity.reach,
47
- incoming: facts.incoming, sameIdentity: sameName,
48
- }).map(recipient => ({ ...recipient, generation: facts.generations.get(nameKey(recipient.recipient)) })) : [];
49
- const actor = activity.actor;
50
- const previous = facts.actor ?? { name: actor, firstIndex: activity.index, joined: false, done: false, sayCount: 0 };
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 }) };
13
+ }) : [];
14
+ const previous = facts.actor ?? { name: activity.actor, firstIndex: activity.index, joined: false, done: false, sayCount: 0 };
51
15
  const participant = {
52
- ...previous,
53
- lastActiveAt: Math.max(previous.lastActiveAt ?? -Infinity, activity.at),
16
+ ...previous, lastActiveAt: Math.max(previous.lastActiveAt ?? -Infinity, activity.at),
54
17
  ...(activity.kind === 'join' ? { joined: true, done: false, lastJoinIndex: activity.index } : {}),
55
18
  ...(activity.kind === 'done' ? { joined: false, done: true } : {}),
56
19
  ...(activity.kind === 'say' ? { sayCount: previous.sayCount + 1 } : {}),
57
20
  };
58
21
  let replaceAttention;
59
22
  if (activity.kind === 'listen' || activity.kind === 'ignore') {
60
- const existing = facts.outgoing.find((item) => sameName(item.target, activity.target));
61
- if (existing?.kind !== activity.kind) {
23
+ const existing = facts.outgoing.find(item => sameName(item.target, activity.target));
24
+ if (existing?.kind !== activity.kind)
62
25
  replaceAttention = { listener: participant.name, target: activity.target, kind: activity.kind, sinceIndex: activity.index };
63
- }
64
26
  }
65
- const removeListening = activity.kind === 'unlisten'
66
- && 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))
67
28
  ? { listener: participant.name, target: activity.target } : undefined;
68
- return {
69
- activity, participant, audience,
70
- ...(activity.kind === 'done' ? { removeListeningOf: participant.name } : {}),
71
- ...(removeListening === undefined ? {} : { removeListening }),
72
- ...(replaceAttention === undefined ? {} : { replaceAttention }),
73
- };
29
+ return { activity, participant, audience, ...(activity.kind === 'done' ? { removeListeningOf: participant.name } : {}),
30
+ ...(removeListening === undefined ? {} : { removeListening }), ...(replaceAttention === undefined ? {} : { replaceAttention }) };
74
31
  }
75
32
  export function landingFacts(read, activity) {
76
- return {
77
- actor: read.participant(activity.actor),
78
- generations: new Map(read.channel.endpoints().map(endpoint => [nameKey(endpoint.participant), endpoint.generation])),
79
- 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,
80
41
  incoming: activity.kind === 'say' ? read.incomingAttention(activity.actor) : [],
81
42
  outgoing: activity.kind === 'listen' || activity.kind === 'ignore' || activity.kind === 'unlisten' ? read.attention(activity.actor) : [],
82
- replyAuthor: activity.kind === 'say' && activity.reply !== undefined ? read.activity(activity.reply)?.actor : undefined,
83
- };
84
- }
85
- function validateGeneration(value) {
86
- endpointGeneration(value);
43
+ replyAuthor: activity.kind === 'say' && activity.reply !== undefined ? read.activity(activity.reply)?.actor : undefined };
87
44
  }
88
- /** Read probes and writes share the same captured-coordinate and attachment fence. */
89
45
  export function assertCurrentSeat(read, expected, allowDetached = false) {
90
46
  const current = read.seat(expected.participant);
91
47
  if (current === undefined || current.owner !== expected.owner || current.generation !== expected.generation)
@@ -93,203 +49,130 @@ export function assertCurrentSeat(read, expected, allowDetached = false) {
93
49
  if (!allowDetached && current.attachment !== 'attached')
94
50
  throw new SquareError('already_joined', 'This participant is away; join again before writing.', { reason: 'needs_seat', activityUnsent: true });
95
51
  }
96
- function immutableAct(activity) {
97
- const copy = { ...activity, ...(activity.kind === 'say' && activity.mentions !== undefined ? { mentions: [...activity.mentions] } : {}) };
98
- if (copy.kind === 'say' && copy.mentions !== undefined)
99
- Object.freeze(copy.mentions);
100
- return Object.freeze(copy);
101
- }
102
- /** The artifact transaction supplies one captured authority; every mutation checks it here. */
103
52
  export function wrapSquareWrite(read, writes, authority) {
104
- if (authority === undefined || typeof authority !== 'object' || authority === null)
53
+ if (!authority || typeof authority !== 'object')
105
54
  throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
106
- const { applyLanding, channel, ...mutations } = writes;
107
- const seated = authority.kind === 'participant' || authority.kind === 'host-cleanup' || authority.kind === 'endpoint-cleanup';
108
- const expectedName = seated ? authority.seat?.participant : authority.kind === 'send' ? authority.actor
109
- : authority.kind === 'endpoint' ? authority.participant : authority.participant;
110
- if (!['participant', 'host-cleanup', 'endpoint-cleanup', 'endpoint', 'join', 'takeover', 'send'].includes(authority.kind) || typeof expectedName !== 'string')
111
- throw new SquareError('invalid_args', 'Artifact write authority is malformed.');
112
- validateName(expectedName);
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
+ }
79
+ const seated = authority.kind === 'participant' || authority.kind === 'host-cleanup';
80
+ const name = seated ? authority.seat.participant : authority.participant;
81
+ validateName(name);
113
82
  if (seated) {
114
83
  validateOwner(authority.seat.owner);
115
- validateGeneration(authority.seat.generation);
84
+ validateEndpointGeneration(authority.seat.generation);
116
85
  }
117
- else if (authority.kind !== 'send') {
86
+ else {
118
87
  validateOwner(authority.owner);
119
88
  if (authority.kind === 'takeover')
120
- validateGeneration(authority.expectedGeneration);
89
+ validateEndpointGeneration(authority.expectedGeneration);
121
90
  }
122
- const assertSeat = (seat, expected = authority.kind === 'participant' || authority.kind === 'host-cleanup' || authority.kind === 'endpoint-cleanup' ? authority.seat : undefined, allowDetached = false) => {
123
- if (expected === undefined || !sameName(expected.participant, seat.participant)
124
- || seat.generation !== expected.generation || seat.owner !== expected.owner)
125
- throw new SquareError('already_joined', 'This write needs the current participant seat.');
126
- assertCurrentSeat(read, expected, allowDetached);
127
- };
128
- const assertParticipant = (name, generation) => {
129
- 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)
130
104
  throw new SquareError('already_joined', 'This write needs the current participant seat.');
131
- assertSeat({ ...authority.seat, participant: name, generation: generation ?? authority.seat.generation });
105
+ assertCurrentSeat(read, authority.seat);
132
106
  };
133
- let reconnectSeat;
134
- let takeoverPhase = authority.kind === 'takeover' ? 'ready' : undefined;
135
- let establishedSeat;
136
- if (authority.kind === 'participant') {
137
- assertSeat(authority.seat);
138
- }
139
- else if (authority.kind === 'host-cleanup' || authority.kind === 'endpoint-cleanup') {
140
- // A late host cleanup is allowed to inspect a stale coordinate and return a no-op.
141
- }
142
- else if (authority.kind === 'endpoint') {
143
- // Endpoint attach/takeover owns no activity.
144
- }
145
- else if (authority.kind === 'join') {
146
- const participant = read.participant(expectedName), seat = read.seat(expectedName);
147
- if (participant?.joined) {
148
- if (seat === undefined || seat.owner !== authority.owner)
149
- throw new SquareError('already_joined', `${expectedName} is held by another owner; take it over explicitly`);
150
- reconnectSeat = seat;
151
- }
152
- }
153
- else if (authority.kind === 'takeover') {
154
- const current = read.seat(expectedName);
155
- if (current === undefined || current.generation !== authority.expectedGeneration)
156
- throw new SquareError('already_joined', 'The participant seat changed before takeover.');
157
- }
158
- let lastActivity;
159
- const lowerAuthority = authority.kind === 'join' || authority.kind === 'takeover'
160
- ? { kind: 'endpoint', participant: authority.participant, owner: authority.owner }
161
- : authority.kind === 'host-cleanup' ? { kind: 'endpoint-cleanup', seat: authority.seat } : authority;
162
- const gatedChannel = wrapChannelWrite(read.channel, channel, lowerAuthority);
163
- const facts = Object.assign({}, read, {
164
- channel: gatedChannel,
107
+ const facts = Object.assign({}, read, deniedWrites, {
165
108
  land(activity) {
166
- const actor = 'actor' in activity ? activity.actor : undefined;
167
- if (actor === undefined)
168
- throw new SquareError('invalid_args', 'Participant activities require a named actor.');
169
- validateName(actor);
170
- if (!sameName(actor, expectedName))
109
+ if (!sameName(activity.actor, name))
171
110
  throw new SquareError('already_joined', 'Activity actor does not match the artifact authority.');
172
- if (authority.kind === 'send' || authority.kind === 'endpoint' || authority.kind === 'endpoint-cleanup') {
173
- throw new SquareError('invalid_args', 'This authority may not publish Agora activities.');
174
- }
175
- else if (authority.kind === 'participant') {
176
- assertSeat(authority.seat);
111
+ if (authority.kind === 'participant') {
112
+ assertCurrentSeat(read, authority.seat);
177
113
  if (activity.kind === 'join')
178
- 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.');
179
115
  }
180
116
  else if (authority.kind === 'host-cleanup') {
181
- assertSeat(authority.seat, authority.seat, true);
117
+ assertCurrentSeat(read, authority.seat, true);
182
118
  if (activity.kind !== 'done')
183
119
  throw new SquareError('invalid_args', 'Host cleanup may only end membership.');
184
120
  }
185
121
  else if (authority.kind === 'join') {
186
- if (activity.kind !== 'join')
187
- throw new SquareError('invalid_args', 'A new seat must be established by its join activity.');
188
- if (read.participant(expectedName)?.joined)
189
- 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.');
190
124
  }
191
125
  else if (takeoverPhase === 'ready') {
192
- const current = read.seat(expectedName);
193
- if (activity.kind !== 'done' || current === undefined || current.generation !== authority.expectedGeneration)
126
+ if (activity.kind !== 'done' || read.seat(name)?.generation !== authority.expectedGeneration)
194
127
  throw new SquareError('already_joined', 'Takeover must first complete the captured participant.');
195
128
  }
196
- else if (authority.kind === 'takeover' && takeoverPhase === 'done') {
197
- if (activity.kind !== 'join' || !read.participant(expectedName)?.done)
129
+ else if (takeoverPhase === 'done') {
130
+ if (activity.kind !== 'join' || !read.participant(name)?.done)
198
131
  throw new SquareError('already_joined', 'Takeover must join after its captured done activity.');
199
132
  }
200
- else {
133
+ else
201
134
  throw new SquareError('invalid_args', 'Takeover permits only one done followed by one join.');
202
- }
203
135
  const index = read.header().nextActIndex;
204
136
  if (!Number.isSafeInteger(index) || index < 0 || index >= Number.MAX_SAFE_INTEGER)
205
137
  throw new SquareError('invalid_args', 'Square activity index is exhausted.');
206
- const captured = authority.kind === 'takeover' && activity.kind === 'done' ? read.seat(expectedName) : undefined;
207
- const effect = deriveLanding({ ...activity, index }, landingFacts(read, activity));
208
- let stored;
209
- if (activity.kind === 'say') {
210
- const published = gatedChannel.publishMessage({ from: activity.actor, at: activity.at,
211
- body: activity.body ?? '', ...(activity.metadata === undefined ? {} : { metadata: activity.metadata }),
212
- to: effect.audience.map(({ recipient, generation }) => ({ recipient, ...(generation === undefined ? {} : { generation }) })) });
213
- // The published content lives in the message; only the relation is stored above it.
214
- const { body: _body, metadata: _metadata, ...relation } = activity;
215
- stored = { ...relation, index, messageIndex: published.index };
216
- }
217
- else {
218
- stored = { ...activity, index };
219
- }
220
- applyLanding({ ...effect, activity: stored });
138
+ const captured = authority.kind === 'takeover' && activity.kind === 'done' ? read.seat(name) : undefined;
139
+ const stored = { ...activity, index };
140
+ writes.applyLanding(deriveLanding(stored, landingFacts(read, activity)));
221
141
  if (stored.kind === 'join') {
222
142
  if (authority.kind !== 'join' && authority.kind !== 'takeover')
223
143
  throw new SquareError('invalid_args', 'Seat authority cannot establish another seat.');
224
- const seat = {
225
- participant: stored.actor,
226
- owner: authority.owner,
227
- generation: newEndpointGeneration(),
228
- attachment: 'attached',
229
- ...(authority.wakeAddress === undefined ? {} : { wakeAddress: authority.wakeAddress }),
230
- };
231
- mutations.storeSeat(seat);
144
+ const seat = { participant: stored.actor, owner: authority.owner, generation: newEndpointGeneration(), attachment: 'attached', ...(authority.wakeAddress === undefined ? {} : { wakeAddress: authority.wakeAddress }) };
145
+ writes.storeSeat(seat);
232
146
  establishedSeat = seat;
233
- const established = read.seat(seat.participant);
234
- if (established === undefined || established.owner !== seat.owner || established.generation !== seat.generation)
235
- throw new SquareError('invalid_args', 'The join activity did not establish its artifact endpoint.');
236
147
  if (authority.kind === 'takeover')
237
148
  takeoverPhase = 'joined';
238
149
  }
239
150
  else if (stored.kind === 'done' && seated) {
240
- if (!mutations.removeSeat(authority.seat))
241
- throw new SquareError('already_joined', 'The participant endpoint 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.');
242
153
  }
243
154
  else if (stored.kind === 'done' && authority.kind === 'takeover') {
244
- if (captured === undefined || !mutations.removeSeat({ participant: expectedName, owner: captured.owner, generation: captured.generation }))
245
- throw new SquareError('already_joined', 'The participant endpoint 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.');
246
157
  takeoverPhase = 'done';
247
158
  }
248
- // The persisted fact is the relation; callers that present a say resolve its message content.
249
- lastActivity = immutableAct(stored);
250
- return lastActivity;
251
- },
252
- updateWakeAddress(seat, wakeAddress) {
253
- if (authority.kind !== 'join' && authority.kind !== 'participant')
254
- throw new SquareError('already_joined', 'This write needs the current participant seat.');
255
- assertSeat(seat, authority.kind === 'join' ? reconnectSeat : authority.seat);
256
- return mutations.updateWakeAddress(seat, wakeAddress);
257
- },
258
- reattachSeat(seat, wakeAddress) {
259
- if (authority.kind !== 'join')
260
- throw new SquareError('already_joined', 'Reattachment requires a same-owner join.');
261
- assertSeat(seat, reconnectSeat, true);
262
- return mutations.reattachSeat(seat, wakeAddress);
263
- },
264
- detachSeat(seat) {
265
- if (authority.kind !== 'host-cleanup')
266
- throw new SquareError('already_joined', 'Detach requires captured host cleanup authority.');
267
- const current = read.seat(seat.participant), expected = authority.seat;
268
- if (!sameName(seat.participant, expected.participant) || seat.owner !== expected.owner || seat.generation !== expected.generation)
269
- throw new SquareError('already_joined', 'Host cleanup does not match its captured seat.');
270
- if (current === undefined || current.attachment !== 'attached' || current.owner !== expected.owner
271
- || current.generation !== expected.generation)
272
- return false;
273
- return mutations.detachSeat(seat);
274
- },
275
- putLease(name, lease, generation) {
276
- assertParticipant(name, generation);
277
- return mutations.putLease(name, lease, generation, catchCoverage(read, name, lease));
278
- },
279
- removeLease(name, leaseId, generation) {
280
- assertParticipant(name, generation);
281
- return mutations.removeLease(name, leaseId, generation);
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;
282
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); },
283
170
  });
284
- return {
285
- facts,
286
- complete() {
171
+ return { facts, complete() {
287
172
  if (authority.kind !== 'takeover')
288
173
  return;
289
- const seat = read.seat(expectedName);
290
- if (takeoverPhase !== 'joined' || lastActivity?.kind !== 'join' || seat === undefined || establishedSeat === undefined
291
- || seat.owner !== establishedSeat.owner || seat.generation !== establishedSeat.generation)
174
+ const seat = read.seat(name);
175
+ if (takeoverPhase !== 'joined' || seat === undefined || establishedSeat === undefined || seat.owner !== establishedSeat.owner || seat.generation !== establishedSeat.generation)
292
176
  throw new SquareError('already_joined', 'Takeover did not complete its replacement join.');
293
- },
294
- };
177
+ } };
295
178
  }
@@ -1,14 +1,24 @@
1
- import type { DeliveryDeferral, MetadataMatch } from './delivery-deferral.js';
2
- export type { DeliveryDeferral, MetadataMatch, MetadataScalar } from './delivery-deferral.js';
1
+ import type { MetadataMatch } from './delivery-deferral.js';
2
+ export type { MetadataMatch, MetadataScalar } from './delivery-deferral.js';
3
3
  import type { ActivityId, Perception, Reach } from './square-core.js';
4
4
  import type { Owner, HydratedAct, WakeAddress, ActivityMetadata, DirectedNotificationRoute } from './model.js';
5
- import type { WakeTransportPort, SquareResourceOwner } from './ports.js';
5
+ import type { SquareResourceOwner } from './ports.js';
6
6
  import type { OperationControl } from './neutral.js';
7
- import type { MessageSearch as ActivitySearch, AckResult } from './channel-facade.js';
8
- export type { ChannelTruncate, ChannelSendInput, ChannelSendResult, ChannelEndpointSummary, ChannelMessage, ChannelPendingResult, MessageCursor, MessageQuery, ChannelHistoryQuery, ChannelPendingOptions, ChannelHistoryResult, ChannelReceiveResult, MessageSearch as ActivitySearch, AckResult, ChannelEndpointHold as HeldSeat } from './channel-facade.js';
7
+ export interface ActivitySearch {
8
+ readonly text: string;
9
+ readonly mode: 'literal' | 'regex';
10
+ }
11
+ export interface AckResult {
12
+ readonly consumed: number;
13
+ }
14
+ import type { FutureAck } from './square-post.js';
15
+ export type { NotificationId, SendInput, SendResult, FutureAck } from './square-post.js';
16
+ export interface HeldSeat {
17
+ readonly name: string;
18
+ readonly generation: string;
19
+ }
9
20
  export type { CatchWaitStart, OperationProgress, OperationControl, OperationFailureDetail, OperationError } from './neutral.js';
10
21
  export type { ActivityId, Perception, Reach } from './square-core.js';
11
- export type { MessageId } from './message.js';
12
22
  export type { Owner, WakeAddress, ActivityMetadata, JsonValue, DirectedNotificationRoute } from './model.js';
13
23
  /** Indexed archive event, including read bookkeeping and a say's linked content. */
14
24
  export type ArchiveRecord = Readonly<HydratedAct>;
@@ -23,8 +33,6 @@ export interface Activity {
23
33
  readonly reach?: Reach;
24
34
  readonly target?: string;
25
35
  readonly reply?: ActivityId;
26
- /** Channel message owning a say's body and metadata. */
27
- readonly messageId?: import('./message.js').MessageId;
28
36
  }
29
37
  export interface PerceivedActivity extends Activity {
30
38
  readonly perception: Perception;
@@ -121,7 +129,6 @@ export interface HistoryResult {
121
129
  }
122
130
  export interface OpenOptions {
123
131
  readonly clock?: () => number;
124
- readonly wakeTransport?: WakeTransportPort | false;
125
132
  }
126
133
  export interface SquareLocation {
127
134
  readonly path?: string;
@@ -162,15 +169,11 @@ export interface Participant {
162
169
  listening(options?: ObservationOptions, control?: OperationControl): Promise<ListeningResult>;
163
170
  catch(options?: CatchOptions, control?: OperationControl): Promise<CatchResult>;
164
171
  pending(options?: PendingOptions, control?: OperationControl): Promise<PendingResult>;
172
+ /** Local custody only: ledger-only detach/delete is observed on privileged reconciliation. */
165
173
  ack(ids: readonly ActivityId[], control?: OperationControl): Promise<AckResult>;
174
+ ack(input: FutureAck, control?: OperationControl): Promise<AckResult>;
166
175
  /** Leave without a message. Only an omitted or empty body is accepted. */
167
176
  done(body?: string, options?: ObservationOptions, control?: OperationControl): Promise<LifecycleResult>;
168
- deferDelivery(input: Readonly<{
169
- match: MetadataMatch;
170
- durationMs: number;
171
- }>, control?: OperationControl): Promise<DeliveryDeferral>;
172
- renewDeliveryDeferral(id: string, durationMs: number, control?: OperationControl): Promise<DeliveryDeferral | null>;
173
- releaseDeliveryDeferral(id: string, control?: OperationControl): Promise<boolean>;
174
177
  }
175
178
  export type ParticipantPresence = 'never-joined' | 'active' | 'watching' | 'away' | 'done';
176
179
  export interface ParticipantSummary {
@@ -0,0 +1,53 @@
1
+ import { type ActivityMetadata, type DirectedNotificationRoute, type Owner } from './model.js';
2
+ import type { MetadataMatch } from './delivery-deferral.js';
3
+ import type { SeatRef } from './ports.js';
4
+ /** Producer notices are not activities in the square. */
5
+ export type NotificationId = `notice/${number}`;
6
+ export interface SendInput {
7
+ readonly from: string;
8
+ readonly to: string | readonly string[];
9
+ readonly body: string;
10
+ readonly metadata?: ActivityMetadata;
11
+ }
12
+ export interface SendResult {
13
+ readonly id: NotificationId;
14
+ readonly at: number;
15
+ readonly from: string;
16
+ readonly to: readonly string[];
17
+ readonly body: string;
18
+ readonly metadata?: ActivityMetadata;
19
+ }
20
+ /** The caller supplies every scalar equality identifying one exact outcome. */
21
+ export interface FutureAck {
22
+ readonly from: string;
23
+ readonly match: MetadataMatch;
24
+ readonly future: 'exact';
25
+ }
26
+ /** One Square-owned recipient card; neither an endpoint nor a shared message resource. */
27
+ export interface SquarePost {
28
+ readonly kind: 'activity' | 'notice';
29
+ readonly index: number;
30
+ readonly recipient: string;
31
+ readonly owner: Owner;
32
+ readonly generation: string;
33
+ readonly from: string;
34
+ readonly at: number;
35
+ readonly body?: string;
36
+ readonly metadata?: ActivityMetadata;
37
+ readonly route?: DirectedNotificationRoute;
38
+ readonly full?: boolean;
39
+ readonly consumed?: boolean;
40
+ }
41
+ export interface ProducerRead {
42
+ pendingPosts(): readonly SquarePost[];
43
+ postedPosts(): readonly SquarePost[];
44
+ ownsPost(token: string, exportAt?: number): boolean;
45
+ }
46
+ export interface ProducerWrite {
47
+ insertNotice(input: SendInput, at: number): SendResult;
48
+ ackMatching(seat: SeatRef, input: FutureAck, at: number): number;
49
+ acquirePost(post: SquarePost, token: string, pid: number, now: number, exportAt?: number): false | 'initial' | 'recovered';
50
+ settlePost(token: string, full: boolean, disposition: 'posted' | 'uncertain' | 'never-exported'): boolean;
51
+ foldReceipt(post: SquarePost, at: number): boolean;
52
+ }
53
+ export declare function squarePostKey(location: string, post: Pick<SquarePost, 'kind' | 'index' | 'recipient' | 'generation'>): string;
@@ -0,0 +1,4 @@
1
+ import { nameKey } from './model.js';
2
+ export function squarePostKey(location, post) {
3
+ return `square:${location}#${post.kind === 'activity' ? 'act' : 'notice'}/${post.index}/recipient/${encodeURIComponent(nameKey(post.recipient))}/join/${encodeURIComponent(post.generation)}`;
4
+ }
@@ -0,0 +1,13 @@
1
+ import { DeliveryLedger, type DeliveryRoute, type DeliverySession } from './delivery-ledger.js';
2
+ import { type Seat } from './model.js';
3
+ import type { SquareArtifactPort } from './ports.js';
4
+ /** Local producer custody and current host attachment are different observations. */
5
+ export declare function deliverySessionsForSeats(ledger: DeliveryLedger, seats: readonly Seat[]): Promise<ReadonlyMap<string, DeliverySession | undefined>>;
6
+ /** Read-only host observations for status; callers fence them against the captured World seats. */
7
+ export declare function readSquareSessions(seats: readonly Seat[], env: NodeJS.ProcessEnv): Promise<ReadonlyMap<string, DeliverySession | undefined>>;
8
+ /** A first producer join may register an absent owner, never resume or revive one. */
9
+ export declare function ensureInitialDeliverySession(owner: Seat['owner'], env: NodeJS.ProcessEnv, route?: DeliveryRoute): Promise<boolean>;
10
+ /** Called only by privileged mutations. Sandbox lease/ACK/selection never enters here. */
11
+ export declare function reconcileSquareDelivery(artifact: SquareArtifactPort, location: string, env: NodeJS.ProcessEnv, clock: () => number): Promise<void>;
12
+ /** One export path for Square activities and Square-owned notices. No transaction spans I/O. */
13
+ export declare function flushSquarePosts(artifact: SquareArtifactPort, location: string, env: NodeJS.ProcessEnv, clock: () => number): Promise<void>;