@loomwork/event-runtime 0.0.0-stage → 0.1.0

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 (153) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +95 -2
  4. package/dist/BranchRegistry.d.ts +140 -0
  5. package/dist/BranchRegistry.d.ts.map +1 -0
  6. package/dist/BranchRegistry.js +66 -0
  7. package/dist/BranchRegistry.js.map +1 -0
  8. package/dist/CoalescingEventStore.d.ts +97 -0
  9. package/dist/CoalescingEventStore.d.ts.map +1 -0
  10. package/dist/CoalescingEventStore.js +180 -0
  11. package/dist/CoalescingEventStore.js.map +1 -0
  12. package/dist/InMemoryArchiveStore.d.ts +19 -0
  13. package/dist/InMemoryArchiveStore.d.ts.map +1 -0
  14. package/dist/InMemoryArchiveStore.js +49 -0
  15. package/dist/InMemoryArchiveStore.js.map +1 -0
  16. package/dist/InMemoryAsyncEventStore.d.ts +111 -0
  17. package/dist/InMemoryAsyncEventStore.d.ts.map +1 -0
  18. package/dist/InMemoryAsyncEventStore.js +374 -0
  19. package/dist/InMemoryAsyncEventStore.js.map +1 -0
  20. package/dist/InMemoryBranchRegistry.d.ts +31 -0
  21. package/dist/InMemoryBranchRegistry.d.ts.map +1 -0
  22. package/dist/InMemoryBranchRegistry.js +167 -0
  23. package/dist/InMemoryBranchRegistry.js.map +1 -0
  24. package/dist/ProfilingEventStore.d.ts +75 -0
  25. package/dist/ProfilingEventStore.d.ts.map +1 -0
  26. package/dist/ProfilingEventStore.js +133 -0
  27. package/dist/ProfilingEventStore.js.map +1 -0
  28. package/dist/ShardPlan.d.ts +87 -0
  29. package/dist/ShardPlan.d.ts.map +1 -0
  30. package/dist/ShardPlan.js +193 -0
  31. package/dist/ShardPlan.js.map +1 -0
  32. package/dist/ShardedEventStore.d.ts +149 -0
  33. package/dist/ShardedEventStore.d.ts.map +1 -0
  34. package/dist/ShardedEventStore.js +468 -0
  35. package/dist/ShardedEventStore.js.map +1 -0
  36. package/dist/SubscriberRegistry.d.ts +59 -0
  37. package/dist/SubscriberRegistry.d.ts.map +1 -0
  38. package/dist/SubscriberRegistry.js +113 -0
  39. package/dist/SubscriberRegistry.js.map +1 -0
  40. package/dist/TenantScopedArchiveStore.d.ts +36 -0
  41. package/dist/TenantScopedArchiveStore.d.ts.map +1 -0
  42. package/dist/TenantScopedArchiveStore.js +96 -0
  43. package/dist/TenantScopedArchiveStore.js.map +1 -0
  44. package/dist/TenantScopedEventStore.d.ts +59 -0
  45. package/dist/TenantScopedEventStore.d.ts.map +1 -0
  46. package/dist/TenantScopedEventStore.js +227 -0
  47. package/dist/TenantScopedEventStore.js.map +1 -0
  48. package/dist/archive.d.ts +68 -0
  49. package/dist/archive.d.ts.map +1 -0
  50. package/dist/archive.js +207 -0
  51. package/dist/archive.js.map +1 -0
  52. package/dist/archiveSpi.d.ts +165 -0
  53. package/dist/archiveSpi.d.ts.map +1 -0
  54. package/dist/archiveSpi.js +184 -0
  55. package/dist/archiveSpi.js.map +1 -0
  56. package/dist/creationMarker.d.ts +13 -0
  57. package/dist/creationMarker.d.ts.map +1 -0
  58. package/dist/creationMarker.js +17 -0
  59. package/dist/creationMarker.js.map +1 -0
  60. package/dist/decoratorPlan.typecheck.d.ts +2 -0
  61. package/dist/decoratorPlan.typecheck.d.ts.map +1 -0
  62. package/dist/decoratorPlan.typecheck.js +41 -0
  63. package/dist/decoratorPlan.typecheck.js.map +1 -0
  64. package/dist/eventKindClass.d.ts +40 -0
  65. package/dist/eventKindClass.d.ts.map +1 -0
  66. package/dist/eventKindClass.js +99 -0
  67. package/dist/eventKindClass.js.map +1 -0
  68. package/dist/executionIdOrder.d.ts +21 -0
  69. package/dist/executionIdOrder.d.ts.map +1 -0
  70. package/dist/executionIdOrder.js +43 -0
  71. package/dist/executionIdOrder.js.map +1 -0
  72. package/dist/fencing.d.ts +21 -0
  73. package/dist/fencing.d.ts.map +1 -0
  74. package/dist/fencing.js +36 -0
  75. package/dist/fencing.js.map +1 -0
  76. package/dist/index.d.ts +39 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +126 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/installOptional.d.ts +52 -0
  81. package/dist/installOptional.d.ts.map +1 -0
  82. package/dist/installOptional.js +112 -0
  83. package/dist/installOptional.js.map +1 -0
  84. package/dist/keyUnavailable.d.ts +67 -0
  85. package/dist/keyUnavailable.d.ts.map +1 -0
  86. package/dist/keyUnavailable.js +87 -0
  87. package/dist/keyUnavailable.js.map +1 -0
  88. package/dist/leases.d.ts +177 -0
  89. package/dist/leases.d.ts.map +1 -0
  90. package/dist/leases.js +142 -0
  91. package/dist/leases.js.map +1 -0
  92. package/dist/payloadShredded.d.ts +30 -0
  93. package/dist/payloadShredded.d.ts.map +1 -0
  94. package/dist/payloadShredded.js +67 -0
  95. package/dist/payloadShredded.js.map +1 -0
  96. package/dist/scaleDecorators.bench.d.ts +2 -0
  97. package/dist/scaleDecorators.bench.d.ts.map +1 -0
  98. package/dist/scaleDecorators.bench.js +93 -0
  99. package/dist/scaleDecorators.bench.js.map +1 -0
  100. package/dist/sequenceRange.bench.d.ts +2 -0
  101. package/dist/sequenceRange.bench.d.ts.map +1 -0
  102. package/dist/sequenceRange.bench.js +46 -0
  103. package/dist/sequenceRange.bench.js.map +1 -0
  104. package/dist/sequenceRange.d.ts +10 -0
  105. package/dist/sequenceRange.d.ts.map +1 -0
  106. package/dist/sequenceRange.js +35 -0
  107. package/dist/sequenceRange.js.map +1 -0
  108. package/dist/timers.d.ts +87 -0
  109. package/dist/timers.d.ts.map +1 -0
  110. package/dist/timers.js +68 -0
  111. package/dist/timers.js.map +1 -0
  112. package/dist/types.d.ts +634 -0
  113. package/dist/types.d.ts.map +1 -0
  114. package/dist/types.js +429 -0
  115. package/dist/types.js.map +1 -0
  116. package/dist/writeConflicts.d.ts +46 -0
  117. package/dist/writeConflicts.d.ts.map +1 -0
  118. package/dist/writeConflicts.js +63 -0
  119. package/dist/writeConflicts.js.map +1 -0
  120. package/memory.d.ts +4 -0
  121. package/memory.js +13 -0
  122. package/package.json +67 -3
  123. package/sequence.d.ts +1 -0
  124. package/sequence.js +5 -0
  125. package/src/BranchRegistry.ts +161 -0
  126. package/src/CoalescingEventStore.ts +214 -0
  127. package/src/InMemoryArchiveStore.ts +69 -0
  128. package/src/InMemoryAsyncEventStore.ts +425 -0
  129. package/src/InMemoryBranchRegistry.ts +178 -0
  130. package/src/ProfilingEventStore.ts +153 -0
  131. package/src/ShardPlan.ts +235 -0
  132. package/src/ShardedEventStore.ts +557 -0
  133. package/src/SubscriberRegistry.ts +115 -0
  134. package/src/TenantScopedArchiveStore.ts +114 -0
  135. package/src/TenantScopedEventStore.ts +266 -0
  136. package/src/archive.ts +248 -0
  137. package/src/archiveSpi.ts +308 -0
  138. package/src/creationMarker.ts +15 -0
  139. package/src/decoratorPlan.typecheck.ts +52 -0
  140. package/src/eventKindClass.ts +127 -0
  141. package/src/executionIdOrder.ts +38 -0
  142. package/src/fencing.ts +31 -0
  143. package/src/index.ts +151 -0
  144. package/src/installOptional.ts +136 -0
  145. package/src/keyUnavailable.ts +98 -0
  146. package/src/leases.ts +319 -0
  147. package/src/payloadShredded.ts +63 -0
  148. package/src/scaleDecorators.bench.ts +107 -0
  149. package/src/sequenceRange.bench.ts +51 -0
  150. package/src/sequenceRange.ts +35 -0
  151. package/src/timers.ts +150 -0
  152. package/src/types.ts +857 -0
  153. package/src/writeConflicts.ts +56 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * How a store decorator handles one OPTIONAL SPI method of the store it wraps (ADR 0074
3
+ * Amendment, L1):
4
+ *
5
+ * - `"forward"` — call the inner store's method verbatim (bound to the inner store);
6
+ * - a function — the decorator's own implementation (it transforms ids, payloads, …);
7
+ * - `{ refuse: reason }` — deliberately NOT offered, with the reason recorded. The method is
8
+ * left uninstalled, so callers see exactly what they see for a store without it.
9
+ *
10
+ * What the compiler enforces: every key in `M` is REQUIRED (`-?` strips the `?` a mapped type
11
+ * over `S` would otherwise inherit from the optional SPI methods). So a decorator whose plan is
12
+ * `satisfies DecoratorPlan<EventStore, OptionalEventStoreMethod>` fails to compile if it omits
13
+ * any optional method — including one added to the SPI later, since `OptionalEventStoreMethod`
14
+ * is derived from the interface. Pinned by `decoratorPlan.typecheck.ts`, which `pnpm typecheck`
15
+ * checks. What it cannot enforce: that a decorator uses a plan at all — that is the decorator
16
+ * conformance kit's job, and the `packages/spec` fitness test makes every decorator run it.
17
+ */
18
+ export type DecoratorPlan<S, M extends keyof S> = {
19
+ readonly [K in M]-?: "forward" | NonNullable<S[K]> | {
20
+ readonly refuse: string;
21
+ };
22
+ };
23
+ /**
24
+ * Install each optional method of `plan` on `target`, **iff the inner store has it** — so a
25
+ * capability probe (`if (store.copyPrefix)`) answers exactly as it would against `inner`,
26
+ * and a decorator never advertises a capability its backend lacks. O(|M|) once, at
27
+ * construction; zero cost per call (a forward is a bound method, not a trampoline).
28
+ *
29
+ * A function entry is bound to `bindTo` when given (a decorator whose implementation uses
30
+ * `this`), else installed as-is. `{ refuse }` entries are recorded for {@link refusalsOf}.
31
+ *
32
+ * Also carries the inner store's structural encryption marker (`keys`) onto `target` when
33
+ * `target` does not define its own — see {@link ENCRYPTION_MARKER}.
34
+ */
35
+ export declare function installOptional<S extends object, M extends keyof S>(target: S, inner: S, plan: DecoratorPlan<S, M>, bindTo?: object): void;
36
+ /** The store `target` was built over (recorded by {@link forwardStoreMarkers}), or `undefined`. */
37
+ export declare function decoratedInnerOf(target: object): object | undefined;
38
+ /**
39
+ * Carry the STRUCTURAL markers other layers read by shape from `inner` up to the decorator `target`
40
+ * (unless `target` defines its own): the encryption marker `keys`, `sealsWholeState` (ADR 0130 D8) and
41
+ * the tenant marker `tenantScope` (ADR 0138 H3), the snapshot codec marker `snapshotCodec` (ADR 0141), and record `target` as wrapping `inner`. EVERY store
42
+ * decorator (event, snapshot, archive) must call this - {@link installOptional} does - or a guard that
43
+ * reads a marker on the outermost store sees a plain, unscoped one. The decorator conformance kit
44
+ * asserts it for each decorator in the repo.
45
+ */
46
+ export declare function forwardStoreMarkers(target: object, inner: object): void;
47
+ /**
48
+ * The optional methods a decorator deliberately refused through {@link installOptional},
49
+ * with their reasons. Empty for a store that refused nothing (or did not use the helper).
50
+ */
51
+ export declare function refusalsOf(store: object): ReadonlyMap<string, string>;
52
+ //# sourceMappingURL=installOptional.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installOptional.d.ts","sourceRoot":"","sources":["../src/installOptional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI;IAChD,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CACjF,CAAC;AAiBF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,EACjE,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,EACR,IAAI,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EACzB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAsBN;AAgBD,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAuBvE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAErE"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installOptional = installOptional;
4
+ exports.decoratedInnerOf = decoratedInnerOf;
5
+ exports.forwardStoreMarkers = forwardStoreMarkers;
6
+ exports.refusalsOf = refusalsOf;
7
+ // Refusals, keyed by the decorated object. A WeakMap so a discarded store takes its entry
8
+ // with it; read back by the decorator conformance kit through `refusalsOf`.
9
+ const refusals = new WeakMap();
10
+ /**
11
+ * The encryption-boundary marker a store exposes structurally (`EncryptingEventStore.keys`,
12
+ * `EncryptingSnapshotStore.keys`). `loom doctor` and `LogLifecycle`'s compaction/archive
13
+ * guard read it by shape, so a decorator stacked ABOVE an encrypting store must carry it up —
14
+ * otherwise `Coalescing(Encrypting(pg))` looks like a plain store and compaction would write
15
+ * the decrypted fold to a plaintext snapshot store (ADR 0022 Amendment).
16
+ */
17
+ const ENCRYPTION_MARKER = "keys";
18
+ /** `EncryptingSnapshotStore.sealsWholeState` (ADR 0130 D8), carried up the same way. */
19
+ const WHOLE_STATE_MARKER = "sealsWholeState";
20
+ /**
21
+ * Install each optional method of `plan` on `target`, **iff the inner store has it** — so a
22
+ * capability probe (`if (store.copyPrefix)`) answers exactly as it would against `inner`,
23
+ * and a decorator never advertises a capability its backend lacks. O(|M|) once, at
24
+ * construction; zero cost per call (a forward is a bound method, not a trampoline).
25
+ *
26
+ * A function entry is bound to `bindTo` when given (a decorator whose implementation uses
27
+ * `this`), else installed as-is. `{ refuse }` entries are recorded for {@link refusalsOf}.
28
+ *
29
+ * Also carries the inner store's structural encryption marker (`keys`) onto `target` when
30
+ * `target` does not define its own — see {@link ENCRYPTION_MARKER}.
31
+ */
32
+ function installOptional(target, inner, plan, bindTo) {
33
+ const writable = target;
34
+ const source = inner;
35
+ let refused;
36
+ for (const method of Object.keys(plan)) {
37
+ const entry = plan[method];
38
+ if (typeof entry === "object" && entry !== null) {
39
+ refused ??= new Map();
40
+ refused.set(String(method), entry.refuse);
41
+ continue;
42
+ }
43
+ const innerMethod = source[method];
44
+ if (typeof innerMethod !== "function")
45
+ continue; // the backend lacks it: so do we
46
+ if (entry === "forward") {
47
+ writable[method] = innerMethod.bind(inner);
48
+ }
49
+ else if (typeof entry === "function") {
50
+ writable[method] =
51
+ bindTo === undefined ? entry : entry.bind(bindTo);
52
+ }
53
+ }
54
+ if (refused !== undefined)
55
+ refusals.set(target, refused);
56
+ forwardStoreMarkers(target, inner);
57
+ }
58
+ /** `TenantScopedEventStore.tenantScope` (and the archive/snapshot twins), carried up the same way (ADR 0138 H3). */
59
+ const TENANT_MARKER = "tenantScope";
60
+ /**
61
+ * ADR 0141 - the snapshot codec a store WRITES (`"none" | "zstd"`), carried up through every snapshot
62
+ * decorator so the composition root (`defineConfig`) reads what the outermost store really does and can
63
+ * refuse a mismatch, instead of the opt-in being silently dropped by a wrapper.
64
+ */
65
+ const CODEC_MARKER = "snapshotCodec";
66
+ // Who a decorator wraps, keyed by the decorator: lets a guard walk a stack whose layers hide their
67
+ // inner store (a closure-built decorator exposes no `inner`). A WeakMap, so it never keeps a store alive.
68
+ const wrapped = new WeakMap();
69
+ /** The store `target` was built over (recorded by {@link forwardStoreMarkers}), or `undefined`. */
70
+ function decoratedInnerOf(target) {
71
+ return wrapped.get(target);
72
+ }
73
+ /**
74
+ * Carry the STRUCTURAL markers other layers read by shape from `inner` up to the decorator `target`
75
+ * (unless `target` defines its own): the encryption marker `keys`, `sealsWholeState` (ADR 0130 D8) and
76
+ * the tenant marker `tenantScope` (ADR 0138 H3), the snapshot codec marker `snapshotCodec` (ADR 0141), and record `target` as wrapping `inner`. EVERY store
77
+ * decorator (event, snapshot, archive) must call this - {@link installOptional} does - or a guard that
78
+ * reads a marker on the outermost store sees a plain, unscoped one. The decorator conformance kit
79
+ * asserts it for each decorator in the repo.
80
+ */
81
+ function forwardStoreMarkers(target, inner) {
82
+ const writable = target;
83
+ const source = inner;
84
+ wrapped.set(target, inner);
85
+ const marker = source[ENCRYPTION_MARKER];
86
+ if (typeof marker === "object" && marker !== null && writable[ENCRYPTION_MARKER] === undefined) {
87
+ writable[ENCRYPTION_MARKER] = marker;
88
+ }
89
+ // ADR 0130 D8 - a snapshot store that seals its WHOLE state says so structurally; a decorator
90
+ // above it must not hide that (the doctor / kernel would read a sealed store as plaintext).
91
+ if (source[WHOLE_STATE_MARKER] === true && writable[WHOLE_STATE_MARKER] === undefined) {
92
+ writable[WHOLE_STATE_MARKER] = true;
93
+ }
94
+ // ADR 0138 H3 - a decorator above a tenant-scoped store is still scoped to that tenant.
95
+ const tenant = source[TENANT_MARKER];
96
+ if (typeof tenant === "string" && writable[TENANT_MARKER] === undefined) {
97
+ writable[TENANT_MARKER] = tenant;
98
+ }
99
+ // ADR 0141 - a decorator above a codec-writing snapshot store still writes that codec.
100
+ const codec = source[CODEC_MARKER];
101
+ if ((codec === "none" || codec === "zstd") && writable[CODEC_MARKER] === undefined) {
102
+ writable[CODEC_MARKER] = codec;
103
+ }
104
+ }
105
+ /**
106
+ * The optional methods a decorator deliberately refused through {@link installOptional},
107
+ * with their reasons. Empty for a store that refused nothing (or did not use the helper).
108
+ */
109
+ function refusalsOf(store) {
110
+ return refusals.get(store) ?? new Map();
111
+ }
112
+ //# sourceMappingURL=installOptional.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installOptional.js","sourceRoot":"","sources":["../src/installOptional.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import type { EventEnvelope } from "@loomwork/core";
2
+ /** The stable `code` of {@link KeyUnavailableError}. */
3
+ export declare const KEY_UNAVAILABLE_CODE: "KEY_UNAVAILABLE";
4
+ /**
5
+ * ADR 0130 residuals D7 - a sealing store holds ciphertext for a subject whose key THIS node's key
6
+ * store does not have, and the log carries NO `PAYLOAD_SHREDDED` tombstone for it: the node is
7
+ * misconfigured (a different or empty key store), not erased. It is deliberately not
8
+ * `PayloadShreddedError`, which stays exclusively the erasure outcome. Lives beside the store error
9
+ * contract so the kernel can recognise it without depending on the crypto plugin.
10
+ *
11
+ * Carries the subject id and stream id, never ciphertext or key material. The kernel translates it
12
+ * to a denial-shaped `SealedStreamUnreadableError` (operator-only detail `key-unavailable`).
13
+ */
14
+ export declare class KeyUnavailableError extends Error {
15
+ readonly subjectId: string;
16
+ readonly streamId?: string | undefined;
17
+ /** The stream's workflow id (a plaintext column), so the kernel can word its refusal. */
18
+ readonly workflowId?: string | undefined;
19
+ /**
20
+ * The stream with each sealed field replaced by a placeholder (its status and tip are
21
+ * plaintext). Lets the erasure lifecycle (`EraseCoordinator`, `exportSubject`) judge a stream
22
+ * in the ShredIncomplete window (key destroyed, tombstone not yet written). Never ciphertext.
23
+ */
24
+ readonly redacted?: readonly EventEnvelope[] | undefined;
25
+ readonly code: "KEY_UNAVAILABLE";
26
+ constructor(subjectId: string, streamId?: string | undefined,
27
+ /** The stream's workflow id (a plaintext column), so the kernel can word its refusal. */
28
+ workflowId?: string | undefined,
29
+ /**
30
+ * The stream with each sealed field replaced by a placeholder (its status and tip are
31
+ * plaintext). Lets the erasure lifecycle (`EraseCoordinator`, `exportSubject`) judge a stream
32
+ * in the ShredIncomplete window (key destroyed, tombstone not yet written). Never ciphertext.
33
+ */
34
+ redacted?: readonly EventEnvelope[] | undefined);
35
+ }
36
+ /** True for a {@link KeyUnavailableError}, by its stable `code` (survives duplicate package copies). */
37
+ export declare function isKeyUnavailable(e: unknown): e is KeyUnavailableError;
38
+ /**
39
+ * ADR 0130 residuals D7 (round 4) - does this stream carry the DURABLE ERASE INTENT of `subjectId`?
40
+ * `shredSubject` appends an `ERASURE_PENDING` (an existing, ignorable, pure-audit kernel kind) naming
41
+ * the key's subject to each affected execution's log BEFORE it destroys the key. It is the only
42
+ * evidence that distinguishes the ShredIncomplete window (key destroyed, tombstone not yet written:
43
+ * intent present) from a node that simply never had the key (no intent). `subjectId` omitted accepts
44
+ * any intent.
45
+ */
46
+ export declare function recordsErasureIntent(events: readonly Pick<EventEnvelope, "eventType" | "payload">[], subjectId?: string): boolean;
47
+ /**
48
+ * ADR 0130 D7 (round 5) - proof that the KEY STORE destroyed `subjectId`'s key (`KeyStore.wasDestroyed`,
49
+ * or the operator's explicit assertion `() => true`). The log proves intent, never destruction.
50
+ */
51
+ export type DestructionProof = (subjectId: string) => boolean | Promise<boolean>;
52
+ /**
53
+ * The retry window, decided: a {@link KeyUnavailableError} carrying a redacted view is the genuine
54
+ * ShredIncomplete window ONLY when that view records this subject's erase intent AND `destroyed`
55
+ * proves the key store destroyed the key (a dangling intent whose key still lives elsewhere is not
56
+ * the window). No proof, or a false one, fails closed. Returns the redacted view in that case,
57
+ * `undefined` otherwise (the caller rethrows).
58
+ */
59
+ export declare function erasureWindowView(err: unknown, destroyed?: DestructionProof): Promise<readonly EventEnvelope[] | undefined>;
60
+ /**
61
+ * The ONE gate of the retry window: `err` is a {@link KeyUnavailableError} AND `events` (the
62
+ * stream's log, redacted or raw) records this subject's erase intent AND `destroyed` proves the key
63
+ * store destroyed the key. Every read in the window (event log, sealed checkpoint) uses it, so
64
+ * they cannot drift. No proof, or a false one, fails closed.
65
+ */
66
+ export declare function isErasureWindow(err: unknown, events: readonly Pick<EventEnvelope, "eventType" | "payload">[], destroyed?: DestructionProof): Promise<boolean>;
67
+ //# sourceMappingURL=keyUnavailable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyUnavailable.d.ts","sourceRoot":"","sources":["../src/keyUnavailable.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,EAAG,iBAA0B,CAAC;AAE/D;;;;;;;;;GASG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAG1C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE;IAX9C,QAAQ,CAAC,IAAI,oBAAwB;gBAE1B,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,YAAA;IAC1B,yFAAyF;IAChF,UAAU,CAAC,EAAE,MAAM,YAAA;IAC5B;;;;OAIG;IACM,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,YAAA;CAQ/C;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,mBAAmB,CAErE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,SAAS,CAAC,EAAE,EAC/D,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAOT;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEjF;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,SAAS,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC,CAG/C;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,SAAS,IAAI,CAAC,aAAa,EAAE,WAAW,GAAG,SAAS,CAAC,EAAE,EAC/D,SAAS,CAAC,EAAE,gBAAgB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAIlB"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeyUnavailableError = exports.KEY_UNAVAILABLE_CODE = void 0;
4
+ exports.isKeyUnavailable = isKeyUnavailable;
5
+ exports.recordsErasureIntent = recordsErasureIntent;
6
+ exports.erasureWindowView = erasureWindowView;
7
+ exports.isErasureWindow = isErasureWindow;
8
+ /** The stable `code` of {@link KeyUnavailableError}. */
9
+ exports.KEY_UNAVAILABLE_CODE = "KEY_UNAVAILABLE";
10
+ /**
11
+ * ADR 0130 residuals D7 - a sealing store holds ciphertext for a subject whose key THIS node's key
12
+ * store does not have, and the log carries NO `PAYLOAD_SHREDDED` tombstone for it: the node is
13
+ * misconfigured (a different or empty key store), not erased. It is deliberately not
14
+ * `PayloadShreddedError`, which stays exclusively the erasure outcome. Lives beside the store error
15
+ * contract so the kernel can recognise it without depending on the crypto plugin.
16
+ *
17
+ * Carries the subject id and stream id, never ciphertext or key material. The kernel translates it
18
+ * to a denial-shaped `SealedStreamUnreadableError` (operator-only detail `key-unavailable`).
19
+ */
20
+ class KeyUnavailableError extends Error {
21
+ subjectId;
22
+ streamId;
23
+ workflowId;
24
+ redacted;
25
+ code = exports.KEY_UNAVAILABLE_CODE;
26
+ constructor(subjectId, streamId,
27
+ /** The stream's workflow id (a plaintext column), so the kernel can word its refusal. */
28
+ workflowId,
29
+ /**
30
+ * The stream with each sealed field replaced by a placeholder (its status and tip are
31
+ * plaintext). Lets the erasure lifecycle (`EraseCoordinator`, `exportSubject`) judge a stream
32
+ * in the ShredIncomplete window (key destroyed, tombstone not yet written). Never ciphertext.
33
+ */
34
+ redacted) {
35
+ super(`the encryption key for subject "${subjectId}"${streamId ? ` (stream "${streamId}")` : ""} is not available to this node and the log records no erasure (no PAYLOAD_SHREDDED): this node's key store lacks the key. It is not a crypto-shred.`);
36
+ this.subjectId = subjectId;
37
+ this.streamId = streamId;
38
+ this.workflowId = workflowId;
39
+ this.redacted = redacted;
40
+ this.name = "KeyUnavailableError";
41
+ Object.setPrototypeOf(this, KeyUnavailableError.prototype);
42
+ }
43
+ }
44
+ exports.KeyUnavailableError = KeyUnavailableError;
45
+ /** True for a {@link KeyUnavailableError}, by its stable `code` (survives duplicate package copies). */
46
+ function isKeyUnavailable(e) {
47
+ return e?.code === exports.KEY_UNAVAILABLE_CODE;
48
+ }
49
+ /**
50
+ * ADR 0130 residuals D7 (round 4) - does this stream carry the DURABLE ERASE INTENT of `subjectId`?
51
+ * `shredSubject` appends an `ERASURE_PENDING` (an existing, ignorable, pure-audit kernel kind) naming
52
+ * the key's subject to each affected execution's log BEFORE it destroys the key. It is the only
53
+ * evidence that distinguishes the ShredIncomplete window (key destroyed, tombstone not yet written:
54
+ * intent present) from a node that simply never had the key (no intent). `subjectId` omitted accepts
55
+ * any intent.
56
+ */
57
+ function recordsErasureIntent(events, subjectId) {
58
+ return events.some((e) => e.eventType === "ERASURE_PENDING" &&
59
+ (subjectId === undefined ||
60
+ e.payload?.subjectId === subjectId));
61
+ }
62
+ /**
63
+ * The retry window, decided: a {@link KeyUnavailableError} carrying a redacted view is the genuine
64
+ * ShredIncomplete window ONLY when that view records this subject's erase intent AND `destroyed`
65
+ * proves the key store destroyed the key (a dangling intent whose key still lives elsewhere is not
66
+ * the window). No proof, or a false one, fails closed. Returns the redacted view in that case,
67
+ * `undefined` otherwise (the caller rethrows).
68
+ */
69
+ async function erasureWindowView(err, destroyed) {
70
+ if (!isKeyUnavailable(err) || !err.redacted)
71
+ return undefined;
72
+ return (await isErasureWindow(err, err.redacted, destroyed)) ? err.redacted : undefined;
73
+ }
74
+ /**
75
+ * The ONE gate of the retry window: `err` is a {@link KeyUnavailableError} AND `events` (the
76
+ * stream's log, redacted or raw) records this subject's erase intent AND `destroyed` proves the key
77
+ * store destroyed the key. Every read in the window (event log, sealed checkpoint) uses it, so
78
+ * they cannot drift. No proof, or a false one, fails closed.
79
+ */
80
+ async function isErasureWindow(err, events, destroyed) {
81
+ if (!isKeyUnavailable(err))
82
+ return false;
83
+ if (!recordsErasureIntent(events, err.subjectId))
84
+ return false;
85
+ return destroyed !== undefined && (await destroyed(err.subjectId)) === true;
86
+ }
87
+ //# sourceMappingURL=keyUnavailable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyUnavailable.js","sourceRoot":"","sources":["../src/keyUnavailable.ts"],"names":[],"mappings":";;;AAsCA,4CAEC;AAUD,oDAUC;AAeD,8CAMC;AAQD,0CAQC;AA/FD,wDAAwD;AAC3C,QAAA,oBAAoB,GAAG,iBAA0B,CAAC;AAE/D;;;;;;;;;GASG;AACH,MAAa,mBAAoB,SAAQ,KAAK;IAGjC;IACA;IAEA;IAMA;IAXF,IAAI,GAAG,4BAAoB,CAAC;IACrC,YACW,SAAiB,EACjB,QAAiB;IAC1B,yFAAyF;IAChF,UAAmB;IAC5B;;;;OAIG;IACM,QAAmC;QAE5C,KAAK,CACH,mCAAmC,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,aAAa,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,qJAAqJ,CAC/O,CAAC;QAbO,cAAS,GAAT,SAAS,CAAQ;QACjB,aAAQ,GAAR,QAAQ,CAAS;QAEjB,eAAU,GAAV,UAAU,CAAS;QAMnB,aAAQ,GAAR,QAAQ,CAA2B;QAK5C,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAC7D,CAAC;CACF;AApBD,kDAoBC;AAED,wGAAwG;AACxG,SAAgB,gBAAgB,CAAC,CAAU;IACzC,OAAQ,CAA+B,EAAE,IAAI,KAAK,4BAAoB,CAAC;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,oBAAoB,CAClC,MAA+D,EAC/D,SAAkB;IAElB,OAAO,MAAM,CAAC,IAAI,CAChB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,SAAS,KAAK,iBAAiB;QACjC,CAAC,SAAS,KAAK,SAAS;YACrB,CAAC,CAAC,OAA0C,EAAE,SAAS,KAAK,SAAS,CAAC,CAC5E,CAAC;AACJ,CAAC;AAQD;;;;;;GAMG;AACI,KAAK,UAAU,iBAAiB,CACrC,GAAY,EACZ,SAA4B;IAE5B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9D,OAAO,CAAC,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1F,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,eAAe,CACnC,GAAY,EACZ,MAA+D,EAC/D,SAA4B;IAE5B,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,GAAG,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/D,OAAO,SAAS,KAAK,SAAS,IAAI,CAAC,MAAM,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,IAAI,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * SPI version for the {@link LeaseStore} contract. Pinned by `runLeaseStoreConformance`.
3
+ *
4
+ * 2.0.0 (L28): `expired`'s `now` became optional and MUST default to the store's stamping clock.
5
+ * MAJOR, not additive: a 1.x store written `expired(now: number)` still type-checks against the
6
+ * new signature but, handed `undefined`, returns `[]` — recovery silently recovers nothing.
7
+ *
8
+ * 2.1.0 (L29): optional `storeNow()` — the store's own clock. Additive: a store without it behaves
9
+ * exactly as before, and a caller with no `storeNow()` falls back to `Date.now()`.
10
+ *
11
+ * 2.2.0 (ADR 0130 D11, heartbeat leases): optional `scope` (who can see this store's leases:
12
+ * "process" | "host" | "cluster"; absent = unknown, treated as NOT cluster-visible), optional `acquireMany` / `renewMany` (returns the HELD
13
+ * set) / `releaseMany`, and the `steal` option on `acquire`. Additive: a store without them is
14
+ * driven through the single-id methods by the caller (correct, just slower). A store that claims
15
+ * 2.2.0 implements all three batch methods and the steal gate; one that claims 2.1.0 is not asked.
16
+ */
17
+ export declare const LEASE_STORE_SPI_VERSION = "2.2.0";
18
+ export interface Lease {
19
+ executionId: string;
20
+ /** Who holds it. A node id, a pod name, a process uuid — the deployment's vocabulary. */
21
+ owner: string;
22
+ /**
23
+ * Absolute expiry (epoch ms), on the store's clock (the one `expired()` defaults to), not
24
+ * necessarily the caller's. Past this, the lease is collectable by anyone.
25
+ */
26
+ expiresAt: number;
27
+ }
28
+ /**
29
+ * The proof a caller must hold to `steal` a lease: the fence it won with a CONDITIONAL fence
30
+ * acquisition (`acquireFenceIf`, i.e. proof the execution is parked). The store cannot verify it
31
+ * against the event log (the lease store is deliberately a separate, dumb SPI); it can only refuse
32
+ * a steal that does not even carry one, so a flag cannot be passed by accident.
33
+ *
34
+ * The proof is SHAPE-ONLY. Callers MUST pass a fence actually won via `acquireFenceIf` /
35
+ * `acquireFence` for THIS execution. A bogus steal is a liveness attack, never log corruption:
36
+ * the live owner's next `renew` fails (`LeaseLostError`), but the fence is the write guard
37
+ * (ADR 0130 D11 section 4), so the stale owner's appends are still rejected. An explicit `now`
38
+ * bypasses the expiry gate on every backend (pre-existing): production callers must never pass
39
+ * `now`, and on Postgres should omit it so the DATABASE clock rules.
40
+ */
41
+ export interface StealProof {
42
+ /** The fence value the caller won: a positive safe integer (0 is the inert park token, ADR 0118). */
43
+ fence: number;
44
+ }
45
+ /** Options for {@link LeaseStore.acquire} / {@link LeaseStore.acquireMany} (SPI 2.2.0). */
46
+ export interface AcquireOptions {
47
+ /**
48
+ * Take the lease even though ANOTHER owner holds it unexpired (a slow release left a stale row
49
+ * behind a driver that has already proved the execution is parked). Valid ONLY with a
50
+ * {@link StealProof}; anything else (`true`, `{}`, a negative or fractional fence) THROWS
51
+ * `TypeError` and changes nothing. After a release (no row), a steal is just an acquire. The
52
+ * displaced owner's next `renew`/`renewMany` reports it lost (`false` / absent).
53
+ *
54
+ * Single-id `acquire` ONLY: a fence belongs to one execution, so `acquireMany` REJECTS `steal`
55
+ * with `TypeError` (nothing changes).
56
+ */
57
+ steal?: StealProof;
58
+ }
59
+ export interface LeaseStore {
60
+ /**
61
+ * OPTIONAL (SPI 2.2.0): who can observe this store's leases. `"process"`: one process (in-memory);
62
+ * `"host"`: processes on one machine (a local file); `"cluster"`: every node (a shared server).
63
+ * Absent means unknown and is treated as NOT cluster-visible. Heartbeat evidence ("a lease
64
+ * expired, so the driver is gone") holds only if every node reads the same table.
65
+ */
66
+ readonly scope?: "process" | "host" | "cluster";
67
+ /**
68
+ * Take ownership, or report that someone else has it.
69
+ *
70
+ * Returns the lease on success and `null` when another owner holds an unexpired one — that
71
+ * single `null` is the mutual exclusion "only one node should run this execution" needs.
72
+ * Re-acquiring a lease you already hold is a renewal, not a conflict: a node that restarts
73
+ * its own work must not be locked out by its own previous claim.
74
+ *
75
+ * An EXPIRED lease is acquirable by anyone, which is the whole point — the previous owner
76
+ * stopped saying it was alive.
77
+ */
78
+ acquire(executionId: string, owner: string, ttlMs: number, now?: number, options?: AcquireOptions): Promise<Lease | null>;
79
+ /**
80
+ * OPTIONAL (SPI 2.2.0): `acquire` for many executions in ONE round trip. Returns the leases
81
+ * granted; an id another owner holds unexpired is simply absent (the batch form of `null`).
82
+ * Duplicate ids count once. Per id the semantics are exactly {@link acquire}'s, and `now`
83
+ * applies to the whole batch. `options.steal` THROWS `TypeError` (steals go through `acquire`). Empty input returns `[]` without touching the store.
84
+ */
85
+ acquireMany?(executionIds: readonly string[], owner: string, ttlMs: number, now?: number, options?: AcquireOptions): Promise<Lease[]>;
86
+ /**
87
+ * OPTIONAL (SPI 2.2.0): `renew` for many executions in one round trip. Returns the HELD set:
88
+ * the ids whose lease this `owner` still holds after the call (their expiry extended). Every
89
+ * other id, released, taken over, or never held, is absent, and nothing about it changes: a
90
+ * renew NEVER resurrects. The returned set is what a keeper diffs against its own to find the
91
+ * lost ids, so a store must not echo its input.
92
+ */
93
+ renewMany?(executionIds: readonly string[], owner: string, ttlMs: number, now?: number): Promise<string[]>;
94
+ /** OPTIONAL (SPI 2.2.0): `release` for many executions in one round trip; other owners' are untouched. */
95
+ releaseMany?(executionIds: readonly string[], owner: string): Promise<void>;
96
+ /**
97
+ * Extend a lease you hold. `false` means you no longer hold it — someone else took over
98
+ * after it expired — and a caller that keeps working on `false` is the split-brain this
99
+ * exists to surface. Never resurrects: renewing an expired lease that another owner has
100
+ * since taken fails rather than stealing it back.
101
+ */
102
+ renew(executionId: string, owner: string, ttlMs: number, now?: number): Promise<boolean>;
103
+ /**
104
+ * Leases past their expiry, soonest-expired first. The evidence an execution was orphaned.
105
+ *
106
+ * `now` is OPTIONAL (SPI 2.0.0) and an implementation MUST default it to the store's OWN
107
+ * clock — the same one `acquire`/`renew` stamp `expiresAt` with. Expiry is only meaningful
108
+ * judged on the clock that stamped it: a caller passing wall-clock `Date.now()` to a store
109
+ * running on an injected/simulated clock sees every live lease as expired (a
110
+ * double-execution hazard). Callers with no reason to override the clock
111
+ * (`recoverAbandoned`) pass `undefined`. An implementation that still declares
112
+ * `expired(now: number)` compiles, but given `undefined` its comparison is false (`<=
113
+ * undefined`, SQL `<= NULL`) and it returns `[]`: recovery silently recovers nothing.
114
+ *
115
+ * The SQL stores' "own clock" is one clock for every node: SQLite is single-host, so the
116
+ * process clock is its one clock; Postgres stamps and judges on DATABASE time (L29), so node
117
+ * skew cannot make a live lease look expired or let a fast node steal one.
118
+ */
119
+ expired(now?: number, limit?: number): Promise<Lease[]>;
120
+ /**
121
+ * OPTIONAL read: the lease on an execution, or `null` if nothing holds one.
122
+ *
123
+ * `expired()` answers "who stopped saying they were alive", which cannot distinguish an
124
+ * execution someone is actively renewing from one that never had a lease at all. That
125
+ * distinction is the whole question for `orphanCensus` (#408): a stuck execution with NO
126
+ * lease is a path nothing is watching, and counting those is how "should the runtime lease
127
+ * every run" stops being a guess.
128
+ */
129
+ get?(executionId: string): Promise<Lease | null>;
130
+ /**
131
+ * OPTIONAL (SPI 2.1.0): the store's OWN clock, in epoch ms, the one `expiresAt` is stamped and
132
+ * `expired()` judged on. A reader that must compare `expiresAt` to "now" outside the store
133
+ * (`orphanCensus`) asks this rather than assuming its host's `Date.now()`, which for a shared
134
+ * database (Postgres stamps on DB time) is a different clock. Absent means the store uses the
135
+ * caller-visible process clock.
136
+ */
137
+ storeNow?(): Promise<number>;
138
+ /** Give it up. Releasing a lease held by someone else is a no-op, never a steal. */
139
+ release(executionId: string, owner: string): Promise<void>;
140
+ close(): Promise<void>;
141
+ }
142
+ export interface InMemoryLeaseStoreOptions {
143
+ /** Injectable clock, as everywhere else in the runtime. Default: `Date.now`. */
144
+ now?: () => number;
145
+ }
146
+ /**
147
+ * Map-backed {@link LeaseStore} — the browser-safe / test reference implementation beside the
148
+ * durable backends. Behaviour is pinned by `runLeaseStoreConformance`.
149
+ */
150
+ export declare class InMemoryLeaseStore implements LeaseStore {
151
+ readonly scope: "process";
152
+ private readonly leases;
153
+ private readonly now;
154
+ constructor(options?: InMemoryLeaseStoreOptions);
155
+ acquire(executionId: string, owner: string, ttlMs: number, now?: number, options?: AcquireOptions): Promise<Lease | null>;
156
+ acquireMany(executionIds: readonly string[], owner: string, ttlMs: number, now?: number, options?: AcquireOptions): Promise<Lease[]>;
157
+ renewMany(executionIds: readonly string[], owner: string, ttlMs: number, now?: number): Promise<string[]>;
158
+ releaseMany(executionIds: readonly string[], owner: string): Promise<void>;
159
+ renew(executionId: string, owner: string, ttlMs: number, now?: number): Promise<boolean>;
160
+ expired(now?: number, limit?: number): Promise<Lease[]>;
161
+ get(executionId: string): Promise<Lease | null>;
162
+ /** The store's own (injectable) clock: the one `expiresAt` is stamped and judged on (SPI 2.1.0). */
163
+ storeNow(): Promise<number>;
164
+ release(executionId: string, owner: string): Promise<void>;
165
+ close(): Promise<void>;
166
+ }
167
+ /** A zero or negative TTL would make a lease expired on arrival — refuse it at every backend. */
168
+ export declare function assertTtl(ttlMs: number): void;
169
+ /**
170
+ * The `steal` gate every backend applies BEFORE touching storage: `true` only for a real
171
+ * {@link StealProof}; `undefined` options or no `steal` is `false`; anything else THROWS
172
+ * `TypeError` (a flag is never a proof).
173
+ */
174
+ export declare function assertSteal(options: AcquireOptions | undefined): boolean;
175
+ /** `acquireMany` takes no `steal`: a fence belongs to one execution. THROWS `TypeError`, changing nothing. */
176
+ export declare function assertNoBatchSteal(options: AcquireOptions | undefined): void;
177
+ //# sourceMappingURL=leases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leases.d.ts","sourceRoot":"","sources":["../src/leases.ts"],"names":[],"mappings":"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"}