@astrosheep/square 0.3.93 → 0.3.94
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.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +2 -2
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/catch-decisions.d.ts +4 -6
- package/dist/catch-decisions.js +4 -4
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -25
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +26 -14
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +2 -2
- package/dist/cli/registry.js +6 -3
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +46 -54
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -11
- package/dist/decisions.js +9 -20
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +38 -38
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +1 -1
- package/dist/help.js +12 -10
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +8 -15
- package/dist/model.js +4 -7
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +6 -5
- package/dist/operation-presentation.d.ts +5 -4
- package/dist/operation-presentation.js +50 -45
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +13 -33
- package/dist/presentation-operations.js +4 -4
- package/dist/presentation.d.ts +5 -8
- package/dist/presentation.js +33 -43
- package/dist/runtime.d.ts +1 -7
- package/dist/runtime.js +0 -14
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -22
- package/dist/sqlite-square.js +138 -161
- package/dist/square-api.d.ts +15 -10
- package/dist/square-api.js +26 -22
- package/dist/square-core.d.ts +0 -5
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +56 -21
- package/dist/square-facade.d.ts +45 -87
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -70
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +2 -1
- package/dist/square-migration.js +52 -30
- package/dist/square-sqlite.d.ts +6 -2
- package/dist/square-sqlite.js +54 -28
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +22 -54
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +21 -10
- package/dist/views.js +38 -38
- package/dist/wakes.d.ts +1 -2
- package/dist/wakes.js +1 -1
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +142 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +33 -4
- package/guides/primitive-history-consolidation.md +1 -1
- package/guides/row-storage-design.md +8 -8
- package/guides/square-api.md +244 -88
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/square-index.js
CHANGED
|
@@ -5,8 +5,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { withFileLock } from './file-lock.js';
|
|
6
6
|
import { canonicalPath } from './canonical-path.js';
|
|
7
7
|
import { observeFileMetadata } from './file-changes.js';
|
|
8
|
-
import {
|
|
9
|
-
/** The index is the only host-side Square state. Its path is test/configuration plumbing, not authority. */
|
|
8
|
+
import { validateOwner } from './model.js';
|
|
10
9
|
export function squareIndexPath(env = process.env) {
|
|
11
10
|
const configured = env.SQUARE_INDEX?.trim();
|
|
12
11
|
return path.resolve(configured ?? path.join(os.homedir(), '.square', 'index.ndjsonl'));
|
|
@@ -18,24 +17,17 @@ function validEntry(value) {
|
|
|
18
17
|
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
19
18
|
return undefined;
|
|
20
19
|
const row = value;
|
|
21
|
-
if (typeof row.location !== 'string' || !path.isAbsolute(row.location)
|
|
22
|
-
|| row.body === null || typeof row.body !== 'object' || Array.isArray(row.body))
|
|
20
|
+
if (typeof row.location !== 'string' || !path.isAbsolute(row.location) || typeof row.owner !== 'string')
|
|
23
21
|
return undefined;
|
|
24
|
-
const raw = row.body;
|
|
25
|
-
const body = {
|
|
26
|
-
channel: raw.channel,
|
|
27
|
-
session: raw.session,
|
|
28
|
-
...(raw.agent === undefined ? {} : { agent: raw.agent }),
|
|
29
|
-
};
|
|
30
22
|
try {
|
|
31
|
-
|
|
23
|
+
validateOwner(row.owner);
|
|
32
24
|
}
|
|
33
25
|
catch {
|
|
34
26
|
return undefined;
|
|
35
27
|
}
|
|
36
|
-
if (Object.keys(
|
|
28
|
+
if (Object.keys(row).some((key) => !['location', 'owner'].includes(key)))
|
|
37
29
|
return undefined;
|
|
38
|
-
return { location: row.location,
|
|
30
|
+
return { location: row.location, owner: row.owner };
|
|
39
31
|
}
|
|
40
32
|
async function readEntries(index) {
|
|
41
33
|
let content;
|
|
@@ -56,25 +48,13 @@ async function readEntries(index) {
|
|
|
56
48
|
if (entry !== undefined)
|
|
57
49
|
entries.push(entry);
|
|
58
50
|
}
|
|
59
|
-
catch {
|
|
60
|
-
// The index is disposable discovery input. A malformed line is ignored rather than
|
|
61
|
-
// becoming a second authority or preventing access to valid entries beside it.
|
|
62
|
-
}
|
|
51
|
+
catch { /* Disposable discovery input. */ }
|
|
63
52
|
}
|
|
64
53
|
return entries;
|
|
65
54
|
}
|
|
66
55
|
function matches(entry, query) {
|
|
67
|
-
|
|
68
|
-
return false;
|
|
69
|
-
if (query.channel !== undefined && entry.body.channel !== query.channel)
|
|
70
|
-
return false;
|
|
71
|
-
if (query.session !== undefined && entry.body.session !== query.session)
|
|
72
|
-
return false;
|
|
73
|
-
if (query.agent !== undefined && entry.body.agent !== query.agent)
|
|
74
|
-
return false;
|
|
75
|
-
return true;
|
|
56
|
+
return query.owner === undefined || entry.owner === query.owner;
|
|
76
57
|
}
|
|
77
|
-
/** Read and verify the disposable index. Paths are canonicalized on access and duplicates collapse. */
|
|
78
58
|
export async function indexedSquareEntries(query = {}, env = process.env) {
|
|
79
59
|
const entries = await readEntries(squareIndexPath(env));
|
|
80
60
|
const result = [];
|
|
@@ -89,11 +69,11 @@ export async function indexedSquareEntries(query = {}, env = process.env) {
|
|
|
89
69
|
catch {
|
|
90
70
|
continue;
|
|
91
71
|
}
|
|
92
|
-
const key = JSON.stringify([location, entry.
|
|
72
|
+
const key = JSON.stringify([location, entry.owner]);
|
|
93
73
|
if (seen.has(key))
|
|
94
74
|
continue;
|
|
95
75
|
seen.add(key);
|
|
96
|
-
result.push({ location,
|
|
76
|
+
result.push({ location, owner: entry.owner });
|
|
97
77
|
}
|
|
98
78
|
return result;
|
|
99
79
|
}
|
|
@@ -101,15 +81,15 @@ export async function indexedSquarePaths(query = {}, env = process.env) {
|
|
|
101
81
|
return [...new Set((await indexedSquareEntries(query, env)).map((entry) => entry.location))];
|
|
102
82
|
}
|
|
103
83
|
/** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
|
|
104
|
-
export async function rememberSquarePath(location,
|
|
105
|
-
|
|
84
|
+
export async function rememberSquarePath(location, owner, index) {
|
|
85
|
+
validateOwner(owner);
|
|
106
86
|
const canonical = await canonicalPath(location);
|
|
107
87
|
await fs.mkdir(path.dirname(index), { recursive: true });
|
|
108
88
|
return await withFileLock(lockPath(index), { retryMs: 10, skipIfBusy: true }, async () => {
|
|
109
89
|
const entries = await readEntries(index);
|
|
110
|
-
if (entries.some((entry) => entry.location === canonical &&
|
|
90
|
+
if (entries.some((entry) => entry.location === canonical && entry.owner === owner))
|
|
111
91
|
return false;
|
|
112
|
-
const next = [...entries, { location: canonical,
|
|
92
|
+
const next = [...entries, { location: canonical, owner }];
|
|
113
93
|
const temporary = `${index}.${process.pid}.${randomUUID()}.tmp`;
|
|
114
94
|
try {
|
|
115
95
|
await fs.writeFile(temporary, next.map((entry) => JSON.stringify(entry)).join('\n') + '\n', { flag: 'wx', mode: 0o600 });
|
|
@@ -121,11 +101,6 @@ export async function rememberSquarePath(location, body, index) {
|
|
|
121
101
|
return true;
|
|
122
102
|
}) ?? false;
|
|
123
103
|
}
|
|
124
|
-
/** Index loss is tolerated by every caller; this observer is only a wake hint for rediscovery. */
|
|
125
104
|
export function observeSquareIndex(env = process.env) {
|
|
126
105
|
return observeFileMetadata(squareIndexPath(env));
|
|
127
106
|
}
|
|
128
|
-
export function bodyKey(body) {
|
|
129
|
-
validateBody(body);
|
|
130
|
-
return JSON.stringify([body.channel, body.session, body.agent ?? null]);
|
|
131
|
-
}
|
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { SquareBackends } from './square-backend.js';
|
|
3
|
-
export type ParsedSquareLocation = {
|
|
4
|
-
readonly kind: 'local';
|
|
5
|
-
readonly path: string;
|
|
6
|
-
} | {
|
|
7
|
-
readonly kind: 'stdio';
|
|
8
|
-
readonly backend: string;
|
|
9
|
-
readonly key: string;
|
|
10
|
-
readonly uri: string;
|
|
11
|
-
};
|
|
12
|
-
export declare function parseSquareLocation(value: string): ParsedSquareLocation;
|
|
1
|
+
import type { SquareLocation } from './square-facade.js';
|
|
13
2
|
export declare function squareLocation(input: SquareLocation): string;
|
|
14
|
-
export declare function localSquareLocation(input: SquareLocation): string;
|
|
15
|
-
export declare function backendConfiguration(input: SquareAtInput): Promise<SquareBackends>;
|
package/dist/square-location.js
CHANGED
|
@@ -1,77 +1,11 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
2
|
import { SquareError } from './model.js';
|
|
5
|
-
export function parseSquareLocation(value) {
|
|
6
|
-
// Windows roots include drive-relative prefixes such as D:.
|
|
7
|
-
if (path.win32.parse(value).root !== '' || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(value))
|
|
8
|
-
return { kind: 'local', path: value };
|
|
9
|
-
// Parse the raw components, before URL dot-segment normalization or host lowercasing.
|
|
10
|
-
const match = /^stdio:\/\/([A-Za-z0-9_-]+)\/([^/?#]+)$/.exec(value);
|
|
11
|
-
if (!match)
|
|
12
|
-
throw new SquareError('invalid_args', 'Expected a local path or stdio://<configured-name>/<encoded-square-key>; no credentials, port, query or fragment');
|
|
13
|
-
let key;
|
|
14
|
-
try {
|
|
15
|
-
key = decodeURIComponent(match[2]);
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
throw new SquareError('invalid_args', 'Invalid percent encoding in stdio square key');
|
|
19
|
-
}
|
|
20
|
-
if (!key || key === '.' || key === '..' || /[/\\\x00-\x1f\x7f]/.test(key))
|
|
21
|
-
throw new SquareError('invalid_args', 'A stdio square key must be one nonempty name, not a path');
|
|
22
|
-
return { kind: 'stdio', backend: match[1], key, uri: `stdio://${match[1]}/${encodeURIComponent(key)}` };
|
|
23
|
-
}
|
|
24
3
|
export function squareLocation(input) {
|
|
25
4
|
const selected = input.path ?? (input.env ?? process.env).SQUARE_LOCATION;
|
|
26
5
|
if (selected === undefined || selected.trim() === '')
|
|
27
6
|
throw new SquareError('invalid_args', 'Square needs a location');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const location = squareLocation(input);
|
|
33
|
-
if (parseSquareLocation(location).kind !== 'local')
|
|
34
|
-
throw new SquareError('invalid_args', 'This file operation requires a local square artifact; backend URIs are not supported');
|
|
35
|
-
return location;
|
|
36
|
-
}
|
|
37
|
-
function validateBackends(value) {
|
|
38
|
-
const object = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
39
|
-
const invalid = () => new SquareError('invalid_args', 'Invalid backend configuration: expected {backends:{name:{command,args?,cwd?,env?}}}');
|
|
40
|
-
if (!object(value))
|
|
41
|
-
throw invalid();
|
|
42
|
-
for (const [name, entry] of Object.entries(value)) {
|
|
43
|
-
if (!/^[A-Za-z0-9_-]+$/.test(name) || !object(entry) || typeof entry.command !== 'string' || !entry.command.trim()
|
|
44
|
-
|| Object.keys(entry).some(key => !['command', 'args', 'cwd', 'env'].includes(key))
|
|
45
|
-
|| (entry.args !== undefined && (!Array.isArray(entry.args) || entry.args.some(arg => typeof arg !== 'string')))
|
|
46
|
-
|| (entry.cwd !== undefined && typeof entry.cwd !== 'string')
|
|
47
|
-
|| (entry.env !== undefined && (!object(entry.env) || Object.values(entry.env).some(v => typeof v !== 'string'))))
|
|
48
|
-
throw invalid();
|
|
49
|
-
}
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
52
|
-
export async function backendConfiguration(input) {
|
|
53
|
-
if (input.backends !== undefined)
|
|
54
|
-
return validateBackends(input.backends);
|
|
55
|
-
const env = input.env ?? process.env;
|
|
56
|
-
const configured = env.SQUARE_BACKENDS_CONFIG;
|
|
57
|
-
const file = configured === undefined ? path.join(env.HOME ?? os.homedir(), '.config/square/backends.json')
|
|
58
|
-
: path.resolve(input.cwd ?? process.cwd(), configured);
|
|
59
|
-
let text;
|
|
60
|
-
try {
|
|
61
|
-
text = await readFile(file, 'utf8');
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
if (configured === undefined && error.code === 'ENOENT')
|
|
65
|
-
return {};
|
|
66
|
-
throw new SquareError('invalid_args', 'Cannot read backend configuration; check SQUARE_BACKENDS_CONFIG');
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
const parsed = JSON.parse(text);
|
|
70
|
-
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object' || Object.keys(parsed).some(key => key !== 'backends') || parsed.backends === undefined)
|
|
71
|
-
throw new Error();
|
|
72
|
-
return validateBackends(parsed.backends);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
throw new SquareError('invalid_args', 'Invalid backend configuration; expected {backends:{name:{command,args?,cwd?,env?}}}');
|
|
76
|
-
}
|
|
7
|
+
// Windows roots include drive-relative prefixes such as D:.
|
|
8
|
+
if (path.win32.parse(selected).root === '' && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(selected))
|
|
9
|
+
throw new SquareError('invalid_args', 'Expected a local square artifact path');
|
|
10
|
+
return path.resolve(input.cwd ?? process.cwd(), selected);
|
|
77
11
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HarnessCoordinate } from './owner.js';
|
|
2
2
|
/** Offline conversion input only; never a runtime ownership or evidence reader. */
|
|
3
3
|
export interface LegacyOwner {
|
|
4
4
|
readonly participant: string;
|
|
5
|
-
readonly
|
|
5
|
+
readonly coordinate: HarnessCoordinate;
|
|
6
6
|
}
|
|
7
7
|
export interface LegacyHostData {
|
|
8
8
|
readonly owners: readonly LegacyOwner[];
|
|
@@ -5,7 +5,8 @@ import path from 'node:path';
|
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import { canonicalPath } from './canonical-path.js';
|
|
7
7
|
import { inSqliteTransaction } from './sqlite-transaction.js';
|
|
8
|
-
import {
|
|
8
|
+
import { harnessOwner } from './owner.js';
|
|
9
|
+
import { nameKey } from './model.js';
|
|
9
10
|
const LEDGER_FILENAME = 'host-ledger.sqlite';
|
|
10
11
|
const LEDGER_APPLICATION_ID = 0x5351484c;
|
|
11
12
|
const LEDGER_VERSION = 2;
|
|
@@ -144,11 +145,11 @@ export async function readLegacyHostData(location, env = process.env, signal) {
|
|
|
144
145
|
if (value.v !== 1 || value.location !== canonical || typeof value.participant !== 'string'
|
|
145
146
|
|| !finite(at))
|
|
146
147
|
throw invalid('owner fields are malformed.');
|
|
147
|
-
const
|
|
148
|
-
|
|
148
|
+
const coordinate = { channel: value.channel, session: value.session, ...(value.agent === undefined ? {} : { agent: value.agent }) };
|
|
149
|
+
harnessOwner(coordinate);
|
|
149
150
|
projections(row, { location_key: recordKey(canonical), participant_key: recordKey(nameKey(value.participant)),
|
|
150
|
-
channel_key: recordKey(
|
|
151
|
-
return { participant: value.participant,
|
|
151
|
+
channel_key: recordKey(coordinate.channel), session_key: recordKey(coordinate.session), effective_at: at });
|
|
152
|
+
return { participant: value.participant, coordinate };
|
|
152
153
|
});
|
|
153
154
|
const evidence = database.prepare("SELECT * FROM host_evidence WHERE location_key=? AND kind IN ('wake','presentation') ORDER BY seq").all(recordKey(canonical)).map(row => {
|
|
154
155
|
const value = decodeEvidence(record(row));
|
|
@@ -20,7 +20,8 @@ export interface MigrationOutcome {
|
|
|
20
20
|
/**
|
|
21
21
|
* Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
|
|
22
22
|
* preflighted read-only, then receive ownership/evidence and deferral storage in one
|
|
23
|
-
* format-
|
|
23
|
+
* format-7 transaction. Formats 4/5 gain attachment; format 4 also gains empty deferrals.
|
|
24
|
+
* All legacy seats are encoded as opaque owners, preserving format-6 attachment.
|
|
24
25
|
* Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
|
|
25
26
|
* unique; no source record is retired. Version 1 is not a supported source for this format transition.
|
|
26
27
|
*/
|
package/dist/square-migration.js
CHANGED
|
@@ -2,11 +2,12 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { openExistingSquareDatabase } from './artifact.js';
|
|
3
3
|
import { inSqliteTransaction } from './sqlite-transaction.js';
|
|
4
4
|
import { canonicalPath, canonicalPathSync } from './canonical-path.js';
|
|
5
|
-
import {
|
|
5
|
+
import { harnessOwner, decodeHarnessOwner } from './owner.js';
|
|
6
|
+
import { formatActivityId, isWakeRouteKind, nameKey } from './model.js';
|
|
6
7
|
import { parseActivityId } from './square-core.js';
|
|
7
8
|
import { readLegacyHostData } from './square-migration-host-ledger.js';
|
|
8
9
|
import { legacyRowFramingIssue, FORMAT4_ADDED_OBJECTS } from './square-legacy-framing.js';
|
|
9
|
-
import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V3_USER_VERSION, V4_USER_VERSION, SCHEMA_SQL, FORMAT4_SCHEMA_SQL, DELIVERY_DEFERRALS_SQL, validateRowFraming, inspectSqlite, createSqliteDriver, invalidArtifact, validateStoredActivity } from './square-sqlite.js';
|
|
10
|
+
import { APPLICATION_ID, ACTIVITIES_TABLE_SQL, ACTIVITY_INDEXES_SQL, CURRENT_USER_VERSION, V2_USER_VERSION, V3_USER_VERSION, V4_USER_VERSION, V5_USER_VERSION, V6_USER_VERSION, SCHEMA_SQL, FORMAT4_SCHEMA_SQL, DELIVERY_DEFERRALS_SQL, validateRowFraming, inspectSqlite, createSqliteDriver, invalidArtifact, validateStoredActivity } from './square-sqlite.js';
|
|
10
11
|
function isObject(value) {
|
|
11
12
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
13
|
}
|
|
@@ -23,6 +24,9 @@ function isNonNegativeInteger(value) {
|
|
|
23
24
|
function isStringArray(value) {
|
|
24
25
|
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
25
26
|
}
|
|
27
|
+
function sameCoordinate(left, right) {
|
|
28
|
+
return left.channel === right.channel && left.session === right.session && left.agent === right.agent;
|
|
29
|
+
}
|
|
26
30
|
function validateRecord(value, item) {
|
|
27
31
|
return isObject(value) && Object.entries(value).every(([key, candidate]) => key.length > 0 && item(candidate));
|
|
28
32
|
}
|
|
@@ -142,7 +146,7 @@ function rebuildV2Activities(database) {
|
|
|
142
146
|
}
|
|
143
147
|
function validateCurrentFraming(database, location) {
|
|
144
148
|
validateRowFraming(database, CURRENT_USER_VERSION, location);
|
|
145
|
-
|
|
149
|
+
inspectSqlite(database);
|
|
146
150
|
}
|
|
147
151
|
function assertSource(condition, detail = 'row is malformed.') {
|
|
148
152
|
if (!condition)
|
|
@@ -190,12 +194,12 @@ function decodeLegacyRoute(row) {
|
|
|
190
194
|
assertSource(row.participant_key_json === JSON.stringify(nameKey(participant)), 'route participant key is malformed.');
|
|
191
195
|
const sessionId = decodeJsonString(row.session_json);
|
|
192
196
|
sourceFinite(row.updated_at);
|
|
193
|
-
const
|
|
194
|
-
|
|
197
|
+
const coordinate = { channel: parsed.channel, session: sessionId, ...(parsed.extra.agent === undefined ? {} : { agent: parsed.extra.agent }) };
|
|
198
|
+
harnessOwner(coordinate);
|
|
195
199
|
const address = parsed.address;
|
|
196
200
|
if (row.kind === 'claude-native')
|
|
197
201
|
assertSource(address.sessionId === undefined || address.sessionId === sessionId, 'native route session conflicts with its body.');
|
|
198
|
-
return { location: parsed.location, participant,
|
|
202
|
+
return { location: parsed.location, participant, coordinate,
|
|
199
203
|
wakeAddress: { kind: row.kind, address: { ...address, ...(row.kind === 'claude-native' ? { sessionId } : {}) } } };
|
|
200
204
|
}
|
|
201
205
|
function validateLegacyLease(value) {
|
|
@@ -235,14 +239,15 @@ function planSeats(location, roster, routes, owners, driver) {
|
|
|
235
239
|
const joined = establishingJoin(driver, participant);
|
|
236
240
|
const candidates = localRoutes.filter((route) => nameKey(route.participant) === nameKey(participant.name));
|
|
237
241
|
const owner = owners.find(row => nameKey(row.participant) === nameKey(participant.name));
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
242
|
+
const coordinates = [...candidates.map(route => route.coordinate), ...(owner === undefined ? [] : [owner.coordinate])];
|
|
243
|
+
if (coordinates.length === 0 || !coordinates.every(coordinate => sameCoordinate(coordinate, coordinates[0])))
|
|
240
244
|
continue;
|
|
241
245
|
const address = uniqueSeatAddress(candidates);
|
|
242
246
|
seats.push({
|
|
243
247
|
participant: participant.name,
|
|
244
|
-
|
|
248
|
+
owner: harnessOwner(coordinates[0]),
|
|
245
249
|
generation: formatActivityId(joined),
|
|
250
|
+
attachment: 'attached',
|
|
246
251
|
...(address === undefined ? {} : { wakeAddress: address }),
|
|
247
252
|
});
|
|
248
253
|
}
|
|
@@ -321,7 +326,7 @@ function inspectLegacyRowArtifact(database, location, host) {
|
|
|
321
326
|
routes: routes.length,
|
|
322
327
|
recoveredSeats: seats.length,
|
|
323
328
|
unownedStanding: standingCount - seats.length,
|
|
324
|
-
sharedBodySeats: seats.filter(seat => seats.some(other => other !== seat &&
|
|
329
|
+
sharedBodySeats: seats.filter(seat => seats.some(other => other !== seat && other.owner === seat.owner)).length,
|
|
325
330
|
skippedEvidence: host.evidence.length - deliveries.length,
|
|
326
331
|
};
|
|
327
332
|
const sourceToken = legacySourceToken(database);
|
|
@@ -373,12 +378,14 @@ function planLegacyDeliveries(records, seats, driver) {
|
|
|
373
378
|
const native = source.kind === 'wake' && source.nativeDelivery !== undefined;
|
|
374
379
|
if (!participant?.joined && !(participant?.done && native))
|
|
375
380
|
continue;
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
const seatCoordinate = seat === undefined ? undefined : decodeHarnessOwner(seat.owner);
|
|
382
|
+
if (participant.joined && (seatCoordinate === undefined || seatCoordinate.session !== source.session
|
|
383
|
+
|| seatCoordinate.agent !== source.agent || source.channel !== undefined && seatCoordinate.channel !== source.channel))
|
|
378
384
|
continue;
|
|
379
385
|
const harness = source.nativeDelivery?.harness;
|
|
380
|
-
if (native &&
|
|
381
|
-
|
|
386
|
+
if (native && source.channel !== undefined && source.channel !== (harness === 'claude' ? 'claude-code' : 'opencode'))
|
|
387
|
+
continue;
|
|
388
|
+
if (native && seat !== undefined && harness !== undefined && seat.owner !== harnessOwner({ channel: harness === 'claude' ? 'claude-code' : 'opencode', session: source.session, ...(source.agent === undefined ? {} : { agent: source.agent }) }))
|
|
382
389
|
continue;
|
|
383
390
|
const joined = establishingJoin(driver, participant);
|
|
384
391
|
const generation = formatActivityId(joined);
|
|
@@ -388,11 +395,9 @@ function planLegacyDeliveries(records, seats, driver) {
|
|
|
388
395
|
const done = driver.activityHeads({ actors: [participant.name], kinds: ['done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
389
396
|
if (done === undefined || index >= done.index)
|
|
390
397
|
continue;
|
|
391
|
-
const
|
|
392
|
-
...(source.agent === undefined ? {} : { agent: source.agent }) };
|
|
393
|
-
|
|
394
|
-
continue;
|
|
395
|
-
const coordinate = JSON.stringify([body.channel, body.session, body.agent]);
|
|
398
|
+
const owner = harnessOwner({ channel: harness === 'claude' ? 'claude-code' : 'opencode', session: source.session,
|
|
399
|
+
...(source.agent === undefined ? {} : { agent: source.agent }) });
|
|
400
|
+
const coordinate = JSON.stringify([owner]);
|
|
396
401
|
const key = nameKey(participant.name), previous = completedBodies.get(key);
|
|
397
402
|
completedBodies.set(key, previous === undefined || previous === coordinate ? coordinate : null);
|
|
398
403
|
}
|
|
@@ -429,7 +434,6 @@ function planLegacyDeliveries(records, seats, driver) {
|
|
|
429
434
|
}
|
|
430
435
|
return deliveries.filter(row => row.claimToken === undefined || tokens.get(row.claimToken) === 1);
|
|
431
436
|
}
|
|
432
|
-
const V4_REBUILT_TABLES = ['square_meta', 'catch_leases'];
|
|
433
437
|
function tableDefinition(name) {
|
|
434
438
|
const definition = SCHEMA_SQL.split(';').map((statement) => statement.trim()).find((statement) => statement.startsWith(`CREATE TABLE ${name} (`));
|
|
435
439
|
if (definition === undefined)
|
|
@@ -439,16 +443,32 @@ function tableDefinition(name) {
|
|
|
439
443
|
function currentAdditionStatements() {
|
|
440
444
|
return SCHEMA_SQL.split(';').map((statement) => statement.trim()).filter((statement) => {
|
|
441
445
|
const match = /^(?:CREATE (?:UNIQUE )?TABLE|CREATE (?:UNIQUE )?INDEX)\s+([a-z_]+)/i.exec(statement);
|
|
442
|
-
return match !== null && FORMAT4_ADDED_OBJECTS.has(match[1]);
|
|
446
|
+
return match !== null && (FORMAT4_ADDED_OBJECTS.has(match[1]) || match[1] === 'seat_owner');
|
|
443
447
|
}).map((statement) => `${statement};`);
|
|
444
448
|
}
|
|
449
|
+
function migrateSeatOwnership(database, source) {
|
|
450
|
+
const rows = database.prepare('SELECT * FROM seats ORDER BY participant_key_json').all();
|
|
451
|
+
database.exec('ALTER TABLE seats RENAME TO seats_legacy;');
|
|
452
|
+
database.exec(tableDefinition('seats'));
|
|
453
|
+
database.exec('CREATE INDEX seat_owner ON seats(owner_json);');
|
|
454
|
+
const insert = database.prepare('INSERT INTO seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?)');
|
|
455
|
+
for (const row of rows) {
|
|
456
|
+
const channel = decodeJsonString(row.channel_json, true), session = decodeJsonString(row.session_json, true);
|
|
457
|
+
const agent = row.agent_json === null ? undefined : decodeJsonString(row.agent_json, true);
|
|
458
|
+
const owner = harnessOwner({ channel, session, ...(agent === undefined ? {} : { agent }) });
|
|
459
|
+
const attachment = source === 6 ? row.attachment : 'attached';
|
|
460
|
+
assertSource(attachment === 'attached' || attachment === 'detached', 'seat attachment is malformed.');
|
|
461
|
+
insert.run(row.participant_key_json, row.participant_json, JSON.stringify(owner), row.generation_json, row.wake_address_json, attachment);
|
|
462
|
+
}
|
|
463
|
+
database.exec('DROP TABLE seats_legacy;');
|
|
464
|
+
}
|
|
445
465
|
function importOwnershipAndEvidence(database, plan) {
|
|
446
466
|
for (const generation of plan.leaseGenerations) {
|
|
447
467
|
database.prepare('UPDATE catch_leases SET generation_json=? WHERE owner_json=?').run(JSON.stringify(generation.generation), generation.ownerJson);
|
|
448
468
|
}
|
|
449
|
-
const insertSeat = database.prepare('INSERT INTO seats(participant_key_json,participant_json,
|
|
469
|
+
const insertSeat = database.prepare('INSERT INTO seats(participant_key_json,participant_json,owner_json,generation_json,wake_address_json,attachment) VALUES(?,?,?,?,?,?)');
|
|
450
470
|
for (const seat of plan.seats) {
|
|
451
|
-
insertSeat.run(JSON.stringify(nameKey(seat.participant)), JSON.stringify(seat.participant), JSON.stringify(seat.
|
|
471
|
+
insertSeat.run(JSON.stringify(nameKey(seat.participant)), JSON.stringify(seat.participant), JSON.stringify(seat.owner), JSON.stringify(seat.generation), seat.wakeAddress === undefined ? null : JSON.stringify(seat.wakeAddress), seat.attachment);
|
|
452
472
|
}
|
|
453
473
|
const insertDelivery = database.prepare('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(?,?,?,?,?,?,?,?,?,?,?)');
|
|
454
474
|
for (const evidence of plan.deliveries) {
|
|
@@ -481,10 +501,9 @@ function validateMigrationSource(database, location) {
|
|
|
481
501
|
throw invalidArtifact(issue);
|
|
482
502
|
return before.userVersion;
|
|
483
503
|
}
|
|
484
|
-
if (before.userVersion === V4_USER_VERSION) {
|
|
485
|
-
validateRowFraming(database,
|
|
486
|
-
|
|
487
|
-
return V4_USER_VERSION;
|
|
504
|
+
if (before.userVersion === V4_USER_VERSION || before.userVersion === V5_USER_VERSION || before.userVersion === V6_USER_VERSION) {
|
|
505
|
+
validateRowFraming(database, before.userVersion, location);
|
|
506
|
+
return before.userVersion;
|
|
488
507
|
}
|
|
489
508
|
if (before.userVersion === CURRENT_USER_VERSION) {
|
|
490
509
|
validateCurrentFraming(database, location);
|
|
@@ -495,7 +514,8 @@ function validateMigrationSource(database, location) {
|
|
|
495
514
|
/**
|
|
496
515
|
* Explicit conversion of the supported legacy formats. Row-backed versions 2 and 3 are
|
|
497
516
|
* preflighted read-only, then receive ownership/evidence and deferral storage in one
|
|
498
|
-
* format-
|
|
517
|
+
* format-7 transaction. Formats 4/5 gain attachment; format 4 also gains empty deferrals.
|
|
518
|
+
* All legacy seats are encoded as opaque owners, preserving format-6 attachment.
|
|
499
519
|
* Legacy delivery evidence is copied from HostLedger only when its owner/session mapping is
|
|
500
520
|
* unique; no source record is retired. Version 1 is not a supported source for this format transition.
|
|
501
521
|
*/
|
|
@@ -506,11 +526,13 @@ export async function migrateSquareFile(location, signal, env = process.env) {
|
|
|
506
526
|
const source = await inSqliteTransaction(database, false, () => validateMigrationSource(database, squarePath), value => value, signal);
|
|
507
527
|
if (source === undefined)
|
|
508
528
|
return { status: 'already-current', standing: standing(database) };
|
|
509
|
-
if (source === V4_USER_VERSION) {
|
|
529
|
+
if (source === V4_USER_VERSION || source === V5_USER_VERSION || source === V6_USER_VERSION) {
|
|
510
530
|
return await inSqliteTransaction(database, true, () => {
|
|
511
531
|
assertSource(validateMigrationSource(database, squarePath) === source, 'source format changed during migration preflight.');
|
|
512
532
|
}, () => {
|
|
513
|
-
|
|
533
|
+
if (source === V4_USER_VERSION)
|
|
534
|
+
database.exec(DELIVERY_DEFERRALS_SQL);
|
|
535
|
+
migrateSeatOwnership(database, source);
|
|
514
536
|
database.exec(`PRAGMA user_version = ${CURRENT_USER_VERSION};`);
|
|
515
537
|
validateCurrentFraming(database, squarePath);
|
|
516
538
|
return { status: 'migrated', standing: standing(database) };
|
package/dist/square-sqlite.d.ts
CHANGED
|
@@ -21,7 +21,9 @@ export declare const APPLICATION_ID = 1397838405;
|
|
|
21
21
|
export declare const V2_USER_VERSION = 2;
|
|
22
22
|
export declare const V3_USER_VERSION = 3;
|
|
23
23
|
export declare const V4_USER_VERSION = 4;
|
|
24
|
-
export declare const
|
|
24
|
+
export declare const V5_USER_VERSION = 5;
|
|
25
|
+
export declare const V6_USER_VERSION = 6;
|
|
26
|
+
export declare const CURRENT_USER_VERSION = 7;
|
|
25
27
|
export declare const V1_SNAPSHOT_TABLE = "square_snapshot";
|
|
26
28
|
export interface LegacyFraming {
|
|
27
29
|
readonly applicationId: number;
|
|
@@ -39,11 +41,13 @@ export declare const ACTIVITIES_TABLE_SQL = "CREATE TABLE activities (\n act_in
|
|
|
39
41
|
export declare const ACTIVITY_INDEXES_SQL: readonly ["CREATE INDEX activity_kind_index ON activities(kind, act_index)", "CREATE INDEX activity_actor_index ON activities(actor_key_json, act_index)", "CREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say'", "CREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say'", "CREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell'"];
|
|
40
42
|
export declare const DELIVERY_DEFERRALS_SQL = "CREATE TABLE delivery_deferrals (\n participant_key_json TEXT COLLATE BINARY NOT NULL,\n participant_json TEXT NOT NULL,\n generation_json TEXT NOT NULL,\n deferral_id_json TEXT NOT NULL,\n match_json TEXT NOT NULL CHECK (json_valid(match_json)),\n expires_at REAL NOT NULL,\n PRIMARY KEY (participant_key_json,generation_json,deferral_id_json)\n) WITHOUT ROWID;\nCREATE INDEX delivery_deferral_expiry ON delivery_deferrals(participant_key_json,generation_json,expires_at);";
|
|
41
43
|
export declare const SCHEMA_SQL: string;
|
|
44
|
+
export declare const FORMAT6_SCHEMA_SQL: string;
|
|
45
|
+
export declare const FORMAT5_SCHEMA_SQL: string;
|
|
42
46
|
export declare const FORMAT4_SCHEMA_SQL: string;
|
|
43
47
|
export declare function invalidArtifact(detail: string): SquareError;
|
|
44
48
|
export declare function validateStoredActivity(value: unknown): StoredAct;
|
|
45
49
|
export declare function validateWatchLease(value: unknown): WatchLease;
|
|
46
|
-
export declare function validateRowFraming(db: DatabaseSync, version: 2 | 3 | 4 | 5, location?: string): void;
|
|
50
|
+
export declare function validateRowFraming(db: DatabaseSync, version: 2 | 3 | 4 | 5 | 6 | 7, location?: string): void;
|
|
47
51
|
export declare function validateSqliteHeader(db: DatabaseSync, location?: string): SquareHeader;
|
|
48
52
|
export interface SqliteDriver extends SquareRead, LandingWriter {
|
|
49
53
|
readonly changed: boolean;
|