@astrosheep/square 0.3.72 → 0.3.73
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 +0 -3
- package/dist/artifact.js +5 -59
- package/dist/catch-decisions.d.ts +7 -1
- package/dist/catch-decisions.js +21 -8
- package/dist/claude-delivery.d.ts +1 -1
- package/dist/claude-delivery.js +7 -3
- package/dist/cli/ledger-command.d.ts +12 -0
- package/dist/cli/ledger-command.js +68 -0
- package/dist/cli/registry.js +2 -0
- package/dist/help.js +7 -2
- package/dist/host-ledger-file-adapter.d.ts +4 -10
- package/dist/host-ledger-file-adapter.js +238 -198
- package/dist/host-ledger-migration.d.ts +26 -0
- package/dist/host-ledger-migration.js +227 -0
- package/dist/host-ledger-sqlite.d.ts +62 -0
- package/dist/host-ledger-sqlite.js +341 -0
- package/dist/host-ledger.d.ts +13 -4
- package/dist/ledger-maintenance.d.ts +30 -0
- package/dist/ledger-maintenance.js +110 -0
- package/dist/ports.d.ts +7 -1
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.js +12 -6
- package/dist/sqlite-transaction.d.ts +5 -0
- package/dist/sqlite-transaction.js +72 -0
- package/dist/square-actions.js +16 -8
- package/dist/square-migration.js +2 -1
- package/dist/square-sqlite.d.ts +2 -2
- package/dist/square-sqlite.js +15 -4
- package/guides/catch-contention-performance.md +157 -0
- package/guides/change-waits.md +1 -1
- package/guides/host-ledger.md +53 -6
- package/guides/indexed-host-ledger-performance.md +39 -0
- 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/paseo/client/square-message.tsx +35 -19
package/dist/artifact.d.ts
CHANGED
|
@@ -22,9 +22,6 @@ export interface SquareSnapshot {
|
|
|
22
22
|
}
|
|
23
23
|
/** Private artifact-boundary connection shared with explicit migration. Never creates a path. */
|
|
24
24
|
export declare function openExistingSquareDatabase(location: string): DatabaseSync;
|
|
25
|
-
export declare function retrySqliteBusy<T>(operation: () => T, signal?: AbortSignal): Promise<T>;
|
|
26
|
-
/** Acquisition may retry; after change starts, only COMMIT retries, retaining its result. */
|
|
27
|
-
export declare function inSqliteTransaction<T>(db: DatabaseSync, write: boolean, validate: () => void, change: () => T, signal?: AbortSignal): Promise<T>;
|
|
28
25
|
/** One connection, ordered operations, guarded synchronous transaction capabilities. */
|
|
29
26
|
export declare function createArtifactPort(location: string, initial?: SquareState, externalSignal?: AbortSignal): SquareArtifactPort;
|
|
30
27
|
/** New-format creation; initial authority is accepted only at this explicit boundary. */
|
package/dist/artifact.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { retrySqliteBusy, inSqliteTransaction } from './sqlite-transaction.js';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -57,9 +58,6 @@ function requireSquareExtension(location) {
|
|
|
57
58
|
if (!location.endsWith('.square'))
|
|
58
59
|
throw new SquareError('invalid_args', `Square artifacts must use the .square extension: ${location}`);
|
|
59
60
|
}
|
|
60
|
-
function isBusy(error) {
|
|
61
|
-
return error instanceof Error && /database is locked|database is busy|SQLITE_BUSY/i.test(error.message);
|
|
62
|
-
}
|
|
63
61
|
function storageError(error, location) {
|
|
64
62
|
if (!(error instanceof Error) || error instanceof SquareError || error instanceof InternalSquareError)
|
|
65
63
|
return error;
|
|
@@ -85,69 +83,17 @@ function abort(signal) {
|
|
|
85
83
|
if (signal?.aborted)
|
|
86
84
|
throw signal.reason ?? new Error('Square artifact operation aborted');
|
|
87
85
|
}
|
|
88
|
-
export async function retrySqliteBusy(operation, signal) {
|
|
89
|
-
while (true) {
|
|
90
|
-
abort(signal);
|
|
91
|
-
try {
|
|
92
|
-
return operation();
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
if (!isBusy(error))
|
|
96
|
-
throw error;
|
|
97
|
-
try {
|
|
98
|
-
await sleep(25, undefined, { signal });
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
abort(signal);
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
86
|
function synchronous(value) {
|
|
108
87
|
if (value !== null && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function') {
|
|
109
88
|
void Promise.resolve(value).catch(() => undefined);
|
|
110
89
|
throw new TypeError('Square artifact callbacks must be synchronous.');
|
|
111
90
|
}
|
|
112
91
|
}
|
|
113
|
-
/** Acquisition may retry; after change starts, only COMMIT retries, retaining its result. */
|
|
114
|
-
export async function inSqliteTransaction(db, write, validate, change, signal) {
|
|
115
|
-
let begun = false;
|
|
116
|
-
try {
|
|
117
|
-
await retrySqliteBusy(() => {
|
|
118
|
-
db.exec(write ? 'BEGIN IMMEDIATE' : 'BEGIN');
|
|
119
|
-
begun = true;
|
|
120
|
-
try {
|
|
121
|
-
validate();
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
db.exec('ROLLBACK');
|
|
125
|
-
begun = false;
|
|
126
|
-
throw error;
|
|
127
|
-
}
|
|
128
|
-
}, signal);
|
|
129
|
-
abort(signal);
|
|
130
|
-
const result = change();
|
|
131
|
-
synchronous(result);
|
|
132
|
-
await retrySqliteBusy(() => db.exec('COMMIT'), signal);
|
|
133
|
-
begun = false;
|
|
134
|
-
return result;
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
if (begun) {
|
|
138
|
-
try {
|
|
139
|
-
db.exec('ROLLBACK');
|
|
140
|
-
}
|
|
141
|
-
catch { /* retain the original failure */ }
|
|
142
|
-
}
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
92
|
const readMethods = [
|
|
147
93
|
'header', 'participant', 'roster', 'attention', 'incomingAttention', 'hold',
|
|
148
94
|
'activity', 'activityHeads', 'activities', 'activityCount', 'contextIndexes', 'sayOrdinals', 'recentSayTimes',
|
|
149
95
|
'audience', 'directed', 'directedAt', 'directedCount', 'pendingAudience',
|
|
150
|
-
'receipt', 'receiptFacts', 'receiptAssignments', 'lease', 'routes',
|
|
96
|
+
'receipt', 'receiptFacts', 'receiptAssignments', 'lease', 'routes', 'participantCounts',
|
|
151
97
|
];
|
|
152
98
|
const writeMethods = ['applyLanding', 'markSeen', 'putLease', 'removeLease', 'replaceRoutes'];
|
|
153
99
|
/** One connection, ordered operations, guarded synchronous transaction capabilities. */
|
|
@@ -202,8 +148,8 @@ export function createArtifactPort(location, initial, externalSignal) {
|
|
|
202
148
|
}
|
|
203
149
|
async function transaction(callback, write, signal) {
|
|
204
150
|
const db = await connection(signal);
|
|
205
|
-
return inSqliteTransaction(db, write, () => validate(db), () => {
|
|
206
|
-
const driver = createSqliteDriver(db), version =
|
|
151
|
+
return inSqliteTransaction(db, write, () => validate(db), (header) => {
|
|
152
|
+
const driver = createSqliteDriver(db), version = header.revision;
|
|
207
153
|
let active = true, failed = false, failure;
|
|
208
154
|
function guard(fn) {
|
|
209
155
|
return ((...args) => {
|
|
@@ -250,7 +196,7 @@ export function createArtifactPort(location, initial, externalSignal) {
|
|
|
250
196
|
}
|
|
251
197
|
const revision = signal => queue(async (s) => {
|
|
252
198
|
const db = await connection(s);
|
|
253
|
-
return inSqliteTransaction(db, false, () => validate(db), () =>
|
|
199
|
+
return inSqliteTransaction(db, false, () => validate(db), (header) => header.revision, s);
|
|
254
200
|
}, signal);
|
|
255
201
|
return {
|
|
256
202
|
read: (select, signal) => queue(async (s) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ActivityId } from './square-core.js';
|
|
2
2
|
import { type StoredAct } from './model.js';
|
|
3
|
-
import type { DirectedFilter, SquareRead, SquareWrite } from './ports.js';
|
|
3
|
+
import type { ActivityIndex, DirectedFilter, SquareRead, SquareWrite } from './ports.js';
|
|
4
4
|
import type { CatchOptions } from './square-facade.js';
|
|
5
5
|
export interface CatchSelection {
|
|
6
6
|
readonly participant: string;
|
|
@@ -20,5 +20,11 @@ export declare function observeCatchSelection(facts: SquareRead, participant: st
|
|
|
20
20
|
export declare const CATCH_DEFAULT_LIMIT = 10;
|
|
21
21
|
export declare const CATCH_MAX_LIMIT = 100;
|
|
22
22
|
export declare function catchLimit(value: number | undefined): number;
|
|
23
|
+
/** Select non-ID catch coordinates without hydrating bodies or recording receipts. */
|
|
24
|
+
export declare function selectCatchPage(facts: SquareRead, requestedName: string, options: CatchOptions): {
|
|
25
|
+
readonly participant: string;
|
|
26
|
+
readonly indexes: readonly ActivityIndex[];
|
|
27
|
+
readonly filter: DirectedFilter;
|
|
28
|
+
};
|
|
23
29
|
/** Selection, receipts and resulting cursor belong to one artifact write transaction. */
|
|
24
30
|
export declare function decideCatch(facts: SquareWrite, requestedName: string, options: CatchOptions, at: number): CatchSelection;
|
package/dist/catch-decisions.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
|
-
import {
|
|
2
|
+
import { readCursor, terminalStatusFor } from './runtime.js';
|
|
3
3
|
import { SquareError } from './model.js';
|
|
4
4
|
import { resolveKnownName } from './decisions.js';
|
|
5
5
|
/** Capture a selected page (or an empty completion) without opening another resource. */
|
|
6
6
|
export function observeCatchSelection(facts, participant, activities, filter, at) {
|
|
7
|
+
if (typeof at !== 'number' || !Number.isFinite(at))
|
|
8
|
+
throw new SquareError('invalid_args', 'Catch time must be a finite number.');
|
|
7
9
|
const cursor = readCursor(facts, participant);
|
|
10
|
+
const counts = facts.participantCounts(), header = facts.header(), current = facts.participant(participant);
|
|
8
11
|
return {
|
|
9
12
|
participant, activities, consumedThrough: cursor < 0 ? null : formatActivityId(cursor),
|
|
10
13
|
remaining: facts.directedCount(participant, filter),
|
|
11
14
|
sayNumbers: activities.length === 0 ? {} : facts.sayOrdinals(activities.map((item) => item.index)),
|
|
12
|
-
standingCount:
|
|
13
|
-
ownActivityCount:
|
|
14
|
-
terminalStatus:
|
|
15
|
+
standingCount: counts.joined, held: facts.hold().active, now: at,
|
|
16
|
+
ownActivityCount: current.sayCount, hardCap: header.hardCap,
|
|
17
|
+
terminalStatus: terminalStatusFor(current, header.hardCap, counts),
|
|
15
18
|
};
|
|
16
19
|
}
|
|
17
20
|
export const CATCH_DEFAULT_LIMIT = 10;
|
|
@@ -23,13 +26,21 @@ export function catchLimit(value) {
|
|
|
23
26
|
}
|
|
24
27
|
return limit;
|
|
25
28
|
}
|
|
29
|
+
/** Select non-ID catch coordinates without hydrating bodies or recording receipts. */
|
|
30
|
+
export function selectCatchPage(facts, requestedName, options) {
|
|
31
|
+
const participant = resolveKnownName(facts, requestedName);
|
|
32
|
+
const limit = catchLimit(options.limit);
|
|
33
|
+
const filter = { unseenOnly: true, ...(options.from === undefined ? {} : { senders: options.from }), ...(options.mention === true ? { attentionOnly: true } : {}) };
|
|
34
|
+
return { participant, indexes: facts.directed(participant, filter, { edge: 'oldest', limit }).items.map((item) => item.index), filter };
|
|
35
|
+
}
|
|
26
36
|
/** Selection, receipts and resulting cursor belong to one artifact write transaction. */
|
|
27
37
|
export function decideCatch(facts, requestedName, options, at) {
|
|
28
|
-
|
|
29
|
-
const limit = catchLimit(options.limit);
|
|
38
|
+
let viewer;
|
|
30
39
|
let delivered;
|
|
31
40
|
let filter = { unseenOnly: true };
|
|
32
41
|
if (options.id !== undefined) {
|
|
42
|
+
viewer = resolveKnownName(facts, requestedName);
|
|
43
|
+
catchLimit(options.limit);
|
|
33
44
|
if (['idle', 'from', 'mention', 'limit'].some((key) => Object.hasOwn(options, key)))
|
|
34
45
|
throw new SquareError('invalid_args', 'Catch id cannot be combined with idle, from, mention or limit.');
|
|
35
46
|
const index = parseActivityId(options.id);
|
|
@@ -44,8 +55,10 @@ export function decideCatch(facts, requestedName, options, at) {
|
|
|
44
55
|
delivered = [activity];
|
|
45
56
|
}
|
|
46
57
|
else {
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
const page = selectCatchPage(facts, requestedName, options);
|
|
59
|
+
viewer = page.participant;
|
|
60
|
+
filter = page.filter;
|
|
61
|
+
delivered = facts.activities(page.indexes);
|
|
49
62
|
}
|
|
50
63
|
facts.markSeen(viewer, delivered.map((item) => item.index), at);
|
|
51
64
|
return observeCatchSelection(facts, viewer, delivered, filter, at);
|
|
@@ -3,7 +3,7 @@ import type { WakeTransportPort } from './ports.js';
|
|
|
3
3
|
export declare function nativeTokens(text: string): string[];
|
|
4
4
|
export declare function nativePayload(token: string, preview: string): string;
|
|
5
5
|
export declare function createClaudeWakeTransport(hostLedger: HostLedgerPort, env?: NodeJS.ProcessEnv): WakeTransportPort;
|
|
6
|
-
/**
|
|
6
|
+
/** Retired tokens veto a frame; only live correlations and bound memberships confer authority. */
|
|
7
7
|
export declare function observeClaudeDelivery(sessionId: string, text: string, operation: 'guard' | 'admitted' | 'stored', env?: NodeJS.ProcessEnv): Promise<{
|
|
8
8
|
recognized: boolean;
|
|
9
9
|
current: boolean;
|
package/dist/claude-delivery.js
CHANGED
|
@@ -101,15 +101,19 @@ async function dispatchClaude(request, hostLedger, timeoutMs, beforeSend, env =
|
|
|
101
101
|
return writeClaudeWake(row.session, native.endpoint, payload, timeoutMs, signal);
|
|
102
102
|
}, signal);
|
|
103
103
|
}
|
|
104
|
-
/**
|
|
104
|
+
/** Retired tokens veto a frame; only live correlations and bound memberships confer authority. */
|
|
105
105
|
export async function observeClaudeDelivery(sessionId, text, operation, env = process.env) {
|
|
106
106
|
const ledger = hostLedgerForEnv(env);
|
|
107
107
|
return ledger.withClaimLock(async () => {
|
|
108
108
|
const tokens = nativeTokens(text);
|
|
109
|
-
const rows =
|
|
109
|
+
const { deliveries: rows, retired } = await ledger.lookupNativeTokens({ harness: 'claude', session: sessionId, tokens });
|
|
110
|
+
// A stale token vetoes the entire frame, including a mixed current/stale frame,
|
|
111
|
+
// before admission, artifact reads or presentation can confer any authority.
|
|
112
|
+
if (retired.length)
|
|
113
|
+
return { recognized: true, current: false };
|
|
110
114
|
let current = false;
|
|
111
115
|
for (const row of rows) {
|
|
112
|
-
if (row.nativeDelivery?.harness !== 'claude')
|
|
116
|
+
if (row.nativeDelivery?.harness !== 'claude' || !['dispatching', 'unknown', 'accepted'].includes(row.outcome))
|
|
113
117
|
continue;
|
|
114
118
|
const native = row.nativeDelivery;
|
|
115
119
|
const pending = await currentDelivery(row, ledger).catch(() => undefined);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type LedgerMaintenanceReport } from '../ledger-maintenance.js';
|
|
2
|
+
import { type HostLedgerReport } from '../host-ledger-migration.js';
|
|
3
|
+
import { type CommandSpec } from './context.js';
|
|
4
|
+
type LedgerIntent = {
|
|
5
|
+
readonly action: 'verify' | 'migrate';
|
|
6
|
+
} | {
|
|
7
|
+
readonly action: 'gc';
|
|
8
|
+
readonly dryRun: boolean;
|
|
9
|
+
};
|
|
10
|
+
type LedgerResult = HostLedgerReport | LedgerMaintenanceReport;
|
|
11
|
+
export declare const ledgerCommand: CommandSpec<LedgerIntent, LedgerResult>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { gcHostLedger } from '../ledger-maintenance.js';
|
|
2
|
+
import { migrateHostLedger, verifyHostLedger } from '../host-ledger-migration.js';
|
|
3
|
+
import { usage } from './context.js';
|
|
4
|
+
function renderReport(report) {
|
|
5
|
+
const lines = [
|
|
6
|
+
`host ledger: ${report.status}`,
|
|
7
|
+
`presence: ${report.presence} · evidence: ${report.evidence}`,
|
|
8
|
+
];
|
|
9
|
+
if (report.presenceRevision !== undefined) {
|
|
10
|
+
lines.push(`revisions: presence ${report.presenceRevision} · evidence ${report.evidenceRevision}`);
|
|
11
|
+
}
|
|
12
|
+
if (report.status === 'upgraded')
|
|
13
|
+
lines.push('SQLite schema upgraded in place; records, order and revisions preserved.');
|
|
14
|
+
if (report.ready !== undefined)
|
|
15
|
+
lines.push(`readiness: ${report.ready ? 'ready' : 'not ready'}`);
|
|
16
|
+
if (report.sources !== undefined) {
|
|
17
|
+
for (const kind of ['presence', 'evidence']) {
|
|
18
|
+
const source = report.sources[kind];
|
|
19
|
+
lines.push(`${kind}.ndjsonl: ${source.records} accepted · ${source.ignored} ignored · ${source.rejected} rejected · sha256 ${source.sha256 ?? 'absent'}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (report.status === 'legacy') {
|
|
23
|
+
if (report.ready)
|
|
24
|
+
lines.push('Stop all Square hosts before conversion.', 'square ledger migrate');
|
|
25
|
+
else
|
|
26
|
+
lines.push('Unsafe legacy records; conversion refused.');
|
|
27
|
+
}
|
|
28
|
+
return lines.join('\n') + '\n';
|
|
29
|
+
}
|
|
30
|
+
/** Counts only: unreadable locations are summarized, never printed, so no payload or credential can leak. */
|
|
31
|
+
function renderMaintenance(report) {
|
|
32
|
+
return [
|
|
33
|
+
`host ledger: gc${report.dryRun ? ' (dry run)' : ''}`,
|
|
34
|
+
`eligible: ${report.eligible} · retired: ${report.retired} · cas misses: ${report.casMisses}`,
|
|
35
|
+
`unreadable artifacts: ${report.unavailableArtifacts.length}`,
|
|
36
|
+
...(report.dryRun ? ['Dry run: nothing retired; ledger and artifacts unchanged.'] : []),
|
|
37
|
+
'',
|
|
38
|
+
].join('\n');
|
|
39
|
+
}
|
|
40
|
+
export const ledgerCommand = {
|
|
41
|
+
parse(argv, context) {
|
|
42
|
+
if (argv[0] === 'gc') {
|
|
43
|
+
if (argv.length === 1)
|
|
44
|
+
return { action: 'gc', dryRun: false };
|
|
45
|
+
if (argv.length === 2 && argv[1] === '--dry-run')
|
|
46
|
+
return { action: 'gc', dryRun: true };
|
|
47
|
+
return usage(context.command);
|
|
48
|
+
}
|
|
49
|
+
if (argv.length !== 1 || (argv[0] !== 'verify' && argv[0] !== 'migrate'))
|
|
50
|
+
usage(context.command);
|
|
51
|
+
return { action: argv[0] };
|
|
52
|
+
},
|
|
53
|
+
execute(intent, context) {
|
|
54
|
+
if (intent.action === 'gc')
|
|
55
|
+
return gcHostLedger({ env: context.env, dryRun: intent.dryRun });
|
|
56
|
+
const options = { env: context.env };
|
|
57
|
+
return intent.action === 'verify' ? verifyHostLedger(options) : migrateHostLedger(options);
|
|
58
|
+
},
|
|
59
|
+
present(result) {
|
|
60
|
+
if ('dryRun' in result) {
|
|
61
|
+
process.stdout.write(renderMaintenance(result));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
process.stdout.write(renderReport(result));
|
|
65
|
+
if (result.ready === false)
|
|
66
|
+
process.exitCode = 2;
|
|
67
|
+
},
|
|
68
|
+
};
|
package/dist/cli/registry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildCommand, doneCommand, expressCommand, holdCommand, ignoreCommand, joinCommand, listenCommand, listeningCommand, resumeCommand } from './square-commands.js';
|
|
2
2
|
import { doctorCommand, migrateCommand } from './maintenance-commands.js';
|
|
3
|
+
import { ledgerCommand } from './ledger-command.js';
|
|
3
4
|
import { harnessCommand, installCommand, uninstallCommand } from './harness-command.js';
|
|
4
5
|
import { helpCommand, versionCommand } from './meta-commands.js';
|
|
5
6
|
import { mcpServerCommand } from './mcp-server-command.js';
|
|
@@ -26,6 +27,7 @@ export const commandRegistry = [
|
|
|
26
27
|
{ names: ['harness'], spec: harnessCommand },
|
|
27
28
|
{ names: ['doctor'], spec: doctorCommand },
|
|
28
29
|
{ names: ['migrate'], spec: migrateCommand },
|
|
30
|
+
{ names: ['ledger'], spec: ledgerCommand },
|
|
29
31
|
{ names: ['history'], spec: historyCommand },
|
|
30
32
|
{ names: ['status'], spec: statusCommand },
|
|
31
33
|
{ names: ['participants'], spec: participantsCommand },
|
package/dist/help.js
CHANGED
|
@@ -77,7 +77,12 @@ const COMMANDS = [
|
|
|
77
77
|
{
|
|
78
78
|
names: ['migrate'], usage: 'migrate', usesSquare: true, group: 'maintenance',
|
|
79
79
|
summary: 'Convert an existing version 1 square to row storage in place.',
|
|
80
|
-
details: ['
|
|
80
|
+
details: ['For the selected square artifact only; there is no --check, --backup or --force.', 'A version 2 square is left untouched; an unrelated or unsupported artifact is refused without a write.'],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
names: ['ledger'], usage: 'ledger <verify|migrate|gc>', usesSquare: false, group: 'maintenance',
|
|
84
|
+
summary: 'Verify, convert, or reclaim the machine-local host ledger.',
|
|
85
|
+
details: ['verify: read-only SQLite integrity or full legacy readiness with accepted/ignored/rejected counts and source hashes; not-ready exits 2, an empty root stays empty. An older or unsupported database is refused with the migrate command and is never upgraded by verify.', 'migrate: stop every Square host first, then convert legacy NDJSON, or upgrade an older SQLite ledger in place. Sources, records, order and observer revisions are preserved; an existing current ledger is an untouched no-op.', 'gc [--dry-run]: reclaim old wake/presentation evidence only where the square artifact proves the recipient is closed — the activity reached them and catch receipted it or they later rejoined. Dispatching, lifecycle and unprovable rows are retained. Deleted rows free reusable pages, not file size (no VACUUM); retired Claude recognition tokens keep growing in total.', 'Run gc with --dry-run first: it reports eligible, retired, cas-miss and unreadable-artifact counts without writing. Eligible rows are candidates only: a changed record or an unusable Claude correlation is retained.', 'No participant or square location is required. Uses SQUARE_HOST_LEDGER_ROOT, then SQUARE_REGISTRY’s directory, then ~/.square/host-ledger.', 'These commands never stop, restart, install into or schedule hosts; conversion and schema upgrade are never automatic. No --force or repair.'],
|
|
81
86
|
},
|
|
82
87
|
];
|
|
83
88
|
function definitionFor(command) {
|
|
@@ -90,7 +95,7 @@ export function renderGlobalHelp() {
|
|
|
90
95
|
const groups = [
|
|
91
96
|
{ key: 'participant', title: 'In the square:', order: ['join', 'express', 'listen', 'ignore', 'listening', 'catch', 'history', 'status', 'hold', 'resume', 'done'] },
|
|
92
97
|
{ key: 'host', title: 'Prepare and manage:', order: ['build', 'list', 'participants'] },
|
|
93
|
-
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor', 'migrate'] },
|
|
98
|
+
{ key: 'maintenance', title: 'Setup:', order: ['install', 'uninstall', 'doctor', 'migrate', 'ledger'] },
|
|
94
99
|
];
|
|
95
100
|
const commandLines = groups.flatMap(({ key, title, order }) => [
|
|
96
101
|
title,
|
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
import { type VersionObserver } from './file-changes.js';
|
|
2
|
-
import type { HostLedgerPort, PresenceRecord, PresenceKey, PresenceLookup, PresenceResult, PresenceClaimResult, EvidenceRecord, EvidenceClaim, EvidenceRelease, EvidenceLookup,
|
|
2
|
+
import type { HostLedgerPort, PresenceRecord, PresenceKey, PresenceLookup, PresenceResult, PresenceClaimResult, EvidenceRecord, EvidenceClaim, EvidenceRelease, EvidenceLookup, NativeTokenLookup, NativeTokenResult, ClaimResult, WakeAttemptClaim, WakeAttemptClaimInput, WakeAttemptLookup } from './host-ledger.js';
|
|
3
3
|
export interface HostLedgerFileAdapterOptions {
|
|
4
4
|
rootPath?: string;
|
|
5
5
|
now?: () => number;
|
|
6
6
|
}
|
|
7
|
+
/** Existing port surface; each operation owns a short-lived SQLite connection. */
|
|
7
8
|
export declare class FileHostLedgerPort implements HostLedgerPort {
|
|
8
9
|
private readonly root;
|
|
9
10
|
private readonly clock;
|
|
10
11
|
constructor(o?: HostLedgerFileAdapterOptions);
|
|
11
|
-
private file;
|
|
12
|
-
/** Invalidation only: callers still use the ledger projections as their authority. */
|
|
13
12
|
observeChanges(): Promise<{
|
|
14
13
|
presence: VersionObserver<string>;
|
|
15
14
|
evidence: VersionObserver<string>;
|
|
16
15
|
}>;
|
|
17
|
-
/** Time can expire bindings/evidence even when their files do not change. */
|
|
18
16
|
nextExpiry(session: string): Promise<number>;
|
|
19
|
-
/** The ownership claim critical section: the same lock as claimPresence and finalize. */
|
|
20
17
|
withClaimLock<T>(fn: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
21
18
|
claimPresence(i: PresenceRecord, signal?: AbortSignal): Promise<PresenceClaimResult>;
|
|
22
19
|
ensurePresence(i: PresenceRecord): Promise<PresenceResult>;
|
|
23
|
-
/** Local batch suppression is monotonic only for the captured host generation. */
|
|
24
20
|
suppressPresence(i: PresenceRecord, through: number): Promise<boolean>;
|
|
25
21
|
removePresence(i: PresenceKey): Promise<void>;
|
|
26
22
|
removePresenceIfUnchanged(i: PresenceRecord): Promise<boolean>;
|
|
27
23
|
listPresence(i?: PresenceLookup): Promise<readonly PresenceRecord[]>;
|
|
28
|
-
/** One attempt row owns attention-wide exclusion and session-scoped history for a wake. */
|
|
29
24
|
claimWakeAttempt(i: WakeAttemptClaimInput): Promise<WakeAttemptClaim>;
|
|
30
|
-
/** One locked, token-fenced replacement; each operation owns its state condition. */
|
|
31
25
|
private updateWake;
|
|
32
26
|
listWakeAttempts(i?: WakeAttemptLookup): Promise<readonly EvidenceRecord[]>;
|
|
33
27
|
prepareNativeWake(i: EvidenceRecord & {
|
|
@@ -40,11 +34,11 @@ export declare class FileHostLedgerPort implements HostLedgerPort {
|
|
|
40
34
|
}): Promise<boolean>;
|
|
41
35
|
claimEvidence(i: EvidenceClaim): Promise<ClaimResult>;
|
|
42
36
|
releaseEvidence(i: EvidenceRelease): Promise<void>;
|
|
43
|
-
/** Token-fenced terminal write. A token may only finish the attempt it claimed. */
|
|
44
37
|
appendEvidence(i: EvidenceRecord & {
|
|
45
38
|
readonly claimToken: string;
|
|
46
39
|
}): Promise<void>;
|
|
47
40
|
listEvidence(i?: EvidenceLookup): Promise<readonly EvidenceRecord[]>;
|
|
48
|
-
|
|
41
|
+
retireEvidenceIfUnchanged(expected: EvidenceRecord): Promise<boolean>;
|
|
42
|
+
lookupNativeTokens(input: NativeTokenLookup): Promise<NativeTokenResult>;
|
|
49
43
|
}
|
|
50
44
|
export declare function createHostLedgerPort(o?: HostLedgerFileAdapterOptions): FileHostLedgerPort;
|