@astrosheep/square 0.3.94 → 0.3.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact.js +1 -1
- package/dist/attention-presentation.d.ts +5 -4
- package/dist/attention-presentation.js +18 -8
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.js +50 -48
- package/dist/catch-decisions.d.ts +1 -2
- package/dist/catch-decisions.js +41 -21
- package/dist/channel.d.ts +72 -0
- package/dist/channel.js +86 -0
- package/dist/claude-delivery.js +2 -1
- package/dist/cli/context.js +0 -2
- package/dist/cli/express-presentation.js +3 -3
- package/dist/cli/json.js +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +1 -3
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +21 -27
- package/dist/decisions.d.ts +0 -5
- package/dist/decisions.js +0 -13
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-operations.js +43 -68
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +8 -7
- package/dist/help.js +4 -6
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/inbox.js +3 -1
- package/dist/mcp.js +65 -63
- package/dist/model.d.ts +10 -6
- package/dist/model.js +20 -1
- package/dist/notifications.js +2 -0
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +12 -20
- package/dist/ports.d.ts +8 -14
- package/dist/presentation-operations.d.ts +15 -19
- package/dist/presentation-operations.js +47 -57
- package/dist/presentation.d.ts +2 -9
- package/dist/presentation.js +13 -48
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +2 -3
- package/dist/sqlite-square.d.ts +3 -2
- package/dist/sqlite-square.js +83 -57
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-core.d.ts +1 -9
- package/dist/square-domain.js +29 -36
- package/dist/square-facade.d.ts +25 -23
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-migration.d.ts +3 -1
- package/dist/square-migration.js +46 -13
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +5 -3
- package/dist/square-sqlite.js +100 -92
- package/dist/square-tools.d.ts +1 -0
- package/dist/square-tools.js +12 -12
- package/dist/views.d.ts +2 -3
- package/dist/views.js +14 -19
- package/dist/wakes.d.ts +0 -3
- package/dist/wakes.js +3 -5
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +5 -5
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/codex-delivery.md +1 -1
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp.md +1 -1
- package/guides/migration.md +12 -6
- package/guides/participant-ownership.md +1 -1
- package/guides/pi-tools.md +2 -2
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +17 -23
- package/guides/square-api.md +77 -10
- package/guides/symmetric-domains.md +92 -0
- package/package.json +18 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/paseo/client/marble.ts +7 -0
- package/plugins/paseo/client/markdown.tsx +65 -9
- package/plugins/paseo/client/palette.ts +43 -20
- package/plugins/paseo/client/square-message.tsx +57 -48
- package/plugins/paseo/shared/message.ts +1 -2
- package/plugins/pi/square-pi.js +6 -4
- package/skills/square/SKILL.md +2 -6
|
@@ -15,6 +15,14 @@ CREATE INDEX route_session ON routes(session_json, position);`;
|
|
|
15
15
|
function normalizedSql(sql) {
|
|
16
16
|
return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1');
|
|
17
17
|
}
|
|
18
|
+
/** Historical activity framing only; no runtime activity decoder accepts these kinds. */
|
|
19
|
+
export function legacyActivitySchema(currentSchemaSql) {
|
|
20
|
+
return currentSchemaSql
|
|
21
|
+
.replace("kind IN ('join','done','say','read','listen','ignore','unlisten')", "kind IN ('join','done','say','hold','resume','read','listen','ignore','unlisten')")
|
|
22
|
+
.replace(' actor_json TEXT COLLATE BINARY NOT NULL,', ' actor_json TEXT COLLATE BINARY,')
|
|
23
|
+
.replace(' actor_key_json TEXT COLLATE BINARY NOT NULL,', ' actor_key_json TEXT COLLATE BINARY,')
|
|
24
|
+
.replace(" CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))", " CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),\n CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),\n CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))");
|
|
25
|
+
}
|
|
18
26
|
function legacyRowSchema(currentSchemaSql, version) {
|
|
19
27
|
const statements = currentSchemaSql.split(';').map((statement) => statement.trim()).filter(Boolean)
|
|
20
28
|
.filter((statement) => {
|
|
@@ -20,8 +20,10 @@ export interface MigrationOutcome {
|
|
|
20
20
|
/**
|
|
21
21
|
* Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
|
|
22
22
|
* preflighted read-only, then receive ownership/evidence and deferral storage in one
|
|
23
|
-
* format-
|
|
23
|
+
* format-8 transaction. Formats 4/5 gain attachment; format 4 also gains empty deferrals.
|
|
24
24
|
* All legacy seats are encoded as opaque owners, preserving format-6 attachment.
|
|
25
|
+
* Every source drops obsolete activities and their identity references, clears dangling
|
|
26
|
+
* replies and rebuilds derived indices; surviving ids and allocation watermarks remain stable.
|
|
25
27
|
* Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
|
|
26
28
|
* unique; no source record is retired. Version 1 is not a supported source for this format transition.
|
|
27
29
|
*/
|
package/dist/square-migration.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { openExistingSquareDatabase } from './artifact.js';
|
|
3
4
|
import { inSqliteTransaction } from './sqlite-transaction.js';
|
|
@@ -7,7 +8,7 @@ import { formatActivityId, isWakeRouteKind, nameKey } from './model.js';
|
|
|
7
8
|
import { parseActivityId } from './square-core.js';
|
|
8
9
|
import { readLegacyHostData } from './square-migration-host-ledger.js';
|
|
9
10
|
import { legacyRowFramingIssue, FORMAT4_ADDED_OBJECTS } from './square-legacy-framing.js';
|
|
10
|
-
import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V3_USER_VERSION, V4_USER_VERSION, V5_USER_VERSION, V6_USER_VERSION, SCHEMA_SQL, FORMAT4_SCHEMA_SQL, DELIVERY_DEFERRALS_SQL, validateRowFraming, inspectSqlite, createSqliteDriver, invalidArtifact, validateStoredActivity } from './square-sqlite.js';
|
|
11
|
+
import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V3_USER_VERSION, V4_USER_VERSION, V5_USER_VERSION, V6_USER_VERSION, V7_USER_VERSION, SCHEMA_SQL, FORMAT4_SCHEMA_SQL, DELIVERY_DEFERRALS_SQL, validateRowFraming, inspectSqlite, createSqliteDriver, invalidArtifact, validateStoredActivity, exportSqliteState, initializeSqlite } from './square-sqlite.js';
|
|
11
12
|
function isObject(value) {
|
|
12
13
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
14
|
}
|
|
@@ -134,15 +135,44 @@ function standing(database) {
|
|
|
134
135
|
return createSqliteDriver(database).roster().filter((participant) => participant.joined).length;
|
|
135
136
|
}
|
|
136
137
|
const ACTIVITY_INDEX_NAMES = ['activity_kind_index', 'activity_actor_index', 'activity_say_actor_index', 'activity_say_time', 'activity_bell_actor_time'];
|
|
137
|
-
function
|
|
138
|
+
function migrateActivities(database) {
|
|
139
|
+
// Only activity identity references are deleted. Range watermarks (read.through and
|
|
140
|
+
// delivery cancellation boundaries) remain meaningful even when their endpoint is gone.
|
|
141
|
+
for (const table of ['landed_audience', 'receipts', 'delivery_evidence']) {
|
|
142
|
+
database.exec(`DELETE FROM ${table} WHERE act_index IN (SELECT act_index FROM activities WHERE kind IN ('hold','resume'));`);
|
|
143
|
+
}
|
|
138
144
|
database.exec([
|
|
145
|
+
"UPDATE activities SET reply_index=NULL WHERE reply_index IN (SELECT act_index FROM activities WHERE kind IN ('hold','resume'));",
|
|
139
146
|
...ACTIVITY_INDEX_NAMES.map((name) => `DROP INDEX ${name};`),
|
|
140
|
-
'ALTER TABLE activities RENAME TO
|
|
147
|
+
'ALTER TABLE activities RENAME TO activities_legacy;',
|
|
141
148
|
ACTIVITIES_TABLE_SQL,
|
|
142
|
-
|
|
143
|
-
'DROP TABLE
|
|
149
|
+
"INSERT INTO activities(act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json) SELECT act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json FROM activities_legacy WHERE kind NOT IN ('hold','resume') ORDER BY act_index;",
|
|
150
|
+
'DROP TABLE activities_legacy;',
|
|
144
151
|
...ACTIVITY_INDEXES_SQL.map((sql) => `${sql};`),
|
|
145
152
|
].join('\n'));
|
|
153
|
+
// Actor-bearing deleted activities and cleared replies can change maintained indices.
|
|
154
|
+
// Replay surviving facts only, without lowering the authoritative next allocator.
|
|
155
|
+
const names = database.prepare('SELECT name_key_json,name_json FROM participants').all();
|
|
156
|
+
const rebuilt = new DatabaseSync(':memory:');
|
|
157
|
+
try {
|
|
158
|
+
initializeSqlite(rebuilt, exportSqliteState(database));
|
|
159
|
+
// Canonical spelling is an established identity fact, not the spelling of
|
|
160
|
+
// whichever actor activity happens to survive this migration first.
|
|
161
|
+
const retainName = rebuilt.prepare('UPDATE participants SET name_json=? WHERE name_key_json=?');
|
|
162
|
+
for (const row of names)
|
|
163
|
+
retainName.run(row.name_json, row.name_key_json);
|
|
164
|
+
for (const table of ['participants', 'attention', 'landed_audience']) {
|
|
165
|
+
database.exec(`DELETE FROM ${table};`);
|
|
166
|
+
for (const row of rebuilt.prepare(`SELECT * FROM ${table}`).all()) {
|
|
167
|
+
const columns = Object.keys(row);
|
|
168
|
+
database.prepare(`INSERT INTO ${table}(${columns.join(',')}) VALUES(${columns.map(() => '?').join(',')})`)
|
|
169
|
+
.run(...Object.values(row));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
rebuilt.close();
|
|
175
|
+
}
|
|
146
176
|
}
|
|
147
177
|
function validateCurrentFraming(database, location) {
|
|
148
178
|
validateRowFraming(database, CURRENT_USER_VERSION, location);
|
|
@@ -282,7 +312,7 @@ function inspectLegacyRowArtifact(database, location, host) {
|
|
|
282
312
|
const driver = createSqliteDriver(database);
|
|
283
313
|
let activityCount = 0;
|
|
284
314
|
let previous = -1;
|
|
285
|
-
for (const row of database.prepare(
|
|
315
|
+
for (const row of database.prepare("SELECT act_index FROM activities WHERE kind NOT IN ('hold','resume') ORDER BY act_index").iterate()) {
|
|
286
316
|
const index = sourceInteger(row.act_index);
|
|
287
317
|
assertSource(index > previous && index < nextActIndex, 'activity index is malformed or unassigned.');
|
|
288
318
|
const activity = driver.activity(index);
|
|
@@ -389,7 +419,7 @@ function planLegacyDeliveries(records, seats, driver) {
|
|
|
389
419
|
continue;
|
|
390
420
|
const joined = establishingJoin(driver, participant);
|
|
391
421
|
const generation = formatActivityId(joined);
|
|
392
|
-
if (index <= joined || driver.
|
|
422
|
+
if (index <= joined || driver.activityHeads({ indexes: [index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items.length === 0 || driver.directedAt(participant.name, index) === undefined)
|
|
393
423
|
continue;
|
|
394
424
|
if (participant.done) {
|
|
395
425
|
const done = driver.activityHeads({ actors: [participant.name], kinds: ['done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
@@ -488,6 +518,7 @@ function upgradeRowArtifact(database, location, plan) {
|
|
|
488
518
|
importOwnershipAndEvidence(database, plan);
|
|
489
519
|
database.exec('DROP TABLE routes;');
|
|
490
520
|
database.exec(DELIVERY_DEFERRALS_SQL);
|
|
521
|
+
migrateActivities(database);
|
|
491
522
|
database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION};`);
|
|
492
523
|
validateCurrentFraming(database, location);
|
|
493
524
|
}
|
|
@@ -501,7 +532,7 @@ function validateMigrationSource(database, location) {
|
|
|
501
532
|
throw invalidArtifact(issue);
|
|
502
533
|
return before.userVersion;
|
|
503
534
|
}
|
|
504
|
-
if (before.userVersion === V4_USER_VERSION || before.userVersion === V5_USER_VERSION || before.userVersion === V6_USER_VERSION) {
|
|
535
|
+
if (before.userVersion === V4_USER_VERSION || before.userVersion === V5_USER_VERSION || before.userVersion === V6_USER_VERSION || before.userVersion === V7_USER_VERSION) {
|
|
505
536
|
validateRowFraming(database, before.userVersion, location);
|
|
506
537
|
return before.userVersion;
|
|
507
538
|
}
|
|
@@ -514,8 +545,10 @@ function validateMigrationSource(database, location) {
|
|
|
514
545
|
/**
|
|
515
546
|
* Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
|
|
516
547
|
* preflighted read-only, then receive ownership/evidence and deferral storage in one
|
|
517
|
-
* format-
|
|
548
|
+
* format-8 transaction. Formats 4/5 gain attachment; format 4 also gains empty deferrals.
|
|
518
549
|
* All legacy seats are encoded as opaque owners, preserving format-6 attachment.
|
|
550
|
+
* Every source drops obsolete activities and their identity references, clears dangling
|
|
551
|
+
* replies and rebuilds derived indices; surviving ids and allocation watermarks remain stable.
|
|
519
552
|
* Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
|
|
520
553
|
* unique; no source record is retired. Version 1 is not a supported source for this format transition.
|
|
521
554
|
*/
|
|
@@ -526,13 +559,15 @@ export async function migrateSquareFile(location, signal, env = process.env) {
|
|
|
526
559
|
const source = await inSqliteTransaction(database, false, () => validateMigrationSource(database, squarePath), value => value, signal);
|
|
527
560
|
if (source === undefined)
|
|
528
561
|
return { status: 'already-current', standing: standing(database) };
|
|
529
|
-
if (source === V4_USER_VERSION || source === V5_USER_VERSION || source === V6_USER_VERSION) {
|
|
562
|
+
if (source === V4_USER_VERSION || source === V5_USER_VERSION || source === V6_USER_VERSION || source === V7_USER_VERSION) {
|
|
530
563
|
return await inSqliteTransaction(database, true, () => {
|
|
531
564
|
assertSource(validateMigrationSource(database, squarePath) === source, 'source format changed during migration preflight.');
|
|
532
565
|
}, () => {
|
|
533
566
|
if (source === V4_USER_VERSION)
|
|
534
567
|
database.exec(DELIVERY_DEFERRALS_SQL);
|
|
535
|
-
|
|
568
|
+
if (source !== V7_USER_VERSION)
|
|
569
|
+
migrateSeatOwnership(database, source);
|
|
570
|
+
migrateActivities(database);
|
|
536
571
|
database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION};`);
|
|
537
572
|
validateCurrentFraming(database, squarePath);
|
|
538
573
|
return { status: 'migrated', standing: standing(database) };
|
|
@@ -552,8 +587,6 @@ export async function migrateSquareFile(location, signal, env = process.env) {
|
|
|
552
587
|
assertSource(lockedPlan.sourceToken === plan.sourceToken, 'source changed during migration preflight.');
|
|
553
588
|
result.plan = lockedPlan;
|
|
554
589
|
}, () => {
|
|
555
|
-
if (source === 2)
|
|
556
|
-
rebuildV2Activities(database);
|
|
557
590
|
const migratedPlan = result.plan;
|
|
558
591
|
upgradeRowArtifact(database, squarePath, migratedPlan);
|
|
559
592
|
result.outcome = { status: 'migrated', standing: migratedPlan.standing, counts: migratedPlan.counts };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AudienceRoute } from './attention.js';
|
|
2
|
+
import type { StoredAct } from './model.js';
|
|
3
|
+
import type { DirectedFilter, SquareRead } from './ports.js';
|
|
4
|
+
import type { ReceiveRead } from './receive.js';
|
|
5
|
+
/** Transaction-local projections only; the artifact still owns indexed selection and bodies. */
|
|
6
|
+
export declare function squareReceiveRead(facts: SquareRead): ReceiveRead<number, StoredAct, AudienceRoute, DirectedFilter>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Transaction-local projections only; the artifact still owns indexed selection and bodies. */
|
|
2
|
+
export function squareReceiveRead(facts) {
|
|
3
|
+
const filter = (query, unconsumed) => {
|
|
4
|
+
const { unseenOnly: _, ...rest } = query;
|
|
5
|
+
return unconsumed ? { ...rest, unseenOnly: true } : rest;
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
heads: (recipient, query, options) => facts.directed(recipient, filter(query, options.unconsumed), { edge: 'oldest', limit: options.limit })
|
|
9
|
+
.items.map(item => ({ key: item.index, via: item.route })),
|
|
10
|
+
addressed(recipient, index) {
|
|
11
|
+
const head = facts.directedAt(recipient, index);
|
|
12
|
+
return head === undefined ? undefined : { key: head.index, via: head.route };
|
|
13
|
+
},
|
|
14
|
+
load: keys => new Map(facts.activities(keys).map(item => [item.index, item])),
|
|
15
|
+
count: (recipient, query, unconsumed) => facts.directedCount(recipient, filter(query, unconsumed)),
|
|
16
|
+
receipt: ref => facts.receipt(ref.recipient, ref.item),
|
|
17
|
+
};
|
|
18
|
+
}
|
package/dist/square-sqlite.d.ts
CHANGED
|
@@ -23,7 +23,8 @@ export declare const V3_USER_VERSION = 3;
|
|
|
23
23
|
export declare const V4_USER_VERSION = 4;
|
|
24
24
|
export declare const V5_USER_VERSION = 5;
|
|
25
25
|
export declare const V6_USER_VERSION = 6;
|
|
26
|
-
export declare const
|
|
26
|
+
export declare const V7_USER_VERSION = 7;
|
|
27
|
+
export declare const CURRENT_USER_VERSION = 8;
|
|
27
28
|
export declare const V1_SNAPSHOT_TABLE = "square_snapshot";
|
|
28
29
|
export interface LegacyFraming {
|
|
29
30
|
readonly applicationId: number;
|
|
@@ -37,17 +38,18 @@ export interface LegacyFraming {
|
|
|
37
38
|
* migration. Anything else is unrelated or unsupported, not repaired.
|
|
38
39
|
*/
|
|
39
40
|
export declare function validateLegacyFraming(database: DatabaseSync, applicationId: number, location?: string): LegacyFraming;
|
|
40
|
-
export declare const ACTIVITIES_TABLE_SQL = "CREATE 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','
|
|
41
|
+
export declare const ACTIVITIES_TABLE_SQL = "CREATE 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);";
|
|
41
42
|
export declare const ACTIVITY_INDEXES_SQL: readonly ["CREATE INDEX activity_kind_index ON activities(kind, act_index)", "CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index)", "CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say'", "CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say'", "CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell'"];
|
|
42
43
|
export declare const DELIVERY_DEFERRALS_SQL = "CREATE TABLE delivery_deferrals (\n participant_key_json TEXT COLLATE BINARY NOT NULL,\n participant_json TEXT NOT NULL,\n generation_json TEXT NOT NULL,\n deferral_id_json TEXT NOT NULL,\n match_json TEXT NOT NULL CHECK (json_valid(match_json)),\n expires_at REAL NOT NULL,\n PRIMARY KEY (participant_key_json,generation_json,deferral_id_json)\n) WITHOUT ROWID;\nCREATE INDEX delivery_deferral_expiry ON delivery_deferrals(participant_key_json,generation_json,expires_at);";
|
|
43
44
|
export declare const SCHEMA_SQL: string;
|
|
45
|
+
export declare const FORMAT7_SCHEMA_SQL: string;
|
|
44
46
|
export declare const FORMAT6_SCHEMA_SQL: string;
|
|
45
47
|
export declare const FORMAT5_SCHEMA_SQL: string;
|
|
46
48
|
export declare const FORMAT4_SCHEMA_SQL: string;
|
|
47
49
|
export declare function invalidArtifact(detail: string): SquareError;
|
|
48
50
|
export declare function validateStoredActivity(value: unknown): StoredAct;
|
|
49
51
|
export declare function validateWatchLease(value: unknown): WatchLease;
|
|
50
|
-
export declare function validateRowFraming(db: DatabaseSync, version: 2 | 3 | 4 | 5 | 6 | 7, location?: string): void;
|
|
52
|
+
export declare function validateRowFraming(db: DatabaseSync, version: 2 | 3 | 4 | 5 | 6 | 7 | 8, location?: string): void;
|
|
51
53
|
export declare function validateSqliteHeader(db: DatabaseSync, location?: string): SquareHeader;
|
|
52
54
|
export interface SqliteDriver extends SquareRead, LandingWriter {
|
|
53
55
|
readonly changed: boolean;
|
package/dist/square-sqlite.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { DatabaseSync as MemoryDatabase } from 'node:sqlite';
|
|
2
|
-
import { SquareError, nameKey, isWakeRouteKind, validateOwner, validateActivityMetadata, activityMetadataHasContent } from './model.js';
|
|
2
|
+
import { SquareError, nameKey, isWakeRouteKind, validateOwner, validateActivityMetadata, validateActivityDelivery, activityMetadataHasContent } from './model.js';
|
|
3
3
|
import { validateMetadataMatch, matchingDeferrals, validateDeferralId } from './delivery-deferral.js';
|
|
4
4
|
import { deriveLanding, landingFacts } from './square-domain.js';
|
|
5
|
-
import {
|
|
5
|
+
import { claimAttempt, prepareAttempt, settleAttempt } from './delivery-attempts.js';
|
|
6
|
+
import { legacyRowFramingIssue, legacyActivitySchema } from './square-legacy-framing.js';
|
|
7
|
+
import { receive, recordConsumed } from './receive.js';
|
|
6
8
|
export function emptyRuntimeState(nextActIndex = 0) {
|
|
7
9
|
return { nextActIndex, observations: {}, leases: {} };
|
|
8
10
|
}
|
|
@@ -13,7 +15,8 @@ export const V3_USER_VERSION = 3;
|
|
|
13
15
|
export const V4_USER_VERSION = 4;
|
|
14
16
|
export const V5_USER_VERSION = 5;
|
|
15
17
|
export const V6_USER_VERSION = 6;
|
|
16
|
-
export const
|
|
18
|
+
export const V7_USER_VERSION = 7;
|
|
19
|
+
export const CURRENT_USER_VERSION = 8;
|
|
17
20
|
export const V1_SNAPSHOT_TABLE = 'square_snapshot';
|
|
18
21
|
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)`;
|
|
19
22
|
const V1_SNAPSHOT_COLUMNS = [
|
|
@@ -58,18 +61,16 @@ export function validateLegacyFraming(database, applicationId, location = '') {
|
|
|
58
61
|
function normalizedLegacySql(sql) {
|
|
59
62
|
return sql.trim().replace(/\s+/g, ' ').replace(/\s*(>=|[(),=])\s*/g, '$1');
|
|
60
63
|
}
|
|
61
|
-
const CURRENT_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','
|
|
64
|
+
const CURRENT_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','read','listen','ignore','unlisten')";
|
|
62
65
|
export const ACTIVITIES_TABLE_SQL = `CREATE TABLE activities (
|
|
63
66
|
act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
64
67
|
at REAL NOT NULL,
|
|
65
68
|
kind TEXT NOT NULL CHECK (${CURRENT_ACTIVITY_KIND_CHECK}),
|
|
66
|
-
actor_json TEXT COLLATE BINARY,
|
|
67
|
-
actor_key_json TEXT COLLATE BINARY,
|
|
69
|
+
actor_json TEXT COLLATE BINARY NOT NULL,
|
|
70
|
+
actor_key_json TEXT COLLATE BINARY NOT NULL,
|
|
68
71
|
reach TEXT CHECK (reach = 'bell'),
|
|
69
72
|
reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),
|
|
70
73
|
payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
|
|
71
|
-
CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),
|
|
72
|
-
CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),
|
|
73
74
|
CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))
|
|
74
75
|
);`;
|
|
75
76
|
export const ACTIVITY_INDEXES_SQL = [
|
|
@@ -182,9 +183,11 @@ CREATE TABLE landed_audience (
|
|
|
182
183
|
UNIQUE (act_index, recipient_order)
|
|
183
184
|
) WITHOUT ROWID;
|
|
184
185
|
CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);`;
|
|
185
|
-
export const
|
|
186
|
+
export const FORMAT7_SCHEMA_SQL = legacyActivitySchema(SCHEMA_SQL);
|
|
187
|
+
export const FORMAT6_SCHEMA_SQL = FORMAT7_SCHEMA_SQL.replace(` owner_json TEXT NOT NULL,\n generation_json TEXT NOT NULL,`, ` channel_json TEXT NOT NULL,\n session_json TEXT NOT NULL,\n agent_json TEXT,\n generation_json TEXT NOT NULL,`).replace('CREATE INDEX seat_owner ON seats(owner_json);', 'CREATE INDEX seat_body ON seats(channel_json, session_json, agent_json);');
|
|
186
188
|
export const FORMAT5_SCHEMA_SQL = FORMAT6_SCHEMA_SQL.replace(`,\n ${ATTACHMENT_COLUMN_SQL}`, '');
|
|
187
189
|
export const FORMAT4_SCHEMA_SQL = FORMAT5_SCHEMA_SQL.replace(DELIVERY_DEFERRALS_SQL, '');
|
|
190
|
+
const FORMAT7_SCHEMA = FORMAT7_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
188
191
|
const FORMAT6_SCHEMA = FORMAT6_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
189
192
|
const FORMAT5_SCHEMA = FORMAT5_SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
190
193
|
const EXPECTED_SCHEMA_SQL = SCHEMA_SQL.split(';').map(s => s.trim()).filter(Boolean).map(normalizedSql).sort();
|
|
@@ -244,17 +247,17 @@ function immutable(value) {
|
|
|
244
247
|
}
|
|
245
248
|
return value;
|
|
246
249
|
}
|
|
247
|
-
const kinds = ['join', 'done', 'say', '
|
|
250
|
+
const kinds = ['join', 'done', 'say', 'read', 'listen', 'ignore', 'unlisten'];
|
|
248
251
|
function head(row) {
|
|
249
252
|
const index = integer(row.act_index), at = finite(row.at);
|
|
250
253
|
requireFact(kinds.includes(string(row.kind)));
|
|
251
|
-
const actor =
|
|
252
|
-
requireFact(
|
|
254
|
+
const actor = decoded(row.actor_json, true);
|
|
255
|
+
requireFact(row.actor_key_json === key(actor));
|
|
253
256
|
requireFact(row.reach === null || row.kind === 'say' && row.reach === 'bell');
|
|
254
257
|
if (row.reply_index !== null)
|
|
255
258
|
requireFact(row.kind === 'say' && integer(row.reply_index) < index);
|
|
256
259
|
return immutable({
|
|
257
|
-
index, at, kind: row.kind,
|
|
260
|
+
index, at, kind: row.kind, actor, ...(row.reach === null ? {} : { reach: 'bell' }), ...(row.reply_index === null ? {} : { reply: row.reply_index })
|
|
258
261
|
});
|
|
259
262
|
}
|
|
260
263
|
export function validateStoredActivity(value) {
|
|
@@ -262,16 +265,20 @@ export function validateStoredActivity(value) {
|
|
|
262
265
|
integer(a.index);
|
|
263
266
|
finite(a.at);
|
|
264
267
|
requireFact(kinds.includes(string(a.kind)));
|
|
265
|
-
const common = ['kind', 'at', 'index'];
|
|
266
|
-
|
|
267
|
-
common.push('actor');
|
|
268
|
-
string(a.actor, true);
|
|
269
|
-
}
|
|
270
|
-
else if (Object.hasOwn(a, 'actor'))
|
|
271
|
-
string(a.actor, true);
|
|
268
|
+
const common = ['kind', 'at', 'index', 'actor'];
|
|
269
|
+
string(a.actor, true);
|
|
272
270
|
switch (a.kind) {
|
|
273
271
|
case 'say':
|
|
274
|
-
shape(a, [...common], ['body', 'mentions', 'reach', 'reply', 'metadata']);
|
|
272
|
+
shape(a, [...common], ['body', 'mentions', 'reach', 'reply', 'metadata', 'delivery']);
|
|
273
|
+
if (Object.hasOwn(a, 'delivery')) {
|
|
274
|
+
requireFact(a.delivery !== undefined, 'say delivery is malformed.');
|
|
275
|
+
try {
|
|
276
|
+
validateActivityDelivery(a.delivery);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
throw invalidArtifact('say delivery is malformed.');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
275
282
|
if (Object.hasOwn(a, 'body'))
|
|
276
283
|
string(a.body);
|
|
277
284
|
if (Object.hasOwn(a, 'metadata')) {
|
|
@@ -292,8 +299,7 @@ export function validateStoredActivity(value) {
|
|
|
292
299
|
requireFact(integer(a.reply) < a.index);
|
|
293
300
|
break;
|
|
294
301
|
case 'done':
|
|
295
|
-
|
|
296
|
-
shape(a, common, ['body', ...(a.kind === 'hold' ? ['actor'] : [])]);
|
|
302
|
+
shape(a, common, ['body']);
|
|
297
303
|
if (Object.hasOwn(a, 'body'))
|
|
298
304
|
string(a.body);
|
|
299
305
|
break;
|
|
@@ -307,14 +313,14 @@ export function validateStoredActivity(value) {
|
|
|
307
313
|
shape(a, [...common, 'target']);
|
|
308
314
|
string(a.target, true);
|
|
309
315
|
break;
|
|
310
|
-
default: shape(a, common
|
|
316
|
+
default: shape(a, common);
|
|
311
317
|
}
|
|
312
318
|
return value;
|
|
313
319
|
}
|
|
314
320
|
function activity(row) {
|
|
315
321
|
const h = head(row), payload = object(json(row.payload_json));
|
|
316
322
|
// Payload cannot shadow indexed structural columns.
|
|
317
|
-
const allowed = h.kind === 'say' ? ['body', 'mentions', 'metadata'] : h.kind === 'done'
|
|
323
|
+
const allowed = h.kind === 'say' ? ['body', 'mentions', 'metadata', 'delivery'] : h.kind === 'done' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
|
|
318
324
|
requireFact(Object.keys(payload).every(k => allowed.includes(k)));
|
|
319
325
|
return immutable(validateStoredActivity({ ...h, ...payload }));
|
|
320
326
|
}
|
|
@@ -403,8 +409,7 @@ function nativeCorrelation(value) {
|
|
|
403
409
|
...(item.messageId === undefined ? {} : { messageId: item.messageId }),
|
|
404
410
|
...(item.payloadHash === undefined ? {} : { payloadHash: item.payloadHash }) });
|
|
405
411
|
}
|
|
406
|
-
function deliveryEvidence(row) {
|
|
407
|
-
const payload = object(json(row.payload_json));
|
|
412
|
+
function deliveryEvidence(row, payload = object(json(row.payload_json))) {
|
|
408
413
|
const participant = decoded(row.participant_json, true);
|
|
409
414
|
requireFact(row.participant_key_json === key(participant));
|
|
410
415
|
const generation = activityId(decoded(row.generation_json, true));
|
|
@@ -461,7 +466,7 @@ export function validateRowFraming(db, version, location = '') {
|
|
|
461
466
|
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
462
467
|
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
463
468
|
requireFact(identity.user_version === version, 'unsupported SQLite format version.');
|
|
464
|
-
validateOwnedSchema(db, version === V4_USER_VERSION ? FORMAT4_SCHEMA : version === V5_USER_VERSION ? FORMAT5_SCHEMA : version === V6_USER_VERSION ? FORMAT6_SCHEMA : EXPECTED_SCHEMA_SQL, location);
|
|
469
|
+
validateOwnedSchema(db, version === V4_USER_VERSION ? FORMAT4_SCHEMA : version === V5_USER_VERSION ? FORMAT5_SCHEMA : version === V6_USER_VERSION ? FORMAT6_SCHEMA : version === V7_USER_VERSION ? FORMAT7_SCHEMA : EXPECTED_SCHEMA_SQL, location);
|
|
465
470
|
}
|
|
466
471
|
export function validateSqliteHeader(db, location = '') {
|
|
467
472
|
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
@@ -470,7 +475,7 @@ export function validateSqliteHeader(db, location = '') {
|
|
|
470
475
|
validateLegacyFraming(db, APPLICATION_ID, location);
|
|
471
476
|
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' });
|
|
472
477
|
}
|
|
473
|
-
if (identity.user_version === V2_USER_VERSION || identity.user_version === V3_USER_VERSION || identity.user_version === V4_USER_VERSION || identity.user_version === V5_USER_VERSION || identity.user_version === V6_USER_VERSION) {
|
|
478
|
+
if (identity.user_version === V2_USER_VERSION || identity.user_version === V3_USER_VERSION || identity.user_version === V4_USER_VERSION || identity.user_version === V5_USER_VERSION || identity.user_version === V6_USER_VERSION || identity.user_version === V7_USER_VERSION) {
|
|
474
479
|
validateRowFraming(db, identity.user_version, location);
|
|
475
480
|
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' });
|
|
476
481
|
}
|
|
@@ -494,8 +499,6 @@ function activitySelection(filter, alias = 'a') {
|
|
|
494
499
|
add(`${alias}.act_index <= ?`, integer(filter.throughIndex, -1));
|
|
495
500
|
if (filter.afterAt !== undefined)
|
|
496
501
|
add(`${alias}.at > ?`, finite(filter.afterAt));
|
|
497
|
-
if (filter.actorRequired)
|
|
498
|
-
terms.push(`${alias}.actor_json IS NOT NULL`);
|
|
499
502
|
for (const [column, values] of [['act_index', filter.indexes], ['kind', filter.kinds], [filter.actorMatch === 'exact' ? 'actor_json' : 'actor_key_json', filter.actors?.map(name => filter.actorMatch === 'exact' ? encode(name) : key(name))]]) {
|
|
500
503
|
if (values !== undefined) {
|
|
501
504
|
if (column === 'act_index')
|
|
@@ -540,7 +543,7 @@ export function createSqliteDriver(db) {
|
|
|
540
543
|
}
|
|
541
544
|
function directedHead(row, recipient) {
|
|
542
545
|
const h = head(row);
|
|
543
|
-
requireFact(h.kind === 'say'
|
|
546
|
+
requireFact(h.kind === 'say');
|
|
544
547
|
return immutable({
|
|
545
548
|
index: h.index, at: h.at, actor: h.actor, recipient, route: routeKind(row.route)
|
|
546
549
|
});
|
|
@@ -566,7 +569,7 @@ export function createSqliteDriver(db) {
|
|
|
566
569
|
const filter = activityIndex === undefined ? '' : ' AND act_index=?';
|
|
567
570
|
if (activityIndex !== undefined)
|
|
568
571
|
args.push(integer(Number(activityIndex.slice(4))));
|
|
569
|
-
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));
|
|
572
|
+
return immutable(all(`SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=?${filter} ORDER BY act_index,kind,attempt_n`, args).map(row => deliveryEvidence(row)));
|
|
570
573
|
},
|
|
571
574
|
deliveryByToken(claimToken) {
|
|
572
575
|
const row = one('SELECT * FROM delivery_evidence WHERE claim_token_json=?', [encode(claimToken)]);
|
|
@@ -596,18 +599,6 @@ export function createSqliteDriver(db) {
|
|
|
596
599
|
roster: () => immutable(all('SELECT * FROM participants ORDER BY first_index').map(participant)),
|
|
597
600
|
attention: name => immutable(attentionRows('t.listener_key_json=?', [key(name)])),
|
|
598
601
|
incomingAttention: name => immutable(attentionRows('t.target_key_json=?', [key(name)])),
|
|
599
|
-
hold() {
|
|
600
|
-
const r = one("SELECT * FROM activities WHERE kind IN ('hold','resume') ORDER BY act_index DESC LIMIT 1");
|
|
601
|
-
if (!r)
|
|
602
|
-
return immutable({ active: false });
|
|
603
|
-
const a = activity(r);
|
|
604
|
-
if (a.kind === 'resume')
|
|
605
|
-
return immutable({ active: false });
|
|
606
|
-
requireFact(a.kind === 'hold');
|
|
607
|
-
return immutable({
|
|
608
|
-
active: true, at: a.at, ...(a.body === undefined ? {} : { reason: a.body }), ...(a.actor === undefined ? {} : { actor: a.actor })
|
|
609
|
-
});
|
|
610
|
-
},
|
|
611
602
|
activity(index) {
|
|
612
603
|
const r = one('SELECT * FROM activities WHERE act_index=?', [integer(index)]);
|
|
613
604
|
return r ? activity(r) : undefined;
|
|
@@ -685,7 +676,7 @@ export function createSqliteDriver(db) {
|
|
|
685
676
|
const groups = indices.map(r => {
|
|
686
677
|
const rows = all(`SELECT a.act_index,a.at,a.kind,a.actor_json,a.actor_key_json,a.reach,a.reply_index,p.name_json,d.route FROM ${pendingFrom} WHERE ${pendingWhere} AND d.act_index=? ORDER BY d.recipient_order`, [integer(r.act_index)]);
|
|
687
678
|
const h = head(rows[0]);
|
|
688
|
-
requireFact(h.kind === 'say'
|
|
679
|
+
requireFact(h.kind === 'say');
|
|
689
680
|
return {
|
|
690
681
|
index: h.index, at: h.at, actor: h.actor, recipients: rows.map(v => ({ recipient: decoded(v.name_json, true), route: routeKind(v.route) }))
|
|
691
682
|
};
|
|
@@ -788,7 +779,10 @@ export function createSqliteDriver(db) {
|
|
|
788
779
|
const a = validateStoredActivity(effect.activity);
|
|
789
780
|
requireFact(a.index >= read.header().nextActIndex && a.index < Number.MAX_SAFE_INTEGER, 'activity index cannot advance.');
|
|
790
781
|
const { index, at, kind, actor, reach, reply, ...payload } = a;
|
|
791
|
-
|
|
782
|
+
receive({
|
|
783
|
+
insertItem: () => { run('INSERT INTO activities(act_index,at,kind,actor_json,actor_key_json,reach,reply_index,payload_json) VALUES(?,?,?,?,?,?,?,?)', [index, at, kind, encode(actor), key(actor), reach ?? null, reply ?? null, encode(payload)]); },
|
|
784
|
+
insertAddress: (item, address, order) => { run('INSERT INTO landed_audience(act_index,recipient_key_json,recipient_order,route) VALUES(?,?,?,?)', [item, key(address.recipient), order, address.via]); },
|
|
785
|
+
}, { item: { key: index, data: a }, audience: effect.audience.map(r => ({ recipient: r.recipient, via: r.route })) });
|
|
792
786
|
if (effect.participant) {
|
|
793
787
|
const p = effect.participant;
|
|
794
788
|
string(p.name, true);
|
|
@@ -817,7 +811,6 @@ export function createSqliteDriver(db) {
|
|
|
817
811
|
requireFact(t.kind === 'listen' || t.kind === 'ignore');
|
|
818
812
|
run('INSERT INTO attention(listener_key_json,target_key_json,target_json,kind,since_index) VALUES(?,?,?,?,?) ON CONFLICT(listener_key_json,target_key_json) DO UPDATE SET target_json=excluded.target_json,kind=excluded.kind,since_index=excluded.since_index', [key(t.listener), key(t.target), encode(t.target), t.kind, t.sinceIndex]);
|
|
819
813
|
}
|
|
820
|
-
effect.audience.forEach((r, order) => run('INSERT INTO landed_audience(act_index,recipient_key_json,recipient_order,route) VALUES(?,?,?,?)', [index, key(r.recipient), order, r.route]));
|
|
821
814
|
run('UPDATE square_meta SET next_act_index=? WHERE id=1', [index + 1]);
|
|
822
815
|
changed = true;
|
|
823
816
|
},
|
|
@@ -825,11 +818,11 @@ export function createSqliteDriver(db) {
|
|
|
825
818
|
finite(at);
|
|
826
819
|
const next = read.header().nextActIndex;
|
|
827
820
|
indexes.forEach(index => requireFact(integer(index) < next, 'receipt references an unassigned activity index.'));
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
821
|
+
const canonical = owner(name);
|
|
822
|
+
string(canonical, true);
|
|
823
|
+
const count = recordConsumed({
|
|
824
|
+
insertReceipt: (ref, time) => run('INSERT OR IGNORE INTO receipts(owner_json,act_index,at) VALUES(?,?,?)', [encode(ref.recipient), ref.item, time]) > 0,
|
|
825
|
+
}, indexes.map(item => ({ recipient: canonical, item })), at);
|
|
833
826
|
if (count)
|
|
834
827
|
changed = true;
|
|
835
828
|
return count;
|
|
@@ -884,6 +877,40 @@ export function createSqliteDriver(db) {
|
|
|
884
877
|
return count > 0;
|
|
885
878
|
},
|
|
886
879
|
};
|
|
880
|
+
function attempt(row) {
|
|
881
|
+
const data = object(json(row.payload_json)), evidence = deliveryEvidence(row, data);
|
|
882
|
+
return { purpose: evidence.kind === 'wake' ? 'dispatch' : 'presentation', token: evidence.claimToken ?? '',
|
|
883
|
+
ordinal: evidence.attemptN, outcome: evidence.outcome, at: evidence.at, expiresAt: evidence.expiresAt, data };
|
|
884
|
+
}
|
|
885
|
+
function attemptRows(seat, index, kind, known) {
|
|
886
|
+
const scope = [key(seat.participant), encode(seat.generation), index, kind];
|
|
887
|
+
let sources = [];
|
|
888
|
+
return {
|
|
889
|
+
list() {
|
|
890
|
+
sources = all('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? ORDER BY attempt_n DESC', scope);
|
|
891
|
+
return sources.map(attempt);
|
|
892
|
+
},
|
|
893
|
+
evidence(value) {
|
|
894
|
+
const source = sources.find(row => row.attempt_n === value.ordinal);
|
|
895
|
+
return deliveryEvidence({ ...source, outcome: value.outcome, at: value.at, expires_at: value.expiresAt ?? null }, value.data);
|
|
896
|
+
},
|
|
897
|
+
byToken(token) {
|
|
898
|
+
if (known?.token === token)
|
|
899
|
+
return known;
|
|
900
|
+
const row = one('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? AND claim_token_json=?', [...scope, encode(token)]);
|
|
901
|
+
return row === undefined ? undefined : attempt(row);
|
|
902
|
+
},
|
|
903
|
+
insert(value) {
|
|
904
|
+
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(?,?,?,?,?,?,?,?,?,?,?)', [scope[0], encode(seat.participant), scope[1], index, kind, value.ordinal, value.outcome, value.at, value.expiresAt ?? null, encode(value.token), encode(value.data)]);
|
|
905
|
+
deliveryChanged = true;
|
|
906
|
+
},
|
|
907
|
+
replace(value) {
|
|
908
|
+
const count = run('UPDATE delivery_evidence SET outcome=?,at=?,expires_at=?,payload_json=? WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? AND attempt_n=? AND claim_token_json IS ?', [value.outcome, value.at, value.expiresAt ?? null, encode(value.data), ...scope, value.ordinal, value.token === '' ? null : encode(value.token)]);
|
|
909
|
+
if (count)
|
|
910
|
+
deliveryChanged = true;
|
|
911
|
+
},
|
|
912
|
+
};
|
|
913
|
+
}
|
|
887
914
|
const delivery = {
|
|
888
915
|
claimDelivery(input) {
|
|
889
916
|
requireFact(input.kind === 'wake' || input.kind === 'presentation');
|
|
@@ -903,42 +930,24 @@ export function createSqliteDriver(db) {
|
|
|
903
930
|
const cancelledThrough = read.cancelledThrough(input.seat.participant, input.seat.generation);
|
|
904
931
|
if (cancelledThrough !== undefined && index <= cancelledThrough)
|
|
905
932
|
return { status: 'stale' };
|
|
906
|
-
const rows =
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
:
|
|
911
|
-
if (
|
|
912
|
-
return { status: '
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
return { status: 'busy', evidence: dispatching };
|
|
916
|
-
if (dispatching !== undefined) {
|
|
917
|
-
const updated = { ...dispatching, outcome: 'unknown', at: input.at, expiresAt: undefined, diagnostic: 'interrupted dispatch' };
|
|
918
|
-
const source = rows.find((row) => row.attempt_n === dispatching.attemptN);
|
|
919
|
-
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]);
|
|
920
|
-
deliveryChanged = true;
|
|
921
|
-
return { status: 'terminal', evidence: updated };
|
|
922
|
-
}
|
|
923
|
-
const attemptN = (decoded[0]?.attemptN ?? -1) + 1;
|
|
924
|
-
const payload = input.route === undefined ? {} : { route: input.route };
|
|
925
|
-
const expiry = finite(input.at + leaseMs);
|
|
926
|
-
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)]);
|
|
927
|
-
deliveryChanged = true;
|
|
928
|
-
return { status: 'acquired', claimToken: input.claimToken, attemptN };
|
|
933
|
+
const rows = attemptRows(input.seat, index, input.kind);
|
|
934
|
+
const result = claimAttempt({ ...rows, replace(value) {
|
|
935
|
+
rows.replace({ ...value, data: { ...value.data, diagnostic: 'interrupted dispatch' } });
|
|
936
|
+
} }, { purpose: input.kind === 'wake' ? 'dispatch' : 'presentation', token: input.claimToken,
|
|
937
|
+
at: input.at, leaseMs, data: input.route === undefined ? {} : { route: input.route } });
|
|
938
|
+
if (result.status === 'acquired')
|
|
939
|
+
return { status: 'acquired', claimToken: result.token, attemptN: result.ordinal };
|
|
940
|
+
const evidence = result.interrupted ? { ...result.evidence, data: { ...result.evidence.data, diagnostic: 'interrupted dispatch' } } : result.evidence;
|
|
941
|
+
return { status: result.status, evidence: rows.evidence(evidence) };
|
|
929
942
|
},
|
|
930
943
|
prepareDelivery(input) {
|
|
931
944
|
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)]);
|
|
932
945
|
if (row === undefined || row.outcome !== 'dispatching')
|
|
933
946
|
return false;
|
|
934
|
-
const correlation = nativeCorrelation(input.correlation);
|
|
935
|
-
const payload = object(json(row.payload_json));
|
|
947
|
+
const previous = attempt(row), correlation = nativeCorrelation(input.correlation), payload = previous.data;
|
|
936
948
|
if (stableJson(payload.correlation ?? null) === stableJson(correlation))
|
|
937
949
|
return true;
|
|
938
|
-
|
|
939
|
-
if (count)
|
|
940
|
-
deliveryChanged = true;
|
|
941
|
-
return count > 0;
|
|
950
|
+
return prepareAttempt(attemptRows(input.seat, integer(row.act_index), row.kind, previous), { token: input.claimToken, data: { ...payload, correlation } });
|
|
942
951
|
},
|
|
943
952
|
completeDelivery(input) {
|
|
944
953
|
const index = activityIndex(input.activity);
|
|
@@ -948,16 +957,9 @@ export function createSqliteDriver(db) {
|
|
|
948
957
|
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)]);
|
|
949
958
|
if (row === undefined)
|
|
950
959
|
return false;
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
return false;
|
|
955
|
-
const previous = object(json(row.payload_json));
|
|
956
|
-
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 }) };
|
|
957
|
-
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)]);
|
|
958
|
-
if (count)
|
|
959
|
-
deliveryChanged = true;
|
|
960
|
-
return count > 0;
|
|
960
|
+
const previous = attempt(row);
|
|
961
|
+
const payload = { ...previous.data, ...(input.signature === undefined ? {} : { signature: input.signature }), ...(input.message === undefined ? {} : { message: input.message }), ...(input.diagnostic === undefined ? {} : { diagnostic: input.diagnostic }), ...(input.correlation === undefined ? {} : { correlation: input.correlation }) };
|
|
962
|
+
return settleAttempt(attemptRows(input.seat, index, row.kind, previous), { token: input.claimToken, outcome: input.outcome, at: input.at, data: payload });
|
|
961
963
|
},
|
|
962
964
|
observeNativeDelivery(input) {
|
|
963
965
|
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)]);
|
|
@@ -1071,6 +1073,12 @@ function inspectRows(db, inspectSeat) {
|
|
|
1071
1073
|
const rebuilt = new MemoryDatabase(':memory:');
|
|
1072
1074
|
try {
|
|
1073
1075
|
initializeSqlite(rebuilt, state, d.header().revision);
|
|
1076
|
+
// A migration may remove the activity that established canonical casing.
|
|
1077
|
+
// Retained participant spelling stays authoritative while replay checks
|
|
1078
|
+
// every derived lifecycle/count fact against surviving activities.
|
|
1079
|
+
const retainName = rebuilt.prepare('UPDATE participants SET name_json=? WHERE name_key_json=?');
|
|
1080
|
+
for (const p of d.roster())
|
|
1081
|
+
retainName.run(encode(p.name), key(p.name));
|
|
1074
1082
|
for (const table of ['participants', 'attention', 'landed_audience']) {
|
|
1075
1083
|
const original = db.prepare(`SELECT * FROM ${table} ORDER BY 1,2`).all();
|
|
1076
1084
|
const expected = rebuilt.prepare(`SELECT * FROM ${table} ORDER BY 1,2`).all();
|
package/dist/square-tools.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ interface ToolDefinition {
|
|
|
13
13
|
/** Human text identical to what the CLI prints; domain receipts stay inside the adapter. */
|
|
14
14
|
run(square: Square, args: JsonObject, control: OperationControl, identity: ToolIdentity): Promise<string>;
|
|
15
15
|
}
|
|
16
|
+
export declare function squareToolsEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
16
17
|
export declare const SQUARE_TOOL_INSTRUCTIONS = "Join once. Then express to speak, and catch to take in what reached you. History looks back without taking anything in.";
|
|
17
18
|
export declare const SQUARE_TOOLS: readonly ToolDefinition[];
|
|
18
19
|
export declare function squareToolFailure(error: unknown, signal?: AbortSignal): JsonObject;
|