@astrosheep/square 0.3.86 → 0.3.88
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 +6 -1
- package/dist/artifact.js +79 -10
- package/dist/boundary-presentation.d.ts +5 -4
- package/dist/boundary-presentation.js +21 -25
- package/dist/catch-decisions.js +2 -1
- package/dist/claude-delivery-status.d.ts +8 -2
- package/dist/claude-delivery-status.js +26 -10
- package/dist/claude-delivery.d.ts +8 -4
- package/dist/claude-delivery.js +114 -104
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +119 -105
- package/dist/cli/context.d.ts +3 -1
- package/dist/cli/context.js +25 -1
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +14 -10
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +1 -0
- package/dist/cli/registry.js +0 -2
- package/dist/cli/square-commands.d.ts +4 -2
- package/dist/cli/square-commands.js +21 -6
- package/dist/codex-hook.d.ts +3 -3
- package/dist/codex-hook.js +4 -4
- package/dist/codex-queue.d.ts +5 -6
- package/dist/codex-queue.js +9 -5
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +18 -6
- package/dist/delivery-health.d.ts +32 -2
- package/dist/delivery-health.js +108 -45
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +155 -196
- package/dist/delivery.d.ts +14 -23
- package/dist/delivery.js +48 -18
- package/dist/file-lock.d.ts +8 -1
- package/dist/file-lock.js +2 -0
- package/dist/help.js +8 -11
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -3
- package/dist/list.js +9 -4
- package/dist/mcp.js +12 -1
- package/dist/model.d.ts +39 -5
- package/dist/model.js +84 -0
- package/dist/notifications.d.ts +8 -6
- package/dist/notifications.js +56 -143
- package/dist/operation-presentation.js +7 -4
- package/dist/participant-identity.d.ts +0 -3
- package/dist/participant-identity.js +0 -31
- package/dist/paseo-delivery.d.ts +5 -6
- package/dist/paseo-delivery.js +13 -8
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/delivery.d.ts +8 -3
- package/dist/plugins/opencode/delivery.js +187 -132
- package/dist/plugins/opencode/index.js +12 -38
- package/dist/ports.d.ts +112 -40
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +51 -35
- package/dist/presentation.js +8 -3
- package/dist/seats.d.ts +7 -0
- package/dist/seats.js +27 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +10 -12
- package/dist/sqlite-square.d.ts +20 -9
- package/dist/sqlite-square.js +476 -341
- package/dist/square-api.d.ts +1 -0
- package/dist/square-api.js +3 -4
- package/dist/square-core.d.ts +3 -2
- package/dist/square-domain.d.ts +12 -5
- package/dist/square-domain.js +163 -7
- package/dist/square-facade.d.ts +16 -10
- package/dist/square-index.d.ts +24 -0
- package/dist/square-index.js +131 -0
- package/dist/square-legacy-framing.d.ts +9 -0
- package/dist/square-legacy-framing.js +56 -0
- package/dist/square-migration-host-ledger.d.ts +26 -0
- package/dist/square-migration-host-ledger.js +168 -0
- package/dist/square-migration.d.ts +5 -5
- package/dist/square-migration.js +373 -71
- package/dist/square-sqlite.d.ts +9 -9
- package/dist/square-sqlite.js +329 -79
- package/dist/square-stdio.d.ts +1 -1
- package/dist/square-stdio.js +12 -7
- package/dist/square-tools.d.ts +1 -1
- package/dist/square-tools.js +80 -27
- package/dist/stdio-server.js +4 -2
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +52 -70
- package/dist/wakes.d.ts +6 -5
- package/dist/wakes.js +20 -17
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +9 -9
- package/guides/mcp.md +2 -2
- package/guides/migration.md +107 -67
- package/guides/pi-tools.md +1 -1
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +61 -28
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/register.js +1 -1
- package/plugins/claude/skills/square/SKILL.md +4 -4
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +170 -118
- package/plugins/opencode/index.ts +12 -30
- package/plugins/pi/square-pi.js +56 -18
- package/skills/square/SKILL.md +4 -4
- package/dist/cli/ledger-command.d.ts +0 -12
- package/dist/cli/ledger-command.js +0 -72
- package/dist/host-ledger-file-adapter.d.ts +0 -47
- package/dist/host-ledger-file-adapter.js +0 -384
- package/dist/host-ledger-migration.d.ts +0 -27
- package/dist/host-ledger-migration.js +0 -264
- package/dist/host-ledger-root.d.ts +0 -2
- package/dist/host-ledger-root.js +0 -9
- package/dist/host-ledger-sqlite.d.ts +0 -65
- package/dist/host-ledger-sqlite.js +0 -368
- package/dist/host-ledger.d.ts +0 -195
- package/dist/host-ledger.js +0 -1
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -38
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -23
- package/dist/participant-host.js +0 -92
- package/dist/registry.d.ts +0 -55
- package/dist/registry.js +0 -123
- package/dist/routes.d.ts +0 -39
- package/dist/routes.js +0 -99
- package/dist/square-actions.d.ts +0 -56
- package/dist/square-actions.js +0 -353
- package/dist/square-projections.d.ts +0 -31
- package/dist/square-projections.js +0 -64
- package/dist/wake-attempts.d.ts +0 -10
- package/dist/wake-attempts.js +0 -39
- package/dist/wake-eligibility.d.ts +0 -53
- package/dist/wake-eligibility.js +0 -123
- package/dist/wake-evidence.d.ts +0 -33
- package/dist/wake-evidence.js +0 -32
- package/guides/host-ledger.md +0 -58
- package/guides/indexed-host-ledger-performance.md +0 -39
package/dist/square-sqlite.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { DatabaseSync as MemoryDatabase } from 'node:sqlite';
|
|
2
|
-
import { SquareError, nameKey, isWakeRouteKind } from './model.js';
|
|
2
|
+
import { SquareError, nameKey, isWakeRouteKind, validateBody, validateActivityMetadata, activityMetadataHasContent } from './model.js';
|
|
3
3
|
import { deriveLanding, landingFacts } from './square-domain.js';
|
|
4
|
+
import { legacyRowFramingIssue } from './square-legacy-framing.js';
|
|
4
5
|
export function emptyRuntimeState(nextActIndex = 0) {
|
|
5
6
|
return { nextActIndex, observations: {}, leases: {} };
|
|
6
7
|
}
|
|
7
8
|
export const APPLICATION_ID = 0x53515245;
|
|
8
9
|
const V1_USER_VERSION = 1;
|
|
9
10
|
export const V2_USER_VERSION = 2;
|
|
10
|
-
export const
|
|
11
|
+
export const V3_USER_VERSION = 3;
|
|
12
|
+
export const CURRENT_USER_VERSION = 4;
|
|
11
13
|
export const V1_SNAPSHOT_TABLE = 'square_snapshot';
|
|
12
14
|
const V1_SNAPSHOT_DDL = `CREATE TABLE ${V1_SNAPSHOT_TABLE} (id INTEGER PRIMARY KEY CHECK(id=1), revision INTEGER NOT NULL CHECK(revision>=0), state TEXT NOT NULL)`;
|
|
13
15
|
const V1_SNAPSHOT_COLUMNS = [
|
|
@@ -52,7 +54,6 @@ export function validateLegacyFraming(database, applicationId, location = '') {
|
|
|
52
54
|
function normalizedLegacySql(sql) {
|
|
53
55
|
return sql.trim().replace(/\s+/g, ' ').replace(/\s*(>=|[(),=])\s*/g, '$1');
|
|
54
56
|
}
|
|
55
|
-
const V2_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','hold','resume','read','listen','ignore')";
|
|
56
57
|
const CURRENT_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')";
|
|
57
58
|
export const ACTIVITIES_TABLE_SQL = `CREATE TABLE activities (
|
|
58
59
|
act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
@@ -77,6 +78,7 @@ export const ACTIVITY_INDEXES_SQL = [
|
|
|
77
78
|
export const SCHEMA_SQL = `CREATE TABLE square_meta (
|
|
78
79
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
79
80
|
revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991),
|
|
81
|
+
delivery_revision INTEGER NOT NULL CHECK (delivery_revision BETWEEN 0 AND 9007199254740991),
|
|
80
82
|
next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),
|
|
81
83
|
hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),
|
|
82
84
|
throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),
|
|
@@ -95,22 +97,45 @@ CREATE TABLE receipts (
|
|
|
95
97
|
CREATE INDEX receipt_owner_time ON receipts(owner_json, at);
|
|
96
98
|
CREATE TABLE catch_leases (
|
|
97
99
|
owner_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
|
|
100
|
+
generation_json TEXT NOT NULL DEFAULT '""',
|
|
98
101
|
lease_id_json TEXT NOT NULL,
|
|
99
102
|
heartbeat_at REAL NOT NULL,
|
|
100
103
|
expires_at REAL NOT NULL CHECK (expires_at >= heartbeat_at),
|
|
101
104
|
filter_json TEXT CHECK (filter_json IS NULL OR json_valid(filter_json))
|
|
102
105
|
) WITHOUT ROWID;
|
|
103
106
|
|
|
104
|
-
CREATE TABLE
|
|
105
|
-
|
|
107
|
+
CREATE TABLE seats (
|
|
108
|
+
participant_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
|
|
109
|
+
participant_json TEXT NOT NULL,
|
|
110
|
+
channel_json TEXT NOT NULL,
|
|
111
|
+
session_json TEXT NOT NULL,
|
|
112
|
+
agent_json TEXT,
|
|
113
|
+
generation_json TEXT NOT NULL,
|
|
114
|
+
wake_address_json TEXT CHECK (wake_address_json IS NULL OR json_valid(wake_address_json))
|
|
115
|
+
) WITHOUT ROWID;
|
|
116
|
+
CREATE INDEX seat_body ON seats(channel_json, session_json, agent_json);
|
|
117
|
+
CREATE TABLE delivery_evidence (
|
|
106
118
|
participant_key_json TEXT COLLATE BINARY NOT NULL,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
participant_json TEXT NOT NULL,
|
|
120
|
+
generation_json TEXT NOT NULL,
|
|
121
|
+
act_index INTEGER NOT NULL CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
122
|
+
kind TEXT NOT NULL CHECK (kind IN ('wake','presentation')),
|
|
123
|
+
attempt_n INTEGER NOT NULL CHECK (attempt_n >= 0),
|
|
124
|
+
outcome TEXT NOT NULL CHECK (outcome IN ('dispatching','accepted','failed','unknown','released','presented','clipped')),
|
|
125
|
+
at REAL NOT NULL,
|
|
126
|
+
expires_at REAL,
|
|
127
|
+
claim_token_json TEXT,
|
|
128
|
+
payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
|
|
129
|
+
PRIMARY KEY (participant_key_json,generation_json,act_index,kind,attempt_n)
|
|
130
|
+
) WITHOUT ROWID;
|
|
131
|
+
CREATE UNIQUE INDEX delivery_claim_token ON delivery_evidence(claim_token_json) WHERE claim_token_json IS NOT NULL;
|
|
132
|
+
CREATE INDEX delivery_activity ON delivery_evidence(participant_key_json,generation_json,act_index,kind,attempt_n);
|
|
133
|
+
CREATE TABLE delivery_cancellations (
|
|
134
|
+
participant_key_json TEXT COLLATE BINARY NOT NULL,
|
|
135
|
+
generation_json TEXT NOT NULL,
|
|
136
|
+
through_index INTEGER NOT NULL CHECK (through_index BETWEEN 0 AND 9007199254740991),
|
|
137
|
+
PRIMARY KEY (participant_key_json,generation_json)
|
|
138
|
+
) WITHOUT ROWID;
|
|
114
139
|
|
|
115
140
|
CREATE TABLE participants (
|
|
116
141
|
name_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
|
|
@@ -142,8 +167,6 @@ CREATE TABLE landed_audience (
|
|
|
142
167
|
) WITHOUT ROWID;
|
|
143
168
|
CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);`;
|
|
144
169
|
const EXPECTED_SCHEMA_SQL = SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
145
|
-
export const SCHEMA_SQL_V2 = SCHEMA_SQL.replace(CURRENT_ACTIVITY_KIND_CHECK, V2_ACTIVITY_KIND_CHECK);
|
|
146
|
-
const EXPECTED_SCHEMA_SQL_V2 = SCHEMA_SQL_V2.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
147
170
|
const encode = JSON.stringify;
|
|
148
171
|
const key = (name) => encode(nameKey(name));
|
|
149
172
|
export function invalidArtifact(detail) {
|
|
@@ -226,8 +249,19 @@ export function validateStoredActivity(value) {
|
|
|
226
249
|
string(a.actor, true);
|
|
227
250
|
switch (a.kind) {
|
|
228
251
|
case 'say':
|
|
229
|
-
shape(a, [...common, 'body'
|
|
230
|
-
|
|
252
|
+
shape(a, [...common], ['body', 'mentions', 'reach', 'reply', 'metadata']);
|
|
253
|
+
if (Object.hasOwn(a, 'body'))
|
|
254
|
+
string(a.body);
|
|
255
|
+
if (Object.hasOwn(a, 'metadata')) {
|
|
256
|
+
requireFact(a.metadata !== undefined, 'say metadata is malformed.');
|
|
257
|
+
try {
|
|
258
|
+
validateActivityMetadata(a.metadata);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
throw invalidArtifact('say metadata is malformed.');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
requireFact((Object.hasOwn(a, 'body') && typeof a.body === 'string' && a.body.trim() !== '') || activityMetadataHasContent(a.metadata), 'say must contain a non-empty body or non-empty metadata.');
|
|
231
265
|
if (Object.hasOwn(a, 'mentions'))
|
|
232
266
|
strings(a.mentions);
|
|
233
267
|
if (Object.hasOwn(a, 'reach'))
|
|
@@ -258,7 +292,7 @@ export function validateStoredActivity(value) {
|
|
|
258
292
|
function activity(row) {
|
|
259
293
|
const h = head(row), payload = object(json(row.payload_json));
|
|
260
294
|
// Payload cannot shadow indexed structural columns.
|
|
261
|
-
const allowed = h.kind === 'say' ? ['body', 'mentions'] : h.kind === 'done' || h.kind === 'hold' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
|
|
295
|
+
const allowed = h.kind === 'say' ? ['body', 'mentions', 'metadata'] : h.kind === 'done' || h.kind === 'hold' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
|
|
262
296
|
requireFact(Object.keys(payload).every(k => allowed.includes(k)));
|
|
263
297
|
return immutable(validateStoredActivity({ ...h, ...payload }));
|
|
264
298
|
}
|
|
@@ -296,34 +330,87 @@ export function validateWatchLease(value) {
|
|
|
296
330
|
// Sorted object keys express small-value structural equality, never whole-state comparison.
|
|
297
331
|
function stableJson(value) {
|
|
298
332
|
if (value === null || typeof value !== 'object') {
|
|
299
|
-
requireFact(value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value), '
|
|
333
|
+
requireFact(value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value), 'value contains a non-finite or unsupported JSON value.');
|
|
300
334
|
return encode(value);
|
|
301
335
|
}
|
|
302
336
|
if (Array.isArray(value))
|
|
303
337
|
return '[' + value.map(stableJson).join(',') + ']';
|
|
304
338
|
return '{' + Object.keys(value).sort().map(k => encode(k) + ':' + stableJson(value[k])).join(',') + '}';
|
|
305
339
|
}
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
requireFact(
|
|
311
|
-
finite(r.updatedAt);
|
|
312
|
-
requireFact(Object.values(object(r.address)).every(v => typeof v === 'string'));
|
|
313
|
-
const extra = object(r.extra);
|
|
314
|
-
requireFact(Object.keys(extra).every(k => !['location', 'participant', 'sessionId', 'channel', 'kind', 'address', 'updatedAt', 'epoch'].includes(k)));
|
|
315
|
-
stableJson(extra);
|
|
316
|
-
if (Object.hasOwn(r, 'epoch'))
|
|
317
|
-
stableJson(r.epoch);
|
|
340
|
+
function activityId(value) {
|
|
341
|
+
const parsed = typeof value === 'string' ? /^act\/(0|[1-9]\d*)$/.exec(value) : null;
|
|
342
|
+
requireFact(parsed !== null, 'activity generation is malformed.');
|
|
343
|
+
const index = Number(parsed[1]);
|
|
344
|
+
requireFact(Number.isSafeInteger(index));
|
|
318
345
|
return value;
|
|
319
346
|
}
|
|
320
|
-
function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
347
|
+
function activityIndex(value) {
|
|
348
|
+
const parsed = activityId(value);
|
|
349
|
+
return integer(Number(parsed.slice(4)));
|
|
350
|
+
}
|
|
351
|
+
function wakeAddress(value) {
|
|
352
|
+
const address = shape(value, ['kind', 'address']);
|
|
353
|
+
requireFact(isWakeRouteKind(address.kind));
|
|
354
|
+
const entries = object(address.address);
|
|
355
|
+
requireFact(Object.values(entries).every((item) => typeof item === 'string'));
|
|
356
|
+
stableJson(entries);
|
|
357
|
+
return immutable({ kind: address.kind, address: entries });
|
|
358
|
+
}
|
|
359
|
+
function bodyFrom(row) {
|
|
360
|
+
const body = { channel: decoded(row.channel_json, true), session: decoded(row.session_json, true), ...(row.agent_json === null ? {} : { agent: decoded(row.agent_json, true) }) };
|
|
361
|
+
validateBody(body);
|
|
362
|
+
return immutable(body);
|
|
363
|
+
}
|
|
364
|
+
function seat(row) {
|
|
365
|
+
const participant = decoded(row.participant_json, true);
|
|
366
|
+
requireFact(row.participant_key_json === key(participant));
|
|
367
|
+
const generation = activityId(decoded(row.generation_json, true));
|
|
368
|
+
const parsedWake = row.wake_address_json === null ? undefined : wakeAddress(json(row.wake_address_json));
|
|
369
|
+
return immutable({ participant, body: bodyFrom(row), generation, ...(parsedWake === undefined ? {} : { wakeAddress: parsedWake }) });
|
|
370
|
+
}
|
|
371
|
+
function nativeCorrelation(value) {
|
|
372
|
+
const item = shape(value, ['harness'], ['token', 'messageId', 'payloadHash']);
|
|
373
|
+
requireFact(['claude', 'opencode', 'pi'].includes(string(item.harness)));
|
|
374
|
+
for (const key of ['token', 'messageId', 'payloadHash'])
|
|
375
|
+
if (Object.hasOwn(item, key))
|
|
376
|
+
string(item[key], true);
|
|
377
|
+
return immutable({ harness: item.harness,
|
|
378
|
+
...(item.token === undefined ? {} : { token: item.token }),
|
|
379
|
+
...(item.messageId === undefined ? {} : { messageId: item.messageId }),
|
|
380
|
+
...(item.payloadHash === undefined ? {} : { payloadHash: item.payloadHash }) });
|
|
381
|
+
}
|
|
382
|
+
function deliveryEvidence(row) {
|
|
383
|
+
const payload = object(json(row.payload_json));
|
|
384
|
+
const participant = decoded(row.participant_json, true);
|
|
385
|
+
requireFact(row.participant_key_json === key(participant));
|
|
386
|
+
const generation = activityId(decoded(row.generation_json, true));
|
|
387
|
+
const activity = activityId(`act/${integer(row.act_index)}`);
|
|
388
|
+
requireFact(['wake', 'presentation'].includes(string(row.kind)) && ['dispatching', 'accepted', 'failed', 'unknown', 'released', 'presented', 'clipped'].includes(string(row.outcome)));
|
|
389
|
+
requireFact(Number.isSafeInteger(row.attempt_n) && row.attempt_n >= 0);
|
|
390
|
+
const nativeObservations = payload.nativeObservations === undefined ? undefined : (() => {
|
|
391
|
+
requireFact(Array.isArray(payload.nativeObservations));
|
|
392
|
+
return payload.nativeObservations.map((value) => {
|
|
393
|
+
const item = shape(value, ['eventId', 'messageId', 'state', 'observedAt'], ['reason']);
|
|
394
|
+
requireFact(['held', 'released', 'dropped'].includes(string(item.state)));
|
|
395
|
+
return {
|
|
396
|
+
eventId: string(item.eventId, true), messageId: string(item.messageId, true),
|
|
397
|
+
state: item.state,
|
|
398
|
+
...(Object.hasOwn(item, 'reason') ? { reason: string(item.reason) } : {}), observedAt: finite(item.observedAt),
|
|
399
|
+
};
|
|
400
|
+
});
|
|
401
|
+
})();
|
|
402
|
+
return immutable({
|
|
403
|
+
participant, generation, activity, kind: row.kind,
|
|
404
|
+
outcome: row.outcome, attemptN: integer(row.attempt_n), at: finite(row.at),
|
|
405
|
+
...(row.expires_at === null ? {} : { expiresAt: finite(row.expires_at) }),
|
|
406
|
+
...(row.claim_token_json === null ? {} : { claimToken: decoded(row.claim_token_json, true) }),
|
|
407
|
+
...(payload.route === undefined ? {} : { route: wakeAddress(payload.route) }),
|
|
408
|
+
...(payload.signature === undefined ? {} : { signature: string(payload.signature) }),
|
|
409
|
+
...(payload.message === undefined ? {} : { message: string(payload.message) }),
|
|
410
|
+
...(payload.diagnostic === undefined ? {} : { diagnostic: payload.diagnostic }),
|
|
411
|
+
...(payload.correlation === undefined ? {} : { correlation: nativeCorrelation(payload.correlation) }),
|
|
412
|
+
...(nativeObservations === undefined ? {} : { nativeObservations }),
|
|
413
|
+
});
|
|
327
414
|
}
|
|
328
415
|
function normalizedSql(sql) { return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1'); }
|
|
329
416
|
function validateOwnedSchema(db, expected, location) {
|
|
@@ -332,10 +419,16 @@ function validateOwnedSchema(db, expected, location) {
|
|
|
332
419
|
requireFact(db.prepare('PRAGMA journal_mode').get().journal_mode === (location === ':memory:' ? 'memory' : 'delete'), 'unsupported journal mode.');
|
|
333
420
|
}
|
|
334
421
|
export function validateRowFraming(db, version, location = '') {
|
|
422
|
+
if (version === V2_USER_VERSION || version === V3_USER_VERSION) {
|
|
423
|
+
const issue = legacyRowFramingIssue(db, APPLICATION_ID, version, location, SCHEMA_SQL);
|
|
424
|
+
if (issue !== undefined)
|
|
425
|
+
throw invalidArtifact(issue);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
335
428
|
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
336
429
|
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
337
430
|
requireFact(identity.user_version === version, 'unsupported SQLite format version.');
|
|
338
|
-
validateOwnedSchema(db,
|
|
431
|
+
validateOwnedSchema(db, EXPECTED_SCHEMA_SQL, location);
|
|
339
432
|
}
|
|
340
433
|
export function validateSqliteHeader(db, location = '') {
|
|
341
434
|
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
@@ -344,13 +437,15 @@ export function validateSqliteHeader(db, location = '') {
|
|
|
344
437
|
validateLegacyFraming(db, APPLICATION_ID, location);
|
|
345
438
|
throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
|
|
346
439
|
}
|
|
347
|
-
if (identity.user_version === V2_USER_VERSION) {
|
|
348
|
-
validateRowFraming(db,
|
|
440
|
+
if (identity.user_version === V2_USER_VERSION || identity.user_version === V3_USER_VERSION) {
|
|
441
|
+
validateRowFraming(db, identity.user_version, location);
|
|
349
442
|
throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
|
|
350
443
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
444
|
+
if (identity.user_version === CURRENT_USER_VERSION) {
|
|
445
|
+
validateRowFraming(db, CURRENT_USER_VERSION, location);
|
|
446
|
+
return createSqliteDriver(db).header();
|
|
447
|
+
}
|
|
448
|
+
throw invalidArtifact('unsupported SQLite format version.');
|
|
354
449
|
}
|
|
355
450
|
function activitySelection(filter, alias = 'a') {
|
|
356
451
|
const terms = [], args = [];
|
|
@@ -390,7 +485,7 @@ function pageLimit(page) {
|
|
|
390
485
|
return Math.min(integer(page.limit, 1) + 1, Number.MAX_SAFE_INTEGER);
|
|
391
486
|
}
|
|
392
487
|
export function createSqliteDriver(db) {
|
|
393
|
-
let changed = false;
|
|
488
|
+
let changed = false, deliveryChanged = false;
|
|
394
489
|
const all = (sql, args = []) => db.prepare(sql).all(...args);
|
|
395
490
|
const one = (sql, args = []) => db.prepare(sql).get(...args);
|
|
396
491
|
const run = (sql, args = []) => Number(db.prepare(sql).run(...args).changes);
|
|
@@ -420,14 +515,42 @@ export function createSqliteDriver(db) {
|
|
|
420
515
|
const directedFrom = 'landed_audience d JOIN activities a ON a.act_index=d.act_index';
|
|
421
516
|
const pendingFrom = `landed_audience d JOIN activities a ON a.act_index=d.act_index JOIN participants p ON p.name_key_json=d.recipient_key_json`;
|
|
422
517
|
const pendingWhere = `p.joined=1 AND d.act_index>COALESCE(p.last_join_index,-1) AND NOT EXISTS (SELECT 1 FROM receipts r WHERE r.owner_json=p.name_json AND r.act_index=d.act_index)`;
|
|
518
|
+
const latestSeats = `seats s JOIN participants p ON p.name_key_json=s.participant_key_json
|
|
519
|
+
JOIN activities a ON a.act_index=p.last_join_index AND a.kind='join' AND a.actor_key_json=p.name_key_json
|
|
520
|
+
AND s.generation_json='"act/'||a.act_index||'"'`;
|
|
521
|
+
function bodySeats(body, lifecycle) {
|
|
522
|
+
validateBody(body);
|
|
523
|
+
return immutable(all(`SELECT s.* FROM ${latestSeats} WHERE p.${lifecycle}=1 AND s.channel_json=? AND s.session_json=? AND s.agent_json IS ? ORDER BY s.participant_key_json`, [encode(body.channel), encode(body.session), body.agent === undefined ? null : encode(body.agent)]).map(seat));
|
|
524
|
+
}
|
|
423
525
|
const read = {
|
|
526
|
+
seat(name) {
|
|
527
|
+
const r = one(`SELECT s.* FROM ${latestSeats} WHERE p.joined=1 AND s.participant_key_json=?`, [key(name)]);
|
|
528
|
+
return r ? seat(r) : undefined;
|
|
529
|
+
},
|
|
530
|
+
seatsForBody: body => bodySeats(body, 'joined'),
|
|
531
|
+
completedSeatsForBody: body => bodySeats(body, 'done'),
|
|
532
|
+
deliveryEvidence(participant, generation, activityIndex) {
|
|
533
|
+
const args = [key(participant), encode(generation)];
|
|
534
|
+
const filter = activityIndex === undefined ? '' : ' AND act_index=?';
|
|
535
|
+
if (activityIndex !== undefined)
|
|
536
|
+
args.push(integer(Number(activityIndex.slice(4))));
|
|
537
|
+
return immutable(all(`SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=?${filter} ORDER BY act_index,kind,attempt_n`, args).map(deliveryEvidence));
|
|
538
|
+
},
|
|
539
|
+
deliveryByToken(claimToken) {
|
|
540
|
+
const row = one('SELECT * FROM delivery_evidence WHERE claim_token_json=?', [encode(claimToken)]);
|
|
541
|
+
return row === undefined ? undefined : deliveryEvidence(row);
|
|
542
|
+
},
|
|
543
|
+
cancelledThrough(participant, generation) {
|
|
544
|
+
const r = one('SELECT through_index FROM delivery_cancellations WHERE participant_key_json=? AND generation_json=?', [key(participant), encode(generation)]);
|
|
545
|
+
return r === undefined ? undefined : integer(r.through_index);
|
|
546
|
+
},
|
|
424
547
|
header() {
|
|
425
548
|
const rows = all('SELECT m.*, (SELECT COALESCE(MAX(act_index),-1) FROM activities) AS last_act_index FROM square_meta m');
|
|
426
549
|
requireFact(rows.length === 1 && rows[0].id === 1, 'header row is malformed.');
|
|
427
550
|
const r = rows[0], nextActIndex = integer(r.next_act_index), lastActIndex = integer(r.last_act_index, -1);
|
|
428
551
|
requireFact(nextActIndex > lastActIndex, 'nextActIndex is behind the activity history.');
|
|
429
552
|
return immutable({
|
|
430
|
-
revision: integer(r.revision), nextActIndex, lastActIndex, hardCap: r.hard_cap === null ? null : integer(r.hard_cap, 1), ...(r.throttle_per_minute === null ? {} : { throttlePerMinute: integer(r.throttle_per_minute, 1) }), preamble: strings(json(r.preamble_json)), warmup: strings(json(r.warmup_json))
|
|
553
|
+
revision: integer(r.revision), deliveryRevision: integer(r.delivery_revision), nextActIndex, lastActIndex, hardCap: r.hard_cap === null ? null : integer(r.hard_cap, 1), ...(r.throttle_per_minute === null ? {} : { throttlePerMinute: integer(r.throttle_per_minute, 1) }), preamble: strings(json(r.preamble_json)), warmup: strings(json(r.warmup_json))
|
|
431
554
|
});
|
|
432
555
|
},
|
|
433
556
|
participant(name) {
|
|
@@ -547,14 +670,63 @@ export function createSqliteDriver(db) {
|
|
|
547
670
|
},
|
|
548
671
|
receiptAssignments: () => immutable(all('SELECT owner_json,act_index FROM receipts ORDER BY owner_json,act_index').map(r => ({ owner: decoded(r.owner_json, true), index: integer(r.act_index) }))),
|
|
549
672
|
lease(name) {
|
|
550
|
-
const
|
|
673
|
+
const canonical = owner(name), current = read.seat(canonical);
|
|
674
|
+
const r = current === undefined
|
|
675
|
+
? one('SELECT * FROM catch_leases WHERE owner_json=?', [encode(canonical)])
|
|
676
|
+
: one('SELECT * FROM catch_leases WHERE owner_json=? AND (generation_json=? OR generation_json=?)', [encode(canonical), encode(current.generation), encode('')]);
|
|
551
677
|
return r ? immutable(validateWatchLease({
|
|
552
678
|
leaseId: decoded(r.lease_id_json, true), heartbeatAt: finite(r.heartbeat_at), expiresAt: finite(r.expires_at), ...(r.filter_json === null ? {} : { filter: json(r.filter_json) })
|
|
553
679
|
})) : undefined;
|
|
554
680
|
},
|
|
555
|
-
routes: () => immutable(all('SELECT * FROM routes ORDER BY position').map(route)),
|
|
556
681
|
};
|
|
557
682
|
const writes = {
|
|
683
|
+
storeSeat(value) {
|
|
684
|
+
validateBody(value.body);
|
|
685
|
+
activityId(value.generation);
|
|
686
|
+
string(value.participant, true);
|
|
687
|
+
const existing = read.seat(value.participant);
|
|
688
|
+
if (existing !== undefined && stableJson(existing) === stableJson(value))
|
|
689
|
+
return false;
|
|
690
|
+
const wake = value.wakeAddress === undefined ? null : encode(value.wakeAddress);
|
|
691
|
+
run('INSERT INTO seats(participant_key_json,participant_json,channel_json,session_json,agent_json,generation_json,wake_address_json) VALUES(?,?,?,?,?,?,?) ON CONFLICT(participant_key_json) DO UPDATE SET participant_json=excluded.participant_json,channel_json=excluded.channel_json,session_json=excluded.session_json,agent_json=excluded.agent_json,generation_json=excluded.generation_json,wake_address_json=excluded.wake_address_json', [key(value.participant), encode(value.participant), encode(value.body.channel), encode(value.body.session), value.body.agent === undefined ? null : encode(value.body.agent), encode(value.generation), wake]);
|
|
692
|
+
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
693
|
+
deliveryChanged = true;
|
|
694
|
+
return true;
|
|
695
|
+
},
|
|
696
|
+
updateWakeAddress(value, wakeAddress) {
|
|
697
|
+
const current = one('SELECT wake_address_json FROM seats WHERE participant_key_json=? AND generation_json=?', [key(value.participant), encode(value.generation)]);
|
|
698
|
+
if (wakeAddress !== undefined) {
|
|
699
|
+
requireFact(isWakeRouteKind(wakeAddress.kind));
|
|
700
|
+
requireFact(Object.values(wakeAddress.address).every((item) => typeof item === 'string'));
|
|
701
|
+
}
|
|
702
|
+
if (stableJson(current.wake_address_json === null ? null : json(current.wake_address_json)) === stableJson(wakeAddress ?? null))
|
|
703
|
+
return false;
|
|
704
|
+
const count = run('UPDATE seats SET wake_address_json=? WHERE participant_key_json=? AND generation_json=?', [wakeAddress === undefined ? null : encode(wakeAddress), key(value.participant), encode(value.generation)]);
|
|
705
|
+
if (count)
|
|
706
|
+
deliveryChanged = true;
|
|
707
|
+
return count > 0;
|
|
708
|
+
},
|
|
709
|
+
removeSeat(value) {
|
|
710
|
+
// Done hides current authority; retain only its latest coordinate for exact-body resume.
|
|
711
|
+
const row = one(`SELECT s.* FROM ${latestSeats} WHERE p.done=1 AND s.participant_key_json=?`, [key(value.participant)]);
|
|
712
|
+
if (row === undefined || decoded(row.generation_json) !== value.generation)
|
|
713
|
+
return false;
|
|
714
|
+
run('UPDATE seats SET wake_address_json=NULL WHERE participant_key_json=?', [key(value.participant)]);
|
|
715
|
+
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
716
|
+
changed = true;
|
|
717
|
+
deliveryChanged = true;
|
|
718
|
+
return true;
|
|
719
|
+
},
|
|
720
|
+
cancelThrough(value, through) {
|
|
721
|
+
activityId(value.generation);
|
|
722
|
+
integer(through);
|
|
723
|
+
const prior = read.cancelledThrough(value.participant, value.generation) ?? -1;
|
|
724
|
+
if (through <= prior)
|
|
725
|
+
return false;
|
|
726
|
+
run('INSERT INTO delivery_cancellations(participant_key_json,generation_json,through_index) VALUES(?,?,?) ON CONFLICT(participant_key_json,generation_json) DO UPDATE SET through_index=excluded.through_index', [key(value.participant), encode(value.generation), through]);
|
|
727
|
+
deliveryChanged = true;
|
|
728
|
+
return true;
|
|
729
|
+
},
|
|
558
730
|
applyLanding(effect) {
|
|
559
731
|
const a = validateStoredActivity(effect.activity);
|
|
560
732
|
requireFact(a.index >= read.header().nextActIndex && a.index < Number.MAX_SAFE_INTEGER, 'activity index cannot advance.');
|
|
@@ -605,47 +777,130 @@ export function createSqliteDriver(db) {
|
|
|
605
777
|
changed = true;
|
|
606
778
|
return count;
|
|
607
779
|
},
|
|
608
|
-
putLease(name, value) {
|
|
609
|
-
const l = validateWatchLease(value),
|
|
610
|
-
|
|
780
|
+
putLease(name, value, generation) {
|
|
781
|
+
const l = validateWatchLease(value), canonical = owner(name);
|
|
782
|
+
const old = read.lease(canonical), row = one('SELECT generation_json FROM catch_leases WHERE owner_json=?', [encode(canonical)]);
|
|
783
|
+
if (generation !== undefined)
|
|
784
|
+
activityId(generation);
|
|
785
|
+
if (row !== undefined && generation !== undefined && decoded(row.generation_json) !== '' && decoded(row.generation_json) !== generation)
|
|
786
|
+
return false;
|
|
787
|
+
if (old && stableJson(old) === stableJson(l) && (generation === undefined || row?.generation_json === encode(generation)))
|
|
611
788
|
return false;
|
|
612
|
-
const canonical = owner(name);
|
|
613
789
|
string(canonical, true);
|
|
614
|
-
run('INSERT INTO catch_leases(owner_json,lease_id_json,heartbeat_at,expires_at,filter_json) VALUES(
|
|
790
|
+
run('INSERT INTO catch_leases(owner_json,generation_json,lease_id_json,heartbeat_at,expires_at,filter_json) VALUES(?,?,?,?,?,?) ON CONFLICT(owner_json) DO UPDATE SET generation_json=excluded.generation_json,lease_id_json=excluded.lease_id_json,heartbeat_at=excluded.heartbeat_at,expires_at=excluded.expires_at,filter_json=excluded.filter_json', [encode(canonical), encode(generation ?? ''), encode(l.leaseId), l.heartbeatAt, l.expiresAt, l.filter === undefined ? null : encode(l.filter)]);
|
|
615
791
|
changed = true;
|
|
616
792
|
return true;
|
|
617
793
|
},
|
|
618
|
-
removeLease(name, expected) {
|
|
619
|
-
const
|
|
794
|
+
removeLease(name, expected, generation) {
|
|
795
|
+
const canonical = owner(name), row = one('SELECT generation_json FROM catch_leases WHERE owner_json=? AND lease_id_json=?', [encode(canonical), encode(expected)]);
|
|
796
|
+
if (row === undefined || generation !== undefined && decoded(row.generation_json) !== generation)
|
|
797
|
+
return false;
|
|
798
|
+
const count = run('DELETE FROM catch_leases WHERE owner_json=? AND lease_id_json=? AND generation_json=?', [encode(canonical), encode(expected), row.generation_json]);
|
|
620
799
|
if (count)
|
|
621
800
|
changed = true;
|
|
622
801
|
return count > 0;
|
|
623
802
|
},
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
803
|
+
};
|
|
804
|
+
const delivery = {
|
|
805
|
+
claimDelivery(input) {
|
|
806
|
+
requireFact(input.kind === 'wake' || input.kind === 'presentation');
|
|
807
|
+
const index = activityIndex(input.activity);
|
|
808
|
+
finite(input.at);
|
|
809
|
+
const leaseMs = integer(input.leaseMs, 1);
|
|
810
|
+
string(input.claimToken, true);
|
|
811
|
+
if (input.route !== undefined)
|
|
812
|
+
wakeAddress(input.route);
|
|
813
|
+
if (read.activity(index)?.kind !== 'say' || read.directedAt(input.seat.participant, index) === undefined)
|
|
814
|
+
return { status: 'stale' };
|
|
815
|
+
const cancelledThrough = read.cancelledThrough(input.seat.participant, input.seat.generation);
|
|
816
|
+
if (cancelledThrough !== undefined && index <= cancelledThrough)
|
|
817
|
+
return { status: 'stale' };
|
|
818
|
+
const rows = all('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? ORDER BY attempt_n DESC', [key(input.seat.participant), encode(input.seat.generation), index, input.kind]);
|
|
819
|
+
const decoded = rows.map(deliveryEvidence);
|
|
820
|
+
const terminal = decoded.find((row) => input.kind === 'wake'
|
|
821
|
+
? row.outcome === 'accepted' || row.outcome === 'unknown'
|
|
822
|
+
: row.outcome === 'presented' || row.outcome === 'unknown');
|
|
823
|
+
if (terminal !== undefined)
|
|
824
|
+
return { status: 'terminal', evidence: terminal };
|
|
825
|
+
const dispatching = decoded.find((row) => row.outcome === 'dispatching');
|
|
826
|
+
if (dispatching !== undefined && (dispatching.expiresAt === undefined || dispatching.expiresAt > input.at))
|
|
827
|
+
return { status: 'busy', evidence: dispatching };
|
|
828
|
+
if (dispatching !== undefined) {
|
|
829
|
+
const updated = { ...dispatching, outcome: 'unknown', at: input.at, expiresAt: undefined, diagnostic: 'interrupted dispatch' };
|
|
830
|
+
const source = rows.find((row) => row.attempt_n === dispatching.attemptN);
|
|
831
|
+
run('UPDATE delivery_evidence SET outcome=?,at=?,expires_at=NULL,payload_json=? WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? AND attempt_n=?', ['unknown', input.at, encode({ ...object(json(source.payload_json)), diagnostic: 'interrupted dispatch' }), key(input.seat.participant), encode(input.seat.generation), index, input.kind, dispatching.attemptN]);
|
|
832
|
+
deliveryChanged = true;
|
|
833
|
+
return { status: 'terminal', evidence: updated };
|
|
834
|
+
}
|
|
835
|
+
const attemptN = (decoded[0]?.attemptN ?? -1) + 1;
|
|
836
|
+
const payload = input.route === undefined ? {} : { route: input.route };
|
|
837
|
+
const expiry = finite(input.at + leaseMs);
|
|
838
|
+
run('INSERT INTO delivery_evidence(participant_key_json,participant_json,generation_json,act_index,kind,attempt_n,outcome,at,expires_at,claim_token_json,payload_json) VALUES(?,?,?,?,?,?,?,?,?,?,?)', [key(input.seat.participant), encode(input.seat.participant), encode(input.seat.generation), index, input.kind, attemptN, 'dispatching', input.at, expiry, encode(input.claimToken), encode(payload)]);
|
|
839
|
+
deliveryChanged = true;
|
|
840
|
+
return { status: 'acquired', claimToken: input.claimToken, attemptN };
|
|
841
|
+
},
|
|
842
|
+
prepareDelivery(input) {
|
|
843
|
+
const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND claim_token_json=?', [key(input.seat.participant), encode(input.seat.generation), encode(input.claimToken)]);
|
|
844
|
+
if (row === undefined || row.outcome !== 'dispatching')
|
|
627
845
|
return false;
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
|
|
846
|
+
const correlation = nativeCorrelation(input.correlation);
|
|
847
|
+
const payload = object(json(row.payload_json));
|
|
848
|
+
if (stableJson(payload.correlation ?? null) === stableJson(correlation))
|
|
849
|
+
return true;
|
|
850
|
+
const count = run('UPDATE delivery_evidence SET payload_json=? WHERE participant_key_json=? AND generation_json=? AND claim_token_json=?', [encode({ ...payload, correlation }), key(input.seat.participant), encode(input.seat.generation), encode(input.claimToken)]);
|
|
851
|
+
if (count)
|
|
852
|
+
deliveryChanged = true;
|
|
853
|
+
return count > 0;
|
|
854
|
+
},
|
|
855
|
+
completeDelivery(input) {
|
|
856
|
+
const index = activityIndex(input.activity);
|
|
857
|
+
finite(input.at);
|
|
858
|
+
requireFact(['accepted', 'failed', 'unknown', 'released', 'presented', 'clipped'].includes(input.outcome));
|
|
859
|
+
string(input.claimToken, true);
|
|
860
|
+
const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND claim_token_json=?', [key(input.seat.participant), encode(input.seat.generation), index, encode(input.claimToken)]);
|
|
861
|
+
if (row === undefined)
|
|
862
|
+
return false;
|
|
863
|
+
if (row.outcome === 'accepted' && input.outcome === 'accepted')
|
|
864
|
+
return true;
|
|
865
|
+
if (row.outcome !== 'dispatching' && !(row.outcome === 'unknown' && input.outcome === 'accepted'))
|
|
866
|
+
return false;
|
|
867
|
+
const previous = object(json(row.payload_json));
|
|
868
|
+
const payload = { ...previous, ...(input.signature === undefined ? {} : { signature: input.signature }), ...(input.message === undefined ? {} : { message: input.message }), ...(input.diagnostic === undefined ? {} : { diagnostic: input.diagnostic }), ...(input.correlation === undefined ? {} : { correlation: input.correlation }) };
|
|
869
|
+
const count = run('UPDATE delivery_evidence SET outcome=?,at=?,expires_at=NULL,payload_json=? WHERE participant_key_json=? AND generation_json=? AND act_index=? AND claim_token_json=?', [input.outcome, input.at, encode(payload), key(input.seat.participant), encode(input.seat.generation), index, encode(input.claimToken)]);
|
|
870
|
+
if (count)
|
|
871
|
+
deliveryChanged = true;
|
|
872
|
+
return count > 0;
|
|
873
|
+
},
|
|
874
|
+
observeNativeDelivery(input) {
|
|
875
|
+
const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND claim_token_json=?', [key(input.seat.participant), encode(input.seat.generation), encode(input.claimToken)]);
|
|
876
|
+
if (row === undefined || !['dispatching', 'accepted', 'unknown'].includes(string(row.outcome)))
|
|
877
|
+
return false;
|
|
878
|
+
const observation = input.observation;
|
|
879
|
+
requireFact(typeof observation.eventId === 'string' && observation.eventId.length > 0);
|
|
880
|
+
requireFact(typeof observation.messageId === 'string' && observation.messageId.length > 0);
|
|
881
|
+
requireFact(observation.state === 'held' || observation.state === 'released' || observation.state === 'dropped');
|
|
882
|
+
finite(observation.observedAt);
|
|
883
|
+
if (observation.reason !== undefined)
|
|
884
|
+
string(observation.reason);
|
|
885
|
+
const payload = object(json(row.payload_json));
|
|
886
|
+
const observations = payload.nativeObservations === undefined ? [] : payload.nativeObservations;
|
|
887
|
+
requireFact(Array.isArray(observations));
|
|
888
|
+
if (observations.some((item) => object(item).eventId === observation.eventId))
|
|
889
|
+
return true;
|
|
890
|
+
const next = [...observations, observation];
|
|
891
|
+
const count = run('UPDATE delivery_evidence SET payload_json=? WHERE participant_key_json=? AND generation_json=? AND claim_token_json=?', [encode({ ...payload, nativeObservations: next }), key(input.seat.participant), encode(input.seat.generation), encode(input.claimToken)]);
|
|
892
|
+
if (count)
|
|
893
|
+
deliveryChanged = true;
|
|
894
|
+
return count > 0;
|
|
635
895
|
},
|
|
636
896
|
};
|
|
637
|
-
return Object.
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
const { location, participant, sessionId, channel, kind, address, updatedAt, epoch, ...extra } = value;
|
|
641
|
-
return validateRoute({
|
|
642
|
-
location, participant, sessionId, channel, kind, address, updatedAt, ...(Object.hasOwn(value, 'epoch') ? { epoch } : {}), extra
|
|
897
|
+
return Object.defineProperties(Object.assign(read, writes, delivery), {
|
|
898
|
+
changed: { get: () => changed },
|
|
899
|
+
deliveryChanged: { get: () => deliveryChanged },
|
|
643
900
|
});
|
|
644
901
|
}
|
|
645
902
|
/** Explicit import only. Caller owns transaction, legacy validation and format publication. */
|
|
646
903
|
export function initializeSqlite(db, state, revision = 0) {
|
|
647
|
-
// Reject unsupported routes BEFORE any schema changes, also for direct fixture callers.
|
|
648
|
-
const routes = (state.routes ?? []).map(r => legacyRouteFact(r));
|
|
649
904
|
integer(revision);
|
|
650
905
|
integer(state.runtime.nextActIndex);
|
|
651
906
|
if (state.hardCap !== null)
|
|
@@ -653,7 +908,7 @@ export function initializeSqlite(db, state, revision = 0) {
|
|
|
653
908
|
if (state.throttlePerMinute !== undefined)
|
|
654
909
|
integer(state.throttlePerMinute, 1);
|
|
655
910
|
db.exec(SCHEMA_SQL);
|
|
656
|
-
db.prepare('INSERT INTO square_meta(id,revision,next_act_index,hard_cap,throttle_per_minute,preamble_json,warmup_json) VALUES(1,?,0,?,?,?,?)').run(revision, state.hardCap, state.throttlePerMinute ?? null, encode(strings(state.preamble)), encode(strings(state.warmup)));
|
|
911
|
+
db.prepare('INSERT INTO square_meta(id,revision,delivery_revision,next_act_index,hard_cap,throttle_per_minute,preamble_json,warmup_json) VALUES(1,?,0,0,?,?,?,?)').run(revision, state.hardCap, state.throttlePerMinute ?? null, encode(strings(state.preamble)), encode(strings(state.warmup)));
|
|
657
912
|
const driver = createSqliteDriver(db);
|
|
658
913
|
for (const activity of state.acts)
|
|
659
914
|
driver.applyLanding(deriveLanding(validateStoredActivity(activity), landingFacts(driver, activity)));
|
|
@@ -676,9 +931,7 @@ export function initializeSqlite(db, state, revision = 0) {
|
|
|
676
931
|
const l = validateWatchLease(value);
|
|
677
932
|
db.prepare('INSERT INTO catch_leases(owner_json,lease_id_json,heartbeat_at,expires_at,filter_json) VALUES(?,?,?,?,?)').run(encode(owner), encode(l.leaseId), l.heartbeatAt, l.expiresAt, l.filter === undefined ? null : encode(l.filter));
|
|
678
933
|
}
|
|
679
|
-
driver.replaceRoutes(routes);
|
|
680
934
|
}
|
|
681
|
-
/** Exceptional export projection for fixtures, never a runtime fallback. */
|
|
682
935
|
export function exportSqliteState(db) {
|
|
683
936
|
const d = createSqliteDriver(db), h = d.header();
|
|
684
937
|
const observations = {}, leases = {};
|
|
@@ -698,10 +951,7 @@ export function exportSqliteState(db) {
|
|
|
698
951
|
}), enumerable: true });
|
|
699
952
|
}
|
|
700
953
|
return {
|
|
701
|
-
hardCap: h.hardCap, ...(h.throttlePerMinute === undefined ? {} : { throttlePerMinute: h.throttlePerMinute }), preamble: [...h.preamble], warmup: [...h.warmup], acts: db.prepare('SELECT * FROM activities ORDER BY act_index').all().map(activity),
|
|
702
|
-
const { extra, ...fields } = r;
|
|
703
|
-
return { ...fields, ...extra };
|
|
704
|
-
}), runtime: {
|
|
954
|
+
hardCap: h.hardCap, ...(h.throttlePerMinute === undefined ? {} : { throttlePerMinute: h.throttlePerMinute }), preamble: [...h.preamble], warmup: [...h.warmup], acts: db.prepare('SELECT * FROM activities ORDER BY act_index').all().map(activity), runtime: {
|
|
705
955
|
nextActIndex: h.nextActIndex, observations, leases
|
|
706
956
|
}
|
|
707
957
|
};
|
package/dist/square-stdio.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ParsedSquareLocation } from './square-location.js';
|
|
|
4
4
|
import type { OperationControl, OperationFailureDetail, SquareAtInput, SquareBuildInput } from './square-facade.js';
|
|
5
5
|
export declare const STDIO_PROTOCOL_VERSION = 1;
|
|
6
6
|
export declare const STDIO_MAX_FRAME_BYTES: number;
|
|
7
|
-
export declare const identityVariables:
|
|
7
|
+
export declare const identityVariables: (import("./participant-identity.js").HarnessSessionVariable | "CLAUDE_CODE_CHILD_SESSION")[];
|
|
8
8
|
export declare function wireIdentity(env: NodeJS.ProcessEnv): Record<string, string>;
|
|
9
9
|
export interface WireError {
|
|
10
10
|
readonly code: number;
|