@powerhousedao/reactor 6.2.2-dev.5 → 6.2.2-dev.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Action, CreateDocumentActionInput, DocumentModelModule, ISigner, Operation, OperationContext, OperationWithContext, OperationWithContext as OperationWithContext$1, PHDocument, PHDocumentState, SignatureVerificationHandler, UpgradeDocumentActionInput, UpgradeManifest, UpgradeReducer, UpgradeTransition, actions as documentActions } from "@powerhousedao/shared/document-model";
1
+ import { Action, AuthRequest, AuthSubject, CreateDocumentActionInput, DocumentModelModule, ISigner, Operation, OperationContext, OperationWithContext, OperationWithContext as OperationWithContext$1, PHAuthState, PHDocument, PHDocumentState, SignatureVerificationHandler, UpgradeDocumentActionInput, UpgradeManifest, UpgradeReducer, UpgradeTransition, actions as documentActions } from "@powerhousedao/shared/document-model";
2
2
  import { DocumentDriveDocument, DriveInput, FolderNode, Node } from "@powerhousedao/shared/document-drive";
3
3
  import { ILogger } from "document-model";
4
4
  import * as kysely from "kysely";
@@ -57,7 +57,14 @@ declare function updateRelationshipAction(sourceId: string, targetId: string, re
57
57
  declare function removeRelationshipAction(sourceId: string, targetId: string, relationshipType?: string): Action;
58
58
  //#endregion
59
59
  //#region src/queue/types.d.ts
60
- type JobKind = "mutation" | "load";
60
+ /**
61
+ * What a job carries: a mutation applies actions, a load imports synced
62
+ * operations, and a reevaluation re-judges a document's stored operations
63
+ * because a read-set stream elsewhere (a group document) changed. A
64
+ * reevaluation job carries no actions or operations; its work is derived
65
+ * from what is already stored.
66
+ */
67
+ type JobKind = "mutation" | "load" | "reevaluation";
61
68
  /**
62
69
  * State of a job in the queue
63
70
  */
@@ -69,6 +76,17 @@ declare enum JobQueueState {
69
76
  RUNNING = 3,
70
77
  RESOLVED = 4
71
78
  }
79
+ /**
80
+ * How a retry is accounted against the job's retry limit.
81
+ * - `CountAgainstLimit` (default): a fault; the job eventually exhausts its
82
+ * retries and fails terminally.
83
+ * - `ExemptFromLimit`: not a fault, so the attempt is not charged to the job.
84
+ * Used for concurrency conflicts, where the retry does new work.
85
+ */
86
+ declare enum RetryAccounting {
87
+ CountAgainstLimit = "count-against-limit",
88
+ ExemptFromLimit = "exempt-from-limit"
89
+ }
72
90
  /**
73
91
  * Interface for a job execution handle
74
92
  */
@@ -127,9 +145,14 @@ type JobAvailableEvent = {
127
145
  //#endregion
128
146
  //#region src/shared/types.d.ts
129
147
  /**
130
- * Information about an error including message and stack trace.
148
+ * Information about an error including its name, message and stack trace.
149
+ *
150
+ * The name is what survives the crossing out of the executor: a consumer that
151
+ * has to tell a terminal failure from a retryable one, or classify a dead
152
+ * letter, only ever sees this record rather than the thrown error.
131
153
  */
132
154
  type ErrorInfo$1 = {
155
+ name: string;
133
156
  message: string;
134
157
  stack: string;
135
158
  };
@@ -230,6 +253,11 @@ type ViewFilter = {
230
253
  branch?: string;
231
254
  scopes?: string[];
232
255
  revision?: number;
256
+ /**
257
+ * Read subject for the IReactorClient read gate; defaults to the client's
258
+ * signer. Set per request when serving many principals. Ignored by IReactor.
259
+ */
260
+ subject?: AuthSubject;
233
261
  };
234
262
  /**
235
263
  * Describes filter options for searching documents.
@@ -328,6 +356,16 @@ interface IReadModelCoordinator {
328
356
  */
329
357
  getChainDepth(): number;
330
358
  }
359
+ type ReadModelRegistrationStage = "pre_ready" | "post_ready";
360
+ /**
361
+ * Optional capability exposed by coordinators that support adding read models
362
+ * after construction. Custom and remote coordinators are not required to
363
+ * implement it.
364
+ */
365
+ interface ILiveReadModelCoordinator extends IReadModelCoordinator {
366
+ addReadModel(readModel: IReadModel, stage: ReadModelRegistrationStage): void;
367
+ }
368
+ declare function supportsLiveReadModelRegistration(coordinator: IReadModelCoordinator): coordinator is ILiveReadModelCoordinator;
331
369
  //#endregion
332
370
  //#region src/sync/types.d.ts
333
371
  declare enum ChannelScheme {
@@ -398,7 +436,13 @@ declare enum ChannelErrorSource {
398
436
  Inbox = "inbox",
399
437
  Outbox = "outbox"
400
438
  }
401
- type SyncOperationErrorType = "SIGNATURE_INVALID" | "HASH_MISMATCH" | "LIBRARY_ERROR" | "MISSING_OPERATIONS" | "EXCESSIVE_SHUFFLE" | "GRACEFUL_ABORT";
439
+ type SyncOperationErrorType = "SIGNATURE_INVALID" | "HASH_MISMATCH" | "LIBRARY_ERROR" | "MISSING_OPERATIONS" | "EXCESSIVE_SHUFFLE" | "GRACEFUL_ABORT"
440
+ /**
441
+ * An arriving auth operation did not exceed the local auth head. Exempt from
442
+ * quarantine, because reconciling the two policies needs the traffic a
443
+ * quarantine would stop.
444
+ */
445
+ | "AUTH_TIMESTAMP_NOT_MONOTONIC" /** An arriving operation carried a timestamp that is not an ISO-8601 instant. */ | "INVALID_TIMESTAMP" /** No classification applies, including rows written before the field. */ | "UNCLASSIFIED";
402
446
  type ChannelHealth = {
403
447
  state: "idle" | "running" | "error";
404
448
  lastSuccessUtcMs?: number;
@@ -491,6 +535,7 @@ type DeadLetterAddedEvent = {
491
535
  remoteName: string;
492
536
  documentId: string;
493
537
  errorSource: ChannelErrorSource;
538
+ errorType: SyncOperationErrorType;
494
539
  };
495
540
  /**
496
541
  * Status of a sync operation result.
@@ -537,6 +582,36 @@ declare class OptimisticLockError extends Error {
537
582
  declare class RevisionMismatchError extends Error {
538
583
  constructor(expected: number, actual: number);
539
584
  }
585
+ /**
586
+ * One read-set stream and the highest operation index observed on it, or -1
587
+ * if it was observed empty.
588
+ */
589
+ type AppendConditionStream = {
590
+ documentId: string;
591
+ scope: string;
592
+ branch: string;
593
+ revision: number;
594
+ };
595
+ /**
596
+ * A read-set enforced by {@link IOperationStore.apply}: the append fails if
597
+ * any stream has operations past its recorded revision.
598
+ */
599
+ type AppendCondition = {
600
+ streams: AppendConditionStream[];
601
+ };
602
+ /** Error history keeps messages, not classes, so failures match by prefix. */
603
+ declare const APPEND_CONDITION_FAILED_PREFIX = "Append condition failed: ";
604
+ /**
605
+ * A read-set stream grew before the append committed. A concurrency
606
+ * conflict, not a fault: the caller retries against the new stream heads.
607
+ */
608
+ declare class AppendConditionFailedError extends Error {
609
+ readonly condition: AppendCondition;
610
+ constructor(condition: AppendCondition);
611
+ static isError(error: unknown): error is AppendConditionFailedError;
612
+ /** True when a recorded error message is an append-condition failure. */
613
+ static isFailureMessage(message: string): boolean;
614
+ }
540
615
  /**
541
616
  * A write transaction passed to {@link IOperationStore.apply}. Accumulates
542
617
  * operations that are committed atomically when the callback returns.
@@ -550,7 +625,7 @@ interface AtomicTxn {
550
625
  * revision field and lastModified timestamp.
551
626
  */
552
627
  type DocumentRevisions = {
553
- /** Map of scope to operation index for that scope */revision: Record<string, number>; /** Latest timestamp across revisions */
628
+ /** Map of scope to operation index for that scope */revision: Record<string, number>; /** The largest operation timestamp in the document, across every scope. */
554
629
  latestTimestamp: string;
555
630
  };
556
631
  /**
@@ -571,6 +646,12 @@ interface IOperationStore {
571
646
  * returned instead of throwing. If no matching stored row is found, the
572
647
  * original error is propagated unchanged.
573
648
  *
649
+ * With an {@link AppendCondition}, the append additionally fails with
650
+ * {@link AppendConditionFailedError} — writing nothing — if any read-set
651
+ * stream has operations past its recorded revision. The written and
652
+ * read-set streams are advisory-locked in sorted key order, so concurrent
653
+ * conditional appends on overlapping streams serialize.
654
+ *
574
655
  * @param documentId - The document id
575
656
  * @param documentType - The document type identifier
576
657
  * @param scope - The operation scope (e.g. "global", "local")
@@ -578,9 +659,10 @@ interface IOperationStore {
578
659
  * @param revision - Expected current revision (optimistic lock)
579
660
  * @param fn - Callback that stages operations via {@link AtomicTxn}
580
661
  * @param signal - Optional abort signal to cancel the request
662
+ * @param condition - Optional read-set to enforce at write time
581
663
  * @returns The stored operations; empty array when no operations were staged
582
664
  */
583
- apply(documentId: string, documentType: string, scope: string, branch: string, revision: number, fn: (txn: AtomicTxn) => void | Promise<void>, signal?: AbortSignal): Promise<Operation[]>;
665
+ apply(documentId: string, documentType: string, scope: string, branch: string, revision: number, fn: (txn: AtomicTxn) => void | Promise<void>, signal?: AbortSignal, condition?: AppendCondition): Promise<Operation[]>;
584
666
  /**
585
667
  * Returns operations for a document/scope/branch whose index is greater
586
668
  * than the given revision.
@@ -627,6 +709,16 @@ interface IOperationStore {
627
709
  * @returns Object containing revision map and latest timestamp
628
710
  */
629
711
  getRevisions(documentId: string, branch: string, signal?: AbortSignal): Promise<DocumentRevisions>;
712
+ /**
713
+ * The largest operation timestamp in one stream, or undefined when it is empty.
714
+ * Distinct from {@link DocumentRevisions.latestTimestamp}, which maxes over
715
+ * every scope.
716
+ *
717
+ * Must be a real maximum, not the last-indexed operation's timestamp: a
718
+ * re-evaluation pass re-appends at a fresh index while keeping the original
719
+ * timestamp, so a later timestamp can sit behind the last row.
720
+ */
721
+ getStreamLatestTimestamp(documentId: string, scope: string, branch: string, signal?: AbortSignal): Promise<string | undefined>;
630
722
  }
631
723
  /**
632
724
  * Stores periodic document snapshots (keyframes) so that document state
@@ -1045,7 +1137,8 @@ type DeadLetterRecord = {
1045
1137
  branch: string;
1046
1138
  operations: OperationWithContext$1[];
1047
1139
  errorSource: ChannelErrorSource;
1048
- errorMessage: string;
1140
+ errorMessage: string; /** Why it failed, in the closed set sync classifies failures into. */
1141
+ errorType: SyncOperationErrorType;
1049
1142
  };
1050
1143
  /**
1051
1144
  * Persists dead-lettered sync operations so they survive reactor restarts.
@@ -1103,6 +1196,15 @@ interface IOperationIndexTxn {
1103
1196
  createCollection(collectionId: string): void;
1104
1197
  addToCollection(collectionId: string, documentId: string): void;
1105
1198
  removeFromCollection(collectionId: string, documentId: string): void;
1199
+ /**
1200
+ * Records the group documents an auth operation's input names, tied to the
1201
+ * last written operation like addToCollection. At commit each reference is
1202
+ * remembered permanently and the group joins every collection the
1203
+ * referencing document belongs to, keeping the earliest join and reopening
1204
+ * a closed membership, so sync serves the group's history to every remote
1205
+ * that can observe the referencing grant.
1206
+ */
1207
+ recordGroupReferences(documentId: string, groupIds: string[]): void;
1106
1208
  write(operations: OperationIndexEntry[]): void;
1107
1209
  }
1108
1210
  /**
@@ -1124,6 +1226,13 @@ interface IOperationIndex {
1124
1226
  * Returns a map of documentId to array of collection IDs.
1125
1227
  */
1126
1228
  getCollectionsForDocuments(documentIds: string[]): Promise<Record<string, string[]>>;
1229
+ /**
1230
+ * The documents whose auth history has ever referenced the group, from the
1231
+ * group-reference relation. This is the set a group-stream change owes a
1232
+ * re-evaluation pass to; it is complete because a group's auth scope cannot
1233
+ * reference other groups.
1234
+ */
1235
+ getGroupReferencers(groupId: string, signal?: AbortSignal): Promise<string[]>;
1127
1236
  }
1128
1237
  /**
1129
1238
  * Identifies the collection a remote synchronizes. Collections are drive-level
@@ -1152,6 +1261,51 @@ declare class DriveCollectionId {
1152
1261
  equals(other: DriveCollectionId): boolean;
1153
1262
  }
1154
1263
  //#endregion
1264
+ //#region src/cache/write-cache-types.d.ts
1265
+ /**
1266
+ * Configuration options for the write cache
1267
+ */
1268
+ type WriteCacheConfig = {
1269
+ /** Maximum number of document streams to cache (LRU eviction). Default: 1000 */maxDocuments: number; /** Number of snapshots to keep in each document's ring buffer. Default: 10 */
1270
+ ringBufferSize: number; /** Persist a keyframe snapshot every N revisions. Default: 10 */
1271
+ keyframeInterval: number;
1272
+ };
1273
+ /**
1274
+ * Unique identifier for a document stream
1275
+ */
1276
+ type DocumentStreamKey = {
1277
+ /** Document identifier */documentId: string; /** Operation scope */
1278
+ scope: string; /** Branch name */
1279
+ branch: string;
1280
+ };
1281
+ /**
1282
+ * Where a snapshot sits in its stream.
1283
+ *
1284
+ * - `Head`: the newest revision of the stream when it was stored. Only these
1285
+ * can answer a read that asks for the head.
1286
+ * - `Historical`: state at an earlier revision. Usable as a starting point to
1287
+ * replay forward from, and as an answer to a read for that same revision.
1288
+ */
1289
+ declare enum SnapshotPosition {
1290
+ Head = "head",
1291
+ Historical = "historical"
1292
+ }
1293
+ /**
1294
+ * A cached document snapshot at a specific revision
1295
+ */
1296
+ type CachedSnapshot = {
1297
+ /** The revision number of this snapshot */revision: number; /** The document state at this revision */
1298
+ document: PHDocument; /** Where this snapshot sat in the stream when it was stored */
1299
+ position: SnapshotPosition;
1300
+ };
1301
+ /**
1302
+ * Serialized keyframe snapshot for K/V store persistence
1303
+ */
1304
+ type KeyframeSnapshot = {
1305
+ /** The revision number of this keyframe */revision: number; /** Serialized document state */
1306
+ document: string;
1307
+ };
1308
+ //#endregion
1155
1309
  //#region src/cache/write/interfaces.d.ts
1156
1310
  /**
1157
1311
  * IWriteCache is a write-side projection that optimizes document state retrieval
@@ -1166,7 +1320,8 @@ interface IWriteCache {
1166
1320
  * @param documentId - The document identifier
1167
1321
  * @param scope - Operation scope
1168
1322
  * @param branch - Branch name
1169
- * @param targetRevision - The exact revision to retrieve (optional, defaults to latest)
1323
+ * @param targetRevision - Index of the last operation to apply, defaulting
1324
+ * to latest. An operation index, never `header.revision[scope]`.
1170
1325
  * @param signal - Optional abort signal to cancel the operation
1171
1326
  * @returns The complete document at the specified revision
1172
1327
  *
@@ -1192,15 +1347,19 @@ interface IWriteCache {
1192
1347
  * @param documentId - The document identifier
1193
1348
  * @param scope - Operation scope
1194
1349
  * @param branch - Branch name
1195
- * @param revision - The revision this document represents
1350
+ * @param revision - Index of the last operation this document reflects, so
1351
+ * `header.revision[scope]` is one greater. -1 for an empty scope.
1196
1352
  * @param document - The document to cache
1353
+ * @param position - Whether `revision` is the stream's head. Nothing checks
1354
+ * it: claiming `Head` for an earlier revision makes a getState() with no
1355
+ * target return stale state.
1197
1356
  *
1198
1357
  * @example
1199
1358
  * ```typescript
1200
- * cache.putState(docId, 'global', 'main', 42, document);
1359
+ * cache.putState(docId, 'global', 'main', 42, document, SnapshotPosition.Head);
1201
1360
  * ```
1202
1361
  */
1203
- putState(documentId: string, scope: string, branch: string, revision: number, document: PHDocument): void;
1362
+ putState(documentId: string, scope: string, branch: string, revision: number, document: PHDocument, position: SnapshotPosition): void;
1204
1363
  /**
1205
1364
  * Invalidates (removes) cached entries for a document stream.
1206
1365
  *
@@ -1500,6 +1659,104 @@ interface IReactorSubscriptionManager {
1500
1659
  onRelationshipChanged(callback: (parentId: string, childId: string, changeType: RelationshipChangeType) => void, search?: SearchFilter): () => void;
1501
1660
  }
1502
1661
  //#endregion
1662
+ //#region src/decision/types.d.ts
1663
+ /** One operation stream. */
1664
+ type StreamQuery = {
1665
+ documentId: string;
1666
+ branch: string;
1667
+ scope: string;
1668
+ };
1669
+ /**
1670
+ * What building a decision model reads a stream's state through.
1671
+ *
1672
+ * `IWriteCache` satisfies this and is what the write paths pass. The read path
1673
+ * cannot: the write cache is a write-side projection invalidated by the process
1674
+ * that runs the executor, so a reactor whose executors live in worker processes
1675
+ * holds state in its parent that no commit ever invalidates. A read there would
1676
+ * decide against a policy arbitrarily far behind the one the write paths
1677
+ * enforce. Reads therefore pass a reader backed by the read side.
1678
+ */
1679
+ interface IStreamStateReader {
1680
+ getState(documentId: string, scope: string, branch: string, targetRevision?: number, signal?: AbortSignal): Promise<PHDocument>;
1681
+ }
1682
+ /** The document and branch a decision model is built for. */
1683
+ type DecisionTarget = {
1684
+ documentId: string;
1685
+ branch: string;
1686
+ };
1687
+ /**
1688
+ * What a decision's conditions may read beyond the projections: the executing
1689
+ * scope's own state and the attempted action's input. Populated only while
1690
+ * authConditions is on; otherwise both stay undefined and conditional grants
1691
+ * never apply.
1692
+ */
1693
+ type DecisionContext = {
1694
+ scopeState: unknown;
1695
+ actionInput?: unknown;
1696
+ };
1697
+ /** A statically-queried stream's operations, named after its projection. */
1698
+ type StreamHistory = {
1699
+ name: string;
1700
+ operations: Operation[];
1701
+ };
1702
+ /**
1703
+ * A named stream whose value in the model is that scope's state from the
1704
+ * document rebuild the reactor already performs. A derived query may read
1705
+ * only statically-queried projections, so composition is one layer deep.
1706
+ */
1707
+ type Projection<M> = {
1708
+ query: StreamQuery | ((model: Partial<M>) => StreamQuery[]);
1709
+ /**
1710
+ * For a derived projection, the streams it may read anywhere in an
1711
+ * evaluated range, derived from the statically-queried streams' operations
1712
+ * (including the operations under evaluation). A positional walk cannot use
1713
+ * `query`, because the folded state it depends on changes over the range;
1714
+ * this over-approximates by design, since a stream referenced at any
1715
+ * position stays readable when the earlier range is re-evaluated even if a
1716
+ * later operation removes the reference. Ignored on static projections.
1717
+ */
1718
+ queryOverHistory?: (reads: StreamHistory[]) => StreamQuery[];
1719
+ /**
1720
+ * Action types in this stream that can change an evaluation. Reads of the stream
1721
+ * are filtered to these, so anything left out is invisible to a decision.
1722
+ */
1723
+ decidingActions: string[]; /** Applies one of this stream's operations while deciding. */
1724
+ apply: (document: PHDocument, operation: Operation) => PHDocument;
1725
+ };
1726
+ /**
1727
+ * The outcome of evaluating one operation. A refusal carries the reason it is
1728
+ * recorded with, because a model has more than one way to refuse.
1729
+ */
1730
+ type Evaluation = {
1731
+ decision: "allow";
1732
+ } | {
1733
+ decision: "deny";
1734
+ reason: string;
1735
+ };
1736
+ /** Projections plus a decision function over the built model. */
1737
+ type DecisionModel<M> = {
1738
+ projections: { [K in keyof M]: Projection<M> };
1739
+ /**
1740
+ * Present when decide reads the executing scope's state through the
1741
+ * decision context. A positional walk then folds the evaluated stream with
1742
+ * this, from its base state through every effective operation, so
1743
+ * conditions read the state as it stood at each operation's position
1744
+ * rather than at the head.
1745
+ */
1746
+ foldEvaluatedScope?: (document: PHDocument, operation: Operation) => PHDocument;
1747
+ /**
1748
+ * Whether or not this model decides about operations in a given scope. That
1749
+ * is, a scope it reads is not necessarily one it evaluates, and vise-versa.
1750
+ */
1751
+ evaluatesScope(scope: string): boolean;
1752
+ decide(model: M, subject: AuthSubject, request: AuthRequest, ctx: DecisionContext): Evaluation;
1753
+ };
1754
+ /** A built model plus the read-set condition recording what the build read. */
1755
+ type BuiltDecisionModel<M> = {
1756
+ model: M;
1757
+ appendCondition: AppendCondition;
1758
+ };
1759
+ //#endregion
1503
1760
  //#region src/client/types.d.ts
1504
1761
  /**
1505
1762
  * Describes the types of document changes that can occur.
@@ -1531,6 +1788,17 @@ type CreateDocumentOptions = {
1531
1788
  /** Optional "id" or "slug" of parent document */parentIdentifier?: string; /** Optional version of the document model to use (defaults to latest) */
1532
1789
  documentModelVersion?: number;
1533
1790
  };
1791
+ /**
1792
+ * Options for upgrading a document.
1793
+ */
1794
+ type UpgradeDocumentOptions = {
1795
+ /**
1796
+ * How many times to retry with a fresh read when the executor rejects the
1797
+ * upgrade because the document changed after it was read. Defaults to
1798
+ * {@link DEFAULT_UPGRADE_CONFLICT_RETRIES}.
1799
+ */
1800
+ maxConflictRetries?: number;
1801
+ };
1534
1802
  /**
1535
1803
  * Drive-aware operations grouped under `client.drives`.
1536
1804
  *
@@ -1596,6 +1864,33 @@ interface IDriveClient {
1596
1864
  */
1597
1865
  listNodes(driveIdentifier: string, parentFolder?: string | null, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<Node>>;
1598
1866
  }
1867
+ /**
1868
+ * One operation an authorization preflight predicts a verdict for. The input is
1869
+ * what a conditional grant reads, so a candidate standing for a filled-in form
1870
+ * carries that form's input.
1871
+ */
1872
+ type ActionCandidate = {
1873
+ scope: string;
1874
+ type: string;
1875
+ input?: unknown;
1876
+ };
1877
+ /**
1878
+ * The predicted verdicts for a set of candidates, in the order they were given,
1879
+ * with the aggregates a UI branches on.
1880
+ *
1881
+ * The aggregates are redundant -- a verdict is binary, so `allDenied` is
1882
+ * `!anyAllowed` and `anyDenied` is `!allAllowed` -- and all four are returned
1883
+ * so that a caller reads the one its question is phrased in rather than
1884
+ * negating another. Over no candidates every aggregate is false: nothing is
1885
+ * allowed and nothing is denied.
1886
+ */
1887
+ type ActionEvaluations = {
1888
+ evaluations: Evaluation[];
1889
+ allAllowed: boolean;
1890
+ anyAllowed: boolean;
1891
+ allDenied: boolean;
1892
+ anyDenied: boolean;
1893
+ };
1599
1894
  /**
1600
1895
  * The ReactorClient interface that wraps lower-level APIs to provide
1601
1896
  * a simpler interface for document operations.
@@ -1646,7 +1941,7 @@ interface IReactorClient {
1646
1941
  * @param signal - Optional abort signal to cancel the request
1647
1942
  * @returns The canonical document id
1648
1943
  */
1649
- resolveIdOrSlug(identifier: string, signal?: AbortSignal): Promise<string>;
1944
+ resolveIdOrSlug(identifier: string, view?: ViewFilter, signal?: AbortSignal): Promise<string>;
1650
1945
  /**
1651
1946
  * Retrieves operations for a document.
1652
1947
  *
@@ -1690,6 +1985,44 @@ interface IReactorClient {
1690
1985
  * @returns List of documents matching criteria and pagination cursor
1691
1986
  */
1692
1987
  find(search: SearchFilter, view?: ViewFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<PHDocument>>;
1988
+ /**
1989
+ * Predicts whether the subject would be admitted to execute each of a set of
1990
+ * candidate operations, without submitting any of them. A UI asks this to
1991
+ * disable a control rather than offer an action that fails on submit.
1992
+ *
1993
+ * The answer is a prediction, not a promise. Three caveats hold:
1994
+ *
1995
+ * - Real admission compiles an append condition over everything it read and
1996
+ * the store enforces it at write time. A preflight reads no future, so a
1997
+ * policy change landing between this answer and the submit changes the
1998
+ * verdict. The submit path stays the only authority.
1999
+ * - The verdict is evaluated at the stream heads. It is therefore correct for
2000
+ * a candidate that will be stamped at or after every timestamp the
2001
+ * evaluation read, which is the normal case for a control the user is about
2002
+ * to click. A backdated submission is out of contract: the reactor decides
2003
+ * that one by position, against the policy as it stood there.
2004
+ * - A candidate whose input decides the verdict needs that input supplied.
2005
+ * With `authConditions` on, a conditional grant reads `action.input`, so
2006
+ * omitting the input predicts the denial an empty input would earn rather
2007
+ * than the verdict the filled-in form will get.
2008
+ *
2009
+ * Document-scope candidates are decided against the policy of the document
2010
+ * their input names, not the one passed here: delete and upgrade name it in
2011
+ * `input.documentId`, and the relationship actions in `input.sourceId`. This
2012
+ * follows the executor's own gate, which decides against the document
2013
+ * guarding the write. `CREATE_DOCUMENT` follows the gate's exemption: it runs
2014
+ * before its document exists, so the executor never decides it against a
2015
+ * policy and the preflight predicts allow.
2016
+ *
2017
+ * @param documentIdentifier - Document "id" or "slug" the candidates target
2018
+ * @param branch - Branch to evaluate against
2019
+ * @param candidates - Operations to predict a verdict for, each with the scope it would execute in
2020
+ * @param subject - Optional subject to decide for, defaulting to the client's own signer. A plain subject rather than a ViewFilter: the evaluation reads no view, so a filter's branch or scopes would be silently ignored here
2021
+ * @param signal - Optional abort signal to cancel the request
2022
+ * @returns One evaluation per candidate, in the order given, with the aggregates over them
2023
+ * @throws AuthEnforcementDisabledError if the reactor's authEnforcement flag is off, in which case it holds no decision model and the legacy host-table permission system cannot answer for one
2024
+ */
2025
+ evaluateActions(documentIdentifier: string, branch: string, candidates: ActionCandidate[], subject?: AuthSubject, signal?: AbortSignal): Promise<ActionEvaluations>;
1693
2026
  /**
1694
2027
  * Creates a document and waits for completion
1695
2028
  *
@@ -1707,6 +2040,38 @@ interface IReactorClient {
1707
2040
  * @param signal - Optional abort signal to cancel the request
1708
2041
  */
1709
2042
  createEmpty<TDocument extends PHDocument>(documentModelType: string, options?: CreateDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
2043
+ /**
2044
+ * Retrieves the document model module matching the version a document is
2045
+ * stamped with. Use this instead of {@link getDocumentModelModule}
2046
+ * whenever a specific document is in hand: the latest-wins lookup feeds
2047
+ * not-yet-upgraded documents the wrong reducer, diverging from replay.
2048
+ *
2049
+ * @param document - The document whose stamped version selects the module
2050
+ * @returns The document model module registered for that version
2051
+ * @throws UnsupportedDocumentModelVersionError if no module is registered for the stamped version
2052
+ */
2053
+ getDocumentModelModuleForDocument(document: PHDocument): Promise<DocumentModelModule<any>>;
2054
+ /**
2055
+ * Upgrades a document to a newer document model version by dispatching an
2056
+ * UPGRADE_DOCUMENT action. When toVersion is omitted, upgrades to the
2057
+ * latest registered module version for the document's type. Returns the
2058
+ * document unchanged when it is already at the target version.
2059
+ *
2060
+ * The action carries a snapshot of the document's version and per-scope
2061
+ * revisions, which the executor validates before persisting. When an edit
2062
+ * lands between the read and the upgrade executing, the upgrade is
2063
+ * rejected and retried with a fresh read up to
2064
+ * {@link UpgradeDocumentOptions.maxConflictRetries} times before the
2065
+ * conflict is surfaced.
2066
+ *
2067
+ * @param documentIdentifier - Target document id or slug
2068
+ * @param toVersion - Optional target document model version; defaults to latest
2069
+ * @param options - Optional upgrade options (maxConflictRetries)
2070
+ * @param signal - Optional abort signal to cancel the request
2071
+ * @returns The upgraded document
2072
+ * @throws DowngradeNotSupportedError if toVersion is less than the document's current version
2073
+ */
2074
+ upgradeDocument<TDocument extends PHDocument = PHDocument>(documentIdentifier: string, toVersion?: number, options?: UpgradeDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
1710
2075
  /**
1711
2076
  * Creates an empty document in a drive as a single batched operation.
1712
2077
  * This is more efficient than createEmpty + addFile as it batches all
@@ -1863,136 +2228,1042 @@ interface IReactorClient {
1863
2228
  subscribe(search: SearchFilter, callback: (event: DocumentChangeEvent) => void, view?: ViewFilter): () => void;
1864
2229
  }
1865
2230
  //#endregion
1866
- //#region src/client/reactor-client.d.ts
2231
+ //#region src/cache/collection-membership-cache.d.ts
2232
+ interface ICollectionMembershipCache {
2233
+ getCollectionsForDocuments(documentIds: string[]): Promise<Record<string, string[]>>;
2234
+ invalidate(documentId: string): void;
2235
+ }
2236
+ //#endregion
2237
+ //#region src/cache/document-meta-cache-types.d.ts
1867
2238
  /**
1868
- * ReactorClient implementation that wraps lower-level APIs to provide
1869
- * a simpler interface for document operations.
2239
+ * Cached document metadata from the "document" scope.
1870
2240
  *
1871
- * Features:
1872
- * - Wraps Jobs with Promises for easier async handling
1873
- * - Manages signing of submitted Action objects
1874
- * - Provides quality-of-life functions for common tasks
1875
- * - Wraps subscription interface with ViewFilters
2241
+ * This lightweight structure holds essential document information needed by
2242
+ * the job executor without fetching full scope state. It provides an explicit
2243
+ * cross-scope contract for accessing document scope metadata.
1876
2244
  */
1877
- declare class ReactorClient implements IReactorClient {
1878
- private logger;
1879
- private reactor;
1880
- private signer;
1881
- private subscriptionManager;
1882
- private jobAwaiter;
1883
- private documentIndexer;
1884
- private documentView;
1885
- readonly drives: IDriveClient;
1886
- constructor(logger: ILogger, reactor: IReactor, signer: ISigner, subscriptionManager: IReactorSubscriptionManager, jobAwaiter: IJobAwaiter, documentIndexer: IDocumentIndexer, documentView: IDocumentView);
2245
+ type CachedDocumentMeta = {
1887
2246
  /**
1888
- * Retrieves a list of document model modules.
2247
+ * The full PHDocumentState from document.state.document.
2248
+ * Contains version, hash, isDeleted, deletedAtUtcIso, etc.
1889
2249
  */
1890
- getDocumentModelModules(namespace?: string, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<DocumentModelModule>>;
2250
+ state: PHDocumentState;
1891
2251
  /**
1892
- * Retrieves a specific document model module by document type.
1893
- *
1894
- * @param documentType - The document type identifier
1895
- * @returns The document model module
2252
+ * The document type (from header), cached for convenience.
1896
2253
  */
1897
- getDocumentModelModule(documentType: string): Promise<DocumentModelModule<any>>;
2254
+ documentType: string;
1898
2255
  /**
1899
- * Retrieves a specific PHDocument
2256
+ * The revision of the document scope when this metadata was captured.
2257
+ * Used for cache invalidation and consistency checks.
1900
2258
  */
1901
- get<TDocument extends PHDocument>(identifier: string, view?: ViewFilter, signal?: AbortSignal): Promise<TDocument>;
2259
+ documentScopeRevision: number;
2260
+ };
2261
+ /**
2262
+ * Interface for the document metadata cache.
2263
+ *
2264
+ * This cache provides an explicit cross-scope contract for accessing document
2265
+ * scope metadata. It solves the problem where job execution in one scope (e.g.,
2266
+ * "global") needs access to document scope state (version, isDeleted, etc.)
2267
+ * which may be stale in scope-specific caches or keyframes.
2268
+ *
2269
+ * The cache supports:
2270
+ * - Latest metadata retrieval with LRU caching
2271
+ * - Historical metadata reconstruction for reshuffling scenarios
2272
+ * - Eager updates after document scope operations
2273
+ */
2274
+ interface IDocumentMetaCache {
1902
2275
  /**
1903
- * Resolves an identifier (id or slug) to the canonical document id, using the
1904
- * same lookup as the data path. Resolves against the "main" branch. Throws if
1905
- * the identifier cannot be resolved or is ambiguous.
2276
+ * Retrieves the LATEST document metadata from cache or rebuilds from operations.
2277
+ *
2278
+ * On cache miss, fetches all document scope operations and reconstructs the
2279
+ * current PHDocumentState by applying UPGRADE_DOCUMENT and DELETE_DOCUMENT
2280
+ * operations.
2281
+ *
2282
+ * @param documentId - The document identifier
2283
+ * @param branch - Branch name
2284
+ * @param signal - Optional abort signal to cancel the operation
2285
+ * @returns The cached or rebuilt document metadata
2286
+ * @throws {Error} "Operation aborted" if signal is aborted
2287
+ * @throws {Error} If document not found (no CREATE_DOCUMENT operation)
1906
2288
  */
1907
- resolveIdOrSlug(identifier: string, signal?: AbortSignal): Promise<string>;
2289
+ getDocumentMeta(documentId: string, branch: string, signal?: AbortSignal): Promise<CachedDocumentMeta>;
1908
2290
  /**
1909
- * Retrieves operations for a document
2291
+ * Rebuilds document metadata at a SPECIFIC revision (always rebuilds, no caching).
2292
+ *
2293
+ * Used during reshuffling when operations need to be inserted at a previous
2294
+ * revision and we need the document scope state as of that point in time.
2295
+ *
2296
+ * @param documentId - The document identifier
2297
+ * @param branch - Branch name
2298
+ * @param targetRevision - The document scope revision to reconstruct up to
2299
+ * @param signal - Optional abort signal to cancel the operation
2300
+ * @returns Document metadata as of the target revision
2301
+ * @throws {Error} "Operation aborted" if signal is aborted
2302
+ * @throws {Error} If document not found
1910
2303
  */
1911
- getOperations(documentIdentifier: string, view?: ViewFilter, filter?: OperationFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<Operation>>;
1912
- private getOperationsWithCompositeCursor;
2304
+ rebuildAtRevision(documentId: string, branch: string, targetRevision: number, signal?: AbortSignal): Promise<CachedDocumentMeta>;
1913
2305
  /**
1914
- * Retrieves outgoing relationships of a given type from a source document.
2306
+ * Eagerly updates cached metadata after document scope operations.
2307
+ *
2308
+ * Called by the job executor after CREATE_DOCUMENT, UPGRADE_DOCUMENT, or
2309
+ * DELETE_DOCUMENT operations to keep the cache current.
2310
+ *
2311
+ * @param documentId - The document identifier
2312
+ * @param branch - Branch name
2313
+ * @param meta - The new metadata to cache
1915
2314
  */
1916
- getOutgoingRelationships(sourceIdentifier: string, relationshipType: string, view?: ViewFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<PHDocument>>;
2315
+ putDocumentMeta(documentId: string, branch: string, meta: CachedDocumentMeta): void;
1917
2316
  /**
1918
- * Retrieves incoming relationships of a given type to a target document.
2317
+ * Invalidates cached document metadata.
2318
+ *
2319
+ * Call before reshuffling operations that modify the document scope, or
2320
+ * when document state may have changed externally.
2321
+ *
2322
+ * @param documentId - The document identifier
2323
+ * @param branch - Optional branch to narrow invalidation (if omitted, all branches)
2324
+ * @returns Number of entries invalidated
1919
2325
  */
1920
- getIncomingRelationships(targetIdentifier: string, relationshipType: string, view?: ViewFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<PHDocument>>;
2326
+ invalidate(documentId: string, branch?: string): number;
1921
2327
  /**
1922
- * Filters documents by criteria and returns a list of them
2328
+ * Clears all cached document metadata.
1923
2329
  */
1924
- find(search: SearchFilter, view?: ViewFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<PHDocument>>;
2330
+ clear(): void;
1925
2331
  /**
1926
- * Creates a document and waits for completion
2332
+ * Performs startup initialization.
1927
2333
  */
1928
- create<TDocument extends PHDocument = PHDocument>(document: PHDocument, parentIdentifier?: string, signal?: AbortSignal): Promise<TDocument>;
2334
+ startup(): Promise<void>;
1929
2335
  /**
1930
- * Creates an empty document and waits for completion
2336
+ * Performs graceful shutdown.
1931
2337
  */
1932
- createEmpty<TDocument extends PHDocument>(documentModelType: string, options?: CreateDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
2338
+ shutdown(): Promise<void>;
2339
+ }
2340
+ //#endregion
2341
+ //#region src/storage/kysely/types.d.ts
2342
+ interface OperationTable {
2343
+ id: Generated<number>;
2344
+ jobId: string;
2345
+ opId: string;
2346
+ prevOpId: string;
2347
+ writeTimestampUtcMs: Generated<Date>;
2348
+ documentId: string;
2349
+ documentType: string;
2350
+ scope: string;
2351
+ branch: string;
2352
+ timestampUtcMs: Date;
2353
+ index: number;
2354
+ action: unknown;
2355
+ skip: number;
2356
+ error?: string | null;
2357
+ deniedReason?: string | null;
2358
+ hash: string;
2359
+ }
2360
+ interface KeyframeTable {
2361
+ id: Generated<number>;
2362
+ documentId: string;
2363
+ documentType: string;
2364
+ scope: string;
2365
+ branch: string;
2366
+ revision: number;
2367
+ document: unknown;
2368
+ createdAt: Generated<Date>;
2369
+ }
2370
+ interface DocumentCollectionTable {
2371
+ documentId: string;
2372
+ collectionId: string;
2373
+ joinedOrdinal: bigint;
2374
+ leftOrdinal: bigint | null;
2375
+ }
2376
+ interface OperationIndexOperationTable {
2377
+ ordinal: Generated<number>;
2378
+ opId: string;
2379
+ documentId: string;
2380
+ documentType: string;
2381
+ scope: string;
2382
+ branch: string;
2383
+ timestampUtcMs: string;
2384
+ writeTimestampUtcMs: Generated<Date>;
2385
+ index: number;
2386
+ skip: number;
2387
+ hash: string;
2388
+ action: unknown;
2389
+ deniedReason?: string | null;
2390
+ sourceRemote: Generated<string>;
2391
+ }
2392
+ interface SyncRemoteTable {
2393
+ name: string;
2394
+ collection_id: string;
2395
+ channel_type: string;
2396
+ channel_id: string;
2397
+ remote_name: string;
2398
+ channel_parameters: unknown;
2399
+ filter_document_ids: unknown;
2400
+ filter_scopes: unknown;
2401
+ filter_branch: string;
2402
+ push_state: string;
2403
+ push_last_success_utc_ms: string | null;
2404
+ push_last_failure_utc_ms: string | null;
2405
+ push_failure_count: number;
2406
+ pull_state: string;
2407
+ pull_last_success_utc_ms: string | null;
2408
+ pull_last_failure_utc_ms: string | null;
2409
+ pull_failure_count: number;
2410
+ created_at: Generated<Date>;
2411
+ updated_at: Generated<Date>;
2412
+ }
2413
+ interface SyncCursorTable {
2414
+ remote_name: string;
2415
+ cursor_type: string;
2416
+ cursor_ordinal: bigint;
2417
+ last_synced_at_utc_ms: string | null;
2418
+ updated_at: Generated<Date>;
2419
+ }
2420
+ /**
2421
+ * Kysely table definition for the `sync_dead_letters` table.
2422
+ */
2423
+ interface SyncDeadLetterTable {
2424
+ ordinal: Generated<number>;
2425
+ id: string;
2426
+ job_id: string;
2427
+ job_dependencies: unknown;
2428
+ remote_name: string;
2429
+ document_id: string;
2430
+ scopes: unknown;
2431
+ branch: string;
2432
+ operations: unknown;
2433
+ error_source: string;
2434
+ error_message: string;
2435
+ error_type: Generated<string>;
2436
+ created_at: Generated<Date>;
2437
+ }
2438
+ /**
2439
+ * One (document, group) reference ever discovered from an auth operation's
2440
+ * input. Rows are never updated or deleted (see migration 017).
2441
+ */
2442
+ interface GroupReferenceTable {
2443
+ documentId: string;
2444
+ groupId: string;
2445
+ }
2446
+ interface Database$1 {
2447
+ Operation: OperationTable;
2448
+ Keyframe: KeyframeTable;
2449
+ document_collections: DocumentCollectionTable;
2450
+ operation_index_operations: OperationIndexOperationTable;
2451
+ group_references: GroupReferenceTable;
2452
+ sync_remotes: SyncRemoteTable;
2453
+ sync_cursors: SyncCursorTable;
2454
+ sync_dead_letters: SyncDeadLetterTable;
2455
+ }
2456
+ interface DocumentTable {
2457
+ id: string;
2458
+ createdAt: Generated<Date>;
2459
+ updatedAt: Generated<Date>;
2460
+ }
2461
+ interface DocumentRelationshipTable {
2462
+ id: Generated<string>;
2463
+ sourceId: string;
2464
+ targetId: string;
2465
+ relationshipType: string;
2466
+ metadata: unknown;
2467
+ createdAt: Generated<Date>;
2468
+ updatedAt: Generated<Date>;
2469
+ }
2470
+ interface IndexerStateTable {
2471
+ id: Generated<number>;
2472
+ lastOperationId: number;
2473
+ lastOperationTimestamp: Generated<Date>;
2474
+ }
2475
+ interface DocumentIndexerDatabase {
2476
+ Document: DocumentTable;
2477
+ DocumentRelationship: DocumentRelationshipTable;
2478
+ IndexerState: IndexerStateTable;
2479
+ }
2480
+ //#endregion
2481
+ //#region src/executor/worker/protocol.d.ts
2482
+ /**
2483
+ * A JSON-clonable value safe to send across the worker IPC boundary.
2484
+ *
2485
+ * The shape mirrors the structured-clone subset used by the parent's
2486
+ * sanitizer: primitives, arrays, plain objects, plus the explicit
2487
+ * {@link ErrorInfo} shape for marshalled Errors.
2488
+ *
2489
+ * @see Wire Protocol Reference wiki page
2490
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2491
+ */
2492
+ type SanitizedArg = null | boolean | number | string | ErrorInfo | SanitizedArg[] | {
2493
+ [key: string]: SanitizedArg;
2494
+ };
2495
+ /**
2496
+ * Structured representation of an Error for IPC transport.
2497
+ *
2498
+ * Class instances cannot be structured-cloned across worker boundaries,
2499
+ * so Errors are flattened into this shape on the worker side and
2500
+ * reconstructed on the parent side.
2501
+ *
2502
+ * @see Wire Protocol Reference wiki page
2503
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2504
+ */
2505
+ type ErrorInfo = {
2506
+ name: string;
2507
+ message: string;
2508
+ stack?: string;
2509
+ cause?: ErrorInfo;
2510
+ };
2511
+ /**
2512
+ * Reference to a module that the worker should `import()` at runtime,
2513
+ * along with the named export to pluck out as the factory.
2514
+ *
2515
+ * Exactly one of `packageName` or `filePath` is provided.
2516
+ *
2517
+ * @see Wire Protocol Reference wiki page
2518
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2519
+ */
2520
+ type ModuleRef = {
2521
+ /** Bare-specifier package name (resolved by the worker's module loader). */packageName: string; /** Named export within the module to invoke as the factory. */
2522
+ exportName: string;
2523
+ } | {
2524
+ /** Absolute or worker-resolvable file path to import. */filePath: string; /** Named export within the module to invoke as the factory. */
2525
+ exportName: string;
2526
+ };
2527
+ /**
2528
+ * Factory specification shared by the signature verifier and document
2529
+ * model spec channels. The worker imports `module.exportName` and invokes
2530
+ * it with `initArgs` to obtain the actual instance.
2531
+ *
2532
+ * `initArgs` must be JSON-clonable.
2533
+ *
2534
+ * @see Wire Protocol Reference wiki page
2535
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2536
+ */
2537
+ type FactorySpec = {
2538
+ module: ModuleRef;
2539
+ initArgs?: SanitizedArg;
2540
+ };
2541
+ /**
2542
+ * Factory spec for the signature verifier the worker should instantiate.
2543
+ *
2544
+ * Structurally identical to {@link FactorySpec}; the alias exists so call
2545
+ * sites read intent-fully.
2546
+ *
2547
+ * @see Wire Protocol Reference wiki page
2548
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2549
+ */
2550
+ type SignatureVerifierSpec = FactorySpec;
2551
+ /**
2552
+ * Factory spec for a document model module the worker should instantiate.
2553
+ *
2554
+ * Structurally identical to {@link FactorySpec}; the alias exists so call
2555
+ * sites read intent-fully.
2556
+ *
2557
+ * @see Wire Protocol Reference wiki page
2558
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2559
+ */
2560
+ type DocumentModelSpec = FactorySpec;
2561
+ /**
2562
+ * One entry in the document model manifest the worker materializes on
2563
+ * startup (or extends lazily via `load-model`).
2564
+ *
2565
+ * @see Wire Protocol Reference wiki page
2566
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2567
+ */
2568
+ type ModelManifestEntry = {
2569
+ /** Document type identifier (e.g. "ph/account"). */documentType: string; /** Document model version this entry registers. */
2570
+ version: string; /** Factory spec the worker imports and invokes to obtain the model. */
2571
+ spec: DocumentModelSpec;
2572
+ };
2573
+ /**
2574
+ * JSON-clonable Postgres connection info passed to the worker so it can
2575
+ * open its own pool. Storage-specific wiring may extend this shape in
2576
+ * later phases.
2577
+ *
2578
+ * @see Wire Protocol Reference wiki page
2579
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2580
+ */
2581
+ type DbConfig = {
2582
+ host: string;
2583
+ port: number;
2584
+ database: string;
2585
+ user: string;
2586
+ password: string;
2587
+ ssl?: boolean;
2588
+ applicationName?: string;
2589
+ poolSize?: number;
2590
+ /**
2591
+ * Maximum time (ms) a caller will wait to acquire a connection from the
2592
+ * pool before pg.Pool throws. When omitted, pg defaults to 0 (unlimited
2593
+ * wait), which hides acquire-starvation as silent latency.
2594
+ */
2595
+ connectionTimeoutMillis?: number;
2596
+ /**
2597
+ * How long (ms) an idle connection stays open before pg closes it. When
2598
+ * omitted, pg defaults to 10000.
2599
+ */
2600
+ idleTimeoutMillis?: number;
2601
+ };
2602
+ /**
2603
+ * Configuration for the executor worker pool.
2604
+ *
2605
+ * Mirrors the `workerPool` sub-config on {@link JobExecutorConfig};
2606
+ * a later card wires this into the executor config.
2607
+ *
2608
+ * @see Wire Protocol Reference wiki page
2609
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2610
+ */
2611
+ type WorkerPoolConfig = {
2612
+ /** Whether the worker pool is active; when false the executor runs in-process. */enabled: boolean; /** Number of worker instances to spawn. */
2613
+ numWorkers: number; /** Worker isolation mode. */
2614
+ workerType: "thread" | "process"; /** Optional heartbeat interval in milliseconds. */
2615
+ heartbeatMs?: number; /** Optional per-worker Postgres pool size override. */
2616
+ workerPgPoolSize?: number;
2617
+ };
2618
+ /**
2619
+ * Payload the worker reports back when a job's write phase is complete.
2620
+ *
2621
+ * Parent fills `collectionMemberships` at emission time, so it is
2622
+ * intentionally absent from the worker -> parent message.
2623
+ *
2624
+ * @see Wire Protocol Reference wiki page
2625
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2626
+ */
2627
+ type JobWriteReadyPayload = {
2628
+ operations: OperationWithContext$1[];
2629
+ jobMeta: JobMeta;
2630
+ };
2631
+ /**
2632
+ * Initializes a freshly spawned worker with the configuration and
2633
+ * factories it needs to start executing jobs.
2634
+ *
2635
+ * @see Wire Protocol Reference wiki page
2636
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2637
+ */
2638
+ type InitMessage = {
2639
+ type: "init";
2640
+ correlationId: string;
2641
+ workerId: string;
2642
+ poolConfig: WorkerPoolConfig;
2643
+ db: DbConfig; /** Omitted = the worker performs no executor-side signature verification. */
2644
+ signatureVerifier?: SignatureVerifierSpec;
2645
+ models: ModelManifestEntry[]; /** Omitted = the worker builds its executor with the built-in defaults. */
2646
+ executorConfig?: JobExecutorConfig;
2647
+ };
2648
+ /**
2649
+ * Dispatches a job to the worker for execution.
2650
+ *
2651
+ * @see Wire Protocol Reference wiki page
2652
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2653
+ */
2654
+ type ExecuteMessage = {
2655
+ type: "execute";
2656
+ correlationId: string;
2657
+ job: Job;
2658
+ };
2659
+ /**
2660
+ * Requests cancellation of an in-flight job.
2661
+ *
2662
+ * @see Wire Protocol Reference wiki page
2663
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2664
+ */
2665
+ type AbortMessage = {
2666
+ type: "abort";
2667
+ correlationId: string; /** correlationId of the `execute` message being aborted. */
2668
+ targetCorrelationId: string;
2669
+ reason?: string;
2670
+ };
2671
+ /**
2672
+ * Asks the worker to drain in-flight work and exit.
2673
+ *
2674
+ * @see Wire Protocol Reference wiki page
2675
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2676
+ */
2677
+ type ShutdownMessage = {
2678
+ type: "shutdown";
2679
+ correlationId: string; /** Optional grace period before the parent force-terminates the worker. */
2680
+ graceMs?: number;
2681
+ };
2682
+ /**
2683
+ * Lazily registers an additional document model on a running worker.
2684
+ *
2685
+ * @see Wire Protocol Reference wiki page
2686
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2687
+ */
2688
+ type LoadModelMessage = {
2689
+ type: "load-model";
2690
+ correlationId: string;
2691
+ model: ModelManifestEntry;
2692
+ };
2693
+ /**
2694
+ * Union of all messages the parent may send to a worker.
2695
+ *
2696
+ * @see Wire Protocol Reference wiki page
2697
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2698
+ */
2699
+ type ParentMessage = InitMessage | ExecuteMessage | AbortMessage | ShutdownMessage | LoadModelMessage;
2700
+ /**
2701
+ * Announces that the worker has finished `init` and is ready to accept
2702
+ * `execute` messages.
2703
+ *
2704
+ * @see Wire Protocol Reference wiki page
2705
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2706
+ */
2707
+ type ReadyMessage = {
2708
+ type: "ready"; /** correlationId of the originating `init` message. */
2709
+ correlationId: string;
2710
+ workerId: string;
2711
+ };
2712
+ /**
2713
+ * Final result for an `execute` job.
2714
+ *
2715
+ * On success, `writeReady` carries the operations and job meta the
2716
+ * parent needs to emit `JOB_WRITE_READY`. On failure, `error` is set
2717
+ * and `result.success` is false.
2718
+ *
2719
+ * @see Wire Protocol Reference wiki page
2720
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2721
+ */
2722
+ type ResultMessage = {
2723
+ type: "result"; /** correlationId of the originating `execute` message. */
2724
+ correlationId: string;
2725
+ result: JobResult;
2726
+ writeReady?: JobWriteReadyPayload;
2727
+ error?: ErrorInfo;
2728
+ };
2729
+ /**
2730
+ * Acknowledges that a `load-model` request succeeded.
2731
+ *
2732
+ * @see Wire Protocol Reference wiki page
2733
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2734
+ */
2735
+ type ModelLoadedMessage = {
2736
+ type: "model-loaded"; /** correlationId of the originating `load-model` message. */
2737
+ correlationId: string;
2738
+ documentType: string;
2739
+ version: string;
2740
+ };
2741
+ /**
2742
+ * Reports that a `load-model` request failed.
2743
+ *
2744
+ * @see Wire Protocol Reference wiki page
2745
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2746
+ */
2747
+ type ModelLoadFailedMessage = {
2748
+ type: "model-load-failed"; /** correlationId of the originating `load-model` message. */
2749
+ correlationId: string;
2750
+ documentType: string;
2751
+ version: string;
2752
+ error: ErrorInfo;
2753
+ };
2754
+ /**
2755
+ * Forwarded log line from the worker. `args` is constrained to
2756
+ * {@link SanitizedArg} so callers cannot accidentally ship non-clonable
2757
+ * values across the boundary.
2758
+ *
2759
+ * The sanitizer in `./sanitize.ts` enforces the {@link SanitizedArg}
2760
+ * invariant on the producer side before each message is posted.
2761
+ *
2762
+ * @see Wire Protocol Reference wiki page
2763
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2764
+ */
2765
+ type LogMessage = {
2766
+ type: "log";
2767
+ level: "debug" | "info" | "warn" | "error";
2768
+ message: string;
2769
+ args: SanitizedArg[]; /** Epoch milliseconds at which the worker generated the log line. */
2770
+ timestamp: number;
2771
+ };
2772
+ /**
2773
+ * Periodic liveness signal. Included now to unblock Phase-3 scaffolding;
2774
+ * the wiki marks heartbeats as a Phase-5 future extension.
2775
+ *
2776
+ * @see Wire Protocol Reference wiki page
2777
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2778
+ */
2779
+ type HeartbeatMessage = {
2780
+ type: "heartbeat";
2781
+ workerId: string; /** Epoch milliseconds the worker generated the heartbeat. */
2782
+ timestamp: number; /** Optional snapshot of in-flight job correlation ids. */
2783
+ inFlightCorrelationIds?: string[];
2784
+ };
2785
+ /**
2786
+ * Periodic counters / gauges the worker reports for observability.
2787
+ *
2788
+ * @see Wire Protocol Reference wiki page
2789
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2790
+ */
2791
+ type MetricsMessage = {
2792
+ type: "metrics";
2793
+ workerId: string; /** Epoch milliseconds the worker generated the metrics snapshot. */
2794
+ timestamp: number;
2795
+ counters: {
2796
+ [name: string]: number;
2797
+ };
2798
+ gauges: {
2799
+ [name: string]: number;
2800
+ };
2801
+ };
2802
+ /**
2803
+ * Snapshot of one worker pool's acquire-wait samples and pool-stat counters,
2804
+ * forwarded periodically so the host can re-record into the shared
2805
+ * pg.Pool histogram and observable gauges. The worker owns the real
2806
+ * pg.Pool; the host's {@link PoolInstrumentation} is a forwarder driven
2807
+ * by these messages.
2808
+ */
2809
+ type PoolAcquireSamplesMessage = {
2810
+ type: "pool-acquire-samples";
2811
+ workerId: string; /** Stable identifier matching the host-side instrumentation name (e.g. "worker-0"). */
2812
+ poolName: string; /** Epoch milliseconds the worker generated the batch. */
2813
+ timestamp: number; /** Acquire-wait durations (ms) accumulated since the previous batch. */
2814
+ durations: number[]; /** Most recent pg.Pool counter snapshot at batch send time. */
2815
+ size: number;
2816
+ idle: number;
2817
+ waiting: number;
2818
+ };
2819
+ /**
2820
+ * Union of all messages a worker may send to the parent.
2821
+ *
2822
+ * @see Wire Protocol Reference wiki page
2823
+ * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2824
+ */
2825
+ type WorkerMessage = ReadyMessage | ResultMessage | ModelLoadedMessage | ModelLoadFailedMessage | LogMessage | HeartbeatMessage | MetricsMessage | PoolAcquireSamplesMessage;
2826
+ //#endregion
2827
+ //#region src/core/model-sources.d.ts
2828
+ /** An importable file holding one or more document-model exports. */
2829
+ type FileModelSource = {
2830
+ filePath: string;
2831
+ exportName?: string;
2832
+ };
2833
+ /** An importable package specifier holding one or more document-model exports. */
2834
+ type PackageModelSource = {
2835
+ packageName: string;
2836
+ subpath?: string;
2837
+ exportName?: string;
2838
+ };
2839
+ /**
2840
+ * A source of document models: a live module, an importable file, or an
2841
+ * importable package. File and package sources can cross a worker-thread
2842
+ * boundary (workers re-import them); a live module cannot.
2843
+ */
2844
+ type DocumentModelSource = DocumentModelModule<any> | FileModelSource | PackageModelSource;
2845
+ //#endregion
2846
+ //#region src/registry/interfaces.d.ts
2847
+ type RegistrationResult<T> = {
2848
+ status: "success";
2849
+ item: T;
2850
+ } | {
2851
+ status: "error";
2852
+ item: T;
2853
+ error: Error;
2854
+ };
2855
+ /**
2856
+ * Loader that asynchronously resolves a document type to a
2857
+ * {@link DocumentModelSource}. Used by the queue to gate CREATE_DOCUMENT
2858
+ * jobs until the required model is available in the registry.
2859
+ *
2860
+ * Return an importable source ({ filePath } or { packageName }) whenever
2861
+ * possible: the resolver registers the resolved models on the host registry
2862
+ * and broadcasts importable sources to executor workers. A live
2863
+ * DocumentModelModule is also valid but host-only — it cannot cross a
2864
+ * worker-thread boundary, so worker pools will not receive it.
2865
+ */
2866
+ interface IDocumentModelLoader {
2867
+ load(documentType: string): Promise<DocumentModelSource>;
2868
+ }
2869
+ /**
2870
+ * Registry for managing document model modules.
2871
+ * Provides centralized access to document models' reducers, utils, and specifications.
2872
+ * Supports version-aware module storage and upgrade manifest management.
2873
+ */
2874
+ interface IDocumentModelRegistry {
2875
+ /**
2876
+ * Register multiple modules at once.
2877
+ * Modules without a version field default to version 1.
2878
+ * Invalid or duplicate modules are skipped without breaking registration of the remaining modules.
2879
+ *
2880
+ * @param modules Document model modules to register
2881
+ * @returns Array of results, one per module, indicating success or failure
2882
+ */
2883
+ registerModules(...modules: DocumentModelModule<any>[]): RegistrationResult<DocumentModelModule<any>>[];
2884
+ /**
2885
+ * Unregister all versions of the specified document types.
2886
+ *
2887
+ * @param documentTypes The document types to unregister
2888
+ * @returns true if all modules were unregistered, false if any were not found
2889
+ */
2890
+ unregisterModules(...documentTypes: string[]): boolean;
2891
+ /**
2892
+ * Get a specific document model module by document type and optional version.
2893
+ * If version is not specified, returns the latest version.
2894
+ *
2895
+ * @param documentType The document type identifier
2896
+ * @param version Optional version number to retrieve
2897
+ * @returns The document model module
2898
+ * @throws ModuleNotFoundError if the document type or version is not registered
2899
+ */
2900
+ getModule(documentType: string, version?: number): DocumentModelModule<any>;
2901
+ /**
2902
+ * Get all registered document model modules.
2903
+ *
2904
+ * @returns Array of all registered modules
2905
+ */
2906
+ getAllModules(): DocumentModelModule<any>[];
2907
+ /**
2908
+ * Clear all registered modules and upgrade manifests.
2909
+ */
2910
+ clear(): void;
2911
+ /**
2912
+ * Get all supported versions for a document type, sorted in ascending order.
2913
+ *
2914
+ * @param documentType The document type identifier
2915
+ * @returns Array of version numbers sorted ascending
2916
+ * @throws ModuleNotFoundError if no modules are registered for the document type
2917
+ */
2918
+ getSupportedVersions(documentType: string): number[];
1933
2919
  /**
1934
- * Creates an empty document in a drive as a single batched operation.
1935
- * Delegates to {@link IDriveClient.addFile}.
2920
+ * Get the latest (highest) version number for a document type.
1936
2921
  *
1937
- * @deprecated Use `client.drives.addFile` instead. This method will be
1938
- * removed in a future release.
2922
+ * @param documentType The document type identifier
2923
+ * @returns The highest version number registered for this document type
2924
+ * @throws ModuleNotFoundError if no modules are registered for the document type
1939
2925
  */
1940
- createDocumentInDrive<TDocument extends PHDocument>(driveId: string, document: PHDocument, parentFolder?: string, signal?: AbortSignal): Promise<TDocument>;
2926
+ getLatestVersion(documentType: string): number;
1941
2927
  /**
1942
- * Applies a list of actions to a document and waits for completion
2928
+ * Register upgrade manifests that define upgrade paths between versions.
2929
+ * Invalid or duplicate manifests are skipped without breaking registration of the remaining manifests.
2930
+ *
2931
+ * @param manifests Upgrade manifests to register
2932
+ * @returns Array of results, one per manifest, indicating success or failure
1943
2933
  */
1944
- execute<TDocument extends PHDocument>(documentIdentifier: string, branch: string, actions: Action[], signal?: AbortSignal): Promise<TDocument>;
2934
+ registerUpgradeManifests(...manifests: UpgradeManifest<readonly number[]>[]): RegistrationResult<UpgradeManifest<readonly number[]>>[];
1945
2935
  /**
1946
- * Submits a list of actions to a document
2936
+ * Unregister upgrade manifests for the specified document types.
2937
+ * @param documentTypes The document types whose upgrade manifests should be unregistered
2938
+ * @returns true if all modules were unregistered, false if any were not found
2939
+ **/
2940
+ unregisterUpgradeManifests(...documentTypes: string[]): boolean;
2941
+ /**
2942
+ * Get the upgrade manifest for a document type.
2943
+ *
2944
+ * @param documentType The document type identifier
2945
+ * @returns The upgrade manifest
2946
+ * @throws ManifestNotFoundError if no manifest is registered for the document type
1947
2947
  */
1948
- executeAsync(documentIdentifier: string, branch: string, actions: Action[], signal?: AbortSignal): Promise<JobInfo>;
1949
- executeBatch(request: BatchExecutionRequest, signal?: AbortSignal): Promise<BatchExecutionResult>;
2948
+ getUpgradeManifest(documentType: string): UpgradeManifest<readonly number[]>;
1950
2949
  /**
1951
- * Renames a document and waits for completion
2950
+ * Compute the upgrade path from one version to another.
2951
+ * Returns the sequence of upgrade transitions needed.
2952
+ *
2953
+ * @param documentType The document type identifier
2954
+ * @param fromVersion The starting version
2955
+ * @param toVersion The target version
2956
+ * @returns Array of upgrade transitions in order
2957
+ * @throws DowngradeNotSupportedError if toVersion is less than fromVersion
2958
+ * @throws ManifestNotFoundError if no upgrade manifest is registered
2959
+ * @throws MissingUpgradeTransitionError if any transition in the path is missing
1952
2960
  */
1953
- rename(documentIdentifier: string, name: string, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
2961
+ computeUpgradePath(documentType: string, fromVersion: number, toVersion: number): UpgradeTransition[];
1954
2962
  /**
1955
- * Updates the preferred editor recorded in the document header meta.
1956
- * Pass `null` to clear it.
2963
+ * Get the upgrade reducer for a single-step version transition.
2964
+ *
2965
+ * @param documentType The document type identifier
2966
+ * @param fromVersion The starting version
2967
+ * @param toVersion The target version (must be fromVersion + 1)
2968
+ * @returns The upgrade reducer function
2969
+ * @throws InvalidUpgradeStepError if toVersion is not fromVersion + 1
2970
+ * @throws ManifestNotFoundError if no upgrade manifest is registered
2971
+ * @throws MissingUpgradeTransitionError if the transition is not found
1957
2972
  */
1958
- setPreferredEditor(documentIdentifier: string, preferredEditor: string | null, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
2973
+ getUpgradeReducer(documentType: string, fromVersion: number, toVersion: number): UpgradeReducer<any, any>;
2974
+ }
2975
+ //#endregion
2976
+ //#region src/cache/buffer/ring-buffer.d.ts
2977
+ /**
2978
+ * RingBuffer is a generic circular buffer implementation that stores a fixed number
2979
+ * of items. When the buffer is full, new items overwrite the oldest items.
2980
+ *
2981
+ * This implementation maintains O(1) time complexity for push operations and provides
2982
+ * items in chronological order (oldest to newest) via getAll().
2983
+ *
2984
+ * @template T - The type of items stored in the buffer
2985
+ */
2986
+ declare class RingBuffer<T> {
2987
+ private buffer;
2988
+ private head;
2989
+ private size;
2990
+ private capacity;
2991
+ constructor(capacity: number);
1959
2992
  /**
1960
- * Adds multiple documents as children to another and waits for completion
2993
+ * Adds an item to the buffer. If the buffer is full, overwrites the oldest item.
2994
+ *
2995
+ * @param item - The item to add
1961
2996
  */
1962
- addRelationship(sourceIdentifier: string, targetIdentifier: string, relationshipType: string, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
2997
+ push(item: T): void;
1963
2998
  /**
1964
- * Removes a relationship between two documents and waits for completion.
2999
+ * Returns all items in the buffer in chronological order (oldest to newest).
3000
+ *
3001
+ * @returns Array of items in insertion order
1965
3002
  */
1966
- removeRelationship(sourceIdentifier: string, targetIdentifier: string, relationshipType: string, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
3003
+ getAll(): T[];
1967
3004
  /**
1968
- * Moves a relationship from one source document to another and waits for completion.
3005
+ * Clears all items from the buffer.
1969
3006
  */
1970
- moveRelationship(sourceParentIdentifier: string, targetParentIdentifier: string, targetIdentifier: string, relationshipType: string, branch?: string, signal?: AbortSignal): Promise<{
1971
- source: PHDocument;
1972
- target: PHDocument;
1973
- }>;
1974
- loadBatch(request: BatchLoadRequest, signal?: AbortSignal): Promise<BatchLoadResult>;
3007
+ clear(): void;
1975
3008
  /**
1976
- * Deletes a document and waits for completion
3009
+ * Gets the current number of items in the buffer.
1977
3010
  */
1978
- deleteDocument(identifier: string, propagate?: PropagationMode, signal?: AbortSignal): Promise<void>;
3011
+ get length(): number;
3012
+ }
3013
+ //#endregion
3014
+ //#region src/cache/kysely-write-cache.d.ts
3015
+ type DocumentStream = {
3016
+ key: string;
3017
+ ringBuffer: RingBuffer<CachedSnapshot>;
3018
+ };
3019
+ /**
3020
+ * In-memory write cache with keyframe persistence for PHDocuments.
3021
+ *
3022
+ * Caches document snapshots in ring buffers with LRU eviction. On cache miss,
3023
+ * rebuilds documents from nearest keyframe or full operation history.
3024
+ *
3025
+ * **Performance Characteristics:**
3026
+ * - Cache hit: O(1) lookup in ring buffer
3027
+ * - Cold miss: O(n) where n is total operation count, or O(k) where k is operations since keyframe
3028
+ * - Warm miss: O(m) where m is operations since cached revision
3029
+ * - Eviction: O(1) for LRU tracking and removal
3030
+ *
3031
+ * **Thread Safety:**
3032
+ * Not thread-safe. Designed for single-threaded job executor environment.
3033
+ * External synchronization required for concurrent access across multiple executors.
3034
+ *
3035
+ * **Example:**
3036
+ * ```typescript
3037
+ * const cache = new KyselyWriteCache(
3038
+ * keyframeStore,
3039
+ * operationStore,
3040
+ * registry,
3041
+ * { maxDocuments: 1000, ringBufferSize: 10, keyframeInterval: 10 }
3042
+ * );
3043
+ *
3044
+ * await cache.startup();
3045
+ *
3046
+ * // Retrieve or rebuild document
3047
+ * const doc = await cache.getState(docId, docType, scope, branch, revision);
3048
+ *
3049
+ * // Cache result after job execution
3050
+ * cache.putState(docId, docType, scope, branch, newRevision, updatedDoc);
3051
+ *
3052
+ * await cache.shutdown();
3053
+ * ```
3054
+ */
3055
+ declare class KyselyWriteCache implements IWriteCache {
3056
+ private streams;
3057
+ private lruTracker;
3058
+ private keyframeStore;
3059
+ private operationStore;
3060
+ private registry;
3061
+ private config;
3062
+ constructor(keyframeStore: IKeyframeStore, operationStore: IOperationStore, registry: IDocumentModelRegistry, config: WriteCacheConfig);
3063
+ withScopedStores(operationStore: IOperationStore, keyframeStore: IKeyframeStore): KyselyWriteCache;
1979
3064
  /**
1980
- * Deletes documents and waits for completion
3065
+ * Initializes the write cache.
3066
+ * Currently a no-op as keyframe store lifecycle is managed externally.
1981
3067
  */
1982
- deleteDocuments(identifiers: string[], propagate?: PropagationMode, signal?: AbortSignal): Promise<void>;
3068
+ startup(): Promise<void>;
1983
3069
  /**
1984
- * Retrieves the status of a job
3070
+ * Shuts down the write cache.
3071
+ * Currently a no-op as keyframe store lifecycle is managed externally.
1985
3072
  */
1986
- getJobStatus(jobId: string, signal?: AbortSignal): Promise<JobInfo>;
3073
+ shutdown(): Promise<void>;
3074
+ /**
3075
+ * Retrieves document state at a specific revision from cache or rebuilds it.
3076
+ *
3077
+ * Note: this returns a _shallow_ copy of the document.
3078
+ *
3079
+ * Cache hit path: Returns cached snapshot if available (O(1))
3080
+ * Warm miss path: Rebuilds from cached base revision + incremental ops
3081
+ * Cold miss path: Rebuilds from keyframe or from scratch using all operations
3082
+ *
3083
+ * @param documentId - The document identifier
3084
+ * @param scope - The operation scope
3085
+ * @param branch - The operation branch
3086
+ * @param targetRevision - The target revision, or undefined for newest
3087
+ * @param signal - Optional abort signal to cancel the operation
3088
+ * @returns The document at the target revision
3089
+ * @throws {Error} "Operation aborted" if signal is aborted
3090
+ * @throws {ModuleNotFoundError} If document type not registered in registry
3091
+ * @throws {Error} "Failed to rebuild document" if operation store fails
3092
+ * @throws {Error} If reducer throws during operation application
3093
+ * @throws {Error} If document serialization fails
3094
+ */
3095
+ getState(documentId: string, scope: string, branch: string, targetRevision?: number, signal?: AbortSignal): Promise<PHDocument>;
3096
+ /**
3097
+ * Stores a document snapshot in the cache at a specific revision.
3098
+ *
3099
+ * The cached document is a shallow copy of the input with its operation history
3100
+ * truncated to the last operation per scope and its clipboard cleared. This keeps
3101
+ * memory use and copy costs constant regardless of operation count. Consumers of
3102
+ * getState() must not rely on the full operation history being present; the only
3103
+ * guaranteed invariant is that operations[scope].at(-1) reflects the latest
3104
+ * operation index for each scope.
3105
+ *
3106
+ * Updates LRU tracker and may evict least recently used stream if at capacity.
3107
+ * Asynchronously persists keyframes at configured intervals (fire-and-forget).
3108
+ *
3109
+ * @param documentId - The document identifier
3110
+ * @param scope - The operation scope
3111
+ * @param branch - The operation branch
3112
+ * @param revision - The revision number
3113
+ * @param document - The document to cache
3114
+ * @throws {Error} If document serialization fails
3115
+ */
3116
+ putState(documentId: string, scope: string, branch: string, revision: number, document: PHDocument, position: SnapshotPosition): void;
3117
+ private store;
3118
+ /**
3119
+ * Invalidates cached document streams.
3120
+ *
3121
+ * Supports three invalidation scopes:
3122
+ * - Document-level: invalidate(documentId) - removes all streams for document
3123
+ * - Scope-level: invalidate(documentId, scope) - removes all branches for scope
3124
+ * - Stream-level: invalidate(documentId, scope, branch) - removes specific stream
3125
+ *
3126
+ * @param documentId - The document identifier
3127
+ * @param scope - Optional scope to narrow invalidation
3128
+ * @param branch - Optional branch to narrow invalidation (requires scope)
3129
+ * @returns The number of streams evicted
3130
+ */
3131
+ invalidate(documentId: string, scope?: string, branch?: string): number;
3132
+ /**
3133
+ * Clears the entire cache, removing all cached document streams.
3134
+ * Resets LRU tracking state. This operation always succeeds.
3135
+ */
3136
+ clear(): void;
3137
+ /**
3138
+ * Retrieves a specific stream for a document. Exposed on the implementation
3139
+ * for testing, but not on the interface.
3140
+ *
3141
+ * @internal
3142
+ */
3143
+ getStream(documentId: string, scope: string, branch: string): DocumentStream | undefined;
3144
+ private findNearestKeyframe;
3145
+ /**
3146
+ * Rebuilds a scope from a keyframe or from the whole operation history.
3147
+ *
3148
+ * The document scope is always rebuilt first, because it carries the type,
3149
+ * the upgrades and the deletion marker. Its version-changing upgrades are not
3150
+ * applied there though: an upgrade reducer must see the state the requested
3151
+ * scope has reached at that upgrade's boundary, so each one is held back and
3152
+ * applied when the replay below crosses the boundary that
3153
+ * resolveModuleVersionForOp derives from it. Upgrades whose boundary lies past
3154
+ * the last replayed operation are applied at the end. Creation-time 0->N seed
3155
+ * upgrades carry the initial state, so they still apply immediately.
3156
+ */
3157
+ private coldMissRebuild;
3158
+ /**
3159
+ * Applies and removes every held-back upgrade whose target version is at or
3160
+ * below `throughVersion`, in the order the document scope recorded them.
3161
+ */
3162
+ private applyPendingUpgrades;
3163
+ /**
3164
+ * Applies the remaining held-back upgrades after the requested scope's
3165
+ * replay has finished. A head read applies them all. A positional read
3166
+ * applies only those whose boundary for this scope lies at or before the
3167
+ * target position: applying a later one would label migrated state with a
3168
+ * pre-upgrade revision, and a keyframe stored from that poisons every
3169
+ * rebuild that resumes from it. Boundaries come from the upgrade's revision
3170
+ * snapshot; an upgrade without one records no position for this scope, and
3171
+ * the replay loop not having crossed it already places it past the target.
3172
+ */
3173
+ private applyTailPendingUpgrades;
3174
+ /**
3175
+ * Applies one held-back upgrade, then re-applies the deletes the document
3176
+ * scope recorded after it so the hold-back cannot invert their order.
3177
+ */
3178
+ private applyPendingUpgrade;
3179
+ /**
3180
+ * Copies the current document revisions onto the document. Overwrites the
3181
+ * requested scope revision with the target revision, if provided.
3182
+ */
3183
+ private stampRevisions;
3184
+ /** The stored operation at `index`, or undefined if it is no longer there. */
3185
+ private operationAt;
3186
+ /**
3187
+ * Resolves which module version to use for a given operation in phase 2.
3188
+ *
3189
+ * Uses the validated-upgrade boundary rules from D7:
3190
+ * - If `input.revision` is present: op.index < revision[scope] → before the upgrade boundary
3191
+ * - Otherwise: timestamp fallback
3192
+ * - Falls back to final module version when neither is decidable
3193
+ */
3194
+ private resolveModuleVersionForOp;
3195
+ private warmMissRebuild;
3196
+ private findNearestOlderSnapshot;
3197
+ private makeStreamKey;
3198
+ private getOrCreateStream;
3199
+ private isKeyframeRevision;
3200
+ }
3201
+ //#endregion
3202
+ //#region src/storage/kysely/store.d.ts
3203
+ declare class KyselyOperationStore implements IOperationStore {
3204
+ private db;
3205
+ private trx?;
3206
+ constructor(db: Kysely<Database$1>);
3207
+ private get queryExecutor();
3208
+ withTransaction(trx: Transaction<Database$1>): KyselyOperationStore;
3209
+ apply(documentId: string, documentType: string, scope: string, branch: string, revision: number, fn: (txn: AtomicTxn) => void | Promise<void>, signal?: AbortSignal, condition?: AppendCondition): Promise<Operation[]>;
3210
+ private resolveUniqueConstraint;
3211
+ private executeApply;
1987
3212
  /**
1988
- * Waits for a job to complete
3213
+ * Locks the written stream and every read-set stream, in sorted key order
3214
+ * so that overlapping concurrent appends serialize rather than deadlock.
3215
+ * The locks are still taken one row at a time, so the query preserves that
3216
+ * order. It must stay separate from the guarded insert, which would
3217
+ * otherwise read a snapshot taken before the locks were held.
1989
3218
  */
1990
- waitForJob(jobId: string | JobInfo, signal?: AbortSignal): Promise<JobInfo>;
3219
+ private acquireStreamLocks;
1991
3220
  /**
1992
- * Subscribes to changes for documents matching specified filters
3221
+ * Inserts the staged operations with the condition compiled in as a WHERE
3222
+ * NOT EXISTS guard, making the check and the append one statement. Returns
3223
+ * the rows inserted; zero means the guard failed and nothing was written.
1993
3224
  */
1994
- subscribe(search: SearchFilter, callback: (event: DocumentChangeEvent) => void, view?: ViewFilter): () => void;
1995
- private removeAllIncomingRelationships;
3225
+ private insertGuarded;
3226
+ private findIdempotentReplay;
3227
+ getSince(documentId: string, scope: string, branch: string, revision: number, filter?: OperationFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<Operation>>;
3228
+ getSinceId(id: number, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<OperationWithContext$1>>;
3229
+ getConflicting(documentId: string, scope: string, branch: string, minTimestamp: string, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<Operation>>;
3230
+ getRevisions(documentId: string, branch: string, signal?: AbortSignal): Promise<DocumentRevisions>;
3231
+ getStreamLatestTimestamp(documentId: string, scope: string, branch: string, signal?: AbortSignal): Promise<string | undefined>;
3232
+ private rowToOperation;
3233
+ private rowToOperationWithContext;
3234
+ }
3235
+ //#endregion
3236
+ //#region src/storage/kysely/keyframe-store.d.ts
3237
+ declare class KyselyKeyframeStore implements IKeyframeStore {
3238
+ private db;
3239
+ private trx?;
3240
+ constructor(db: Kysely<Database$1>);
3241
+ private get queryExecutor();
3242
+ withTransaction(trx: Transaction<Database$1>): KyselyKeyframeStore;
3243
+ putKeyframe(documentId: string, scope: string, branch: string, revision: number, document: PHDocument, signal?: AbortSignal): Promise<void>;
3244
+ findNearestKeyframe(documentId: string, scope: string, branch: string, targetRevision: number, signal?: AbortSignal): Promise<{
3245
+ revision: number;
3246
+ document: PHDocument;
3247
+ } | undefined>;
3248
+ listKeyframes(documentId: string, scope?: string, branch?: string, signal?: AbortSignal): Promise<Array<{
3249
+ scope: string;
3250
+ branch: string;
3251
+ revision: number;
3252
+ document: PHDocument;
3253
+ }>>;
3254
+ deleteKeyframes(documentId: string, scope?: string, branch?: string, signal?: AbortSignal): Promise<number>;
3255
+ }
3256
+ //#endregion
3257
+ //#region src/executor/execution-scope.d.ts
3258
+ interface ExecutionStores {
3259
+ operationStore: IOperationStore;
3260
+ operationIndex: IOperationIndex;
3261
+ writeCache: IWriteCache;
3262
+ documentMetaCache: IDocumentMetaCache;
3263
+ collectionMembershipCache: ICollectionMembershipCache;
3264
+ }
3265
+ interface IExecutionScope {
3266
+ run<T>(fn: (stores: ExecutionStores) => Promise<T>, signal?: AbortSignal): Promise<T>;
1996
3267
  }
1997
3268
  //#endregion
1998
3269
  //#region src/executor/types.d.ts
@@ -2013,11 +3284,43 @@ type JobResult = {
2013
3284
  duration?: number; /** Any additional metadata from the execution */
2014
3285
  metadata?: Record<string, any>;
2015
3286
  };
3287
+ /**
3288
+ * Enforcement the reactor performs, each off by default.
3289
+ *
3290
+ * An evaluation made while replaying is part of the document's history, so two
3291
+ * reactors that share documents and disagree on these diverge. A flag is turned
3292
+ * on for a set of reactors that sync with each other, not for one node.
3293
+ */
3294
+ type ReactorFeatureFlags = {
3295
+ /**
3296
+ * Decide whether an operation may be admitted by building a decision model
3297
+ * over the document stream, rather than reading the deleted flag from the
3298
+ * document meta cache. Deletion then takes effect from the deleting
3299
+ * operation's position rather than for the whole document.
3300
+ */
3301
+ documentDecisions: boolean;
3302
+ /**
3303
+ * Evaluate the auth policy by reading the auth scope as a second projection.
3304
+ * Requires documentDecisions.
3305
+ */
3306
+ authEnforcement: boolean;
3307
+ /**
3308
+ * Match { group } principals by folding the referenced PHGroup documents as
3309
+ * derived projections. Requires authEnforcement.
3310
+ */
3311
+ authGroups: boolean;
3312
+ /**
3313
+ * Evaluate `where` clauses and { match } principals against the executing
3314
+ * scope's state, the subject, and the action input. Requires authGroups.
3315
+ */
3316
+ authConditions: boolean;
3317
+ };
2016
3318
  /**
2017
3319
  * Configuration options for the job executor
2018
3320
  */
2019
3321
  type JobExecutorConfig = {
2020
- /** Maximum number of conflicting operations to skip when reshuffling. */maxSkipThreshold?: number; /** Maximum number of concurrent jobs to execute */
3322
+ /** Feature flags; anything unset is off. */featureFlags?: Partial<ReactorFeatureFlags>; /** Maximum number of conflicting operations to skip when reshuffling. */
3323
+ maxSkipThreshold?: number; /** Maximum number of concurrent jobs to execute */
2021
3324
  maxConcurrency?: number; /** Maximum time in milliseconds a job can run before being considered timed out */
2022
3325
  jobTimeoutMs?: number; /** Base delay in milliseconds for exponential backoff retries */
2023
3326
  retryBaseDelayMs?: number; /** Maximum delay in milliseconds for exponential backoff retries */
@@ -2074,356 +3377,430 @@ type ExecutorStoppedEvent = {
2074
3377
  * Status information for the job executor manager
2075
3378
  */
2076
3379
  type ExecutorManagerStatus = {
2077
- /** Whether the manager is currently running */isRunning: boolean; /** Number of executor instances managed */
2078
- numExecutors: number; /** Number of jobs currently being processed */
2079
- activeJobs: number; /** Total number of jobs processed since start */
2080
- totalJobsProcessed: number;
2081
- };
2082
- //#endregion
2083
- //#region src/executor/worker/protocol.d.ts
2084
- /**
2085
- * A JSON-clonable value safe to send across the worker IPC boundary.
2086
- *
2087
- * The shape mirrors the structured-clone subset used by the parent's
2088
- * sanitizer: primitives, arrays, plain objects, plus the explicit
2089
- * {@link ErrorInfo} shape for marshalled Errors.
2090
- *
2091
- * @see Wire Protocol Reference wiki page
2092
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2093
- */
2094
- type SanitizedArg = null | boolean | number | string | ErrorInfo | SanitizedArg[] | {
2095
- [key: string]: SanitizedArg;
2096
- };
2097
- /**
2098
- * Structured representation of an Error for IPC transport.
2099
- *
2100
- * Class instances cannot be structured-cloned across worker boundaries,
2101
- * so Errors are flattened into this shape on the worker side and
2102
- * reconstructed on the parent side.
2103
- *
2104
- * @see Wire Protocol Reference wiki page
2105
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2106
- */
2107
- type ErrorInfo = {
2108
- name: string;
2109
- message: string;
2110
- stack?: string;
2111
- cause?: ErrorInfo;
2112
- };
2113
- /**
2114
- * Reference to a module that the worker should `import()` at runtime,
2115
- * along with the named export to pluck out as the factory.
2116
- *
2117
- * Exactly one of `packageName` or `filePath` is provided.
2118
- *
2119
- * @see Wire Protocol Reference wiki page
2120
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2121
- */
2122
- type ModuleRef = {
2123
- /** Bare-specifier package name (resolved by the worker's module loader). */packageName: string; /** Named export within the module to invoke as the factory. */
2124
- exportName: string;
2125
- } | {
2126
- /** Absolute or worker-resolvable file path to import. */filePath: string; /** Named export within the module to invoke as the factory. */
2127
- exportName: string;
2128
- };
2129
- /**
2130
- * Factory specification shared by the signature verifier and document
2131
- * model spec channels. The worker imports `module.exportName` and invokes
2132
- * it with `initArgs` to obtain the actual instance.
2133
- *
2134
- * `initArgs` must be JSON-clonable.
2135
- *
2136
- * @see Wire Protocol Reference wiki page
2137
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2138
- */
2139
- type FactorySpec = {
2140
- module: ModuleRef;
2141
- initArgs?: SanitizedArg;
2142
- };
2143
- /**
2144
- * Factory spec for the signature verifier the worker should instantiate.
2145
- *
2146
- * Structurally identical to {@link FactorySpec}; the alias exists so call
2147
- * sites read intent-fully.
2148
- *
2149
- * @see Wire Protocol Reference wiki page
2150
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2151
- */
2152
- type SignatureVerifierSpec = FactorySpec;
2153
- /**
2154
- * Factory spec for a document model module the worker should instantiate.
2155
- *
2156
- * Structurally identical to {@link FactorySpec}; the alias exists so call
2157
- * sites read intent-fully.
2158
- *
2159
- * @see Wire Protocol Reference wiki page
2160
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2161
- */
2162
- type DocumentModelSpec = FactorySpec;
2163
- /**
2164
- * One entry in the document model manifest the worker materializes on
2165
- * startup (or extends lazily via `load-model`).
2166
- *
2167
- * @see Wire Protocol Reference wiki page
2168
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2169
- */
2170
- type ModelManifestEntry = {
2171
- /** Document type identifier (e.g. "ph/account"). */documentType: string; /** Document model version this entry registers. */
2172
- version: string; /** Factory spec the worker imports and invokes to obtain the model. */
2173
- spec: DocumentModelSpec;
2174
- };
2175
- /**
2176
- * JSON-clonable Postgres connection info passed to the worker so it can
2177
- * open its own pool. Storage-specific wiring may extend this shape in
2178
- * later phases.
2179
- *
2180
- * @see Wire Protocol Reference wiki page
2181
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2182
- */
2183
- type DbConfig = {
2184
- host: string;
2185
- port: number;
2186
- database: string;
2187
- user: string;
2188
- password: string;
2189
- ssl?: boolean;
2190
- applicationName?: string;
2191
- poolSize?: number;
2192
- /**
2193
- * Maximum time (ms) a caller will wait to acquire a connection from the
2194
- * pool before pg.Pool throws. When omitted, pg defaults to 0 (unlimited
2195
- * wait), which hides acquire-starvation as silent latency.
2196
- */
2197
- connectionTimeoutMillis?: number;
2198
- /**
2199
- * How long (ms) an idle connection stays open before pg closes it. When
2200
- * omitted, pg defaults to 10000.
2201
- */
2202
- idleTimeoutMillis?: number;
2203
- };
2204
- /**
2205
- * Configuration for the executor worker pool.
2206
- *
2207
- * Mirrors the `workerPool` sub-config on {@link JobExecutorConfig};
2208
- * a later card wires this into the executor config.
2209
- *
2210
- * @see Wire Protocol Reference wiki page
2211
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2212
- */
2213
- type WorkerPoolConfig = {
2214
- /** Whether the worker pool is active; when false the executor runs in-process. */enabled: boolean; /** Number of worker instances to spawn. */
2215
- numWorkers: number; /** Worker isolation mode. */
2216
- workerType: "thread" | "process"; /** Optional heartbeat interval in milliseconds. */
2217
- heartbeatMs?: number; /** Optional per-worker Postgres pool size override. */
2218
- workerPgPoolSize?: number;
2219
- };
2220
- /**
2221
- * Payload the worker reports back when a job's write phase is complete.
2222
- *
2223
- * Parent fills `collectionMemberships` at emission time, so it is
2224
- * intentionally absent from the worker -> parent message.
2225
- *
2226
- * @see Wire Protocol Reference wiki page
2227
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2228
- */
2229
- type JobWriteReadyPayload = {
2230
- operations: OperationWithContext$1[];
2231
- jobMeta: JobMeta;
2232
- };
2233
- /**
2234
- * Initializes a freshly spawned worker with the configuration and
2235
- * factories it needs to start executing jobs.
2236
- *
2237
- * @see Wire Protocol Reference wiki page
2238
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2239
- */
2240
- type InitMessage = {
2241
- type: "init";
2242
- correlationId: string;
2243
- workerId: string;
2244
- poolConfig: WorkerPoolConfig;
2245
- db: DbConfig; /** Omitted = the worker performs no executor-side signature verification. */
2246
- signatureVerifier?: SignatureVerifierSpec;
2247
- models: ModelManifestEntry[];
2248
- };
2249
- /**
2250
- * Dispatches a job to the worker for execution.
2251
- *
2252
- * @see Wire Protocol Reference wiki page
2253
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2254
- */
2255
- type ExecuteMessage = {
2256
- type: "execute";
2257
- correlationId: string;
2258
- job: Job;
3380
+ /** Whether the manager is currently running */isRunning: boolean; /** Number of executor instances managed */
3381
+ numExecutors: number; /** Number of jobs currently being processed */
3382
+ activeJobs: number; /** Total number of jobs processed since start */
3383
+ totalJobsProcessed: number;
3384
+ };
3385
+ //#endregion
3386
+ //#region src/decision/document-decision-model.d.ts
3387
+ /** What the document decision model reads: the target's document scope. */
3388
+ type DocumentDecisionModel = {
3389
+ document: PHDocumentState;
2259
3390
  };
2260
3391
  /**
2261
- * Requests cancellation of an in-flight job.
2262
- *
2263
- * @see Wire Protocol Reference wiki page
2264
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3392
+ * The simplest decision model: one projection over the document scope, which
3393
+ * rejects on a deleted document.
2265
3394
  */
2266
- type AbortMessage = {
2267
- type: "abort";
2268
- correlationId: string; /** correlationId of the `execute` message being aborted. */
2269
- targetCorrelationId: string;
2270
- reason?: string;
2271
- };
3395
+ declare function documentDecisionModel(target: DecisionTarget): DecisionModel<DocumentDecisionModel>;
3396
+ //#endregion
3397
+ //#region src/decision/registered-model.d.ts
2272
3398
  /**
2273
- * Asks the worker to drain in-flight work and exit.
2274
- *
2275
- * @see Wire Protocol Reference wiki page
2276
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3399
+ * A model this reactor can register. Every one carries the document projection,
3400
+ * because admission reads the version and the deletion timestamp off it; a model
3401
+ * with more projections than that is still assignable here.
2277
3402
  */
2278
- type ShutdownMessage = {
2279
- type: "shutdown";
2280
- correlationId: string; /** Optional grace period before the parent force-terminates the worker. */
2281
- graceMs?: number;
3403
+ type RegisteredDecisionModel = (target: DecisionTarget) => DecisionModel<DocumentDecisionModel>;
3404
+ /** What admission needs out of a model built at the stream heads. */
3405
+ type AdmissionDecision = {
3406
+ evaluation: Evaluation;
3407
+ appendCondition: AppendCondition;
3408
+ documentVersion: number;
3409
+ deletedAtUtcIso: string | null;
2282
3410
  };
2283
3411
  /**
2284
- * Lazily registers an additional document model on a running worker.
2285
- *
2286
- * @see Wire Protocol Reference wiki page
2287
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3412
+ * What decideAtHead resolves a condition context from: the action's input,
3413
+ * with the executing scope's state read at the head. Supplied only while
3414
+ * authConditions is on.
2288
3415
  */
2289
- type LoadModelMessage = {
2290
- type: "load-model";
2291
- correlationId: string;
2292
- model: ModelManifestEntry;
3416
+ type AdmissionConditions = {
3417
+ actionInput?: unknown;
2293
3418
  };
2294
3419
  /**
2295
- * Union of all messages the parent may send to a worker.
3420
+ * Builds the model at the stream heads and decides one request against it. The
3421
+ * append condition it returns is the read-set the store enforces at write time.
2296
3422
  *
2297
- * @see Wire Protocol Reference wiki page
2298
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3423
+ * With `conditions` supplied, the executing scope's state is read at the head
3424
+ * for `doc.<scope>.*` paths. That read carries no append-condition entry of
3425
+ * its own: the written stream's expected-revision check already refuses a
3426
+ * write whose scope grew between the read and the append.
2299
3427
  */
2300
- type ParentMessage = InitMessage | ExecuteMessage | AbortMessage | ShutdownMessage | LoadModelMessage;
3428
+ declare function decideAtHead(model: RegisteredDecisionModel, cache: IWriteCache, target: DecisionTarget, subject: AuthSubject, request: AuthRequest, signal?: AbortSignal, conditions?: AdmissionConditions): Promise<AdmissionDecision>;
2301
3429
  /**
2302
- * Announces that the worker has finished `init` and is ready to accept
2303
- * `execute` messages.
2304
- *
2305
- * @see Wire Protocol Reference wiki page
2306
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3430
+ * The model this reactor enforces. With `authEnforcement` off the auth scope is
3431
+ * absent from every append condition and no load walks it; with `authGroups`
3432
+ * on, the group documents the grant list names join the read-set and the
3433
+ * registry supplies the reducer that folds them.
2307
3434
  */
2308
- type ReadyMessage = {
2309
- type: "ready"; /** correlationId of the originating `init` message. */
2310
- correlationId: string;
2311
- workerId: string;
2312
- };
3435
+ declare function selectDecisionModel(flags: ReactorFeatureFlags, registry: IDocumentModelRegistry): RegisteredDecisionModel;
3436
+ //#endregion
3437
+ //#region src/decision/read-gate.d.ts
2313
3438
  /**
2314
- * Final result for an `execute` job.
2315
- *
2316
- * On success, `writeReady` carries the operations and job meta the
2317
- * parent needs to emit `JOB_WRITE_READY`. On failure, `error` is set
2318
- * and `result.success` is false.
2319
- *
2320
- * @see Wire Protocol Reference wiki page
2321
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3439
+ * Scopes every holder of a document may read, whatever the grants say. Denying
3440
+ * the policy itself would let a replica sync a document without it, read the
3441
+ * auth scope as uninitialized, and allow every operation it holds, so replicas
3442
+ * would diverge permanently. The document scope carries the metadata the same
3443
+ * argument covers. Grants gate domain-scope reads only.
2322
3444
  */
2323
- type ResultMessage = {
2324
- type: "result"; /** correlationId of the originating `execute` message. */
2325
- correlationId: string;
2326
- result: JobResult;
2327
- writeReady?: JobWriteReadyPayload;
2328
- error?: ErrorInfo;
2329
- };
3445
+ declare const ALWAYS_READABLE_SCOPES: ReadonlySet<string>;
3446
+ /** Whether a subject may read each scope of one document. */
3447
+ interface IReadGate {
3448
+ /**
3449
+ * Resolves, for one document, which of its scopes the subject may read.
3450
+ *
3451
+ * The predicate is resolved up front rather than asked per scope so that the
3452
+ * filtering itself stays synchronous, and so that a model backing the answer
3453
+ * is built once per document instead of once per scope.
3454
+ */
3455
+ scopePredicate(document: PHDocument, subject: AuthSubject, branch: string, signal?: AbortSignal): Promise<(scope: string) => boolean>;
3456
+ }
2330
3457
  /**
2331
- * Acknowledges that a `load-model` request succeeded.
2332
- *
2333
- * @see Wire Protocol Reference wiki page
2334
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3458
+ * The model reads enforce. Below `authEnforcement` there is no model to
3459
+ * enforce: the document-only model ignores the auth scope entirely, so reading
3460
+ * through it would serve every domain scope of a policied document to anyone.
3461
+ * Undefined therefore means "evaluate the policy alone", which is what the read
3462
+ * surface did before the model existed.
2335
3463
  */
2336
- type ModelLoadedMessage = {
2337
- type: "model-loaded"; /** correlationId of the originating `load-model` message. */
2338
- correlationId: string;
2339
- documentType: string;
2340
- version: string;
2341
- };
3464
+ declare function readDecisionModel(flags: ReactorFeatureFlags, registry: IDocumentModelRegistry): RegisteredDecisionModel | undefined;
2342
3465
  /**
2343
- * Reports that a `load-model` request failed.
2344
- *
2345
- * @see Wire Protocol Reference wiki page
2346
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3466
+ * Evaluates the policy on its own, with no groups map and no condition context.
3467
+ * A `{ group }` or conditional grant therefore never applies: an allow that
3468
+ * does not apply withholds access, so this cannot widen a policy, but a policy
3469
+ * relying on a conditional deny is weaker here than it is written.
2347
3470
  */
2348
- type ModelLoadFailedMessage = {
2349
- type: "model-load-failed"; /** correlationId of the originating `load-model` message. */
2350
- correlationId: string;
2351
- documentType: string;
2352
- version: string;
2353
- error: ErrorInfo;
2354
- };
3471
+ declare class BareReadGate implements IReadGate {
3472
+ scopePredicate(document: PHDocument, subject: AuthSubject): Promise<(scope: string) => boolean>;
3473
+ }
2355
3474
  /**
2356
- * Forwarded log line from the worker. `args` is constrained to
2357
- * {@link SanitizedArg} so callers cannot accidentally ship non-clonable
2358
- * values across the boundary.
3475
+ * Answers a stream read from the document already fetched, and anything else
3476
+ * through the read side.
2359
3477
  *
2360
- * The sanitizer in `./sanitize.ts` enforces the {@link SanitizedArg}
2361
- * invariant on the producer side before each message is posted.
2362
- *
2363
- * @see Wire Protocol Reference wiki page
2364
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3478
+ * The seed is why routing reads through a decision model costs no extra I/O for
3479
+ * the document being read: its `document` and `auth` scopes are the two static
3480
+ * projections, and the caller has both in hand. Only a group stream the grant
3481
+ * list names is fetched.
2365
3482
  */
2366
- type LogMessage = {
2367
- type: "log";
2368
- level: "debug" | "info" | "warn" | "error";
2369
- message: string;
2370
- args: SanitizedArg[]; /** Epoch milliseconds at which the worker generated the log line. */
2371
- timestamp: number;
2372
- };
3483
+ declare class SeededStateReader implements IStreamStateReader {
3484
+ private readonly documentView;
3485
+ private readonly seed;
3486
+ private readonly branch;
3487
+ constructor(documentView: IDocumentView, seed: PHDocument, branch: string);
3488
+ /**
3489
+ * A stream this replica does not hold has to reach buildDecisionModel as the
3490
+ * absence it recognises, or the whole read fails instead of leaving the group
3491
+ * out of the model, where its principal does not match and the policy fails
3492
+ * closed. The read side reports absence as a plain Error, so the absence is
3493
+ * confirmed rather than inferred from the message: a transient failure must
3494
+ * surface, not silently deny.
3495
+ */
3496
+ getState(documentId: string, scope: string, branch: string, targetRevision?: number, signal?: AbortSignal): Promise<PHDocument>;
3497
+ private assertAbsent;
3498
+ }
2373
3499
  /**
2374
- * Periodic liveness signal. Included now to unblock Phase-3 scaffolding;
2375
- * the wiki marks heartbeats as a Phase-5 future extension.
3500
+ * Evaluates a read against the registered decision model, built at the stream
3501
+ * heads. This is what makes `{ group }` principals and conditional grants apply
3502
+ * to a read: the model supplies the groups map and the scope's own state, the
3503
+ * same two things admission supplies.
2376
3504
  *
2377
- * @see Wire Protocol Reference wiki page
2378
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
2379
- */
2380
- type HeartbeatMessage = {
2381
- type: "heartbeat";
2382
- workerId: string; /** Epoch milliseconds the worker generated the heartbeat. */
2383
- timestamp: number; /** Optional snapshot of in-flight job correlation ids. */
2384
- inFlightCorrelationIds?: string[];
2385
- };
2386
- /**
2387
- * Periodic counters / gauges the worker reports for observability.
3505
+ * A read has no action, so a condition on `action.input.*` never holds for one.
2388
3506
  *
2389
- * @see Wire Protocol Reference wiki page
2390
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3507
+ * State is read through the read side rather than the write cache. The write
3508
+ * cache is invalidated by whichever process runs the executor, so a reactor
3509
+ * running its executors in worker processes would answer reads in the parent
3510
+ * from state no commit ever invalidates.
2391
3511
  */
2392
- type MetricsMessage = {
2393
- type: "metrics";
2394
- workerId: string; /** Epoch milliseconds the worker generated the metrics snapshot. */
2395
- timestamp: number;
2396
- counters: {
2397
- [name: string]: number;
2398
- };
2399
- gauges: {
2400
- [name: string]: number;
2401
- };
2402
- };
3512
+ declare class ModelReadGate implements IReadGate {
3513
+ private readonly model;
3514
+ private readonly documentView;
3515
+ /**
3516
+ * Whether a group a policy names is served to that policy's audience. Only
3517
+ * meaningful with `authGroups`, which is what makes a `{ group }` grant
3518
+ * match at all; below it the grant fails closed, so serving the roster
3519
+ * would publish a member list no read grant can use.
3520
+ */
3521
+ private readonly servesGroups;
3522
+ private readonly operationIndex?;
3523
+ private readonly logger?;
3524
+ constructor(model: RegisteredDecisionModel, documentView: IDocumentView,
3525
+ /**
3526
+ * Whether a group a policy names is served to that policy's audience. Only
3527
+ * meaningful with `authGroups`, which is what makes a `{ group }` grant
3528
+ * match at all; below it the grant fails closed, so serving the roster
3529
+ * would publish a member list no read grant can use.
3530
+ */
3531
+
3532
+ servesGroups: boolean, operationIndex?: IOperationIndex | undefined, logger?: ILogger | undefined);
3533
+ /**
3534
+ * A served group yields its member list and nothing else. What the audience
3535
+ * is owed is the state it must fold to evaluate auth with the group; a
3536
+ * group's other scopes are its own business and stay behind its own grants.
3537
+ */
3538
+ scopePredicate(document: PHDocument, subject: AuthSubject, branch: string, signal?: AbortSignal): Promise<(scope: string) => boolean>;
3539
+ /**
3540
+ * Whether the subject is served this group because a policy names it.
3541
+ *
3542
+ * A replica must fold a group's membership to evaluate auth with it, so a
3543
+ * group a grant names is served to the audience of the document that names
3544
+ * it, whatever the group's own read grants say. Naming a group in a policy
3545
+ * publishes its roster to that policy's audience; a group whose membership
3546
+ * must stay confidential does not belong in a grant.
3547
+ *
3548
+ * The referencing document's own domain scopes are the test. Its `auth` and
3549
+ * `document` scopes are readable by every holder, so testing those would
3550
+ * serve every referenced group to everybody.
3551
+ *
3552
+ * One level only. A referencer that is itself a group is skipped, and a
3553
+ * referencer's own readability is decided from its policy alone, so a
3554
+ * reference cycle terminates. Cycles are reachable: the reference relation
3555
+ * is recorded from an operation's input, including one later stored denied,
3556
+ * so a refused grant naming a group from inside another group leaves a row
3557
+ * behind that validation never saw.
3558
+ *
3559
+ * The referencers are probed a few at a time and the walk stops at the first
3560
+ * that serves, because a subject outside the audience is the case that runs to
3561
+ * the bound, and it is the common one. A probe that failed decides only when
3562
+ * nothing served: serving rests on a real allow, so this cannot widen, and it
3563
+ * stops one unreachable referencer from turning an allow already in hand into
3564
+ * a denial. A read records no operation, so replicas differing over a
3565
+ * transient failure has no consensus consequence.
3566
+ *
3567
+ * The probes are awaited together rather than raced, so none is ever left
3568
+ * running with nobody awaiting it, which is where unhandled rejections come
3569
+ * from.
3570
+ */
3571
+ private servesGroupTo;
3572
+ /**
3573
+ * Whether one referencing document serves the subject any domain scope. A
3574
+ * referencer this replica does not hold serves nothing, which fails closed
3575
+ * the same way a group it does not hold does.
3576
+ */
3577
+ private servesThrough;
3578
+ private servesGroup;
3579
+ /**
3580
+ * What this document's own policy says, with no group serving applied.
3581
+ *
3582
+ * An unpoliced document is readable in full, which is the common case and the
3583
+ * one worth answering without building anything. The test is the one
3584
+ * `evaluate` makes: a legacy `{}` auth scope and version 0 both mean
3585
+ * uninitialized, and "no grants" does not, because a policy with a version and
3586
+ * an empty grant list denies everything.
3587
+ */
3588
+ private ownPolicyPredicate;
3589
+ }
3590
+ //#endregion
3591
+ //#region src/client/reactor-client.d.ts
2403
3592
  /**
2404
- * Snapshot of one worker pool's acquire-wait samples and pool-stat counters,
2405
- * forwarded periodically so the host can re-record into the shared
2406
- * pg.Pool histogram and observable gauges. The worker owns the real
2407
- * pg.Pool; the host's {@link PoolInstrumentation} is a forwarder driven
2408
- * by these messages.
3593
+ * What {@link IReactorClient.evaluateActions} decides against: the decision
3594
+ * model this reactor enforces, and the flags that selected it.
3595
+ *
3596
+ * Both, because neither alone is enough. The flags say which of the model's
3597
+ * inputs a decision may read, and the model itself cannot be derived from them
3598
+ * here: selecting one needs the document model registry, which a client does not
3599
+ * hold. Absent, this client answers no preflight at all -- which is the whole of
3600
+ * the non-coexistence guarantee, since a client built without a reactor holding
3601
+ * a decision model has nothing to answer from.
2409
3602
  */
2410
- type PoolAcquireSamplesMessage = {
2411
- type: "pool-acquire-samples";
2412
- workerId: string; /** Stable identifier matching the host-side instrumentation name (e.g. "worker-0"). */
2413
- poolName: string; /** Epoch milliseconds the worker generated the batch. */
2414
- timestamp: number; /** Acquire-wait durations (ms) accumulated since the previous batch. */
2415
- durations: number[]; /** Most recent pg.Pool counter snapshot at batch send time. */
2416
- size: number;
2417
- idle: number;
2418
- waiting: number;
3603
+ type ActionEvaluationConfig = {
3604
+ model: RegisteredDecisionModel;
3605
+ flags: ReactorFeatureFlags;
2419
3606
  };
2420
3607
  /**
2421
- * Union of all messages a worker may send to the parent.
3608
+ * ReactorClient implementation that wraps lower-level APIs to provide
3609
+ * a simpler interface for document operations.
2422
3610
  *
2423
- * @see Wire Protocol Reference wiki page
2424
- * (Powerhouse board wiki id: 64c03e51-1aa4-4fa9-93d8-daa45642484d)
3611
+ * Features:
3612
+ * - Wraps Jobs with Promises for easier async handling
3613
+ * - Manages signing of submitted Action objects
3614
+ * - Provides quality-of-life functions for common tasks
3615
+ * - Wraps subscription interface with ViewFilters
2425
3616
  */
2426
- type WorkerMessage = ReadyMessage | ResultMessage | ModelLoadedMessage | ModelLoadFailedMessage | LogMessage | HeartbeatMessage | MetricsMessage | PoolAcquireSamplesMessage;
3617
+ declare class ReactorClient implements IReactorClient {
3618
+ private logger;
3619
+ private reactor;
3620
+ private signer;
3621
+ private subscriptionManager;
3622
+ private jobAwaiter;
3623
+ private documentIndexer;
3624
+ private documentView;
3625
+ private readGate;
3626
+ private actionEvaluation;
3627
+ readonly drives: IDriveClient;
3628
+ constructor(logger: ILogger, reactor: IReactor, signer: ISigner, subscriptionManager: IReactorSubscriptionManager, jobAwaiter: IJobAwaiter, documentIndexer: IDocumentIndexer, documentView: IDocumentView, readGate?: IReadGate, actionEvaluation?: ActionEvaluationConfig);
3629
+ private readSubject;
3630
+ /**
3631
+ * Which scopes of one document the subject may read. Resolved once per
3632
+ * document, so the gate builds its model once however many scopes are then
3633
+ * tested, and the filtering itself stays synchronous.
3634
+ */
3635
+ private readableScopes;
3636
+ /**
3637
+ * One document, filtered to the scopes the subject may read. Every method
3638
+ * that hands a document back goes through here, including the ones that
3639
+ * follow a write: a document returned from a mutation is a read like any
3640
+ * other, and returning it whole served scopes the same subject would be
3641
+ * refused by `get`. Its author still sees what it wrote, because an allow on
3642
+ * execute confers read of that scope.
3643
+ */
3644
+ private gateDocument;
3645
+ /**
3646
+ * Retrieves a list of document model modules.
3647
+ */
3648
+ getDocumentModelModules(namespace?: string, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<DocumentModelModule>>;
3649
+ /**
3650
+ * Retrieves a specific document model module by document type.
3651
+ *
3652
+ * @param documentType - The document type identifier
3653
+ * @returns The document model module
3654
+ */
3655
+ getDocumentModelModule(documentType: string): Promise<DocumentModelModule<any>>;
3656
+ /**
3657
+ * Retrieves the document model module matching the version the document is
3658
+ * stamped with, so not-yet-upgraded documents get the reducer their
3659
+ * history was written with rather than the latest.
3660
+ */
3661
+ getDocumentModelModuleForDocument(document: PHDocument): Promise<DocumentModelModule<any>>;
3662
+ /**
3663
+ * Retrieves a specific PHDocument
3664
+ */
3665
+ get<TDocument extends PHDocument>(identifier: string, view?: ViewFilter, signal?: AbortSignal): Promise<TDocument>;
3666
+ /**
3667
+ * Resolves an identifier (id or slug) to the canonical document id, using the
3668
+ * same lookup as the data path. Resolves against the "main" branch. Throws if
3669
+ * the identifier cannot be resolved or is ambiguous.
3670
+ */
3671
+ resolveIdOrSlug(identifier: string, view?: ViewFilter, signal?: AbortSignal): Promise<string>;
3672
+ /**
3673
+ * Retrieves operations for a document
3674
+ */
3675
+ getOperations(documentIdentifier: string, view?: ViewFilter, filter?: OperationFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<Operation>>;
3676
+ private getOperationsWithCompositeCursor;
3677
+ /**
3678
+ * Retrieves outgoing relationships of a given type from a source document.
3679
+ */
3680
+ getOutgoingRelationships(sourceIdentifier: string, relationshipType: string, view?: ViewFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<PHDocument>>;
3681
+ /**
3682
+ * Retrieves incoming relationships of a given type to a target document.
3683
+ */
3684
+ getIncomingRelationships(targetIdentifier: string, relationshipType: string, view?: ViewFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<PHDocument>>;
3685
+ /**
3686
+ * Filters documents by criteria and returns a list of them
3687
+ */
3688
+ find(search: SearchFilter, view?: ViewFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<PHDocument>>;
3689
+ /**
3690
+ * Predicts the admission verdict for each candidate. See
3691
+ * {@link IReactorClient.evaluateActions} for the contract and its caveats.
3692
+ *
3693
+ * Read-only throughout, and never through the write cache: that cache is
3694
+ * invalidated by whichever process runs the executor, so a reactor running
3695
+ * its executors in worker processes would answer here from state no commit
3696
+ * ever invalidates.
3697
+ */
3698
+ evaluateActions(documentIdentifier: string, branch: string, candidates: ActionCandidate[], subject?: AuthSubject, signal?: AbortSignal): Promise<ActionEvaluations>;
3699
+ /**
3700
+ * The decision model for one target document, built at its stream heads.
3701
+ *
3702
+ * The document is fetched unfiltered, because the policy is what decides:
3703
+ * reading it through the read gate would withhold the very scopes the
3704
+ * decision is about. A deleted document is served at its deletion boundary,
3705
+ * which is what lets the model refuse an execute against it -- authEnforcement
3706
+ * requires documentDecisions, so that read is available whenever this runs.
3707
+ *
3708
+ * Reading past the gate discloses nothing a submit does not. The `auth` and
3709
+ * `document` scopes are readable by every holder, so a verdict resting on the
3710
+ * policy alone is one the caller could compute unaided; and a verdict resting
3711
+ * on a conditional grant reads the executing scope's state exactly as
3712
+ * admission reads it, so the answer here is what submitting and being refused
3713
+ * would have revealed anyway.
3714
+ *
3715
+ * The append condition the build records is dropped. It guards a write, and
3716
+ * this makes none; reproducing it is also what the preflight cannot do, which
3717
+ * is why the answer is a prediction.
3718
+ */
3719
+ private buildEvaluationTarget;
3720
+ /**
3721
+ * Creates a document and waits for completion
3722
+ */
3723
+ create<TDocument extends PHDocument = PHDocument>(document: PHDocument, parentIdentifier?: string, signal?: AbortSignal): Promise<TDocument>;
3724
+ /**
3725
+ * Creates an empty document and waits for completion
3726
+ */
3727
+ createEmpty<TDocument extends PHDocument>(documentModelType: string, options?: CreateDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
3728
+ /**
3729
+ * Upgrades a document to a newer document model version by dispatching an
3730
+ * UPGRADE_DOCUMENT action. When toVersion is omitted, upgrades to the
3731
+ * latest registered module version for the document's type. Returns the
3732
+ * document unchanged when it is already at the target version.
3733
+ *
3734
+ * The executor validates the action's version and revision snapshot against
3735
+ * the state the migration actually runs on. When a concurrent edit
3736
+ * invalidates the snapshot, the upgrade is rebuilt from a fresh read and
3737
+ * retried up to maxConflictRetries times before the conflict is surfaced.
3738
+ */
3739
+ upgradeDocument<TDocument extends PHDocument = PHDocument>(documentIdentifier: string, toVersion?: number, options?: UpgradeDocumentOptions, signal?: AbortSignal): Promise<TDocument>;
3740
+ /**
3741
+ * Creates an empty document in a drive as a single batched operation.
3742
+ * Delegates to {@link IDriveClient.addFile}.
3743
+ *
3744
+ * @deprecated Use `client.drives.addFile` instead. This method will be
3745
+ * removed in a future release.
3746
+ */
3747
+ createDocumentInDrive<TDocument extends PHDocument>(driveId: string, document: PHDocument, parentFolder?: string, signal?: AbortSignal): Promise<TDocument>;
3748
+ /**
3749
+ * Applies a list of actions to a document and waits for completion
3750
+ */
3751
+ execute<TDocument extends PHDocument>(documentIdentifier: string, branch: string, actions: Action[], signal?: AbortSignal): Promise<TDocument>;
3752
+ /**
3753
+ * Submits a list of actions to a document
3754
+ */
3755
+ executeAsync(documentIdentifier: string, branch: string, actions: Action[], signal?: AbortSignal): Promise<JobInfo>;
3756
+ executeBatch(request: BatchExecutionRequest, signal?: AbortSignal): Promise<BatchExecutionResult>;
3757
+ /**
3758
+ * Renames a document and waits for completion
3759
+ */
3760
+ rename(documentIdentifier: string, name: string, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
3761
+ /**
3762
+ * Updates the preferred editor recorded in the document header meta.
3763
+ * Pass `null` to clear it.
3764
+ */
3765
+ setPreferredEditor(documentIdentifier: string, preferredEditor: string | null, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
3766
+ /**
3767
+ * Adds multiple documents as children to another and waits for completion
3768
+ */
3769
+ addRelationship(sourceIdentifier: string, targetIdentifier: string, relationshipType: string, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
3770
+ /**
3771
+ * Removes a relationship between two documents and waits for completion.
3772
+ */
3773
+ removeRelationship(sourceIdentifier: string, targetIdentifier: string, relationshipType: string, branch?: string, signal?: AbortSignal): Promise<PHDocument>;
3774
+ /**
3775
+ * Moves a relationship from one source document to another and waits for completion.
3776
+ */
3777
+ moveRelationship(sourceParentIdentifier: string, targetParentIdentifier: string, targetIdentifier: string, relationshipType: string, branch?: string, signal?: AbortSignal): Promise<{
3778
+ source: PHDocument;
3779
+ target: PHDocument;
3780
+ }>;
3781
+ loadBatch(request: BatchLoadRequest, signal?: AbortSignal): Promise<BatchLoadResult>;
3782
+ /**
3783
+ * Deletes a document and waits for completion
3784
+ */
3785
+ deleteDocument(identifier: string, propagate?: PropagationMode, signal?: AbortSignal): Promise<void>;
3786
+ /**
3787
+ * Deletes documents and waits for completion
3788
+ */
3789
+ deleteDocuments(identifiers: string[], propagate?: PropagationMode, signal?: AbortSignal): Promise<void>;
3790
+ /**
3791
+ * Retrieves the status of a job
3792
+ */
3793
+ getJobStatus(jobId: string, signal?: AbortSignal): Promise<JobInfo>;
3794
+ /**
3795
+ * Waits for a job to complete
3796
+ */
3797
+ waitForJob(jobId: string | JobInfo, signal?: AbortSignal): Promise<JobInfo>;
3798
+ /**
3799
+ * Subscribes to changes for documents matching specified filters
3800
+ */
3801
+ subscribe(search: SearchFilter, callback: (event: DocumentChangeEvent) => void, view?: ViewFilter): () => void;
3802
+ private removeAllIncomingRelationships;
3803
+ }
2427
3804
  //#endregion
2428
3805
  //#region src/executor/interfaces.d.ts
2429
3806
  /**
@@ -2679,9 +4056,11 @@ interface IQueue {
2679
4056
  * Retry a failed job.
2680
4057
  * @param jobId - The ID of the job to retry
2681
4058
  * @param error - Optional error information from the failure
4059
+ * @param accounting - Whether the attempt counts against the job's retry
4060
+ * limit; defaults to {@link RetryAccounting.CountAgainstLimit}
2682
4061
  * @returns Promise that resolves when the job is requeued for retry
2683
4062
  */
2684
- retryJob(jobId: string, error?: ErrorInfo$1): Promise<void>;
4063
+ retryJob(jobId: string, error?: ErrorInfo$1, accounting?: RetryAccounting): Promise<void>;
2685
4064
  /**
2686
4065
  * Returns true if and only if all jobs have been resolved.
2687
4066
  */
@@ -2690,213 +4069,88 @@ interface IQueue {
2690
4069
  * Blocks the queue from accepting new jobs.
2691
4070
  * @param onDrained - Optional callback to call when the queue is drained
2692
4071
  */
2693
- block(onDrained?: () => void): void;
2694
- /**
2695
- * Unblocks the queue from accepting new jobs.
2696
- */
2697
- unblock(): void;
2698
- }
2699
- //#endregion
2700
- //#region src/read-models/types.d.ts
2701
- interface ViewStateTable {
2702
- readModelId: string;
2703
- lastOrdinal: number;
2704
- lastOperationTimestamp: Generated<Date>;
2705
- }
2706
- interface DocumentSnapshotTable {
2707
- id: Generated<string>;
2708
- documentId: string;
2709
- slug: string | null;
2710
- name: string | null;
2711
- scope: string;
2712
- branch: string;
2713
- content: unknown;
2714
- documentType: string;
2715
- lastOperationIndex: number;
2716
- lastOperationHash: string;
2717
- lastUpdatedAt: Generated<Date>;
2718
- snapshotVersion: Generated<number>;
2719
- identifiers: unknown;
2720
- metadata: unknown;
2721
- isDeleted: Generated<boolean>;
2722
- deletedAt: Date | null;
2723
- }
2724
- interface SlugMappingTable {
2725
- slug: string;
2726
- documentId: string;
2727
- scope: string;
2728
- branch: string;
2729
- createdAt: Generated<Date>;
2730
- updatedAt: Generated<Date>;
2731
- }
2732
- interface ProcessorCursorTable {
2733
- processorId: string;
2734
- factoryId: string;
2735
- driveId: string;
2736
- processorIndex: number;
2737
- lastOrdinal: Generated<number>;
2738
- status: Generated<string>;
2739
- lastError: string | null;
2740
- lastErrorTimestamp: Date | null;
2741
- createdAt: Generated<Date>;
2742
- updatedAt: Generated<Date>;
2743
- }
2744
- interface DocumentViewDatabase {
2745
- ViewState: ViewStateTable;
2746
- DocumentSnapshot: DocumentSnapshotTable;
2747
- SlugMapping: SlugMappingTable;
2748
- ProcessorCursor: ProcessorCursorTable;
2749
- }
2750
- type InsertableDocumentSnapshot = Insertable<DocumentSnapshotTable>;
2751
- //#endregion
2752
- //#region src/core/model-sources.d.ts
2753
- /** An importable file holding one or more document-model exports. */
2754
- type FileModelSource = {
2755
- filePath: string;
2756
- exportName?: string;
2757
- };
2758
- /** An importable package specifier holding one or more document-model exports. */
2759
- type PackageModelSource = {
2760
- packageName: string;
2761
- subpath?: string;
2762
- exportName?: string;
2763
- };
2764
- /**
2765
- * A source of document models: a live module, an importable file, or an
2766
- * importable package. File and package sources can cross a worker-thread
2767
- * boundary (workers re-import them); a live module cannot.
2768
- */
2769
- type DocumentModelSource = DocumentModelModule<any> | FileModelSource | PackageModelSource;
2770
- //#endregion
2771
- //#region src/registry/interfaces.d.ts
2772
- type RegistrationResult<T> = {
2773
- status: "success";
2774
- item: T;
2775
- } | {
2776
- status: "error";
2777
- item: T;
2778
- error: Error;
2779
- };
2780
- /**
2781
- * Loader that asynchronously resolves a document type to a
2782
- * {@link DocumentModelSource}. Used by the queue to gate CREATE_DOCUMENT
2783
- * jobs until the required model is available in the registry.
2784
- *
2785
- * Return an importable source ({ filePath } or { packageName }) whenever
2786
- * possible: the resolver registers the resolved models on the host registry
2787
- * and broadcasts importable sources to executor workers. A live
2788
- * DocumentModelModule is also valid but host-only — it cannot cross a
2789
- * worker-thread boundary, so worker pools will not receive it.
2790
- */
2791
- interface IDocumentModelLoader {
2792
- load(documentType: string): Promise<DocumentModelSource>;
2793
- }
2794
- /**
2795
- * Registry for managing document model modules.
2796
- * Provides centralized access to document models' reducers, utils, and specifications.
2797
- * Supports version-aware module storage and upgrade manifest management.
2798
- */
2799
- interface IDocumentModelRegistry {
2800
- /**
2801
- * Register multiple modules at once.
2802
- * Modules without a version field default to version 1.
2803
- * Invalid or duplicate modules are skipped without breaking registration of the remaining modules.
2804
- *
2805
- * @param modules Document model modules to register
2806
- * @returns Array of results, one per module, indicating success or failure
2807
- */
2808
- registerModules(...modules: DocumentModelModule<any>[]): RegistrationResult<DocumentModelModule<any>>[];
2809
- /**
2810
- * Unregister all versions of the specified document types.
2811
- *
2812
- * @param documentTypes The document types to unregister
2813
- * @returns true if all modules were unregistered, false if any were not found
2814
- */
2815
- unregisterModules(...documentTypes: string[]): boolean;
2816
- /**
2817
- * Get a specific document model module by document type and optional version.
2818
- * If version is not specified, returns the latest version.
2819
- *
2820
- * @param documentType The document type identifier
2821
- * @param version Optional version number to retrieve
2822
- * @returns The document model module
2823
- * @throws ModuleNotFoundError if the document type or version is not registered
2824
- */
2825
- getModule(documentType: string, version?: number): DocumentModelModule<any>;
2826
- /**
2827
- * Get all registered document model modules.
2828
- *
2829
- * @returns Array of all registered modules
2830
- */
2831
- getAllModules(): DocumentModelModule<any>[];
2832
- /**
2833
- * Clear all registered modules and upgrade manifests.
2834
- */
2835
- clear(): void;
2836
- /**
2837
- * Get all supported versions for a document type, sorted in ascending order.
2838
- *
2839
- * @param documentType The document type identifier
2840
- * @returns Array of version numbers sorted ascending
2841
- * @throws ModuleNotFoundError if no modules are registered for the document type
2842
- */
2843
- getSupportedVersions(documentType: string): number[];
2844
- /**
2845
- * Get the latest (highest) version number for a document type.
2846
- *
2847
- * @param documentType The document type identifier
2848
- * @returns The highest version number registered for this document type
2849
- * @throws ModuleNotFoundError if no modules are registered for the document type
2850
- */
2851
- getLatestVersion(documentType: string): number;
2852
- /**
2853
- * Register upgrade manifests that define upgrade paths between versions.
2854
- * Invalid or duplicate manifests are skipped without breaking registration of the remaining manifests.
2855
- *
2856
- * @param manifests Upgrade manifests to register
2857
- * @returns Array of results, one per manifest, indicating success or failure
2858
- */
2859
- registerUpgradeManifests(...manifests: UpgradeManifest<readonly number[]>[]): RegistrationResult<UpgradeManifest<readonly number[]>>[];
2860
- /**
2861
- * Unregister upgrade manifests for the specified document types.
2862
- * @param documentTypes The document types whose upgrade manifests should be unregistered
2863
- * @returns true if all modules were unregistered, false if any were not found
2864
- **/
2865
- unregisterUpgradeManifests(...documentTypes: string[]): boolean;
2866
- /**
2867
- * Get the upgrade manifest for a document type.
2868
- *
2869
- * @param documentType The document type identifier
2870
- * @returns The upgrade manifest
2871
- * @throws ManifestNotFoundError if no manifest is registered for the document type
2872
- */
2873
- getUpgradeManifest(documentType: string): UpgradeManifest<readonly number[]>;
2874
- /**
2875
- * Compute the upgrade path from one version to another.
2876
- * Returns the sequence of upgrade transitions needed.
2877
- *
2878
- * @param documentType The document type identifier
2879
- * @param fromVersion The starting version
2880
- * @param toVersion The target version
2881
- * @returns Array of upgrade transitions in order
2882
- * @throws DowngradeNotSupportedError if toVersion is less than fromVersion
2883
- * @throws ManifestNotFoundError if no upgrade manifest is registered
2884
- * @throws MissingUpgradeTransitionError if any transition in the path is missing
2885
- */
2886
- computeUpgradePath(documentType: string, fromVersion: number, toVersion: number): UpgradeTransition[];
4072
+ block(onDrained?: () => void): void;
2887
4073
  /**
2888
- * Get the upgrade reducer for a single-step version transition.
2889
- *
2890
- * @param documentType The document type identifier
2891
- * @param fromVersion The starting version
2892
- * @param toVersion The target version (must be fromVersion + 1)
2893
- * @returns The upgrade reducer function
2894
- * @throws InvalidUpgradeStepError if toVersion is not fromVersion + 1
2895
- * @throws ManifestNotFoundError if no upgrade manifest is registered
2896
- * @throws MissingUpgradeTransitionError if the transition is not found
4074
+ * Unblocks the queue from accepting new jobs.
2897
4075
  */
2898
- getUpgradeReducer(documentType: string, fromVersion: number, toVersion: number): UpgradeReducer<any, any>;
4076
+ unblock(): void;
4077
+ }
4078
+ //#endregion
4079
+ //#region src/core/group-reevaluation-trigger.d.ts
4080
+ /**
4081
+ * Watches committed writes for group membership changes and enqueues a
4082
+ * re-evaluation job for every document whose auth history references the
4083
+ * changed group, found through the reverse direction of the group-reference
4084
+ * relation. Each affected document is re-judged in its own job, so the work
4085
+ * runs under that document's execution slot rather than the group's.
4086
+ *
4087
+ * The job carries the earliest changed membership timestamp; the executor
4088
+ * skips the pass when everything the document holds sorts before it, which
4089
+ * keeps the common case (a membership write later than all history) free.
4090
+ */
4091
+ declare class GroupReevaluationTrigger {
4092
+ private logger;
4093
+ private eventBus;
4094
+ private queue;
4095
+ private operationIndex;
4096
+ private unsubscribe?;
4097
+ constructor(logger: ILogger, eventBus: IEventBus, queue: IQueue, operationIndex: IOperationIndex);
4098
+ startup(): void;
4099
+ shutdown(): void;
4100
+ private onWriteReady;
4101
+ }
4102
+ //#endregion
4103
+ //#region src/read-models/types.d.ts
4104
+ interface ViewStateTable {
4105
+ readModelId: string;
4106
+ lastOrdinal: number;
4107
+ lastOperationTimestamp: Generated<Date>;
4108
+ }
4109
+ interface DocumentSnapshotTable {
4110
+ id: Generated<string>;
4111
+ documentId: string;
4112
+ slug: string | null;
4113
+ name: string | null;
4114
+ scope: string;
4115
+ branch: string;
4116
+ content: unknown;
4117
+ documentType: string;
4118
+ lastOperationIndex: number;
4119
+ lastOperationHash: string;
4120
+ lastUpdatedAt: Generated<Date>;
4121
+ snapshotVersion: Generated<number>;
4122
+ identifiers: unknown;
4123
+ metadata: unknown;
4124
+ isDeleted: Generated<boolean>;
4125
+ deletedAt: Date | null;
4126
+ }
4127
+ interface SlugMappingTable {
4128
+ slug: string;
4129
+ documentId: string;
4130
+ scope: string;
4131
+ branch: string;
4132
+ createdAt: Generated<Date>;
4133
+ updatedAt: Generated<Date>;
4134
+ }
4135
+ interface ProcessorCursorTable {
4136
+ processorId: string;
4137
+ factoryId: string;
4138
+ driveId: string;
4139
+ processorIndex: number;
4140
+ lastOrdinal: Generated<number>;
4141
+ status: Generated<string>;
4142
+ lastError: string | null;
4143
+ lastErrorTimestamp: Date | null;
4144
+ createdAt: Generated<Date>;
4145
+ updatedAt: Generated<Date>;
4146
+ }
4147
+ interface DocumentViewDatabase {
4148
+ ViewState: ViewStateTable;
4149
+ DocumentSnapshot: DocumentSnapshotTable;
4150
+ SlugMapping: SlugMappingTable;
4151
+ ProcessorCursor: ProcessorCursorTable;
2899
4152
  }
4153
+ type InsertableDocumentSnapshot = Insertable<DocumentSnapshotTable>;
2900
4154
  //#endregion
2901
4155
  //#region src/shared/consistency-tracker.d.ts
2902
4156
  interface IConsistencyTracker {
@@ -2946,134 +4200,6 @@ declare class ConsistencyTracker implements IConsistencyTracker {
2946
4200
  private removeWaiter;
2947
4201
  }
2948
4202
  //#endregion
2949
- //#region src/storage/kysely/types.d.ts
2950
- interface OperationTable {
2951
- id: Generated<number>;
2952
- jobId: string;
2953
- opId: string;
2954
- prevOpId: string;
2955
- writeTimestampUtcMs: Generated<Date>;
2956
- documentId: string;
2957
- documentType: string;
2958
- scope: string;
2959
- branch: string;
2960
- timestampUtcMs: Date;
2961
- index: number;
2962
- action: unknown;
2963
- skip: number;
2964
- error?: string | null;
2965
- hash: string;
2966
- }
2967
- interface KeyframeTable {
2968
- id: Generated<number>;
2969
- documentId: string;
2970
- documentType: string;
2971
- scope: string;
2972
- branch: string;
2973
- revision: number;
2974
- document: unknown;
2975
- createdAt: Generated<Date>;
2976
- }
2977
- interface DocumentCollectionTable {
2978
- documentId: string;
2979
- collectionId: string;
2980
- joinedOrdinal: bigint;
2981
- leftOrdinal: bigint | null;
2982
- }
2983
- interface OperationIndexOperationTable {
2984
- ordinal: Generated<number>;
2985
- opId: string;
2986
- documentId: string;
2987
- documentType: string;
2988
- scope: string;
2989
- branch: string;
2990
- timestampUtcMs: string;
2991
- writeTimestampUtcMs: Generated<Date>;
2992
- index: number;
2993
- skip: number;
2994
- hash: string;
2995
- action: unknown;
2996
- sourceRemote: Generated<string>;
2997
- }
2998
- interface SyncRemoteTable {
2999
- name: string;
3000
- collection_id: string;
3001
- channel_type: string;
3002
- channel_id: string;
3003
- remote_name: string;
3004
- channel_parameters: unknown;
3005
- filter_document_ids: unknown;
3006
- filter_scopes: unknown;
3007
- filter_branch: string;
3008
- push_state: string;
3009
- push_last_success_utc_ms: string | null;
3010
- push_last_failure_utc_ms: string | null;
3011
- push_failure_count: number;
3012
- pull_state: string;
3013
- pull_last_success_utc_ms: string | null;
3014
- pull_last_failure_utc_ms: string | null;
3015
- pull_failure_count: number;
3016
- created_at: Generated<Date>;
3017
- updated_at: Generated<Date>;
3018
- }
3019
- interface SyncCursorTable {
3020
- remote_name: string;
3021
- cursor_type: string;
3022
- cursor_ordinal: bigint;
3023
- last_synced_at_utc_ms: string | null;
3024
- updated_at: Generated<Date>;
3025
- }
3026
- /**
3027
- * Kysely table definition for the `sync_dead_letters` table.
3028
- */
3029
- interface SyncDeadLetterTable {
3030
- ordinal: Generated<number>;
3031
- id: string;
3032
- job_id: string;
3033
- job_dependencies: unknown;
3034
- remote_name: string;
3035
- document_id: string;
3036
- scopes: unknown;
3037
- branch: string;
3038
- operations: unknown;
3039
- error_source: string;
3040
- error_message: string;
3041
- created_at: Generated<Date>;
3042
- }
3043
- interface Database$1 {
3044
- Operation: OperationTable;
3045
- Keyframe: KeyframeTable;
3046
- document_collections: DocumentCollectionTable;
3047
- operation_index_operations: OperationIndexOperationTable;
3048
- sync_remotes: SyncRemoteTable;
3049
- sync_cursors: SyncCursorTable;
3050
- sync_dead_letters: SyncDeadLetterTable;
3051
- }
3052
- interface DocumentTable {
3053
- id: string;
3054
- createdAt: Generated<Date>;
3055
- updatedAt: Generated<Date>;
3056
- }
3057
- interface DocumentRelationshipTable {
3058
- id: Generated<string>;
3059
- sourceId: string;
3060
- targetId: string;
3061
- relationshipType: string;
3062
- metadata: unknown;
3063
- createdAt: Generated<Date>;
3064
- updatedAt: Generated<Date>;
3065
- }
3066
- interface IndexerStateTable {
3067
- id: Generated<number>;
3068
- lastOperationId: number;
3069
- lastOperationTimestamp: Generated<Date>;
3070
- }
3071
- interface DocumentIndexerDatabase {
3072
- Document: DocumentTable;
3073
- DocumentRelationship: DocumentRelationshipTable;
3074
- IndexerState: IndexerStateTable;
3075
- }
3076
- //#endregion
3077
4203
  //#region src/storage/pool-instrumentation.d.ts
3078
4204
  /**
3079
4205
  * Snapshot of a pg.Pool's internal counters at a point in time.
@@ -3137,7 +4263,13 @@ declare class PollingChannelError extends Error {
3137
4263
  declare class ChannelError extends Error {
3138
4264
  source: ChannelErrorSource;
3139
4265
  error: Error;
3140
- constructor(source: ChannelErrorSource, error: Error);
4266
+ /**
4267
+ * The classification when something other than the error carries it. Absent
4268
+ * means derive it from `error.name`; a dead letter mirrored from a peer sets it,
4269
+ * because only the message crosses the wire.
4270
+ */
4271
+ readonly errorType?: SyncOperationErrorType;
4272
+ constructor(source: ChannelErrorSource, error: Error, errorType?: SyncOperationErrorType);
3141
4273
  }
3142
4274
  //#endregion
3143
4275
  //#region src/sync/sync-operation.d.ts
@@ -3736,6 +4868,12 @@ interface ReactorModule {
3736
4868
  * integration scenarios.
3737
4869
  */
3738
4870
  interface InProcessReactorModule extends ReactorModule {
4871
+ /**
4872
+ * The enforcement flags this reactor resolved, as plain booleans. Held on the
4873
+ * module because they select what a read enforces as well as what a write
4874
+ * does, and the read surface is composed outside the reactor.
4875
+ */
4876
+ featureFlags: ReactorFeatureFlags;
3739
4877
  queue: IQueue;
3740
4878
  jobTracker: IJobTracker;
3741
4879
  executorManager: IJobExecutorManager;
@@ -3754,6 +4892,12 @@ interface InProcessReactorModule extends ReactorModule {
3754
4892
  processorManagerConsistencyTracker: IConsistencyTracker;
3755
4893
  reactor: IReactor;
3756
4894
  syncModule: InProcessSyncModule | undefined;
4895
+ /**
4896
+ * Present when authGroups is on: enqueues re-evaluation jobs for the
4897
+ * documents a group membership change affects. Started by the builder;
4898
+ * hosts shut it down alongside the sync manager.
4899
+ */
4900
+ groupReevaluationTrigger: GroupReevaluationTrigger | undefined;
3757
4901
  /**
3758
4902
  * Instrumented pg.Pool handles registered with the builder, either by
3759
4903
  * createPostgresDatabase or by withInstrumentedPool. Empty when no pg
@@ -3815,12 +4959,6 @@ declare class DriveClient implements IDriveClient {
3815
4959
  private removeFileNode;
3816
4960
  }
3817
4961
  //#endregion
3818
- //#region src/cache/collection-membership-cache.d.ts
3819
- interface ICollectionMembershipCache {
3820
- getCollectionsForDocuments(documentIds: string[]): Promise<Record<string, string[]>>;
3821
- invalidate(documentId: string): void;
3822
- }
3823
- //#endregion
3824
4962
  //#region src/registry/document-model-resolver.d.ts
3825
4963
  interface IDocumentModelResolver {
3826
4964
  ensureModelLoaded(documentType: string): Promise<void>;
@@ -3864,53 +5002,21 @@ declare class DocumentModelResolver implements IDocumentModelResolver {
3864
5002
  /**
3865
5003
  * No-op resolver used when no document model loader is configured.
3866
5004
  * Checks the registry for the model and returns if found; throws if not.
3867
- * Since there is no loader, missing models cannot be recovered.
3868
- */
3869
- declare class NullDocumentModelResolver implements IDocumentModelResolver {
3870
- private registry?;
3871
- constructor(registry?: IDocumentModelRegistry | undefined);
3872
- ensureModelLoaded(documentType: string): Promise<void>;
3873
- }
3874
- //#endregion
3875
- //#region src/executor/worker-pool-job-executor-manager.d.ts
3876
- /**
3877
- * Factory invoked once per worker at `start()` time. The index is the
3878
- * worker's position in the pool and the same value the manager will use
3879
- * for sticky routing (`bucketFor(documentId) === index`).
3880
- */
3881
- type WorkerFactory = (index: number) => IExecutorWorker;
3882
- //#endregion
3883
- //#region src/cache/write-cache-types.d.ts
3884
- /**
3885
- * Configuration options for the write cache
3886
- */
3887
- type WriteCacheConfig = {
3888
- /** Maximum number of document streams to cache (LRU eviction). Default: 1000 */maxDocuments: number; /** Number of snapshots to keep in each document's ring buffer. Default: 10 */
3889
- ringBufferSize: number; /** Persist a keyframe snapshot every N revisions. Default: 10 */
3890
- keyframeInterval: number;
3891
- };
3892
- /**
3893
- * Unique identifier for a document stream
3894
- */
3895
- type DocumentStreamKey = {
3896
- /** Document identifier */documentId: string; /** Operation scope */
3897
- scope: string; /** Branch name */
3898
- branch: string;
3899
- };
3900
- /**
3901
- * A cached document snapshot at a specific revision
3902
- */
3903
- type CachedSnapshot = {
3904
- /** The revision number of this snapshot */revision: number; /** The document state at this revision */
3905
- document: PHDocument;
3906
- };
5005
+ * Since there is no loader, missing models cannot be recovered.
5006
+ */
5007
+ declare class NullDocumentModelResolver implements IDocumentModelResolver {
5008
+ private registry?;
5009
+ constructor(registry?: IDocumentModelRegistry | undefined);
5010
+ ensureModelLoaded(documentType: string): Promise<void>;
5011
+ }
5012
+ //#endregion
5013
+ //#region src/executor/worker-pool-job-executor-manager.d.ts
3907
5014
  /**
3908
- * Serialized keyframe snapshot for K/V store persistence
5015
+ * Factory invoked once per worker at `start()` time. The index is the
5016
+ * worker's position in the pool and the same value the manager will use
5017
+ * for sticky routing (`bucketFor(documentId) === index`).
3909
5018
  */
3910
- type KeyframeSnapshot = {
3911
- /** The revision number of this keyframe */revision: number; /** Serialized document state */
3912
- document: string;
3913
- };
5019
+ type WorkerFactory = (index: number) => IExecutorWorker;
3914
5020
  //#endregion
3915
5021
  //#region src/projection/protocol.d.ts
3916
5022
  /**
@@ -4162,6 +5268,7 @@ declare class SyncBuilder {
4162
5268
  * them (`BaseReadModel` subclasses, in particular).
4163
5269
  */
4164
5270
  interface ReadModelFactoryDeps {
5271
+ documentModelRegistry: IDocumentModelRegistry;
4165
5272
  operationIndex: IOperationIndex;
4166
5273
  writeCache: IWriteCache;
4167
5274
  processorManagerConsistencyTracker: IConsistencyTracker;
@@ -4383,6 +5490,7 @@ declare class ReactorClientBuilder {
4383
5490
  private subscriptionManager?;
4384
5491
  private jobAwaiter?;
4385
5492
  private documentModelLoader?;
5493
+ private readGate?;
4386
5494
  /**
4387
5495
  * Sets the logger for the ReactorClient.
4388
5496
  * @param logger - The logger to use.
@@ -4406,6 +5514,36 @@ declare class ReactorClientBuilder {
4406
5514
  withSubscriptionManager(subscriptionManager: IReactorSubscriptionManager): this;
4407
5515
  withJobAwaiter(jobAwaiter: IJobAwaiter): this;
4408
5516
  withDocumentModelLoader(loader: IDocumentModelLoader): this;
5517
+ /**
5518
+ * Overrides how reads are gated. A client built from a ReactorBuilder derives
5519
+ * this from that reactor's flags; one built from `withReactor` cannot, because
5520
+ * it is handed no flags and no registry, so it gates on the policy alone
5521
+ * unless a gate is supplied here.
5522
+ */
5523
+ withReadGate(readGate: IReadGate): this;
5524
+ /**
5525
+ * The gate the resolved model calls for. Below authEnforcement there is no
5526
+ * model to enforce -- the registered one ignores the auth scope -- so the
5527
+ * policy is evaluated on its own, which is what reads did before the model
5528
+ * existed. Group serving turns on with authGroups, because below it a
5529
+ * `{ group }` grant does not match, so a served roster is one no grant can
5530
+ * use.
5531
+ */
5532
+ private resolveReadGate;
5533
+ /**
5534
+ * What the client answers an authorization preflight from, or undefined when
5535
+ * it answers none.
5536
+ *
5537
+ * Resolved from the same model reads enforce, so a preflight and a read can
5538
+ * never decide against different models. Undefined below authEnforcement, and
5539
+ * undefined on the `withReactor` path, where there are no flags and no
5540
+ * registry to select a model with -- a client with no model refuses the
5541
+ * preflight rather than answering it from the legacy host-side permission
5542
+ * tables. Deliberately not derived from the read gate: `withReadGate`
5543
+ * overrides that, so sniffing the gate's type would report enforcement from a
5544
+ * caller's substitution.
5545
+ */
5546
+ private resolveActionEvaluation;
4409
5547
  build(): Promise<ReactorClient>;
4410
5548
  buildModule(): Promise<InProcessReactorClientModule>;
4411
5549
  }
@@ -4478,6 +5616,28 @@ declare function parseDriveUrl(url: string): ParsedDriveUrl;
4478
5616
  */
4479
5617
  declare function driveIdFromUrl(url: string): string;
4480
5618
  //#endregion
5619
+ //#region src/shared/errors.d.ts
5620
+ /**
5621
+ * An authorization preflight was asked for while the reactor's decision model
5622
+ * is off, so there is no model to answer from.
5623
+ *
5624
+ * Thrown rather than answered from the legacy host-side permission tables. The
5625
+ * two systems do not compose: the tables record which addresses a host lets
5626
+ * near a drive, the policy records what a document's own grants permit, and an
5627
+ * answer stitched from both would report an admission verdict neither system
5628
+ * would reach. A caller that cannot get a prediction disables nothing, which
5629
+ * leaves the submit path -- and its real gate -- as the only authority.
5630
+ *
5631
+ * Detection is by `name`, not `instanceof`: the SharedWorker RPC boundary
5632
+ * rebuilds a thrown error from `{ name, message, stack, cause }` alone
5633
+ * (`reactor-browser/src/rpc/error-info.ts`), so the class identity and any
5634
+ * custom field are lost in transit. This error therefore carries no fields.
5635
+ */
5636
+ declare class AuthEnforcementDisabledError extends Error {
5637
+ constructor();
5638
+ static isError(error: unknown): error is AuthEnforcementDisabledError;
5639
+ }
5640
+ //#endregion
4481
5641
  //#region src/shared/factories.d.ts
4482
5642
  /**
4483
5643
  * Factory method to create a ShutdownStatus that can be updated
@@ -4500,568 +5660,229 @@ type ParsedPaging = {
4500
5660
  */
4501
5661
  declare function parsePagingOptions(paging: PagingOptions | undefined, defaultLimit: number): ParsedPaging;
4502
5662
  //#endregion
4503
- //#region src/subs/default-error-handler.d.ts
4504
- /**
4505
- * Default error handler that re-throws subscription errors.
4506
- * This ensures that errors are not silently swallowed.
4507
- */
4508
- declare class DefaultSubscriptionErrorHandler implements ISubscriptionErrorHandler {
4509
- handleError(error: unknown, context: SubscriptionErrorContext): void;
4510
- }
4511
- //#endregion
4512
- //#region src/subs/react-subscription-manager.d.ts
4513
- type DocumentCreatedCallback = (result: PagedResults<string>) => void;
4514
- type DocumentDeletedCallback = (documentIds: string[]) => void;
4515
- type DocumentStateUpdatedCallback = (result: PagedResults<PHDocument>) => void;
4516
- type RelationshipChangedCallback = (parentId: string, childId: string, changeType: RelationshipChangeType) => void;
4517
- declare class ReactorSubscriptionManager implements IReactorSubscriptionManager {
4518
- private createdSubscriptions;
4519
- private deletedSubscriptions;
4520
- private updatedSubscriptions;
4521
- private relationshipSubscriptions;
4522
- private subscriptionCounter;
4523
- private errorHandler;
4524
- constructor(errorHandler: ISubscriptionErrorHandler);
4525
- onDocumentCreated(callback: DocumentCreatedCallback, search?: SearchFilter): () => void;
4526
- onDocumentDeleted(callback: DocumentDeletedCallback, search?: SearchFilter): () => void;
4527
- onDocumentStateUpdated(callback: DocumentStateUpdatedCallback, search?: SearchFilter, view?: ViewFilter): () => void;
4528
- onRelationshipChanged(callback: RelationshipChangedCallback, search?: SearchFilter): () => void;
4529
- /**
4530
- * Notify subscribers about created documents
4531
- */
4532
- notifyDocumentsCreated(documentIds: string[], documentTypes?: Map<string, string>, parentIds?: Map<string, string | null>): void;
4533
- /**
4534
- * Notify subscribers about deleted documents
4535
- */
4536
- notifyDocumentsDeleted(documentIds: string[], documentTypes?: Map<string, string>, parentIds?: Map<string, string | null>): void;
4537
- /**
4538
- * Notify subscribers about updated documents
4539
- */
4540
- notifyDocumentsUpdated(documents: PHDocument[]): void;
4541
- /**
4542
- * Notify subscribers about relationship changes
4543
- */
4544
- notifyRelationshipChanged(parentId: string, childId: string, changeType: RelationshipChangeType, childType?: string): void;
4545
- /**
4546
- * Clear all subscriptions
4547
- */
4548
- clearAll(): void;
4549
- private filterDocumentIds;
4550
- private filterDocuments;
4551
- private matchesRelationshipFilter;
4552
- }
4553
- //#endregion
4554
- //#region src/events/event-bus.d.ts
4555
- declare class EventBus implements IEventBus {
4556
- readonly eventTypeToSubscribers: Map<number, Subscriber[]>;
4557
- subscribe<K>(type: number, subscriber: (type: number, event: K) => void | Promise<void>): Unsubscribe;
4558
- emit(type: number, data: any): Promise<void>;
4559
- }
4560
- //#endregion
4561
- //#region src/queue/queue.d.ts
4562
- /**
4563
- * In-memory implementation of the IQueue interface.
4564
- * Organizes jobs by documentId, scope, and branch to ensure proper ordering.
4565
- * Ensures serial execution per document by tracking executing jobs.
4566
- * Implements dependency management through queue hints.
4567
- */
4568
- declare class InMemoryQueue implements IQueue {
4569
- private eventBus;
4570
- private resolver;
4571
- private queues;
4572
- private jobIdToQueueKey;
4573
- private docIdToJobId;
4574
- private jobIdToDocId;
4575
- private completedJobs;
4576
- private jobIndex;
4577
- private isBlocked;
4578
- private onDrainedCallback?;
4579
- private isPausedFlag;
4580
- constructor(eventBus: IEventBus, resolver: IDocumentModelResolver);
4581
- private toErrorInfo;
4582
- /**
4583
- * Creates a unique key for a document/scope/branch combination
4584
- */
4585
- private createQueueKey;
4586
- /**
4587
- * Gets or creates a queue for the given key
4588
- */
4589
- private getQueue;
4590
- /**
4591
- * Check if a document has any jobs currently executing
4592
- */
4593
- private isDocumentExecuting;
4594
- /**
4595
- * Mark a job as executing for its document
4596
- */
4597
- private markJobExecuting;
4598
- /**
4599
- * Mark a job as no longer executing for its document
4600
- */
4601
- private markJobComplete;
4602
- /**
4603
- * Check if all dependencies for a job have been completed
4604
- */
4605
- private areDependenciesMet;
4606
- /**
4607
- * Returns the head of the sub-queue if its dependencies are met, or null.
4608
- *
4609
- * The dispatcher only ever considers the head — a dep-blocked head holds
4610
- * the rest of its sub-queue. This preserves per-(documentId, scope, branch)
4611
- * FIFO regardless of how dependencies are authored, and makes the queue's
4612
- * documented "serialized per document" invariant hold even when callers
4613
- * omit queueHint dependencies on jobs that share a sub-queue.
4614
- */
4615
- private getNextJobWithMetDependencies;
4616
- private getCreateDocumentType;
4617
- enqueue(job: Job): Promise<void>;
4618
- dequeue(documentId: string, scope: string, branch: string, signal?: AbortSignal): Promise<IJobExecutionHandle | null>;
4619
- dequeueNext(signal?: AbortSignal): Promise<IJobExecutionHandle | null>;
4620
- dequeueNextMatching(predicate: (meta: JobRoutingMeta) => boolean, signal?: AbortSignal): Promise<IJobExecutionHandle | null>;
4621
- size(documentId: string, scope: string, branch: string): Promise<number>;
4622
- totalSize(): Promise<number>;
4623
- remove(jobId: string): Promise<boolean>;
4624
- clear(documentId: string, scope: string, branch: string): Promise<void>;
4625
- clearAll(): Promise<void>;
4626
- hasJobs(): Promise<boolean>;
4627
- completeJob(jobId: string): Promise<void>;
4628
- failJob(jobId: string, error?: ErrorInfo$1): Promise<void>;
4629
- deferJob(jobId: string): void;
4630
- retryJob(jobId: string, error?: ErrorInfo$1): Promise<void>;
4631
- /**
4632
- * Check if the queue is drained and call the callback if it is
4633
- */
4634
- private checkDrained;
4635
- /**
4636
- * Returns true if and only if all jobs have been resolved.
4637
- */
4638
- get isDrained(): boolean;
4639
- /**
4640
- * Blocks the queue from accepting new jobs.
4641
- * @param onDrained - Optional callback to call when the queue is drained
4642
- */
4643
- block(onDrained?: () => void): void;
4644
- /**
4645
- * Unblocks the queue from accepting new jobs.
4646
- */
4647
- unblock(): void;
4648
- /**
4649
- * Pauses job dequeuing. Jobs can still be enqueued but dequeueNext() will return null.
4650
- */
4651
- pause(): void;
4652
- /**
4653
- * Resumes job dequeuing and emits JOB_AVAILABLE events for pending jobs to wake up executors.
4654
- */
4655
- resume(): Promise<void>;
4656
- /**
4657
- * Returns whether job dequeuing is paused.
4658
- */
4659
- get paused(): boolean;
4660
- /**
4661
- * Returns all pending jobs across all queues.
4662
- */
4663
- getPendingJobs(): Job[];
4664
- /**
4665
- * Returns a map of document IDs to sets of executing job IDs.
4666
- */
4667
- getExecutingJobIds(): Map<string, Set<string>>;
4668
- /**
4669
- * Returns a job by ID from the job index.
4670
- */
4671
- getJob(jobId: string): Job | undefined;
4672
- }
4673
- //#endregion
4674
- //#region src/job-tracker/in-memory-job-tracker.d.ts
4675
- /**
4676
- * In-memory implementation of IJobTracker.
4677
- * Maintains job status in a Map for synchronous access.
4678
- * Subscribes to operation events to update job states.
4679
- */
4680
- declare class InMemoryJobTracker implements IJobTracker {
4681
- private eventBus;
4682
- private jobs;
4683
- private unsubscribers;
4684
- constructor(eventBus: IEventBus);
4685
- private subscribeToEvents;
4686
- private handleWriteReady;
4687
- private handleReadReady;
4688
- private handleJobFailed;
4689
- shutdown(): void;
4690
- registerJob(jobInfo: JobInfo): void;
4691
- markRunning(jobId: string): void;
4692
- markFailed(jobId: string, error: ErrorInfo$1, job?: Job): void;
4693
- getJobStatus(jobId: string): JobInfo | null;
4694
- }
4695
- //#endregion
4696
- //#region src/executor/simple-job-executor-manager.d.ts
4697
- type JobExecutorFactory = () => IJobExecutor;
4698
- /**
4699
- * Manages multiple job executors and coordinates job distribution.
4700
- * Listens for job available events and dispatches jobs to executors.
4701
- */
4702
- declare class SimpleJobExecutorManager implements IJobExecutorManager {
4703
- private executorFactory;
4704
- private eventBus;
4705
- private queue;
4706
- private jobTracker;
4707
- private logger;
4708
- private resolver;
4709
- private executors;
4710
- private isRunning;
4711
- private activeJobs;
4712
- private totalJobsProcessed;
4713
- private unsubscribe?;
4714
- private deferredJobs;
4715
- private resultHandler;
4716
- private jobTimeoutMs;
4717
- constructor(executorFactory: JobExecutorFactory, eventBus: IEventBus, queue: IQueue, jobTracker: IJobTracker, logger: ILogger, resolver: IDocumentModelResolver, jobTimeoutMs?: number);
4718
- start(numExecutors: number): Promise<void>;
4719
- stop(graceful?: boolean): Promise<void>;
4720
- getExecutors(): IJobExecutor[];
4721
- getStatus(): ExecutorManagerStatus;
4722
- private processNextJob;
4723
- private checkForMoreJobs;
4724
- private processExistingJobs;
4725
- private flushDeferredJobs;
4726
- }
4727
- //#endregion
4728
- //#region src/cache/document-meta-cache-types.d.ts
5663
+ //#region src/subs/default-error-handler.d.ts
4729
5664
  /**
4730
- * Cached document metadata from the "document" scope.
4731
- *
4732
- * This lightweight structure holds essential document information needed by
4733
- * the job executor without fetching full scope state. It provides an explicit
4734
- * cross-scope contract for accessing document scope metadata.
5665
+ * Default error handler that re-throws subscription errors.
5666
+ * This ensures that errors are not silently swallowed.
4735
5667
  */
4736
- type CachedDocumentMeta = {
5668
+ declare class DefaultSubscriptionErrorHandler implements ISubscriptionErrorHandler {
5669
+ handleError(error: unknown, context: SubscriptionErrorContext): void;
5670
+ }
5671
+ //#endregion
5672
+ //#region src/subs/react-subscription-manager.d.ts
5673
+ type DocumentCreatedCallback = (result: PagedResults<string>) => void;
5674
+ type DocumentDeletedCallback = (documentIds: string[]) => void;
5675
+ type DocumentStateUpdatedCallback = (result: PagedResults<PHDocument>) => void;
5676
+ type RelationshipChangedCallback = (parentId: string, childId: string, changeType: RelationshipChangeType) => void;
5677
+ declare class ReactorSubscriptionManager implements IReactorSubscriptionManager {
5678
+ private createdSubscriptions;
5679
+ private deletedSubscriptions;
5680
+ private updatedSubscriptions;
5681
+ private relationshipSubscriptions;
5682
+ private subscriptionCounter;
5683
+ private errorHandler;
5684
+ constructor(errorHandler: ISubscriptionErrorHandler);
5685
+ onDocumentCreated(callback: DocumentCreatedCallback, search?: SearchFilter): () => void;
5686
+ onDocumentDeleted(callback: DocumentDeletedCallback, search?: SearchFilter): () => void;
5687
+ onDocumentStateUpdated(callback: DocumentStateUpdatedCallback, search?: SearchFilter, view?: ViewFilter): () => void;
5688
+ onRelationshipChanged(callback: RelationshipChangedCallback, search?: SearchFilter): () => void;
4737
5689
  /**
4738
- * The full PHDocumentState from document.state.document.
4739
- * Contains version, hash, isDeleted, deletedAtUtcIso, etc.
5690
+ * Notify subscribers about created documents
4740
5691
  */
4741
- state: PHDocumentState;
5692
+ notifyDocumentsCreated(documentIds: string[], documentTypes?: Map<string, string>, parentIds?: Map<string, string | null>): void;
4742
5693
  /**
4743
- * The document type (from header), cached for convenience.
5694
+ * Notify subscribers about deleted documents
4744
5695
  */
4745
- documentType: string;
5696
+ notifyDocumentsDeleted(documentIds: string[], documentTypes?: Map<string, string>, parentIds?: Map<string, string | null>): void;
4746
5697
  /**
4747
- * The revision of the document scope when this metadata was captured.
4748
- * Used for cache invalidation and consistency checks.
5698
+ * Notify subscribers about updated documents
4749
5699
  */
4750
- documentScopeRevision: number;
4751
- };
4752
- /**
4753
- * Interface for the document metadata cache.
4754
- *
4755
- * This cache provides an explicit cross-scope contract for accessing document
4756
- * scope metadata. It solves the problem where job execution in one scope (e.g.,
4757
- * "global") needs access to document scope state (version, isDeleted, etc.)
4758
- * which may be stale in scope-specific caches or keyframes.
4759
- *
4760
- * The cache supports:
4761
- * - Latest metadata retrieval with LRU caching
4762
- * - Historical metadata reconstruction for reshuffling scenarios
4763
- * - Eager updates after document scope operations
4764
- */
4765
- interface IDocumentMetaCache {
5700
+ notifyDocumentsUpdated(documents: PHDocument[]): void;
4766
5701
  /**
4767
- * Retrieves the LATEST document metadata from cache or rebuilds from operations.
4768
- *
4769
- * On cache miss, fetches all document scope operations and reconstructs the
4770
- * current PHDocumentState by applying UPGRADE_DOCUMENT and DELETE_DOCUMENT
4771
- * operations.
4772
- *
4773
- * @param documentId - The document identifier
4774
- * @param branch - Branch name
4775
- * @param signal - Optional abort signal to cancel the operation
4776
- * @returns The cached or rebuilt document metadata
4777
- * @throws {Error} "Operation aborted" if signal is aborted
4778
- * @throws {Error} If document not found (no CREATE_DOCUMENT operation)
5702
+ * Notify subscribers about relationship changes
4779
5703
  */
4780
- getDocumentMeta(documentId: string, branch: string, signal?: AbortSignal): Promise<CachedDocumentMeta>;
5704
+ notifyRelationshipChanged(parentId: string, childId: string, changeType: RelationshipChangeType, childType?: string): void;
4781
5705
  /**
4782
- * Rebuilds document metadata at a SPECIFIC revision (always rebuilds, no caching).
4783
- *
4784
- * Used during reshuffling when operations need to be inserted at a previous
4785
- * revision and we need the document scope state as of that point in time.
4786
- *
4787
- * @param documentId - The document identifier
4788
- * @param branch - Branch name
4789
- * @param targetRevision - The document scope revision to reconstruct up to
4790
- * @param signal - Optional abort signal to cancel the operation
4791
- * @returns Document metadata as of the target revision
4792
- * @throws {Error} "Operation aborted" if signal is aborted
4793
- * @throws {Error} If document not found
5706
+ * Clear all subscriptions
4794
5707
  */
4795
- rebuildAtRevision(documentId: string, branch: string, targetRevision: number, signal?: AbortSignal): Promise<CachedDocumentMeta>;
5708
+ clearAll(): void;
5709
+ private filterDocumentIds;
5710
+ private filterDocuments;
5711
+ private matchesRelationshipFilter;
5712
+ }
5713
+ //#endregion
5714
+ //#region src/events/event-bus.d.ts
5715
+ declare class EventBus implements IEventBus {
5716
+ readonly eventTypeToSubscribers: Map<number, Subscriber[]>;
5717
+ subscribe<K>(type: number, subscriber: (type: number, event: K) => void | Promise<void>): Unsubscribe;
5718
+ emit(type: number, data: any): Promise<void>;
5719
+ }
5720
+ //#endregion
5721
+ //#region src/queue/queue.d.ts
5722
+ /**
5723
+ * In-memory implementation of the IQueue interface.
5724
+ * Organizes jobs by documentId, scope, and branch to ensure proper ordering.
5725
+ * Ensures serial execution per document by tracking executing jobs.
5726
+ * Implements dependency management through queue hints.
5727
+ */
5728
+ declare class InMemoryQueue implements IQueue {
5729
+ private eventBus;
5730
+ private resolver;
5731
+ private queues;
5732
+ private jobIdToQueueKey;
5733
+ private docIdToJobId;
5734
+ private jobIdToDocId;
5735
+ private completedJobs;
5736
+ private jobIndex;
5737
+ private isBlocked;
5738
+ private onDrainedCallback?;
5739
+ private isPausedFlag;
5740
+ constructor(eventBus: IEventBus, resolver: IDocumentModelResolver);
5741
+ private toErrorInfo;
4796
5742
  /**
4797
- * Eagerly updates cached metadata after document scope operations.
4798
- *
4799
- * Called by the job executor after CREATE_DOCUMENT, UPGRADE_DOCUMENT, or
4800
- * DELETE_DOCUMENT operations to keep the cache current.
4801
- *
4802
- * @param documentId - The document identifier
4803
- * @param branch - Branch name
4804
- * @param meta - The new metadata to cache
5743
+ * Creates a unique key for a document/scope/branch combination
4805
5744
  */
4806
- putDocumentMeta(documentId: string, branch: string, meta: CachedDocumentMeta): void;
5745
+ private createQueueKey;
4807
5746
  /**
4808
- * Invalidates cached document metadata.
4809
- *
4810
- * Call before reshuffling operations that modify the document scope, or
4811
- * when document state may have changed externally.
4812
- *
4813
- * @param documentId - The document identifier
4814
- * @param branch - Optional branch to narrow invalidation (if omitted, all branches)
4815
- * @returns Number of entries invalidated
5747
+ * Gets or creates a queue for the given key
4816
5748
  */
4817
- invalidate(documentId: string, branch?: string): number;
5749
+ private getQueue;
4818
5750
  /**
4819
- * Clears all cached document metadata.
5751
+ * Check if a document has any jobs currently executing
4820
5752
  */
4821
- clear(): void;
5753
+ private isDocumentExecuting;
4822
5754
  /**
4823
- * Performs startup initialization.
5755
+ * Mark a job as executing for its document
4824
5756
  */
4825
- startup(): Promise<void>;
5757
+ private markJobExecuting;
4826
5758
  /**
4827
- * Performs graceful shutdown.
5759
+ * Mark a job as no longer executing for its document
4828
5760
  */
4829
- shutdown(): Promise<void>;
4830
- }
4831
- //#endregion
4832
- //#region src/cache/buffer/ring-buffer.d.ts
4833
- /**
4834
- * RingBuffer is a generic circular buffer implementation that stores a fixed number
4835
- * of items. When the buffer is full, new items overwrite the oldest items.
4836
- *
4837
- * This implementation maintains O(1) time complexity for push operations and provides
4838
- * items in chronological order (oldest to newest) via getAll().
4839
- *
4840
- * @template T - The type of items stored in the buffer
4841
- */
4842
- declare class RingBuffer<T> {
4843
- private buffer;
4844
- private head;
4845
- private size;
4846
- private capacity;
4847
- constructor(capacity: number);
5761
+ private markJobComplete;
4848
5762
  /**
4849
- * Adds an item to the buffer. If the buffer is full, overwrites the oldest item.
4850
- *
4851
- * @param item - The item to add
5763
+ * Check if all dependencies for a job have been completed
4852
5764
  */
4853
- push(item: T): void;
5765
+ private areDependenciesMet;
4854
5766
  /**
4855
- * Returns all items in the buffer in chronological order (oldest to newest).
5767
+ * Returns the head of the sub-queue if its dependencies are met, or null.
4856
5768
  *
4857
- * @returns Array of items in insertion order
4858
- */
4859
- getAll(): T[];
4860
- /**
4861
- * Clears all items from the buffer.
4862
- */
4863
- clear(): void;
4864
- /**
4865
- * Gets the current number of items in the buffer.
5769
+ * The dispatcher only ever considers the head — a dep-blocked head holds
5770
+ * the rest of its sub-queue. This preserves per-(documentId, scope, branch)
5771
+ * FIFO regardless of how dependencies are authored, and makes the queue's
5772
+ * documented "serialized per document" invariant hold even when callers
5773
+ * omit queueHint dependencies on jobs that share a sub-queue.
4866
5774
  */
4867
- get length(): number;
4868
- }
4869
- //#endregion
4870
- //#region src/cache/kysely-write-cache.d.ts
4871
- type DocumentStream = {
4872
- key: string;
4873
- ringBuffer: RingBuffer<CachedSnapshot>;
4874
- };
4875
- /**
4876
- * In-memory write cache with keyframe persistence for PHDocuments.
4877
- *
4878
- * Caches document snapshots in ring buffers with LRU eviction. On cache miss,
4879
- * rebuilds documents from nearest keyframe or full operation history.
4880
- *
4881
- * **Performance Characteristics:**
4882
- * - Cache hit: O(1) lookup in ring buffer
4883
- * - Cold miss: O(n) where n is total operation count, or O(k) where k is operations since keyframe
4884
- * - Warm miss: O(m) where m is operations since cached revision
4885
- * - Eviction: O(1) for LRU tracking and removal
4886
- *
4887
- * **Thread Safety:**
4888
- * Not thread-safe. Designed for single-threaded job executor environment.
4889
- * External synchronization required for concurrent access across multiple executors.
4890
- *
4891
- * **Example:**
4892
- * ```typescript
4893
- * const cache = new KyselyWriteCache(
4894
- * keyframeStore,
4895
- * operationStore,
4896
- * registry,
4897
- * { maxDocuments: 1000, ringBufferSize: 10, keyframeInterval: 10 }
4898
- * );
4899
- *
4900
- * await cache.startup();
4901
- *
4902
- * // Retrieve or rebuild document
4903
- * const doc = await cache.getState(docId, docType, scope, branch, revision);
4904
- *
4905
- * // Cache result after job execution
4906
- * cache.putState(docId, docType, scope, branch, newRevision, updatedDoc);
4907
- *
4908
- * await cache.shutdown();
4909
- * ```
4910
- */
4911
- declare class KyselyWriteCache implements IWriteCache {
4912
- private streams;
4913
- private lruTracker;
4914
- private keyframeStore;
4915
- private operationStore;
4916
- private registry;
4917
- private config;
4918
- constructor(keyframeStore: IKeyframeStore, operationStore: IOperationStore, registry: IDocumentModelRegistry, config: WriteCacheConfig);
4919
- withScopedStores(operationStore: IOperationStore, keyframeStore: IKeyframeStore): KyselyWriteCache;
5775
+ private getNextJobWithMetDependencies;
5776
+ private getCreateDocumentType;
5777
+ enqueue(job: Job): Promise<void>;
5778
+ dequeue(documentId: string, scope: string, branch: string, signal?: AbortSignal): Promise<IJobExecutionHandle | null>;
5779
+ dequeueNext(signal?: AbortSignal): Promise<IJobExecutionHandle | null>;
5780
+ dequeueNextMatching(predicate: (meta: JobRoutingMeta) => boolean, signal?: AbortSignal): Promise<IJobExecutionHandle | null>;
5781
+ size(documentId: string, scope: string, branch: string): Promise<number>;
5782
+ totalSize(): Promise<number>;
5783
+ remove(jobId: string): Promise<boolean>;
5784
+ clear(documentId: string, scope: string, branch: string): Promise<void>;
5785
+ clearAll(): Promise<void>;
5786
+ hasJobs(): Promise<boolean>;
5787
+ completeJob(jobId: string): Promise<void>;
5788
+ failJob(jobId: string, error?: ErrorInfo$1): Promise<void>;
5789
+ deferJob(jobId: string): void;
5790
+ retryJob(jobId: string, error?: ErrorInfo$1, accounting?: RetryAccounting): Promise<void>;
4920
5791
  /**
4921
- * Initializes the write cache.
4922
- * Currently a no-op as keyframe store lifecycle is managed externally.
5792
+ * Check if the queue is drained and call the callback if it is
4923
5793
  */
4924
- startup(): Promise<void>;
5794
+ private checkDrained;
4925
5795
  /**
4926
- * Shuts down the write cache.
4927
- * Currently a no-op as keyframe store lifecycle is managed externally.
5796
+ * Returns true if and only if all jobs have been resolved.
4928
5797
  */
4929
- shutdown(): Promise<void>;
5798
+ get isDrained(): boolean;
4930
5799
  /**
4931
- * Retrieves document state at a specific revision from cache or rebuilds it.
4932
- *
4933
- * Cache hit path: Returns cached snapshot if available (O(1))
4934
- * Warm miss path: Rebuilds from cached base revision + incremental ops
4935
- * Cold miss path: Rebuilds from keyframe or from scratch using all operations
4936
- *
4937
- * @param documentId - The document identifier
4938
- * @param scope - The operation scope
4939
- * @param branch - The operation branch
4940
- * @param targetRevision - The target revision, or undefined for newest
4941
- * @param signal - Optional abort signal to cancel the operation
4942
- * @returns The document at the target revision
4943
- * @throws {Error} "Operation aborted" if signal is aborted
4944
- * @throws {ModuleNotFoundError} If document type not registered in registry
4945
- * @throws {Error} "Failed to rebuild document" if operation store fails
4946
- * @throws {Error} If reducer throws during operation application
4947
- * @throws {Error} If document serialization fails
5800
+ * Blocks the queue from accepting new jobs.
5801
+ * @param onDrained - Optional callback to call when the queue is drained
4948
5802
  */
4949
- getState(documentId: string, scope: string, branch: string, targetRevision?: number, signal?: AbortSignal): Promise<PHDocument>;
5803
+ block(onDrained?: () => void): void;
4950
5804
  /**
4951
- * Stores a document snapshot in the cache at a specific revision.
4952
- *
4953
- * The cached document is a shallow copy of the input with its operation history
4954
- * truncated to the last operation per scope and its clipboard cleared. This keeps
4955
- * memory use and copy costs constant regardless of operation count. Consumers of
4956
- * getState() must not rely on the full operation history being present; the only
4957
- * guaranteed invariant is that operations[scope].at(-1) reflects the latest
4958
- * operation index for each scope.
4959
- *
4960
- * Updates LRU tracker and may evict least recently used stream if at capacity.
4961
- * Asynchronously persists keyframes at configured intervals (fire-and-forget).
4962
- *
4963
- * @param documentId - The document identifier
4964
- * @param scope - The operation scope
4965
- * @param branch - The operation branch
4966
- * @param revision - The revision number
4967
- * @param document - The document to cache
4968
- * @throws {Error} If document serialization fails
5805
+ * Unblocks the queue from accepting new jobs.
4969
5806
  */
4970
- putState(documentId: string, scope: string, branch: string, revision: number, document: PHDocument): void;
5807
+ unblock(): void;
4971
5808
  /**
4972
- * Invalidates cached document streams.
4973
- *
4974
- * Supports three invalidation scopes:
4975
- * - Document-level: invalidate(documentId) - removes all streams for document
4976
- * - Scope-level: invalidate(documentId, scope) - removes all branches for scope
4977
- * - Stream-level: invalidate(documentId, scope, branch) - removes specific stream
4978
- *
4979
- * @param documentId - The document identifier
4980
- * @param scope - Optional scope to narrow invalidation
4981
- * @param branch - Optional branch to narrow invalidation (requires scope)
4982
- * @returns The number of streams evicted
5809
+ * Pauses job dequeuing. Jobs can still be enqueued but dequeueNext() will return null.
4983
5810
  */
4984
- invalidate(documentId: string, scope?: string, branch?: string): number;
5811
+ pause(): void;
4985
5812
  /**
4986
- * Clears the entire cache, removing all cached document streams.
4987
- * Resets LRU tracking state. This operation always succeeds.
5813
+ * Resumes job dequeuing and emits JOB_AVAILABLE events for pending jobs to wake up executors.
4988
5814
  */
4989
- clear(): void;
5815
+ resume(): Promise<void>;
4990
5816
  /**
4991
- * Retrieves a specific stream for a document. Exposed on the implementation
4992
- * for testing, but not on the interface.
4993
- *
4994
- * @internal
5817
+ * Returns whether job dequeuing is paused.
4995
5818
  */
4996
- getStream(documentId: string, scope: string, branch: string): DocumentStream | undefined;
4997
- private findNearestKeyframe;
4998
- private coldMissRebuild;
5819
+ get paused(): boolean;
4999
5820
  /**
5000
- * Resolves which module version to use for a given operation in phase 2.
5001
- *
5002
- * Uses the validated-upgrade boundary rules from D7:
5003
- * - If `input.revision` is present: op.index < revision[scope] → before the upgrade boundary
5004
- * - Otherwise: timestamp fallback
5005
- * - Falls back to final module version when neither is decidable
5821
+ * Returns all pending jobs across all queues.
5006
5822
  */
5007
- private resolveModuleVersionForOp;
5008
- private warmMissRebuild;
5009
- private findNearestOlderSnapshot;
5010
- private makeStreamKey;
5011
- private getOrCreateStream;
5012
- private isKeyframeRevision;
5013
- }
5014
- //#endregion
5015
- //#region src/storage/kysely/store.d.ts
5016
- declare class KyselyOperationStore implements IOperationStore {
5017
- private db;
5018
- private trx?;
5019
- constructor(db: Kysely<Database$1>);
5020
- private get queryExecutor();
5021
- withTransaction(trx: Transaction<Database$1>): KyselyOperationStore;
5022
- apply(documentId: string, documentType: string, scope: string, branch: string, revision: number, fn: (txn: AtomicTxn) => void | Promise<void>, signal?: AbortSignal): Promise<Operation[]>;
5023
- private resolveUniqueConstraint;
5024
- private executeApply;
5025
- private findIdempotentReplay;
5026
- getSince(documentId: string, scope: string, branch: string, revision: number, filter?: OperationFilter, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<Operation>>;
5027
- getSinceId(id: number, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<OperationWithContext$1>>;
5028
- getConflicting(documentId: string, scope: string, branch: string, minTimestamp: string, paging?: PagingOptions, signal?: AbortSignal): Promise<PagedResults<Operation>>;
5029
- getRevisions(documentId: string, branch: string, signal?: AbortSignal): Promise<DocumentRevisions>;
5030
- private rowToOperation;
5031
- private rowToOperationWithContext;
5823
+ getPendingJobs(): Job[];
5824
+ /**
5825
+ * Returns a map of document IDs to sets of executing job IDs.
5826
+ */
5827
+ getExecutingJobIds(): Map<string, Set<string>>;
5828
+ /**
5829
+ * Returns a job by ID from the job index.
5830
+ */
5831
+ getJob(jobId: string): Job | undefined;
5032
5832
  }
5033
5833
  //#endregion
5034
- //#region src/storage/kysely/keyframe-store.d.ts
5035
- declare class KyselyKeyframeStore implements IKeyframeStore {
5036
- private db;
5037
- private trx?;
5038
- constructor(db: Kysely<Database$1>);
5039
- private get queryExecutor();
5040
- withTransaction(trx: Transaction<Database$1>): KyselyKeyframeStore;
5041
- putKeyframe(documentId: string, scope: string, branch: string, revision: number, document: PHDocument, signal?: AbortSignal): Promise<void>;
5042
- findNearestKeyframe(documentId: string, scope: string, branch: string, targetRevision: number, signal?: AbortSignal): Promise<{
5043
- revision: number;
5044
- document: PHDocument;
5045
- } | undefined>;
5046
- listKeyframes(documentId: string, scope?: string, branch?: string, signal?: AbortSignal): Promise<Array<{
5047
- scope: string;
5048
- branch: string;
5049
- revision: number;
5050
- document: PHDocument;
5051
- }>>;
5052
- deleteKeyframes(documentId: string, scope?: string, branch?: string, signal?: AbortSignal): Promise<number>;
5834
+ //#region src/job-tracker/in-memory-job-tracker.d.ts
5835
+ /**
5836
+ * In-memory implementation of IJobTracker.
5837
+ * Maintains job status in a Map for synchronous access.
5838
+ * Subscribes to operation events to update job states.
5839
+ */
5840
+ declare class InMemoryJobTracker implements IJobTracker {
5841
+ private eventBus;
5842
+ private jobs;
5843
+ private unsubscribers;
5844
+ constructor(eventBus: IEventBus);
5845
+ private subscribeToEvents;
5846
+ private handleWriteReady;
5847
+ private handleReadReady;
5848
+ private handleJobFailed;
5849
+ shutdown(): void;
5850
+ registerJob(jobInfo: JobInfo): void;
5851
+ markRunning(jobId: string): void;
5852
+ markFailed(jobId: string, error: ErrorInfo$1, job?: Job): void;
5853
+ getJobStatus(jobId: string): JobInfo | null;
5053
5854
  }
5054
5855
  //#endregion
5055
- //#region src/executor/execution-scope.d.ts
5056
- interface ExecutionStores {
5057
- operationStore: IOperationStore;
5058
- operationIndex: IOperationIndex;
5059
- writeCache: IWriteCache;
5060
- documentMetaCache: IDocumentMetaCache;
5061
- collectionMembershipCache: ICollectionMembershipCache;
5062
- }
5063
- interface IExecutionScope {
5064
- run<T>(fn: (stores: ExecutionStores) => Promise<T>, signal?: AbortSignal): Promise<T>;
5856
+ //#region src/executor/simple-job-executor-manager.d.ts
5857
+ type JobExecutorFactory = () => IJobExecutor;
5858
+ /**
5859
+ * Manages multiple job executors and coordinates job distribution.
5860
+ * Listens for job available events and dispatches jobs to executors.
5861
+ */
5862
+ declare class SimpleJobExecutorManager implements IJobExecutorManager {
5863
+ private executorFactory;
5864
+ private eventBus;
5865
+ private queue;
5866
+ private jobTracker;
5867
+ private logger;
5868
+ private resolver;
5869
+ private executors;
5870
+ private isRunning;
5871
+ private activeJobs;
5872
+ private totalJobsProcessed;
5873
+ private unsubscribe?;
5874
+ private deferredJobs;
5875
+ private resultHandler;
5876
+ private jobTimeoutMs;
5877
+ constructor(executorFactory: JobExecutorFactory, eventBus: IEventBus, queue: IQueue, jobTracker: IJobTracker, logger: ILogger, resolver: IDocumentModelResolver, jobTimeoutMs?: number);
5878
+ start(numExecutors: number): Promise<void>;
5879
+ stop(graceful?: boolean): Promise<void>;
5880
+ getExecutors(): IJobExecutor[];
5881
+ getStatus(): ExecutorManagerStatus;
5882
+ private processNextJob;
5883
+ private checkForMoreJobs;
5884
+ private processExistingJobs;
5885
+ private flushDeferredJobs;
5065
5886
  }
5066
5887
  //#endregion
5067
5888
  //#region src/executor/simple-job-executor.d.ts
@@ -5079,6 +5900,8 @@ declare class SimpleJobExecutor implements IJobExecutor {
5079
5900
  private collectionMembershipCache;
5080
5901
  private driveContainerTypes;
5081
5902
  private config;
5903
+ private featureFlags;
5904
+ private decisionModel;
5082
5905
  private signatureVerifierModule;
5083
5906
  private documentActionHandler;
5084
5907
  private executionScope;
@@ -5091,6 +5914,69 @@ declare class SimpleJobExecutor implements IJobExecutor {
5091
5914
  private getCollectionMembershipsForOperations;
5092
5915
  private processActions;
5093
5916
  private executeRegularAction;
5917
+ /**
5918
+ * Orders a write by timestamp and decides it where it lands. The caller
5919
+ * supplies the timestamp, so a write can belong before operations already
5920
+ * stored; those are re-appended alongside it, the way a load reshuffles.
5921
+ *
5922
+ * Deciding a backdated write at the stream heads instead of at its position
5923
+ * would overwrite the verdict every other replica computes for it.
5924
+ */
5925
+ private positionByTimestamp;
5926
+ /**
5927
+ * Decides each operation where it lands and carries the verdict on it. A
5928
+ * refused submitted action is reported to the caller and nothing is stored; a
5929
+ * refused operation the reshuffle merely moved keeps its verdict, because it
5930
+ * already holds a position.
5931
+ *
5932
+ * The operations carry the indexes and skips they will be stored at, because
5933
+ * the walk resolves skips before it orders them.
5934
+ */
5935
+ private evaluatePositioned;
5936
+ /**
5937
+ * The scopes a re-evaluation pass visits, in a fixed order.
5938
+ *
5939
+ * The revisions map comes from a query with no ORDER BY, and the order is
5940
+ * load-bearing: each scope's pass re-reads the auth stream, and the walk skips
5941
+ * an operation by its stored denial, so a denial this pass just wrote is
5942
+ * visible to a later-visited scope and invisible to an earlier one. The model's
5943
+ * own projection order leads, then the rest sorted, so the pass is reproducible
5944
+ * across replicas and across runs.
5945
+ */
5946
+ private evaluationOrder;
5947
+ /**
5948
+ * The first timestamp in the batch that does not strictly exceed everything
5949
+ * ahead of it, or undefined when the whole batch is monotonic.
5950
+ *
5951
+ * The bound is carried forward rather than compared against one stored maximum,
5952
+ * because a single execute can carry several auth actions stamped in the same
5953
+ * millisecond. Letting a tie through would store a stream the position walk
5954
+ * then refuses to read, with no repair path.
5955
+ */
5956
+ private firstNonMonotonicTimestamp;
5957
+ /** The operations a batch of submitted actions appends at the scope's tail. */
5958
+ private appendedOperations;
5959
+ /**
5960
+ * Re-evaluates the document when a write meets both criteria: it was written
5961
+ * to a stream the model reads, and it is timestamped before an operation
5962
+ * already stored. The caller supplies the timestamp and the reactor does not replace
5963
+ * it, so a mutation job can write such an operation just as a load job can,
5964
+ * which is why both executeJob and executeLoadJob call this.
5965
+ */
5966
+ private reevaluateIfCriteriaMet;
5967
+ /**
5968
+ * Re-evaluates every scope the model evaluates. Where an operation's
5969
+ * evaluation differs from what is stored, the tail from that operation is
5970
+ * re-appended, carrying a skip that spans the indices it supersedes.
5971
+ */
5972
+ private reevaluateDocument;
5973
+ /**
5974
+ * Re-judges a document's stored operations because a read-set stream in
5975
+ * another document (a group) gained an operation. The trigger timestamp
5976
+ * bounds the work: an operation later than everything this document holds
5977
+ * cannot change any evaluation, so the pass is skipped.
5978
+ */
5979
+ private executeReevaluationJob;
5094
5980
  private executeLoadJob;
5095
5981
  private accumulateResultOrReturnError;
5096
5982
  }
@@ -5159,6 +6045,49 @@ declare class DocumentModelRegistry implements IDocumentModelRegistry {
5159
6045
  getUpgradeReducer(documentType: string, fromVersion: number, toVersion: number): UpgradeReducer<any, any>;
5160
6046
  }
5161
6047
  //#endregion
6048
+ //#region src/decision/build-decision-model.d.ts
6049
+ /**
6050
+ * Reads each projection's stream through the supplied reader, recording the
6051
+ * revision observed. Static projections resolve first; derived projections
6052
+ * see only those and contribute a map from document id to state. Each
6053
+ * distinct stream is read once and yields one append condition entry.
6054
+ */
6055
+ declare function buildDecisionModel<M>(reader: IStreamStateReader, definition: (target: DecisionTarget) => DecisionModel<M>, target: DecisionTarget, signal?: AbortSignal): Promise<BuiltDecisionModel<M>>;
6056
+ //#endregion
6057
+ //#region src/decision/auth-decision-model.d.ts
6058
+ type AuthDecisionModel = {
6059
+ document: PHDocumentState;
6060
+ auth: PHAuthState;
6061
+ };
6062
+ /** This decision model uses both the document and the auth streams. */
6063
+ declare function authDecisionModel(target: DecisionTarget): DecisionModel<AuthDecisionModel>;
6064
+ //#endregion
6065
+ //#region src/decision/stream-order.d.ts
6066
+ /** Where a stream's stored order contradicts its timestamps. */
6067
+ type OutOfOrderPair = {
6068
+ previous: Operation;
6069
+ current: Operation;
6070
+ /**
6071
+ * `descending` cannot be walked at all. `tied` walks fine — the intra-stream
6072
+ * rule breaks the tie by index — but violates the monotonic auth rule, so a
6073
+ * stream holding one can never be replicated to a peer that lacks it.
6074
+ */
6075
+ kind: "descending" | "tied";
6076
+ };
6077
+ /**
6078
+ * The first pair of effective operations whose stored order contradicts their
6079
+ * timestamps, or undefined when the stream is in position order.
6080
+ *
6081
+ * Such a stream cannot be walked, and the auth stream is never reshuffled once
6082
+ * the monotonic rule is on, so run this before enabling enforcement on a fleet.
6083
+ *
6084
+ * `requireStrict` additionally rejects a tie, which is what the auth stream's
6085
+ * monotonic rule requires and what the walk alone does not care about.
6086
+ */
6087
+ declare function firstOutOfOrderPair(operations: Operation[], options?: {
6088
+ requireStrict?: boolean;
6089
+ }): OutOfOrderPair | undefined;
6090
+ //#endregion
5162
6091
  //#region src/read-models/base-read-model.d.ts
5163
6092
  type BaseReadModelConfig = {
5164
6093
  readModelId: string;
@@ -5243,7 +6172,7 @@ declare class KyselyDocumentIndexer extends BaseReadModel implements IDocumentIn
5243
6172
  * serialized so the executor can return to dispatch without holding ordering
5244
6173
  * implicitly.
5245
6174
  */
5246
- declare class ReadModelCoordinator implements IReadModelCoordinator {
6175
+ declare class ReadModelCoordinator implements ILiveReadModelCoordinator {
5247
6176
  private eventBus;
5248
6177
  readonly preReady: IReadModel[];
5249
6178
  readonly postReady: IReadModel[];
@@ -5262,6 +6191,7 @@ declare class ReadModelCoordinator implements IReadModelCoordinator {
5262
6191
  */
5263
6192
  drain(): Promise<void>;
5264
6193
  getChainDepth(): number;
6194
+ addReadModel(readModel: IReadModel, stage: ReadModelRegistrationStage): void;
5265
6195
  private handleWriteReady;
5266
6196
  private emitEmptyReadReady;
5267
6197
  private runChain;
@@ -5275,8 +6205,31 @@ declare class ReadModelCoordinator implements IReadModelCoordinator {
5275
6205
  type Database$2 = Database$1 & DocumentViewDatabase;
5276
6206
  declare class KyselyDocumentView extends BaseReadModel implements IDocumentView {
5277
6207
  private operationStore;
6208
+ /**
6209
+ * Whether a single-document read serves a deleted document's state as of the
6210
+ * deletion rather than hiding it. Only meaningful with `documentDecisions`,
6211
+ * which is what makes deletion positional. Listings omit it either way.
6212
+ */
6213
+ private readonly servesDeletionBoundary;
5278
6214
  private _db;
5279
- constructor(db: Kysely<Database$2>, operationStore: IOperationStore, operationIndex: IOperationIndex, writeCache: IWriteCache, consistencyTracker: IConsistencyTracker);
6215
+ constructor(db: Kysely<Database$2>, operationStore: IOperationStore, operationIndex: IOperationIndex, writeCache: IWriteCache, consistencyTracker: IConsistencyTracker,
6216
+ /**
6217
+ * Whether a single-document read serves a deleted document's state as of the
6218
+ * deletion rather than hiding it. Only meaningful with `documentDecisions`,
6219
+ * which is what makes deletion positional. Listings omit it either way.
6220
+ */
6221
+
6222
+ servesDeletionBoundary: boolean);
6223
+ /**
6224
+ * Indexes committed operations into DocumentSnapshot rows. CREATE_DOCUMENT
6225
+ * only seeds header/document/auth. UPGRADE_DOCUMENT reindexes every scope
6226
+ * present in resultingState when the operation vouches for them — a seed
6227
+ * carrying initialState or a migration stamped with the __migrated marker
6228
+ * — since the upgrade reducer may have reshaped any of them; upgrades
6229
+ * without either fall back to header/document/auth, because their sibling
6230
+ * echoes may be stale. All other action types index only header and their
6231
+ * own scope.
6232
+ */
5280
6233
  protected commitOperations(items: OperationWithContext$1[]): Promise<void>;
5281
6234
  exists(documentIds: string[], consistencyToken?: ConsistencyToken, signal?: AbortSignal): Promise<boolean[]>;
5282
6235
  get<TDocument extends PHDocument>(documentId: string, view?: ViewFilter$1, consistencyToken?: ConsistencyToken, signal?: AbortSignal): Promise<TDocument>;
@@ -5408,6 +6361,8 @@ declare class GqlRequestChannel implements IChannel {
5408
6361
  private isPushing;
5409
6362
  private pendingDrain;
5410
6363
  private receivingPages;
6364
+ /** Cleared for good the first time the remote rejects {@link DECISION_FIELDS}. */
6365
+ private peerServesDecisionFields;
5411
6366
  private isRecovering;
5412
6367
  private connectionState;
5413
6368
  /** Latest unrecoverable error was an auth rejection; cleared on connect. */
@@ -5454,6 +6409,18 @@ declare class GqlRequestChannel implements IChannel {
5454
6409
  * Queries the remote GraphQL endpoint for sync envelopes.
5455
6410
  */
5456
6411
  private pollSyncEnvelopes;
6412
+ /**
6413
+ * True when the remote rejected the query for naming a field it does not
6414
+ * have. Selecting an unknown field fails validation for the whole query, so
6415
+ * an unhandled one takes the channel's polling down until the process
6416
+ * restarts rather than degrading.
6417
+ */
6418
+ private rejectsDecisionFields;
6419
+ /**
6420
+ * The poll query. `withDecisionFields` selects the two fields added with the
6421
+ * auth projection; a remote on the previous schema is polled without them.
6422
+ */
6423
+ private pollQuery;
5457
6424
  /**
5458
6425
  * Registers or updates this channel on the remote server via GraphQL mutation.
5459
6426
  * Returns the remote's ack ordinal so the client can trim its outbox.
@@ -5616,6 +6583,18 @@ declare function batchOperationsByDocument(operations: OperationWithContext$1[])
5616
6583
  * jobId; all other jobIds are remapped so external dependencies still resolve.
5617
6584
  */
5618
6585
  declare function consolidateSyncOperations(syncOps: SyncOperation[]): SyncOperation[];
6586
+ /**
6587
+ * Classifies a failure by error name rather than `instanceof`, because a failure
6588
+ * that crossed the pooled-worker boundary arrives as plain data.
6589
+ */
6590
+ declare function classifyJobFailure(errorName: string): SyncOperationErrorType;
6591
+ /** The explicit type when something else carried it, else derived by name. */
6592
+ declare function syncOperationErrorType(error: ChannelError | undefined): SyncOperationErrorType;
6593
+ /**
6594
+ * A held auth operation must not quarantine: reconciling the two policies needs
6595
+ * the traffic a quarantine would stop.
6596
+ */
6597
+ declare function quarantinesDocument(errorType: SyncOperationErrorType): boolean;
5619
6598
  //#endregion
5620
6599
  //#region src/admin/types.d.ts
5621
6600
  type KeyframeValidationIssue = {
@@ -5631,11 +6610,20 @@ type SnapshotValidationIssue = {
5631
6610
  snapshotHash: string;
5632
6611
  replayedHash: string;
5633
6612
  };
6613
+ /** Effective operations whose stored order contradicts their timestamps. */
6614
+ type StreamOrderIssue = {
6615
+ scope: string;
6616
+ branch: string;
6617
+ previous: Operation;
6618
+ current: Operation;
6619
+ kind: OutOfOrderPair["kind"];
6620
+ };
5634
6621
  type ValidationResult = {
5635
6622
  documentId: string;
5636
6623
  isConsistent: boolean;
5637
6624
  keyframeIssues: KeyframeValidationIssue[];
5638
6625
  snapshotIssues: SnapshotValidationIssue[];
6626
+ streamOrderIssues: StreamOrderIssue[];
5639
6627
  };
5640
6628
  type RebuildResult = {
5641
6629
  documentId: string;
@@ -5659,6 +6647,7 @@ declare class DocumentIntegrityService implements IDocumentIntegrityService {
5659
6647
  validateDocument(documentId: string, branch?: string, signal?: AbortSignal): Promise<ValidationResult>;
5660
6648
  rebuildKeyframes(documentId: string, branch?: string, signal?: AbortSignal): Promise<RebuildResult>;
5661
6649
  rebuildSnapshots(documentId: string, branch?: string, signal?: AbortSignal): Promise<RebuildResult>;
6650
+ private findStreamOrderIssues;
5662
6651
  private discoverScopes;
5663
6652
  }
5664
6653
  //#endregion
@@ -5707,5 +6696,5 @@ declare class ProcessorManager extends BaseReadModel implements IProcessorManage
5707
6696
  private deleteProcessorCursors;
5708
6697
  }
5709
6698
  //#endregion
5710
- export { type AbortMessage, type AtomicTxn, type AttachmentHash, type AttachmentRef, BaseReadModel, type BatchExecutionRequest, type BatchExecutionResult, type BatchLoadRequest, type BatchLoadResult, type BuiltInReadModelKind, type CachedSnapshot, type ChannelConfig, ChannelError, ChannelErrorSource, type ChannelHealth, type ChannelMeta, ChannelScheme, type ConnectionState, type ConnectionStateChangeCallback, type ConnectionStateChangedEvent, type ConnectionStateSnapshot, type ConsistencyCoordinate, type ConsistencyKey, type ConsistencyToken, ConsistencyTracker, DEFAULT_DRIVE_CONTAINER_TYPES, DRIVE_AUTH_ERROR_MESSAGES, type Database, type DbConfig, type DeadLetterAddedEvent, DefaultSubscriptionErrorHandler, type DocumentChangeEvent, DocumentChangeType, type DocumentGraphEdge, type DocumentIndexerDatabase, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, type DocumentModelSource, type DocumentModelSpec, type DocumentRelationship, type DocumentRevisions, type DocumentStreamKey, type DocumentViewDatabase, DriveClient, DriveCollectionId, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, EventBus, EventBusAggregateError, type ExecuteMessage, type ExecutionJobPlan, type ExecutorStartedEvent, type ExecutorStoppedEvent, type FactorySpec, type FileModelSource, type GqlChannelConfig, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, type HeartbeatMessage, type IChannel, type IChannelFactory, type IConsistencyTracker, type IDocumentGraph, type IDocumentIndexer, type IDocumentIntegrityService, type IDocumentModelLoader, type IDocumentModelRegistry, type IDocumentModelResolver, type IDocumentView, type IDriveClient, type IEventBus, type IJobAwaiter, type IJobExecutor, type IJobExecutorManager, type IJobTracker, type IKeyframeStore, type IMailbox, type IOperationIndex, type IOperationStore, type IPollTimer, type IProcessor, type IProcessorHostModule, type IProcessorManager, type IProjectionTransport, type IQueue, type IReactor, type IReactorClient, type IReactorSubscriptionManager, type IReadModel, type IReadModelCoordinator, type IRelationalDb, type ISubscriptionErrorHandler, type ISyncCursorStorage, type ISyncManager, type ISyncRemoteStorage, type ISyncStatusTracker, type IWriteCache, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, type InProcessReactorClientModule, type InProcessReactorModule, type InProcessSyncModule, type InitMessage, type InsertableDocumentSnapshot, IntervalPollTimer, InvalidModuleError, type Job, type JobAvailableEvent, JobAwaiter, type JobCompletedEvent, type JobExecutorConfig, JobExecutorEventTypes, type JobExecutorFactory, type JobFailedEvent, type JobInfo, type JobPendingEvent, type JobReadReadyEvent, type JobResult, type JobRunningEvent, type JobStartedEvent, JobStatus, type JobWriteReadyEvent, type JobWriteReadyPayload, type JwtHandler, type KeyframeSnapshot, type KeyframeValidationIssue, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, type LoadJobPlan, type LoadModelMessage, type LogMessage, Mailbox, type MetricsMessage, type ModelLoadFailedMessage, type ModelLoadedEvent, type ModelLoadedMessage, type ModelManifestEntry, ModuleNotFoundError, type ModuleRef, NullDocumentModelResolver, type OperationBatch, type OperationContext, type OperationFilter, type OperationIndexEntry, type OperationTable, type OperationWithContext, OptimisticLockError, type PackageModelSource, type PagedResults, type PagingOptions, type ParentMessage, type ParsedDriveUrl, type ParsedPaging, PollBehavior, PollingChannelError, type PoolInstrumentation, type PoolStats, type ProcessorApp, type ProcessorFactory, type ProcessorFactoryBuilder, type ProcessorFilter, ProcessorManager, type ProcessorRecord, type ProcessorStatus, type ProjectionShardBuilderConfig, type ProjectionShardManagerConfig, type ProjectionWorkerFactory, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, type ReactorClientModule, ReactorEventTypes, type ReactorFeatures, type JobFailedEvent$1 as ReactorJobFailedEvent, type ReactorModule, ReactorSubscriptionManager, type ReadModelBatchCompletedEvent, ReadModelCoordinator, type ReadModelFactory, type ReadModelFactoryDeps, type ReadModelIndexedEvent, type ReadModelIndexingStage, type ReadModelStage, type ReadyMessage, type RebuildResult, RelationalDbProcessor, RelationshipChangeType, type Remote, type RemoteCursor, type RemoteFilter, type RemoteMeta, type RemoteOptions, type RemoteRecord, type RemoteStatus, type ResultMessage, RevisionMismatchError, type SanitizedArg, type SearchFilter, type ShutdownMessage, type ShutdownStatus, type SignatureVerificationHandler, type SignatureVerifierSpec, type SignerConfig, SimpleJobExecutorManager, type SnapshotValidationIssue, type Database$1 as StorageDatabase, type SubscriptionErrorContext, SyncBuilder, type SyncEnvelope, type SyncEnvelopeType, SyncEventTypes, type SyncFailedEvent, type SyncModule, SyncOperation, SyncOperationAggregateError, type SyncOperationErrorType, SyncOperationStatus, type SyncPendingEvent, SyncStatus, type SyncStatusChangeCallback, SyncStatusTracker, type SyncSucceededEvent, type TrackedProcessor, type Unsubscribe, type ValidationResult, type ViewFilter, type ErrorInfo as WorkerErrorInfo, type WorkerMessage, type WorkerPoolConfig, type WorkerPoolOptions, type WriteCacheConfig, addRelationshipAction, batchOperationsByDocument, consolidateSyncOperations, createDocumentAction, createForwardingLogger, createMutableShutdownStatus, createRelationalDb, deleteDocumentAction, documentActions, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, getMigrationStatus, instrumentPgPool, isDriveAuthError, makeConsistencyKey, parseDriveUrl, parsePagingOptions, removeRelationshipAction, runMigrations, sanitizeArg, trimMailboxFromAckOrdinal, updateRelationshipAction, upgradeDocumentAction, workerEntryPath };
6699
+ export { ALWAYS_READABLE_SCOPES, APPEND_CONDITION_FAILED_PREFIX, type AbortMessage, type ActionCandidate, type ActionEvaluationConfig, type ActionEvaluations, type AdmissionDecision, type AppendCondition, AppendConditionFailedError, type AppendConditionStream, type AtomicTxn, type AttachmentHash, type AttachmentRef, type AuthDecisionModel, AuthEnforcementDisabledError, BareReadGate, BaseReadModel, type BatchExecutionRequest, type BatchExecutionResult, type BatchLoadRequest, type BatchLoadResult, type BuiltDecisionModel, type BuiltInReadModelKind, type CachedSnapshot, type ChannelConfig, ChannelError, ChannelErrorSource, type ChannelHealth, type ChannelMeta, ChannelScheme, type ConnectionState, type ConnectionStateChangeCallback, type ConnectionStateChangedEvent, type ConnectionStateSnapshot, type ConsistencyCoordinate, type ConsistencyKey, type ConsistencyToken, ConsistencyTracker, DEFAULT_DRIVE_CONTAINER_TYPES, DRIVE_AUTH_ERROR_MESSAGES, type Database, type DbConfig, type DeadLetterAddedEvent, type DecisionContext, type DecisionModel, type DecisionTarget, DefaultSubscriptionErrorHandler, type DocumentChangeEvent, DocumentChangeType, type DocumentDecisionModel, type DocumentGraphEdge, type DocumentIndexerDatabase, DocumentIntegrityService, DocumentModelRegistry, DocumentModelResolver, type DocumentModelSource, type DocumentModelSpec, type DocumentRelationship, type DocumentRevisions, type DocumentStreamKey, type DocumentViewDatabase, DriveClient, DriveCollectionId, DuplicateManifestError, DuplicateModuleError, DuplicateOperationError, type Evaluation, EventBus, EventBusAggregateError, type ExecuteMessage, type ExecutionJobPlan, type ExecutorStartedEvent, type ExecutorStoppedEvent, type FactorySpec, type FileModelSource, type GqlChannelConfig, GqlRequestChannel, GqlRequestChannelFactory, GqlResponseChannel, GqlResponseChannelFactory, type HeartbeatMessage, type IChannel, type IChannelFactory, type IConsistencyTracker, type IDocumentGraph, type IDocumentIndexer, type IDocumentIntegrityService, type IDocumentModelLoader, type IDocumentModelRegistry, type IDocumentModelResolver, type IDocumentView, type IDriveClient, type IEventBus, type IJobAwaiter, type IJobExecutor, type IJobExecutorManager, type IJobTracker, type IKeyframeStore, type ILiveReadModelCoordinator, type IMailbox, type IOperationIndex, type IOperationStore, type IPollTimer, type IProcessor, type IProcessorHostModule, type IProcessorManager, type IProjectionTransport, type IQueue, type IReactor, type IReactorClient, type IReactorSubscriptionManager, type IReadGate, type IReadModel, type IReadModelCoordinator, type IRelationalDb, type IStreamStateReader, type ISubscriptionErrorHandler, type ISyncCursorStorage, type ISyncManager, type ISyncRemoteStorage, type ISyncStatusTracker, type IWriteCache, SimpleJobExecutor as InMemoryJobExecutor, SimpleJobExecutor, InMemoryJobTracker, InMemoryQueue, type InProcessReactorClientModule, type InProcessReactorModule, type InProcessSyncModule, type InitMessage, type InsertableDocumentSnapshot, IntervalPollTimer, InvalidModuleError, type Job, type JobAvailableEvent, JobAwaiter, type JobCompletedEvent, type JobExecutorConfig, JobExecutorEventTypes, type JobExecutorFactory, type JobFailedEvent, type JobInfo, type JobPendingEvent, type JobReadReadyEvent, type JobResult, type JobRunningEvent, type JobStartedEvent, JobStatus, type JobWriteReadyEvent, type JobWriteReadyPayload, type JwtHandler, type KeyframeSnapshot, type KeyframeValidationIssue, KyselyDocumentIndexer, KyselyDocumentView, KyselyKeyframeStore, KyselyOperationStore, KyselySyncCursorStorage, KyselySyncRemoteStorage, KyselyWriteCache, type LoadJobPlan, type LoadModelMessage, type LogMessage, Mailbox, type MetricsMessage, type ModelLoadFailedMessage, type ModelLoadedEvent, type ModelLoadedMessage, type ModelManifestEntry, ModelReadGate, ModuleNotFoundError, type ModuleRef, NullDocumentModelResolver, type OperationBatch, type OperationContext, type OperationFilter, type OperationIndexEntry, type OperationTable, type OperationWithContext, OptimisticLockError, type OutOfOrderPair, type PackageModelSource, type PagedResults, type PagingOptions, type ParentMessage, type ParsedDriveUrl, type ParsedPaging, PollBehavior, PollingChannelError, type PoolInstrumentation, type PoolStats, type ProcessorApp, type ProcessorFactory, type ProcessorFactoryBuilder, type ProcessorFilter, ProcessorManager, type ProcessorRecord, type ProcessorStatus, type Projection, type ProjectionShardBuilderConfig, type ProjectionShardManagerConfig, type ProjectionWorkerFactory, PropagationMode, QueueEventTypes, REACTOR_SCHEMA, Reactor, ReactorBuilder, ReactorClient, ReactorClientBuilder, type ReactorClientModule, ReactorEventTypes, type ReactorFeatureFlags, type ReactorFeatures, type JobFailedEvent$1 as ReactorJobFailedEvent, type ReactorModule, ReactorSubscriptionManager, type ReadModelBatchCompletedEvent, ReadModelCoordinator, type ReadModelFactory, type ReadModelFactoryDeps, type ReadModelIndexedEvent, type ReadModelIndexingStage, type ReadModelRegistrationStage, type ReadModelStage, type ReadyMessage, type RebuildResult, type RegisteredDecisionModel, RelationalDbProcessor, RelationshipChangeType, type Remote, type RemoteCursor, type RemoteFilter, type RemoteMeta, type RemoteOptions, type RemoteRecord, type RemoteStatus, type ResultMessage, RetryAccounting, RevisionMismatchError, type SanitizedArg, type SearchFilter, SeededStateReader, type ShutdownMessage, type ShutdownStatus, type SignatureVerificationHandler, type SignatureVerifierSpec, type SignerConfig, SimpleJobExecutorManager, type SnapshotValidationIssue, type Database$1 as StorageDatabase, type StreamOrderIssue, type StreamQuery, type SubscriptionErrorContext, SyncBuilder, type SyncEnvelope, type SyncEnvelopeType, SyncEventTypes, type SyncFailedEvent, type SyncModule, SyncOperation, SyncOperationAggregateError, type SyncOperationErrorType, SyncOperationStatus, type SyncPendingEvent, SyncStatus, type SyncStatusChangeCallback, SyncStatusTracker, type SyncSucceededEvent, type TrackedProcessor, type Unsubscribe, type ValidationResult, type ViewFilter, type ErrorInfo as WorkerErrorInfo, type WorkerMessage, type WorkerPoolConfig, type WorkerPoolOptions, type WriteCacheConfig, addRelationshipAction, authDecisionModel, batchOperationsByDocument, buildDecisionModel, classifyJobFailure, consolidateSyncOperations, createDocumentAction, createForwardingLogger, createMutableShutdownStatus, createRelationalDb, decideAtHead, deleteDocumentAction, documentActions, documentDecisionModel, driveIdFromUrl, envelopesToSyncOperations, errorToInfo, firstOutOfOrderPair, getMigrationStatus, instrumentPgPool, isDriveAuthError, makeConsistencyKey, parseDriveUrl, parsePagingOptions, quarantinesDocument, readDecisionModel, removeRelationshipAction, runMigrations, sanitizeArg, selectDecisionModel, supportsLiveReadModelRegistration, syncOperationErrorType, trimMailboxFromAckOrdinal, updateRelationshipAction, upgradeDocumentAction, workerEntryPath };
5711
6700
  //# sourceMappingURL=index.d.ts.map