@astrosheep/square 0.3.86 → 0.3.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact.d.ts +6 -1
- package/dist/artifact.js +79 -10
- package/dist/boundary-presentation.d.ts +5 -4
- package/dist/boundary-presentation.js +21 -25
- package/dist/catch-decisions.js +2 -1
- package/dist/claude-delivery-status.d.ts +8 -2
- package/dist/claude-delivery-status.js +26 -10
- package/dist/claude-delivery.d.ts +8 -4
- package/dist/claude-delivery.js +114 -104
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +119 -105
- package/dist/cli/context.d.ts +3 -1
- package/dist/cli/context.js +25 -1
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +14 -10
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +1 -0
- package/dist/cli/registry.js +0 -2
- package/dist/cli/square-commands.d.ts +4 -2
- package/dist/cli/square-commands.js +21 -6
- package/dist/codex-hook.d.ts +3 -3
- package/dist/codex-hook.js +4 -4
- package/dist/codex-queue.d.ts +5 -6
- package/dist/codex-queue.js +9 -5
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +18 -6
- package/dist/delivery-health.d.ts +32 -2
- package/dist/delivery-health.js +108 -45
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +155 -196
- package/dist/delivery.d.ts +14 -23
- package/dist/delivery.js +48 -18
- package/dist/file-lock.d.ts +8 -1
- package/dist/file-lock.js +2 -0
- package/dist/help.js +8 -11
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -3
- package/dist/list.js +9 -4
- package/dist/mcp.js +12 -1
- package/dist/model.d.ts +39 -5
- package/dist/model.js +84 -0
- package/dist/notifications.d.ts +8 -6
- package/dist/notifications.js +56 -143
- package/dist/operation-presentation.js +7 -4
- package/dist/participant-identity.d.ts +0 -3
- package/dist/participant-identity.js +0 -31
- package/dist/paseo-delivery.d.ts +5 -6
- package/dist/paseo-delivery.js +13 -8
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/delivery.d.ts +8 -3
- package/dist/plugins/opencode/delivery.js +187 -132
- package/dist/plugins/opencode/index.js +12 -38
- package/dist/ports.d.ts +112 -40
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +51 -35
- package/dist/presentation.js +8 -3
- package/dist/seats.d.ts +7 -0
- package/dist/seats.js +27 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +10 -12
- package/dist/sqlite-square.d.ts +20 -9
- package/dist/sqlite-square.js +476 -341
- package/dist/square-api.d.ts +1 -0
- package/dist/square-api.js +3 -4
- package/dist/square-core.d.ts +3 -2
- package/dist/square-domain.d.ts +12 -5
- package/dist/square-domain.js +163 -7
- package/dist/square-facade.d.ts +16 -10
- package/dist/square-index.d.ts +24 -0
- package/dist/square-index.js +131 -0
- package/dist/square-legacy-framing.d.ts +9 -0
- package/dist/square-legacy-framing.js +56 -0
- package/dist/square-migration-host-ledger.d.ts +26 -0
- package/dist/square-migration-host-ledger.js +168 -0
- package/dist/square-migration.d.ts +5 -5
- package/dist/square-migration.js +373 -71
- package/dist/square-sqlite.d.ts +9 -9
- package/dist/square-sqlite.js +329 -79
- package/dist/square-stdio.d.ts +1 -1
- package/dist/square-stdio.js +12 -7
- package/dist/square-tools.d.ts +1 -1
- package/dist/square-tools.js +80 -27
- package/dist/stdio-server.js +4 -2
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +52 -70
- package/dist/wakes.d.ts +6 -5
- package/dist/wakes.js +20 -17
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +9 -9
- package/guides/mcp.md +2 -2
- package/guides/migration.md +107 -67
- package/guides/pi-tools.md +1 -1
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +61 -28
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/register.js +1 -1
- package/plugins/claude/skills/square/SKILL.md +4 -4
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +170 -118
- package/plugins/opencode/index.ts +12 -30
- package/plugins/pi/square-pi.js +56 -18
- package/skills/square/SKILL.md +4 -4
- package/dist/cli/ledger-command.d.ts +0 -12
- package/dist/cli/ledger-command.js +0 -72
- package/dist/host-ledger-file-adapter.d.ts +0 -47
- package/dist/host-ledger-file-adapter.js +0 -384
- package/dist/host-ledger-migration.d.ts +0 -27
- package/dist/host-ledger-migration.js +0 -264
- package/dist/host-ledger-root.d.ts +0 -2
- package/dist/host-ledger-root.js +0 -9
- package/dist/host-ledger-sqlite.d.ts +0 -65
- package/dist/host-ledger-sqlite.js +0 -368
- package/dist/host-ledger.d.ts +0 -195
- package/dist/host-ledger.js +0 -1
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -38
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -23
- package/dist/participant-host.js +0 -92
- package/dist/registry.d.ts +0 -55
- package/dist/registry.js +0 -123
- package/dist/routes.d.ts +0 -39
- package/dist/routes.js +0 -99
- package/dist/square-actions.d.ts +0 -56
- package/dist/square-actions.js +0 -353
- package/dist/square-projections.d.ts +0 -31
- package/dist/square-projections.js +0 -64
- package/dist/wake-attempts.d.ts +0 -10
- package/dist/wake-attempts.js +0 -39
- package/dist/wake-eligibility.d.ts +0 -53
- package/dist/wake-eligibility.js +0 -123
- package/dist/wake-evidence.d.ts +0 -33
- package/dist/wake-evidence.js +0 -32
- package/guides/host-ledger.md +0 -58
- package/guides/indexed-host-ledger-performance.md +0 -39
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { SquareError, nameKey } from './model.js';
|
|
7
|
-
import { inSqliteTransaction, retrySqliteBusy } from './sqlite-transaction.js';
|
|
8
|
-
export const LEDGER_FILENAME = 'host-ledger.sqlite';
|
|
9
|
-
export const LEDGER_APPLICATION_ID = 0x5351484c;
|
|
10
|
-
export const LEDGER_VERSION = 2;
|
|
11
|
-
export const LEDGER_RETENTION = 7 * 86400000;
|
|
12
|
-
const tables = { presence: 'host_presence', evidence: 'host_evidence' };
|
|
13
|
-
const columns = {
|
|
14
|
-
presence: ['location_key', 'participant_key', 'session_key', 'channel_key', 'effective_at'],
|
|
15
|
-
evidence: ['location_key', 'participant_key', 'session_key', 'activity_key', 'kind', 'outcome', 'claim_token_key', 'effective_at'],
|
|
16
|
-
};
|
|
17
|
-
const indexes = {
|
|
18
|
-
host_presence_identity: ['host_presence', 'location_key', 'participant_key', 'seq'],
|
|
19
|
-
host_presence_session: ['host_presence', 'session_key', 'seq'],
|
|
20
|
-
host_presence_time: ['host_presence', 'effective_at'],
|
|
21
|
-
host_evidence_attention: ['host_evidence', 'location_key', 'participant_key', 'activity_key', 'kind', 'session_key', 'seq'],
|
|
22
|
-
host_evidence_kind_session: ['host_evidence', 'kind', 'session_key', 'seq'],
|
|
23
|
-
host_evidence_session: ['host_evidence', 'session_key', 'seq'],
|
|
24
|
-
host_evidence_token: ['host_evidence', 'location_key', 'participant_key', 'activity_key', 'session_key', 'claim_token_key', 'seq'],
|
|
25
|
-
host_evidence_outcome_time: ['host_evidence', 'outcome', 'effective_at'],
|
|
26
|
-
host_evidence_time: ['host_evidence', 'effective_at'],
|
|
27
|
-
host_evidence_native_token: ['host_evidence', 'session_key', 'claim_token_key', 'seq'],
|
|
28
|
-
};
|
|
29
|
-
const tableSchema = {
|
|
30
|
-
host_meta: 'CREATE TABLE host_meta (id INTEGER PRIMARY KEY CHECK(id=1), presence_revision INTEGER NOT NULL, evidence_revision INTEGER NOT NULL)',
|
|
31
|
-
host_presence: 'CREATE TABLE host_presence (seq INTEGER PRIMARY KEY AUTOINCREMENT, location_key TEXT NOT NULL, participant_key TEXT NOT NULL, session_key TEXT NOT NULL, channel_key TEXT NOT NULL, effective_at REAL NOT NULL, row_json TEXT NOT NULL)',
|
|
32
|
-
host_evidence: 'CREATE TABLE host_evidence (seq INTEGER PRIMARY KEY AUTOINCREMENT, location_key TEXT NOT NULL, participant_key TEXT NOT NULL, session_key TEXT NOT NULL, activity_key TEXT NOT NULL, kind TEXT NOT NULL, outcome TEXT NOT NULL, claim_token_key TEXT, effective_at REAL NOT NULL, row_json TEXT NOT NULL)',
|
|
33
|
-
host_native_tokens: "CREATE TABLE host_native_tokens (harness TEXT NOT NULL CHECK(harness='claude'), session_key TEXT NOT NULL, token_key TEXT NOT NULL, PRIMARY KEY(harness,session_key,token_key)) WITHOUT ROWID",
|
|
34
|
-
};
|
|
35
|
-
const versionTwoNames = new Set(['host_native_tokens', 'host_evidence_native_token']);
|
|
36
|
-
const retainedEvidence = "(kind='wake' AND (outcome IN ('accepted','unknown','dispatching') OR COALESCE(json_extract(row_json,'$.nativeDelivery.harness'),'')='claude'))";
|
|
37
|
-
function validToken(token) { return typeof token === 'string' && /^[a-zA-Z0-9-]+$/.test(token); }
|
|
38
|
-
function indexSql(name, [table, ...fields]) {
|
|
39
|
-
return `CREATE INDEX ${name} ON ${table} (${fields.join(',')})`;
|
|
40
|
-
}
|
|
41
|
-
function invalid(message) { return new Error(`Invalid host ledger: ${message}`); }
|
|
42
|
-
function key(value) { return JSON.stringify(value); }
|
|
43
|
-
/** The complete record remains authority; these are only reproducible lookup projections. */
|
|
44
|
-
export function ledgerRecordColumns(kind, input) {
|
|
45
|
-
const row = input;
|
|
46
|
-
if (!row || Array.isArray(row) || row.v !== 1)
|
|
47
|
-
throw invalid('record version');
|
|
48
|
-
for (const field of ['location', 'participant', 'session', ...(kind === 'presence' ? ['channel'] : ['activity', 'kind', 'outcome'])]) {
|
|
49
|
-
if (typeof row[field] !== 'string')
|
|
50
|
-
throw invalid(`record ${field}`);
|
|
51
|
-
}
|
|
52
|
-
if (kind === 'evidence' && row.claimToken !== undefined && typeof row.claimToken !== 'string')
|
|
53
|
-
throw invalid('record claimToken');
|
|
54
|
-
const at = row.at ?? row.updatedAt;
|
|
55
|
-
if (typeof at !== 'number' || !Number.isFinite(at))
|
|
56
|
-
throw invalid('record time');
|
|
57
|
-
return {
|
|
58
|
-
location_key: key(row.location), participant_key: key(nameKey(row.participant)), session_key: key(row.session),
|
|
59
|
-
...(kind === 'presence' ? { channel_key: key(row.channel) } : {
|
|
60
|
-
activity_key: key(row.activity), kind: row.kind, outcome: row.outcome,
|
|
61
|
-
claim_token_key: row.claimToken === undefined ? null : key(row.claimToken),
|
|
62
|
-
}), effective_at: at,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
export function initializeLedger(db) {
|
|
66
|
-
db.exec(`PRAGMA journal_mode=DELETE; PRAGMA synchronous=FULL;
|
|
67
|
-
PRAGMA application_id=${LEDGER_APPLICATION_ID}; PRAGMA user_version=${LEDGER_VERSION};`);
|
|
68
|
-
initializeLedgerSchema(db);
|
|
69
|
-
}
|
|
70
|
-
export function setLedgerRevisions(db, revisions) {
|
|
71
|
-
if (![revisions.presence, revisions.evidence].every((n) => Number.isSafeInteger(n) && n >= 0))
|
|
72
|
-
throw invalid('revisions');
|
|
73
|
-
db.prepare('UPDATE host_meta SET presence_revision=?,evidence_revision=? WHERE id=1').run(revisions.presence, revisions.evidence);
|
|
74
|
-
}
|
|
75
|
-
export function insertLedgerRecord(db, kind, row) {
|
|
76
|
-
const derived = ledgerRecordColumns(kind, row);
|
|
77
|
-
db.prepare(`INSERT INTO ${tables[kind]} (${columns[kind].join(',')},row_json) VALUES (${columns[kind].map(() => '?').join(',')},?)`)
|
|
78
|
-
.run(...columns[kind].map((column) => derived[column]), JSON.stringify(row));
|
|
79
|
-
}
|
|
80
|
-
export function ledgerRevisions(db) {
|
|
81
|
-
const rows = db.prepare('SELECT id,presence_revision,evidence_revision FROM host_meta').all();
|
|
82
|
-
if (rows.length !== 1 || rows[0].id !== 1 || ![rows[0].presence_revision, rows[0].evidence_revision].every((n) => typeof n === 'number' && Number.isSafeInteger(n) && n >= 0))
|
|
83
|
-
throw invalid('revisions');
|
|
84
|
-
return { presence: rows[0].presence_revision, evidence: rows[0].evidence_revision };
|
|
85
|
-
}
|
|
86
|
-
export function validateLedger(db) { validateLedgerAtVersion(db, LEDGER_VERSION); }
|
|
87
|
-
function validateLedgerAtVersion(db, version) {
|
|
88
|
-
validateLedgerHeader(db, version);
|
|
89
|
-
if (db.prepare('PRAGMA journal_mode').get()?.journal_mode !== 'delete')
|
|
90
|
-
throw invalid('journal mode');
|
|
91
|
-
const expected = new Map([...Object.entries(tableSchema), ...Object.entries(indexes).map(([name, fields]) => [name, indexSql(name, fields)])].filter(([name]) => version >= 2 || !versionTwoNames.has(name)));
|
|
92
|
-
const schema = db.prepare("SELECT name,sql FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%'").all();
|
|
93
|
-
const normalize = (sql) => sql.replace(/\s+/g, '').replace(/;$/, '');
|
|
94
|
-
if (schema.length !== expected.size || schema.some((row) => typeof row.sql !== 'string' || !expected.has(row.name) || normalize(row.sql) !== normalize(expected.get(row.name))))
|
|
95
|
-
throw invalid('schema or indexes');
|
|
96
|
-
ledgerRevisions(db);
|
|
97
|
-
}
|
|
98
|
-
export function verifyLedger(db) {
|
|
99
|
-
return verifyLedgerAtVersion(db, LEDGER_VERSION);
|
|
100
|
-
}
|
|
101
|
-
function verifyLedgerAtVersion(db, version) {
|
|
102
|
-
validateLedgerAtVersion(db, version);
|
|
103
|
-
const integrity = db.prepare('PRAGMA integrity_check').all();
|
|
104
|
-
if (integrity.length !== 1 || integrity[0].integrity_check !== 'ok')
|
|
105
|
-
throw invalid('integrity check failed');
|
|
106
|
-
const counts = { presence: 0, evidence: 0 };
|
|
107
|
-
for (const kind of ['presence', 'evidence']) {
|
|
108
|
-
for (const stored of db.prepare(`SELECT * FROM ${tables[kind]} ORDER BY seq ASC`).iterate()) {
|
|
109
|
-
let row;
|
|
110
|
-
try {
|
|
111
|
-
row = JSON.parse(stored.row_json);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
throw invalid('record JSON');
|
|
115
|
-
}
|
|
116
|
-
const derived = ledgerRecordColumns(kind, row);
|
|
117
|
-
if (!Number.isSafeInteger(stored.seq) || stored.seq < 1 || columns[kind].some((column) => stored[column] !== derived[column]))
|
|
118
|
-
throw invalid('record projections');
|
|
119
|
-
counts[kind] += 1;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (version >= 2) {
|
|
123
|
-
for (const row of db.prepare('SELECT harness,session_key,token_key FROM host_native_tokens').iterate()) {
|
|
124
|
-
try {
|
|
125
|
-
const session = JSON.parse(row.session_key), token = JSON.parse(row.token_key);
|
|
126
|
-
if (row.harness !== 'claude' || typeof session !== 'string' || !session.length || !validToken(token)
|
|
127
|
-
|| key(session) !== row.session_key || key(token) !== row.token_key)
|
|
128
|
-
throw invalid('native token codec');
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
throw invalid('native token codec');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const revision = ledgerRevisions(db);
|
|
136
|
-
if ((counts.presence && !revision.presence) || (counts.evidence && !revision.evidence))
|
|
137
|
-
throw invalid('records without revision');
|
|
138
|
-
return { ...counts, presenceRevision: revision.presence, evidenceRevision: revision.evidence };
|
|
139
|
-
}
|
|
140
|
-
function inspectLegacyRoot(root) {
|
|
141
|
-
return fs.existsSync(path.join(root, 'presence.ndjsonl')) || fs.existsSync(path.join(root, 'evidence.ndjsonl'));
|
|
142
|
-
}
|
|
143
|
-
/** Read the file-type stamp and format version without making the runtime authority. */
|
|
144
|
-
export function inspectLedgerIdentity(db) {
|
|
145
|
-
const application = db.prepare('PRAGMA application_id').get().application_id;
|
|
146
|
-
const version = db.prepare('PRAGMA user_version').get().user_version;
|
|
147
|
-
if (application !== LEDGER_APPLICATION_ID)
|
|
148
|
-
return 'unrelated';
|
|
149
|
-
if (version === LEDGER_VERSION)
|
|
150
|
-
return 'current';
|
|
151
|
-
if (version === 1)
|
|
152
|
-
return 'old-sqlite';
|
|
153
|
-
return 'unsupported';
|
|
154
|
-
}
|
|
155
|
-
/** Explicit offline migration only; the caller owns the write transaction. Preserve all records and revisions. */
|
|
156
|
-
export function upgradeLedger(db) {
|
|
157
|
-
if (db.prepare('PRAGMA user_version').get()?.user_version === LEDGER_VERSION) {
|
|
158
|
-
verifyLedger(db);
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
verifyLedgerAtVersion(db, 1);
|
|
162
|
-
db.exec(tableSchema.host_native_tokens);
|
|
163
|
-
db.exec(indexSql('host_evidence_native_token', indexes.host_evidence_native_token));
|
|
164
|
-
db.exec(`PRAGMA user_version=${LEDGER_VERSION}`);
|
|
165
|
-
verifyLedger(db);
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
function exists(file) { try {
|
|
169
|
-
fs.statSync(file);
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
if (error.code === 'ENOENT')
|
|
174
|
-
return false;
|
|
175
|
-
throw error;
|
|
176
|
-
} }
|
|
177
|
-
export function migrationRequired(root) {
|
|
178
|
-
return new SquareError('invalid_args', `Host ledger at ${root} needs offline migration.\nStop sessions and MCP services using it, back it up, then migrate and verify from the same environment before restarting.`, { reason: 'migration_required', ledgerRoot: root });
|
|
179
|
-
}
|
|
180
|
-
/** Compact runtime framing. Full schema, projections and integrity belong to explicit verify. */
|
|
181
|
-
function validateLedgerHeader(db, version = LEDGER_VERSION) {
|
|
182
|
-
if (db.prepare('PRAGMA application_id').get()?.application_id !== LEDGER_APPLICATION_ID)
|
|
183
|
-
throw invalid('unrelated identity.');
|
|
184
|
-
if (db.prepare('PRAGMA user_version').get()?.user_version !== version)
|
|
185
|
-
throw invalid('unsupported format version.');
|
|
186
|
-
}
|
|
187
|
-
/** The runtime's only upgrade door: a supported old ledger is pointed at explicit migration. */
|
|
188
|
-
function runtimeLedgerHeader(db, root) {
|
|
189
|
-
try {
|
|
190
|
-
validateLedgerHeader(db);
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
if (inspectLedgerIdentity(db) === 'old-sqlite')
|
|
194
|
-
throw migrationRequired(root);
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
/** Publish new empty authority only. Never initialize or replace an existing destination. */
|
|
199
|
-
function createEmptyLedger(root) {
|
|
200
|
-
fs.mkdirSync(root, { recursive: true });
|
|
201
|
-
try {
|
|
202
|
-
fs.writeFileSync(path.join(root, '.gitignore'), '*\n', { flag: 'wx' });
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
if (error.code !== 'EEXIST')
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
const temporary = path.join(root, `.host-ledger.${process.pid}.${randomUUID()}.tmp`);
|
|
209
|
-
let db;
|
|
210
|
-
try {
|
|
211
|
-
fs.closeSync(fs.openSync(temporary, 'wx', 0o600));
|
|
212
|
-
db = new DatabaseSync(temporary);
|
|
213
|
-
initializeLedger(db);
|
|
214
|
-
db.close();
|
|
215
|
-
db = undefined;
|
|
216
|
-
try {
|
|
217
|
-
fs.linkSync(temporary, path.join(root, LEDGER_FILENAME));
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (error.code !== 'EEXIST')
|
|
221
|
-
throw error;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
finally {
|
|
225
|
-
db?.close();
|
|
226
|
-
fs.rmSync(temporary, { force: true });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
/** Never create authority on a read, or silently replace legacy authority on a write. */
|
|
230
|
-
async function openLedger(root, write, signal) {
|
|
231
|
-
const file = path.join(root, LEDGER_FILENAME);
|
|
232
|
-
if (!exists(file)) {
|
|
233
|
-
if (inspectLegacyRoot(root))
|
|
234
|
-
throw migrationRequired(root);
|
|
235
|
-
if (!write)
|
|
236
|
-
return undefined;
|
|
237
|
-
if (signal?.aborted)
|
|
238
|
-
throw signal.reason;
|
|
239
|
-
createEmptyLedger(root);
|
|
240
|
-
}
|
|
241
|
-
const uri = pathToFileURL(file);
|
|
242
|
-
uri.searchParams.set('mode', write ? 'rw' : 'ro');
|
|
243
|
-
const db = new DatabaseSync(uri.href);
|
|
244
|
-
try {
|
|
245
|
-
await retrySqliteBusy(() => db.exec('PRAGMA busy_timeout=0; PRAGMA synchronous=FULL'), signal);
|
|
246
|
-
return db;
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
db.close();
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
function initializeLedgerSchema(db) {
|
|
254
|
-
for (const sql of Object.values(tableSchema))
|
|
255
|
-
db.exec(sql);
|
|
256
|
-
db.exec('INSERT INTO host_meta VALUES(1,0,0)');
|
|
257
|
-
for (const [name, fields] of Object.entries(indexes))
|
|
258
|
-
db.exec(indexSql(name, fields));
|
|
259
|
-
}
|
|
260
|
-
function visibility(kind, now, includeFuture, includeExpired) {
|
|
261
|
-
const durable = kind === 'evidence' ? ` OR ${retainedEvidence}` : '';
|
|
262
|
-
const sql = includeExpired ? '1' : `(effective_at>=?${durable})`;
|
|
263
|
-
const args = includeExpired ? [] : [now - LEDGER_RETENTION];
|
|
264
|
-
if (!includeFuture)
|
|
265
|
-
args.push(now);
|
|
266
|
-
return { sql: `${sql}${includeFuture ? '' : ' AND effective_at<=?'}`, args };
|
|
267
|
-
}
|
|
268
|
-
/** Transaction-local storage rows; sequence coordinates never cross HostLedgerPort. */
|
|
269
|
-
export class LedgerTransaction {
|
|
270
|
-
db;
|
|
271
|
-
changed = new Set();
|
|
272
|
-
constructor(db) {
|
|
273
|
-
this.db = db;
|
|
274
|
-
}
|
|
275
|
-
presence(input, now, includeFuture = false) {
|
|
276
|
-
return this.select('presence', input, now, includeFuture);
|
|
277
|
-
}
|
|
278
|
-
evidence(input, now, includeFuture = false) {
|
|
279
|
-
return this.select('evidence', input, now, includeFuture);
|
|
280
|
-
}
|
|
281
|
-
select(kind, input, now, includeFuture) {
|
|
282
|
-
const visible = visibility(kind, now, includeFuture, kind === 'evidence' && input.includeExpired === true), where = [visible.sql], args = visible.args;
|
|
283
|
-
for (const [field, column] of Object.entries({ location: 'location_key', participant: 'participant_key', session: 'session_key', ...(kind === 'evidence' ? { activity: 'activity_key', kind: 'kind', claimToken: 'claim_token_key' } : { channel: 'channel_key' }) })) {
|
|
284
|
-
const value = input[field];
|
|
285
|
-
if (value !== undefined) {
|
|
286
|
-
where.push(`${column}=?`);
|
|
287
|
-
args.push(column === 'kind' ? value : key(field === 'participant' ? nameKey(value) : value));
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (kind === 'evidence' && input.includeReleased !== true)
|
|
291
|
-
where.push("outcome<>'released'");
|
|
292
|
-
return this.db.prepare(`SELECT seq,row_json FROM ${tables[kind]} WHERE ${where.join(' AND ')} ORDER BY seq ASC`).all(...args).map((stored) => ({ seq: stored.seq, record: JSON.parse(stored.row_json) }));
|
|
293
|
-
}
|
|
294
|
-
/** Both halves of recognition are read in the enclosing SQLite snapshot, with indexed session/token predicates. */
|
|
295
|
-
nativeTokens(input) {
|
|
296
|
-
const tokens = [...new Set(input.tokens)], encoded = JSON.stringify(tokens.map(key));
|
|
297
|
-
const deliveries = this.db.prepare("SELECT row_json FROM host_evidence WHERE session_key=? AND claim_token_key IN (SELECT value FROM json_each(?)) AND kind='wake' AND json_extract(row_json,'$.nativeDelivery.harness')='claude' ORDER BY seq ASC")
|
|
298
|
-
.all(key(input.session), encoded).map(row => JSON.parse(row.row_json));
|
|
299
|
-
const retired = new Set(this.db.prepare('SELECT token_key FROM host_native_tokens WHERE harness=? AND session_key=? AND token_key IN (SELECT value FROM json_each(?))')
|
|
300
|
-
.all(input.harness, key(input.session), encoded).map(row => row.token_key));
|
|
301
|
-
return { deliveries, retired: tokens.filter(token => retired.has(key(token))) };
|
|
302
|
-
}
|
|
303
|
-
/** Called only after exact-record CAS. No housekeeping of unrelated rows belongs here. */
|
|
304
|
-
retireEvidence(row) {
|
|
305
|
-
const record = row.record;
|
|
306
|
-
if (record.outcome === 'dispatching')
|
|
307
|
-
return false;
|
|
308
|
-
if (record.nativeDelivery?.harness === 'claude') {
|
|
309
|
-
const native = record.nativeDelivery;
|
|
310
|
-
if (!record.session || !validToken(record.claimToken) || typeof native.payload !== 'string' || !native.payload.length
|
|
311
|
-
|| typeof native.endpoint !== 'string' || !native.endpoint.length || !Number.isSafeInteger(native.epoch) || native.epoch < 0)
|
|
312
|
-
return false;
|
|
313
|
-
this.db.prepare("INSERT OR IGNORE INTO host_native_tokens(harness,session_key,token_key) VALUES('claude',?,?)")
|
|
314
|
-
.run(key(record.session), key(record.claimToken));
|
|
315
|
-
}
|
|
316
|
-
this.remove('evidence', [row]);
|
|
317
|
-
return true;
|
|
318
|
-
}
|
|
319
|
-
insert(kind, row) { insertLedgerRecord(this.db, kind, row); this.changed.add(kind); }
|
|
320
|
-
replace(kind, seq, row) {
|
|
321
|
-
const derived = ledgerRecordColumns(kind, row);
|
|
322
|
-
const result = this.db.prepare(`UPDATE ${tables[kind]} SET ${columns[kind].map((column) => `${column}=?`).join(',')},row_json=? WHERE seq=?`).run(...columns[kind].map((column) => derived[column]), JSON.stringify(row), seq);
|
|
323
|
-
if (result.changes)
|
|
324
|
-
this.changed.add(kind);
|
|
325
|
-
}
|
|
326
|
-
remove(kind, rows) {
|
|
327
|
-
const statement = this.db.prepare(`DELETE FROM ${tables[kind]} WHERE seq=?`);
|
|
328
|
-
for (const row of rows)
|
|
329
|
-
if (statement.run(row.seq).changes)
|
|
330
|
-
this.changed.add(kind);
|
|
331
|
-
}
|
|
332
|
-
/** Only invoke at the point where the former implementation published a mutation. */
|
|
333
|
-
publish(kind, now) {
|
|
334
|
-
const durable = kind === 'evidence' ? ` AND NOT ${retainedEvidence}` : '';
|
|
335
|
-
if (this.db.prepare(`DELETE FROM ${tables[kind]} WHERE effective_at<?${durable}`).run(now - LEDGER_RETENTION).changes)
|
|
336
|
-
this.changed.add(kind);
|
|
337
|
-
}
|
|
338
|
-
commitRevisions() {
|
|
339
|
-
for (const kind of this.changed)
|
|
340
|
-
this.db.exec(`UPDATE host_meta SET ${kind}_revision=${kind}_revision+1 WHERE id=1`);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
export async function withLedger(root, write, select, absent, signal) {
|
|
344
|
-
const db = await openLedger(root, write, signal);
|
|
345
|
-
if (!db)
|
|
346
|
-
return absent;
|
|
347
|
-
try {
|
|
348
|
-
return await inSqliteTransaction(db, write, () => runtimeLedgerHeader(db, root), () => {
|
|
349
|
-
const rows = new LedgerTransaction(db), result = select(rows);
|
|
350
|
-
rows.commitRevisions();
|
|
351
|
-
return result;
|
|
352
|
-
}, signal);
|
|
353
|
-
}
|
|
354
|
-
finally {
|
|
355
|
-
db.close();
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
export async function readLedgerRevisions(root, signal) {
|
|
359
|
-
const db = await openLedger(root, false, signal);
|
|
360
|
-
if (!db)
|
|
361
|
-
return undefined;
|
|
362
|
-
try {
|
|
363
|
-
return await inSqliteTransaction(db, false, () => runtimeLedgerHeader(db, root), () => ledgerRevisions(db), signal);
|
|
364
|
-
}
|
|
365
|
-
finally {
|
|
366
|
-
db.close();
|
|
367
|
-
}
|
|
368
|
-
}
|
package/dist/host-ledger.d.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import type { WakeRouteKind } from './model.js';
|
|
2
|
-
import type { HarnessSessionChannel } from './participant-identity.js';
|
|
3
|
-
export type PresenceChannel = HarnessSessionChannel | 'unknown';
|
|
4
|
-
export interface PresenceRecord {
|
|
5
|
-
readonly location: string;
|
|
6
|
-
readonly participant: string;
|
|
7
|
-
readonly session: string;
|
|
8
|
-
readonly channel: PresenceChannel;
|
|
9
|
-
readonly updatedAt?: number;
|
|
10
|
-
readonly epoch?: number;
|
|
11
|
-
readonly cancelledThrough?: number;
|
|
12
|
-
}
|
|
13
|
-
export type PresenceKey = Pick<PresenceRecord, 'location' | 'participant' | 'session' | 'channel'>;
|
|
14
|
-
export interface PresenceLookup {
|
|
15
|
-
readonly location?: string;
|
|
16
|
-
readonly participant?: string;
|
|
17
|
-
readonly session?: string;
|
|
18
|
-
readonly now?: number;
|
|
19
|
-
}
|
|
20
|
-
export type PresenceResult = {
|
|
21
|
-
readonly status: 'ensured';
|
|
22
|
-
readonly record: PresenceRecord;
|
|
23
|
-
} | {
|
|
24
|
-
readonly status: 'degraded';
|
|
25
|
-
readonly record: PresenceRecord;
|
|
26
|
-
readonly error: unknown;
|
|
27
|
-
};
|
|
28
|
-
export type PresenceClaimResult = {
|
|
29
|
-
readonly status: 'acquired' | 'owned';
|
|
30
|
-
readonly record: PresenceRecord;
|
|
31
|
-
} | {
|
|
32
|
-
readonly status: 'busy';
|
|
33
|
-
readonly record: PresenceRecord;
|
|
34
|
-
} | {
|
|
35
|
-
readonly status: 'degraded';
|
|
36
|
-
readonly record: PresenceRecord;
|
|
37
|
-
readonly error: unknown;
|
|
38
|
-
};
|
|
39
|
-
export type NativeDeliveryEvidence = {
|
|
40
|
-
readonly payload: string;
|
|
41
|
-
readonly epoch: number;
|
|
42
|
-
} & ({
|
|
43
|
-
readonly harness: 'claude';
|
|
44
|
-
readonly endpoint: string;
|
|
45
|
-
readonly messageId?: string;
|
|
46
|
-
} | {
|
|
47
|
-
readonly harness: 'opencode';
|
|
48
|
-
readonly inputId: string;
|
|
49
|
-
});
|
|
50
|
-
export interface NativeDeliveryObservation {
|
|
51
|
-
readonly eventId: string;
|
|
52
|
-
readonly messageId: string;
|
|
53
|
-
readonly state: 'held' | 'released' | 'dropped';
|
|
54
|
-
readonly reason?: string;
|
|
55
|
-
readonly observedAt: number;
|
|
56
|
-
}
|
|
57
|
-
export interface EvidenceRecord {
|
|
58
|
-
readonly nativeObservations?: readonly NativeDeliveryObservation[];
|
|
59
|
-
readonly bindingEpoch?: number;
|
|
60
|
-
readonly nativeDelivery?: NativeDeliveryEvidence;
|
|
61
|
-
readonly location: string;
|
|
62
|
-
readonly participant: string;
|
|
63
|
-
readonly session: string;
|
|
64
|
-
readonly activity: string;
|
|
65
|
-
readonly kind: 'wake' | 'presentation' | 'lifecycle';
|
|
66
|
-
readonly outcome: string;
|
|
67
|
-
readonly at?: number;
|
|
68
|
-
readonly expiresAt?: number;
|
|
69
|
-
readonly routeKind?: WakeRouteKind;
|
|
70
|
-
readonly signature?: string;
|
|
71
|
-
readonly attemptN?: number;
|
|
72
|
-
readonly message?: string;
|
|
73
|
-
readonly diagnostic?: unknown;
|
|
74
|
-
readonly claimToken?: string;
|
|
75
|
-
readonly ownerPid?: number;
|
|
76
|
-
readonly unknownSource?: 'interrupted' | 'transport';
|
|
77
|
-
}
|
|
78
|
-
/** Presentation and lifecycle claims only: wake attempts are acquired by claimWakeAttempt. */
|
|
79
|
-
export interface EvidenceClaim {
|
|
80
|
-
readonly location: string;
|
|
81
|
-
readonly participant: string;
|
|
82
|
-
readonly session: string;
|
|
83
|
-
readonly activity: string;
|
|
84
|
-
readonly kind: 'presentation' | 'lifecycle';
|
|
85
|
-
readonly leaseMs: number;
|
|
86
|
-
readonly now?: number;
|
|
87
|
-
}
|
|
88
|
-
/** Release metadata and includeReleased are diagnostic-only; default evidence reads stay behavior-safe. */
|
|
89
|
-
export interface EvidenceRelease {
|
|
90
|
-
readonly location: string;
|
|
91
|
-
readonly participant: string;
|
|
92
|
-
readonly session: string;
|
|
93
|
-
readonly activity: string;
|
|
94
|
-
readonly kind: EvidenceRecord['kind'];
|
|
95
|
-
readonly claimToken: string;
|
|
96
|
-
readonly routeKind?: WakeRouteKind;
|
|
97
|
-
readonly attemptN?: number;
|
|
98
|
-
readonly signature?: string;
|
|
99
|
-
readonly message?: string;
|
|
100
|
-
readonly diagnostic?: unknown;
|
|
101
|
-
readonly now?: number;
|
|
102
|
-
}
|
|
103
|
-
/** includeExpired is maintenance-only: bypass age retention, never future filtering. */
|
|
104
|
-
export interface EvidenceLookup {
|
|
105
|
-
readonly location?: string;
|
|
106
|
-
readonly participant?: string;
|
|
107
|
-
readonly session?: string;
|
|
108
|
-
readonly activity?: string;
|
|
109
|
-
readonly kind?: EvidenceRecord['kind'];
|
|
110
|
-
readonly now?: number;
|
|
111
|
-
readonly includeReleased?: boolean;
|
|
112
|
-
readonly includeExpired?: boolean;
|
|
113
|
-
}
|
|
114
|
-
export interface NativeTokenLookup {
|
|
115
|
-
readonly harness: 'claude';
|
|
116
|
-
readonly session: string;
|
|
117
|
-
readonly tokens: readonly string[];
|
|
118
|
-
}
|
|
119
|
-
/** One snapshot of live correlations and recognition-only retired tokens. Neither tokens nor lookup confer authority. */
|
|
120
|
-
export interface NativeTokenResult {
|
|
121
|
-
readonly deliveries: readonly EvidenceRecord[];
|
|
122
|
-
readonly retired: readonly string[];
|
|
123
|
-
}
|
|
124
|
-
export interface WakeAttention {
|
|
125
|
-
readonly squarePath: string;
|
|
126
|
-
readonly actIndex: number;
|
|
127
|
-
readonly recipient: string;
|
|
128
|
-
}
|
|
129
|
-
/** One wake attempt row carries attention-wide exclusion and session-specific history. */
|
|
130
|
-
export interface WakeAttemptClaimInput {
|
|
131
|
-
readonly attention: WakeAttention;
|
|
132
|
-
readonly session: string;
|
|
133
|
-
readonly routeKind: WakeRouteKind;
|
|
134
|
-
readonly leaseMs: number;
|
|
135
|
-
readonly now?: number;
|
|
136
|
-
}
|
|
137
|
-
export type WakeAttemptClaim = {
|
|
138
|
-
readonly status: 'acquired';
|
|
139
|
-
readonly claimToken: string;
|
|
140
|
-
readonly attemptN: number;
|
|
141
|
-
} | {
|
|
142
|
-
readonly status: 'busy';
|
|
143
|
-
readonly record: EvidenceRecord;
|
|
144
|
-
} | {
|
|
145
|
-
readonly status: 'terminal';
|
|
146
|
-
readonly record: EvidenceRecord;
|
|
147
|
-
} | {
|
|
148
|
-
readonly status: 'degraded';
|
|
149
|
-
readonly error: unknown;
|
|
150
|
-
};
|
|
151
|
-
export interface WakeAttemptLookup {
|
|
152
|
-
readonly attention?: WakeAttention;
|
|
153
|
-
readonly session?: string;
|
|
154
|
-
readonly now?: number;
|
|
155
|
-
}
|
|
156
|
-
export type ClaimResult = {
|
|
157
|
-
readonly status: 'acquired';
|
|
158
|
-
readonly claimToken: string;
|
|
159
|
-
} | {
|
|
160
|
-
readonly status: 'busy' | 'delivered';
|
|
161
|
-
readonly record: EvidenceRecord;
|
|
162
|
-
} | {
|
|
163
|
-
readonly status: 'degraded';
|
|
164
|
-
readonly error: unknown;
|
|
165
|
-
};
|
|
166
|
-
export interface HostLedgerPort {
|
|
167
|
-
withClaimLock<T>(fn: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
168
|
-
claimPresence(input: PresenceRecord, signal?: AbortSignal): Promise<PresenceClaimResult>;
|
|
169
|
-
ensurePresence(input: PresenceRecord): Promise<PresenceResult>;
|
|
170
|
-
suppressPresence(input: PresenceRecord, through: number): Promise<boolean>;
|
|
171
|
-
removePresence(input: PresenceKey): Promise<void>;
|
|
172
|
-
removePresenceIfUnchanged(input: PresenceRecord): Promise<boolean>;
|
|
173
|
-
listPresence(input: PresenceLookup): Promise<readonly PresenceRecord[]>;
|
|
174
|
-
claimEvidence(input: EvidenceClaim): Promise<ClaimResult>;
|
|
175
|
-
releaseEvidence(input: EvidenceRelease): Promise<void>;
|
|
176
|
-
appendEvidence(input: EvidenceRecord & {
|
|
177
|
-
readonly claimToken: string;
|
|
178
|
-
}): Promise<void>;
|
|
179
|
-
listEvidence(input: EvidenceLookup): Promise<readonly EvidenceRecord[]>;
|
|
180
|
-
retireEvidenceIfUnchanged(expected: EvidenceRecord): Promise<boolean>;
|
|
181
|
-
lookupNativeTokens(input: NativeTokenLookup): Promise<NativeTokenResult>;
|
|
182
|
-
listWakeAttempts(input?: WakeAttemptLookup): Promise<readonly EvidenceRecord[]>;
|
|
183
|
-
prepareNativeWake(input: EvidenceRecord & {
|
|
184
|
-
readonly claimToken: string;
|
|
185
|
-
readonly routeKind: WakeRouteKind;
|
|
186
|
-
readonly nativeDelivery: NativeDeliveryEvidence;
|
|
187
|
-
}): Promise<boolean>;
|
|
188
|
-
confirmWakeAdmission(input: EvidenceRecord & {
|
|
189
|
-
readonly claimToken: string;
|
|
190
|
-
}): Promise<boolean>;
|
|
191
|
-
observeNativeDelivery(input: EvidenceRecord & {
|
|
192
|
-
readonly claimToken: string;
|
|
193
|
-
}, observation: NativeDeliveryObservation): Promise<boolean>;
|
|
194
|
-
claimWakeAttempt(input: WakeAttemptClaimInput): Promise<WakeAttemptClaim>;
|
|
195
|
-
}
|
package/dist/host-ledger.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { HostLedgerPort } from './host-ledger.js';
|
|
2
|
-
/** The two host-ledger capabilities maintenance uses; no parallel ledger contract is owned here. */
|
|
3
|
-
type MaintenanceLedger = Pick<HostLedgerPort, 'listEvidence' | 'retireEvidenceIfUnchanged'>;
|
|
4
|
-
export interface LedgerMaintenanceOptions {
|
|
5
|
-
/** Prove eligible rows without retiring anything. */
|
|
6
|
-
readonly dryRun?: boolean;
|
|
7
|
-
readonly now?: number;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Counts are retired rows, not reclaimed bytes: SQLite reuses freed storage without necessarily
|
|
11
|
-
* shrinking its file. Retired Claude correlations leave indefinite recognition tokens; total
|
|
12
|
-
* token count remains unbounded. Maintenance does not run VACUUM.
|
|
13
|
-
*/
|
|
14
|
-
export interface LedgerMaintenanceReport {
|
|
15
|
-
readonly dryRun: boolean;
|
|
16
|
-
/** Old rows whose closure proof passed; exact-record CAS may still refuse. */
|
|
17
|
-
readonly eligible: number;
|
|
18
|
-
/** Rows actually removed by this run; always 0 in a dry run. */
|
|
19
|
-
readonly retired: number;
|
|
20
|
-
/** Eligible rows whose full-record compare-and-swap refused; always 0 in a dry run. */
|
|
21
|
-
readonly casMisses: number;
|
|
22
|
-
/** Canonical locations whose artifact could not be read; their rows were retained. */
|
|
23
|
-
readonly unavailableArtifacts: readonly string[];
|
|
24
|
-
}
|
|
25
|
-
/** Maintenance entry: root selection uses the caller's captured environment. */
|
|
26
|
-
export declare function gcHostLedger(options?: LedgerMaintenanceOptions & {
|
|
27
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
28
|
-
}): Promise<LedgerMaintenanceReport>;
|
|
29
|
-
export declare function maintainHostLedger(ledger: MaintenanceLedger, options?: LedgerMaintenanceOptions): Promise<LedgerMaintenanceReport>;
|
|
30
|
-
export {};
|