@astrosheep/square 0.3.93 → 0.3.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +3 -3
  4. package/dist/attention.d.ts +19 -0
  5. package/dist/attention.js +30 -0
  6. package/dist/boundary-presentation.d.ts +2 -2
  7. package/dist/boundary-presentation.js +33 -40
  8. package/dist/catch-decisions.d.ts +5 -8
  9. package/dist/catch-decisions.js +45 -25
  10. package/dist/claude-delivery-status.js +10 -4
  11. package/dist/claude-delivery.js +19 -11
  12. package/dist/claude-mod.js +24 -53
  13. package/dist/cli/context.d.ts +9 -4
  14. package/dist/cli/context.js +30 -27
  15. package/dist/cli/express-presentation.d.ts +3 -1
  16. package/dist/cli/express-presentation.js +28 -16
  17. package/dist/cli/json.d.ts +3 -0
  18. package/dist/cli/json.js +14 -0
  19. package/dist/cli/mcp-server-command.js +2 -2
  20. package/dist/cli/observation-commands.d.ts +1 -3
  21. package/dist/cli/observation-commands.js +43 -56
  22. package/dist/cli/program.js +3 -3
  23. package/dist/cli/registry.js +7 -6
  24. package/dist/cli/square-commands.d.ts +0 -2
  25. package/dist/cli/square-commands.js +30 -44
  26. package/dist/codex-hook.d.ts +3 -2
  27. package/dist/codex-hook.js +13 -4
  28. package/dist/codex-queue.js +4 -4
  29. package/dist/decisions.d.ts +5 -16
  30. package/dist/decisions.js +9 -33
  31. package/dist/delivery-attempts.d.ts +52 -0
  32. package/dist/delivery-attempts.js +73 -0
  33. package/dist/delivery-deferral.js +2 -2
  34. package/dist/delivery-effects.d.ts +139 -0
  35. package/dist/delivery-effects.js +163 -0
  36. package/dist/delivery-health.d.ts +0 -1
  37. package/dist/delivery-health.js +1 -3
  38. package/dist/delivery-operations.d.ts +2 -2
  39. package/dist/delivery-operations.js +76 -101
  40. package/dist/delivery-primitives.d.ts +5 -0
  41. package/dist/delivery-primitives.js +3 -0
  42. package/dist/delivery.d.ts +1 -1
  43. package/dist/delivery.js +7 -6
  44. package/dist/help.js +14 -14
  45. package/dist/http-delivery.d.ts +16 -0
  46. package/dist/http-delivery.js +101 -0
  47. package/dist/inbox.d.ts +9 -22
  48. package/dist/inbox.js +23 -27
  49. package/dist/index.d.ts +1 -3
  50. package/dist/index.js +1 -1
  51. package/dist/mcp.d.ts +3 -0
  52. package/dist/mcp.js +23 -24
  53. package/dist/model.d.ts +12 -21
  54. package/dist/model.js +5 -8
  55. package/dist/notifications.d.ts +1 -1
  56. package/dist/notifications.js +8 -5
  57. package/dist/operation-presentation.d.ts +6 -6
  58. package/dist/operation-presentation.js +55 -58
  59. package/dist/owner.d.ts +11 -0
  60. package/dist/owner.js +32 -0
  61. package/dist/paseo-delivery.d.ts +4 -1
  62. package/dist/paseo-delivery.js +7 -12
  63. package/dist/paseo-state.d.ts +2 -2
  64. package/dist/paseo-state.js +6 -4
  65. package/dist/plugins/opencode/delivery.js +27 -15
  66. package/dist/ports.d.ts +18 -47
  67. package/dist/presentation-operations.d.ts +14 -19
  68. package/dist/presentation-operations.js +51 -61
  69. package/dist/presentation.d.ts +7 -17
  70. package/dist/presentation.js +46 -91
  71. package/dist/receive.d.ts +66 -0
  72. package/dist/receive.js +54 -0
  73. package/dist/runtime.d.ts +1 -8
  74. package/dist/runtime.js +2 -17
  75. package/dist/seats.d.ts +2 -3
  76. package/dist/seats.js +7 -15
  77. package/dist/session-lifecycle.d.ts +6 -2
  78. package/dist/session-lifecycle.js +39 -11
  79. package/dist/sqlite-square.d.ts +36 -23
  80. package/dist/sqlite-square.js +162 -211
  81. package/dist/square-api.d.ts +16 -11
  82. package/dist/square-api.js +27 -23
  83. package/dist/square-core.d.ts +0 -14
  84. package/dist/square-core.js +0 -9
  85. package/dist/square-domain.d.ts +4 -2
  86. package/dist/square-domain.js +65 -52
  87. package/dist/square-facade.d.ts +47 -105
  88. package/dist/square-index.d.ts +5 -13
  89. package/dist/square-index.js +13 -38
  90. package/dist/square-legacy-framing.d.ts +2 -0
  91. package/dist/square-legacy-framing.js +8 -0
  92. package/dist/square-location.d.ts +1 -14
  93. package/dist/square-location.js +4 -70
  94. package/dist/square-migration-host-ledger.d.ts +2 -2
  95. package/dist/square-migration-host-ledger.js +6 -5
  96. package/dist/square-migration.d.ts +4 -1
  97. package/dist/square-migration.js +93 -38
  98. package/dist/square-receive.d.ts +6 -0
  99. package/dist/square-receive.js +18 -0
  100. package/dist/square-sqlite.d.ts +9 -3
  101. package/dist/square-sqlite.js +139 -114
  102. package/dist/square-tools.d.ts +7 -10
  103. package/dist/square-tools.js +21 -61
  104. package/dist/stream.js +4 -5
  105. package/dist/views.d.ts +23 -13
  106. package/dist/views.js +47 -53
  107. package/dist/wakes.d.ts +1 -5
  108. package/dist/wakes.js +4 -6
  109. package/examples/symmetric-domains.mjs +250 -0
  110. package/guides/application-facade-delivery-evidence.md +6 -6
  111. package/guides/attention-primitive.md +32 -0
  112. package/guides/change-waits.md +2 -2
  113. package/guides/claude-native.md +18 -9
  114. package/guides/codex-delivery.md +17 -6
  115. package/guides/delivery-primitives.md +180 -0
  116. package/guides/mcp-connection-lifetime.md +1 -1
  117. package/guides/mcp.md +3 -3
  118. package/guides/migration.md +148 -151
  119. package/guides/operation-cancellation.md +1 -1
  120. package/guides/participant-ownership.md +78 -47
  121. package/guides/pi-tools.md +34 -5
  122. package/guides/primitive-catch-consolidation.md +5 -5
  123. package/guides/primitive-history-consolidation.md +2 -2
  124. package/guides/primitive-status-consolidation.md +3 -3
  125. package/guides/primitive-storage-consolidation.md +1 -1
  126. package/guides/receive-primitive.md +90 -0
  127. package/guides/row-runtime-delivery-evidence.md +1 -1
  128. package/guides/row-storage-design.md +22 -28
  129. package/guides/square-api.md +314 -91
  130. package/guides/symmetric-domains.md +92 -0
  131. package/package.json +14 -1
  132. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  133. package/plugins/claude/skills/square/SKILL.md +2 -6
  134. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  135. package/plugins/opencode/delivery.ts +27 -17
  136. package/plugins/paseo/client/palette.ts +18 -18
  137. package/plugins/paseo/client/square-message.tsx +17 -23
  138. package/plugins/pi/square-pi.js +17 -17
  139. package/skills/square/SKILL.md +2 -6
  140. package/dist/square-backend.d.ts +0 -13
  141. package/dist/square-backend.js +0 -1
  142. package/dist/square-stdio.d.ts +0 -22
  143. package/dist/square-stdio.js +0 -235
  144. package/dist/stdio-server.d.ts +0 -3
  145. package/dist/stdio-server.js +0 -212
  146. package/guides/backends.md +0 -193
@@ -1,14 +1,19 @@
1
- import type { SquareBackend } from './square-backend.js';
2
- import type { SquareAtInput, SquareBuildInput, OperationControl } from './square-facade.js';
1
+ import { SqliteSquare } from './sqlite-square.js';
2
+ import { type Owner } from './model.js';
3
+ import type { OperationControl, Participant, SquareAtInput, SquareBuildInput } from './square-facade.js';
4
+ export type Square = SqliteSquare;
5
+ export declare const Square: {
6
+ at: (input: SquareAtInput, control?: OperationControl) => Promise<SqliteSquare>;
7
+ build: (input: SquareBuildInput, control?: OperationControl) => Promise<SqliteSquare>;
8
+ inMemory: typeof SqliteSquare.inMemory;
9
+ };
10
+ export { requireOwner, requireJoinName } from './seats.js';
3
11
  export type * from './square-facade.js';
4
12
  export { squareLocation } from './square-location.js';
13
+ export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' | 'seatsOf'>, owner: Owner | undefined, name?: string): Promise<Participant>;
14
+ export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' | 'seatsOf'>, owner: Owner | undefined, name: string | undefined, options: {
15
+ readonly optional: true;
16
+ }): Promise<Participant | undefined>;
5
17
  export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
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';
7
- /** The same structural resource regardless of persistence or transport. */
8
- export interface Square extends SquareBackend {
9
- }
10
- export declare const Square: {
11
- at(input: SquareAtInput, control?: OperationControl): Promise<Square>;
12
- build(input: SquareBuildInput, control?: OperationControl): Promise<Square>;
13
- inMemory(input: Omit<SquareBuildInput, "path" | "resources">): Square;
14
- };
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';
@@ -1,26 +1,30 @@
1
1
  import { SqliteSquare } from './sqlite-square.js';
2
- import { squareLocation, backendConfiguration, parseSquareLocation } from './square-location.js';
3
- import { openStdioSquare } from './square-stdio.js';
2
+ import { SquareError } from './model.js';
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);
10
+ export { requireOwner, requireJoinName } from './seats.js';
4
11
  export { squareLocation } from './square-location.js';
12
+ export async function selectOwnSeat(square, owner, name, options) {
13
+ if (owner === undefined && options?.optional && name === undefined)
14
+ return undefined;
15
+ const caller = requireOwner(owner);
16
+ if (name !== undefined)
17
+ return square.participant(name, { owner: caller });
18
+ const seats = (await square.seatsOf(caller)).filter(seat => seat.attachment === 'attached');
19
+ if (seats.length !== 1) {
20
+ if (options?.optional)
21
+ return undefined;
22
+ throw new SquareError('invalid_args', seats.length > 1
23
+ ? 'Square needs an explicit participant name: this owner holds multiple names'
24
+ : 'Square needs one unambiguous participant name');
25
+ }
26
+ return square.participant(seats[0].name, { owner: caller });
27
+ }
5
28
  export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
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';
7
- export const Square = {
8
- async at(input, control) {
9
- const captured = { ...input, cwd: input.cwd ?? process.cwd(), env: { ...(input.env ?? process.env) } };
10
- const location = squareLocation(captured);
11
- const selected = parseSquareLocation(location);
12
- return selected.kind === 'local' ? SqliteSquare.at({ ...captured, path: location }, control)
13
- : openStdioSquare(location, selected, captured, await backendConfiguration(captured), undefined, control);
14
- },
15
- async build(input, control) {
16
- const captured = { ...input, cwd: input.cwd ?? process.cwd(), env: { ...(input.env ?? process.env) } };
17
- const location = squareLocation(captured);
18
- const selected = parseSquareLocation(location);
19
- return selected.kind === 'local' ? SqliteSquare.build({ ...captured, path: location }, control)
20
- : openStdioSquare(location, selected, captured, await backendConfiguration(captured), {
21
- markdown: input.markdown, hardCap: input.hardCap, throttlePerMinute: input.throttlePerMinute,
22
- template: input.template, force: input.force,
23
- }, control);
24
- },
25
- inMemory(input) { return SqliteSquare.inMemory(input); },
26
- };
29
+ 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';
@@ -29,15 +29,6 @@ export type Act = {
29
29
  mentions?: readonly Participant[];
30
30
  reach?: Reach;
31
31
  reply?: number;
32
- } | {
33
- kind: 'hold';
34
- actor?: Participant;
35
- at?: number;
36
- body?: string;
37
- } | {
38
- kind: 'resume';
39
- actor?: Participant;
40
- at?: number;
41
32
  } | {
42
33
  kind: 'read';
43
34
  actor: Participant;
@@ -59,9 +50,4 @@ export type Act = {
59
50
  target: Participant;
60
51
  at?: number;
61
52
  };
62
- export declare function audienceOf(say: {
63
- body?: string;
64
- mentions?: readonly Participant[];
65
- reach?: Reach;
66
- }): Audience;
67
53
  export declare function audienceIncludes(audience: Audience, name: string): boolean;
@@ -13,15 +13,6 @@ export function parseActivityId(value) {
13
13
  const index = Number(value.slice(4));
14
14
  return Number.isSafeInteger(index) ? index : undefined;
15
15
  }
16
- export function audienceOf(say) {
17
- if (say.reach === 'bell')
18
- return { kind: 'bell' };
19
- const names = [];
20
- for (const name of say.mentions ?? [])
21
- if (!names.some((existing) => sameName(existing, name)))
22
- names.push(name);
23
- return { kind: 'mentions', names };
24
- }
25
16
  export function audienceIncludes(audience, name) {
26
17
  return audience.kind === 'bell' || audience.names.some((mentioned) => sameName(mentioned, name));
27
18
  }
@@ -1,5 +1,5 @@
1
1
  import { type Seat, type StoredAct } from './model.js';
2
- import type { ArtifactWriteAuthority, AttentionFact, LandedRecipient, ParticipantFact, SquareRead, SquareWrite } from './ports.js';
2
+ import type { ArtifactWriteAuthority, AttentionFact, LandedRecipient, ParticipantFact, SeatRef, SquareRead, SquareWrite } from './ports.js';
3
3
  export interface LandingEffect {
4
4
  readonly activity: StoredAct;
5
5
  readonly participant?: ParticipantFact;
@@ -21,7 +21,7 @@ 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' | 'updateWakeAddress' | 'cancelThrough' | 'putDeliveryDeferral' | 'renewDeliveryDeferral' | 'releaseDeliveryDeferral' | 'claimDelivery' | 'prepareDelivery' | 'completeDelivery' | 'observeNativeDelivery'> {
24
+ export interface LandingWriter extends Pick<SquareWrite, 'markSeen' | 'putLease' | 'removeLease' | 'updateWakeAddress' | 'reattachSeat' | 'detachSeat' | 'cancelThrough' | 'putDeliveryDeferral' | 'renewDeliveryDeferral' | 'releaseDeliveryDeferral' | 'claimDelivery' | 'prepareDelivery' | 'completeDelivery' | 'observeNativeDelivery'> {
25
25
  /** Artifact-private seat persistence used only by this gate's lifecycle transition. */
26
26
  storeSeat(seat: Seat): boolean;
27
27
  removeSeat(seat: import('./ports.js').SeatRef): boolean;
@@ -31,5 +31,7 @@ export interface SquareWriteGate {
31
31
  readonly facts: SquareWrite;
32
32
  complete(): void;
33
33
  }
34
+ /** Read probes and writes share the same captured-coordinate and attachment fence. */
35
+ export declare function assertCurrentSeat(read: SquareRead, expected: SeatRef, allowDetached?: boolean): void;
34
36
  /** The artifact transaction supplies one captured authority; every mutation checks it here. */
35
37
  export declare function wrapSquareWrite(read: SquareRead, writes: LandingWriter, authority: ArtifactWriteAuthority): SquareWriteGate;
@@ -1,35 +1,13 @@
1
- import { SquareError, formatActivityId, parseActivityId, sameBody, sameName, validateBody, validateName } from './model.js';
1
+ import { SquareError, formatActivityId, parseActivityId, sameName, validateOwner, validateName } from './model.js';
2
+ import { resolveAudience } from './attention.js';
2
3
  /** The single landing transition, shared by both adapters and explicit rebuild. */
3
4
  export function deriveLanding(activity, facts) {
4
- const audience = [];
5
- if (activity.kind === 'say') {
6
- const add = (requested, route) => {
7
- const recipient = facts.standing.find((item) => sameName(item.name, requested))?.name;
8
- if (recipient === undefined || sameName(recipient, activity.actor)
9
- || audience.some((item) => sameName(item.recipient, recipient)))
10
- return;
11
- if (activity.reach !== 'bell' && facts.incoming.some((item) => item.kind === 'ignore' && sameName(item.listener, recipient)))
12
- return;
13
- audience.push({ recipient, route });
14
- };
15
- if (activity.reach === 'bell') {
16
- for (const peer of facts.standing)
17
- add(peer.name, 'bell');
18
- }
19
- else {
20
- for (const mention of activity.mentions ?? [])
21
- add(mention, 'mention');
22
- if (facts.replyAuthor !== undefined)
23
- add(facts.replyAuthor, 'reply');
24
- for (const peer of facts.standing) {
25
- if (facts.incoming.some((item) => item.kind === 'listen' && sameName(item.listener, peer.name)))
26
- add(peer.name, 'attention');
27
- }
28
- }
29
- }
5
+ const audience = activity.kind === 'say' ? resolveAudience({
6
+ actor: activity.actor, recipients: facts.standing.map((item) => item.name),
7
+ mentions: activity.mentions, replyAuthor: facts.replyAuthor, reach: activity.reach,
8
+ incoming: facts.incoming, sameIdentity: sameName,
9
+ }) : [];
30
10
  const actor = activity.actor;
31
- if (actor === undefined)
32
- return { activity, audience };
33
11
  const previous = facts.actor ?? { name: actor, firstIndex: activity.index, joined: false, done: false, sayCount: 0 };
34
12
  const participant = {
35
13
  ...previous,
@@ -57,7 +35,7 @@ export function deriveLanding(activity, facts) {
57
35
  }
58
36
  export function landingFacts(read, activity) {
59
37
  return {
60
- actor: activity.actor === undefined ? undefined : read.participant(activity.actor),
38
+ actor: read.participant(activity.actor),
61
39
  standing: activity.kind === 'say' ? read.roster().filter((item) => item.joined) : [],
62
40
  incoming: activity.kind === 'say' ? read.incomingAttention(activity.actor) : [],
63
41
  outgoing: activity.kind === 'listen' || activity.kind === 'ignore' || activity.kind === 'unlisten' ? read.attention(activity.actor) : [],
@@ -68,31 +46,38 @@ function validateGeneration(value) {
68
46
  if (typeof value !== 'string' || parseActivityId(value) === undefined)
69
47
  throw new SquareError('invalid_args', 'Artifact authority requires a valid join generation.');
70
48
  }
49
+ /** Read probes and writes share the same captured-coordinate and attachment fence. */
50
+ export function assertCurrentSeat(read, expected, allowDetached = false) {
51
+ const current = read.seat(expected.participant);
52
+ if (current === undefined || current.owner !== expected.owner || current.generation !== expected.generation)
53
+ throw new SquareError('already_joined', 'This participant seat has been replaced.');
54
+ if (!allowDetached && current.attachment !== 'attached')
55
+ throw new SquareError('already_joined', 'This participant is away; join again before writing.', { reason: 'needs_seat', activityUnsent: true });
56
+ }
71
57
  /** The artifact transaction supplies one captured authority; every mutation checks it here. */
72
58
  export function wrapSquareWrite(read, writes, authority) {
73
59
  if (authority === undefined || typeof authority !== 'object' || authority === null)
74
60
  throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
75
61
  const { applyLanding, ...mutations } = writes;
76
- const expectedName = authority.kind === 'participant' ? authority.seat?.participant : authority.participant;
77
- if (!['participant', 'join', 'takeover'].includes(authority.kind) || typeof expectedName !== 'string')
62
+ const seated = authority.kind === 'participant' || authority.kind === 'host-cleanup';
63
+ const expectedName = seated ? authority.seat?.participant : authority.participant;
64
+ if (!['participant', 'host-cleanup', 'join', 'takeover'].includes(authority.kind) || typeof expectedName !== 'string')
78
65
  throw new SquareError('invalid_args', 'Artifact write authority is malformed.');
79
66
  validateName(expectedName);
80
- if (authority.kind === 'participant') {
81
- validateBody(authority.seat.body);
67
+ if (seated) {
68
+ validateOwner(authority.seat.owner);
82
69
  validateGeneration(authority.seat.generation);
83
70
  }
84
71
  else {
85
- validateBody(authority.body);
72
+ validateOwner(authority.owner);
86
73
  if (authority.kind === 'takeover')
87
74
  validateGeneration(authority.expectedGeneration);
88
75
  }
89
- const assertSeat = (seat, expected = authority.kind === 'participant' ? authority.seat : undefined) => {
76
+ const assertSeat = (seat, expected = authority.kind === 'participant' ? authority.seat : undefined, allowDetached = false) => {
90
77
  if (expected === undefined || !sameName(expected.participant, seat.participant)
91
- || seat.generation !== expected.generation || !sameBody(seat.body, expected.body))
78
+ || seat.generation !== expected.generation || seat.owner !== expected.owner)
92
79
  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.');
80
+ assertCurrentSeat(read, expected, allowDetached);
96
81
  };
97
82
  const assertParticipant = (name, generation) => {
98
83
  if (authority.kind !== 'participant')
@@ -104,11 +89,14 @@ export function wrapSquareWrite(read, writes, authority) {
104
89
  if (authority.kind === 'participant') {
105
90
  assertSeat(authority.seat);
106
91
  }
92
+ else if (authority.kind === 'host-cleanup') {
93
+ // A late host cleanup is allowed to inspect a stale coordinate and return a no-op.
94
+ }
107
95
  else if (authority.kind === 'join') {
108
96
  const participant = read.participant(expectedName), seat = read.seat(expectedName);
109
97
  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`);
98
+ if (seat === undefined || seat.owner !== authority.owner)
99
+ throw new SquareError('already_joined', `${expectedName} is held by another owner; take it over explicitly`);
112
100
  reconnectSeat = seat;
113
101
  }
114
102
  }
@@ -131,6 +119,11 @@ export function wrapSquareWrite(read, writes, authority) {
131
119
  if (activity.kind === 'join')
132
120
  throw new SquareError('invalid_args', 'A participant seat can only be re-established by join.');
133
121
  }
122
+ else if (authority.kind === 'host-cleanup') {
123
+ assertSeat(authority.seat, authority.seat, true);
124
+ if (activity.kind !== 'done')
125
+ throw new SquareError('invalid_args', 'Host cleanup may only end membership.');
126
+ }
134
127
  else if (authority.kind === 'join') {
135
128
  if (activity.kind !== 'join')
136
129
  throw new SquareError('invalid_args', 'A new seat must be established by its join activity.');
@@ -157,27 +150,28 @@ export function wrapSquareWrite(read, writes, authority) {
157
150
  const stored = { ...activity, index };
158
151
  applyLanding(deriveLanding(stored, landingFacts(read, stored)));
159
152
  if (stored.kind === 'join') {
160
- if (authority.kind === 'participant')
161
- throw new SquareError('invalid_args', 'Participant authority cannot establish a seat.');
153
+ if (seated)
154
+ throw new SquareError('invalid_args', 'Seat authority cannot establish another seat.');
162
155
  const seat = {
163
156
  participant: stored.actor,
164
- body: authority.body,
157
+ owner: authority.owner,
165
158
  generation: formatActivityId(stored.index),
159
+ attachment: 'attached',
166
160
  ...(authority.wakeAddress === undefined ? {} : { wakeAddress: authority.wakeAddress }),
167
161
  };
168
162
  mutations.storeSeat(seat);
169
163
  const established = read.seat(seat.participant);
170
- if (established === undefined || !sameBody(established.body, seat.body) || established.generation !== seat.generation)
164
+ if (established === undefined || established.owner !== seat.owner || established.generation !== seat.generation)
171
165
  throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
172
166
  if (authority.kind === 'takeover')
173
167
  takeoverPhase = 'joined';
174
168
  }
175
- else if (stored.kind === 'done' && authority.kind === 'participant') {
169
+ else if (stored.kind === 'done' && seated) {
176
170
  if (!mutations.removeSeat(authority.seat))
177
171
  throw new SquareError('already_joined', 'The participant seat changed while it was being completed.');
178
172
  }
179
173
  else if (stored.kind === 'done' && authority.kind === 'takeover') {
180
- if (captured === undefined || !mutations.removeSeat({ participant: expectedName, body: captured.body, generation: captured.generation }))
174
+ if (captured === undefined || !mutations.removeSeat({ participant: expectedName, owner: captured.owner, generation: captured.generation }))
181
175
  throw new SquareError('already_joined', 'The participant seat changed while takeover was completing it.');
182
176
  takeoverPhase = 'done';
183
177
  }
@@ -185,13 +179,32 @@ export function wrapSquareWrite(read, writes, authority) {
185
179
  return lastActivity;
186
180
  },
187
181
  updateWakeAddress(seat, wakeAddress) {
188
- assertSeat(seat, authority.kind === 'join' ? reconnectSeat : undefined);
182
+ if (authority.kind !== 'join' && authority.kind !== 'participant')
183
+ throw new SquareError('already_joined', 'This write needs the current participant seat.');
184
+ assertSeat(seat, authority.kind === 'join' ? reconnectSeat : authority.seat);
189
185
  return mutations.updateWakeAddress(seat, wakeAddress);
190
186
  },
187
+ reattachSeat(seat, wakeAddress) {
188
+ if (authority.kind !== 'join')
189
+ throw new SquareError('already_joined', 'Reattachment requires a same-owner join.');
190
+ assertSeat(seat, reconnectSeat, true);
191
+ return mutations.reattachSeat(seat, wakeAddress);
192
+ },
193
+ detachSeat(seat) {
194
+ if (authority.kind !== 'host-cleanup')
195
+ throw new SquareError('already_joined', 'Detach requires captured host cleanup authority.');
196
+ const current = read.seat(seat.participant), expected = authority.seat;
197
+ if (!sameName(seat.participant, expected.participant) || seat.owner !== expected.owner || seat.generation !== expected.generation)
198
+ throw new SquareError('already_joined', 'Host cleanup does not match its captured seat.');
199
+ if (current === undefined || current.attachment !== 'attached' || current.owner !== expected.owner
200
+ || current.generation !== expected.generation)
201
+ return false;
202
+ return mutations.detachSeat(seat);
203
+ },
191
204
  cancelThrough(seat, through) {
192
205
  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.');
206
+ if (!Number.isSafeInteger(through) || through < 0 || through >= read.header().nextActIndex)
207
+ throw new SquareError('invalid_args', 'Cancellation boundary must be an assigned activity index below the allocator.');
195
208
  return mutations.cancelThrough(seat, through);
196
209
  },
197
210
  markSeen(name, indexes, at) {
@@ -242,7 +255,7 @@ export function wrapSquareWrite(read, writes, authority) {
242
255
  return;
243
256
  const seat = read.seat(expectedName);
244
257
  if (takeoverPhase !== 'joined' || lastActivity?.kind !== 'join' || seat === undefined
245
- || !sameBody(seat.body, authority.body) || seat.generation !== formatActivityId(lastActivity.index))
258
+ || seat.owner !== authority.owner || seat.generation !== formatActivityId(lastActivity.index))
246
259
  throw new SquareError('already_joined', 'Takeover did not complete its replacement join.');
247
260
  },
248
261
  };