@astrosheep/square 0.3.94 → 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.
- package/dist/artifact.js +1 -1
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +1 -2
- package/dist/catch-decisions.js +41 -21
- package/dist/cli/context.js +0 -2
- package/dist/cli/express-presentation.js +3 -3
- package/dist/cli/json.js +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +1 -3
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +21 -27
- package/dist/decisions.d.ts +0 -5
- package/dist/decisions.js +0 -13
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-operations.js +43 -68
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +6 -5
- package/dist/help.js +4 -6
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/model.d.ts +4 -6
- package/dist/model.js +1 -1
- package/dist/notifications.js +2 -0
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +12 -20
- package/dist/ports.d.ts +5 -14
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +47 -57
- package/dist/presentation.d.ts +2 -9
- package/dist/presentation.js +13 -48
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +2 -3
- package/dist/sqlite-square.d.ts +0 -1
- package/dist/sqlite-square.js +29 -55
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-core.d.ts +0 -9
- package/dist/square-domain.js +9 -31
- package/dist/square-facade.d.ts +5 -21
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-migration.d.ts +3 -1
- package/dist/square-migration.js +46 -13
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +5 -3
- package/dist/square-sqlite.js +89 -90
- package/dist/square-tools.js +3 -11
- package/dist/views.d.ts +2 -3
- package/dist/views.js +13 -19
- package/dist/wakes.d.ts +0 -3
- package/dist/wakes.js +3 -5
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +5 -5
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/codex-delivery.md +1 -1
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp.md +1 -1
- package/guides/migration.md +12 -6
- package/guides/participant-ownership.md +1 -1
- package/guides/pi-tools.md +2 -2
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +17 -23
- package/guides/square-api.md +77 -10
- package/guides/symmetric-domains.md +92 -0
- package/package.json +14 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +2 -6
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { resolveAudience } from '@astrosheep/square/attention';
|
|
5
|
+
import { receive, consumeReceived, recordConsumed } from '@astrosheep/square/receive';
|
|
6
|
+
import { claimAttempt, settleAttempt, dispatchEffect, preparePresentation, presentPrepared } from '@astrosheep/square/delivery';
|
|
7
|
+
|
|
8
|
+
// Application routing fixture, not discovery or a core identity format.
|
|
9
|
+
const directory = new Map([
|
|
10
|
+
['e/author', 'east'], ['e/reader', 'east'], ['e/other', 'east'],
|
|
11
|
+
['w/author', 'west'], ['w/reader', 'west'], ['w/other', 'west'],
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
// One structural ReceiveWrite adapter. Array insertion order is local order;
|
|
15
|
+
// opaque item keys are only compared for equality, never parsed or sorted.
|
|
16
|
+
function receiveStore(state) {
|
|
17
|
+
const tx = {
|
|
18
|
+
insertItem({ key, data }) {
|
|
19
|
+
if (state.items.has(key)) throw new Error('duplicate item');
|
|
20
|
+
state.items.set(key, structuredClone(data));
|
|
21
|
+
},
|
|
22
|
+
insertAddress(key, address) {
|
|
23
|
+
if (tx.addressed(address.recipient, key)) throw new Error('duplicate address');
|
|
24
|
+
state.addresses.push({ key, ...address });
|
|
25
|
+
},
|
|
26
|
+
heads(recipient, query, { unconsumed, limit }) {
|
|
27
|
+
return state.addresses.filter(row => row.recipient === recipient
|
|
28
|
+
&& (!query.via || row.via === query.via)
|
|
29
|
+
&& (!unconsumed || !tx.receipt({ recipient, item: row.key }))).slice(0, limit);
|
|
30
|
+
},
|
|
31
|
+
addressed: (recipient, key) => state.addresses.find(row => row.recipient === recipient && row.key === key),
|
|
32
|
+
load: keys => new Map(keys.filter(key => state.items.has(key)).map(key => [key, state.items.get(key)])),
|
|
33
|
+
count: (recipient, query, unconsumed) => tx.heads(recipient, query, { unconsumed, limit: Infinity }).length,
|
|
34
|
+
receipt: ({ recipient, item }) => state.receipts.get(recipient)?.get(item),
|
|
35
|
+
insertReceipt({ recipient, item }, at) {
|
|
36
|
+
if (!state.receipts.has(recipient)) state.receipts.set(recipient, new Map());
|
|
37
|
+
const receipts = state.receipts.get(recipient);
|
|
38
|
+
if (receipts.has(item)) return false;
|
|
39
|
+
receipts.set(item, { at });
|
|
40
|
+
return true;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
return tx;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// A tiny illustrative consumer, NOT a storage backend or durable runtime.
|
|
47
|
+
class Domain {
|
|
48
|
+
#state = { items: new Map(), addresses: [], receipts: new Map(), series: [] };
|
|
49
|
+
|
|
50
|
+
constructor(name, nextKey) {
|
|
51
|
+
this.name = name;
|
|
52
|
+
this.nextKey = nextKey;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
transaction(change) {
|
|
56
|
+
const draft = structuredClone(this.#state);
|
|
57
|
+
const result = change(draft, receiveStore(draft));
|
|
58
|
+
if (result instanceof Promise) throw new Error('no awaits inside memory transactions');
|
|
59
|
+
// A throw above discards the draft. No capability or mutable row escapes.
|
|
60
|
+
const captured = structuredClone(result);
|
|
61
|
+
this.#state = draft;
|
|
62
|
+
return captured;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
originate(actor, mentions, text) {
|
|
66
|
+
// Attention names its route; receive stores that application route as via.
|
|
67
|
+
const audience = resolveAudience({ actor, recipients: [...directory.keys()], mentions })
|
|
68
|
+
.map(({ recipient, route }) => ({ recipient, via: route }));
|
|
69
|
+
const key = this.nextKey();
|
|
70
|
+
const data = { text, origin: { domain: this.name, key } };
|
|
71
|
+
this.transaction((_state, tx) => receive(tx, {
|
|
72
|
+
item: { key, data }, audience: audience.filter(a => directory.get(a.recipient) === this.name),
|
|
73
|
+
}));
|
|
74
|
+
const outgoing = new Map();
|
|
75
|
+
for (const address of audience) {
|
|
76
|
+
const destination = directory.get(address.recipient);
|
|
77
|
+
if (destination === this.name) continue;
|
|
78
|
+
if (!outgoing.has(destination)) outgoing.set(destination, []);
|
|
79
|
+
outgoing.get(destination).push(address);
|
|
80
|
+
}
|
|
81
|
+
return { key, data, outgoing };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
admit(data, audience) {
|
|
85
|
+
const key = this.nextKey(); // Destination owns this identity, not origin.key.
|
|
86
|
+
this.transaction((_state, tx) => receive(tx, {
|
|
87
|
+
item: { key, data }, audience,
|
|
88
|
+
}));
|
|
89
|
+
return key;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Captured item/target/purpose are application coordinates, not an encoded key.
|
|
93
|
+
// For dispatch target is a destination; for presentation it is one recipient.
|
|
94
|
+
effectPort(purpose, item, target, fullReceipts = []) {
|
|
95
|
+
const rows = state => {
|
|
96
|
+
let series = state.series.find(s => s.purpose === purpose && s.item === item && s.target === target);
|
|
97
|
+
if (!series) {
|
|
98
|
+
series = { purpose, item, target, attempts: new Map() };
|
|
99
|
+
state.series.push(series);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
list: () => structuredClone([...series.attempts.values()].sort((a, b) => b.ordinal - a.ordinal)),
|
|
103
|
+
byToken: token => structuredClone(series.attempts.get(token)),
|
|
104
|
+
insert: row => { series.attempts.set(row.token, structuredClone(row)); },
|
|
105
|
+
replace: row => { series.attempts.set(row.token, structuredClone(row)); },
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const current = (state, tx) => state.items.has(item) && (purpose === 'dispatch'
|
|
109
|
+
|| Boolean(tx.addressed(target, item)) && !tx.receipt({ recipient: target, item }));
|
|
110
|
+
return {
|
|
111
|
+
claim: async () => this.transaction((state, tx) => {
|
|
112
|
+
if (!current(state, tx)) return undefined;
|
|
113
|
+
const claim = claimAttempt(rows(state), {
|
|
114
|
+
purpose, token: randomUUID(), at: Date.now(), leaseMs: 30_000, data: {},
|
|
115
|
+
});
|
|
116
|
+
return claim.status === 'acquired' ? { token: claim.token, ordinal: claim.ordinal } : undefined;
|
|
117
|
+
}),
|
|
118
|
+
current: async () => this.transaction(current),
|
|
119
|
+
settle: async (claim, completion) => this.transaction((state, tx) => {
|
|
120
|
+
const recorded = settleAttempt(rows(state), {
|
|
121
|
+
token: claim.token, outcome: completion.outcome, at: Date.now(), data: completion,
|
|
122
|
+
});
|
|
123
|
+
// ONE commit: attempt completion plus captured full-consumption receipts.
|
|
124
|
+
// No post-effect cancellation/currentness veto and no re-selection.
|
|
125
|
+
if (recorded && completion.outcome === 'presented') recordConsumed(tx, fullReceipts, Date.now());
|
|
126
|
+
return recorded;
|
|
127
|
+
}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
consume(recipient, selection) {
|
|
132
|
+
return this.transaction((_state, tx) => consumeReceived(tx, recipient, selection, Date.now()));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pending(recipient) {
|
|
136
|
+
return this.transaction((_state, tx) => tx.heads(recipient, {}, { unconsumed: true, limit: Infinity }).map(h => h.key));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
receipts() {
|
|
140
|
+
return this.transaction(state => [...state.receipts].flatMap(([recipient, items]) =>
|
|
141
|
+
[...items].map(([item, { at }]) => ({ recipient, item, at }))));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
attempts() {
|
|
145
|
+
return this.transaction(state => state.series.map(({ purpose, item, target, attempts }) =>
|
|
146
|
+
({ purpose, item, target, outcomes: [...attempts.values()].map(a => a.outcome) })));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async present(recipient, item, sink) {
|
|
150
|
+
const payload = this.transaction((_state, tx) => {
|
|
151
|
+
if (!tx.addressed(recipient, item)) throw new Error('not addressed');
|
|
152
|
+
return tx.load([item]).get(item);
|
|
153
|
+
});
|
|
154
|
+
const prepared = await preparePresentation({
|
|
155
|
+
payload, coverage: 'full',
|
|
156
|
+
port: this.effectPort('presentation', item, recipient, [{ recipient, item }]),
|
|
157
|
+
});
|
|
158
|
+
return prepared ? presentPrepared(prepared, sink) : undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function demonstrateSymmetricDomains() {
|
|
163
|
+
// Same implementation, truly separate stores and unrelated key spaces.
|
|
164
|
+
let westKey = -700;
|
|
165
|
+
const east = new Domain('east', () => `opaque?${randomUUID()}`);
|
|
166
|
+
const west = new Domain('west', () => westKey--);
|
|
167
|
+
const domains = new Map([['east', east], ['west', west]]);
|
|
168
|
+
const handoffs = [];
|
|
169
|
+
let online = true;
|
|
170
|
+
|
|
171
|
+
async function sendAll(source, message, mode = 'accepted') {
|
|
172
|
+
const reports = [];
|
|
173
|
+
for (const [destination, audience] of message.outgoing) {
|
|
174
|
+
const report = await dispatchEffect(source.effectPort('dispatch', message.key, destination), async context => {
|
|
175
|
+
if (!await context.beforeSend()) return { outcome: 'not-capable' };
|
|
176
|
+
// A final policy gate may finish after the runner's timeout. Never mutate
|
|
177
|
+
// the destination after our deadline or cancellation, even if it said yes.
|
|
178
|
+
if (context.signal?.aborted || Date.now() >= context.deadline) return { outcome: 'not-capable' };
|
|
179
|
+
if (!online) return { outcome: 'failed' }; // Known no-send in this in-process fixture.
|
|
180
|
+
const key = domains.get(destination).admit(message.data, audience);
|
|
181
|
+
handoffs.push({ from: source.name, to: destination, key, audience: structuredClone(audience) });
|
|
182
|
+
// Unknown deliberately happens AFTER admission. This is not a dedup protocol.
|
|
183
|
+
return { outcome: mode, data: { destinationKey: key } };
|
|
184
|
+
}, { timeoutMs: 1000 });
|
|
185
|
+
reports.push(report);
|
|
186
|
+
}
|
|
187
|
+
return reports;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const eastMessage = east.originate('e/author', ['e/reader', 'w/reader', 'w/other'], 'East says hello');
|
|
191
|
+
const westMessage = west.originate('w/author', ['w/reader', 'e/reader', 'e/other'], 'West says hello');
|
|
192
|
+
// Establish origin receipts before destination presentation: neither can overwrite the other.
|
|
193
|
+
east.consume('e/reader', { kind: 'exact', key: eastMessage.key, remainingQuery: {} });
|
|
194
|
+
west.consume('w/reader', { kind: 'exact', key: westMessage.key, remainingQuery: {} });
|
|
195
|
+
|
|
196
|
+
const exchanges = [];
|
|
197
|
+
for (const [source, destination, message, mode, first, second] of [
|
|
198
|
+
[east, west, eastMessage, 'accepted', 'w/reader', 'w/other'],
|
|
199
|
+
[west, east, westMessage, 'unknown', 'e/reader', 'e/other'],
|
|
200
|
+
]) {
|
|
201
|
+
const [report] = await sendAll(source, message, mode);
|
|
202
|
+
const destinationKey = report.settlement.completion.data.destinationKey;
|
|
203
|
+
const admission = [destination.pending(first), destination.pending(second)];
|
|
204
|
+
const originReceiptsBefore = source.receipts();
|
|
205
|
+
const output = [];
|
|
206
|
+
// The sink proves only that this example callback captured the full payload.
|
|
207
|
+
const presentation = await destination.present(first, destinationKey, payload => { output.push(payload); });
|
|
208
|
+
const afterPresentation = [destination.pending(first), destination.pending(second)];
|
|
209
|
+
const presentationReceipts = destination.receipts().filter(ref => ref.item === destinationKey);
|
|
210
|
+
const originReceiptsAfter = source.receipts();
|
|
211
|
+
const exact = { kind: 'exact', key: destinationKey, remainingQuery: {} };
|
|
212
|
+
const unmatched = destination.consume(second, { kind: 'page', query: { via: 'bell' }, limit: 1 });
|
|
213
|
+
const consumed = destination.consume(second, { kind: 'page', query: { via: 'mention' }, limit: 1 });
|
|
214
|
+
const repeated = destination.consume(second, exact);
|
|
215
|
+
const handoffsBeforeReplay = handoffs.length;
|
|
216
|
+
const [replay] = await sendAll(source, message, mode);
|
|
217
|
+
exchanges.push({
|
|
218
|
+
from: source.name, to: destination.name, sourceKey: message.key, destinationKey,
|
|
219
|
+
dispatch: report, admission, presentation, output, afterPresentation, presentationReceipts,
|
|
220
|
+
originReceiptsBefore, originReceiptsAfter, unmatched, consumed, repeated, replay,
|
|
221
|
+
handoffsBeforeReplay, handoffsAfterReplay: handoffs.length,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
online = false;
|
|
226
|
+
const handoffsBeforeOffline = handoffs.length;
|
|
227
|
+
const offline = [];
|
|
228
|
+
for (const [domain, author, recipient] of [[east, 'e/author', 'e/reader'], [west, 'w/author', 'w/reader']]) {
|
|
229
|
+
const messages = [];
|
|
230
|
+
for (const text of ['oldest', 'middle', 'newest']) {
|
|
231
|
+
const message = domain.originate(author, [recipient], text);
|
|
232
|
+
messages.push(message);
|
|
233
|
+
await sendAll(domain, message); // Local work has no remote destination at all.
|
|
234
|
+
}
|
|
235
|
+
const exact = { kind: 'exact', key: messages[1].key, remainingQuery: {} };
|
|
236
|
+
const middle = domain.consume(recipient, exact);
|
|
237
|
+
const repeated = domain.consume(recipient, exact);
|
|
238
|
+
const page = () => domain.consume(recipient, { kind: 'page', query: { via: 'mention' }, limit: 1 });
|
|
239
|
+
offline.push({ domain: domain.name, keys: messages.map(m => m.key), middle, repeated, pages: [page(), page()] });
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
exchanges, handoffs, offline, handoffsBeforeOffline, handoffsAfterOffline: handoffs.length,
|
|
243
|
+
attempts: { east: east.attempts(), west: west.attempts() },
|
|
244
|
+
receipts: { east: east.receipts(), west: west.receipts() },
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
249
|
+
console.log(JSON.stringify(await demonstrateSymmetricDomains(), null, 2));
|
|
250
|
+
}
|
|
@@ -117,7 +117,7 @@ production line delta -7
|
|
|
117
117
|
- Root `activity.ts` (271 lines) and `watch.ts` (328 lines) are deleted. Their surviving draft/retry/rendering code is counted in the new CLI helpers (206 + 68 lines); moving renderers is not claimed as semantic deletion.
|
|
118
118
|
- CLI build/join/listener/lifecycle/history/status/catch no longer open storage for admission, before/after facts, projections or sweep coordination. `cli/context.ts` no longer constructs a ledger or discovers bindings during parsing. Application `selected` captures an actor per operation, not a mutable-binding cache.
|
|
119
119
|
- Catch has one leased quiet-wait owner; the old action absolute-idle loop and CLI heartbeat/revision/finish wrappers are removed. Selection carries records/ordinals/header facts in its receipt transaction. Optional nearby presence does not rehydrate the selected page.
|
|
120
|
-
- Express has one preparation/reentry and retry/commit owner. It selects the pending directed feed once, and uses commit-observed count/
|
|
120
|
+
- Express has one preparation/reentry and retry/commit owner. It selects the pending directed feed once, and uses commit-observed count/cap facts rather than rebuilding that feed or issuing a duplicate post-success header query.
|
|
121
121
|
- MCP `publicResult`, stored-activity conversion and status-result policy are deleted. Private producers reuse the facade DTOs instead of maintaining parallel status/unread declarations.
|
|
122
122
|
- Deleted unused `Square.joinWithActivity`, `takeoverWithActivity`, `joinFacts`, `standingCount`; deleted `views.activityPresentation`, `standingCount`, `joinFacts`, `watchPresentation`, and `presentation.renderWatchOutput`. Focused `recoveryObservation` and optional `catchPresence` replace the displaced mixed queries. Supported SDK/session methods remain.
|
|
123
123
|
- `list.ts` no longer parses argv or writes process output; `stream.ts` no longer owns stdout/drain. The CLI owns those adaptations.
|
|
@@ -141,7 +141,7 @@ rg exit: 1
|
|
|
141
141
|
|
|
142
142
|
## private action imports
|
|
143
143
|
pattern: from\s*['"][^'"]*square-actions\.js['"]
|
|
144
|
-
square-application.ts:14:import { catchUp, done, endOwnedSession, express,
|
|
144
|
+
square-application.ts:14:import { catchUp, done, endOwnedSession, express, ignore, implicitJoin, join, takeover, listen, listening, retireEndedSessionRoutes, type ExpressCommittedResult } from './square-actions.js';
|
|
145
145
|
rg exit: 0
|
|
146
146
|
|
|
147
147
|
## adapter direct private execution
|
|
@@ -157,7 +157,7 @@ rg exit: 1
|
|
|
157
157
|
|
|
158
158
|
The adapter grep's sole match resolves to **`cli/registry.ts`**, the argv registry, not the root host-binding `registry.ts`. The AST dependency test resolves paths instead of mistaking equal basenames for equal ownership.
|
|
159
159
|
|
|
160
|
-
`test/architecture-boundary.test.js` passed the transitive adapter/helper traversal and its deliberate hidden-bypass fixtures. It checks all CLI roots, MCP and presentation helpers; permits only named pure bindings (core IDs,
|
|
160
|
+
`test/architecture-boundary.test.js` passed the transitive adapter/helper traversal and its deliberate hidden-bypass fixtures. It checks all CLI roots, MCP and presentation helpers; permits only named pure bindings (core IDs, model syntax/errors, identity formatting, search formatting) and closed protocol edges (harness, native hook I/O, MCP immutable `operationEnv`). It rejects private execution hidden through nested helpers, namespace/extra mixed-module bindings, an exceeded native exception and unbounded imports. These are not blanket directory exemptions. Existing storage-boundary/synchronous-capability tests also pass.
|
|
161
161
|
|
|
162
162
|
## Closure map for pre-delivery findings
|
|
163
163
|
|
|
@@ -174,8 +174,8 @@ These are deliverer closures, not a reviewer verdict. Rows distinguish behavior
|
|
|
174
174
|
| Actor discovery cached across binding changes | No actor cache. Reused-app fixture joins Alice, leaves, binds Bob, then expresses as current Bob without reentering Alice or claiming a second name. |
|
|
175
175
|
| Join count derived by racy pre-read arithmetic | `square-actions.join` returns known joined-or-done count from its authoritative join transaction; takeover returns the same fact from its atomic done+join transaction. `joinOwned` uses returned count, never adds to a pre-read. Verified by transaction/source inspection; existing concurrent-owner/join tests remain passing. Standing headers remain distinct. |
|
|
176
176
|
| Express cleanup/enrichment erases commit or calls it unsent | Resource close cannot replace a completed result. Action route/host publication is best effort after commit. Commit observation replaces duplicate post-read. Boundary fixtures prove postcommit host-publication failure returns the activity, projection-clock failure retains landed ID without replay, and selected-row corruption is unknown rather than proven unsent. Operation-cancellation has before-return commit abort coverage. |
|
|
177
|
-
| Catch completion/enrichment erases a committed page | Raw page, ordinals/count/cap/
|
|
178
|
-
| Replaced/cancelled waiter consumes later activity or releases new lease | Catch selection transaction checks lease ID. Finally cleanup is non-aborted and ID-fenced. Boundary fixtures prove replacement owns consumption, old waiter returns replaced, cancelled waiter releases lease, later attention stays pending.
|
|
177
|
+
| Catch completion/enrichment erases a committed page | Raw page, ordinals/count/cap/terminal facts travel with receipt selection; completion never rereads them. Only optional `catchPresence` can be unavailable. `performance`: real selection hydrates exactly its page; presence/rendering adds no hydration/reads. Operation-cancellation preserves committed consumption after abort. |
|
|
178
|
+
| Replaced/cancelled waiter consumes later activity or releases new lease | Catch selection transaction checks lease ID. Finally cleanup is non-aborted and ID-fenced. Boundary fixtures prove replacement owns consumption, old waiter returns replaced, cancelled waiter releases lease, later attention stays pending. Lease expiry and CLI watch-wait heartbeat fixtures pass. |
|
|
179
179
|
| SDK projection roundtrip / leaked observations | The current SDK result shapes are documented in [Square/Participant API](square-api.md); parity tests exercise the real resource and projection tests preserve the detached delivered predicate. |
|
|
180
180
|
| Duplicate contracts, aliases and dead mixed views | Private status/unread producers import/re-export facade contracts; catch observation extends shared expression facts and enriched projection reuses it; required express participant replaces name alias. Retired helper search is empty. Recovery selects only gates/bounded unread summaries, not discarded feed/ordinals/perceptions. |
|
|
181
181
|
| Packed owner orchestration hides effects/count integrity | History, lifecycle branches, maintenance/discovery/recovery, catch conflict and SDK recognize are expanded into readable steps. Remaining one-line SDK delegates are supported simple resource calls, not packed async policy. Inspect implementation diff; all new source is included in count. |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Attention audience resolution
|
|
2
|
+
|
|
3
|
+
The standalone `@astrosheep/square/attention` subpath computes an audience from
|
|
4
|
+
caller data. It does not open a square, send or consume anything, or grant permission.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { resolveAudience } from '@astrosheep/square/attention';
|
|
8
|
+
|
|
9
|
+
const audience = resolveAudience({
|
|
10
|
+
actor: 'east:alice',
|
|
11
|
+
recipients: ['east:bob', 'west:carol'],
|
|
12
|
+
mentions: ['west:carol'],
|
|
13
|
+
incoming: [{ listener: 'east:bob', kind: 'listen' }],
|
|
14
|
+
});
|
|
15
|
+
// [{ recipient: 'west:carol', route: 'mention' },
|
|
16
|
+
// { recipient: 'east:bob', route: 'attention' }]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The caller supplies eligible recipients in canonical spelling and order, and owns
|
|
20
|
+
their validation and persistence. Identity is exact by default; `sameIdentity` may
|
|
21
|
+
provide a deterministic equivalence relation. The first equivalent recipient gives
|
|
22
|
+
the output spelling. Mentions precede the reply author, followed by listeners in
|
|
23
|
+
recipient order; ignores exclude all three. A `reach: 'bell'` reaches all eligible
|
|
24
|
+
recipients except the actor, bypassing ignores. Equivalent recipients appear once.
|
|
25
|
+
|
|
26
|
+
The caller decides whether a bell's recipient snapshot is local or global, and when
|
|
27
|
+
that snapshot is authoritative. Square supplies its standing participants and
|
|
28
|
+
incoming relationships at landing, inside its persistence transaction. The result
|
|
29
|
+
is computed attention, separate from delivery and consumption.
|
|
30
|
+
|
|
31
|
+
For an installed consumer composing this with receive and delivery in two equal,
|
|
32
|
+
independent domains, see the [symmetric domains example](symmetric-domains.md).
|
package/guides/change-waits.md
CHANGED
|
@@ -28,9 +28,9 @@ notifications are hints, not activity delivery or consumption receipts.
|
|
|
28
28
|
returns promptly. An existing active delivery observer will reconsider release/expiry; absent an
|
|
29
29
|
observer, pending work waits for the next normal delivery opportunity. A lease does not create a
|
|
30
30
|
process that was not already running.
|
|
31
|
-
- Blocking catch waits for conversation revision or its heartbeat/quiet deadline.
|
|
31
|
+
- Blocking catch waits for conversation revision or its heartbeat/quiet deadline. An active catch keeps
|
|
32
32
|
its single lease alive and pauses quiet expiry. Core catch and streams use the same storage change
|
|
33
|
-
detector.
|
|
33
|
+
detector. Throttling waits for its eligibility time.
|
|
34
34
|
- Deferrals are independent of catch filters, presentation claims, full-delivery seen receipts,
|
|
35
35
|
clipped previews and session-local cancellation. Sharing storage detection does not share session
|
|
36
36
|
delivery state. Cancellation never creates a receipt or removes archive history. A deferral cannot
|
package/guides/codex-delivery.md
CHANGED
|
@@ -40,7 +40,7 @@ an exact owner from `harnessOwner({ channel: 'codex', session })`, not a bare se
|
|
|
40
40
|
boundary store uses Square's caller-selected `SQUARE_CODEX_BOUNDARIES` file,
|
|
41
41
|
defaulting to `~/.square/codex-boundaries.json`; its JSON observations are not
|
|
42
42
|
conversation storage and are not proof of current native execution or consumption.
|
|
43
|
-
Square's format-
|
|
43
|
+
Square's format-8 artifact remains authoritative for membership, attachment and delivery evidence.
|
|
44
44
|
Detached members appear away indefinitely and keep accumulating unread audience, but have no
|
|
45
45
|
write or proactive delivery eligibility until same-owner reattachment. Only a host-confirmed
|
|
46
46
|
permanent deletion uses fenced membership-ending cleanup; Codex SessionEnd is not that proof.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Delivery custody primitives
|
|
2
|
+
|
|
3
|
+
`@astrosheep/square/delivery` provides storage-independent attempt transitions, suppression predicates,
|
|
4
|
+
and four bound effect functions: `dispatchEffect`, `preparePresentation`, `presentPrepared`, and
|
|
5
|
+
`presentPreparedBatch`. It does not open a square, interpret identities, own transactions, or schedule
|
|
6
|
+
retries. Square's actual dispatch, single presentation and boundary batch adapters compose these same
|
|
7
|
+
lifecycles; its SQLite adapter uses the attempt transitions with existing tables. Ownership, membership,
|
|
8
|
+
cancellation and delivery-deferral policy remain in the bound application capability.
|
|
9
|
+
|
|
10
|
+
## Bind a series inside a transaction
|
|
11
|
+
|
|
12
|
+
`AttemptRows<D>` is a capability for **one logical item / recipient / authority / purpose series**.
|
|
13
|
+
The caller chooses those coordinates, including arbitrary opaque strings, and binds the store to them.
|
|
14
|
+
No Square name, seat, generation, activity-id codec or composite-key format is required.
|
|
15
|
+
|
|
16
|
+
- `list()` returns that series newest ordinal first.
|
|
17
|
+
- `byToken(token)` returns only a matching attempt in that series.
|
|
18
|
+
- `insert(attempt)` and `replace(attempt)` write within the caller's current transaction.
|
|
19
|
+
- Replacements preserve purpose, token, ordinal and the caller's external series coordinates.
|
|
20
|
+
|
|
21
|
+
The caller guarantees isolation against competing claims, token uniqueness within its token lookup
|
|
22
|
+
scope, atomic commit/rollback, and durable storage where needed. Run each operation and its reads and
|
|
23
|
+
writes inside that boundary; an asynchronous read followed later by an unguarded write is not custody.
|
|
24
|
+
Stores must not retain mutable references that allow a caller to change durable rows without a write.
|
|
25
|
+
`D` belongs entirely to the caller. The primitive does not merge, serialize or interpret it.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { claimAttempt, prepareAttempt, settleAttempt, type AttemptRows } from '@astrosheep/square/delivery';
|
|
29
|
+
|
|
30
|
+
type Payload = { route?: string; nativeToken?: string; ack?: string };
|
|
31
|
+
// Supplied by the application, already scoped and transaction-bound:
|
|
32
|
+
declare const rows: AttemptRows<Payload>;
|
|
33
|
+
|
|
34
|
+
const claim = claimAttempt(rows, {
|
|
35
|
+
purpose: 'dispatch', token: 'application-generated-unique-token',
|
|
36
|
+
at: 1000, leaseMs: 30_000, data: { route: 'opaque endpoint' },
|
|
37
|
+
});
|
|
38
|
+
if (claim.status === 'acquired') {
|
|
39
|
+
prepareAttempt(rows, { token: claim.token, data: { route: 'opaque endpoint', nativeToken: 'correlation' } });
|
|
40
|
+
}
|
|
41
|
+
// In a later transaction, only after external proof of acceptance:
|
|
42
|
+
settleAttempt(rows, {
|
|
43
|
+
token: 'application-generated-unique-token', outcome: 'accepted', at: 1100,
|
|
44
|
+
data: { route: 'opaque endpoint', nativeToken: 'correlation', ack: 'external proof' },
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The example shows only transaction-scoped transitions. Commit a successful claim before external I/O;
|
|
49
|
+
settle its exact token in a new transaction after observing the result. The bound effect capability
|
|
50
|
+
below connects those operations without introducing a generic transaction runtime. Authority fencing,
|
|
51
|
+
currentness policy and the meaning of external proof remain the application's responsibility.
|
|
52
|
+
An `accepted` dispatch is not proof of presentation or consumption. Only the responsible presentation
|
|
53
|
+
boundary may assert `presented`; consuming an item is a separate application operation.
|
|
54
|
+
|
|
55
|
+
## Transitions
|
|
56
|
+
|
|
57
|
+
An `Attempt<D>` has `purpose: 'dispatch' | 'presentation'`, `token`, zero-based `ordinal`, `outcome`,
|
|
58
|
+
finite `at`, optional `expiresAt`, and `data`.
|
|
59
|
+
|
|
60
|
+
- `claimAttempt` first returns terminal evidence: dispatch `accepted`/`unknown`, presentation
|
|
61
|
+
`presented`/`unknown`. Otherwise a live `dispatching` attempt is `busy`; missing expiry is live.
|
|
62
|
+
- At exact expiry or later, an interrupted `dispatching` attempt becomes `unknown`, keeps its original
|
|
63
|
+
payload, loses expiry, and returns `terminal` with `interrupted: true`. There is no new attempt and
|
|
64
|
+
**no automatic replay of uncertainty**. Square maps that flag to its interruption diagnostic.
|
|
65
|
+
- With neither terminal nor dispatching evidence, claim inserts `dispatching` at the next ordinal.
|
|
66
|
+
Failed/released attempts can therefore be retried. Purpose-specific terminal meanings are not
|
|
67
|
+
inferred from other completion labels.
|
|
68
|
+
- `prepareAttempt` replaces only the data of the exact `dispatching` token. Pass the complete merged
|
|
69
|
+
payload. It returns false for an absent or no-longer-dispatching token. Callers that already know
|
|
70
|
+
the payload is unchanged may skip the replacement; core performs no JSON equality checks.
|
|
71
|
+
- `settleAttempt` allows `dispatching` to any defined completion, and late `unknown` to `accepted`.
|
|
72
|
+
It clears expiry and sets time/outcome/data. `accepted` to `accepted` returns true **without any
|
|
73
|
+
write or payload/time mutation**. All other transitions return false, including `unknown` to
|
|
74
|
+
`presented`. Missing or foreign tokens cannot settle another series.
|
|
75
|
+
|
|
76
|
+
Invalid purpose/completion/token/time, non-positive or unsafe lease durations, non-finite expiry, and
|
|
77
|
+
unsafe next ordinals reject before mutation with `TypeError` or `RangeError`, not `SquareError`.
|
|
78
|
+
The caller supplies valid stored attempts and consistently scoped, ordered rows.
|
|
79
|
+
|
|
80
|
+
## Suppression without payloads
|
|
81
|
+
|
|
82
|
+
`presentationBlocksDispatch(attempts, now)` and `automaticDispatchEligible(attempts, now)` accept only
|
|
83
|
+
`purpose`, `outcome`, and optional `expiresAt`; no dummy payload or identity is needed. These predicates
|
|
84
|
+
may see both purposes for the same logical delivery.
|
|
85
|
+
|
|
86
|
+
Presentation `presented`, `clipped`, `unknown`, or live `dispatching` blocks dispatch. Expired
|
|
87
|
+
presentation dispatching alone does not. Automatic dispatch additionally requires no dispatch
|
|
88
|
+
`accepted`, `unknown`, or `dispatching`; even expired dispatch custody blocks automatic replay.
|
|
89
|
+
These are preselection predicates, not substitutes for a transactional claim or application gates.
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## Bound effects: one captured capability, four functions
|
|
93
|
+
|
|
94
|
+
`EffectPort<C>` is bound by the caller to **one captured work item / recipient / authority / purpose**.
|
|
95
|
+
It exposes only:
|
|
96
|
+
|
|
97
|
+
- `claim(signal?)`: the application's existing atomic claim, returning `{ token, ordinal }` or
|
|
98
|
+
`undefined` when busy, terminal, stale or deferred. The runner does not claim the attempt twice.
|
|
99
|
+
- `current(signal?)`: pre-effect policy; a rejected check is unverifiable and treated as false.
|
|
100
|
+
- `settle(claim, completion)`: one captured settlement operation, with no request signal. Return true
|
|
101
|
+
only when it commits, false when not recorded (not necessarily stale), or throw on persistence error.
|
|
102
|
+
|
|
103
|
+
The application owns tokens, leases, semantic timestamps, authority, proof/diagnostic payloads and
|
|
104
|
+
resource lifetimes. There is no public transaction type, series-key codec or extra persisted lifecycle.
|
|
105
|
+
`Settlement<C>` keeps the selected completion separate from persistence: `recorded`, `not-recorded`,
|
|
106
|
+
or `error` with the original error. Observations are never erased by persistence refusal/error.
|
|
107
|
+
|
|
108
|
+
### Dispatch
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { dispatchEffect, type EffectPort, type DispatchCompletion } from '@astrosheep/square/delivery';
|
|
112
|
+
|
|
113
|
+
declare const port: EffectPort<DispatchCompletion>; // bound claim/current/settle
|
|
114
|
+
// A sender must check its absolute deadline/cancellation immediately before external mutation.
|
|
115
|
+
const report = await dispatchEffect(port, async context => {
|
|
116
|
+
if (!await context.beforeSend()) return { outcome: 'not-capable' };
|
|
117
|
+
if (context.signal?.aborted || Date.now() >= context.deadline) return { outcome: 'unknown' };
|
|
118
|
+
// Call the application's transport here, passing the remaining budget and captured claim.
|
|
119
|
+
return { outcome: 'accepted', data: { proof: 'application acknowledgement' } };
|
|
120
|
+
}, { timeoutMs: 5000 });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`dispatchEffect` owns claim → initial currentness → send → settlement. Cancellation suppresses
|
|
124
|
+
pre-effect work. It starts its timer immediately before invoking the sender, not during claim,
|
|
125
|
+
initial policy reads or persistence. Exactly one returned/thrown/timeout decision is frozen before
|
|
126
|
+
settlement. A sender exception defaults to `unknown`, not failed; returned `not-capable` releases
|
|
127
|
+
custody. A denied final gate releases with `not-current / final` under the sender's obligation to
|
|
128
|
+
obey it. No automatic retry occurs.
|
|
129
|
+
|
|
130
|
+
`beforeSend()` is **policy currentness, not an elapsed-budget gate**. A stalled policy check may
|
|
131
|
+
return true after the outer timeout. The sender must recheck `deadline` and `signal` after that await
|
|
132
|
+
and immediately before I/O; true does not authorize a late send. Outer timeout stays unknown even
|
|
133
|
+
when no I/O is known to have started. Late gates and late promise results cannot rewrite the frozen
|
|
134
|
+
settlement. Separately correlated native proof may still use the attempt transition's permitted late
|
|
135
|
+
unknown → accepted path; the timed-out runner does not reconcile itself.
|
|
136
|
+
|
|
137
|
+
### Prepared presentation
|
|
138
|
+
|
|
139
|
+
`preparePresentation({ payload, coverage, port }, { signal? })` acquires custody without running a
|
|
140
|
+
sink. A returned `PreparedPresentation<P>` exposes `payload`, pre-effect `eligible(signal?)`, and
|
|
141
|
+
promise-cached `release()` for abandonment. It is a local one-shot capability, not a durable row and
|
|
142
|
+
not a public success writer.
|
|
143
|
+
|
|
144
|
+
`presentPrepared(handle, sink, control?)` specializes the batch lifecycle to one item.
|
|
145
|
+
`presentPreparedBatch(handles, render, sink, control?)` takes all supplied custody before its first
|
|
146
|
+
await. It rejects duplicate, foreign or used handles before any effect, checks eligibility, then calls
|
|
147
|
+
synchronous side-effect-free `render(eligible)` returning `{ payload, included }`. Included handles
|
|
148
|
+
must be a unique exact subset of those eligible handles; invalid rendering releases custody and
|
|
149
|
+
rejects without presentation evidence. No included items means no sink call.
|
|
150
|
+
|
|
151
|
+
Cancellation is checked immediately before the one sink invocation. There is **no presentation timer**
|
|
152
|
+
and no post-effect cancellation, deferral or currentness veto. The runner awaits the sink and marks
|
|
153
|
+
**all included handles** effect-observed before starting asynchronous settlements. Full coverage
|
|
154
|
+
settles `presented`; clipped coverage settles `clipped`. A thrown sink defaults to `unknown`;
|
|
155
|
+
`classifyError` may explicitly return `failed` (classifier failure stays unknown). Unused handles are
|
|
156
|
+
released, all sibling settlements and cleanup drain even on persistence failure, and later cleanup
|
|
157
|
+
cannot overwrite success or uncertainty.
|
|
158
|
+
|
|
159
|
+
### Mandatory atomic full-consumption settlement
|
|
160
|
+
|
|
161
|
+
The presentation binding's single `settle` operation must enforce:
|
|
162
|
+
|
|
163
|
+
| Completion | Atomic persistence |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| `presented` | Complete the captured attempt **and all captured full-consumption receipts in the same transaction**. Return true only when the combined operation commits. |
|
|
166
|
+
| `clipped` | Complete the attempt; no full-consumption receipt. |
|
|
167
|
+
| `failed`, `unknown`, `released` | Complete the attempt; no success consumption receipt. |
|
|
168
|
+
|
|
169
|
+
If receipt writing throws, both receipt writes and attempt completion must roll back. Do not reselect
|
|
170
|
+
items or split `complete` and `recordConsumed` into separately awaited callbacks. Captured ownership
|
|
171
|
+
still fences settlement; request cancellation and new delivery deferrals do not. TypeScript cannot
|
|
172
|
+
supply isolation for a caller, so this is a mandatory port contract, tested in Square's real transaction.
|
|
173
|
+
|
|
174
|
+
Presentation reports distinguish observed sink behavior from durable persistence. A returned sink
|
|
175
|
+
with `not-recorded` or `error` still returned successfully; that alone proves only the supplied sink's
|
|
176
|
+
documented effect, not model consumption or human display. Square explicitly classifies its existing
|
|
177
|
+
rejected sinks as failed and rethrows the original exception after draining. Its single result stays
|
|
178
|
+
`{ presented: true, activity }` only on recorded success, false on ownership refusal; unexpected
|
|
179
|
+
success-settlement errors propagate. Batch returns its original sink value after all settlements,
|
|
180
|
+
with the same error propagation. No schema, storage format or public Square result changes are needed.
|
package/guides/mcp.md
CHANGED
|
@@ -4,7 +4,7 @@ Square provides a stdio MCP server through `square mcp-server`.
|
|
|
4
4
|
|
|
5
5
|
Set `SQUARE_LOCATION` to the default `.square` artifact path; calls may override it with `location`. Relative paths use the server's startup working directory. Join with an explicit `as` name. Later calls may omit `as` only when the artifact recognizes exactly one current name held by the call's exact opaque owner; zero or multiple matches refuse and require explicit selection. An explicit name selects a voice but never grants custody. Request metadata supplies the live Codex (`threadId`) or OpenCode (`ai.opencode/sessionID`) coordinate when present, encoded through `harnessOwner`; otherwise the server-configured owner applies. CLI startup resolves `--owner`/`SQUARE_OWNER`, a complete harness coordinate, or the local user owner in that order, refusing incomplete harness identity. Owner is never a model tool argument. Read-only calls do not need an owner.
|
|
6
6
|
|
|
7
|
-
The server exposes `join`, `express`, `catch`, `history`, `attention`, `
|
|
7
|
+
The server exposes `join`, `express`, `catch`, `history`, `attention`, `done`, and `status`. `attention` takes `action: listen | unlisten | ignore`; `status` also reports who you listen to when a name resolves. `catch` consumes activities. `history` is read-only and accepts stable `act/<index>` cursors. All seven tools return the same human-readable text as the CLI in `content`, without `outputSchema` or `structuredContent`. Square failures set `isError: true` and return readable refusal and recovery text in `content`. Clients keep their native MCP result envelope; machine-readable domain results remain available through the SDK.
|
|
8
8
|
|
|
9
9
|
Claude's bundled plugin configures this server automatically. Its mod supplies live session/cwd/subagent attribution through an out-of-band tool-use-ID association, not AI-visible arguments; see [Claude call identity](claude-native.md#mcp-tools-and-call-identity). The bundled endpoint requires that association rather than falling back to its startup environment.
|
|
10
10
|
|
package/guides/migration.md
CHANGED
|
@@ -4,7 +4,8 @@ Square stores its authority in one versioned SQLite artifact. Format 1 kept a sn
|
|
|
4
4
|
format 2 introduced typed facts; format 3 added `unlisten`; format 4 moved participant custody
|
|
5
5
|
and delivery evidence into the artifact; format 5 added independent delivery deferrals.
|
|
6
6
|
**Format 6 separates participant membership from host attachment; format 7 replaces harness Body
|
|
7
|
-
coordinates with opaque owners
|
|
7
|
+
coordinates with opaque owners; format 8 removes Square hold/resume activities and held state.**
|
|
8
|
+
Existing artifacts are never converted implicitly.
|
|
8
9
|
|
|
9
10
|
Stop all processes using the artifact, make an offline backup, then run:
|
|
10
11
|
|
|
@@ -13,12 +14,16 @@ square --location /path/to/SQUARE.square migrate
|
|
|
13
14
|
square --location /path/to/SQUARE.square doctor
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
The command handles supported formats 2–
|
|
17
|
+
The command handles supported formats 2–7 in place and publishes only final format 8, in one
|
|
17
18
|
transaction. It does not make a backup, stop processes, install a runtime or update another
|
|
18
19
|
artifact. For formats 2/3, also freeze and back up the legacy HostLedger used for owner/evidence
|
|
19
20
|
import. A legacy ledger needing its own conversion must be prepared separately with its old
|
|
20
21
|
runtime's offline tooling. Snapshot format 1 is not accepted by this conversion.
|
|
21
22
|
|
|
23
|
+
Format 7 to 8 deletes hold/resume activities and rows keyed to them, clears replies that point to
|
|
24
|
+
deleted activities, and preserves every surviving activity index. Gaps remain legal; `lastActIndex`
|
|
25
|
+
is the greatest surviving index and `nextActIndex` never reuses a deleted index.
|
|
26
|
+
|
|
22
27
|
## Membership and attachment after conversion
|
|
23
28
|
|
|
24
29
|
Membership owns a participant's join/done history, read horizon and outgoing listens. Attachment
|
|
@@ -101,9 +106,10 @@ not import. Evidence without current or verified completed ownership is skipped
|
|
|
101
106
|
read-only legacy ledger remains available for offline diagnosis; an absent ledger permits route-
|
|
102
107
|
derived custody, but an unreadable or unsupported ledger refuses before changing the square.
|
|
103
108
|
|
|
104
|
-
One transaction imports verified authority/evidence, adds deferral and attachment support
|
|
105
|
-
publishes format
|
|
106
|
-
|
|
109
|
+
One transaction imports verified authority/evidence, adds deferral and attachment support, applies
|
|
110
|
+
the format-7-to-8 deletion, and publishes format 8. Existing conversation and membership facts are
|
|
111
|
+
preserved apart from the deleted hold/resume rows and cleared replies; no intermediate format is
|
|
112
|
+
published. Legacy catch ownership is generation-bound only for exact standing owner
|
|
107
113
|
spellings; otherwise it stays unbound. Legacy routes are consumed only during migration and are
|
|
108
114
|
not a live authority source. The new delivery revision starts at zero. Format 2 first adds support
|
|
109
115
|
for `unlisten`, then follows the same conversion.
|
|
@@ -142,7 +148,7 @@ rows are ignored. Rejoin with the exact migrated owner to republish discovery (o
|
|
|
142
148
|
harness reconnect). This preserves generation, listens and unread activity; do not use takeover
|
|
143
149
|
merely to rebuild the index.
|
|
144
150
|
|
|
145
|
-
Upgrade all attached runtimes before reopening format
|
|
151
|
+
Upgrade all attached runtimes before reopening format 8; older runtimes must not be mixed with it.
|
|
146
152
|
Updating a package does not migrate an artifact, and migration does not install upgraded runtimes.
|
|
147
153
|
After conversion, run read-only `doctor` before restarting clients. To roll back a committed
|
|
148
154
|
conversion, stop all clients and restore the offline backup with its compatible older runtime;
|
|
@@ -53,7 +53,7 @@ for resource lifetimes and adapter selection.
|
|
|
53
53
|
failed reattachment does not block the others. A new or forked session has a different owner and
|
|
54
54
|
restores nothing. Hosts never infer restoration from a historical departure.
|
|
55
55
|
- Participant mutations require both the captured generation and an attached current seat.
|
|
56
|
-
Detach suspends existing handles; they cannot express, catch, listen,
|
|
56
|
+
Detach suspends existing handles; they cannot express, catch, listen, leave or change
|
|
57
57
|
deferrals until reattachment. Rejoin/takeover fences old handles permanently, even if the same
|
|
58
58
|
owner later reacquires the name. Host cleanup checks owner and generation inside its transaction;
|
|
59
59
|
stale, foreign, done or already-detached detach changes nothing. Host `cancelDeliveries` also
|
package/guides/pi-tools.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Pi native tools
|
|
2
2
|
|
|
3
|
-
The existing Square Pi extension registers `square_join`, `square_express`, `square_catch`, `square_history`, `square_attention`, `
|
|
3
|
+
The existing Square Pi extension registers `square_join`, `square_express`, `square_catch`, `square_history`, `square_attention`, `square_done`, and `square_status`. `square_attention` covers listen, unlisten and ignore; `square_status` also shows who you listen to. No additional MCP server or subprocess is needed.
|
|
4
4
|
|
|
5
5
|
Tools use the `square` namespace with codemode exposure. Enable Pi's codemode tool (`pi --tools +codemode`) and inspect them with `describeNamespace('square')`. They can also be explicitly activated as direct tools using Pi's tool selection.
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ overview if there are zero or multiple. The extension encodes the live Pi sessio
|
|
|
22
22
|
`harnessOwner({channel: 'pi', session})`, never a model-provided session id, without mutating
|
|
23
23
|
the process environment.
|
|
24
24
|
|
|
25
|
-
All
|
|
25
|
+
All seven tools return the CLI's human-readable text, without an `outputSchema` or
|
|
26
26
|
`structuredContent`. In Pi codemode, a successful call resolves to a `string`:
|
|
27
27
|
use `text(result)`, not `text(result.text)`. Failures are marked as failed tool results;
|
|
28
28
|
codemode rejects with the readable error and recovery commands rather than returning
|