@xyo-network/event-kit-node 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +53 -34
  2. package/dist/node/bestEffortSnapshotWorker.d.ts +44 -0
  3. package/dist/node/bestEffortSnapshotWorker.d.ts.map +1 -0
  4. package/dist/node/bestEffortTypes.d.ts +55 -0
  5. package/dist/node/bestEffortTypes.d.ts.map +1 -0
  6. package/dist/node/filesystemCursorStore.d.ts.map +1 -1
  7. package/dist/node/filesystemJson.d.ts.map +1 -1
  8. package/dist/node/filesystemPublisherOutbox.d.ts.map +1 -1
  9. package/dist/node/filesystemWakeInbox.d.ts +3 -20
  10. package/dist/node/filesystemWakeInbox.d.ts.map +1 -1
  11. package/dist/node/filesystemWakeQueue.d.ts +10 -19
  12. package/dist/node/filesystemWakeQueue.d.ts.map +1 -1
  13. package/dist/node/handshake.mjs +12 -11
  14. package/dist/node/handshake.mjs.map +2 -2
  15. package/dist/node/index.d.ts +8 -1
  16. package/dist/node/index.d.ts.map +1 -1
  17. package/dist/node/index.mjs +1911 -611
  18. package/dist/node/index.mjs.map +4 -4
  19. package/dist/node/privateSqlite.d.ts +19 -0
  20. package/dist/node/privateSqlite.d.ts.map +1 -0
  21. package/dist/node/receiverAuthority.d.ts +102 -0
  22. package/dist/node/receiverAuthority.d.ts.map +1 -0
  23. package/dist/node/receiverMigration.d.ts +16 -0
  24. package/dist/node/receiverMigration.d.ts.map +1 -0
  25. package/dist/node/receiverMigrationInventory.d.ts +6 -0
  26. package/dist/node/receiverMigrationInventory.d.ts.map +1 -0
  27. package/dist/node/receiverMigrationSnapshot.d.ts +6 -0
  28. package/dist/node/receiverMigrationSnapshot.d.ts.map +1 -0
  29. package/dist/node/receiverMigrationTypes.d.ts +113 -0
  30. package/dist/node/receiverMigrationTypes.d.ts.map +1 -0
  31. package/dist/node/runtime.mjs +2 -2
  32. package/dist/node/runtime.mjs.map +2 -2
  33. package/dist/node/sqliteBestEffortInbox.d.ts +20 -0
  34. package/dist/node/sqliteBestEffortInbox.d.ts.map +1 -0
  35. package/dist/node/sqliteWakeQueue.d.ts +83 -17
  36. package/dist/node/sqliteWakeQueue.d.ts.map +1 -1
  37. package/package.json +27 -27
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # `@xyo-network/event-kit-node`
2
2
 
3
- Node.js 24+ durability adapters for Event Kit (verified on Node 24.14.1):
3
+ Node.js 26+ durability adapters for Event Kit. The repository pins Node 26.9.0:
4
4
 
5
5
  - `createFilesystemCursorStore` — publisher cursor for the wake publisher actor
6
6
  - `createFilesystemPublisherOutbox` — durable pending-wake slot for retry stability
7
- - `createFilesystemWakeQueue` — crash-safe at-least-once `WakeQueue`
8
- - `createSqliteWakeQueue` — `WakeQueue` over `node:sqlite`, no new dependencies
9
- - `createFilesystemWakeInbox` — durable `AtomicWakeInbox` over that queue
7
+ - `createSqliteWakeReceiver` — atomic durable inbox/queue over one private FULL/WAL SQLite database
8
+ - `createSqliteWakeQueue` — standalone fenced `WakeQueue` over `node:sqlite`
9
+ - `createFilesystemWakeQueue` / `createFilesystemWakeInbox` — coordinated SQLite authority with inspectable JSON projections
10
+ - `createSqliteBestEffortInbox` / `createBestEffortSnapshotWorker` — persistent bounded hint security/coalescing and independent snapshot work
11
+ - `migrateReceiverStore` / `migrateIdOnlyReducerCheckpoint` — explicit stopped-owner offline migration and reports
10
12
 
11
13
  ## Handshake clients
12
14
 
@@ -63,43 +65,60 @@ the remaining hosted/Sequence gates.
63
65
 
64
66
  ## Durability model
65
67
 
66
- `createFilesystemWakeInbox` commits in one order and that order is the
67
- correctness property: the wake reaches the durable queue **before** either
68
- identity record is written.
69
-
70
- - A crash in the gap redelivers the wake, which the idempotent consumer absorbs.
71
- - The reverse order could claim `wakeId` for a wake that was never queued and
72
- wedge it permanently, which D-002 §6 forbids.
73
-
74
- Records and queue entries are published with `link`, never `rename`. `rename` is
75
- unsound as a claim: when two callers race the same source, both can observe
76
- success once the paths resolve to one inode, and the entry is handed out twice.
77
- `link` fails with `EEXIST`, so exactly one caller wins and the loser re-reads the
78
- winner's receipt.
79
-
80
- A claim is `link(pending -> inflight)` then `unlink(pending)`. A consumer that
81
- dies mid-handler leaves its entry in `inflight/`; `recoverInflight()` returns it
82
- to `pending/` at startup. A crash between the link and the unlink leaves one
83
- inode in both sets, and recovery keeps the pending copy.
68
+ The [2026-10-05 remediation](../../docs/CODE_REVIEW_REMEDIATION_PLAN_2026-10-05.md)
69
+ replaces split identity/enqueue commits with one transaction over scoped wake
70
+ and token bindings, a stable receipt, exact authorized work, lease state and
71
+ completion. Duplicate token binding/expiry extension is atomic. FULL/WAL SQLite
72
+ coordinates objects and processes on one local host; a bounded 100 ms busy wait
73
+ fails visibly under unresolved contention. Retrying retains logical identity.
74
+
75
+ The filesystem adapter uses the same authority in `receiver.sqlite`. Private
76
+ `pending/` and `inflight/` JSON files are projections, published exclusively with
77
+ `link` and synced directories. They do not decide admission or lease ownership.
78
+ Missing projections can be repaired; corrupt or mismatching bytes stop processing
79
+ and remain available for inspection. Claims validate shape and canonical hash
80
+ against committed authority. Completion removes the retained body while keeping
81
+ the stable identity/receipt tombstone; stale files cannot resurrect completed work.
82
+ `repairProjections()` scans active work and filesystem references explicitly.
83
+
84
+ Owned directories/files are `0700`/`0600`, with owner, regular-file and symlink
85
+ checks. Unsafe existing custody is refused instead of chmodding unrelated paths.
86
+ Publisher cursor/outbox replacement remains single-writer `rename`, with file
87
+ and publication-directory syncs. Read, parse, state and sync failures propagate;
88
+ retained retry bytes/IDs are never replaced because a read failed.
89
+
90
+ Legacy stores are refused before journal-mode mutation. Use the
91
+ [offline migration runbook](../../docs/CODE_REVIEW_RECEIVER_MIGRATION_2026-10-05.md)
92
+ on stopped-owner copies; retain backup, inventory and quarantine evidence. The
93
+ new source format is intentionally incompatible with old writers and runtime
94
+ dual readers are absent.
84
95
 
85
96
  ## Queue providers
86
97
 
87
- Both queues implement the shared lease-based `WakeQueue` port and pass the
88
- conformance suite in `@xyo-network/event-kit-testing`, so they are swappable
89
- with each other and with the in-process memory provider.
98
+ All three providers assign a fresh lease fence on each claim. `ack()`/`release()`
99
+ compare the current entry/state/token atomically. A stale or opposite completion
100
+ throws `StaleWakeLeaseError`; repeating the same successful operation is inert.
101
+ Recovery requires `recoverInflight({ consumersStopped: true })` after every
102
+ consumer sharing the store has stopped. The assertion is an operator contract,
103
+ not a liveness detector. Startup alone does not establish quiescence.
104
+
105
+ Expanded shared conformance covers stale/repeated completion and successor
106
+ preservation. Independent-object/process admission races, real SIGKILL boundaries,
107
+ private custody and signed HTTP tests supplement it. Provider clauses requiring
108
+ `reopen`/cross-writer support remain explicitly skipped for memory.
90
109
 
91
- SQLite makes the lease a single statement: `UPDATE … WHERE state = 'pending'`,
92
- where the affected-row count decides the winner. That is the transactional form
93
- of what the filesystem provider approximates with `link`. WAL lets several
94
- processes on one host share the file; it does not coordinate across hosts.
110
+ Application effects remain at least once. Fence queue state and separately make
111
+ external effects idempotent or atomically commit effects/checkpoints in their owner.
95
112
 
96
- `node:sqlite` is built in, so this adds no dependency. It does emit an
97
- ExperimentalWarning.
113
+ `node:sqlite` is built in, so this adds no dependency. It does not emit an
114
+ ExperimentalWarning when loaded on the pinned Node 26.9.0 runtime.
98
115
 
99
116
  ## Boundary
100
117
 
101
- This survives process death, not host loss, and does not coordinate across
102
- machines. The historical core implementation plan's Gate 5 durable-adapter
103
- clause is satisfied for a single node only. The on-chain publisher roadmap
118
+ Current recovery evidence covers local objects/processes and copied fixtures,
119
+ not host-loss, storage hardware failure, restore or cross-machine qualification.
120
+ Historical core Gate 5 evidence remains scoped to its original artifacts. See
121
+ the remediation plan for current checks and measured performance/retention costs.
122
+ The on-chain publisher roadmap
104
123
  qualifies a single VM in Phases 4-7; a coordinated multi-VM deployment needs a
105
124
  transactional shared store, leases, and fencing and is roadmap Phase 8.
@@ -0,0 +1,44 @@
1
+ import type { BestEffortWorkerLease, PersistentBestEffortInbox, VerifiedBestEffortSnapshot } from './bestEffortTypes.ts';
2
+ /** The port must enforce the byte bound while reading and independently verify source identity/finality/content. */
3
+ export interface BestEffortSnapshotSource<TSnapshot, TValue> {
4
+ readCurrent(input: {
5
+ readonly maxBytes: number;
6
+ readonly signal: AbortSignal;
7
+ readonly source: BestEffortWorkerLease['binding']['source'];
8
+ }): Promise<TSnapshot>;
9
+ verifySnapshot(input: {
10
+ readonly signal: AbortSignal;
11
+ readonly snapshot: TSnapshot;
12
+ readonly source: BestEffortWorkerLease['binding']['source'];
13
+ }): Promise<VerifiedBestEffortSnapshot<TValue>>;
14
+ }
15
+ /**
16
+ * Host-owned effect/checkpoint commit. The host must atomically reject older
17
+ * binding/fence commits and make repeats safe through idempotence or its effect
18
+ * journal/checkpoint transaction. A process can die after this commit and before
19
+ * inbox completion, so it can be repeated. `lease.isCurrent()` is advisory and
20
+ * cannot replace the app's own atomic fence/effect commit.
21
+ * dapp-kit consumers implement this through their effect journal/checkpoint,
22
+ * rather than an independent app engine in Event Kit.
23
+ */
24
+ export interface BestEffortSnapshotApplication<TValue> {
25
+ commitSnapshot(input: {
26
+ readonly lease: BestEffortWorkerLease;
27
+ readonly snapshot: VerifiedBestEffortSnapshot<TValue>;
28
+ }): Promise<void>;
29
+ }
30
+ export interface BestEffortSnapshotWorkerOptions<TSnapshot, TValue> {
31
+ readonly application: BestEffortSnapshotApplication<TValue>;
32
+ readonly inbox: PersistentBestEffortInbox;
33
+ readonly leaseSeconds?: number;
34
+ readonly maxSnapshotBytes: number;
35
+ readonly nowSeconds?: () => number;
36
+ readonly source: BestEffortSnapshotSource<TSnapshot, TValue>;
37
+ }
38
+ export interface BestEffortSnapshotWorker {
39
+ /** Run at startup and periodically; durable scanning, not callbacks, chooses work. */
40
+ runOnce(signal?: AbortSignal): Promise<'idle' | 'completed' | 'superseded'>;
41
+ }
42
+ /** A bounded observer worker; no full-history replay or public-ingress app execution. */
43
+ export declare function createBestEffortSnapshotWorker<TSnapshot, TValue>(options: BestEffortSnapshotWorkerOptions<TSnapshot, TValue>): BestEffortSnapshotWorker;
44
+ //# sourceMappingURL=bestEffortSnapshotWorker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bestEffortSnapshotWorker.d.ts","sourceRoot":"","sources":["../../src/bestEffortSnapshotWorker.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,qBAAqB,EAAE,yBAAyB,EAAE,0BAA0B,EAC7E,MAAM,sBAAsB,CAAA;AAE7B,oHAAoH;AACpH,MAAM,WAAW,wBAAwB,CAAC,SAAS,EAAE,MAAM;IACzD,WAAW,CAAC,KAAK,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;QAC5B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAA;KAC5D,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IACtB,cAAc,CAAC,KAAK,EAAE;QACpB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;QAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;QAC5B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAA;KAC5D,GAAG,OAAO,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAA;CAChD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,6BAA6B,CAAC,MAAM;IACnD,cAAc,CAAC,KAAK,EAAE;QACpB,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAA;QACrC,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC,MAAM,CAAC,CAAA;KACtD,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB;AAED,MAAM,WAAW,+BAA+B,CAAC,SAAS,EAAE,MAAM;IAChE,QAAQ,CAAC,WAAW,EAAE,6BAA6B,CAAC,MAAM,CAAC,CAAA;IAC3D,QAAQ,CAAC,KAAK,EAAE,yBAAyB,CAAA;IACzC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;CAC7D;AAED,MAAM,WAAW,wBAAwB;IACvC,sFAAsF;IACtF,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC,CAAA;CAC5E;AAED,yFAAyF;AACzF,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAC9D,OAAO,EAAE,+BAA+B,CAAC,SAAS,EAAE,MAAM,CAAC,GAC1D,wBAAwB,CAqE1B"}
@@ -0,0 +1,55 @@
1
+ import type { AtomicBestEffortInbox } from '@xyo-network/event-kit-protocol';
2
+ import type { Hash, WakeSource } from '@xyo-network/event-kit-schemas';
3
+ /** The exact authenticated consumer/source/profile scope; filterHash commits mode and policy. */
4
+ export interface BestEffortConsumerBinding {
5
+ readonly audience: string;
6
+ readonly authorizationChainId: string;
7
+ readonly consumerId: string;
8
+ readonly controller: string;
9
+ readonly deploymentId: string;
10
+ readonly filterHash: string;
11
+ readonly publisher: string;
12
+ readonly queueId?: string;
13
+ readonly source: WakeSource;
14
+ readonly subscriptionHash: string;
15
+ }
16
+ /** Durable scheduling authority. A callback is only an acceleration signal. */
17
+ export interface BestEffortDirtyTarget {
18
+ readonly binding: BestEffortConsumerBinding;
19
+ readonly completedGeneration: number;
20
+ readonly generation: number;
21
+ readonly id: string;
22
+ readonly requestedPosition: number;
23
+ }
24
+ /** A receiver-verified bounded snapshot, never evidence supplied by the hint publisher. */
25
+ export interface VerifiedBestEffortSnapshot<TValue = unknown> {
26
+ readonly hash?: Hash;
27
+ readonly position: number;
28
+ readonly source: WakeSource;
29
+ readonly value: TValue;
30
+ }
31
+ export interface BestEffortWorkerLease extends BestEffortDirtyTarget {
32
+ /** Monotonic per-target fence. Host app commits must fence their own effects/checkpoint. */
33
+ readonly fence: number;
34
+ readonly leaseToken: string;
35
+ readonly previousSnapshot?: Pick<VerifiedBestEffortSnapshot, 'hash' | 'position'>;
36
+ complete(snapshot: Pick<VerifiedBestEffortSnapshot, 'hash' | 'position'>): Promise<boolean>;
37
+ isCurrent(): Promise<boolean>;
38
+ release(): Promise<void>;
39
+ }
40
+ export interface PersistentBestEffortInbox extends AtomicBestEffortInbox {
41
+ claim(input: {
42
+ readonly leaseSeconds: number;
43
+ readonly nowSeconds: number;
44
+ }): Promise<BestEffortWorkerLease | undefined>;
45
+ close(): void;
46
+ dirtyTargets(): Promise<readonly BestEffortDirtyTarget[]>;
47
+ /** Expired work remains dirty; a reclaimed worker gets a new token and fence. */
48
+ recoverExpired(nowSeconds: number): Promise<number>;
49
+ /** Startup/periodic scanning repairs lost callback delivery. Failure cannot delete dirty work. */
50
+ repairDirty(): Promise<{
51
+ readonly failed: number;
52
+ readonly scheduled: number;
53
+ }>;
54
+ }
55
+ //# sourceMappingURL=bestEffortTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bestEffortTypes.d.ts","sourceRoot":"","sources":["../../src/bestEffortTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAC5E,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAA;AAEtE,iGAAiG;AACjG,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAA;IAC3C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;CACnC;AAED,2FAA2F;AAC3F,MAAM,WAAW,0BAA0B,CAAC,MAAM,GAAG,OAAO;IAC1D,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,qBAAsB,SAAQ,qBAAqB;IAClE,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,IAAI,CAAC,0BAA0B,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,0BAA0B,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC3F,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,yBAA0B,SAAQ,qBAAqB;IACtE,KAAK,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAA;IACxH,KAAK,IAAI,IAAI,CAAA;IACb,YAAY,IAAI,OAAO,CAAC,SAAS,qBAAqB,EAAE,CAAC,CAAA;IACzD,iFAAiF;IACjF,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACnD,kGAAkG;IAClG,WAAW,IAAI,OAAO,CAAC;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChF"}
@@ -1 +1 @@
1
- {"version":3,"file":"filesystemCursorStore.d.ts","sourceRoot":"","sources":["../../src/filesystemCursorStore.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AAExE,MAAM,WAAW,4BAA4B;IAC3C,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAMD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,oBAAoB,CAmBtB"}
1
+ {"version":3,"file":"filesystemCursorStore.d.ts","sourceRoot":"","sources":["../../src/filesystemCursorStore.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AAQxE,MAAM,WAAW,4BAA4B;IAC3C,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAID;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,4BAA4B,GACpC,oBAAoB,CAuBtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"filesystemJson.d.ts","sourceRoot":"","sources":["../../src/filesystemJson.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AAED,+EAA+E;AAC/E,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,OAAO,CAAC,CAelB;AAMD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
1
+ {"version":3,"file":"filesystemJson.d.ts","sourceRoot":"","sources":["../../src/filesystemJson.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWvE;AAED,+EAA+E;AAC/E,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,OAAO,CAAC,CAelB;AAMD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"filesystemPublisherOutbox.d.ts","sourceRoot":"","sources":["../../src/filesystemPublisherOutbox.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAIhF,MAAM,WAAW,gCAAgC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,eAAe,CAgCjB"}
1
+ {"version":3,"file":"filesystemPublisherOutbox.d.ts","sourceRoot":"","sources":["../../src/filesystemPublisherOutbox.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAWhF,MAAM,WAAW,gCAAgC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,eAAe,CA2DjB"}
@@ -1,30 +1,13 @@
1
1
  import type { AtomicWakeInbox } from '@xyo-network/event-kit-protocol';
2
2
  import type { FilesystemWakeQueue } from './filesystemWakeQueue.ts';
3
3
  export interface FilesystemWakeInboxOptions {
4
- /** Directory owning the `tokens/` and `wakes/` record sets. */
5
4
  readonly path: string;
6
- /** Durable sink receiving each newly admitted wake inside the commit. */
5
+ /** Must be the coordinated queue created by createFilesystemWakeQueue. */
7
6
  readonly queue: FilesystemWakeQueue;
8
7
  }
9
8
  export interface FilesystemWakeInbox extends AtomicWakeInbox {
10
- /** Deletes token records past `retainJwtUntil`. Returns how many were removed. */
11
- pruneExpiredTokens: (nowSeconds: number) => Promise<number>;
9
+ pruneExpiredTokens(nowSeconds: number): Promise<number>;
12
10
  }
13
- /**
14
- * Crash-safe single-node `AtomicWakeInbox`.
15
- *
16
- * Commit order is the correctness property: the wake reaches the durable queue
17
- * **before** either identity record is written. A crash in the gap redelivers
18
- * the wake, which the idempotent consumer absorbs; the reverse order could
19
- * claim `wakeId` for a wake that was never queued and wedge it permanently.
20
- * D-002 §6 forbids burning a valid `jti` ahead of a fallible enqueue.
21
- *
22
- * Identity records are published by `link`, which fails rather than overwrites,
23
- * so a concurrent writer loses the race and re-reads the winner's receipt.
24
- *
25
- * This survives process death, not host loss, and does not coordinate across
26
- * machines. A multi-node deployment needs a transactional store or a cloud
27
- * queue with native deduplication.
28
- */
11
+ /** Both identity indexes, stable receipt and exact work share the queue's transaction. */
29
12
  export declare function createFilesystemWakeInbox(options: FilesystemWakeInboxOptions): FilesystemWakeInbox;
30
13
  //# sourceMappingURL=filesystemWakeInbox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filesystemWakeInbox.d.ts","sourceRoot":"","sources":["../../src/filesystemWakeInbox.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,eAAe,EAKhB,MAAM,iCAAiC,CAAA;AAExC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAGnE,MAAM,WAAW,0BAA0B;IACzC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAA;CACpC;AAED,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,kFAAkF;IAClF,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5D;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,mBAAmB,CA4HrB"}
1
+ {"version":3,"file":"filesystemWakeInbox.d.ts","sourceRoot":"","sources":["../../src/filesystemWakeInbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAInE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAA;CACpC;AACD,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACxD;AACD,0FAA0F;AAC1F,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,mBAAmB,CAiBlG"}
@@ -1,28 +1,19 @@
1
1
  import type { WakeQueue } from '@xyo-network/event-kit-protocol';
2
2
  import { ensureDurableDirectory, syncDirectory, writeDurableFile } from './filesystemDurability.ts';
3
+ import type { ReceiverAuthority } from './receiverAuthority.ts';
3
4
  export { ensureDurableDirectory, syncDirectory, writeDurableFile, };
4
5
  export interface FilesystemWakeQueueOptions {
5
- /** Directory owning the `pending/` and `inflight/` entry sets. */
6
+ /** Private directory; receiver.sqlite owns state, JSON files are projections. */
6
7
  readonly path: string;
7
8
  }
8
- export type FilesystemWakeQueue = WakeQueue;
9
- /**
10
- * Directory-backed at-least-once wake queue.
11
- *
12
- * Entries are published by `link`, never `rename`. `rename` is unsound as a
13
- * claim: when two callers race the same source, both can observe success once
14
- * the paths resolve to one inode, and the entry is then handed out twice. `link`
15
- * fails with `EEXIST`, so exactly one claimer wins.
16
- *
17
- * A claim is `link(pending -> inflight)` followed by `unlink(pending)`. A crash
18
- * between the two leaves the entry in both sets as one inode; recovery drops the
19
- * inflight copy and the entry stays pending. A consumer that dies mid-handler
20
- * leaves its entry inflight for `recoverInflight` rather than dropping it.
21
- *
22
- * Ordering is approximate FIFO by entry name. Single-node only: this survives
23
- * process death, not host loss, and does not coordinate across machines.
24
- */
9
+ export interface FilesystemWakeQueue extends WakeQueue {
10
+ close(): void;
11
+ repairProjections(): Promise<void>;
12
+ repairRow(id: number): Promise<void>;
13
+ }
14
+ export declare function filesystemReceiverAuthority(queue: FilesystemWakeQueue): ReceiverAuthority;
15
+ /** Filesystem-facing receiver with one SQLite admission/lease/completion authority. */
25
16
  export declare function createFilesystemWakeQueue(options: FilesystemWakeQueueOptions): FilesystemWakeQueue;
26
- /** Reads and parses a JSON file, treating an absent or unreadable file as empty. */
17
+ /** Only open-time absence means an empty optional record. */
27
18
  export declare function readJsonFile<T>(path: string): Promise<T | undefined>;
28
19
  //# sourceMappingURL=filesystemWakeQueue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filesystemWakeQueue.d.ts","sourceRoot":"","sources":["../../src/filesystemWakeQueue.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,SAAS,EACV,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,gBAAgB,EACjB,MAAM,2BAA2B,CAAA;AAElC,OAAO,EACL,sBAAsB,EACtB,aAAa,EACb,gBAAgB,GACjB,CAAA;AAED,MAAM,WAAW,0BAA0B;IACzC,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAA;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,mBAAmB,CAwFrB;AAED,oFAAoF;AACpF,wBAAsB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAY1E"}
1
+ {"version":3,"file":"filesystemWakeQueue.d.ts","sourceRoot":"","sources":["../../src/filesystemWakeQueue.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAA;AAGhE,OAAO,EACL,sBAAsB,EAAE,aAAa,EAAE,gBAAgB,EACxD,MAAM,2BAA2B,CAAA;AAIlC,OAAO,KAAK,EAAE,iBAAiB,EAAoB,MAAM,wBAAwB,CAAA;AAGjF,OAAO,EACL,sBAAsB,EAAE,aAAa,EAAE,gBAAgB,GACxD,CAAA;AAED,MAAM,WAAW,0BAA0B;IACzC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AACD,MAAM,WAAW,mBAAoB,SAAQ,SAAS;IACpD,KAAK,IAAI,IAAI,CAAA;IACb,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,mBAAmB,GAAG,iBAAiB,CAIzF;AAED,uFAAuF;AACvF,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,mBAAmB,CA4HlG;AAED,6DAA6D;AAC7D,wBAAsB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAY1E"}
@@ -58,25 +58,26 @@ function errorCode(error) {
58
58
 
59
59
  // src/filesystemJson.ts
60
60
  async function readJsonFileStrict(path) {
61
+ let handle;
61
62
  try {
62
- const handle = await open2(path, "r");
63
- try {
64
- return JSON.parse(await handle.readFile("utf8"), reviveJson);
65
- } finally {
66
- await handle.close();
67
- }
63
+ handle = await open2(path, "r");
68
64
  } catch (error) {
69
65
  if (errorCode2(error) === "ENOENT") return void 0;
70
66
  throw error;
71
67
  }
68
+ try {
69
+ return JSON.parse(await handle.readFile("utf8"), reviveJson);
70
+ } finally {
71
+ await handle.close();
72
+ }
72
73
  }
73
74
  async function replaceJsonFile(path, value, options = {}) {
74
75
  const directory = PATH2.dirname(path);
75
76
  await ensureDurableDirectory(directory, options.directoryMode);
76
77
  const tempPath = PATH2.join(directory, `.${PATH2.basename(path)}.${process.pid}.${suffix()}.tmp`);
77
- await writeDurableFile(tempPath, `${stringifyJson(value)}
78
- `, options.mode);
79
78
  try {
79
+ await writeDurableFile(tempPath, `${stringifyJson(value)}
80
+ `, options.mode);
80
81
  await rename(tempPath, path);
81
82
  await syncDirectory(directory);
82
83
  } catch (error) {
@@ -88,9 +89,9 @@ async function publishJsonFile(path, value, options = {}) {
88
89
  const directory = PATH2.dirname(path);
89
90
  await ensureDurableDirectory(directory, options.directoryMode);
90
91
  const tempPath = PATH2.join(directory, `.${PATH2.basename(path)}.${process.pid}.${suffix()}.tmp`);
91
- await writeDurableFile(tempPath, `${stringifyJson(value)}
92
- `, options.mode);
93
92
  try {
93
+ await writeDurableFile(tempPath, `${stringifyJson(value)}
94
+ `, options.mode);
94
95
  await link(tempPath, path);
95
96
  await syncDirectory(directory);
96
97
  return true;
@@ -1197,7 +1198,7 @@ function createPublisherHandshakeClient(options) {
1197
1198
  const reconciliation = await reconcileAcceptance(acceptanceHash, publication, intent.challenge.subscriber, intent.challenge.subscription);
1198
1199
  return Object.freeze({
1199
1200
  acceptance: Object.freeze({ body: intent.acceptance, objectHash: acceptanceHash }),
1200
- challenge: intent.operationId,
1201
+ challenge: intent.acceptance.challenge,
1201
1202
  intentDisposition,
1202
1203
  operationId: intent.operationId,
1203
1204
  publication,