@astrosheep/square 0.3.106 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activity-feed.js +1 -1
- package/dist/artifact-store.d.ts +41 -0
- package/dist/artifact-store.js +272 -0
- package/dist/artifact.d.ts +6 -12
- package/dist/artifact.js +43 -344
- package/dist/attention-presentation.d.ts +13 -7
- package/dist/attention-presentation.js +29 -68
- package/dist/boundary-presentation.d.ts +3 -16
- package/dist/boundary-presentation.js +29 -109
- package/dist/card-presentation.d.ts +1 -0
- package/dist/card-presentation.js +2 -0
- package/dist/catch-decisions.d.ts +2 -2
- package/dist/catch-decisions.js +7 -3
- package/dist/catch-selection.d.ts +8 -0
- package/dist/catch-selection.js +5 -0
- package/dist/claude-delivery.d.ts +14 -12
- package/dist/claude-delivery.js +42 -142
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +28 -140
- package/dist/claude-transport.d.ts +5 -0
- package/dist/claude-transport.js +34 -0
- package/dist/cli/context.d.ts +2 -2
- package/dist/cli/context.js +7 -10
- package/dist/cli/maintenance-commands.d.ts +0 -4
- package/dist/cli/maintenance-commands.js +2 -29
- package/dist/cli/observation-commands.d.ts +2 -0
- package/dist/cli/observation-commands.js +12 -2
- package/dist/cli/program.js +1 -1
- package/dist/cli/registry.js +4 -3
- package/dist/codex-address.d.ts +2 -2
- package/dist/codex-boundary-state.d.ts +1 -5
- package/dist/codex-boundary-state.js +0 -4
- package/dist/codex-hook.d.ts +7 -10
- package/dist/codex-hook.js +69 -33
- package/dist/codex-queue.d.ts +10 -11
- package/dist/codex-queue.js +32 -34
- package/dist/codex-watch.d.ts +2 -0
- package/dist/codex-watch.js +100 -0
- package/dist/decisions.js +3 -3
- package/dist/delivery-deferral.d.ts +1 -19
- package/dist/delivery-deferral.js +1 -29
- package/dist/delivery-frame.d.ts +7 -0
- package/dist/delivery-frame.js +11 -0
- package/dist/delivery-ledger-sqlite.d.ts +97 -0
- package/dist/delivery-ledger-sqlite.js +496 -0
- package/dist/delivery-ledger.d.ts +196 -0
- package/dist/delivery-ledger.js +268 -0
- package/dist/delivery-lifetime.d.ts +6 -0
- package/dist/delivery-lifetime.js +45 -0
- package/dist/harness-links.js +61 -7
- package/dist/harness.js +12 -17
- package/dist/help.js +3 -6
- package/dist/http-delivery.d.ts +5 -9
- package/dist/http-delivery.js +20 -79
- package/dist/inbox.d.ts +6 -57
- package/dist/inbox.js +24 -380
- package/dist/index.d.ts +7 -4
- package/dist/index.js +5 -2
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +40 -70
- package/dist/message.d.ts +7 -0
- package/dist/message.js +23 -0
- package/dist/model.d.ts +15 -91
- package/dist/model.js +2 -140
- package/dist/neutral.d.ts +72 -0
- package/dist/neutral.js +27 -0
- package/dist/notifications.d.ts +24 -26
- package/dist/notifications.js +39 -143
- package/dist/operation-control.d.ts +4 -0
- package/dist/operation-control.js +20 -0
- package/dist/operation-presentation.d.ts +1 -2
- package/dist/operation-presentation.js +1 -7
- package/dist/owner.d.ts +2 -2
- package/dist/owner.js +1 -1
- package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
- package/dist/packages/agent-delivery/src/claude-native.js +5 -0
- package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
- package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
- package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
- package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
- package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
- package/dist/packages/agent-delivery/src/codex.js +41 -4
- package/dist/packages/agent-delivery/src/index.d.ts +3 -1
- package/dist/packages/agent-delivery/src/index.js +1 -1
- package/dist/packages/agent-delivery/src/paseo.js +11 -2
- package/dist/packages/agent-delivery/src/types.d.ts +1 -0
- package/dist/paseo-delivery.d.ts +5 -5
- package/dist/paseo-delivery.js +19 -99
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/index.d.ts +5 -3
- package/dist/plugins/opencode/index.js +99 -21
- package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
- package/dist/plugins/opencode-shared/delivery.js +109 -0
- package/dist/plugins/opencode-v1/index.d.ts +28 -0
- package/dist/plugins/opencode-v1/index.js +41 -0
- package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
- package/dist/plugins/opencode-v1/runtime.js +152 -0
- package/dist/plugins/opencode-v1/transport.d.ts +13 -0
- package/dist/plugins/opencode-v1/transport.js +50 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
- package/dist/plugins/paseo/shared/card-presentation.js +7 -0
- package/dist/ports.d.ts +33 -214
- package/dist/presentation.d.ts +5 -5
- package/dist/presentation.js +1 -0
- package/dist/runtime.js +2 -2
- package/dist/search.js +1 -1
- package/dist/session-lifecycle.d.ts +8 -7
- package/dist/session-lifecycle.js +40 -42
- package/dist/sqlite-framing.d.ts +8 -0
- package/dist/sqlite-framing.js +31 -0
- package/dist/sqlite-square.d.ts +16 -26
- package/dist/sqlite-square.js +199 -262
- package/dist/sqlite-values.d.ts +21 -0
- package/dist/sqlite-values.js +78 -0
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +5 -8
- package/dist/square-core.d.ts +0 -1
- package/dist/square-domain.d.ts +12 -10
- package/dist/square-domain.js +105 -223
- package/dist/square-facade.d.ts +25 -76
- package/dist/square-location.d.ts +6 -1
- package/dist/square-location.js +1 -1
- package/dist/square-post.d.ts +53 -0
- package/dist/square-post.js +4 -0
- package/dist/square-posting.d.ts +13 -0
- package/dist/square-posting.js +174 -0
- package/dist/square-producer-sqlite.d.ts +11 -0
- package/dist/square-producer-sqlite.js +210 -0
- package/dist/square-receive.d.ts +2 -3
- package/dist/square-sqlite.d.ts +33 -42
- package/dist/square-sqlite.js +236 -768
- package/dist/square-tools.d.ts +8 -0
- package/dist/square-tools.js +39 -0
- package/dist/square-values.d.ts +27 -0
- package/dist/square-values.js +90 -0
- package/dist/views.d.ts +6 -6
- package/dist/views.js +14 -9
- package/dist/wake-sink.d.ts +4 -11
- package/dist/wake-sink.js +8 -32
- package/dist/wake-transports.d.ts +4 -0
- package/dist/wake-transports.js +11 -0
- package/examples/symmetric-domains.mjs +29 -249
- package/guides/change-waits.md +13 -53
- package/guides/claude-native.md +19 -172
- package/guides/codex-delivery.md +55 -78
- package/guides/delivery-primitives.md +48 -207
- package/guides/operation-cancellation.md +2 -5
- package/guides/participant-ownership.md +25 -89
- package/guides/pi-native-delivery.md +19 -56
- package/guides/pi-tools.md +6 -8
- package/guides/row-runtime-delivery-evidence.md +2 -0
- package/guides/row-storage-design.md +19 -44
- package/guides/square-api.md +113 -538
- package/package.json +8 -8
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/hooks.json +1 -1
- package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/hooks/hooks.json +11 -23
- package/plugins/opencode/index.ts +91 -23
- package/plugins/opencode-shared/delivery.ts +91 -0
- package/plugins/opencode-v1/index.ts +36 -0
- package/plugins/opencode-v1/runtime.ts +134 -0
- package/plugins/opencode-v1/transport.ts +41 -0
- package/plugins/paseo/OVERVIEW.md +5 -5
- package/plugins/paseo/README.md +7 -6
- package/plugins/paseo/client/markdown.tsx +5 -5
- package/plugins/paseo/client/square-message.tsx +28 -24
- package/plugins/paseo/client/transform.ts +8 -5
- package/plugins/paseo/shared/card-presentation.ts +16 -0
- package/plugins/paseo/shared/message.ts +6 -11
- package/plugins/pi/square-pi.js +167 -451
- package/dist/channel.d.ts +0 -74
- package/dist/channel.js +0 -92
- package/dist/claude-delivery-status.d.ts +0 -13
- package/dist/claude-delivery-status.js +0 -46
- package/dist/cmd/notify-once.d.ts +0 -2
- package/dist/cmd/notify-once.js +0 -29
- package/dist/delivery-attempts.d.ts +0 -52
- package/dist/delivery-attempts.js +0 -73
- package/dist/delivery-effects.d.ts +0 -139
- package/dist/delivery-effects.js +0 -163
- package/dist/delivery-health.d.ts +0 -51
- package/dist/delivery-health.js +0 -173
- package/dist/delivery-operations.d.ts +0 -9
- package/dist/delivery-operations.js +0 -168
- package/dist/delivery-primitives.d.ts +0 -5
- package/dist/delivery-primitives.js +0 -3
- package/dist/delivery.d.ts +0 -45
- package/dist/delivery.js +0 -106
- package/dist/plugins/opencode/delivery.d.ts +0 -19
- package/dist/plugins/opencode/delivery.js +0 -238
- package/dist/presentation-operations.d.ts +0 -18
- package/dist/presentation-operations.js +0 -86
- package/dist/square-index.d.ts +0 -16
- package/dist/square-index.js +0 -106
- package/dist/square-legacy-framing.d.ts +0 -11
- package/dist/square-legacy-framing.js +0 -64
- package/dist/square-migration-host-ledger.d.ts +0 -26
- package/dist/square-migration-host-ledger.js +0 -169
- package/dist/square-migration.d.ts +0 -30
- package/dist/square-migration.js +0 -599
- package/guides/migration.md +0 -156
- package/plugins/opencode/delivery.ts +0 -211
package/dist/square-sqlite.js
CHANGED
|
@@ -1,161 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SquareError, nameKey, isWakeRouteKind, validateOwner, validateActivityMetadata, validateActivityDelivery, activityMetadataHasContent } from './model.js';
|
|
3
|
-
import { validateMetadataMatch, matchingDeferrals, validateDeferralId } from './delivery-deferral.js';
|
|
1
|
+
import { validateActivityMetadata, activityMetadataHasContent } from './model.js';
|
|
4
2
|
import { deriveLanding, landingFacts } from './square-domain.js';
|
|
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';
|
|
8
3
|
import { formatActivityId } from './square-core.js';
|
|
9
4
|
import { compileSearchPattern } from './search.js';
|
|
5
|
+
import { validateEndpointGeneration as endpointGeneration } from './message.js';
|
|
6
|
+
import { createProducerDriver, producerSchemaSql, initializeProducerSqlite, resetProducerSqlite } from './square-producer-sqlite.js';
|
|
7
|
+
import { readStorageRevision, initializeStorageMeta, STORAGE_META_SQL, validateSchema } from './sqlite-framing.js';
|
|
8
|
+
import { encode, key, invalidArtifact, requireFact, integer, finite, string, json, object, shape, strings, decoded, immutable, pageRows, pageLimit } from './sqlite-values.js';
|
|
9
|
+
export { invalidArtifact } from './sqlite-values.js';
|
|
10
|
+
export { validateEndpointGeneration as endpointGeneration } from './message.js';
|
|
11
|
+
export const APPLICATION_ID = 0x53515245;
|
|
12
|
+
export const CURRENT_USER_VERSION = 12;
|
|
10
13
|
export function emptyRuntimeState(nextActIndex = 0) {
|
|
11
14
|
return { nextActIndex, observations: {}, leases: {} };
|
|
12
15
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export const
|
|
16
|
-
export const V3_USER_VERSION = 3;
|
|
17
|
-
export const V4_USER_VERSION = 4;
|
|
18
|
-
export const V5_USER_VERSION = 5;
|
|
19
|
-
export const V6_USER_VERSION = 6;
|
|
20
|
-
export const V7_USER_VERSION = 7;
|
|
21
|
-
export const CURRENT_USER_VERSION = 8;
|
|
22
|
-
export const V1_SNAPSHOT_TABLE = 'square_snapshot';
|
|
23
|
-
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)`;
|
|
24
|
-
const V1_SNAPSHOT_COLUMNS = [
|
|
25
|
-
{ cid: 0, name: 'id', type: 'INTEGER', notnull: 0, dflt_value: null, pk: 1, hidden: 0 },
|
|
26
|
-
{ cid: 1, name: 'revision', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 0, hidden: 0 },
|
|
27
|
-
{ cid: 2, name: 'state', type: 'TEXT', notnull: 1, dflt_value: null, pk: 0, hidden: 0 },
|
|
28
|
-
];
|
|
29
|
-
/**
|
|
30
|
-
* Read-only legacy framing: application id, format version, journal mode and the
|
|
31
|
-
* exact owned snapshot schema. It decodes no snapshot payload, writes no pragma
|
|
32
|
-
* and runs no DDL, so ordinary open can establish ownership before requiring
|
|
33
|
-
* migration. Anything else is unrelated or unsupported, not repaired.
|
|
34
|
-
*/
|
|
35
|
-
export function validateLegacyFraming(database, applicationId, location = '') {
|
|
36
|
-
const identity = database.prepare('SELECT application_id, user_version FROM pragma_application_id, pragma_user_version').get();
|
|
37
|
-
if (identity.application_id !== applicationId)
|
|
38
|
-
throw invalidArtifact('unrelated SQLite database.');
|
|
39
|
-
if (identity.user_version !== V1_USER_VERSION)
|
|
40
|
-
throw invalidArtifact('unsupported SQLite format version.');
|
|
41
|
-
const journalMode = database.prepare('PRAGMA journal_mode').get().journal_mode;
|
|
42
|
-
if (typeof journalMode !== 'string' || journalMode !== (location === ':memory:' ? 'memory' : 'delete'))
|
|
43
|
-
throw invalidArtifact('unsupported journal mode.');
|
|
44
|
-
const objects = database.prepare("SELECT type, name, tbl_name, sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' ORDER BY type, name")
|
|
45
|
-
.all();
|
|
46
|
-
if (objects.length !== 1)
|
|
47
|
-
throw invalidArtifact('unsupported snapshot schema.');
|
|
48
|
-
const [object] = objects;
|
|
49
|
-
if (object.type !== 'table' || object.name !== V1_SNAPSHOT_TABLE || object.tbl_name !== V1_SNAPSHOT_TABLE
|
|
50
|
-
|| typeof object.sql !== 'string' || normalizedLegacySql(object.sql) !== normalizedLegacySql(V1_SNAPSHOT_DDL))
|
|
51
|
-
throw invalidArtifact('unsupported snapshot schema.');
|
|
52
|
-
const columns = database.prepare('SELECT cid, name, type, "notnull", dflt_value, pk, hidden FROM pragma_table_xinfo(?) ORDER BY cid')
|
|
53
|
-
.all(V1_SNAPSHOT_TABLE);
|
|
54
|
-
if (columns.length !== V1_SNAPSHOT_COLUMNS.length || !columns.every((column, index) => {
|
|
55
|
-
const expected = V1_SNAPSHOT_COLUMNS[index];
|
|
56
|
-
return column.cid === expected.cid && column.name === expected.name && column.type === expected.type
|
|
57
|
-
&& column.notnull === expected.notnull && column.dflt_value === expected.dflt_value
|
|
58
|
-
&& column.pk === expected.pk && column.hidden === expected.hidden;
|
|
59
|
-
}))
|
|
60
|
-
throw invalidArtifact('unsupported snapshot schema.');
|
|
61
|
-
return { applicationId, userVersion: V1_USER_VERSION, journalMode };
|
|
62
|
-
}
|
|
63
|
-
function normalizedLegacySql(sql) {
|
|
64
|
-
return sql.trim().replace(/\s+/g, ' ').replace(/\s*(>=|[(),=])\s*/g, '$1');
|
|
65
|
-
}
|
|
66
|
-
const CURRENT_ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','read','listen','ignore','unlisten')";
|
|
67
|
-
export const ACTIVITIES_TABLE_SQL = `CREATE TABLE activities (
|
|
68
|
-
act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
69
|
-
at REAL NOT NULL,
|
|
70
|
-
kind TEXT NOT NULL CHECK (${CURRENT_ACTIVITY_KIND_CHECK}),
|
|
71
|
-
actor_json TEXT COLLATE BINARY NOT NULL,
|
|
72
|
-
actor_key_json TEXT COLLATE BINARY NOT NULL,
|
|
73
|
-
reach TEXT CHECK (reach = 'bell'),
|
|
74
|
-
reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),
|
|
75
|
-
payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
|
|
76
|
-
CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))
|
|
77
|
-
);`;
|
|
78
|
-
export const ACTIVITY_INDEXES_SQL = [
|
|
79
|
-
'CREATE INDEX activity_kind_index ON activities(kind, act_index)',
|
|
80
|
-
'CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index)',
|
|
81
|
-
"CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say'",
|
|
82
|
-
"CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say'",
|
|
83
|
-
"CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell'",
|
|
84
|
-
];
|
|
85
|
-
export const DELIVERY_DEFERRALS_SQL = `CREATE TABLE delivery_deferrals (
|
|
86
|
-
participant_key_json TEXT COLLATE BINARY NOT NULL,
|
|
87
|
-
participant_json TEXT NOT NULL,
|
|
88
|
-
generation_json TEXT NOT NULL,
|
|
89
|
-
deferral_id_json TEXT NOT NULL,
|
|
90
|
-
match_json TEXT NOT NULL CHECK (json_valid(match_json)),
|
|
91
|
-
expires_at REAL NOT NULL,
|
|
92
|
-
PRIMARY KEY (participant_key_json,generation_json,deferral_id_json)
|
|
93
|
-
) WITHOUT ROWID;
|
|
94
|
-
CREATE INDEX delivery_deferral_expiry ON delivery_deferrals(participant_key_json,generation_json,expires_at);`;
|
|
95
|
-
const ATTACHMENT_COLUMN_SQL = "attachment TEXT NOT NULL DEFAULT 'attached' CHECK (attachment IN ('attached','detached'))";
|
|
96
|
-
export const SCHEMA_SQL = `CREATE TABLE square_meta (
|
|
16
|
+
const ACTIVITY_KIND_CHECK = "kind IN ('join','done','say','read','listen','ignore','unlisten')";
|
|
17
|
+
/** Square owns activity, audience, participant, and read-state facts. */
|
|
18
|
+
export const AGORA_SCHEMA_SQL = `CREATE TABLE square_meta (
|
|
97
19
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
98
|
-
revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991),
|
|
99
|
-
delivery_revision INTEGER NOT NULL CHECK (delivery_revision BETWEEN 0 AND 9007199254740991),
|
|
100
20
|
next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),
|
|
101
21
|
hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),
|
|
102
22
|
throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),
|
|
103
23
|
preamble_json TEXT NOT NULL CHECK (json_valid(preamble_json)),
|
|
104
24
|
warmup_json TEXT NOT NULL CHECK (json_valid(warmup_json))
|
|
105
25
|
);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
CREATE TABLE receipts (
|
|
110
|
-
owner_json TEXT COLLATE BINARY NOT NULL,
|
|
111
|
-
act_index INTEGER NOT NULL CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
112
|
-
at REAL NOT NULL,
|
|
113
|
-
PRIMARY KEY (owner_json, act_index)
|
|
114
|
-
) WITHOUT ROWID;
|
|
115
|
-
CREATE INDEX receipt_owner_time ON receipts(owner_json, at);
|
|
116
|
-
CREATE TABLE catch_leases (
|
|
117
|
-
owner_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
|
|
118
|
-
generation_json TEXT NOT NULL DEFAULT '""',
|
|
119
|
-
lease_id_json TEXT NOT NULL,
|
|
120
|
-
heartbeat_at REAL NOT NULL,
|
|
121
|
-
expires_at REAL NOT NULL CHECK (expires_at >= heartbeat_at),
|
|
122
|
-
filter_json TEXT CHECK (filter_json IS NULL OR json_valid(filter_json))
|
|
123
|
-
) WITHOUT ROWID;
|
|
124
|
-
|
|
125
|
-
CREATE TABLE seats (
|
|
126
|
-
participant_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
|
|
127
|
-
participant_json TEXT NOT NULL,
|
|
128
|
-
owner_json TEXT NOT NULL,
|
|
129
|
-
generation_json TEXT NOT NULL,
|
|
130
|
-
wake_address_json TEXT CHECK (wake_address_json IS NULL OR json_valid(wake_address_json)),
|
|
131
|
-
${ATTACHMENT_COLUMN_SQL}
|
|
132
|
-
) WITHOUT ROWID;
|
|
133
|
-
CREATE INDEX seat_owner ON seats(owner_json);
|
|
134
|
-
CREATE TABLE delivery_evidence (
|
|
135
|
-
participant_key_json TEXT COLLATE BINARY NOT NULL,
|
|
136
|
-
participant_json TEXT NOT NULL,
|
|
137
|
-
generation_json TEXT NOT NULL,
|
|
138
|
-
act_index INTEGER NOT NULL CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
139
|
-
kind TEXT NOT NULL CHECK (kind IN ('wake','presentation')),
|
|
140
|
-
attempt_n INTEGER NOT NULL CHECK (attempt_n >= 0),
|
|
141
|
-
outcome TEXT NOT NULL CHECK (outcome IN ('dispatching','accepted','failed','unknown','released','presented','clipped')),
|
|
26
|
+
CREATE TABLE activities (
|
|
27
|
+
act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),
|
|
142
28
|
at REAL NOT NULL,
|
|
143
|
-
|
|
144
|
-
|
|
29
|
+
kind TEXT NOT NULL CHECK (${ACTIVITY_KIND_CHECK}),
|
|
30
|
+
actor_json TEXT COLLATE BINARY NOT NULL,
|
|
31
|
+
actor_key_json TEXT COLLATE BINARY NOT NULL,
|
|
32
|
+
reach TEXT CHECK (reach = 'bell'),
|
|
33
|
+
reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),
|
|
145
34
|
payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),
|
|
146
|
-
|
|
147
|
-
)
|
|
148
|
-
CREATE
|
|
149
|
-
CREATE INDEX
|
|
150
|
-
CREATE
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
through_index INTEGER NOT NULL CHECK (through_index BETWEEN 0 AND 9007199254740991),
|
|
154
|
-
PRIMARY KEY (participant_key_json,generation_json)
|
|
155
|
-
) WITHOUT ROWID;
|
|
156
|
-
|
|
157
|
-
${DELIVERY_DEFERRALS_SQL}
|
|
158
|
-
|
|
35
|
+
CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))
|
|
36
|
+
);
|
|
37
|
+
CREATE INDEX activity_kind_index ON activities(kind, act_index);
|
|
38
|
+
CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index);
|
|
39
|
+
CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say';
|
|
40
|
+
CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say';
|
|
41
|
+
CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell';
|
|
159
42
|
CREATE TABLE participants (
|
|
160
43
|
name_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,
|
|
161
44
|
name_json TEXT NOT NULL,
|
|
@@ -180,74 +63,49 @@ CREATE TABLE landed_audience (
|
|
|
180
63
|
act_index INTEGER NOT NULL,
|
|
181
64
|
recipient_key_json TEXT COLLATE BINARY NOT NULL,
|
|
182
65
|
recipient_order INTEGER NOT NULL CHECK (recipient_order >= 0),
|
|
183
|
-
|
|
66
|
+
reason TEXT NOT NULL CHECK (reason IN ('mention','reply','attention','bell')),
|
|
67
|
+
owner_json TEXT,
|
|
68
|
+
generation_json TEXT,
|
|
184
69
|
PRIMARY KEY (act_index, recipient_key_json),
|
|
185
70
|
UNIQUE (act_index, recipient_order)
|
|
186
71
|
) WITHOUT ROWID;
|
|
187
|
-
CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
function
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return value;
|
|
217
|
-
}
|
|
218
|
-
function json(value) {
|
|
219
|
-
try {
|
|
220
|
-
return JSON.parse(string(value));
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
throw invalidArtifact('row contains invalid JSON.');
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
function object(value) {
|
|
227
|
-
requireFact(typeof value === 'object' && value !== null && !Array.isArray(value));
|
|
228
|
-
return value;
|
|
229
|
-
}
|
|
230
|
-
function shape(value, required, optional = []) {
|
|
231
|
-
const result = object(value);
|
|
232
|
-
requireFact(required.every(k => Object.hasOwn(result, k)) && Object.keys(result).every(k => required.includes(k) || optional.includes(k)));
|
|
233
|
-
return result;
|
|
234
|
-
}
|
|
235
|
-
function strings(value) {
|
|
236
|
-
requireFact(Array.isArray(value) && value.every(v => typeof v === 'string'));
|
|
237
|
-
return value;
|
|
238
|
-
}
|
|
239
|
-
function decoded(value, nonempty = false) {
|
|
240
|
-
const result = string(json(value), nonempty);
|
|
241
|
-
requireFact(encode(result) === value, 'string encoding is malformed.');
|
|
242
|
-
return result;
|
|
72
|
+
CREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);
|
|
73
|
+
CREATE TABLE participant_seats (
|
|
74
|
+
participant_key_json TEXT PRIMARY KEY NOT NULL, participant_json TEXT NOT NULL,
|
|
75
|
+
owner_json TEXT NOT NULL, generation_json TEXT NOT NULL,
|
|
76
|
+
wake_address_json TEXT, attachment TEXT NOT NULL CHECK (attachment IN ('attached','detached'))
|
|
77
|
+
) WITHOUT ROWID;
|
|
78
|
+
CREATE INDEX participant_seat_owner ON participant_seats(owner_json);
|
|
79
|
+
CREATE TABLE caught_activity (
|
|
80
|
+
recipient_key_json TEXT NOT NULL, act_index INTEGER NOT NULL, at REAL NOT NULL,
|
|
81
|
+
PRIMARY KEY (recipient_key_json,act_index)
|
|
82
|
+
) WITHOUT ROWID;
|
|
83
|
+
CREATE TABLE catch_leases (
|
|
84
|
+
recipient_key_json TEXT PRIMARY KEY NOT NULL, generation_json TEXT NOT NULL,
|
|
85
|
+
lease_json TEXT NOT NULL CHECK (json_valid(lease_json))
|
|
86
|
+
) WITHOUT ROWID;`;
|
|
87
|
+
export function initializeAgoraSqlite(db, configuration = {}) {
|
|
88
|
+
db.exec(AGORA_SCHEMA_SQL);
|
|
89
|
+
db.prepare('INSERT INTO square_meta(id,next_act_index,hard_cap,throttle_per_minute,preamble_json,warmup_json) VALUES(1,0,?,?,?,?)')
|
|
90
|
+
.run(configuration.hardCap ?? null, configuration.throttlePerMinute ?? null, JSON.stringify(configuration.preamble ?? []), JSON.stringify(configuration.warmup ?? []));
|
|
91
|
+
}
|
|
92
|
+
export function resetAgoraSqlite(db) {
|
|
93
|
+
for (const table of ['catch_leases', 'caught_activity', 'participant_seats', 'landed_audience', 'attention', 'participants', 'activities', 'square_meta'])
|
|
94
|
+
db.exec(`DROP TABLE ${table}`);
|
|
95
|
+
}
|
|
96
|
+
function validateSquareFraming(db, location = '') {
|
|
97
|
+
const identity = db.prepare('SELECT application_id, user_version FROM pragma_application_id, pragma_user_version').get();
|
|
98
|
+
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
99
|
+
requireFact(identity.user_version === CURRENT_USER_VERSION, 'unsupported SQLite format version; this build opens only fresh format-12 squares.');
|
|
100
|
+
validateSchema(db, [STORAGE_META_SQL, producerSchemaSql, AGORA_SCHEMA_SQL].join('\n'), location);
|
|
243
101
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
102
|
+
/** Full Square framing and reset, including producer state. */
|
|
103
|
+
export function squareStoreFormat(resetStorage) {
|
|
104
|
+
return {
|
|
105
|
+
applicationId: APPLICATION_ID, userVersion: CURRENT_USER_VERSION, observerKey: 'square',
|
|
106
|
+
validate: (db, location) => validateSquareFraming(db, location),
|
|
107
|
+
reset(db) { resetProducerSqlite(db); resetAgoraSqlite(db); resetStorage(db); },
|
|
108
|
+
};
|
|
251
109
|
}
|
|
252
110
|
const kinds = ['join', 'done', 'say', 'read', 'listen', 'ignore', 'unlisten'];
|
|
253
111
|
function head(row) {
|
|
@@ -259,9 +117,10 @@ function head(row) {
|
|
|
259
117
|
if (row.reply_index !== null)
|
|
260
118
|
requireFact(row.kind === 'say' && integer(row.reply_index) < index);
|
|
261
119
|
return immutable({
|
|
262
|
-
index, at, kind: row.kind, actor, ...(row.reach === null ? {} : { reach: 'bell' }), ...(row.reply_index === null ? {} : { reply: row.reply_index })
|
|
120
|
+
index, at, kind: row.kind, actor, ...(row.reach === null ? {} : { reach: 'bell' }), ...(row.reply_index === null ? {} : { reply: row.reply_index }),
|
|
263
121
|
});
|
|
264
122
|
}
|
|
123
|
+
/** Validate the complete persisted activity. */
|
|
265
124
|
export function validateStoredActivity(value) {
|
|
266
125
|
const a = object(value);
|
|
267
126
|
integer(a.index);
|
|
@@ -271,20 +130,10 @@ export function validateStoredActivity(value) {
|
|
|
271
130
|
string(a.actor, true);
|
|
272
131
|
switch (a.kind) {
|
|
273
132
|
case 'say':
|
|
274
|
-
shape(a, [...common], ['
|
|
275
|
-
if (
|
|
276
|
-
requireFact(a.delivery !== undefined, 'say delivery is malformed.');
|
|
277
|
-
try {
|
|
278
|
-
validateActivityDelivery(a.delivery);
|
|
279
|
-
}
|
|
280
|
-
catch {
|
|
281
|
-
throw invalidArtifact('say delivery is malformed.');
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
if (Object.hasOwn(a, 'body'))
|
|
133
|
+
shape(a, [...common], ['mentions', 'reach', 'reply', 'body', 'metadata']);
|
|
134
|
+
if (a.body !== undefined)
|
|
285
135
|
string(a.body);
|
|
286
|
-
if (
|
|
287
|
-
requireFact(a.metadata !== undefined, 'say metadata is malformed.');
|
|
136
|
+
if (a.metadata !== undefined) {
|
|
288
137
|
try {
|
|
289
138
|
validateActivityMetadata(a.metadata);
|
|
290
139
|
}
|
|
@@ -292,7 +141,7 @@ export function validateStoredActivity(value) {
|
|
|
292
141
|
throw invalidArtifact('say metadata is malformed.');
|
|
293
142
|
}
|
|
294
143
|
}
|
|
295
|
-
requireFact((
|
|
144
|
+
requireFact((typeof a.body === 'string' && a.body.trim() !== '') || activityMetadataHasContent(a.metadata), 'say must contain a non-empty body or non-empty metadata.');
|
|
296
145
|
if (Object.hasOwn(a, 'mentions'))
|
|
297
146
|
strings(a.mentions);
|
|
298
147
|
if (Object.hasOwn(a, 'reach'))
|
|
@@ -319,13 +168,25 @@ export function validateStoredActivity(value) {
|
|
|
319
168
|
}
|
|
320
169
|
return value;
|
|
321
170
|
}
|
|
171
|
+
/** Validate complete activity content. */
|
|
172
|
+
export function validateHydratedActivity(value) {
|
|
173
|
+
const a = object(value);
|
|
174
|
+
if (a.kind !== 'say')
|
|
175
|
+
return validateStoredActivity(a);
|
|
176
|
+
return validateStoredActivity(a);
|
|
177
|
+
}
|
|
178
|
+
/** Decode an activity with its complete payload. */
|
|
322
179
|
function activity(row) {
|
|
323
180
|
const h = head(row), payload = object(json(row.payload_json));
|
|
324
181
|
// Payload cannot shadow indexed structural columns.
|
|
325
|
-
const allowed = h.kind === 'say' ? ['
|
|
182
|
+
const allowed = h.kind === 'say' ? ['mentions', 'body', 'metadata'] : h.kind === 'done' ? ['body'] : h.kind === 'read' ? ['through'] : h.kind === 'listen' || h.kind === 'ignore' || h.kind === 'unlisten' ? ['target'] : [];
|
|
326
183
|
requireFact(Object.keys(payload).every(k => allowed.includes(k)));
|
|
327
184
|
return immutable(validateStoredActivity({ ...h, ...payload }));
|
|
328
185
|
}
|
|
186
|
+
/** Activity and content are one stored fact. */
|
|
187
|
+
function activityWithContent(row) {
|
|
188
|
+
return activity(row);
|
|
189
|
+
}
|
|
329
190
|
function participant(row) {
|
|
330
191
|
const name = decoded(row.name_json, true);
|
|
331
192
|
requireFact(row.name_key_json === key(name) && [0, 1].includes(row.joined) && [0, 1].includes(row.done) && !(row.joined && row.done));
|
|
@@ -357,135 +218,14 @@ export function validateWatchLease(value) {
|
|
|
357
218
|
}
|
|
358
219
|
return value;
|
|
359
220
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return '{' + Object.keys(value).sort().map(k => encode(k) + ':' + stableJson(value[k])).join(',') + '}';
|
|
369
|
-
}
|
|
370
|
-
function activityId(value) {
|
|
371
|
-
const parsed = typeof value === 'string' ? /^act\/(0|[1-9]\d*)$/.exec(value) : null;
|
|
372
|
-
requireFact(parsed !== null, 'activity generation is malformed.');
|
|
373
|
-
const index = Number(parsed[1]);
|
|
374
|
-
requireFact(Number.isSafeInteger(index));
|
|
375
|
-
return value;
|
|
376
|
-
}
|
|
377
|
-
function activityIndex(value) {
|
|
378
|
-
const parsed = activityId(value);
|
|
379
|
-
return integer(Number(parsed.slice(4)));
|
|
380
|
-
}
|
|
381
|
-
function wakeAddress(value) {
|
|
382
|
-
const address = shape(value, ['kind', 'address']);
|
|
383
|
-
requireFact(isWakeRouteKind(address.kind));
|
|
384
|
-
const entries = object(address.address);
|
|
385
|
-
requireFact(Object.values(entries).every((item) => typeof item === 'string'));
|
|
386
|
-
stableJson(entries);
|
|
387
|
-
return immutable({ kind: address.kind, address: entries });
|
|
388
|
-
}
|
|
389
|
-
function ownerFrom(row) {
|
|
390
|
-
const owner = decoded(row.owner_json, true);
|
|
391
|
-
validateOwner(owner);
|
|
392
|
-
return owner;
|
|
393
|
-
}
|
|
394
|
-
function seat(row) {
|
|
395
|
-
const participant = decoded(row.participant_json, true);
|
|
396
|
-
requireFact(row.participant_key_json === key(participant));
|
|
397
|
-
const generation = activityId(decoded(row.generation_json, true));
|
|
398
|
-
const parsedWake = row.wake_address_json === null ? undefined : wakeAddress(json(row.wake_address_json));
|
|
399
|
-
requireFact(row.attachment === 'attached' || row.attachment === 'detached', 'seat attachment is malformed.');
|
|
400
|
-
requireFact(row.attachment !== 'detached' || parsedWake === undefined, 'a detached seat has a wake address.');
|
|
401
|
-
return immutable({ participant, owner: ownerFrom(row), generation, attachment: row.attachment, ...(parsedWake === undefined ? {} : { wakeAddress: parsedWake }) });
|
|
402
|
-
}
|
|
403
|
-
function nativeCorrelation(value) {
|
|
404
|
-
const item = shape(value, ['harness'], ['token', 'messageId', 'payloadHash']);
|
|
405
|
-
requireFact(['claude', 'opencode', 'pi'].includes(string(item.harness)));
|
|
406
|
-
for (const key of ['token', 'messageId', 'payloadHash'])
|
|
407
|
-
if (Object.hasOwn(item, key))
|
|
408
|
-
string(item[key], true);
|
|
409
|
-
return immutable({ harness: item.harness,
|
|
410
|
-
...(item.token === undefined ? {} : { token: item.token }),
|
|
411
|
-
...(item.messageId === undefined ? {} : { messageId: item.messageId }),
|
|
412
|
-
...(item.payloadHash === undefined ? {} : { payloadHash: item.payloadHash }) });
|
|
413
|
-
}
|
|
414
|
-
function deliveryEvidence(row, payload = object(json(row.payload_json))) {
|
|
415
|
-
const participant = decoded(row.participant_json, true);
|
|
416
|
-
requireFact(row.participant_key_json === key(participant));
|
|
417
|
-
const generation = activityId(decoded(row.generation_json, true));
|
|
418
|
-
const activity = activityId(`act/${integer(row.act_index)}`);
|
|
419
|
-
requireFact(['wake', 'presentation'].includes(string(row.kind)) && ['dispatching', 'accepted', 'failed', 'unknown', 'released', 'presented', 'clipped'].includes(string(row.outcome)));
|
|
420
|
-
requireFact(Number.isSafeInteger(row.attempt_n) && row.attempt_n >= 0);
|
|
421
|
-
const nativeObservations = payload.nativeObservations === undefined ? undefined : (() => {
|
|
422
|
-
requireFact(Array.isArray(payload.nativeObservations));
|
|
423
|
-
return payload.nativeObservations.map((value) => {
|
|
424
|
-
const item = shape(value, ['eventId', 'messageId', 'state', 'observedAt'], ['reason']);
|
|
425
|
-
requireFact(['held', 'released', 'dropped'].includes(string(item.state)));
|
|
426
|
-
return {
|
|
427
|
-
eventId: string(item.eventId, true), messageId: string(item.messageId, true),
|
|
428
|
-
state: item.state,
|
|
429
|
-
...(Object.hasOwn(item, 'reason') ? { reason: string(item.reason) } : {}), observedAt: finite(item.observedAt),
|
|
430
|
-
};
|
|
431
|
-
});
|
|
432
|
-
})();
|
|
433
|
-
return immutable({
|
|
434
|
-
participant, generation, activity, kind: row.kind,
|
|
435
|
-
outcome: row.outcome, attemptN: integer(row.attempt_n), at: finite(row.at),
|
|
436
|
-
...(row.expires_at === null ? {} : { expiresAt: finite(row.expires_at) }),
|
|
437
|
-
...(row.claim_token_json === null ? {} : { claimToken: decoded(row.claim_token_json, true) }),
|
|
438
|
-
...(payload.route === undefined ? {} : { route: wakeAddress(payload.route) }),
|
|
439
|
-
...(payload.signature === undefined ? {} : { signature: string(payload.signature) }),
|
|
440
|
-
...(payload.message === undefined ? {} : { message: string(payload.message) }),
|
|
441
|
-
...(payload.diagnostic === undefined ? {} : { diagnostic: payload.diagnostic }),
|
|
442
|
-
...(payload.correlation === undefined ? {} : { correlation: nativeCorrelation(payload.correlation) }),
|
|
443
|
-
...(nativeObservations === undefined ? {} : { nativeObservations }),
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
function deliveryDeferral(row) {
|
|
447
|
-
const participant = decoded(row.participant_json, true);
|
|
448
|
-
requireFact(row.participant_key_json === key(participant));
|
|
449
|
-
const generation = activityId(decoded(row.generation_json, true));
|
|
450
|
-
const id = validateDeferralId(decoded(row.deferral_id_json, true));
|
|
451
|
-
const match = validateMetadataMatch(json(row.match_json));
|
|
452
|
-
requireFact(encode(match) === row.match_json, 'delivery deferral match encoding is malformed.');
|
|
453
|
-
return immutable({ participant, generation, id, match, expiresAt: finite(row.expires_at) });
|
|
454
|
-
}
|
|
455
|
-
function normalizedSql(sql) { return sql.trim().replace(/;$/, '').replace(/\s+/g, ' ').replace(/\s*([(),=])\s*/g, '$1'); }
|
|
456
|
-
function validateOwnedSchema(db, expected, location) {
|
|
457
|
-
const actual = db.prepare("SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' AND sql IS NOT NULL").all().map(r => normalizedSql(string(r.sql))).sort();
|
|
458
|
-
requireFact(actual.length === expected.length && actual.every((sql, i) => sql === expected[i]), 'unsupported row schema.');
|
|
459
|
-
requireFact(db.prepare('PRAGMA journal_mode').get().journal_mode === (location === ':memory:' ? 'memory' : 'delete'), 'unsupported journal mode.');
|
|
460
|
-
}
|
|
461
|
-
export function validateRowFraming(db, version, location = '') {
|
|
462
|
-
if (version === V2_USER_VERSION || version === V3_USER_VERSION) {
|
|
463
|
-
const issue = legacyRowFramingIssue(db, APPLICATION_ID, version, location, FORMAT4_SCHEMA_SQL);
|
|
464
|
-
if (issue !== undefined)
|
|
465
|
-
throw invalidArtifact(issue);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
469
|
-
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
470
|
-
requireFact(identity.user_version === version, 'unsupported SQLite format version.');
|
|
471
|
-
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);
|
|
472
|
-
}
|
|
473
|
-
export function validateSqliteHeader(db, location = '') {
|
|
474
|
-
const identity = db.prepare('SELECT application_id,user_version FROM pragma_application_id,pragma_user_version').get();
|
|
475
|
-
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
476
|
-
if (identity.user_version === 1) {
|
|
477
|
-
validateLegacyFraming(db, APPLICATION_ID, location);
|
|
478
|
-
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' });
|
|
479
|
-
}
|
|
480
|
-
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) {
|
|
481
|
-
validateRowFraming(db, identity.user_version, location);
|
|
482
|
-
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' });
|
|
483
|
-
}
|
|
484
|
-
if (identity.user_version === CURRENT_USER_VERSION) {
|
|
485
|
-
validateRowFraming(db, CURRENT_USER_VERSION, location);
|
|
486
|
-
return createSqliteDriver(db).header();
|
|
487
|
-
}
|
|
488
|
-
throw invalidArtifact('unsupported SQLite format version.');
|
|
221
|
+
export function readSquareHeader(db) {
|
|
222
|
+
const rows = db.prepare('SELECT m.*, (SELECT COALESCE(MAX(act_index),-1) FROM activities) AS last_act_index FROM square_meta m').all();
|
|
223
|
+
requireFact(rows.length === 1 && rows[0].id === 1, 'header row is malformed.');
|
|
224
|
+
const r = rows[0], nextActIndex = integer(r.next_act_index), lastActIndex = integer(r.last_act_index, -1);
|
|
225
|
+
requireFact(nextActIndex > lastActIndex, 'nextActIndex is behind the activity history.');
|
|
226
|
+
return immutable({ revision: readStorageRevision(db), deliveryRevision: readStorageRevision(db, true), nextActIndex, lastActIndex,
|
|
227
|
+
hardCap: r.hard_cap === null ? null : integer(r.hard_cap, 1), ...(r.throttle_per_minute === null ? {} : { throttlePerMinute: integer(r.throttle_per_minute, 1) }),
|
|
228
|
+
preamble: strings(json(r.preamble_json)), warmup: strings(json(r.warmup_json)) });
|
|
489
229
|
}
|
|
490
230
|
function activitySelection(filter, alias = 'a') {
|
|
491
231
|
const terms = [], args = [];
|
|
@@ -513,7 +253,7 @@ function activitySelection(filter, alias = 'a') {
|
|
|
513
253
|
terms.push(`NOT EXISTS (
|
|
514
254
|
SELECT 1 FROM json_each(?) field WHERE NOT EXISTS (
|
|
515
255
|
WITH RECURSIVE walk(value,type,depth) AS (
|
|
516
|
-
SELECT json_extract(${alias}.payload_json,'$.metadata'),json_type(${alias}.payload_json,'$.metadata'),0
|
|
256
|
+
SELECT json_extract(${alias}.payload_json,'$.metadata'), json_type(${alias}.payload_json,'$.metadata'),0
|
|
517
257
|
UNION ALL
|
|
518
258
|
SELECT child.value,child.type,walk.depth+1
|
|
519
259
|
FROM walk JOIN json_each(CASE WHEN walk.type='object' THEN walk.value ELSE '{}' END) child
|
|
@@ -529,134 +269,89 @@ function activitySelection(filter, alias = 'a') {
|
|
|
529
269
|
args.push(encode(filter.metadataMatch.fields));
|
|
530
270
|
}
|
|
531
271
|
if (filter.search !== undefined) {
|
|
532
|
-
terms.push(`square_search(
|
|
272
|
+
terms.push(`square_search(?,?,'act/'||${alias}.act_index,${alias}.actor_json,json_extract(${alias}.payload_json,'$.body'))=1`);
|
|
533
273
|
args.push(filter.search.mode, filter.search.text);
|
|
534
274
|
}
|
|
535
275
|
return { sql: terms.length ? terms.join(' AND ') : '1', args };
|
|
536
276
|
}
|
|
537
|
-
function pageRows(rows, page, decode) {
|
|
538
|
-
const hasMore = rows.length > page.limit;
|
|
539
|
-
if (hasMore)
|
|
540
|
-
rows.pop();
|
|
541
|
-
if (page.edge === 'newest')
|
|
542
|
-
rows.reverse();
|
|
543
|
-
return immutable({ items: rows.map(decode), hasMore });
|
|
544
|
-
}
|
|
545
|
-
function pageLimit(page) {
|
|
546
|
-
requireFact(page.edge === 'oldest' || page.edge === 'newest');
|
|
547
|
-
return Math.min(integer(page.limit, 1) + 1, Number.MAX_SAFE_INTEGER);
|
|
548
|
-
}
|
|
549
277
|
export function createSqliteDriver(db) {
|
|
550
|
-
let changed = false
|
|
278
|
+
let changed = false;
|
|
551
279
|
const all = (sql, args = []) => db.prepare(sql).all(...args);
|
|
552
280
|
const one = (sql, args = []) => db.prepare(sql).get(...args);
|
|
553
281
|
const run = (sql, args = []) => Number(db.prepare(sql).run(...args).changes);
|
|
554
282
|
let searchRegistered = false;
|
|
283
|
+
function ensureSearch() {
|
|
284
|
+
if (searchRegistered)
|
|
285
|
+
return;
|
|
286
|
+
const patterns = new Map();
|
|
287
|
+
db.function('square_search', { deterministic: true }, (mode, text, index, actor, body) => {
|
|
288
|
+
const cacheKey = encode([mode, text]);
|
|
289
|
+
let pattern = patterns.get(cacheKey);
|
|
290
|
+
if (pattern === undefined) {
|
|
291
|
+
pattern = compileSearchPattern(string(text), mode === 'literal');
|
|
292
|
+
patterns.set(cacheKey, pattern);
|
|
293
|
+
}
|
|
294
|
+
return Number([string(index), decoded(actor, true), body === null ? '' : string(body)]
|
|
295
|
+
.some(field => pattern.test(field)));
|
|
296
|
+
});
|
|
297
|
+
searchRegistered = true;
|
|
298
|
+
}
|
|
555
299
|
function selectActivity(filter) {
|
|
556
|
-
if (filter.search !== undefined
|
|
557
|
-
|
|
558
|
-
db.function('square_search', { deterministic: true }, (mode, text, index, actor, body) => {
|
|
559
|
-
const key = encode([mode, text]);
|
|
560
|
-
let pattern = patterns.get(key);
|
|
561
|
-
if (pattern === undefined) {
|
|
562
|
-
pattern = compileSearchPattern(string(text), mode === 'literal');
|
|
563
|
-
patterns.set(key, pattern);
|
|
564
|
-
}
|
|
565
|
-
return Number([formatActivityId(integer(index)), decoded(actor, true), body === null ? '' : string(body)]
|
|
566
|
-
.some(field => pattern.test(field)));
|
|
567
|
-
});
|
|
568
|
-
searchRegistered = true;
|
|
569
|
-
}
|
|
300
|
+
if (filter.search !== undefined)
|
|
301
|
+
ensureSearch();
|
|
570
302
|
return activitySelection(filter);
|
|
571
303
|
}
|
|
572
|
-
const
|
|
573
|
-
const
|
|
304
|
+
const read = {};
|
|
305
|
+
const producer = createProducerDriver(db);
|
|
306
|
+
const currentEndpoint = (name) => {
|
|
307
|
+
const row = one('SELECT * FROM participant_seats WHERE participant_key_json=?', [key(name)]);
|
|
308
|
+
if (!row)
|
|
309
|
+
return undefined;
|
|
310
|
+
return immutable({ participant: decoded(row.participant_json, true), owner: decoded(row.owner_json, true), generation: endpointGeneration(decoded(row.generation_json, true)), attachment: row.attachment, ...(row.wake_address_json === null ? {} : { wakeAddress: json(row.wake_address_json) }) });
|
|
311
|
+
};
|
|
574
312
|
function directedSelection(recipient, filter) {
|
|
575
|
-
const
|
|
313
|
+
const participant = read.participant(recipient), base = selectActivity({ ...filter, actors: filter.senders });
|
|
576
314
|
base.sql += ' AND d.recipient_key_json=?';
|
|
577
|
-
base.args.push(
|
|
315
|
+
base.args.push(participant ? key(participant.name) : '');
|
|
316
|
+
if (filter.currentGeneration) {
|
|
317
|
+
base.sql += ' AND (d.generation_json IS NULL OR d.generation_json=?)';
|
|
318
|
+
base.args.push(participant ? encode(currentEndpoint(participant.name)?.generation ?? '') : '');
|
|
319
|
+
}
|
|
578
320
|
if (filter.unseenOnly) {
|
|
579
|
-
base.sql += ' AND NOT EXISTS (SELECT 1 FROM
|
|
580
|
-
base.args.push(
|
|
321
|
+
base.sql += ' AND NOT EXISTS (SELECT 1 FROM caught_activity r WHERE r.recipient_key_json=? AND r.act_index=a.act_index)';
|
|
322
|
+
base.args.push(key(participant?.name ?? recipient));
|
|
581
323
|
}
|
|
582
324
|
if (filter.attentionOnly)
|
|
583
|
-
base.sql += " AND d.
|
|
584
|
-
return base;
|
|
585
|
-
}
|
|
586
|
-
function directedHead(row, recipient) {
|
|
587
|
-
const h = head(row);
|
|
588
|
-
requireFact(h.kind === 'say');
|
|
589
|
-
return immutable({
|
|
590
|
-
index: h.index, at: h.at, actor: h.actor, recipient, route: routeKind(row.route)
|
|
591
|
-
});
|
|
325
|
+
base.sql += " AND d.reason IN ('mention','reply','bell')";
|
|
326
|
+
return { base, participant };
|
|
592
327
|
}
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
validateOwner(owner);
|
|
601
|
-
return immutable(all(`SELECT s.* FROM ${latestSeats} WHERE p.joined=1 AND s.owner_json=? ORDER BY s.participant_key_json`, [encode(owner)]).map(seat));
|
|
602
|
-
}
|
|
603
|
-
const read = {
|
|
604
|
-
seat(name) {
|
|
605
|
-
const r = one(`SELECT s.* FROM ${latestSeats} WHERE p.joined=1 AND s.participant_key_json=?`, [key(name)]);
|
|
606
|
-
return r ? seat(r) : undefined;
|
|
607
|
-
},
|
|
608
|
-
seatsForOwner: ownerSeats,
|
|
609
|
-
deliveryEvidence(participant, generation, activityIndex) {
|
|
610
|
-
const args = [key(participant), encode(generation)];
|
|
611
|
-
const filter = activityIndex === undefined ? '' : ' AND act_index=?';
|
|
612
|
-
if (activityIndex !== undefined)
|
|
613
|
-
args.push(integer(Number(activityIndex.slice(4))));
|
|
614
|
-
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)));
|
|
615
|
-
},
|
|
616
|
-
deliveryByToken(claimToken) {
|
|
617
|
-
const row = one('SELECT * FROM delivery_evidence WHERE claim_token_json=?', [encode(claimToken)]);
|
|
618
|
-
return row === undefined ? undefined : deliveryEvidence(row);
|
|
619
|
-
},
|
|
620
|
-
cancelledThrough(participant, generation) {
|
|
621
|
-
const r = one('SELECT through_index FROM delivery_cancellations WHERE participant_key_json=? AND generation_json=?', [key(participant), encode(generation)]);
|
|
622
|
-
return r === undefined ? undefined : integer(r.through_index);
|
|
623
|
-
},
|
|
624
|
-
header() {
|
|
625
|
-
const rows = all('SELECT m.*, (SELECT COALESCE(MAX(act_index),-1) FROM activities) AS last_act_index FROM square_meta m');
|
|
626
|
-
requireFact(rows.length === 1 && rows[0].id === 1, 'header row is malformed.');
|
|
627
|
-
const r = rows[0], nextActIndex = integer(r.next_act_index), lastActIndex = integer(r.last_act_index, -1);
|
|
628
|
-
requireFact(nextActIndex > lastActIndex, 'nextActIndex is behind the activity history.');
|
|
629
|
-
return immutable({
|
|
630
|
-
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))
|
|
631
|
-
});
|
|
632
|
-
},
|
|
633
|
-
participant(name) {
|
|
634
|
-
const r = one('SELECT * FROM participants WHERE name_key_json=?', [key(name)]);
|
|
635
|
-
return r ? participant(r) : undefined;
|
|
636
|
-
},
|
|
328
|
+
// ----- Upper Agora read capabilities -----
|
|
329
|
+
const readMethods = {
|
|
330
|
+
pendingPosts: producer.pendingPosts, postedPosts: producer.postedPosts, ownsPost: producer.ownsPost,
|
|
331
|
+
seat: currentEndpoint,
|
|
332
|
+
seatsForOwner: requested => immutable(all('SELECT participant_json FROM participant_seats WHERE owner_json=?', [encode(requested)]).map(row => currentEndpoint(decoded(row.participant_json, true)))),
|
|
333
|
+
header() { return readSquareHeader(db); },
|
|
334
|
+
participant(name) { const r = one('SELECT * FROM participants WHERE name_key_json=?', [key(name)]); return r ? participant(r) : undefined; },
|
|
637
335
|
participantCounts() {
|
|
638
336
|
const r = one('SELECT COUNT(*) AS known, COALESCE(SUM(joined),0) AS joined, COALESCE(SUM(done),0) AS done FROM participants');
|
|
639
337
|
return immutable({ known: integer(r.known), joined: integer(r.joined), done: integer(r.done) });
|
|
640
338
|
},
|
|
641
339
|
roster: () => immutable(all('SELECT * FROM participants ORDER BY first_index').map(participant)),
|
|
642
|
-
attention: name => immutable(
|
|
643
|
-
incomingAttention: name => immutable(
|
|
340
|
+
attention: name => immutable(all('SELECT t.*,p.name_json FROM attention t JOIN participants p ON p.name_key_json=t.listener_key_json WHERE t.listener_key_json=? ORDER BY t.since_index', [key(name)]).map(attention)),
|
|
341
|
+
incomingAttention: name => immutable(all('SELECT t.*,p.name_json FROM attention t JOIN participants p ON p.name_key_json=t.listener_key_json WHERE t.target_key_json=? ORDER BY t.since_index', [key(name)]).map(attention)),
|
|
644
342
|
activity(index) {
|
|
645
343
|
const r = one('SELECT * FROM activities WHERE act_index=?', [integer(index)]);
|
|
646
344
|
return r ? activity(r) : undefined;
|
|
647
345
|
},
|
|
648
346
|
activityHeads(filter, page) {
|
|
649
347
|
const s = selectActivity(filter), limit = pageLimit(page);
|
|
650
|
-
return pageRows(all(`SELECT a
|
|
348
|
+
return pageRows(all(`SELECT a.* FROM activities a WHERE ${s.sql} ORDER BY a.act_index ${page.edge === 'newest' ? 'DESC' : 'ASC'} LIMIT ?`, [...s.args, limit]), page, head);
|
|
651
349
|
},
|
|
652
350
|
activities(indexes) {
|
|
653
351
|
indexes.forEach(v => integer(v));
|
|
654
|
-
return immutable(all('SELECT
|
|
655
|
-
},
|
|
656
|
-
activityCount(filter) {
|
|
657
|
-
const s = selectActivity(filter);
|
|
658
|
-
return integer(one(`SELECT COUNT(*) AS n FROM activities a WHERE ${s.sql}`, s.args).n);
|
|
352
|
+
return immutable(all('SELECT a.* FROM activities a WHERE a.act_index IN (SELECT value FROM json_each(?)) ORDER BY a.act_index', [encode(indexes)]).map(activityWithContent));
|
|
659
353
|
},
|
|
354
|
+
activityCount(filter) { const s = selectActivity(filter); return integer(one(`SELECT COUNT(*) AS n FROM activities a WHERE ${s.sql}`, s.args).n); },
|
|
660
355
|
contextIndexes(centers, before, after) {
|
|
661
356
|
integer(before);
|
|
662
357
|
integer(after);
|
|
@@ -676,7 +371,6 @@ export function createSqliteDriver(db) {
|
|
|
676
371
|
sayOrdinals(indexes) {
|
|
677
372
|
indexes.forEach(v => integer(v));
|
|
678
373
|
const result = {};
|
|
679
|
-
// Only an actor's latest say can use its authoritative lifetime count.
|
|
680
374
|
for (const r of all(`SELECT a.act_index, CASE
|
|
681
375
|
WHEN p.say_count IS NOT NULL AND a.act_index=(SELECT MAX(s.act_index) FROM activities s WHERE s.kind='say' AND s.actor_key_json=a.actor_key_json)
|
|
682
376
|
THEN p.say_count
|
|
@@ -699,131 +393,67 @@ export function createSqliteDriver(db) {
|
|
|
699
393
|
args.push(integer(take));
|
|
700
394
|
return immutable(all(`SELECT at FROM activities WHERE ${where} ORDER BY at DESC,act_index DESC LIMIT ?`, args).map(r => finite(r.at)).reverse());
|
|
701
395
|
},
|
|
702
|
-
audience(index) { return immutable(all('SELECT p.name_json,d.
|
|
396
|
+
audience(index) { return immutable(all('SELECT p.name_json,d.reason,d.generation_json,d.owner_json FROM landed_audience d JOIN participants p ON p.name_key_json=d.recipient_key_json WHERE d.act_index=? ORDER BY d.recipient_order', [integer(index)]).map(r => ({ recipient: decoded(r.name_json, true), route: routeKind(r.reason), ...(r.generation_json === null ? {} : { generation: endpointGeneration(decoded(r.generation_json, true)) }), ...(r.owner_json === null ? {} : { owner: decoded(r.owner_json, true) }) }))); },
|
|
703
397
|
directed(recipient, filter, page) {
|
|
704
|
-
const
|
|
705
|
-
|
|
398
|
+
const { base, participant: p } = directedSelection(recipient, filter), limit = pageLimit(page);
|
|
399
|
+
const rows = all(`SELECT a.*,d.reason,d.generation_json FROM activities a JOIN landed_audience d ON d.act_index=a.act_index WHERE ${base.sql} ORDER BY a.act_index ${page.edge === 'newest' ? 'DESC' : 'ASC'} LIMIT ?`, [...base.args, limit]);
|
|
400
|
+
return pageRows(rows, page, r => {
|
|
401
|
+
const h = head(r);
|
|
402
|
+
return { index: h.index, at: h.at, actor: h.actor, recipient: p?.name ?? recipient,
|
|
403
|
+
route: routeKind(r.reason),
|
|
404
|
+
...(r.generation_json === null ? {} : { generation: endpointGeneration(decoded(r.generation_json, true)) }) };
|
|
405
|
+
});
|
|
706
406
|
},
|
|
707
407
|
directedAt(recipient, index) { return read.directed(recipient, { afterIndex: integer(index) - 1, throughIndex: index }, { edge: 'oldest', limit: 1 }).items[0]; },
|
|
708
408
|
directedCount(recipient, filter) {
|
|
709
|
-
const
|
|
710
|
-
return integer(one(`SELECT COUNT(*) AS n FROM
|
|
711
|
-
},
|
|
712
|
-
pendingAudience(after, through, limit) {
|
|
713
|
-
const args = [integer(after, -1), integer(through, -1)], extra = pageLimit({ edge: 'oldest', limit });
|
|
714
|
-
const indices = all(`SELECT DISTINCT d.act_index FROM ${pendingFrom} WHERE ${pendingWhere} AND d.act_index>? AND d.act_index<=? ORDER BY d.act_index LIMIT ?`, [...args, extra]);
|
|
715
|
-
const hasMore = indices.length > limit;
|
|
716
|
-
if (hasMore)
|
|
717
|
-
indices.pop();
|
|
718
|
-
const groups = indices.map(r => {
|
|
719
|
-
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)]);
|
|
720
|
-
const h = head(rows[0]);
|
|
721
|
-
requireFact(h.kind === 'say');
|
|
722
|
-
return {
|
|
723
|
-
index: h.index, at: h.at, actor: h.actor, recipients: rows.map(v => ({ recipient: decoded(v.name_json, true), route: routeKind(v.route) }))
|
|
724
|
-
};
|
|
725
|
-
});
|
|
726
|
-
return immutable({ groups, hasMore });
|
|
409
|
+
const { base } = directedSelection(recipient, filter);
|
|
410
|
+
return integer(one(`SELECT COUNT(*) AS n FROM activities a JOIN landed_audience d ON d.act_index=a.act_index WHERE ${base.sql}`, base.args).n);
|
|
727
411
|
},
|
|
728
412
|
receipt(name, index) {
|
|
729
|
-
const r = one('SELECT at FROM
|
|
413
|
+
const r = one('SELECT at FROM caught_activity WHERE recipient_key_json=? AND act_index=?', [key(name), integer(index)]);
|
|
730
414
|
return r ? immutable({ at: finite(r.at) }) : undefined;
|
|
731
415
|
},
|
|
732
416
|
receiptFacts(name, afterIndex) {
|
|
733
|
-
const k =
|
|
417
|
+
const k = key(name);
|
|
418
|
+
const r = one('SELECT MAX(at) AS latest FROM caught_activity WHERE recipient_key_json=?', [k]);
|
|
419
|
+
const e = one('SELECT MAX(act_index) AS latest FROM caught_activity WHERE recipient_key_json=? AND act_index>?', [k, integer(afterIndex, -1)]);
|
|
734
420
|
return immutable({ ...(r.latest === null ? {} : { latestAt: finite(r.latest) }), ...(e.latest === null ? {} : { latestActorActivityIndex: integer(e.latest) }) });
|
|
735
421
|
},
|
|
736
|
-
deliveryDeferrals(name, generation) {
|
|
737
|
-
const canonical = owner(name);
|
|
738
|
-
const current = read.seat(canonical);
|
|
739
|
-
if (current === undefined || current.generation !== generation)
|
|
740
|
-
return [];
|
|
741
|
-
return immutable(all('SELECT * FROM delivery_deferrals WHERE participant_key_json=? AND generation_json=? ORDER BY expires_at,deferral_id_json', [key(canonical), encode(generation)]).map(deliveryDeferral));
|
|
742
|
-
},
|
|
743
422
|
lease(name) {
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
? one('SELECT * FROM catch_leases WHERE owner_json=?', [encode(canonical)])
|
|
747
|
-
: one('SELECT * FROM catch_leases WHERE owner_json=? AND (generation_json=? OR generation_json=?)', [encode(canonical), encode(current.generation), encode('')]);
|
|
748
|
-
return r ? immutable(validateWatchLease({
|
|
749
|
-
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) })
|
|
750
|
-
})) : undefined;
|
|
423
|
+
const row = one('SELECT lease_json FROM catch_leases WHERE recipient_key_json=?', [key(name)]);
|
|
424
|
+
return row ? immutable(validateWatchLease(json(row.lease_json))) : undefined;
|
|
751
425
|
},
|
|
752
426
|
};
|
|
427
|
+
Object.assign(read, readMethods);
|
|
428
|
+
// ----- Upper Agora writes / landing -----
|
|
753
429
|
const writes = {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if (existing !== undefined && stableJson(existing) === stableJson(value))
|
|
760
|
-
return false;
|
|
761
|
-
const wake = value.wakeAddress === undefined ? null : encode(value.wakeAddress);
|
|
762
|
-
run('INSERT INTO seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?) ON CONFLICT(participant_key_json) DO UPDATE SET participant_json=excluded.participant_json,owner_json=excluded.owner_json,generation_json=excluded.generation_json,wake_address_json=excluded.wake_address_json,attachment=excluded.attachment', [key(value.participant), encode(value.participant), encode(value.owner), encode(value.generation), wake, value.attachment]);
|
|
763
|
-
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
764
|
-
run('DELETE FROM delivery_deferrals WHERE participant_key_json=?', [key(value.participant)]);
|
|
765
|
-
deliveryChanged = true;
|
|
766
|
-
return true;
|
|
767
|
-
},
|
|
768
|
-
updateWakeAddress(value, wakeAddress) {
|
|
769
|
-
const current = one('SELECT wake_address_json FROM seats WHERE participant_key_json=? AND generation_json=?', [key(value.participant), encode(value.generation)]);
|
|
770
|
-
if (wakeAddress !== undefined) {
|
|
771
|
-
requireFact(isWakeRouteKind(wakeAddress.kind));
|
|
772
|
-
requireFact(Object.values(wakeAddress.address).every((item) => typeof item === 'string'));
|
|
773
|
-
}
|
|
774
|
-
if (stableJson(current.wake_address_json === null ? null : json(current.wake_address_json)) === stableJson(wakeAddress ?? null))
|
|
775
|
-
return false;
|
|
776
|
-
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)]);
|
|
777
|
-
if (count)
|
|
778
|
-
deliveryChanged = true;
|
|
430
|
+
insertNotice: producer.insertNotice, ackMatching: producer.ackMatching,
|
|
431
|
+
acquirePost: producer.acquirePost, settlePost: producer.settlePost, foldReceipt: producer.foldReceipt,
|
|
432
|
+
setSeatAttachment(seat, attachment) {
|
|
433
|
+
const count = run('UPDATE participant_seats SET attachment=? WHERE participant_key_json=? AND owner_json=? AND generation_json=? AND attachment<>?', [attachment, key(seat.participant), encode(seat.owner), encode(seat.generation), attachment]);
|
|
434
|
+
changed ||= count > 0;
|
|
779
435
|
return count > 0;
|
|
780
436
|
},
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if (current?.attachment !== 'detached')
|
|
784
|
-
return false;
|
|
785
|
-
run("UPDATE seats SET attachment='attached',wake_address_json=? WHERE participant_key_json=? AND generation_json=?", [address === undefined ? null : encode(wakeAddress(address)), key(value.participant), encode(value.generation)]);
|
|
786
|
-
deliveryChanged = true;
|
|
787
|
-
return true;
|
|
788
|
-
},
|
|
789
|
-
detachSeat(value) {
|
|
790
|
-
const count = run("UPDATE seats SET attachment='detached',wake_address_json=NULL WHERE participant_key_json=? AND generation_json=? AND attachment='attached'", [key(value.participant), encode(value.generation)]);
|
|
791
|
-
if (!count)
|
|
792
|
-
return false;
|
|
793
|
-
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
794
|
-
run('DELETE FROM delivery_deferrals WHERE participant_key_json=? AND generation_json=?', [key(value.participant), encode(value.generation)]);
|
|
795
|
-
deliveryChanged = true;
|
|
796
|
-
return true;
|
|
797
|
-
},
|
|
798
|
-
removeSeat(value) {
|
|
799
|
-
const row = one(`SELECT s.* FROM ${latestSeats} WHERE p.done=1 AND s.participant_key_json=?`, [key(value.participant)]);
|
|
800
|
-
if (row === undefined || decoded(row.generation_json) !== value.generation || ownerFrom(row) !== value.owner)
|
|
801
|
-
return false;
|
|
802
|
-
run('DELETE FROM seats WHERE participant_key_json=?', [key(value.participant)]);
|
|
803
|
-
run('DELETE FROM catch_leases WHERE owner_json=?', [encode(value.participant)]);
|
|
804
|
-
run('DELETE FROM delivery_deferrals WHERE participant_key_json=?', [key(value.participant)]);
|
|
437
|
+
storeSeat(value) {
|
|
438
|
+
run('INSERT INTO participant_seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?) ON CONFLICT(participant_key_json) DO UPDATE SET participant_json=excluded.participant_json,owner_json=excluded.owner_json,generation_json=excluded.generation_json,wake_address_json=excluded.wake_address_json,attachment=excluded.attachment', [key(value.participant), encode(value.participant), encode(value.owner), encode(value.generation), value.wakeAddress === undefined ? null : encode(value.wakeAddress), value.attachment]);
|
|
805
439
|
changed = true;
|
|
806
|
-
deliveryChanged = true;
|
|
807
440
|
return true;
|
|
808
441
|
},
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
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]);
|
|
816
|
-
deliveryChanged = true;
|
|
817
|
-
return true;
|
|
442
|
+
removeSeat(value) {
|
|
443
|
+
const count = run('DELETE FROM participant_seats WHERE participant_key_json=? AND owner_json=? AND generation_json=?', [key(value.participant), encode(value.owner), encode(value.generation)]);
|
|
444
|
+
if (count)
|
|
445
|
+
run('DELETE FROM catch_leases WHERE recipient_key_json=? AND generation_json=?', [key(value.participant), encode(value.generation)]);
|
|
446
|
+
changed ||= count > 0;
|
|
447
|
+
return count > 0;
|
|
818
448
|
},
|
|
819
449
|
applyLanding(effect) {
|
|
820
450
|
const a = validateStoredActivity(effect.activity);
|
|
821
451
|
requireFact(a.index >= read.header().nextActIndex && a.index < Number.MAX_SAFE_INTEGER, 'activity index cannot advance.');
|
|
822
452
|
const { index, at, kind, actor, reach, reply, ...payload } = a;
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
453
|
+
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)]);
|
|
454
|
+
effect.audience.forEach((address, order) => {
|
|
455
|
+
run('INSERT INTO landed_audience(act_index,recipient_key_json,recipient_order,reason,owner_json,generation_json) VALUES(?,?,?,?,?,?)', [index, key(address.recipient), order, address.route, address.owner === undefined ? null : encode(address.owner), address.generation === undefined ? null : encode(address.generation)]);
|
|
456
|
+
});
|
|
827
457
|
if (effect.participant) {
|
|
828
458
|
const p = effect.participant;
|
|
829
459
|
string(p.name, true);
|
|
@@ -853,199 +483,69 @@ export function createSqliteDriver(db) {
|
|
|
853
483
|
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]);
|
|
854
484
|
}
|
|
855
485
|
run('UPDATE square_meta SET next_act_index=? WHERE id=1', [index + 1]);
|
|
486
|
+
if (kind === 'say')
|
|
487
|
+
producer.consumeActivity(index, at);
|
|
856
488
|
changed = true;
|
|
857
489
|
},
|
|
858
|
-
markSeen(name, indexes, at) {
|
|
859
|
-
finite(at);
|
|
860
|
-
const next = read.header().nextActIndex;
|
|
861
|
-
indexes.forEach(index => requireFact(integer(index) < next, 'receipt references an unassigned activity index.'));
|
|
862
|
-
const canonical = owner(name);
|
|
863
|
-
string(canonical, true);
|
|
864
|
-
const count = recordConsumed({
|
|
865
|
-
insertReceipt: (ref, time) => run('INSERT OR IGNORE INTO receipts(owner_json,act_index,at) VALUES(?,?,?)', [encode(ref.recipient), ref.item, time]) > 0,
|
|
866
|
-
}, indexes.map(item => ({ recipient: canonical, item })), at);
|
|
867
|
-
if (count)
|
|
868
|
-
changed = true;
|
|
869
|
-
return count;
|
|
870
|
-
},
|
|
871
490
|
putLease(name, value, generation) {
|
|
872
|
-
const l = validateWatchLease(value),
|
|
873
|
-
|
|
874
|
-
if (generation !== undefined)
|
|
875
|
-
activityId(generation);
|
|
876
|
-
if (row !== undefined && generation !== undefined && decoded(row.generation_json) !== '' && decoded(row.generation_json) !== generation)
|
|
877
|
-
return false;
|
|
878
|
-
if (old && stableJson(old) === stableJson(l) && (generation === undefined || row?.generation_json === encode(generation)))
|
|
491
|
+
const l = validateWatchLease(value), seat = currentEndpoint(name);
|
|
492
|
+
if (!seat || generation !== undefined && generation !== seat.generation)
|
|
879
493
|
return false;
|
|
880
|
-
|
|
881
|
-
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)]);
|
|
494
|
+
run('INSERT INTO catch_leases(recipient_key_json,generation_json,lease_json) VALUES(?,?,?) ON CONFLICT(recipient_key_json) DO UPDATE SET generation_json=excluded.generation_json,lease_json=excluded.lease_json', [key(name), encode(seat.generation), encode(l)]);
|
|
882
495
|
changed = true;
|
|
883
496
|
return true;
|
|
884
497
|
},
|
|
885
|
-
putDeliveryDeferral(seatRef, id, match, expiresAt, now) {
|
|
886
|
-
finite(now);
|
|
887
|
-
finite(expiresAt);
|
|
888
|
-
const canonicalMatch = validateMetadataMatch(match);
|
|
889
|
-
run('DELETE FROM delivery_deferrals WHERE participant_key_json=? AND expires_at<=?', [key(seatRef.participant), now]);
|
|
890
|
-
run('INSERT INTO delivery_deferrals(participant_key_json,participant_json,generation_json,deferral_id_json,match_json,expires_at) VALUES(?,?,?,?,?,?)', [key(seatRef.participant), encode(seatRef.participant), encode(seatRef.generation), encode(id), encode(canonicalMatch), expiresAt]);
|
|
891
|
-
deliveryChanged = true;
|
|
892
|
-
return true;
|
|
893
|
-
},
|
|
894
|
-
renewDeliveryDeferral(seatRef, id, expiresAt, now) {
|
|
895
|
-
finite(now);
|
|
896
|
-
finite(expiresAt);
|
|
897
|
-
const pruned = run('DELETE FROM delivery_deferrals WHERE participant_key_json=? AND expires_at<=?', [key(seatRef.participant), now]);
|
|
898
|
-
const count = run('UPDATE delivery_deferrals SET expires_at=? WHERE participant_key_json=? AND generation_json=? AND deferral_id_json=? AND expires_at>?', [expiresAt, key(seatRef.participant), encode(seatRef.generation), encode(id), now]);
|
|
899
|
-
if (count || pruned)
|
|
900
|
-
deliveryChanged = true;
|
|
901
|
-
return count > 0;
|
|
902
|
-
},
|
|
903
|
-
releaseDeliveryDeferral(seatRef, id, now) {
|
|
904
|
-
finite(now);
|
|
905
|
-
const pruned = run('DELETE FROM delivery_deferrals WHERE participant_key_json=? AND expires_at<=?', [key(seatRef.participant), now]);
|
|
906
|
-
const count = run('DELETE FROM delivery_deferrals WHERE participant_key_json=? AND generation_json=? AND deferral_id_json=? AND expires_at>?', [key(seatRef.participant), encode(seatRef.generation), encode(id), now]);
|
|
907
|
-
if (count || pruned)
|
|
908
|
-
deliveryChanged = true;
|
|
909
|
-
return count > 0;
|
|
910
|
-
},
|
|
911
498
|
removeLease(name, expected, generation) {
|
|
912
|
-
|
|
913
|
-
if (row === undefined || generation !== undefined && decoded(row.generation_json) !== generation)
|
|
499
|
+
if (read.lease(name)?.leaseId !== expected || generation !== undefined && currentEndpoint(name)?.generation !== generation)
|
|
914
500
|
return false;
|
|
915
|
-
const count = run('DELETE FROM catch_leases WHERE
|
|
916
|
-
|
|
917
|
-
changed = true;
|
|
501
|
+
const count = run('DELETE FROM catch_leases WHERE recipient_key_json=?', [key(name)]);
|
|
502
|
+
changed ||= count > 0;
|
|
918
503
|
return count > 0;
|
|
919
504
|
},
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const scope = [key(seat.participant), encode(seat.generation), index, kind];
|
|
928
|
-
let sources = [];
|
|
929
|
-
return {
|
|
930
|
-
list() {
|
|
931
|
-
sources = all('SELECT * FROM delivery_evidence WHERE participant_key_json=? AND generation_json=? AND act_index=? AND kind=? ORDER BY attempt_n DESC', scope);
|
|
932
|
-
return sources.map(attempt);
|
|
933
|
-
},
|
|
934
|
-
evidence(value) {
|
|
935
|
-
const source = sources.find(row => row.attempt_n === value.ordinal);
|
|
936
|
-
return deliveryEvidence({ ...source, outcome: value.outcome, at: value.at, expires_at: value.expiresAt ?? null }, value.data);
|
|
937
|
-
},
|
|
938
|
-
byToken(token) {
|
|
939
|
-
if (known?.token === token)
|
|
940
|
-
return known;
|
|
941
|
-
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)]);
|
|
942
|
-
return row === undefined ? undefined : attempt(row);
|
|
943
|
-
},
|
|
944
|
-
insert(value) {
|
|
945
|
-
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)]);
|
|
946
|
-
deliveryChanged = true;
|
|
947
|
-
},
|
|
948
|
-
replace(value) {
|
|
949
|
-
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)]);
|
|
950
|
-
if (count)
|
|
951
|
-
deliveryChanged = true;
|
|
952
|
-
},
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
const delivery = {
|
|
956
|
-
claimDelivery(input) {
|
|
957
|
-
requireFact(input.kind === 'wake' || input.kind === 'presentation');
|
|
958
|
-
const index = activityIndex(input.activity);
|
|
959
|
-
finite(input.at);
|
|
960
|
-
const leaseMs = integer(input.leaseMs, 1);
|
|
961
|
-
string(input.claimToken, true);
|
|
962
|
-
if (input.route !== undefined)
|
|
963
|
-
wakeAddress(input.route);
|
|
964
|
-
const currentSeat = read.seat(input.seat.participant);
|
|
965
|
-
const item = read.activity(index);
|
|
966
|
-
if (currentSeat === undefined || currentSeat.attachment !== 'attached' || currentSeat.generation !== input.seat.generation || currentSeat.owner !== input.seat.owner
|
|
967
|
-
|| item?.kind !== 'say' || read.directedAt(input.seat.participant, index) === undefined)
|
|
968
|
-
return { status: 'stale' };
|
|
969
|
-
if (matchingDeferrals(read.deliveryDeferrals(currentSeat.participant, currentSeat.generation), item.metadata, input.at).length > 0)
|
|
970
|
-
return { status: 'deferred' };
|
|
971
|
-
const cancelledThrough = read.cancelledThrough(input.seat.participant, input.seat.generation);
|
|
972
|
-
if (cancelledThrough !== undefined && index <= cancelledThrough)
|
|
973
|
-
return { status: 'stale' };
|
|
974
|
-
const rows = attemptRows(input.seat, index, input.kind);
|
|
975
|
-
const result = claimAttempt({ ...rows, replace(value) {
|
|
976
|
-
rows.replace({ ...value, data: { ...value.data, diagnostic: 'interrupted dispatch' } });
|
|
977
|
-
} }, { purpose: input.kind === 'wake' ? 'dispatch' : 'presentation', token: input.claimToken,
|
|
978
|
-
at: input.at, leaseMs, data: input.route === undefined ? {} : { route: input.route } });
|
|
979
|
-
if (result.status === 'acquired')
|
|
980
|
-
return { status: 'acquired', claimToken: result.token, attemptN: result.ordinal };
|
|
981
|
-
const evidence = result.interrupted ? { ...result.evidence, data: { ...result.evidence.data, diagnostic: 'interrupted dispatch' } } : result.evidence;
|
|
982
|
-
return { status: result.status, evidence: rows.evidence(evidence) };
|
|
983
|
-
},
|
|
984
|
-
prepareDelivery(input) {
|
|
985
|
-
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)]);
|
|
986
|
-
if (row === undefined || row.outcome !== 'dispatching')
|
|
987
|
-
return false;
|
|
988
|
-
const previous = attempt(row), correlation = nativeCorrelation(input.correlation), payload = previous.data;
|
|
989
|
-
if (stableJson(payload.correlation ?? null) === stableJson(correlation))
|
|
990
|
-
return true;
|
|
991
|
-
return prepareAttempt(attemptRows(input.seat, integer(row.act_index), row.kind, previous), { token: input.claimToken, data: { ...payload, correlation } });
|
|
992
|
-
},
|
|
993
|
-
completeDelivery(input) {
|
|
994
|
-
const index = activityIndex(input.activity);
|
|
995
|
-
finite(input.at);
|
|
996
|
-
requireFact(['accepted', 'failed', 'unknown', 'released', 'presented', 'clipped'].includes(input.outcome));
|
|
997
|
-
string(input.claimToken, true);
|
|
998
|
-
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)]);
|
|
999
|
-
if (row === undefined)
|
|
1000
|
-
return false;
|
|
1001
|
-
const previous = attempt(row);
|
|
1002
|
-
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 }) };
|
|
1003
|
-
return settleAttempt(attemptRows(input.seat, index, row.kind, previous), { token: input.claimToken, outcome: input.outcome, at: input.at, data: payload });
|
|
1004
|
-
},
|
|
1005
|
-
observeNativeDelivery(input) {
|
|
1006
|
-
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)]);
|
|
1007
|
-
if (row === undefined || !['dispatching', 'accepted', 'unknown'].includes(string(row.outcome)))
|
|
1008
|
-
return false;
|
|
1009
|
-
const observation = input.observation;
|
|
1010
|
-
requireFact(typeof observation.eventId === 'string' && observation.eventId.length > 0);
|
|
1011
|
-
requireFact(typeof observation.messageId === 'string' && observation.messageId.length > 0);
|
|
1012
|
-
requireFact(observation.state === 'held' || observation.state === 'released' || observation.state === 'dropped');
|
|
1013
|
-
finite(observation.observedAt);
|
|
1014
|
-
if (observation.reason !== undefined)
|
|
1015
|
-
string(observation.reason);
|
|
1016
|
-
const payload = object(json(row.payload_json));
|
|
1017
|
-
const observations = payload.nativeObservations === undefined ? [] : payload.nativeObservations;
|
|
1018
|
-
requireFact(Array.isArray(observations));
|
|
1019
|
-
if (observations.some((item) => object(item).eventId === observation.eventId))
|
|
1020
|
-
return true;
|
|
1021
|
-
const next = [...observations, observation];
|
|
1022
|
-
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)]);
|
|
1023
|
-
if (count)
|
|
1024
|
-
deliveryChanged = true;
|
|
1025
|
-
return count > 0;
|
|
505
|
+
markSeen(name, indexes, at) {
|
|
506
|
+
finite(at);
|
|
507
|
+
let count = 0;
|
|
508
|
+
for (const index of indexes)
|
|
509
|
+
count += run('INSERT OR IGNORE INTO caught_activity(recipient_key_json,act_index,at) SELECT recipient_key_json,act_index,? FROM landed_audience WHERE recipient_key_json=? AND act_index=?', [at, key(name), integer(index)]);
|
|
510
|
+
changed ||= count > 0;
|
|
511
|
+
return count;
|
|
1026
512
|
},
|
|
1027
513
|
};
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
514
|
+
function importLanding(activityValue) {
|
|
515
|
+
const validated = validateHydratedActivity(activityValue);
|
|
516
|
+
driver.applyLanding(deriveLanding(validated, landingFacts(read, validated)));
|
|
517
|
+
if (validated.kind === 'join')
|
|
518
|
+
driver.storeSeat({ participant: validated.actor, owner: 'import', generation: formatActivityId(validated.index), attachment: 'attached' });
|
|
519
|
+
else if (validated.kind === 'done') {
|
|
520
|
+
const current = read.seat(validated.actor);
|
|
521
|
+
if (current)
|
|
522
|
+
driver.removeSeat(current);
|
|
523
|
+
}
|
|
524
|
+
return validated;
|
|
525
|
+
}
|
|
526
|
+
const driver = Object.defineProperties(Object.assign(read, writes, { importLanding }), {
|
|
527
|
+
changed: { get: () => changed || producer.changed },
|
|
528
|
+
deliveryChanged: { get: () => producer.deliveryChanged },
|
|
1031
529
|
});
|
|
530
|
+
return driver;
|
|
1032
531
|
}
|
|
1033
|
-
/** Explicit import
|
|
1034
|
-
export function initializeSqlite(db, state, revision
|
|
532
|
+
/** Explicit import replay. Caller owns the transaction and format publication. */
|
|
533
|
+
export function initializeSqlite(db, state, revision) {
|
|
1035
534
|
integer(revision);
|
|
1036
535
|
integer(state.runtime.nextActIndex);
|
|
1037
536
|
if (state.hardCap !== null)
|
|
1038
537
|
integer(state.hardCap, 1);
|
|
1039
538
|
if (state.throttlePerMinute !== undefined)
|
|
1040
539
|
integer(state.throttlePerMinute, 1);
|
|
1041
|
-
db
|
|
1042
|
-
db
|
|
540
|
+
initializeStorageMeta(db, revision);
|
|
541
|
+
initializeProducerSqlite(db);
|
|
542
|
+
initializeAgoraSqlite(db, { ...state, preamble: strings(state.preamble), warmup: strings(state.warmup) });
|
|
1043
543
|
const driver = createSqliteDriver(db);
|
|
1044
544
|
for (const activity of state.acts)
|
|
1045
|
-
driver.
|
|
545
|
+
driver.importLanding(activity);
|
|
1046
546
|
requireFact(state.runtime.nextActIndex > driver.header().lastActIndex, 'nextActIndex is behind the activity history.');
|
|
1047
547
|
db.prepare('UPDATE square_meta SET next_act_index=? WHERE id=1').run(state.runtime.nextActIndex);
|
|
1048
|
-
const insert = db.prepare('INSERT INTO
|
|
548
|
+
const insert = db.prepare('INSERT INTO caught_activity(recipient_key_json,act_index,at) VALUES(?,?,?)');
|
|
1049
549
|
for (const [owner, observations] of Object.entries(state.runtime.observations)) {
|
|
1050
550
|
string(owner, true);
|
|
1051
551
|
for (const [id, observation] of Object.entries(observations)) {
|
|
@@ -1054,80 +554,48 @@ export function initializeSqlite(db, state, revision = 0) {
|
|
|
1054
554
|
const index = integer(Number(match[1]));
|
|
1055
555
|
requireFact(index < state.runtime.nextActIndex, 'receipt references an unassigned activity index.');
|
|
1056
556
|
requireFact(observation.state === 'seen');
|
|
1057
|
-
|
|
557
|
+
const activity = driver.activity(index);
|
|
558
|
+
if (activity?.kind !== 'say')
|
|
559
|
+
continue;
|
|
560
|
+
insert.run(key(owner), index, finite(observation.at));
|
|
1058
561
|
}
|
|
1059
562
|
}
|
|
1060
563
|
for (const [owner, value] of Object.entries(state.runtime.leases)) {
|
|
1061
564
|
string(owner, true);
|
|
1062
|
-
const l = validateWatchLease(value);
|
|
1063
|
-
db.prepare('INSERT INTO catch_leases(
|
|
565
|
+
const l = validateWatchLease(value), generation = driver.seat(owner)?.generation ?? 'detached';
|
|
566
|
+
db.prepare('INSERT INTO catch_leases(recipient_key_json,generation_json,lease_json) VALUES(?,?,?)').run(key(owner), encode(generation), encode(l));
|
|
1064
567
|
}
|
|
1065
568
|
}
|
|
1066
569
|
export function exportSqliteState(db) {
|
|
1067
570
|
const d = createSqliteDriver(db), h = d.header();
|
|
1068
571
|
const observations = {}, leases = {};
|
|
1069
|
-
for (const row of db.prepare('SELECT
|
|
1070
|
-
const owner = decoded(row.
|
|
1071
|
-
requireFact(index < h.nextActIndex, 'receipt references an unassigned activity index.');
|
|
572
|
+
for (const row of db.prepare('SELECT p.name_json,r.act_index,r.at FROM caught_activity r JOIN participants p ON p.name_key_json=r.recipient_key_json').all()) {
|
|
573
|
+
const owner = decoded(row.name_json, true);
|
|
1072
574
|
if (!Object.hasOwn(observations, owner))
|
|
1073
|
-
Object.defineProperty(observations, owner, {
|
|
1074
|
-
|
|
1075
|
-
});
|
|
1076
|
-
observations[owner][`act/${index}`] = { state: 'seen', at: finite(row.at) };
|
|
575
|
+
Object.defineProperty(observations, owner, { value: {}, enumerable: true, writable: true });
|
|
576
|
+
observations[owner][formatActivityId(integer(row.act_index))] = { state: 'seen', at: finite(row.at) };
|
|
1077
577
|
}
|
|
1078
|
-
for (const row of db.prepare('SELECT
|
|
1079
|
-
|
|
1080
|
-
Object.defineProperty(leases, owner, { value: validateWatchLease({
|
|
1081
|
-
leaseId: decoded(row.lease_id_json, true), heartbeatAt: finite(row.heartbeat_at), expiresAt: finite(row.expires_at), ...(row.filter_json === null ? {} : { filter: json(row.filter_json) })
|
|
1082
|
-
}), enumerable: true });
|
|
578
|
+
for (const row of db.prepare('SELECT p.name_json,l.lease_json FROM catch_leases l JOIN participants p ON p.name_key_json=l.recipient_key_json').all()) {
|
|
579
|
+
Object.defineProperty(leases, decoded(row.name_json, true), { value: validateWatchLease(json(row.lease_json)), enumerable: true });
|
|
1083
580
|
}
|
|
1084
581
|
return {
|
|
1085
|
-
hardCap: h.hardCap, ...(h.throttlePerMinute === undefined ? {} : { throttlePerMinute: h.throttlePerMinute }), preamble: [...h.preamble], warmup: [...h.warmup],
|
|
1086
|
-
|
|
1087
|
-
}
|
|
582
|
+
hardCap: h.hardCap, ...(h.throttlePerMinute === undefined ? {} : { throttlePerMinute: h.throttlePerMinute }), preamble: [...h.preamble], warmup: [...h.warmup],
|
|
583
|
+
acts: db.prepare('SELECT * FROM activities ORDER BY act_index').all().map(activityWithContent),
|
|
584
|
+
runtime: { nextActIndex: h.nextActIndex, observations, leases }
|
|
1088
585
|
};
|
|
1089
586
|
}
|
|
1090
|
-
/**
|
|
587
|
+
/** Read-only integrity inspection. */
|
|
1091
588
|
export function inspectSqlite(db) {
|
|
1092
|
-
return inspectRows(db, seat);
|
|
1093
|
-
}
|
|
1094
|
-
function inspectRows(db, inspectSeat) {
|
|
1095
589
|
const check = db.prepare('PRAGMA integrity_check').all();
|
|
1096
590
|
requireFact(check.length === 1 && check[0].integrity_check === 'ok', 'SQLite integrity check failed.');
|
|
1097
591
|
const state = exportSqliteState(db), d = createSqliteDriver(db);
|
|
1098
592
|
d.roster();
|
|
1099
|
-
for (const row of db.prepare('SELECT * FROM seats').all())
|
|
1100
|
-
inspectSeat(row);
|
|
1101
|
-
for (const row of db.prepare('SELECT * FROM delivery_evidence').all())
|
|
1102
|
-
deliveryEvidence(row);
|
|
1103
|
-
if (Number(db.prepare('PRAGMA user_version').get().user_version) >= V5_USER_VERSION) {
|
|
1104
|
-
for (const row of db.prepare('SELECT * FROM delivery_deferrals').all())
|
|
1105
|
-
deliveryDeferral(row);
|
|
1106
|
-
}
|
|
1107
593
|
for (const p of d.roster()) {
|
|
1108
594
|
d.attention(p.name);
|
|
1109
595
|
d.incomingAttention(p.name);
|
|
1110
596
|
}
|
|
1111
597
|
for (const a of state.acts)
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
try {
|
|
1116
|
-
initializeSqlite(rebuilt, state, d.header().revision);
|
|
1117
|
-
// A migration may remove the activity that established canonical casing.
|
|
1118
|
-
// Retained participant spelling stays authoritative while replay checks
|
|
1119
|
-
// every derived lifecycle/count fact against surviving activities.
|
|
1120
|
-
const retainName = rebuilt.prepare('UPDATE participants SET name_json=? WHERE name_key_json=?');
|
|
1121
|
-
for (const p of d.roster())
|
|
1122
|
-
retainName.run(encode(p.name), key(p.name));
|
|
1123
|
-
for (const table of ['participants', 'attention', 'landed_audience']) {
|
|
1124
|
-
const original = db.prepare(`SELECT * FROM ${table} ORDER BY 1,2`).all();
|
|
1125
|
-
const expected = rebuilt.prepare(`SELECT * FROM ${table} ORDER BY 1,2`).all();
|
|
1126
|
-
requireFact(stableJson(original) === stableJson(expected), `derived ${table} index is inconsistent.`);
|
|
1127
|
-
}
|
|
1128
|
-
return { standingCount: d.roster().filter(p => p.joined).length };
|
|
1129
|
-
}
|
|
1130
|
-
finally {
|
|
1131
|
-
rebuilt.close();
|
|
1132
|
-
}
|
|
598
|
+
if (a.kind === 'say')
|
|
599
|
+
d.audience(a.index);
|
|
600
|
+
return { standingCount: d.roster().filter(p => p.joined).length };
|
|
1133
601
|
}
|