@powerhousedao/reactor 6.2.2-dev.43 → 6.2.2-dev.44

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.
@@ -1,5 +1,5 @@
1
1
  import { n as ReactorEventTypes, t as EventBusAggregateError } from "./types-DMKLa0Ok.js";
2
- import { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, DOCUMENT_DELETED_REASON, DowngradeNotSupportedError as DowngradeNotSupportedError$1, appendWithoutApplying, applyAuthAction, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, baseReducerVersion, createPresignedHeader, decide, defaultBaseState, deriveOperationId, evaluate, garbageCollect, hashDocumentStateForScope, isDenied, isUndoRedo, normalizeDocumentModelVersion, sortOperations } from "@powerhousedao/shared/document-model";
2
+ import { AUTH_ACTION_TYPES, AUTH_DENIED_BY_GRANT_REASON, AUTH_NO_GRANT_REASON, AUTH_VERSION_UNSUPPORTED_REASON, DOCUMENT_DELETED_REASON, DowngradeNotSupportedError as DowngradeNotSupportedError$1, appendWithoutApplying, applyAuthAction, applyDeleteDocumentAction, applyDeleteDocumentAction as applyDeleteDocumentAction$1, applyUpgradeDocumentAction, applyUpgradeDocumentAction as applyUpgradeDocumentAction$1, baseReducerVersion, createPresignedHeader, decide, defaultBaseState, deriveOperationId, evaluate, garbageCollect, groupDocumentType, groupMembershipActionTypes, hashDocumentStateForScope, isDenied, isUndoRedo, mentionedGroupIds, normalizeDocumentModelVersion, referencedGroupIds, sortOperations } from "@powerhousedao/shared/document-model";
3
3
  import { v4 } from "uuid";
4
4
  import { Migrator, sql } from "kysely";
5
5
  //#region \0rolldown/runtime.js
@@ -758,6 +758,7 @@ var KyselyOperationIndexTxn = class {
758
758
  collections = [];
759
759
  collectionMemberships = [];
760
760
  collectionRemovals = [];
761
+ groupReferences = [];
761
762
  operations = [];
762
763
  createCollection(collectionId) {
763
764
  this.collections.push(collectionId);
@@ -780,12 +781,25 @@ var KyselyOperationIndexTxn = class {
780
781
  operationIndex: lastOpIndex
781
782
  });
782
783
  }
784
+ recordGroupReferences(documentId, groupIds) {
785
+ const lastOpIndex = this.operations.length - 1;
786
+ if (lastOpIndex < 0) throw new Error("recordGroupReferences must be called after write() - no operations in transaction");
787
+ if (groupIds.length === 0) return;
788
+ this.groupReferences.push({
789
+ documentId,
790
+ groupIds,
791
+ operationIndex: lastOpIndex
792
+ });
793
+ }
783
794
  write(operations) {
784
795
  this.operations.push(...operations);
785
796
  }
786
797
  getCollections() {
787
798
  return this.collections;
788
799
  }
800
+ getGroupReferenceRecords() {
801
+ return this.groupReferences;
802
+ }
789
803
  getCollectionMembershipRecords() {
790
804
  return this.collectionMemberships;
791
805
  }
@@ -822,10 +836,27 @@ var KyselyOperationIndex = class KyselyOperationIndex {
822
836
  });
823
837
  return resultOrdinals;
824
838
  }
839
+ /**
840
+ * A policy-driven join: keeps the earliest join so a rediscovered reference
841
+ * never shrinks a backfill window remotes already rely on, and reopens a
842
+ * closed membership because a policy reference is not a removable one.
843
+ */
844
+ async joinKeepingEarliest(trx, documentId, collectionId, ordinal) {
845
+ await trx.insertInto("document_collections").values({
846
+ documentId,
847
+ collectionId,
848
+ joinedOrdinal: ordinal,
849
+ leftOrdinal: null
850
+ }).onConflict((oc) => oc.columns(["documentId", "collectionId"]).doUpdateSet({
851
+ joinedOrdinal: sql`LEAST("document_collections"."joinedOrdinal", EXCLUDED."joinedOrdinal")`,
852
+ leftOrdinal: null
853
+ })).execute();
854
+ }
825
855
  async executeCommit(trx, kyselyTxn) {
826
856
  const collections = kyselyTxn.getCollections();
827
857
  const memberships = kyselyTxn.getCollectionMembershipRecords();
828
858
  const removals = kyselyTxn.getCollectionRemovals();
859
+ const groupReferences = kyselyTxn.getGroupReferenceRecords();
829
860
  const operations = kyselyTxn.getOperations();
830
861
  if (collections.length > 0) {
831
862
  const collectionRows = collections.map((collectionId) => ({
@@ -865,13 +896,28 @@ var KyselyOperationIndex = class KyselyOperationIndex {
865
896
  joinedOrdinal: BigInt(ordinal),
866
897
  leftOrdinal: null
867
898
  })).execute();
899
+ const references = await trx.selectFrom("group_references").select("groupId").where("documentId", "=", m.documentId).execute();
900
+ for (const { groupId } of references) await this.joinKeepingEarliest(trx, groupId, m.collectionId, BigInt(ordinal));
868
901
  }
869
902
  if (removals.length > 0) for (const r of removals) {
870
903
  const ordinal = operationOrdinals[r.operationIndex];
871
904
  await trx.updateTable("document_collections").set({ leftOrdinal: BigInt(ordinal) }).where("collectionId", "=", r.collectionId).where("documentId", "=", r.documentId).where("leftOrdinal", "is", null).execute();
872
905
  }
906
+ if (groupReferences.length > 0) for (const record of groupReferences) {
907
+ const ordinal = operationOrdinals[record.operationIndex];
908
+ await trx.insertInto("group_references").values(record.groupIds.map((groupId) => ({
909
+ documentId: record.documentId,
910
+ groupId
911
+ }))).onConflict((oc) => oc.doNothing()).execute();
912
+ const rows = await trx.selectFrom("document_collections").select("collectionId").where("documentId", "=", record.documentId).execute();
913
+ for (const groupId of record.groupIds) for (const { collectionId } of rows) await this.joinKeepingEarliest(trx, groupId, collectionId, BigInt(ordinal));
914
+ }
873
915
  return operationOrdinals;
874
916
  }
917
+ async getGroupReferencers(groupId, signal) {
918
+ if (signal?.aborted) throw new Error("Operation aborted");
919
+ return (await this.queryExecutor.selectFrom("group_references").select("documentId").where("groupId", "=", groupId).orderBy("documentId").execute()).map((row) => row.documentId);
920
+ }
875
921
  async find(collectionId, cursor, view, paging, signal) {
876
922
  if (signal?.aborted) throw new Error("Operation aborted");
877
923
  const outerCursor = cursor ?? -1;
@@ -1768,7 +1814,9 @@ var EventBus = class {
1768
1814
  */
1769
1815
  const FLAG_PREREQUISITES = {
1770
1816
  documentDecisions: [],
1771
- authEnforcement: ["documentDecisions"]
1817
+ authEnforcement: ["documentDecisions"],
1818
+ authGroups: ["authEnforcement"],
1819
+ authConditions: ["authGroups"]
1772
1820
  };
1773
1821
  /**
1774
1822
  * Throws when the flags ask for enforcement the reactor cannot deliver. Either
@@ -1891,49 +1939,159 @@ function refusalReason(refusal) {
1891
1939
  case "no-applicable-grant": return AUTH_NO_GRANT_REASON;
1892
1940
  }
1893
1941
  }
1942
+ function decideAuthModel(model, subject, request, groups, conditions) {
1943
+ if (request.verb === "execute" && model.document.isDeleted) return {
1944
+ decision: "deny",
1945
+ reason: DOCUMENT_DELETED_REASON
1946
+ };
1947
+ const evaluation = evaluate(model.auth, subject, request, groups, conditions);
1948
+ if (evaluation.decision === "allow") return { decision: "allow" };
1949
+ return {
1950
+ decision: "deny",
1951
+ reason: refusalReason(evaluation.refusal)
1952
+ };
1953
+ }
1954
+ function documentProjection(target) {
1955
+ return {
1956
+ decidingActions: ["DELETE_DOCUMENT"],
1957
+ apply: (document, operation) => operation.action.type === "DELETE_DOCUMENT" ? applyDeleteDocumentAction({
1958
+ ...document,
1959
+ state: { ...document.state }
1960
+ }, operation.action) : document,
1961
+ query: {
1962
+ documentId: target.documentId,
1963
+ branch: target.branch,
1964
+ scope: "document"
1965
+ }
1966
+ };
1967
+ }
1968
+ function authProjection(target) {
1969
+ return {
1970
+ decidingActions: [...AUTH_ACTION_TYPES],
1971
+ apply: (document, operation) => applyAuthAction(document, operation.action),
1972
+ query: {
1973
+ documentId: target.documentId,
1974
+ branch: target.branch,
1975
+ scope: "auth"
1976
+ }
1977
+ };
1978
+ }
1894
1979
  /** This decision model uses both the document and the auth streams. */
1895
1980
  function authDecisionModel(target) {
1896
1981
  return {
1897
1982
  projections: {
1898
- document: {
1899
- decidingActions: ["DELETE_DOCUMENT"],
1900
- apply: (document, operation) => operation.action.type === "DELETE_DOCUMENT" ? applyDeleteDocumentAction({
1901
- ...document,
1902
- state: { ...document.state }
1903
- }, operation.action) : document,
1904
- query: {
1905
- documentId: target.documentId,
1906
- branch: target.branch,
1907
- scope: "document"
1908
- }
1909
- },
1910
- auth: {
1911
- decidingActions: [...AUTH_ACTION_TYPES],
1912
- apply: (document, operation) => applyAuthAction(document, operation.action),
1913
- query: {
1914
- documentId: target.documentId,
1915
- branch: target.branch,
1916
- scope: "auth"
1917
- }
1918
- }
1983
+ document: documentProjection(target),
1984
+ auth: authProjection(target)
1919
1985
  },
1920
1986
  evaluatesScope() {
1921
1987
  return true;
1922
1988
  },
1923
1989
  decide(model, subject, request) {
1924
- if (request.verb === "execute" && model.document.isDeleted) return {
1925
- decision: "deny",
1926
- reason: DOCUMENT_DELETED_REASON
1927
- };
1928
- const evaluation = evaluate(model.auth, subject, request);
1929
- if (evaluation.decision === "allow") return { decision: "allow" };
1930
- return {
1931
- decision: "deny",
1932
- reason: refusalReason(evaluation.refusal)
1933
- };
1990
+ return decideAuthModel(model, subject, request);
1934
1991
  }
1935
1992
  };
1936
1993
  }
1994
+ /**
1995
+ * Folds one group-stream operation with the registered group model's reducer.
1996
+ * A reactor without the module registered folds nothing, so the member list
1997
+ * stays as read and a missing reducer never widens access.
1998
+ */
1999
+ function applyGroupOperation(registry, document, operation) {
2000
+ let reducer;
2001
+ try {
2002
+ reducer = registry.getModule(groupDocumentType).reducer;
2003
+ } catch {
2004
+ return document;
2005
+ }
2006
+ return reducer(document, operation.action);
2007
+ }
2008
+ /**
2009
+ * Folds one evaluated-scope operation with the reducer registered for the
2010
+ * document's own type, at the document's stamped version. A reactor without
2011
+ * that module folds nothing, so conditions read the base state and an
2012
+ * unresolvable reducer never widens access.
2013
+ */
2014
+ function applyModelOperation(registry, document, operation) {
2015
+ let reducer;
2016
+ try {
2017
+ const version = normalizeDocumentModelVersion(document.state.document?.version);
2018
+ reducer = registry.getModule(document.header.documentType, version).reducer;
2019
+ } catch {
2020
+ return document;
2021
+ }
2022
+ return reducer(document, operation.action);
2023
+ }
2024
+ /**
2025
+ * The auth model extended with a derived groups projection: the streams it
2026
+ * reads are the group documents the folded grant list names, so adding a
2027
+ * grant that names a new group pulls that group's stream into the read-set.
2028
+ * Group queries pin the main branch, because a group's member list lives on
2029
+ * its main branch no matter which branch the referencing document is on.
2030
+ */
2031
+ function groupsProjection(registry) {
2032
+ return {
2033
+ decidingActions: [...groupMembershipActionTypes],
2034
+ apply: (document, operation) => applyGroupOperation(registry, document, operation),
2035
+ query: (model) => referencedGroupIds(model.auth?.grants ?? []).map((id) => ({
2036
+ documentId: id,
2037
+ branch: "main",
2038
+ scope: "global"
2039
+ })),
2040
+ queryOverHistory: (reads) => {
2041
+ const ids = [];
2042
+ for (const read of reads) {
2043
+ if (read.name !== "auth") continue;
2044
+ for (const operation of read.operations) for (const id of mentionedGroupIds(operation.action)) if (!ids.includes(id)) ids.push(id);
2045
+ }
2046
+ return ids.map((id) => ({
2047
+ documentId: id,
2048
+ branch: "main",
2049
+ scope: "global"
2050
+ }));
2051
+ }
2052
+ };
2053
+ }
2054
+ function authGroupsDecisionModel(registry) {
2055
+ return (target) => ({
2056
+ projections: {
2057
+ document: documentProjection(target),
2058
+ auth: authProjection(target),
2059
+ groups: groupsProjection(registry)
2060
+ },
2061
+ evaluatesScope() {
2062
+ return true;
2063
+ },
2064
+ decide(model, subject, request) {
2065
+ return decideAuthModel(model, subject, request, model.groups);
2066
+ }
2067
+ });
2068
+ }
2069
+ /**
2070
+ * The groups model with conditions live: decide hands the executing scope's
2071
+ * state and the action input through to the evaluator, so `where` clauses
2072
+ * and { match } principals apply. The model folds the evaluated scope during
2073
+ * a positional walk, so a condition reads the state as it stood at each
2074
+ * operation's position.
2075
+ */
2076
+ function authConditionsDecisionModel(registry) {
2077
+ return (target) => ({
2078
+ projections: {
2079
+ document: documentProjection(target),
2080
+ auth: authProjection(target),
2081
+ groups: groupsProjection(registry)
2082
+ },
2083
+ foldEvaluatedScope: (document, operation) => applyModelOperation(registry, document, operation),
2084
+ evaluatesScope() {
2085
+ return true;
2086
+ },
2087
+ decide(model, subject, request, ctx) {
2088
+ return decideAuthModel(model, subject, request, model.groups, {
2089
+ scopeState: ctx.scopeState,
2090
+ actionInput: ctx.actionInput
2091
+ });
2092
+ }
2093
+ });
2094
+ }
1937
2095
  //#endregion
1938
2096
  //#region src/decision/build-decision-model.ts
1939
2097
  /**
@@ -1957,7 +2115,16 @@ async function buildDecisionModel(cache, definition, target, signal) {
1957
2115
  const queries = projection.query(staticModel);
1958
2116
  const value = {};
1959
2117
  for (const query of queries) {
1960
- const read = await readStream(cache, query, reads, signal);
2118
+ let read;
2119
+ try {
2120
+ read = await readStream(cache, query, reads, signal);
2121
+ } catch (error) {
2122
+ if (error instanceof DocumentNotFoundError) {
2123
+ recordEmptyStream(query, reads);
2124
+ continue;
2125
+ }
2126
+ throw error;
2127
+ }
1961
2128
  value[query.documentId] = read.state;
1962
2129
  }
1963
2130
  model[key] = value;
@@ -1967,6 +2134,20 @@ async function buildDecisionModel(cache, definition, target, signal) {
1967
2134
  appendCondition: { streams: [...reads.values()].map((read) => read.stream) }
1968
2135
  };
1969
2136
  }
2137
+ /** Guards a stream that holds nothing yet: any operation appearing is growth. */
2138
+ function recordEmptyStream(query, reads) {
2139
+ const key = `${query.documentId}:${query.scope}:${query.branch}`;
2140
+ if (reads.has(key)) return;
2141
+ reads.set(key, {
2142
+ state: void 0,
2143
+ stream: {
2144
+ documentId: query.documentId,
2145
+ scope: query.scope,
2146
+ branch: query.branch,
2147
+ revision: -1
2148
+ }
2149
+ });
2150
+ }
1970
2151
  async function readStream(cache, query, reads, signal) {
1971
2152
  const key = `${query.documentId}:${query.scope}:${query.branch}`;
1972
2153
  const existing = reads.get(key);
@@ -1998,6 +2179,24 @@ function observedRevision(document, scope) {
1998
2179
  return document.header.revision[scope] - 1;
1999
2180
  }
2000
2181
  /**
2182
+ * The projections whose queries depend on folded state. A positional walk
2183
+ * resolves their streams through `queryOverHistory`; a projection without one
2184
+ * contributes no streams to a walk.
2185
+ */
2186
+ function derivedReadSet(definition) {
2187
+ const projections = [];
2188
+ for (const [name, projection] of Object.entries(definition.projections)) {
2189
+ if (typeof projection.query !== "function") continue;
2190
+ projections.push({
2191
+ name,
2192
+ decidingActions: projection.decidingActions,
2193
+ apply: projection.apply,
2194
+ queryOverHistory: projection.queryOverHistory
2195
+ });
2196
+ }
2197
+ return projections;
2198
+ }
2199
+ /**
2001
2200
  * The streams a model reads whose queries are known before it is built. A
2002
2201
  * derived query needs the statically-queried projections first, so it is not
2003
2202
  * included here.
@@ -2051,11 +2250,21 @@ function documentDecisionModel(target) {
2051
2250
  /**
2052
2251
  * Builds the model at the stream heads and decides one request against it. The
2053
2252
  * append condition it returns is the read-set the store enforces at write time.
2253
+ *
2254
+ * With `conditions` supplied, the executing scope's state is read at the head
2255
+ * for `doc.<scope>.*` paths. That read carries no append-condition entry of
2256
+ * its own: the written stream's expected-revision check already refuses a
2257
+ * write whose scope grew between the read and the append.
2054
2258
  */
2055
- async function decideAtHead(model, cache, target, subject, request, signal) {
2259
+ async function decideAtHead(model, cache, target, subject, request, signal, conditions) {
2056
2260
  const built = await buildDecisionModel(cache, model, target, signal);
2261
+ let scopeState;
2262
+ if (conditions !== void 0) scopeState = (await cache.getState(target.documentId, request.scope, target.branch, void 0, signal)).state[request.scope];
2057
2263
  return {
2058
- evaluation: model(target).decide(built.model, subject, request, { scopeState: void 0 }),
2264
+ evaluation: model(target).decide(built.model, subject, request, {
2265
+ scopeState,
2266
+ actionInput: conditions?.actionInput
2267
+ }),
2059
2268
  appendCondition: built.appendCondition,
2060
2269
  documentVersion: built.model.document.version,
2061
2270
  deletedAtUtcIso: built.model.document.deletedAtUtcIso ?? null
@@ -2063,9 +2272,13 @@ async function decideAtHead(model, cache, target, subject, request, signal) {
2063
2272
  }
2064
2273
  /**
2065
2274
  * The model this reactor enforces. With `authEnforcement` off the auth scope is
2066
- * absent from every append condition and no load walks it.
2275
+ * absent from every append condition and no load walks it; with `authGroups`
2276
+ * on, the group documents the grant list names join the read-set and the
2277
+ * registry supplies the reducer that folds them.
2067
2278
  */
2068
- function selectDecisionModel(flags) {
2279
+ function selectDecisionModel(flags, registry) {
2280
+ if (flags.authConditions) return authConditionsDecisionModel(registry);
2281
+ if (flags.authGroups) return authGroupsDecisionModel(registry);
2069
2282
  return flags.authEnforcement ? authDecisionModel : documentDecisionModel;
2070
2283
  }
2071
2284
  //#endregion
@@ -2195,16 +2408,24 @@ function subjectOf(operation) {
2195
2408
  };
2196
2409
  }
2197
2410
  /**
2198
- * The model as the walk reached this operation: each projection's value is its
2199
- * own scope's state, taken from the stream that projection reads.
2411
+ * The model as the walk reached this operation: each static projection's value
2412
+ * is its own scope's state, and each derived projection's value maps document
2413
+ * id to that document's state, holding only the streams this replica walked. A
2414
+ * derived stream it does not hold stays out of the map, which fails closed.
2200
2415
  */
2201
- function modelAt(readSet, states) {
2416
+ function modelAt(readSet, derivedNames, derived, states) {
2202
2417
  const model = {};
2203
2418
  for (const stream of readSet) {
2204
2419
  const document = states.get(streamKey(stream.query));
2205
2420
  if (document === void 0) throw new Error(`No state walked for projection ${stream.name}`);
2206
2421
  model[stream.name] = document.state[stream.query.scope];
2207
2422
  }
2423
+ for (const name of derivedNames) model[name] = {};
2424
+ for (const entry of derived) {
2425
+ const map = model[entry.name];
2426
+ const document = states.get(streamKey(entry.query));
2427
+ if (document !== void 0) map[entry.query.documentId] = document.state[entry.query.scope];
2428
+ }
2208
2429
  return model;
2209
2430
  }
2210
2431
  /**
@@ -2220,6 +2441,7 @@ async function evaluateByPosition(model, target, subject, stores, signal) {
2220
2441
  const { writeCache, operationStore } = stores;
2221
2442
  const definition = model(target);
2222
2443
  const readSet = staticReadSet(definition);
2444
+ const derivedSet = derivedReadSet(definition);
2223
2445
  if (!definition.evaluatesScope(scope)) return operations.map(() => void 0);
2224
2446
  const evaluating = new Set(operations.map((operation) => operation.id));
2225
2447
  const readStreams = await Promise.all(readSet.map(async (stream) => ({
@@ -2231,24 +2453,76 @@ async function evaluateByPosition(model, target, subject, stores, signal) {
2231
2453
  if (readStreams.length === 0) throw new Error(`Decision model for ${target.documentId} reads no stream whose query is known before it is built`);
2232
2454
  const writtenProjection = readSet.find((stream) => stream.query.scope === scope);
2233
2455
  const walked = [];
2456
+ const histories = [];
2234
2457
  for (const read of readStreams) {
2235
- const isWritten = read.stream === writtenProjection;
2458
+ const streamOperations = read.stream === writtenProjection ? [...read.operations, ...operations] : read.operations;
2236
2459
  const before = await writeCache.getState(read.stream.query.documentId, read.stream.query.scope, read.stream.query.branch, -1, signal);
2237
2460
  walked.push({
2238
2461
  streamKey: streamKey(read.stream.query),
2239
2462
  scope: read.stream.query.scope,
2240
2463
  document: before,
2241
- operations: isWritten ? [...read.operations, ...operations] : read.operations,
2464
+ operations: streamOperations,
2242
2465
  apply: read.stream.apply
2243
2466
  });
2467
+ histories.push({
2468
+ name: read.stream.name,
2469
+ operations: streamOperations
2470
+ });
2244
2471
  }
2245
- if (writtenProjection === void 0) walked.push({
2472
+ let evaluatedStateKey;
2473
+ if (writtenProjection !== void 0) evaluatedStateKey = streamKey(writtenProjection.query);
2474
+ else if (definition.foldEvaluatedScope !== void 0) {
2475
+ const query = {
2476
+ documentId: target.documentId,
2477
+ scope,
2478
+ branch: target.branch
2479
+ };
2480
+ const storedOperations = (await operationStore.getSince(query.documentId, query.scope, query.branch, -1, void 0, void 0, signal)).results.filter((operation) => !evaluating.has(operation.id));
2481
+ const before = await writeCache.getState(query.documentId, query.scope, query.branch, -1, signal);
2482
+ evaluatedStateKey = streamKey(query);
2483
+ walked.push({
2484
+ streamKey: evaluatedStateKey,
2485
+ scope,
2486
+ document: before,
2487
+ operations: [...storedOperations, ...operations],
2488
+ apply: definition.foldEvaluatedScope
2489
+ });
2490
+ } else walked.push({
2246
2491
  streamKey: EVALUATED_ONLY,
2247
2492
  scope,
2248
2493
  document: walked[0].document,
2249
2494
  operations,
2250
2495
  apply: (document) => document
2251
2496
  });
2497
+ const derivedEntries = [];
2498
+ const walkedKeys = new Set(walked.map((stream) => stream.streamKey));
2499
+ for (const projection of derivedSet) {
2500
+ const queries = projection.queryOverHistory?.(histories) ?? [];
2501
+ for (const query of queries) {
2502
+ const key = streamKey(query);
2503
+ if (walkedKeys.has(key)) continue;
2504
+ let before;
2505
+ try {
2506
+ before = await writeCache.getState(query.documentId, query.scope, query.branch, -1, signal);
2507
+ } catch (error) {
2508
+ if (error instanceof DocumentNotFoundError) continue;
2509
+ throw error;
2510
+ }
2511
+ const streamOperations = (await operationStore.getSince(query.documentId, query.scope, query.branch, -1, { actionTypes: projection.decidingActions }, void 0, signal)).results.filter((operation) => !evaluating.has(operation.id));
2512
+ walkedKeys.add(key);
2513
+ walked.push({
2514
+ streamKey: key,
2515
+ scope: query.scope,
2516
+ document: before,
2517
+ operations: streamOperations,
2518
+ apply: projection.apply
2519
+ });
2520
+ derivedEntries.push({
2521
+ name: projection.name,
2522
+ query
2523
+ });
2524
+ }
2525
+ }
2252
2526
  const reasons = /* @__PURE__ */ new Map();
2253
2527
  const walk = walkByPosition(walked);
2254
2528
  let step = walk.next(false);
@@ -2258,11 +2532,16 @@ async function evaluateByPosition(model, target, subject, stores, signal) {
2258
2532
  step = walk.next(false);
2259
2533
  continue;
2260
2534
  }
2261
- const evaluation = definition.decide(modelAt(readSet, position.states), subjectOf(position.operation), {
2535
+ const evaluatedDocument = evaluatedStateKey === void 0 ? void 0 : position.states.get(evaluatedStateKey);
2536
+ const scopeState = evaluatedDocument === void 0 ? void 0 : evaluatedDocument.state[scope];
2537
+ const evaluation = definition.decide(modelAt(readSet, derivedSet.map((projection) => projection.name), derivedEntries, position.states), subjectOf(position.operation), {
2262
2538
  verb: "execute",
2263
2539
  scope: position.operation.action.scope,
2264
2540
  operation: position.operation.action.type
2265
- }, { scopeState: void 0 });
2541
+ }, {
2542
+ scopeState,
2543
+ actionInput: position.operation.action.input
2544
+ });
2266
2545
  const denied = evaluation.decision === "deny";
2267
2546
  reasons.set(position.operation.id, denied ? evaluation.reason : void 0);
2268
2547
  step = walk.next(denied);
@@ -2363,7 +2642,7 @@ var DocumentActionHandler = class {
2363
2642
  verb: "execute",
2364
2643
  scope: action.scope,
2365
2644
  operation: action.type
2366
- }, signal);
2645
+ }, signal, this.featureFlags.authConditions ? { actionInput: action.input } : void 0);
2367
2646
  } catch (error) {
2368
2647
  return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
2369
2648
  }
@@ -2845,10 +3124,12 @@ var SimpleJobExecutor = class {
2845
3124
  };
2846
3125
  this.featureFlags = {
2847
3126
  documentDecisions: config.featureFlags?.documentDecisions ?? false,
2848
- authEnforcement: config.featureFlags?.authEnforcement ?? false
3127
+ authEnforcement: config.featureFlags?.authEnforcement ?? false,
3128
+ authGroups: config.featureFlags?.authGroups ?? false,
3129
+ authConditions: config.featureFlags?.authConditions ?? false
2849
3130
  };
2850
3131
  validateFeatureFlags(this.featureFlags, FLAG_PREREQUISITES);
2851
- this.decisionModel = selectDecisionModel(this.featureFlags);
3132
+ this.decisionModel = selectDecisionModel(this.featureFlags, registry);
2852
3133
  this.signatureVerifierModule = new SignatureVerifier(signatureVerifier);
2853
3134
  this.documentActionHandler = new DocumentActionHandler(registry, logger, driveContainerTypes, this.featureFlags, this.decisionModel);
2854
3135
  this.executionScope = executionScope ?? new DefaultExecutionScope(operationStore, operationIndex, writeCache, documentMetaCache, collectionMembershipCache);
@@ -2895,6 +3176,37 @@ var SimpleJobExecutor = class {
2895
3176
  }
2896
3177
  return loadResult;
2897
3178
  }
3179
+ if (job.kind === "reevaluation") {
3180
+ const reevalResult = await this.executeReevaluationJob({
3181
+ job,
3182
+ startTime,
3183
+ indexTxn,
3184
+ stores,
3185
+ signal,
3186
+ replayingAcceptedHistory: false,
3187
+ evaluatedByPosition: false,
3188
+ postCommitInvalidations
3189
+ });
3190
+ if (reevalResult.success && reevalResult.operationsWithContext) {
3191
+ for (const owc of reevalResult.operationsWithContext) touchedCacheEntries.push({
3192
+ documentId: owc.context.documentId,
3193
+ scope: owc.context.scope,
3194
+ branch: owc.context.branch
3195
+ });
3196
+ const ordinals = await stores.operationIndex.commit(indexTxn, signal);
3197
+ for (let i = 0; i < reevalResult.operationsWithContext.length; i++) reevalResult.operationsWithContext[i].context.ordinal = ordinals[i];
3198
+ if (reevalResult.operationsWithContext.length > 0) {
3199
+ const collectionMemberships = await this.getCollectionMembershipsForOperations(reevalResult.operationsWithContext, stores);
3200
+ pendingEvent = {
3201
+ jobId: job.id,
3202
+ operations: reevalResult.operationsWithContext,
3203
+ jobMeta: job.meta,
3204
+ collectionMemberships
3205
+ };
3206
+ }
3207
+ }
3208
+ return reevalResult;
3209
+ }
2898
3210
  const positioned = await this.positionByTimestamp(job, stores, signal);
2899
3211
  if (positioned.error) return buildErrorResult(job, positioned.error, startTime);
2900
3212
  const executing = {
@@ -3033,7 +3345,7 @@ var SimpleJobExecutor = class {
3033
3345
  verb: "execute",
3034
3346
  scope: action.scope,
3035
3347
  operation: action.type
3036
- }, signal);
3348
+ }, signal, this.featureFlags.authConditions ? { actionInput: action.input } : void 0);
3037
3349
  } catch (error) {
3038
3350
  return buildErrorResult(job, error instanceof Error ? error : new Error(String(error)), startTime);
3039
3351
  }
@@ -3156,6 +3468,7 @@ var SimpleJobExecutor = class {
3156
3468
  scope,
3157
3469
  sourceRemote
3158
3470
  }]);
3471
+ if (scope === "auth") indexTxn.recordGroupReferences(job.documentId, mentionedGroupIds(action));
3159
3472
  return {
3160
3473
  job,
3161
3474
  success: true,
@@ -3352,7 +3665,7 @@ var SimpleJobExecutor = class {
3352
3665
  const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
3353
3666
  const latest = Date.parse(revisions.latestTimestamp);
3354
3667
  if (!criteria.operations.some((operation) => Date.parse(operation.timestampUtcMs) < latest)) return;
3355
- return this.reevaluateDocument(executing);
3668
+ return (await this.reevaluateDocument(executing)).error;
3356
3669
  }
3357
3670
  /**
3358
3671
  * Re-evaluates every scope the model evaluates. Where an operation's
@@ -3365,6 +3678,7 @@ var SimpleJobExecutor = class {
3365
3678
  documentId: job.documentId,
3366
3679
  branch: job.branch
3367
3680
  };
3681
+ const reappended = [];
3368
3682
  const revisions = await stores.operationStore.getRevisions(job.documentId, job.branch, signal);
3369
3683
  for (const scope of this.evaluationOrder(target, revisions.revision)) {
3370
3684
  const stored = (await stores.operationStore.getSince(job.documentId, scope, job.branch, -1, void 0, void 0, signal)).results;
@@ -3393,8 +3707,60 @@ var SimpleJobExecutor = class {
3393
3707
  replayingAcceptedHistory: true,
3394
3708
  evaluatedByPosition: true
3395
3709
  });
3396
- if (!result.success) return result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`);
3710
+ if (!result.success) return {
3711
+ error: result.error ?? /* @__PURE__ */ new Error(`Re-evaluation of ${job.documentId} ${scope} failed`),
3712
+ operationsWithContext: reappended
3713
+ };
3714
+ reappended.push(...result.operationsWithContext);
3715
+ }
3716
+ return { operationsWithContext: reappended };
3717
+ }
3718
+ /**
3719
+ * Re-judges a document's stored operations because a read-set stream in
3720
+ * another document (a group) gained an operation. The trigger timestamp
3721
+ * bounds the work: an operation later than everything this document holds
3722
+ * cannot change any evaluation, so the pass is skipped.
3723
+ */
3724
+ async executeReevaluationJob(executing) {
3725
+ const { job, startTime, stores, signal } = executing;
3726
+ if (!this.featureFlags.documentDecisions) return {
3727
+ job,
3728
+ success: true,
3729
+ operations: [],
3730
+ operationsWithContext: [],
3731
+ duration: Date.now() - startTime
3732
+ };
3733
+ const trigger = job.meta.triggerTimestampUtcMs;
3734
+ if (typeof trigger === "string") {
3735
+ let latestTimestamp;
3736
+ try {
3737
+ latestTimestamp = (await stores.operationStore.getRevisions(job.documentId, job.branch, signal)).latestTimestamp;
3738
+ } catch {
3739
+ return {
3740
+ job,
3741
+ success: true,
3742
+ operations: [],
3743
+ operationsWithContext: [],
3744
+ duration: Date.now() - startTime
3745
+ };
3746
+ }
3747
+ if (Date.parse(trigger) > Date.parse(latestTimestamp)) return {
3748
+ job,
3749
+ success: true,
3750
+ operations: [],
3751
+ operationsWithContext: [],
3752
+ duration: Date.now() - startTime
3753
+ };
3397
3754
  }
3755
+ const outcome = await this.reevaluateDocument(executing);
3756
+ if (outcome.error) return buildErrorResult(job, outcome.error, startTime);
3757
+ return {
3758
+ job,
3759
+ success: true,
3760
+ operations: outcome.operationsWithContext.map((owc) => owc.operation),
3761
+ operationsWithContext: outcome.operationsWithContext,
3762
+ duration: Date.now() - startTime
3763
+ };
3398
3764
  }
3399
3765
  async executeLoadJob(executing) {
3400
3766
  const { job, startTime, indexTxn, stores, signal } = executing;
@@ -4156,8 +4522,8 @@ function createForwardingPoolInstrumentation(name) {
4156
4522
  }
4157
4523
  //#endregion
4158
4524
  //#region src/storage/migrations/001_create_operation_table.ts
4159
- var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$15 });
4160
- async function up$15(db) {
4525
+ var _001_create_operation_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$16 });
4526
+ async function up$16(db) {
4161
4527
  await db.schema.createTable("Operation").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("jobId", "text", (col) => col.notNull()).addColumn("opId", "text", (col) => col.notNull()).addColumn("prevOpId", "text", (col) => col.notNull()).addColumn("writeTimestampUtcMs", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("timestampUtcMs", "timestamptz", (col) => col.notNull()).addColumn("index", "integer", (col) => col.notNull()).addColumn("action", "jsonb", (col) => col.notNull()).addColumn("skip", "integer", (col) => col.notNull()).addColumn("error", "text").addColumn("hash", "text", (col) => col.notNull()).addUniqueConstraint("unique_revision", [
4162
4528
  "documentId",
4163
4529
  "scope",
@@ -4182,8 +4548,8 @@ async function up$15(db) {
4182
4548
  }
4183
4549
  //#endregion
4184
4550
  //#region src/storage/migrations/002_create_keyframe_table.ts
4185
- var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$14 });
4186
- async function up$14(db) {
4551
+ var _002_create_keyframe_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$15 });
4552
+ async function up$15(db) {
4187
4553
  await db.schema.createTable("Keyframe").addColumn("id", "serial", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("revision", "integer", (col) => col.notNull()).addColumn("document", "jsonb", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_keyframe", [
4188
4554
  "documentId",
4189
4555
  "scope",
@@ -4199,14 +4565,14 @@ async function up$14(db) {
4199
4565
  }
4200
4566
  //#endregion
4201
4567
  //#region src/storage/migrations/003_create_document_table.ts
4202
- var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
4203
- async function up$13(db) {
4568
+ var _003_create_document_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$14 });
4569
+ async function up$14(db) {
4204
4570
  await db.schema.createTable("Document").addColumn("id", "text", (col) => col.primaryKey()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
4205
4571
  }
4206
4572
  //#endregion
4207
4573
  //#region src/storage/migrations/004_create_document_relationship_table.ts
4208
- var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
4209
- async function up$12(db) {
4574
+ var _004_create_document_relationship_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$13 });
4575
+ async function up$13(db) {
4210
4576
  await db.schema.createTable("DocumentRelationship").addColumn("id", "text", (col) => col.primaryKey()).addColumn("sourceId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("targetId", "text", (col) => col.notNull().references("Document.id").onDelete("cascade")).addColumn("relationshipType", "text", (col) => col.notNull()).addColumn("metadata", "jsonb").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_source_target_type", [
4211
4577
  "sourceId",
4212
4578
  "targetId",
@@ -4218,14 +4584,14 @@ async function up$12(db) {
4218
4584
  }
4219
4585
  //#endregion
4220
4586
  //#region src/storage/migrations/005_create_indexer_state_table.ts
4221
- var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
4222
- async function up$11(db) {
4587
+ var _005_create_indexer_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$12 });
4588
+ async function up$12(db) {
4223
4589
  await db.schema.createTable("IndexerState").addColumn("id", "integer", (col) => col.primaryKey().generatedAlwaysAsIdentity()).addColumn("lastOperationId", "integer", (col) => col.notNull()).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
4224
4590
  }
4225
4591
  //#endregion
4226
4592
  //#region src/storage/migrations/006_create_document_snapshot_table.ts
4227
- var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
4228
- async function up$10(db) {
4593
+ var _006_create_document_snapshot_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$11 });
4594
+ async function up$11(db) {
4229
4595
  await db.schema.createTable("DocumentSnapshot").addColumn("id", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("slug", "text").addColumn("name", "text").addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("content", "jsonb", (col) => col.notNull()).addColumn("documentType", "text", (col) => col.notNull()).addColumn("lastOperationIndex", "integer", (col) => col.notNull()).addColumn("lastOperationHash", "text", (col) => col.notNull()).addColumn("lastUpdatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("snapshotVersion", "integer", (col) => col.notNull().defaultTo(1)).addColumn("identifiers", "jsonb").addColumn("metadata", "jsonb").addColumn("isDeleted", "boolean", (col) => col.notNull().defaultTo(false)).addColumn("deletedAt", "timestamptz").addUniqueConstraint("unique_doc_scope_branch", [
4230
4596
  "documentId",
4231
4597
  "scope",
@@ -4246,8 +4612,8 @@ async function up$10(db) {
4246
4612
  }
4247
4613
  //#endregion
4248
4614
  //#region src/storage/migrations/007_create_slug_mapping_table.ts
4249
- var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
4250
- async function up$9(db) {
4615
+ var _007_create_slug_mapping_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$10 });
4616
+ async function up$10(db) {
4251
4617
  await db.schema.createTable("SlugMapping").addColumn("slug", "text", (col) => col.primaryKey()).addColumn("documentId", "text", (col) => col.notNull()).addColumn("scope", "text", (col) => col.notNull()).addColumn("branch", "text", (col) => col.notNull()).addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addUniqueConstraint("unique_docid_scope_branch", [
4252
4618
  "documentId",
4253
4619
  "scope",
@@ -4257,14 +4623,14 @@ async function up$9(db) {
4257
4623
  }
4258
4624
  //#endregion
4259
4625
  //#region src/storage/migrations/008_create_view_state_table.ts
4260
- var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$8 });
4261
- async function up$8(db) {
4626
+ var _008_create_view_state_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$9 });
4627
+ async function up$9(db) {
4262
4628
  await db.schema.createTable("ViewState").addColumn("readModelId", "text", (col) => col.primaryKey()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(0)).addColumn("lastOperationTimestamp", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
4263
4629
  }
4264
4630
  //#endregion
4265
4631
  //#region src/storage/migrations/009_create_operation_index_tables.ts
4266
- var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$7 });
4267
- async function up$7(db) {
4632
+ var _009_create_operation_index_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$8 });
4633
+ async function up$8(db) {
4268
4634
  await db.schema.createTable("document_collections").addColumn("documentId", "text", (col) => col.notNull()).addColumn("collectionId", "text", (col) => col.notNull()).addColumn("joinedOrdinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("leftOrdinal", "bigint").addPrimaryKeyConstraint("document_collections_pkey", ["documentId", "collectionId"]).execute();
4269
4635
  await db.schema.createIndex("idx_document_collections_collectionId").on("document_collections").column("collectionId").execute();
4270
4636
  await db.schema.createIndex("idx_doc_collections_collection_range").on("document_collections").columns(["collectionId", "joinedOrdinal"]).execute();
@@ -4278,8 +4644,8 @@ async function up$7(db) {
4278
4644
  }
4279
4645
  //#endregion
4280
4646
  //#region src/storage/migrations/010_create_sync_tables.ts
4281
- var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$6 });
4282
- async function up$6(db) {
4647
+ var _010_create_sync_tables_exports = /* @__PURE__ */ __exportAll({ up: () => up$7 });
4648
+ async function up$7(db) {
4283
4649
  await db.schema.createTable("sync_remotes").addColumn("name", "text", (col) => col.primaryKey()).addColumn("collection_id", "text", (col) => col.notNull()).addColumn("channel_type", "text", (col) => col.notNull()).addColumn("channel_id", "text", (col) => col.notNull().defaultTo("")).addColumn("remote_name", "text", (col) => col.notNull().defaultTo("")).addColumn("channel_parameters", "jsonb", (col) => col.notNull().defaultTo(sql`'{}'::jsonb`)).addColumn("filter_document_ids", "jsonb").addColumn("filter_scopes", "jsonb").addColumn("filter_branch", "text", (col) => col.notNull().defaultTo("main")).addColumn("push_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("push_last_success_utc_ms", "text").addColumn("push_last_failure_utc_ms", "text").addColumn("push_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("pull_state", "text", (col) => col.notNull().defaultTo("idle")).addColumn("pull_last_success_utc_ms", "text").addColumn("pull_last_failure_utc_ms", "text").addColumn("pull_failure_count", "integer", (col) => col.notNull().defaultTo(0)).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
4284
4650
  await db.schema.createIndex("idx_sync_remotes_collection").on("sync_remotes").column("collection_id").execute();
4285
4651
  await db.schema.createTable("sync_cursors").addColumn("remote_name", "text", (col) => col.primaryKey().references("sync_remotes.name").onDelete("cascade")).addColumn("cursor_ordinal", "bigint", (col) => col.notNull().defaultTo(0)).addColumn("last_synced_at_utc_ms", "text").addColumn("updated_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
@@ -4287,8 +4653,8 @@ async function up$6(db) {
4287
4653
  }
4288
4654
  //#endregion
4289
4655
  //#region src/storage/migrations/011_add_cursor_type_column.ts
4290
- var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$5 });
4291
- async function up$5(db) {
4656
+ var _011_add_cursor_type_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$6 });
4657
+ async function up$6(db) {
4292
4658
  await db.deleteFrom("sync_cursors").where("remote_name", "like", "outbox::%").execute();
4293
4659
  await db.deleteFrom("sync_remotes").where("name", "like", "outbox::%").execute();
4294
4660
  await db.schema.dropTable("sync_cursors").execute();
@@ -4297,60 +4663,82 @@ async function up$5(db) {
4297
4663
  }
4298
4664
  //#endregion
4299
4665
  //#region src/storage/migrations/012_add_source_remote_column.ts
4300
- var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$4 });
4301
- async function up$4(db) {
4666
+ var _012_add_source_remote_column_exports = /* @__PURE__ */ __exportAll({ up: () => up$5 });
4667
+ async function up$5(db) {
4302
4668
  await db.schema.alterTable("operation_index_operations").addColumn("sourceRemote", "text", (col) => col.notNull().defaultTo("")).execute();
4303
4669
  }
4304
4670
  //#endregion
4305
4671
  //#region src/storage/migrations/013_create_sync_dead_letters_table.ts
4306
- var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$3 });
4307
- async function up$3(db) {
4672
+ var _013_create_sync_dead_letters_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$4 });
4673
+ async function up$4(db) {
4308
4674
  await db.schema.createTable("sync_dead_letters").addColumn("ordinal", "serial", (col) => col.primaryKey()).addColumn("id", "text", (col) => col.unique().notNull()).addColumn("job_id", "text", (col) => col.notNull()).addColumn("job_dependencies", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("remote_name", "text", (col) => col.notNull().references("sync_remotes.name").onDelete("cascade")).addColumn("document_id", "text", (col) => col.notNull()).addColumn("scopes", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("branch", "text", (col) => col.notNull()).addColumn("operations", "jsonb", (col) => col.notNull().defaultTo(sql`'[]'::jsonb`)).addColumn("error_source", "text", (col) => col.notNull()).addColumn("error_message", "text", (col) => col.notNull()).addColumn("created_at", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
4309
4675
  await db.schema.createIndex("idx_sync_dead_letters_remote").on("sync_dead_letters").column("remote_name").execute();
4310
4676
  }
4311
4677
  //#endregion
4312
4678
  //#region src/storage/migrations/014_create_processor_cursor_table.ts
4313
- var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$2 });
4314
- async function up$2(db) {
4679
+ var _014_create_processor_cursor_table_exports = /* @__PURE__ */ __exportAll({ up: () => up$3 });
4680
+ async function up$3(db) {
4315
4681
  await db.schema.createTable("ProcessorCursor").addColumn("processorId", "text", (col) => col.primaryKey()).addColumn("factoryId", "text", (col) => col.notNull()).addColumn("driveId", "text", (col) => col.notNull()).addColumn("processorIndex", "integer", (col) => col.notNull()).addColumn("lastOrdinal", "integer", (col) => col.notNull().defaultTo(sql`0`)).addColumn("status", "text", (col) => col.notNull().defaultTo(sql`'active'`)).addColumn("lastError", "text").addColumn("lastErrorTimestamp", "timestamptz").addColumn("createdAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).addColumn("updatedAt", "timestamptz", (col) => col.notNull().defaultTo(sql`NOW()`)).execute();
4316
4682
  }
4317
4683
  //#endregion
4318
4684
  //#region src/storage/migrations/015_add_operation_denied_reason.ts
4319
4685
  var _015_add_operation_denied_reason_exports = /* @__PURE__ */ __exportAll({
4320
- down: () => down$1,
4321
- up: () => up$1
4686
+ down: () => down$2,
4687
+ up: () => up$2
4322
4688
  });
4323
4689
  /**
4324
4690
  * Records why authorization refused an operation. Separate from `error` so a
4325
4691
  * denial is distinguishable from a reducer failure without matching on a
4326
4692
  * message. Null for every operation written before decisions were enforced.
4327
4693
  */
4328
- async function up$1(db) {
4694
+ async function up$2(db) {
4329
4695
  await db.schema.alterTable("Operation").addColumn("deniedReason", "text").execute();
4330
4696
  await db.schema.alterTable("operation_index_operations").addColumn("deniedReason", "text").execute();
4331
4697
  }
4332
- async function down$1(db) {
4698
+ async function down$2(db) {
4333
4699
  await db.schema.alterTable("operation_index_operations").dropColumn("deniedReason").execute();
4334
4700
  await db.schema.alterTable("Operation").dropColumn("deniedReason").execute();
4335
4701
  }
4336
4702
  //#endregion
4337
4703
  //#region src/storage/migrations/016_add_dead_letter_error_type.ts
4338
4704
  var _016_add_dead_letter_error_type_exports = /* @__PURE__ */ __exportAll({
4339
- down: () => down,
4340
- up: () => up
4705
+ down: () => down$1,
4706
+ up: () => up$1
4341
4707
  });
4342
4708
  /**
4343
4709
  * The classification a dead letter falls into, stored because it decides whether
4344
4710
  * the document stays quarantined and the in-memory error is gone after a restart.
4345
4711
  * Defaulted rather than nullable, so a pre-existing row rehydrates.
4346
4712
  */
4347
- async function up(db) {
4713
+ async function up$1(db) {
4348
4714
  await db.schema.alterTable("sync_dead_letters").addColumn("error_type", "text", (col) => col.notNull().defaultTo("UNCLASSIFIED")).execute();
4349
4715
  }
4350
- async function down(db) {
4716
+ async function down$1(db) {
4351
4717
  await db.schema.alterTable("sync_dead_letters").dropColumn("error_type").execute();
4352
4718
  }
4353
4719
  //#endregion
4720
+ //#region src/storage/migrations/017_create_group_references.ts
4721
+ var _017_create_group_references_exports = /* @__PURE__ */ __exportAll({
4722
+ down: () => down,
4723
+ up: () => up
4724
+ });
4725
+ /**
4726
+ * One row per (document, group) reference ever discovered from an auth
4727
+ * operation's input. Rows are never updated or deleted: auth evaluation is
4728
+ * positional, so a grant that named a group at any position keeps that
4729
+ * group's stream in the document's read-set even after a later operation
4730
+ * removes the reference. Read by documentId for the groups a document
4731
+ * requires (sync), and by groupId for the documents a group change affects
4732
+ * (re-evaluation).
4733
+ */
4734
+ async function up(db) {
4735
+ await db.schema.createTable("group_references").addColumn("documentId", "text", (col) => col.notNull()).addColumn("groupId", "text", (col) => col.notNull()).addPrimaryKeyConstraint("group_references_pkey", ["documentId", "groupId"]).execute();
4736
+ await db.schema.createIndex("idx_group_references_groupId").on("group_references").column("groupId").execute();
4737
+ }
4738
+ async function down(db) {
4739
+ await db.schema.dropTable("group_references").execute();
4740
+ }
4741
+ //#endregion
4354
4742
  //#region src/storage/migrations/migrator.ts
4355
4743
  const REACTOR_SCHEMA = "reactor";
4356
4744
  const migrations = {
@@ -4369,7 +4757,8 @@ const migrations = {
4369
4757
  "013_create_sync_dead_letters_table": _013_create_sync_dead_letters_table_exports,
4370
4758
  "014_create_processor_cursor_table": _014_create_processor_cursor_table_exports,
4371
4759
  "015_add_operation_denied_reason": _015_add_operation_denied_reason_exports,
4372
- "016_add_dead_letter_error_type": _016_add_dead_letter_error_type_exports
4760
+ "016_add_dead_letter_error_type": _016_add_dead_letter_error_type_exports,
4761
+ "017_create_group_references": _017_create_group_references_exports
4373
4762
  };
4374
4763
  var ProgrammaticMigrationProvider = class {
4375
4764
  getMigrations() {
@@ -4425,4 +4814,4 @@ const DEFAULT_DRIVE_CONTAINER_TYPES = new Set(["powerhouse/document-drive", "pow
4425
4814
  //#endregion
4426
4815
  export { OptimisticLockError as A, ExcessiveReshuffleError as B, DocumentMetaCache as C, APPEND_CONDITION_FAILED_PREFIX as D, CollectionMembershipCache as E, ModuleNotFoundError as F, throwIfAborted as G, UpgradePreconditionFailedError as H, AuthTimestampNotMonotonicError as I, __exportAll as K, AuthorizationDeniedError as L, DuplicateManifestError as M, DuplicateModuleError as N, AppendConditionFailedError as O, InvalidModuleError as P, DocumentDeletedError as R, KyselyOperationIndex as S, createEmptyConsistencyToken as T, matchesScope as U, InvalidOperationTimestampError as V, parsePagingOptions as W, KyselyExecutionScope as _, createForwardingPoolInstrumentation as a, EventBus as b, KyselyKeyframeStore as c, DriveCollectionId as d, decideAtHead as f, authDecisionModel as g, buildDecisionModel as h, runMigrations as i, RevisionMismatchError as j, DuplicateOperationError as k, DocumentModelRegistry as l, documentDecisionModel as m, REACTOR_SCHEMA as n, instrumentPgPool as o, selectDecisionModel as p, getMigrationStatus as r, KyselyOperationStore as s, DEFAULT_DRIVE_CONTAINER_TYPES as t, SimpleJobExecutor as u, FLAG_PREREQUISITES as v, createConsistencyToken as w, KyselyWriteCache as x, validateFeatureFlags as y, DocumentNotFoundError as z };
4427
4816
 
4428
- //# sourceMappingURL=drive-container-types-CU1ZUfD1.js.map
4817
+ //# sourceMappingURL=drive-container-types-h3M1AK3K.js.map