@astrosheep/square 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-boundary-state.d.ts +20 -0
- package/dist/claude-boundary-state.js +84 -0
- package/dist/claude-hook.d.ts +2 -0
- package/dist/claude-hook.js +231 -0
- package/dist/claude-mcp-context.d.ts +1 -1
- package/dist/claude-mcp-context.js +12 -3
- package/dist/cli/context.js +7 -9
- package/dist/cli/observation-commands.d.ts +4 -1
- package/dist/cli/observation-commands.js +37 -4
- package/dist/cli/registry.js +5 -2
- 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-claude.js +3 -4
- package/dist/harness-links.js +63 -7
- package/dist/harness.js +12 -17
- package/dist/help.js +4 -1
- 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 +2 -3
- 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 +7 -39
- package/examples/symmetric-domains.mjs +29 -249
- package/guides/change-waits.md +13 -53
- package/guides/claude-native.md +7 -171
- package/guides/codex-delivery.md +55 -80
- package/guides/delivery-primitives.md +48 -207
- package/guides/mcp.md +1 -1
- 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 +2 -3
- package/plugins/claude/hooks/hooks.json +11 -1
- 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/claude-delivery.d.ts +0 -12
- package/dist/claude-delivery.js +0 -108
- package/dist/claude-mod.d.ts +0 -8
- package/dist/claude-mod.js +0 -172
- package/dist/claude-transport.d.ts +0 -5
- package/dist/claude-transport.js +0 -58
- 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/claude/hooks/register.js +0 -132
- package/plugins/claude/types.d.ts +0 -7
- 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
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
|
|
3
|
+
import { dispatchPostedCard } from './notifications.js';
|
|
4
|
+
import { retainDeliveryLedger } from './delivery-lifetime.js';
|
|
5
|
+
import { projectNotification, displayAttentionPath } from './attention-presentation.js';
|
|
6
|
+
import { coreDone } from './decisions.js';
|
|
7
|
+
import { activityMetadataHasContent } from './model.js';
|
|
8
|
+
import { formatActivityId } from './square-core.js';
|
|
9
|
+
import { squarePostKey } from './square-post.js';
|
|
10
|
+
const openLedger = (env) => DeliveryLedger.open({
|
|
11
|
+
location: deliveryLedgerLocation(env),
|
|
12
|
+
});
|
|
13
|
+
function renderPost(location, post) {
|
|
14
|
+
if (post.kind === 'notice')
|
|
15
|
+
return { text: post.body ?? '', full: true };
|
|
16
|
+
const projected = projectNotification({ squarePath: location, actIndex: post.index, recipient: post.recipient,
|
|
17
|
+
actor: post.from, route: post.route, body: post.body ?? '',
|
|
18
|
+
metadataOnly: !(post.body?.trim()) && activityMetadataHasContent(post.metadata) });
|
|
19
|
+
return { text: projected.text, full: projected.isFull };
|
|
20
|
+
}
|
|
21
|
+
/** Local producer custody and current host attachment are different observations. */
|
|
22
|
+
export async function deliverySessionsForSeats(ledger, seats) {
|
|
23
|
+
const owners = [...new Set(seats.map(seat => seat.owner))];
|
|
24
|
+
return new Map(await Promise.all(owners.map(async (owner) => [owner, await ledger.session(owner)])));
|
|
25
|
+
}
|
|
26
|
+
/** Read-only host observations for status; callers fence them against the captured World seats. */
|
|
27
|
+
export async function readSquareSessions(seats, env) {
|
|
28
|
+
if (!seats.length)
|
|
29
|
+
return new Map();
|
|
30
|
+
const ledger = await openLedger(env);
|
|
31
|
+
try {
|
|
32
|
+
return await deliverySessionsForSeats(ledger, seats);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await ledger.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** A first producer join may register an absent owner, never resume or revive one. */
|
|
39
|
+
export async function ensureInitialDeliverySession(owner, env, route) {
|
|
40
|
+
const ledger = await openLedger(env);
|
|
41
|
+
try {
|
|
42
|
+
const current = await ledger.session(owner);
|
|
43
|
+
if (!current) {
|
|
44
|
+
return !!await ledger.attach({ owner, expectedGeneration: null, routes: route ? [route] : [] });
|
|
45
|
+
}
|
|
46
|
+
if (current.state !== 'attached')
|
|
47
|
+
return false;
|
|
48
|
+
if (route)
|
|
49
|
+
return ledger.updateRoutes({ owner, generation: current.generation,
|
|
50
|
+
routes: [...current.routes.filter(candidate => candidate.kind !== route.kind), route] });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await ledger.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Called only by privileged mutations. Sandbox lease/ACK/selection never enters here. */
|
|
58
|
+
export async function reconcileSquareDelivery(artifact, location, env, clock) {
|
|
59
|
+
const snapshot = (await artifact.read(facts => ({ seats: facts.roster().filter(person => person.joined)
|
|
60
|
+
.flatMap(person => { const seat = facts.seat(person.name); return seat ? [seat] : []; }), posts: facts.postedPosts() }))).value;
|
|
61
|
+
if (!snapshot.seats.length && !snapshot.posts.length)
|
|
62
|
+
return;
|
|
63
|
+
const ledger = await openLedger(env);
|
|
64
|
+
try {
|
|
65
|
+
const sessions = await deliverySessionsForSeats(ledger, snapshot.seats);
|
|
66
|
+
for (const seat of snapshot.seats) {
|
|
67
|
+
const session = sessions.get(seat.owner);
|
|
68
|
+
if (!session)
|
|
69
|
+
continue;
|
|
70
|
+
if (session.state === 'deleted') {
|
|
71
|
+
await artifact.transactAs({ kind: 'host-cleanup', seat }, facts => {
|
|
72
|
+
const current = facts.seat(seat.participant);
|
|
73
|
+
if (current?.owner === seat.owner && current.generation === seat.generation && facts.participant(seat.participant)?.joined)
|
|
74
|
+
facts.land(coreDone(facts, seat.participant, '', clock()));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else if (seat.attachment !== session.state) {
|
|
78
|
+
const attachment = session.state;
|
|
79
|
+
await artifact.transactAs({ kind: 'producer' }, facts => facts.setSeatAttachment(seat, attachment));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// A full receipt is monotonic external presentation evidence, never inferred from silence.
|
|
83
|
+
const receipts = new Map();
|
|
84
|
+
for (const owner of new Set(snapshot.posts.map(post => post.owner))) {
|
|
85
|
+
const keys = snapshot.posts.filter(post => post.owner === owner).map(post => squarePostKey(location, post));
|
|
86
|
+
receipts.set(owner, new Map((await ledger.receipts(owner, { keys })).map(receipt => [receipt.key, receipt.at])));
|
|
87
|
+
}
|
|
88
|
+
for (const post of snapshot.posts) {
|
|
89
|
+
const cardKey = squarePostKey(location, post);
|
|
90
|
+
const receipt = receipts.get(post.owner)?.get(cardKey);
|
|
91
|
+
if (receipt !== undefined && post.full === true && !post.consumed)
|
|
92
|
+
await artifact.transactAs({ kind: 'producer' }, facts => facts.foldReceipt(post, receipt));
|
|
93
|
+
}
|
|
94
|
+
await suppressWithdrawn(artifact, ledger, location);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await ledger.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function suppressWithdrawn(artifact, ledger, location) {
|
|
101
|
+
const posts = (await artifact.read(facts => facts.postedPosts().filter(post => {
|
|
102
|
+
const seat = facts.seat(post.recipient);
|
|
103
|
+
return post.consumed || !seat || seat.owner !== post.owner || seat.generation !== post.generation;
|
|
104
|
+
}))).value;
|
|
105
|
+
// Departure withdraws only the captured notification, never producer read history or held effects.
|
|
106
|
+
for (const post of posts)
|
|
107
|
+
await ledger.suppress({ owner: post.owner, key: squarePostKey(location, post), scope: 'exact' });
|
|
108
|
+
}
|
|
109
|
+
/** One export path for Square activities and Square-owned notices. No transaction spans I/O. */
|
|
110
|
+
export async function flushSquarePosts(artifact, location, env, clock) {
|
|
111
|
+
// Delivery enrichment cannot turn a committed producer mutation into a retry of that mutation.
|
|
112
|
+
let lifetime;
|
|
113
|
+
try {
|
|
114
|
+
const snapshot = (await artifact.read(facts => ({ pending: facts.pendingPosts(), posted: facts.postedPosts() }))).value;
|
|
115
|
+
if (!snapshot.pending.length && !snapshot.posted.length)
|
|
116
|
+
return;
|
|
117
|
+
const ledger = await openLedger(env);
|
|
118
|
+
lifetime = retainDeliveryLedger(ledger);
|
|
119
|
+
for (const post of snapshot.pending) {
|
|
120
|
+
const session = await ledger.session(post.owner);
|
|
121
|
+
if (session?.state !== 'attached')
|
|
122
|
+
continue;
|
|
123
|
+
const rendered = renderPost(location, post), token = randomUUID();
|
|
124
|
+
const owned = (await artifact.transactAs({ kind: 'producer' }, facts => facts.acquirePost({ ...post, full: rendered.full }, token, process.pid, clock(), Date.now()))).value;
|
|
125
|
+
if (!owned)
|
|
126
|
+
continue;
|
|
127
|
+
let posted = false, knownUnsent = true;
|
|
128
|
+
try {
|
|
129
|
+
// This check is not custody across the await; the ledger independently fences delivery.
|
|
130
|
+
const current = await ledger.session(post.owner);
|
|
131
|
+
if (current?.state !== 'attached' || current.generation !== session.generation
|
|
132
|
+
|| !(await artifact.read(facts => facts.ownsPost(token, Date.now()))).value)
|
|
133
|
+
continue;
|
|
134
|
+
knownUnsent = false;
|
|
135
|
+
const result = await ledger.post({ owner: post.owner, key: squarePostKey(location, post), text: rendered.text,
|
|
136
|
+
...(post.kind === 'activity' ? { details: { kind: 'presentation', origin: displayAttentionPath(location),
|
|
137
|
+
ref: formatActivityId(post.index), author: post.from, body: post.body ?? '',
|
|
138
|
+
...(post.route === 'bell' ? { badge: '🔔' } : {}) } } : {}),
|
|
139
|
+
});
|
|
140
|
+
posted = ['posted', 'duplicate', 'suppressed'].includes(result.status);
|
|
141
|
+
knownUnsent = !posted;
|
|
142
|
+
if (result.status === 'conflict')
|
|
143
|
+
throw new Error('A Square card key already holds different text.');
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
const disposition = posted ? 'posted' : owned === 'initial' && knownUnsent ? 'never-exported' : 'uncertain';
|
|
147
|
+
await artifact.settleAs({ kind: 'producer' }, facts => facts.settlePost(token, rendered.full, disposition));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await suppressWithdrawn(artifact, ledger, location);
|
|
151
|
+
const posts = (await artifact.read(facts => facts.postedPosts().filter(post => {
|
|
152
|
+
const seat = facts.seat(post.recipient);
|
|
153
|
+
return !post.consumed && seat?.owner === post.owner && seat.generation === post.generation;
|
|
154
|
+
}))).value;
|
|
155
|
+
for (const owner of new Set(posts.map(post => post.owner))) {
|
|
156
|
+
const session = await ledger.session(owner);
|
|
157
|
+
if (session?.state !== 'attached')
|
|
158
|
+
continue;
|
|
159
|
+
const pendingKeys = new Set((await ledger.pending(owner, { generation: session.generation })).map(card => card.key));
|
|
160
|
+
for (const post of posts.filter(post => post.owner === owner)) {
|
|
161
|
+
const key = squarePostKey(location, post);
|
|
162
|
+
if (pendingKeys.has(key))
|
|
163
|
+
await dispatchPostedCard(ledger, owner, key, lifetime.track, env);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Unsettled/uncertain producer work is retried with its stable key at the next mutation.
|
|
169
|
+
// Missing ledger evidence never proves that an external card was not inserted or delivered.
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
await lifetime?.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { ProducerRead, ProducerWrite } from './square-post.js';
|
|
3
|
+
/** Private Square producer bookkeeping, on the artifact's existing connection/transaction. */
|
|
4
|
+
export declare const producerSchemaSql = "CREATE TABLE square_notices (\n notice_index INTEGER PRIMARY KEY CHECK (notice_index >= 0), at REAL NOT NULL,\n sender_json TEXT NOT NULL, body_json TEXT NOT NULL, metadata_json TEXT\n);\nCREATE TABLE notice_audience (\n notice_index INTEGER NOT NULL, recipient_key_json TEXT NOT NULL, recipient_json TEXT NOT NULL,\n owner_json TEXT NOT NULL, generation_json TEXT NOT NULL, consumed_at REAL,\n PRIMARY KEY (notice_index,recipient_key_json)\n) WITHOUT ROWID;\nCREATE TABLE producer_consumption (\n recipient_key_json TEXT NOT NULL, owner_json TEXT NOT NULL, generation_json TEXT NOT NULL,\n sender_json TEXT NOT NULL, match_json TEXT NOT NULL, at REAL NOT NULL,\n PRIMARY KEY (recipient_key_json,generation_json,sender_json,match_json)\n) WITHOUT ROWID;\nCREATE TABLE square_posts (\n kind TEXT NOT NULL CHECK (kind IN ('activity','notice')), item_index INTEGER NOT NULL,\n recipient_key_json TEXT NOT NULL, token TEXT UNIQUE, owner_pid INTEGER,\n first_export_at REAL NOT NULL, claim_at REAL NOT NULL,\n posted INTEGER NOT NULL DEFAULT 0 CHECK (posted IN (0,1)), full INTEGER CHECK (full IN (0,1)),\n PRIMARY KEY (kind,item_index,recipient_key_json)\n) WITHOUT ROWID;";
|
|
5
|
+
export declare function initializeProducerSqlite(db: DatabaseSync): void;
|
|
6
|
+
export declare function resetProducerSqlite(db: DatabaseSync): void;
|
|
7
|
+
export declare function createProducerDriver(db: DatabaseSync): ProducerRead & ProducerWrite & {
|
|
8
|
+
readonly changed: boolean;
|
|
9
|
+
readonly deliveryChanged: boolean;
|
|
10
|
+
consumeActivity(index: number, at: number): void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { matchesMetadata, validateMetadataMatch } from './delivery-deferral.js';
|
|
2
|
+
import { SquareError, sameName, validateName, validateActivityMetadata } from './model.js';
|
|
3
|
+
import { decoded, encode, finite, integer, json, key, object } from './sqlite-values.js';
|
|
4
|
+
/** Private Square producer bookkeeping, on the artifact's existing connection/transaction. */
|
|
5
|
+
export const producerSchemaSql = `CREATE TABLE square_notices (
|
|
6
|
+
notice_index INTEGER PRIMARY KEY CHECK (notice_index >= 0), at REAL NOT NULL,
|
|
7
|
+
sender_json TEXT NOT NULL, body_json TEXT NOT NULL, metadata_json TEXT
|
|
8
|
+
);
|
|
9
|
+
CREATE TABLE notice_audience (
|
|
10
|
+
notice_index INTEGER NOT NULL, recipient_key_json TEXT NOT NULL, recipient_json TEXT NOT NULL,
|
|
11
|
+
owner_json TEXT NOT NULL, generation_json TEXT NOT NULL, consumed_at REAL,
|
|
12
|
+
PRIMARY KEY (notice_index,recipient_key_json)
|
|
13
|
+
) WITHOUT ROWID;
|
|
14
|
+
CREATE TABLE producer_consumption (
|
|
15
|
+
recipient_key_json TEXT NOT NULL, owner_json TEXT NOT NULL, generation_json TEXT NOT NULL,
|
|
16
|
+
sender_json TEXT NOT NULL, match_json TEXT NOT NULL, at REAL NOT NULL,
|
|
17
|
+
PRIMARY KEY (recipient_key_json,generation_json,sender_json,match_json)
|
|
18
|
+
) WITHOUT ROWID;
|
|
19
|
+
CREATE TABLE square_posts (
|
|
20
|
+
kind TEXT NOT NULL CHECK (kind IN ('activity','notice')), item_index INTEGER NOT NULL,
|
|
21
|
+
recipient_key_json TEXT NOT NULL, token TEXT UNIQUE, owner_pid INTEGER,
|
|
22
|
+
first_export_at REAL NOT NULL, claim_at REAL NOT NULL,
|
|
23
|
+
posted INTEGER NOT NULL DEFAULT 0 CHECK (posted IN (0,1)), full INTEGER CHECK (full IN (0,1)),
|
|
24
|
+
PRIMARY KEY (kind,item_index,recipient_key_json)
|
|
25
|
+
) WITHOUT ROWID;`;
|
|
26
|
+
export function initializeProducerSqlite(db) { db.exec(producerSchemaSql); }
|
|
27
|
+
export function resetProducerSqlite(db) {
|
|
28
|
+
for (const table of ['square_posts', 'producer_consumption', 'notice_audience', 'square_notices'])
|
|
29
|
+
db.exec(`DROP TABLE ${table}`);
|
|
30
|
+
}
|
|
31
|
+
function canonicalMatch(match) {
|
|
32
|
+
const fields = validateMetadataMatch(match).fields.map(field => ({ path: [...field.path], equals: field.equals }));
|
|
33
|
+
fields.sort((a, b) => {
|
|
34
|
+
const left = JSON.stringify(a.path), right = JSON.stringify(b.path);
|
|
35
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
36
|
+
});
|
|
37
|
+
return encode({ fields });
|
|
38
|
+
}
|
|
39
|
+
const EXPORT_IDENTITY_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
|
|
40
|
+
const EXPORT_OWNERSHIP_MS = 60_000;
|
|
41
|
+
function withinExportWindow(first, now) {
|
|
42
|
+
return now >= first && now - first < EXPORT_IDENTITY_WINDOW_MS;
|
|
43
|
+
}
|
|
44
|
+
function processIsAlive(pid) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, 0);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return error.code !== 'ESRCH';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function createProducerDriver(db) {
|
|
54
|
+
let changed = false;
|
|
55
|
+
const rows = (sql, args = []) => db.prepare(sql).all(...args);
|
|
56
|
+
const one = (sql, args = []) => db.prepare(sql).get(...args);
|
|
57
|
+
const run = (sql, args = []) => {
|
|
58
|
+
const count = Number(db.prepare(sql).run(...args).changes);
|
|
59
|
+
if (count)
|
|
60
|
+
changed = true;
|
|
61
|
+
return count;
|
|
62
|
+
};
|
|
63
|
+
function authority(seat) {
|
|
64
|
+
const current = one(`SELECT s.*,p.joined FROM participant_seats s JOIN participants p
|
|
65
|
+
ON p.name_key_json=s.participant_key_json WHERE s.participant_key_json=?`, [key(seat.participant)]);
|
|
66
|
+
if (!current || current.joined !== 1 || current.owner_json !== encode(seat.owner)
|
|
67
|
+
|| current.generation_json !== encode(seat.generation) || current.attachment !== 'attached')
|
|
68
|
+
throw new SquareError('already_joined', 'This participant seat is away or has been replaced.', { reason: 'needs_seat' });
|
|
69
|
+
}
|
|
70
|
+
function consumed(post) {
|
|
71
|
+
return rows(`SELECT sender_json,match_json FROM producer_consumption WHERE recipient_key_json=? AND owner_json=? AND generation_json=?`, [key(post.recipient), encode(post.owner), encode(post.generation)])
|
|
72
|
+
.some(row => sameName(decoded(row.sender_json, true), post.from) && matchesMetadata(post.metadata, json(row.match_json)));
|
|
73
|
+
}
|
|
74
|
+
function knownConsumed(post) {
|
|
75
|
+
return post.kind === 'activity'
|
|
76
|
+
? !!one('SELECT 1 FROM caught_activity WHERE recipient_key_json=? AND act_index=?', [key(post.recipient), post.index])
|
|
77
|
+
|| !!one(`SELECT 1 FROM activities WHERE kind='read' AND actor_key_json=? AND json_extract(payload_json,'$.through')>=?`, [key(post.recipient), post.index])
|
|
78
|
+
: one('SELECT consumed_at FROM notice_audience WHERE recipient_key_json=? AND notice_index=?', [key(post.recipient), post.index])?.consumed_at != null;
|
|
79
|
+
}
|
|
80
|
+
function consume(post, at) {
|
|
81
|
+
return post.kind === 'activity'
|
|
82
|
+
? run('INSERT INTO caught_activity(recipient_key_json,act_index,at) VALUES(?,?,?) ON CONFLICT DO NOTHING', [key(post.recipient), post.index, at])
|
|
83
|
+
: run('UPDATE notice_audience SET consumed_at=? WHERE recipient_key_json=? AND notice_index=? AND consumed_at IS NULL', [at, key(post.recipient), post.index]);
|
|
84
|
+
}
|
|
85
|
+
function allPosts() {
|
|
86
|
+
const activity = rows(`SELECT a.act_index,a.at,a.actor_json,a.payload_json,d.*,p.name_json,
|
|
87
|
+
x.full,x.posted FROM activities a JOIN landed_audience d ON d.act_index=a.act_index
|
|
88
|
+
JOIN participants p ON p.name_key_json=d.recipient_key_json
|
|
89
|
+
LEFT JOIN square_posts x ON x.kind='activity' AND x.item_index=a.act_index AND x.recipient_key_json=d.recipient_key_json
|
|
90
|
+
WHERE a.kind='say' AND d.owner_json IS NOT NULL AND d.generation_json IS NOT NULL ORDER BY a.act_index,d.recipient_order`)
|
|
91
|
+
.map((row) => {
|
|
92
|
+
const payload = object(json(row.payload_json));
|
|
93
|
+
return { kind: 'activity', index: integer(row.act_index), recipient: decoded(row.name_json, true),
|
|
94
|
+
owner: decoded(row.owner_json, true), generation: decoded(row.generation_json, true), from: decoded(row.actor_json, true), at: finite(row.at),
|
|
95
|
+
...(payload.body === undefined ? {} : { body: payload.body }),
|
|
96
|
+
...(payload.metadata === undefined ? {} : { metadata: payload.metadata }), route: row.reason,
|
|
97
|
+
...(row.full === null ? {} : { full: row.full === 1 }) };
|
|
98
|
+
});
|
|
99
|
+
const notices = rows(`SELECT n.*,a.*,x.full FROM square_notices n JOIN notice_audience a USING(notice_index)
|
|
100
|
+
LEFT JOIN square_posts x ON x.kind='notice' AND x.item_index=n.notice_index AND x.recipient_key_json=a.recipient_key_json ORDER BY n.notice_index,a.recipient_key_json`)
|
|
101
|
+
.map((row) => ({ kind: 'notice', index: integer(row.notice_index), recipient: decoded(row.recipient_json, true),
|
|
102
|
+
owner: decoded(row.owner_json, true), generation: decoded(row.generation_json, true), from: decoded(row.sender_json, true), at: finite(row.at),
|
|
103
|
+
body: decoded(row.body_json), ...(row.metadata_json === null ? {} : { metadata: json(row.metadata_json) }),
|
|
104
|
+
...(row.full === null ? {} : { full: row.full === 1 }) }));
|
|
105
|
+
return [...activity, ...notices].map(post => ({ ...post, consumed: knownConsumed(post) }));
|
|
106
|
+
}
|
|
107
|
+
const driver = {
|
|
108
|
+
pendingPosts() {
|
|
109
|
+
return allPosts().filter(post => !post.consumed && !one(`SELECT 1 FROM square_posts WHERE kind=? AND item_index=? AND recipient_key_json=? AND posted=1`, [post.kind, post.index, key(post.recipient)]));
|
|
110
|
+
},
|
|
111
|
+
postedPosts() {
|
|
112
|
+
return allPosts().filter(post => !!one(`SELECT 1 FROM square_posts WHERE kind=? AND item_index=? AND recipient_key_json=? AND (posted=1 OR token IS NOT NULL)`, [post.kind, post.index, key(post.recipient)]));
|
|
113
|
+
},
|
|
114
|
+
ownsPost(token, exportAt = Date.now()) {
|
|
115
|
+
const row = one('SELECT * FROM square_posts WHERE token=?', [token]);
|
|
116
|
+
return !!row && row.posted !== 1 && withinExportWindow(finite(row.first_export_at), finite(exportAt))
|
|
117
|
+
&& exportAt >= finite(row.claim_at) && exportAt - finite(row.claim_at) < EXPORT_OWNERSHIP_MS;
|
|
118
|
+
},
|
|
119
|
+
insertNotice(input, at) {
|
|
120
|
+
validateName(input.from);
|
|
121
|
+
if (typeof input.body !== 'string' || !input.body.trim())
|
|
122
|
+
throw new SquareError('invalid_args', 'A notification needs a non-empty body.');
|
|
123
|
+
const metadata = validateActivityMetadata(input.metadata);
|
|
124
|
+
const targets = typeof input.to === 'string' ? [input.to] : input.to;
|
|
125
|
+
if (!Array.isArray(targets) || targets.length < 1 || targets.length > 10)
|
|
126
|
+
throw new SquareError('invalid_args', 'A notification needs between 1 and 10 recipients.');
|
|
127
|
+
const recipients = [];
|
|
128
|
+
for (const name of targets) {
|
|
129
|
+
validateName(name);
|
|
130
|
+
if (recipients.some(row => sameName(decoded(row.participant_json, true), name)))
|
|
131
|
+
continue;
|
|
132
|
+
const row = one(`SELECT s.* FROM participant_seats s JOIN participants p ON p.name_key_json=s.participant_key_json WHERE p.joined=1 AND p.name_key_json=?`, [key(name)]);
|
|
133
|
+
if (!row)
|
|
134
|
+
throw new SquareError('invalid_args', `Notification recipient ${name} is not standing.`, { reason: 'not_standing', targets: [name] });
|
|
135
|
+
recipients.push(row);
|
|
136
|
+
}
|
|
137
|
+
const index = integer(one('SELECT COALESCE(MAX(notice_index),-1)+1 AS n FROM square_notices').n);
|
|
138
|
+
if (!Number.isSafeInteger(index + 1))
|
|
139
|
+
throw new SquareError('invalid_args', 'Notification index is exhausted.');
|
|
140
|
+
const body = input.body;
|
|
141
|
+
run('INSERT INTO square_notices VALUES(?,?,?,?,?)', [index, finite(at), encode(input.from), encode(body), metadata === undefined ? null : encode(metadata)]);
|
|
142
|
+
for (const row of recipients) {
|
|
143
|
+
const post = { kind: 'notice', index, recipient: decoded(row.participant_json, true), owner: decoded(row.owner_json, true),
|
|
144
|
+
generation: decoded(row.generation_json, true), from: input.from, body, at, ...(metadata === undefined ? {} : { metadata }) };
|
|
145
|
+
run('INSERT INTO notice_audience VALUES(?,?,?,?,?,?)', [index, key(post.recipient), encode(post.recipient), encode(post.owner), encode(post.generation), consumed(post) ? at : null]);
|
|
146
|
+
}
|
|
147
|
+
return { id: `notice/${index}`, at, from: input.from, to: recipients.map(row => decoded(row.participant_json, true)), body, ...(metadata === undefined ? {} : { metadata }) };
|
|
148
|
+
},
|
|
149
|
+
ackMatching(seat, input, at) {
|
|
150
|
+
authority(seat);
|
|
151
|
+
validateName(input.from);
|
|
152
|
+
if (input.future !== 'exact')
|
|
153
|
+
throw new SquareError('invalid_args', 'Future consumption requires an explicit exact outcome.');
|
|
154
|
+
const match = canonicalMatch(input.match);
|
|
155
|
+
run('INSERT INTO producer_consumption VALUES(?,?,?,?,?,?) ON CONFLICT DO NOTHING', [key(seat.participant), encode(seat.owner), encode(seat.generation), key(input.from), match, finite(at)]);
|
|
156
|
+
let count = 0;
|
|
157
|
+
for (const post of allPosts()) {
|
|
158
|
+
if (sameName(post.recipient, seat.participant) && post.owner === seat.owner && post.generation === seat.generation
|
|
159
|
+
&& sameName(post.from, input.from) && matchesMetadata(post.metadata, input.match) && !post.consumed)
|
|
160
|
+
count += consume(post, at);
|
|
161
|
+
}
|
|
162
|
+
return count;
|
|
163
|
+
},
|
|
164
|
+
acquirePost(post, token, pid, now, exportAt = Date.now()) {
|
|
165
|
+
// A scanned value is not custody. Re-read all source and policy facts under the World writer.
|
|
166
|
+
const current = allPosts().find(candidate => candidate.kind === post.kind && candidate.index === post.index && candidate.recipient === post.recipient);
|
|
167
|
+
if (!current || current.owner !== post.owner || current.generation !== post.generation || current.consumed)
|
|
168
|
+
return false;
|
|
169
|
+
const seat = one('SELECT * FROM participant_seats WHERE participant_key_json=?', [key(post.recipient)]);
|
|
170
|
+
if (!seat || seat.owner_json !== encode(post.owner) || seat.generation_json !== encode(post.generation) || seat.attachment !== 'attached')
|
|
171
|
+
return false;
|
|
172
|
+
if (consumed(current)) {
|
|
173
|
+
consume(current, now);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const previous = one('SELECT * FROM square_posts WHERE kind=? AND item_index=? AND recipient_key_json=?', [post.kind, post.index, key(post.recipient)]);
|
|
177
|
+
finite(exportAt);
|
|
178
|
+
if (previous?.posted === 1)
|
|
179
|
+
return false;
|
|
180
|
+
const first = previous ? finite(previous.first_export_at) : exportAt;
|
|
181
|
+
// Missing ledger identity after retention cannot prove an abandoned export was unsent.
|
|
182
|
+
if (!withinExportWindow(first, exportAt) || previous && exportAt < finite(previous.claim_at))
|
|
183
|
+
return false;
|
|
184
|
+
if (previous?.token != null && exportAt >= finite(previous.claim_at)
|
|
185
|
+
&& exportAt - finite(previous.claim_at) < EXPORT_OWNERSHIP_MS && processIsAlive(integer(previous.owner_pid, 1)))
|
|
186
|
+
return false;
|
|
187
|
+
run(`INSERT INTO square_posts(kind,item_index,recipient_key_json,token,owner_pid,full,first_export_at,claim_at) VALUES(?,?,?,?,?,?,?,?)
|
|
188
|
+
ON CONFLICT(kind,item_index,recipient_key_json) DO UPDATE SET token=excluded.token,owner_pid=excluded.owner_pid,full=excluded.full,claim_at=excluded.claim_at`, [post.kind, post.index, key(post.recipient), token, integer(pid, 1), post.full === undefined ? null : Number(post.full), first, exportAt]);
|
|
189
|
+
return previous ? 'recovered' : 'initial';
|
|
190
|
+
},
|
|
191
|
+
settlePost(token, full, disposition) {
|
|
192
|
+
// Only a proved-unsent initial claim may erase its export age; prior uncertainty survives.
|
|
193
|
+
if (disposition === 'never-exported')
|
|
194
|
+
return run('DELETE FROM square_posts WHERE token=?', [token]) > 0;
|
|
195
|
+
return run('UPDATE square_posts SET token=NULL,owner_pid=NULL,posted=?,full=? WHERE token=?', [Number(disposition === 'posted'), Number(full), token]) > 0;
|
|
196
|
+
},
|
|
197
|
+
foldReceipt(post, at) {
|
|
198
|
+
const current = allPosts().find(candidate => candidate.kind === post.kind && candidate.index === post.index && candidate.recipient === post.recipient);
|
|
199
|
+
if (!current || current.owner !== post.owner || current.generation !== post.generation || current.full !== true || current.consumed)
|
|
200
|
+
return false;
|
|
201
|
+
return consume(current, finite(at)) > 0;
|
|
202
|
+
},
|
|
203
|
+
consumeActivity(index, at) {
|
|
204
|
+
for (const post of allPosts())
|
|
205
|
+
if (post.kind === 'activity' && post.index === index && consumed(post))
|
|
206
|
+
consume(post, at);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
return Object.defineProperties(driver, { changed: { get: () => changed }, deliveryChanged: { get: () => changed } });
|
|
210
|
+
}
|
package/dist/square-sqlite.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import { type ActivityObservation, type HardCap, type Seat, type StoredAct, type HydratedAct, type WatchLease } from './model.js';
|
|
3
3
|
import type { SquareHeader, SquareRead } from './ports.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { SeatRef } from './ports.js';
|
|
5
5
|
import type { StoreFormat } from './artifact-store.js';
|
|
6
6
|
export { invalidArtifact } from './sqlite-values.js';
|
|
7
|
-
export { endpointGeneration } from './
|
|
7
|
+
export { validateEndpointGeneration as endpointGeneration } from './message.js';
|
|
8
8
|
export declare const APPLICATION_ID = 1397838405;
|
|
9
|
-
export declare const CURRENT_USER_VERSION =
|
|
9
|
+
export declare const CURRENT_USER_VERSION = 12;
|
|
10
10
|
/** Explicit import/export DTOs, not a runtime transaction model. */
|
|
11
11
|
export interface SquareRuntimeState {
|
|
12
12
|
nextActIndex: number;
|
|
@@ -22,8 +22,8 @@ export interface SquareState {
|
|
|
22
22
|
runtime: SquareRuntimeState;
|
|
23
23
|
}
|
|
24
24
|
export declare function emptyRuntimeState(nextActIndex?: number): SquareRuntimeState;
|
|
25
|
-
/**
|
|
26
|
-
export declare const AGORA_SCHEMA_SQL = "CREATE TABLE square_meta (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),\n hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),\n throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),\n preamble_json TEXT NOT NULL CHECK (json_valid(preamble_json)),\n warmup_json TEXT NOT NULL CHECK (json_valid(warmup_json))\n);\nCREATE TABLE activities (\n act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),\n at REAL NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('join','done','say','read','listen','ignore','unlisten')),\n actor_json TEXT COLLATE BINARY NOT NULL,\n actor_key_json TEXT COLLATE BINARY NOT NULL,\n reach TEXT CHECK (reach = 'bell'),\n reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),\n
|
|
25
|
+
/** Square owns activity, audience, participant, and read-state facts. */
|
|
26
|
+
export declare const AGORA_SCHEMA_SQL = "CREATE TABLE square_meta (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),\n hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),\n throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),\n preamble_json TEXT NOT NULL CHECK (json_valid(preamble_json)),\n warmup_json TEXT NOT NULL CHECK (json_valid(warmup_json))\n);\nCREATE TABLE activities (\n act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),\n at REAL NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('join','done','say','read','listen','ignore','unlisten')),\n actor_json TEXT COLLATE BINARY NOT NULL,\n actor_key_json TEXT COLLATE BINARY NOT NULL,\n reach TEXT CHECK (reach = 'bell'),\n reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))\n);\nCREATE INDEX activity_kind_index ON activities(kind, act_index);\nCREATE INDEX activity_actor_index ON activities(actor_key_json, act_index);\nCREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say';\nCREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say';\nCREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell';\nCREATE TABLE participants (\n name_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,\n name_json TEXT NOT NULL,\n first_index INTEGER NOT NULL UNIQUE,\n joined INTEGER NOT NULL CHECK (joined IN (0,1)),\n done INTEGER NOT NULL CHECK (done IN (0,1)),\n say_count INTEGER NOT NULL CHECK (say_count >= 0),\n last_active_at REAL,\n last_join_index INTEGER,\n CHECK (NOT (joined=1 AND done=1))\n) WITHOUT ROWID;\nCREATE TABLE attention (\n listener_key_json TEXT COLLATE BINARY NOT NULL,\n target_key_json TEXT COLLATE BINARY NOT NULL,\n target_json TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('listen','ignore')),\n since_index INTEGER NOT NULL,\n PRIMARY KEY (listener_key_json, target_key_json)\n) WITHOUT ROWID;\nCREATE INDEX attention_target ON attention(target_key_json, kind, listener_key_json);\nCREATE TABLE landed_audience (\n act_index INTEGER NOT NULL,\n recipient_key_json TEXT COLLATE BINARY NOT NULL,\n recipient_order INTEGER NOT NULL CHECK (recipient_order >= 0),\n reason TEXT NOT NULL CHECK (reason IN ('mention','reply','attention','bell')),\n owner_json TEXT,\n generation_json TEXT,\n PRIMARY KEY (act_index, recipient_key_json),\n UNIQUE (act_index, recipient_order)\n) WITHOUT ROWID;\nCREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);\nCREATE TABLE participant_seats (\n participant_key_json TEXT PRIMARY KEY NOT NULL, participant_json TEXT NOT NULL,\n owner_json TEXT NOT NULL, generation_json TEXT NOT NULL,\n wake_address_json TEXT, attachment TEXT NOT NULL CHECK (attachment IN ('attached','detached'))\n) WITHOUT ROWID;\nCREATE INDEX participant_seat_owner ON participant_seats(owner_json);\nCREATE TABLE caught_activity (\n recipient_key_json TEXT NOT NULL, act_index INTEGER NOT NULL, at REAL NOT NULL,\n PRIMARY KEY (recipient_key_json,act_index)\n) WITHOUT ROWID;\nCREATE TABLE catch_leases (\n recipient_key_json TEXT PRIMARY KEY NOT NULL, generation_json TEXT NOT NULL,\n lease_json TEXT NOT NULL CHECK (json_valid(lease_json))\n) WITHOUT ROWID;";
|
|
27
27
|
export interface AgoraConfig {
|
|
28
28
|
readonly hardCap?: number | null;
|
|
29
29
|
readonly throttlePerMinute?: number;
|
|
@@ -32,38 +32,28 @@ export interface AgoraConfig {
|
|
|
32
32
|
}
|
|
33
33
|
export declare function initializeAgoraSqlite(db: DatabaseSync, configuration?: AgoraConfig): void;
|
|
34
34
|
export declare function resetAgoraSqlite(db: DatabaseSync): void;
|
|
35
|
-
/**
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
initialize(db: DatabaseSync): void;
|
|
39
|
-
reset(db: DatabaseSync): void;
|
|
40
|
-
}
|
|
41
|
-
/** Explicit full-Square framing and reset, composed from the neutral store and the supplied Channel schema. */
|
|
42
|
-
export declare function squareStoreFormat(lower: LowerSchema, resetStorage: (db: DatabaseSync) => void): StoreFormat;
|
|
43
|
-
/** A persisted upper activity. A say is a relation: it may not carry content and must name its message. */
|
|
35
|
+
/** Full Square framing and reset, including producer state. */
|
|
36
|
+
export declare function squareStoreFormat(resetStorage: (db: DatabaseSync) => void): StoreFormat;
|
|
37
|
+
/** Validate the complete persisted activity. */
|
|
44
38
|
export declare function validateStoredActivity(value: unknown): StoredAct;
|
|
45
|
-
/**
|
|
39
|
+
/** Validate complete activity content. */
|
|
46
40
|
export declare function validateHydratedActivity(value: unknown): HydratedAct;
|
|
47
41
|
export declare function validateWatchLease(value: unknown): WatchLease;
|
|
48
42
|
export declare function readSquareHeader(db: DatabaseSync): SquareHeader;
|
|
49
43
|
export interface SqliteDriver extends SquareRead {
|
|
50
|
-
readonly channel: ChannelWrite;
|
|
51
44
|
storeSeat(seat: Seat): boolean;
|
|
52
|
-
removeSeat(seat:
|
|
45
|
+
removeSeat(seat: SeatRef): boolean;
|
|
53
46
|
applyLanding(effect: import('./square-domain.js').LandingEffect): void;
|
|
54
47
|
readonly changed: boolean;
|
|
55
48
|
readonly deliveryChanged: boolean;
|
|
56
|
-
/** Explicit import replay
|
|
49
|
+
/** Explicit import replay of activity and audience facts. */
|
|
57
50
|
importLanding(activity: HydratedAct): StoredAct;
|
|
58
51
|
}
|
|
59
|
-
export declare function createSqliteDriver(db: DatabaseSync
|
|
60
|
-
readonly changed: boolean;
|
|
61
|
-
readonly deliveryChanged: boolean;
|
|
62
|
-
}): SqliteDriver;
|
|
52
|
+
export declare function createSqliteDriver(db: DatabaseSync): SqliteDriver;
|
|
63
53
|
/** Explicit import replay. Caller owns the transaction and format publication. */
|
|
64
|
-
export declare function initializeSqlite(db: DatabaseSync, state: SquareState, revision: number
|
|
54
|
+
export declare function initializeSqlite(db: DatabaseSync, state: SquareState, revision: number): void;
|
|
65
55
|
export declare function exportSqliteState(db: DatabaseSync): SquareState;
|
|
66
|
-
/** Read-only integrity inspection
|
|
67
|
-
export declare function inspectSqlite(db: DatabaseSync
|
|
56
|
+
/** Read-only integrity inspection. */
|
|
57
|
+
export declare function inspectSqlite(db: DatabaseSync): {
|
|
68
58
|
standingCount: number;
|
|
69
59
|
};
|