@astrosheep/square 0.4.0 → 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.
- package/dist/artifact.d.ts +1 -3
- package/dist/artifact.js +12 -36
- package/dist/attention-presentation.d.ts +2 -22
- package/dist/attention-presentation.js +9 -12
- package/dist/boundary-presentation.d.ts +3 -14
- package/dist/boundary-presentation.js +29 -104
- package/dist/card-presentation.d.ts +1 -0
- package/dist/card-presentation.js +2 -0
- package/dist/catch-decisions.js +1 -7
- package/dist/claude-delivery.d.ts +13 -8
- package/dist/claude-delivery.js +40 -93
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +28 -140
- package/dist/claude-transport.d.ts +3 -3
- package/dist/claude-transport.js +20 -44
- package/dist/cli/context.js +7 -9
- package/dist/cli/observation-commands.d.ts +2 -0
- package/dist/cli/observation-commands.js +12 -2
- package/dist/cli/registry.js +3 -1
- package/dist/codex-address.d.ts +2 -2
- package/dist/codex-boundary-state.d.ts +1 -5
- package/dist/codex-boundary-state.js +0 -4
- package/dist/codex-hook.d.ts +7 -10
- package/dist/codex-hook.js +69 -33
- package/dist/codex-queue.d.ts +10 -11
- package/dist/codex-queue.js +32 -32
- package/dist/codex-watch.d.ts +2 -0
- package/dist/codex-watch.js +100 -0
- package/dist/decisions.js +2 -2
- package/dist/delivery-deferral.d.ts +8 -17
- package/dist/delivery-deferral.js +0 -28
- package/dist/delivery-frame.d.ts +7 -0
- package/dist/delivery-frame.js +11 -0
- package/dist/delivery-ledger-sqlite.d.ts +97 -0
- package/dist/delivery-ledger-sqlite.js +496 -0
- package/dist/delivery-ledger.d.ts +196 -0
- package/dist/delivery-ledger.js +268 -0
- package/dist/delivery-lifetime.d.ts +6 -0
- package/dist/delivery-lifetime.js +45 -0
- package/dist/harness-links.js +61 -7
- package/dist/harness.js +12 -17
- package/dist/help.js +2 -0
- package/dist/http-delivery.d.ts +5 -9
- package/dist/http-delivery.js +20 -77
- package/dist/inbox.d.ts +6 -49
- package/dist/inbox.js +24 -371
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -1
- package/dist/mcp.js +1 -1
- package/dist/model.d.ts +7 -47
- package/dist/model.js +1 -7
- package/dist/notifications.d.ts +24 -20
- package/dist/notifications.js +39 -108
- package/dist/operation-control.d.ts +4 -0
- package/dist/operation-control.js +20 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
- package/dist/packages/agent-delivery/src/claude-native.js +5 -0
- package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
- package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
- package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
- package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
- package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
- package/dist/packages/agent-delivery/src/codex.js +41 -4
- package/dist/packages/agent-delivery/src/index.d.ts +3 -1
- package/dist/packages/agent-delivery/src/index.js +1 -1
- package/dist/packages/agent-delivery/src/paseo.js +11 -2
- package/dist/packages/agent-delivery/src/types.d.ts +1 -0
- package/dist/paseo-delivery.d.ts +5 -5
- package/dist/paseo-delivery.js +19 -97
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/index.d.ts +5 -3
- package/dist/plugins/opencode/index.js +99 -21
- package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
- package/dist/plugins/opencode-shared/delivery.js +109 -0
- package/dist/plugins/opencode-v1/index.d.ts +28 -0
- package/dist/plugins/opencode-v1/index.js +41 -0
- package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
- package/dist/plugins/opencode-v1/runtime.js +152 -0
- package/dist/plugins/opencode-v1/transport.d.ts +13 -0
- package/dist/plugins/opencode-v1/transport.js +50 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
- package/dist/plugins/paseo/shared/card-presentation.js +7 -0
- package/dist/ports.d.ts +31 -90
- package/dist/session-lifecycle.d.ts +8 -7
- package/dist/session-lifecycle.js +40 -42
- package/dist/sqlite-square.d.ts +14 -23
- package/dist/sqlite-square.js +132 -139
- package/dist/square-api.js +4 -7
- package/dist/square-core.d.ts +0 -1
- package/dist/square-domain.d.ts +5 -17
- package/dist/square-domain.js +104 -221
- package/dist/square-facade.d.ts +18 -15
- package/dist/square-post.d.ts +53 -0
- package/dist/square-post.js +4 -0
- package/dist/square-posting.d.ts +13 -0
- package/dist/square-posting.js +174 -0
- package/dist/square-producer-sqlite.d.ts +11 -0
- package/dist/square-producer-sqlite.js +210 -0
- package/dist/square-sqlite.d.ts +15 -25
- package/dist/square-sqlite.js +150 -226
- package/dist/views.d.ts +2 -2
- package/dist/views.js +0 -1
- package/dist/wake-sink.d.ts +4 -11
- package/dist/wake-sink.js +8 -32
- package/dist/wake-transports.d.ts +4 -7
- package/dist/wake-transports.js +8 -39
- package/examples/symmetric-domains.mjs +29 -249
- package/guides/change-waits.md +13 -53
- package/guides/claude-native.md +19 -175
- package/guides/codex-delivery.md +55 -80
- package/guides/delivery-primitives.md +48 -207
- package/guides/operation-cancellation.md +2 -5
- package/guides/participant-ownership.md +25 -89
- package/guides/pi-native-delivery.md +19 -56
- package/guides/pi-tools.md +6 -8
- package/guides/square-api.md +113 -615
- package/package.json +8 -8
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/hooks.json +1 -1
- package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/hooks/hooks.json +11 -23
- package/plugins/opencode/index.ts +91 -23
- package/plugins/opencode-shared/delivery.ts +91 -0
- package/plugins/opencode-v1/index.ts +36 -0
- package/plugins/opencode-v1/runtime.ts +134 -0
- package/plugins/opencode-v1/transport.ts +41 -0
- package/plugins/paseo/OVERVIEW.md +5 -5
- package/plugins/paseo/README.md +7 -6
- package/plugins/paseo/client/markdown.tsx +5 -5
- package/plugins/paseo/client/square-message.tsx +28 -24
- package/plugins/paseo/client/transform.ts +8 -5
- package/plugins/paseo/shared/card-presentation.ts +16 -0
- package/plugins/paseo/shared/message.ts +6 -11
- package/plugins/pi/square-pi.js +156 -417
- package/dist/channel-artifact.d.ts +0 -10
- package/dist/channel-artifact.js +0 -55
- package/dist/channel-delivery.d.ts +0 -11
- package/dist/channel-delivery.js +0 -51
- package/dist/channel-domain.d.ts +0 -3
- package/dist/channel-domain.js +0 -83
- package/dist/channel-facade.d.ts +0 -76
- package/dist/channel-facade.js +0 -1
- package/dist/channel-internal.d.ts +0 -126
- package/dist/channel-internal.js +0 -440
- package/dist/channel-metadata.d.ts +0 -11
- package/dist/channel-metadata.js +0 -1
- package/dist/channel-ports.d.ts +0 -241
- package/dist/channel-ports.js +0 -1
- package/dist/channel-presentation.d.ts +0 -17
- package/dist/channel-presentation.js +0 -22
- package/dist/channel-sqlite-schema.d.ts +0 -13
- package/dist/channel-sqlite-schema.js +0 -81
- package/dist/channel-transport.d.ts +0 -62
- package/dist/channel-transport.js +0 -1
- package/dist/channel.d.ts +0 -84
- package/dist/channel.js +0 -60
- package/dist/claude-delivery-status.d.ts +0 -13
- package/dist/claude-delivery-status.js +0 -46
- package/dist/cmd/notify-once.d.ts +0 -2
- package/dist/cmd/notify-once.js +0 -29
- package/dist/delivery-attempts.d.ts +0 -52
- package/dist/delivery-attempts.js +0 -73
- package/dist/delivery-effects.d.ts +0 -139
- package/dist/delivery-effects.js +0 -163
- package/dist/delivery-health.d.ts +0 -51
- package/dist/delivery-health.js +0 -173
- package/dist/delivery-operations.d.ts +0 -22
- package/dist/delivery-operations.js +0 -198
- package/dist/delivery-primitives.d.ts +0 -5
- package/dist/delivery-primitives.js +0 -3
- package/dist/delivery.d.ts +0 -56
- package/dist/delivery.js +0 -80
- package/dist/plugins/opencode/delivery.d.ts +0 -19
- package/dist/plugins/opencode/delivery.js +0 -223
- package/dist/presentation-operations.d.ts +0 -5
- package/dist/presentation-operations.js +0 -86
- package/dist/sqlite-channel.d.ts +0 -15
- package/dist/sqlite-channel.js +0 -600
- package/dist/square-index.d.ts +0 -16
- package/dist/square-index.js +0 -106
- package/dist/wake-preview.d.ts +0 -7
- package/dist/wake-preview.js +0 -15
- package/plugins/opencode/delivery.ts +0 -198
- /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
- /package/dist/{channel-values.js → square-values.js} +0 -0
package/dist/square-domain.js
CHANGED
|
@@ -1,91 +1,47 @@
|
|
|
1
1
|
import { SquareError, nameKey, sameName, validateOwner, validateName } from './model.js';
|
|
2
|
-
import {
|
|
3
|
-
import { newEndpointGeneration } from './message.js';
|
|
2
|
+
import { newEndpointGeneration, validateEndpointGeneration } from './message.js';
|
|
4
3
|
import { resolveAudience } from './attention.js';
|
|
5
|
-
import {
|
|
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(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
53
|
+
if (!authority || typeof authority !== 'object')
|
|
105
54
|
throw new SquareError('invalid_args', 'Artifact writes require an explicit authority.');
|
|
106
|
-
const {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
84
|
+
validateEndpointGeneration(authority.seat.generation);
|
|
116
85
|
}
|
|
117
|
-
else
|
|
86
|
+
else {
|
|
118
87
|
validateOwner(authority.owner);
|
|
119
88
|
if (authority.kind === 'takeover')
|
|
120
|
-
|
|
89
|
+
validateEndpointGeneration(authority.expectedGeneration);
|
|
121
90
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
105
|
+
assertCurrentSeat(read, authority.seat);
|
|
132
106
|
};
|
|
133
|
-
|
|
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
|
-
|
|
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 === '
|
|
173
|
-
|
|
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
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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 (
|
|
197
|
-
if (activity.kind !== 'join' || !read.participant(
|
|
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(
|
|
207
|
-
const
|
|
208
|
-
|
|
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
|
-
|
|
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 (!
|
|
241
|
-
throw new SquareError('already_joined', 'The participant
|
|
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 || !
|
|
245
|
-
throw new SquareError('already_joined', 'The participant
|
|
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
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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(
|
|
290
|
-
if (takeoverPhase !== 'joined' ||
|
|
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
|
}
|
package/dist/square-facade.d.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export type {
|
|
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 {
|
|
5
|
+
import type { SquareResourceOwner } from './ports.js';
|
|
6
6
|
import type { OperationControl } from './neutral.js';
|
|
7
|
-
|
|
8
|
-
|
|
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>;
|