@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.
Files changed (143) hide show
  1. package/dist/artifact.d.ts +6 -1
  2. package/dist/artifact.js +79 -10
  3. package/dist/boundary-presentation.d.ts +5 -4
  4. package/dist/boundary-presentation.js +21 -25
  5. package/dist/catch-decisions.js +2 -1
  6. package/dist/claude-delivery-status.d.ts +8 -2
  7. package/dist/claude-delivery-status.js +26 -10
  8. package/dist/claude-delivery.d.ts +8 -4
  9. package/dist/claude-delivery.js +114 -104
  10. package/dist/claude-mcp-context.js +2 -2
  11. package/dist/claude-mod.js +119 -105
  12. package/dist/cli/context.d.ts +3 -1
  13. package/dist/cli/context.js +25 -1
  14. package/dist/cli/express-presentation.d.ts +3 -1
  15. package/dist/cli/express-presentation.js +14 -10
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +1 -0
  18. package/dist/cli/registry.js +0 -2
  19. package/dist/cli/square-commands.d.ts +4 -2
  20. package/dist/cli/square-commands.js +21 -6
  21. package/dist/codex-hook.d.ts +3 -3
  22. package/dist/codex-hook.js +4 -4
  23. package/dist/codex-queue.d.ts +5 -6
  24. package/dist/codex-queue.js +9 -5
  25. package/dist/decisions.d.ts +3 -2
  26. package/dist/decisions.js +18 -6
  27. package/dist/delivery-health.d.ts +32 -2
  28. package/dist/delivery-health.js +108 -45
  29. package/dist/delivery-operations.d.ts +1 -2
  30. package/dist/delivery-operations.js +155 -196
  31. package/dist/delivery.d.ts +14 -23
  32. package/dist/delivery.js +48 -18
  33. package/dist/file-lock.d.ts +8 -1
  34. package/dist/file-lock.js +2 -0
  35. package/dist/help.js +8 -11
  36. package/dist/inbox.d.ts +35 -9
  37. package/dist/inbox.js +228 -144
  38. package/dist/index.d.ts +3 -4
  39. package/dist/index.js +1 -3
  40. package/dist/list.js +9 -4
  41. package/dist/mcp.js +12 -1
  42. package/dist/model.d.ts +39 -5
  43. package/dist/model.js +84 -0
  44. package/dist/notifications.d.ts +8 -6
  45. package/dist/notifications.js +56 -143
  46. package/dist/operation-presentation.js +7 -4
  47. package/dist/participant-identity.d.ts +0 -3
  48. package/dist/participant-identity.js +0 -31
  49. package/dist/paseo-delivery.d.ts +5 -6
  50. package/dist/paseo-delivery.js +13 -8
  51. package/dist/paseo.d.ts +1 -1
  52. package/dist/paseo.js +1 -1
  53. package/dist/plugins/opencode/delivery.d.ts +8 -3
  54. package/dist/plugins/opencode/delivery.js +187 -132
  55. package/dist/plugins/opencode/index.js +12 -38
  56. package/dist/ports.d.ts +112 -40
  57. package/dist/presentation-operations.d.ts +1 -1
  58. package/dist/presentation-operations.js +51 -35
  59. package/dist/presentation.js +8 -3
  60. package/dist/seats.d.ts +7 -0
  61. package/dist/seats.js +27 -0
  62. package/dist/session-lifecycle.d.ts +2 -2
  63. package/dist/session-lifecycle.js +10 -12
  64. package/dist/sqlite-square.d.ts +20 -9
  65. package/dist/sqlite-square.js +476 -341
  66. package/dist/square-api.d.ts +1 -0
  67. package/dist/square-api.js +3 -4
  68. package/dist/square-core.d.ts +3 -2
  69. package/dist/square-domain.d.ts +12 -5
  70. package/dist/square-domain.js +163 -7
  71. package/dist/square-facade.d.ts +16 -10
  72. package/dist/square-index.d.ts +24 -0
  73. package/dist/square-index.js +131 -0
  74. package/dist/square-legacy-framing.d.ts +9 -0
  75. package/dist/square-legacy-framing.js +56 -0
  76. package/dist/square-migration-host-ledger.d.ts +26 -0
  77. package/dist/square-migration-host-ledger.js +168 -0
  78. package/dist/square-migration.d.ts +5 -5
  79. package/dist/square-migration.js +373 -71
  80. package/dist/square-sqlite.d.ts +9 -9
  81. package/dist/square-sqlite.js +329 -79
  82. package/dist/square-stdio.d.ts +1 -1
  83. package/dist/square-stdio.js +12 -7
  84. package/dist/square-tools.d.ts +1 -1
  85. package/dist/square-tools.js +80 -27
  86. package/dist/stdio-server.js +4 -2
  87. package/dist/stream.d.ts +3 -2
  88. package/dist/stream.js +9 -10
  89. package/dist/views.d.ts +9 -14
  90. package/dist/views.js +52 -70
  91. package/dist/wakes.d.ts +6 -5
  92. package/dist/wakes.js +20 -17
  93. package/guides/application-facade-delivery-evidence.md +1 -1
  94. package/guides/claude-native.md +9 -9
  95. package/guides/mcp.md +2 -2
  96. package/guides/migration.md +107 -67
  97. package/guides/pi-tools.md +1 -1
  98. package/guides/row-storage-design.md +1 -1
  99. package/guides/square-api.md +61 -28
  100. package/package.json +1 -1
  101. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  102. package/plugins/claude/hooks/register.js +1 -1
  103. package/plugins/claude/skills/square/SKILL.md +4 -4
  104. package/plugins/claude/types.d.ts +1 -1
  105. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  106. package/plugins/opencode/delivery.ts +170 -118
  107. package/plugins/opencode/index.ts +12 -30
  108. package/plugins/pi/square-pi.js +56 -18
  109. package/skills/square/SKILL.md +4 -4
  110. package/dist/cli/ledger-command.d.ts +0 -12
  111. package/dist/cli/ledger-command.js +0 -72
  112. package/dist/host-ledger-file-adapter.d.ts +0 -47
  113. package/dist/host-ledger-file-adapter.js +0 -384
  114. package/dist/host-ledger-migration.d.ts +0 -27
  115. package/dist/host-ledger-migration.js +0 -264
  116. package/dist/host-ledger-root.d.ts +0 -2
  117. package/dist/host-ledger-root.js +0 -9
  118. package/dist/host-ledger-sqlite.d.ts +0 -65
  119. package/dist/host-ledger-sqlite.js +0 -368
  120. package/dist/host-ledger.d.ts +0 -195
  121. package/dist/host-ledger.js +0 -1
  122. package/dist/ledger-maintenance.d.ts +0 -30
  123. package/dist/ledger-maintenance.js +0 -110
  124. package/dist/open-square.d.ts +0 -38
  125. package/dist/open-square.js +0 -81
  126. package/dist/participant-host.d.ts +0 -23
  127. package/dist/participant-host.js +0 -92
  128. package/dist/registry.d.ts +0 -55
  129. package/dist/registry.js +0 -123
  130. package/dist/routes.d.ts +0 -39
  131. package/dist/routes.js +0 -99
  132. package/dist/square-actions.d.ts +0 -56
  133. package/dist/square-actions.js +0 -353
  134. package/dist/square-projections.d.ts +0 -31
  135. package/dist/square-projections.js +0 -64
  136. package/dist/wake-attempts.d.ts +0 -10
  137. package/dist/wake-attempts.js +0 -39
  138. package/dist/wake-eligibility.d.ts +0 -53
  139. package/dist/wake-eligibility.js +0 -123
  140. package/dist/wake-evidence.d.ts +0 -33
  141. package/dist/wake-evidence.js +0 -32
  142. package/guides/host-ledger.md +0 -58
  143. package/guides/indexed-host-ledger-performance.md +0 -39
@@ -1,39 +0,0 @@
1
- import { hostLedgerRoot } from './host-ledger-root.js';
2
- import { nameKey } from './model.js';
3
- import { canonicalPath } from './canonical-path.js';
4
- import { formatActivityId } from './square-core.js';
5
- import { createHostLedgerPort } from './host-ledger-file-adapter.js';
6
- import { redactDiagnostic } from './diagnostic-redaction.js';
7
- import { decodeWakeEvidence } from './wake-evidence.js';
8
- export function redactWakeDiagnostic(value, env = process.env) {
9
- return redactDiagnostic(value, env.PASEO_PASSWORD);
10
- }
11
- export async function readWakeReleaseDiagnostics(opts = {}) {
12
- const now = opts.now ?? Date.now();
13
- const env = opts.env ?? process.env;
14
- const rows = await createHostLedgerPort({ rootPath: hostLedgerRoot(env) }).listEvidence({
15
- kind: 'wake',
16
- location: opts.location ?? opts.attention?.squarePath,
17
- includeReleased: true,
18
- now,
19
- });
20
- const expected = opts.attention === undefined ? undefined : JSON.stringify([await canonicalPath(opts.attention.squarePath), formatActivityId(opts.attention.actIndex), nameKey(opts.attention.recipient)]);
21
- const releases = [];
22
- for (const row of rows) {
23
- const decoded = decodeWakeEvidence(row, now);
24
- if (decoded?.kind !== 'release')
25
- continue;
26
- const release = decoded.value;
27
- if (opts.sessionId !== undefined && release.session !== opts.sessionId)
28
- continue;
29
- const { attention } = release;
30
- if (expected !== undefined && JSON.stringify([attention.squarePath, formatActivityId(attention.actIndex), nameKey(attention.recipient)]) !== expected)
31
- continue;
32
- releases.push({
33
- ...release,
34
- ...(release.message === undefined ? {} : { message: redactWakeDiagnostic(release.message, env) }),
35
- ...(release.diagnostic === undefined ? {} : { diagnostic: redactWakeDiagnostic(release.diagnostic, env) }),
36
- });
37
- }
38
- return releases.sort((left, right) => right.at - left.at);
39
- }
@@ -1,53 +0,0 @@
1
- import { type WakeRoute } from './model.js';
2
- import type { EvidenceRecord, PresenceRecord } from './host-ledger.js';
3
- import type { DirectedHead, HostLedgerPort, PresentationEvidenceProjection, RouteFact, WakeRequest } from './ports.js';
4
- import { type WakeAttempt } from './wake-evidence.js';
5
- export interface WakeSnapshot {
6
- readonly routes: readonly RouteFact[];
7
- readonly pending: readonly DirectedHead[];
8
- readonly seen?: readonly {
9
- readonly recipient: string;
10
- readonly index: number;
11
- }[];
12
- /** Canonicalized by the caller; every identity in this snapshot is square-local. */
13
- readonly location: string;
14
- readonly owners: readonly PresenceRecord[];
15
- readonly bindings?: readonly PresenceRecord[];
16
- readonly wakeRecords: readonly EvidenceRecord[];
17
- readonly presentations: readonly PresentationEvidenceProjection[];
18
- readonly now: number;
19
- }
20
- export interface OwnedWakeRoute {
21
- readonly route: WakeRoute;
22
- readonly owner: PresenceRecord;
23
- }
24
- export interface WakeCurrentness {
25
- readonly current: boolean;
26
- readonly activityPending: boolean;
27
- readonly sessionBound: boolean;
28
- readonly routePublished: boolean;
29
- readonly selectedOwner: boolean;
30
- readonly cancelled: boolean;
31
- readonly presented: boolean;
32
- }
33
- export interface WakeEvidence {
34
- readonly delivered: boolean;
35
- /** All selected, uncancelled routes have session-local presentation evidence. */
36
- readonly presented: boolean;
37
- readonly attempts: readonly WakeAttempt[];
38
- readonly terminal?: WakeAttempt;
39
- readonly attemptableRoutes: readonly WakeRoute[];
40
- }
41
- export interface WakeEligibility {
42
- evidence(recipient: string, actIndex: number): WakeEvidence;
43
- currentness(request: WakeRequest): WakeCurrentness;
44
- }
45
- /** One strict ownership join, latest selection, and request fence over primary evidence only. */
46
- export declare function projectWakeEligibility(input: WakeSnapshot): WakeEligibility;
47
- /** Host observation happens only after detached artifact facts have left their transaction. */
48
- export declare function projectWakeEvidence(input: Pick<WakeSnapshot, 'routes' | 'pending' | 'seen' | 'location' | 'now' | 'bindings'> & {
49
- readonly hostLedger: HostLedgerPort;
50
- readonly owners?: readonly PresenceRecord[];
51
- }): Promise<WakeEligibility>;
52
- export declare function sameBinding(captured: PresenceRecord, current: PresenceRecord): boolean;
53
- export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
@@ -1,123 +0,0 @@
1
- import { nameKey } from './model.js';
2
- import { formatActivityId, parseActivityId } from './square-core.js';
3
- import { presenceEpoch } from './registry.js';
4
- import { canonicalPath } from './canonical-path.js';
5
- import { decodeWakeEvidence } from './wake-evidence.js';
6
- function identity(...parts) { return JSON.stringify(parts); }
7
- /** One strict ownership join, latest selection, and request fence over primary evidence only. */
8
- export function projectWakeEligibility(input) {
9
- const ownersBySession = new Map();
10
- for (const owner of input.owners) {
11
- if (owner.location !== input.location)
12
- continue;
13
- const key = identity(nameKey(owner.participant), owner.session);
14
- const rows = ownersBySession.get(key) ?? [];
15
- rows.push(owner);
16
- ownersBySession.set(key, rows);
17
- }
18
- const ownerFor = (route) => route.location !== input.location ? undefined
19
- : ownersBySession.get(identity(nameKey(route.participant), route.sessionId))?.find((row) => route.epoch === undefined || presenceEpoch(row) === route.epoch);
20
- const ownedRoutes = [];
21
- const latest = new Map();
22
- for (const fact of input.routes) {
23
- const participant = nameKey(fact.participant);
24
- const owner = ownerFor(fact);
25
- // Malformed legacy epoch cannot acquire an owner by coercion or enter a transport request.
26
- if (owner === undefined || (fact.epoch !== undefined && typeof fact.epoch !== 'number'))
27
- continue;
28
- const { extra, epoch, ...route } = fact;
29
- ownedRoutes.push({ route: { ...route, ...(epoch === undefined ? {} : { epoch }) }, owner });
30
- latest.set(participant, Math.max(latest.get(participant) ?? Number.NEGATIVE_INFINITY, route.updatedAt));
31
- }
32
- const selectedByRecipient = new Map();
33
- for (const owned of ownedRoutes) {
34
- const key = nameKey(owned.route.participant);
35
- if (owned.route.updatedAt !== latest.get(key))
36
- continue;
37
- const rows = selectedByRecipient.get(key) ?? [];
38
- rows.push(owned);
39
- selectedByRecipient.set(key, rows);
40
- }
41
- const pending = new Set(input.pending.map((head) => identity(nameKey(head.recipient), head.index)));
42
- const seen = new Set(input.seen?.map((receipt) => identity(nameKey(receipt.recipient), receipt.index)));
43
- const attemptsByAttention = new Map();
44
- for (const record of input.wakeRecords) {
45
- if (record.location !== input.location)
46
- continue;
47
- const decoded = decodeWakeEvidence(record, input.now);
48
- if (decoded?.kind !== 'attempt')
49
- continue;
50
- const attempt = decoded.value;
51
- const key = identity(nameKey(attempt.attention.recipient), attempt.attention.actIndex);
52
- const rows = attemptsByAttention.get(key) ?? [];
53
- rows.push(attempt);
54
- attemptsByAttention.set(key, rows);
55
- }
56
- const presentedSessions = new Set();
57
- for (const row of input.presentations) {
58
- if (row.location !== input.location || (row.outcome !== 'presented' && row.outcome !== 'clipped'))
59
- continue;
60
- presentedSessions.add(identity(nameKey(row.participant), row.activity, row.sessionId));
61
- }
62
- const isPresented = (route, actIndex) => presentedSessions.has(identity(nameKey(route.participant), formatActivityId(actIndex), route.sessionId));
63
- const isCancelled = (owner, actIndex) => actIndex <= (owner.cancelledThrough ?? -1);
64
- const bindingCurrent = (owner) => input.bindings === undefined
65
- || input.bindings.some((captured) => sameBinding(captured, owner));
66
- return {
67
- evidence(recipient, actIndex) {
68
- const key = nameKey(recipient);
69
- // Accepted/unknown exclusion belongs to attention, not to a surviving owner or route kind.
70
- const attempts = attemptsByAttention.get(identity(key, actIndex)) ?? [];
71
- const terminal = attempts.findLast((attempt) => attempt.outcome === 'accepted')
72
- ?? attempts.findLast((attempt) => attempt.outcome === 'unknown');
73
- const routes = (selectedByRecipient.get(key) ?? []).filter(({ owner }) => !isCancelled(owner, actIndex));
74
- const presented = routes.length > 0 && routes.every(({ route }) => isPresented(route, actIndex));
75
- return {
76
- delivered: seen.has(identity(key, actIndex)), presented, attempts,
77
- ...(terminal === undefined ? {} : { terminal }),
78
- attemptableRoutes: terminal === undefined && pending.has(identity(key, actIndex))
79
- ? routes.filter(({ route, owner }) => bindingCurrent(owner) && !isPresented(route, actIndex)).map(({ route }) => route) : [],
80
- };
81
- },
82
- currentness(request) {
83
- const actIndex = parseActivityId(request.activity);
84
- const route = request.route;
85
- const sameRecipient = nameKey(request.participant) === nameKey(route.participant);
86
- const sameLocation = request.location === input.location && route.location === input.location;
87
- const activityPending = actIndex !== undefined && request.location === input.location
88
- && pending.has(identity(nameKey(request.participant), actIndex));
89
- const owner = sameLocation && sameRecipient ? ownerFor(route) : undefined;
90
- const sessionBound = owner !== undefined && bindingCurrent(owner);
91
- const matchesRequest = (published) => published.location === route.location
92
- && nameKey(published.participant) === nameKey(route.participant) && published.sessionId === route.sessionId
93
- && published.kind === route.kind && published.epoch === route.epoch && Object.keys(published.address).length === Object.keys(route.address).length && Object.keys(published.address).every((key) => published.address[key] === route.address[key]);
94
- const routePublished = sameLocation && sameRecipient && input.routes.some(matchesRequest);
95
- const selectedOwner = sameLocation && sameRecipient
96
- && (selectedByRecipient.get(nameKey(route.participant)) ?? []).some(({ route: published }) => matchesRequest(published));
97
- const cancelled = actIndex !== undefined && owner !== undefined && isCancelled(owner, actIndex);
98
- const presented = actIndex !== undefined && sameLocation && sameRecipient && isPresented(route, actIndex);
99
- // Dispatching evidence deliberately does not fence the request that owns the atomic claim.
100
- return { current: activityPending && sessionBound && routePublished && selectedOwner && !cancelled && !presented,
101
- activityPending, sessionBound, routePublished, selectedOwner, cancelled, presented };
102
- },
103
- };
104
- }
105
- /** Host observation happens only after detached artifact facts have left their transaction. */
106
- export async function projectWakeEvidence(input) {
107
- const location = await canonicalPath(input.location);
108
- const owners = input.owners ?? await input.hostLedger.listPresence({ location, now: input.now });
109
- const records = await input.hostLedger.listEvidence({ location, now: input.now });
110
- const presentations = records.filter((row) => row.kind === 'presentation').map((row) => ({
111
- location: row.location, participant: row.participant, sessionId: row.session, activity: row.activity, outcome: row.outcome,
112
- ...(row.at === undefined ? {} : { at: row.at }),
113
- }));
114
- return projectWakeEligibility({ ...input, location, owners, wakeRecords: records.filter((row) => row.kind === 'wake'), presentations });
115
- }
116
- export function sameBinding(captured, current) {
117
- return captured.location === current.location && nameKey(captured.participant) === nameKey(current.participant)
118
- && captured.session === current.session && captured.channel === current.channel && captured.epoch === current.epoch
119
- && (captured.cancelledThrough ?? -1) === (current.cancelledThrough ?? -1);
120
- }
121
- export function wakeIsEligible(evidence) {
122
- return !evidence.delivered && !evidence.presented && evidence.terminal === undefined && evidence.attemptableRoutes.length > 0;
123
- }
@@ -1,33 +0,0 @@
1
- import type { EvidenceRecord, WakeAttention, NativeDeliveryObservation } from './host-ledger.js';
2
- import type { WakeRouteKind } from './model.js';
3
- export interface WakeAttempt {
4
- readonly nativeObservations?: readonly NativeDeliveryObservation[];
5
- readonly at: number;
6
- readonly attention: WakeAttention;
7
- readonly routeKind: WakeRouteKind;
8
- readonly outcome: 'accepted' | 'unknown' | 'failed';
9
- readonly signature?: string;
10
- readonly attemptN: number;
11
- readonly session?: string;
12
- readonly message?: string;
13
- readonly diagnostic?: unknown;
14
- }
15
- export interface WakeReleaseDiagnostic {
16
- readonly at: number;
17
- readonly attention: WakeAttention;
18
- readonly routeKind?: WakeRouteKind;
19
- readonly attemptN?: number;
20
- readonly session?: string;
21
- readonly signature?: string;
22
- readonly message?: string;
23
- readonly diagnostic?: unknown;
24
- }
25
- export type DecodedWakeEvidence = {
26
- readonly kind: 'attempt';
27
- readonly value: WakeAttempt;
28
- } | {
29
- readonly kind: 'release';
30
- readonly value: WakeReleaseDiagnostic;
31
- };
32
- /** Decode primary attempts and diagnostic-only releases without mixing their evidence. */
33
- export declare function decodeWakeEvidence(record: EvidenceRecord, now: number): DecodedWakeEvidence | undefined;
@@ -1,32 +0,0 @@
1
- import { parseActivityId } from './square-core.js';
2
- /** Decode primary attempts and diagnostic-only releases without mixing their evidence. */
3
- export function decodeWakeEvidence(record, now) {
4
- if (record.kind !== 'wake')
5
- return undefined;
6
- const actIndex = parseActivityId(record.activity);
7
- if (actIndex === undefined)
8
- return undefined;
9
- const value = {
10
- at: record.at ?? now,
11
- attention: { squarePath: record.location, recipient: record.participant, actIndex },
12
- ...(record.session === undefined ? {} : { session: record.session }),
13
- ...(record.signature === undefined ? {} : { signature: record.signature }),
14
- ...(record.message === undefined ? {} : { message: record.message }),
15
- ...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }),
16
- };
17
- if (record.outcome === 'released') {
18
- if (record.at === undefined)
19
- return undefined;
20
- return { kind: 'release', value: {
21
- ...value,
22
- ...(record.routeKind === undefined ? {} : { routeKind: record.routeKind }),
23
- ...(record.attemptN === undefined ? {} : { attemptN: record.attemptN }),
24
- } };
25
- }
26
- if (record.outcome !== 'accepted' && record.outcome !== 'unknown' && record.outcome !== 'failed')
27
- return undefined;
28
- if (record.routeKind === undefined || typeof record.attemptN !== 'number')
29
- return undefined;
30
- return { kind: 'attempt', value: { ...value, routeKind: record.routeKind, attemptN: record.attemptN, outcome: record.outcome,
31
- ...(record.nativeObservations === undefined ? {} : { nativeObservations: record.nativeObservations }) } };
32
- }
@@ -1,58 +0,0 @@
1
- # Host ledger
2
-
3
- Square uses one user-level `host-ledger.sqlite` for participant bindings, delivery evidence, and wake attempts. It is separate from every `.square` artifact: the ledger owns host identity, ownership epochs, discovery and delivery evidence; the artifact owns square activity and participant lifecycle.
4
-
5
- The default root is `~/.square/host-ledger`. Tests and isolated harnesses may set `SQUARE_HOST_LEDGER_ROOT`; otherwise `SQUARE_REGISTRY` selects its containing directory. An explicit adapter `rootPath` takes precedence. These settings select a single root, not additional ledgers.
6
-
7
- ## Storage and delivery
8
-
9
- SQLite uses DELETE journaling and FULL synchronous writes. Records retain original order and duplicates, with derived lookup indexes and separate presence/evidence revisions. Normal reads of an empty root create nothing. First mutation initializes storage only when neither legacy NDJSON file exists. Legacy-only storage refuses ordinary operation and requires explicit offline conversion; normal operation never dual-reads or automatically migrates.
10
-
11
- One wake attempt is one evidence record. Acquisition creates `dispatching` directly, before a send may happen. An interrupted dispatch becomes `unknown`, which forbids redispatch; current token-fenced recovery may still accept a late native admission. Accepted and possibly-sent wake records remain readable at any age. Prepared Claude correlations also bypass age visibility and housekeeping across every outcome, including failed and released, until explicitly retired. Ordinary records retain their existing seven-day visibility horizon, with expired ordinary rows reclaimed only during actual mutations. There is no automatic maintenance GC or activity-id-only sweep; durable delivery evidence is retired only by the explicit, artifact-proven `square ledger gc` below.
12
-
13
- ## Verify and convert offline
14
-
15
- These commands need no participant or square location and use the resolved ledger root:
16
-
17
- ```bash
18
- square ledger verify
19
- ```
20
-
21
- Verification is read-only. It checks SQLite identity, version, schema, integrity, record codecs, derived indexes and revisions. With only legacy files, it scans both complete sources and reports accepted, ignored and rejected counts, readiness, and SHA-256 hashes of the original source bytes. Unsafe indexed records make readiness false without hiding the remaining counts or hashes; the CLI reports not ready and exits with status 2. With no files, it reports empty without creating a directory, database or lock. Output never includes record payloads, native credentials or endpoints.
22
-
23
- To upgrade an NDJSON ledger:
24
-
25
- 1. Stop **every** agent session and MCP service using Square with this ledger, including interactive harnesses and background agents. Do not run old and new ones together.
26
- 2. Keep a backup of the stopped legacy files.
27
- 3. Run:
28
-
29
- ```bash
30
- square ledger verify
31
- square ledger migrate
32
- square ledger verify
33
- ```
34
-
35
- 4. Start all hosts with the updated Square version together, using the same root.
36
-
37
- Migration does not stop or restart hosts. Its locks fence compatible writers but do not prove hosts are stopped. It acquires the ownership claim fence, then `presence.ndjsonl.lock`, then `evidence.ndjsonl.lock`.
38
-
39
- An older SQLite ledger format is upgraded in place by the same `square ledger migrate` under the same stopped-sessions prerequisite and the same lock order. The upgrade preserves every record, duplicate, original order and both observer revisions. `square ledger verify` stays read-only: it reports a supported old format with the migrate command and never upgrades silently. An unsupported format version or an unrelated database is refused with a diagnosis path instead: migration is not a repair command, and a failed migration is never answered by rerunning it blindly.
40
-
41
- The importer preserves all ages, future records, duplicates, original order, native correlation and residual fields. It ignores malformed JSON, wrong versions and records without a numeric timestamp, as the old reader did, and reports those counts. Any valid version-1 record whose indexed fields are unsafe refuses the entire conversion rather than losing evidence. Refusal identifies only source kind, rejected count and first rejected line; no accepted subset is published. Legacy unknown wake sources are normalized once during import.
42
-
43
- A temporary SQLite database is built and verified under the same root, then published without overwriting an existing destination. Legacy sources remain byte-for-byte untouched. A valid existing SQLite ledger is an idempotent no-op; an older SQLite ledger format is upgraded in place under the fence; invalid or unsupported destinations are refused without repair. There is no force option. On a reported failure, keep hosts stopped, resolve the cause and rerun verification/conversion. An abrupt process termination can leave an unpublished `.host-ledger-migrate-*` directory; after confirming no converter is running, it can be removed without touching the sources or published database.
44
-
45
- Once SQLite exists, legacy files are not consulted. Keeping them does not provide a rollback path after new SQLite writes: reverting to them would lose ownership and possibly-sent evidence. For a live SQLite ledger, use SQLite backup facilities rather than copying only its main file.
46
-
47
- ## Reclaim old evidence
48
-
49
- ```bash
50
- square ledger gc --dry-run
51
- square ledger gc
52
- ```
53
-
54
- `gc` considers wake and presentation evidence at least seven days old whose attention is permanently closed, proven per row against the square artifact named in the row: the activity historically reached that participant, and either catch recorded an exact receipt or the participant's later rejoin moved past it. Rows still `dispatching`, lifecycle rows, rows with unusable coordinates and rows whose artifact is missing, invalid or unreadable are retained. Eligibility is a proof, not a promise: `retired` counts rows actually removed, and a row whose record changed after the proof, or whose Claude correlation cannot be retired safely, is retained instead. `--dry-run` proves the same set and changes neither the ledger nor any artifact.
55
-
56
- Reclamation is counted in rows, not bytes. Deleted rows free storage for reuse but do not necessarily shrink the SQLite file immediately; `gc` never runs `VACUUM`. Retiring a prepared Claude correlation payload leaves only a permanent `(harness, session, token)` recognition record, without payload, endpoint, location, participant, epoch or timestamp. These records are absent from ordinary evidence and attempts and confer no admission, presentation or receipt authority. Any retired token vetoes the entire native frame, including a mixed live/retired frame. Total token count remains unbounded. There are no hooks, timers or install steps: maintenance runs only when asked.
57
-
58
- The obsolete `SQUARE_HOST_LEDGER_USER` and `SQUARE_HOST_LEDGER_LOCAL` settings, project-local fallback and scope-based copying are not used. Older local files and `wake-claims.ndjsonl` remain untouched and are not imported. Bindings that existed only in those abandoned stores require their host to rejoin; do not discard the supported ledger to reset bindings.
@@ -1,39 +0,0 @@
1
- # Indexed host-ledger measurement
2
-
3
- The indexed host ledger eliminates whole-file NDJSON reads. This run does not show a consistent reduction in the 300-process catch-phase latency: the smaller-history case was slightly slower and the larger-history case was faster. Artifact contention remains substantial.
4
-
5
- ## Comparable workload
6
-
7
- Baseline: `895ff50` (NDJSON with origin-separated instrumentation). SQLite plus safe retirement candidate: `60ea8b0`. Both used the same benchmark script, 300 processes, session discovery, active bell, disabled filesystem hints, and histories of 1,800 and 18,000 unrelated activities.
8
-
9
- ```sh
10
- node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 300 --mode session --active --no-hints
11
- ```
12
-
13
- The catch phase times opening the square, recognizing the participant, a first catch, a second catch checking duplicate consumption, and closing the square. These numbers are **not single-catch latency**, nor ordinary single-user latency. Discovery is measured separately. These are one completed run per implementation and history size, not repeated-run confidence intervals.
14
-
15
- | Metric | NDJSON, 1,800 | SQLite, 1,800 | NDJSON, 18,000 | SQLite, 18,000 |
16
- | --- | ---: | ---: | ---: | ---: |
17
- | Catch-phase p50 (ms) | 6,134.67 | 6,159.17 | 6,775.98 | 5,532.52 |
18
- | Catch-phase p95 (ms) | 7,307.72 | 7,481.01 | 8,182.00 | 7,170.30 |
19
- | Catch-phase aggregate CPU (ms) | 24,171.34 | 24,045.67 | 27,538.51 | 23,574.19 |
20
- | Artifact busy observations | 46,710 | 52,686 | 56,401 | 46,069 |
21
- | Catch-phase artifact body rows | 300 | 300 | 300 | 300 |
22
- | Catch-phase artifact writes | 600 | 600 | 600 | 600 |
23
- | Discovery p95 (ms) | 1,931 | 1,927 | 1,881 | 1,915 |
24
-
25
- Every completed scenario discovered all 300 participants, consumed exactly one activity per participant, and consumed none again on the second catch.
26
-
27
- ## Ledger work
28
-
29
- For either history size, the NDJSON catch phase performed 1,500 file reads totaling 95,520,000 bytes. The SQLite catch phase performed 1,500 data-read executions, returning 1,500 presence rows; all SQL result values together totaled 316,900 bytes (4,500 rows including header results). Host-ledger SQL busy observations were zero.
30
-
31
- These are different transport measurements: file bytes versus SQL values returned to JavaScript. They demonstrate that whole-ledger materialization is gone; they do **not** measure equivalent physical disk I/O or prove a proportional speedup. SQLite bypasses the JavaScript filesystem hooks, so zero NDJSON operations/bytes is structural, not a standalone performance result.
32
-
33
- The remaining catch-phase contention is attributed to the square artifact, separately from the host ledger. The result motivates investigating concurrent artifact transactions if reducing this latency is the next objective; it does not establish the benefit of any unimplemented change.
34
-
35
- ## Evidence and limits
36
-
37
- Local raw reports: `/tmp/square-indexed-baseline-300.json` and `/tmp/square-indexed-final-300.json`. The completed SQLite run exited zero with empty stderr. An earlier invocation was terminated by the tool's 60-second execution limit before producing a report and was excluded; the completed run used the unchanged workload in a background process.
38
-
39
- This benchmark uses an isolated ledger. It does not migrate the live host ledger, exercise real native transports, or measure GC reclamation. GC correctness is covered by the separate retirement, closure-proof, schema-upgrade, and CLI tests. Retirement leaves reusable SQLite pages and indefinitely retained Claude recognition tokens; neither immediate file shrinkage nor bounded total token growth is promised.