mindoodb 0.0.45 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +9 -0
  2. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  3. package/dist/appendonlystores/network/HttpTransport.js +4 -0
  4. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  5. package/dist/browser/index.mjs +31 -0
  6. package/dist/core/BaseMindooDB.d.ts +74 -0
  7. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  8. package/dist/core/BaseMindooDB.js +160 -0
  9. package/dist/core/BaseMindooDB.js.map +1 -1
  10. package/dist/core/BaseMindooTenant.d.ts +22 -0
  11. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  12. package/dist/core/BaseMindooTenant.js +38 -0
  13. package/dist/core/BaseMindooTenant.js.map +1 -1
  14. package/dist/core/copy/attachments.d.ts +53 -0
  15. package/dist/core/copy/attachments.d.ts.map +1 -0
  16. package/dist/core/copy/attachments.js +186 -0
  17. package/dist/core/copy/attachments.js.map +1 -0
  18. package/dist/core/copy/copyDocument.d.ts +86 -0
  19. package/dist/core/copy/copyDocument.d.ts.map +1 -0
  20. package/dist/core/copy/copyDocument.js +369 -0
  21. package/dist/core/copy/copyDocument.js.map +1 -0
  22. package/dist/core/copy/copyDocuments.d.ts +57 -0
  23. package/dist/core/copy/copyDocuments.d.ts.map +1 -0
  24. package/dist/core/copy/copyDocuments.js +167 -0
  25. package/dist/core/copy/copyDocuments.js.map +1 -0
  26. package/dist/core/copy/entryRewrite.d.ts +49 -0
  27. package/dist/core/copy/entryRewrite.d.ts.map +1 -0
  28. package/dist/core/copy/entryRewrite.js +74 -0
  29. package/dist/core/copy/entryRewrite.js.map +1 -0
  30. package/dist/core/copy/feasibility.d.ts +73 -0
  31. package/dist/core/copy/feasibility.d.ts.map +1 -0
  32. package/dist/core/copy/feasibility.js +169 -0
  33. package/dist/core/copy/feasibility.js.map +1 -0
  34. package/dist/core/copy/flatten.d.ts +55 -0
  35. package/dist/core/copy/flatten.d.ts.map +1 -0
  36. package/dist/core/copy/flatten.js +155 -0
  37. package/dist/core/copy/flatten.js.map +1 -0
  38. package/dist/core/copy/host.d.ts +92 -0
  39. package/dist/core/copy/host.d.ts.map +1 -0
  40. package/dist/core/copy/host.js +16 -0
  41. package/dist/core/copy/host.js.map +1 -0
  42. package/dist/core/copy/preflight.d.ts +62 -0
  43. package/dist/core/copy/preflight.d.ts.map +1 -0
  44. package/dist/core/copy/preflight.js +98 -0
  45. package/dist/core/copy/preflight.js.map +1 -0
  46. package/dist/core/copy/provenance.d.ts +74 -0
  47. package/dist/core/copy/provenance.d.ts.map +1 -0
  48. package/dist/core/copy/provenance.js +91 -0
  49. package/dist/core/copy/provenance.js.map +1 -0
  50. package/dist/core/copy/types.d.ts +418 -0
  51. package/dist/core/copy/types.d.ts.map +1 -0
  52. package/dist/core/copy/types.js +21 -0
  53. package/dist/core/copy/types.js.map +1 -0
  54. package/dist/core/crypto/DomainStatement.d.ts +44 -0
  55. package/dist/core/crypto/DomainStatement.d.ts.map +1 -0
  56. package/dist/core/crypto/DomainStatement.js +71 -0
  57. package/dist/core/crypto/DomainStatement.js.map +1 -0
  58. package/dist/core/crypto/EntrySignature.d.ts +109 -15
  59. package/dist/core/crypto/EntrySignature.d.ts.map +1 -1
  60. package/dist/core/crypto/EntrySignature.js +83 -22
  61. package/dist/core/crypto/EntrySignature.js.map +1 -1
  62. package/dist/core/index.d.ts +21 -0
  63. package/dist/core/index.d.ts.map +1 -1
  64. package/dist/core/index.js +54 -3
  65. package/dist/core/index.js.map +1 -1
  66. package/dist/core/timestamps/timestampClient.d.ts +88 -0
  67. package/dist/core/timestamps/timestampClient.d.ts.map +1 -0
  68. package/dist/core/timestamps/timestampClient.js +157 -0
  69. package/dist/core/timestamps/timestampClient.js.map +1 -0
  70. package/dist/core/types.d.ts +90 -0
  71. package/dist/core/types.d.ts.map +1 -1
  72. package/dist/core/types.js.map +1 -1
  73. package/dist/node/server/MindooDBServer.d.ts +37 -0
  74. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  75. package/dist/node/server/MindooDBServer.js +157 -0
  76. package/dist/node/server/MindooDBServer.js.map +1 -1
  77. package/dist/node/server/config.d.ts.map +1 -1
  78. package/dist/node/server/config.js +16 -0
  79. package/dist/node/server/config.js.map +1 -1
  80. package/dist/node/server/timestampProxy.d.ts +129 -0
  81. package/dist/node/server/timestampProxy.d.ts.map +1 -0
  82. package/dist/node/server/timestampProxy.js +361 -0
  83. package/dist/node/server/timestampProxy.js.map +1 -0
  84. package/dist/node/server/types.d.ts +20 -0
  85. package/dist/node/server/types.d.ts.map +1 -1
  86. package/dist/node/server/types.js +12 -0
  87. package/dist/node/server/types.js.map +1 -1
  88. package/package.json +1 -1
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ /**
3
+ * The flatten copy strategy.
4
+ *
5
+ * Reads the source document's state — at its head, or as of
6
+ * {@link CopyDocumentOptions.atTimestamp} — and writes it into the target as a
7
+ * brand-new document with a single revision. History, original authors and the
8
+ * CRDT structure are all deliberately left behind; what arrives is a clean
9
+ * document that happens to have the same content.
10
+ *
11
+ * Unlike the two history strategies this one goes entirely through the public
12
+ * document API, which is what makes it work regardless of tenant, key or
13
+ * document id: `createDocument` + one `changeDoc`, with attachments re-uploaded
14
+ * through `addAttachmentStream` so they get fresh chunk ids in the target.
15
+ *
16
+ * **Caveat worth knowing:** materializing an Automerge document to plain JS
17
+ * collapses its text fields into ordinary strings. A flattened copy therefore
18
+ * loses the character-level text CRDT structure, and concurrent edits to those
19
+ * fields merge as whole-value conflicts rather than per-character. That is
20
+ * usually the right trade for a fresh document, but it is the reason
21
+ * `mode: "history"` exists.
22
+ *
23
+ * @module
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.flattenDocument = flattenDocument;
27
+ const types_1 = require("../types");
28
+ /** Payload key holding {@link FlattenedDocumentProvenance}. */
29
+ const PROVENANCE_FIELD = "_provenance";
30
+ /**
31
+ * Materialize the source payload as plain data the target can accept.
32
+ *
33
+ * `_`-prefixed keys are MindooDB's own namespace (`_attachments`,
34
+ * `_lastModified`) and are managed by the target database, so they are dropped
35
+ * rather than copied over stale.
36
+ */
37
+ function copyablePayload(payload) {
38
+ const copy = {};
39
+ for (const [key, value] of Object.entries(payload)) {
40
+ if (key.startsWith("_"))
41
+ continue;
42
+ copy[key] = structuredClone(value);
43
+ }
44
+ return copy;
45
+ }
46
+ /** Flatten one document into the target database. */
47
+ async function flattenDocument(source, target, context, options, reporter) {
48
+ const { sourceDocId, targetDocId } = context;
49
+ reporter.emit("planning", `Reading ${sourceDocId} for a flattened copy`);
50
+ const sourceDoc = options.atTimestamp != null
51
+ ? await source.db.getDocumentAtTimestamp(sourceDocId, options.atTimestamp)
52
+ : await source.db.getDocument(sourceDocId);
53
+ if (!sourceDoc) {
54
+ throw new Error(options.atTimestamp != null
55
+ ? `Document ${sourceDocId} did not exist at timestamp ${options.atTimestamp}.`
56
+ : `Document ${sourceDocId} was not found in database ${context.sourceDbId}.`);
57
+ }
58
+ const usesGeneratedId = (options.targetDocId ?? "new") === "new";
59
+ if (!usesGeneratedId && !types_1.CUSTOM_DOC_ID_REGEX.test(targetDocId)) {
60
+ // Ids minted by MindooDB may start with a digit, which the caller-provided
61
+ // id rules reject. Say so here rather than letting createDocument fail with
62
+ // a message that gives no hint about the copy.
63
+ throw new Error(`Cannot flatten into document id '${targetDocId}': caller-provided ids must ` +
64
+ "match /^[A-Za-z][A-Za-z0-9_]*$/. Copy with mode 'history' to keep a " +
65
+ "MindooDB-generated id, or pass an explicit targetDocId that satisfies the rule.");
66
+ }
67
+ const copiedByPublicKey = options.signingKeyPair?.publicKey ??
68
+ (await target.tenant.getCurrentUserId()).userSigningPublicKey;
69
+ const payload = copyablePayload(sourceDoc.getData());
70
+ let provenance;
71
+ if (options.provenance !== false) {
72
+ provenance = {
73
+ copiedAt: Date.now(),
74
+ sourceTenantId: context.sourceTenantId,
75
+ sourceDbId: context.sourceDbId,
76
+ sourceDocId,
77
+ copiedByPublicKey,
78
+ };
79
+ if (options.atTimestamp != null) {
80
+ // Automerge rejects an explicit `undefined`, so optional fields have to be
81
+ // left out entirely rather than assigned an absent value.
82
+ provenance.sourceAtTimestamp = options.atTimestamp;
83
+ }
84
+ }
85
+ const includeAttachments = options.includeAttachments !== false;
86
+ const sourceAttachments = includeAttachments ? sourceDoc.getAttachments() : [];
87
+ // Fold the payload into the `doc_create` change instead of writing a second
88
+ // entry for it. A caller-provided id normally cannot do this: it is seeded
89
+ // from a hard-coded Automerge change so independent replicas converge on the
90
+ // same hash, and baking content into that change would diverge it per
91
+ // document — `createDocument` rejects the combination. `assumeUniqueId` is
92
+ // the caller's assertion that concurrent same-id creation cannot happen, so
93
+ // convergence is moot and the create can take the generated-id path.
94
+ //
95
+ // `copyablePayload` has already dropped every `_`-prefixed key, so nothing is
96
+ // silently lost to `initialValues`' own reserved-field sanitizer.
97
+ const assumeUniqueId = !usesGeneratedId && options.assumeUniqueTargetDocId === true;
98
+ const seedPayloadIntoCreate = usesGeneratedId || assumeUniqueId;
99
+ const idsBefore = new Set((await target.store.findNewEntriesForDoc([], targetDocId)).map((entry) => entry.id));
100
+ reporter.emit("transferring", `Writing flattened copy as ${targetDocId}`);
101
+ const targetDoc = await target.db.createDocument({
102
+ ...(usesGeneratedId
103
+ ? { idPrefix: options.idPrefix }
104
+ : { id: targetDocId, assumeUniqueId }),
105
+ ...(seedPayloadIntoCreate ? { initialValues: payload } : {}),
106
+ decryptionKeyId: context.targetDecryptionKeyId,
107
+ signingKeyPair: options.signingKeyPair,
108
+ signingKeyPassword: options.signingKeyPassword,
109
+ bypassAccessControlPrecheck: options.bypassAccessControlPrecheck,
110
+ });
111
+ // Whatever the create could not carry. `_provenance` is in MindooDB's
112
+ // reserved namespace, which `initialValues` refuses to seed, so asking for
113
+ // provenance costs the second entry — the price of keeping the marker out of
114
+ // the caller's field namespace, where a later re-copy would treat it as
115
+ // ordinary content.
116
+ const needsFollowUpChange = !seedPayloadIntoCreate || provenance !== undefined || sourceAttachments.length > 0;
117
+ if (needsFollowUpChange) {
118
+ await target.db.changeDoc(targetDoc, async (draft) => {
119
+ if (!seedPayloadIntoCreate) {
120
+ Object.assign(draft.getData(), payload);
121
+ }
122
+ if (provenance) {
123
+ draft.getData()[PROVENANCE_FIELD] = provenance;
124
+ }
125
+ for (const attachment of sourceAttachments) {
126
+ if (options.signal?.aborted)
127
+ break;
128
+ await draft.addAttachmentStream(sourceDoc.streamAttachment(attachment.attachmentId), attachment.fileName, attachment.mimeType);
129
+ }
130
+ }, {
131
+ signingKeyPair: options.signingKeyPair,
132
+ signingKeyPassword: options.signingKeyPassword,
133
+ bypassAccessControlPrecheck: options.bypassAccessControlPrecheck,
134
+ });
135
+ }
136
+ const written = (await target.store.findNewEntriesForDoc([], targetDoc.getId())).filter((entry) => !idsBefore.has(entry.id));
137
+ reporter.copiedEntries += written.length;
138
+ for (const entry of written) {
139
+ reporter.copiedBytes += entry.encryptedSize;
140
+ }
141
+ reporter.copiedAttachments += sourceAttachments.length;
142
+ reporter.emit("complete", `Flattened ${sourceDocId} into ${targetDoc.getId()}`);
143
+ return {
144
+ sourceDocId,
145
+ targetDocId: targetDoc.getId(),
146
+ strategy: "flatten",
147
+ authorshipPreserved: false,
148
+ mergedIntoExisting: context.targetHasDocId,
149
+ copiedEntries: written.length,
150
+ copiedBytes: written.reduce((total, entry) => total + entry.encryptedSize, 0),
151
+ copiedAttachments: sourceAttachments.length,
152
+ cancelled: options.signal?.aborted ?? false,
153
+ };
154
+ }
155
+ //# sourceMappingURL=flatten.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flatten.js","sourceRoot":"","sources":["../../../src/core/copy/flatten.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The narrow seam the copy engine uses to reach into a database.
3
+ *
4
+ * `BaseMindooDB` is already very large, so the copy engine lives in its own
5
+ * module and receives only what it needs through {@link CopyEngineHost} rather
6
+ * than being written as more methods on the class. A handful of the things it
7
+ * needs (entry signing, the attachment-ref snapshot) are private on
8
+ * `BaseMindooDB`; the class exposes them here through one `@internal` accessor
9
+ * instead of widening their visibility.
10
+ *
11
+ * @internal
12
+ * @module
13
+ */
14
+ import type { ContentAddressedStore } from "../appendonlystores/types";
15
+ import type { AccessDecision, RuleType } from "../accesscontrol/types";
16
+ import type { EntryProvenance } from "../crypto/EntrySignature";
17
+ import type { MindooDB, MindooDocPayload, MindooTenant, SigningKeyPair, StoreEntryAttachmentRef, StoreEntryMetadata } from "../types";
18
+ /**
19
+ * Everything the copy engine needs from one side of a copy.
20
+ *
21
+ * @internal
22
+ */
23
+ export interface CopyEngineHost {
24
+ /** The database itself, for the strategies that go through the public API. */
25
+ readonly db: MindooDB;
26
+ /** The tenant owning {@link db}. */
27
+ readonly tenant: MindooTenant;
28
+ /** The document entry store. */
29
+ readonly store: ContentAddressedStore;
30
+ /** The attachment chunk store. */
31
+ readonly attachmentStore: ContentAddressedStore;
32
+ /**
33
+ * Compute the author's metadata-binding signature for an entry, using the
34
+ * supplied signing key when given and the current user's key otherwise.
35
+ * Mirrors what every ordinary write path does, so copied entries are
36
+ * indistinguishable in structure from natively written ones.
37
+ */
38
+ computeEntryMetadataSignature(meta: Pick<StoreEntryMetadata, "entryType" | "id" | "docId" | "decryptionKeyId" | "createdAt" | "dependencyIds" | "contentHash" | "createdByPublicKey" | "attachmentRefs"> & {
39
+ provenance?: EntryProvenance;
40
+ }, signing?: {
41
+ signingKeyPair?: SigningKeyPair;
42
+ signingKeyPassword?: string;
43
+ }): Promise<Uint8Array>;
44
+ /**
45
+ * Build the canonical, sorted attachment-ref snapshot for a payload, matching
46
+ * exactly what the writer would have signed.
47
+ */
48
+ collectAttachmentRefs(doc: MindooDocPayload): StoreEntryAttachmentRef[];
49
+ /**
50
+ * Decrypt an attachment chunk payload.
51
+ *
52
+ * Attachment chunks use a different envelope from document changes (a
53
+ * deterministic IV, so identical plaintext yields identical ciphertext), and
54
+ * the tenant's attachment crypto is not part of the public `MindooTenant`
55
+ * surface — hence the passthrough here rather than widening that interface.
56
+ */
57
+ decryptAttachmentPayload(encryptedPayload: Uint8Array, decryptionKeyId: string): Promise<Uint8Array>;
58
+ /** Encrypt an attachment chunk payload. Counterpart of {@link decryptAttachmentPayload}. */
59
+ encryptAttachmentPayload(payload: Uint8Array, decryptionKeyId: string): Promise<Uint8Array>;
60
+ /** Materialize newly written store entries into the in-memory document state. */
61
+ syncStoreChanges(): Promise<void>;
62
+ /**
63
+ * Evaluate this database's write policy for an entry signed by `signerKey`,
64
+ * exactly as the ordinary write paths do. Resolves to `null` when access
65
+ * control is not enforced here.
66
+ *
67
+ * The copy engine writes entries straight to the store rather than through
68
+ * `changeDoc`, so it does not inherit that precheck and has to run its own —
69
+ * see `preflight.ts` for why that matters most when grafting.
70
+ */
71
+ evaluateWriteAccess(op: RuleType, signerKey: string, isAuthor: boolean): Promise<AccessDecision | null>;
72
+ /**
73
+ * Whether a Tier 2 (content) rule governs `op` on this database. A copy
74
+ * cannot evaluate one — grafting never decrypts anything — so the preflight
75
+ * reports such cases as undecidable instead of guessing.
76
+ */
77
+ hasWriteContentRules(op: RuleType): Promise<boolean>;
78
+ /**
79
+ * Write one `doc_change` that re-points `_attachments[].lastChunkId` at the
80
+ * given chunk ids, keyed by attachment id.
81
+ *
82
+ * Needed only when a document is duplicated inside its own database, where
83
+ * the copied chunks had to be renamed (see `attachments.ts`). There is no
84
+ * public API for this — `changeDoc` deliberately does not let a caller write
85
+ * `_attachments` directly — so the database exposes it here.
86
+ */
87
+ remapAttachmentPointers(docId: string, lastChunkIdByAttachmentId: Map<string, string>, signing?: {
88
+ signingKeyPair: SigningKeyPair;
89
+ signingKeyPassword: string;
90
+ }): Promise<void>;
91
+ }
92
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../../src/core/copy/host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,UAAU,CAAC;AAElB;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,oCAAoC;IACpC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,kCAAkC;IAClC,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC;IAEhD;;;;;OAKG;IACH,6BAA6B,CAC3B,IAAI,EAAE,IAAI,CACR,kBAAkB,EAChB,WAAW,GACX,IAAI,GACJ,OAAO,GACP,iBAAiB,GACjB,WAAW,GACX,eAAe,GACf,aAAa,GACb,oBAAoB,GACpB,gBAAgB,CACnB,GAAG;QAAE,UAAU,CAAC,EAAE,eAAe,CAAA;KAAE,EACpC,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,cAAc,CAAC;QAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAAE,GACzE,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB;;;OAGG;IACH,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,uBAAuB,EAAE,CAAC;IAExE;;;;;;;OAOG;IACH,wBAAwB,CACtB,gBAAgB,EAAE,UAAU,EAC5B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB,4FAA4F;IAC5F,wBAAwB,CACtB,OAAO,EAAE,UAAU,EACnB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB,iFAAiF;IACjF,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC;;;;;;;;OAQG;IACH,mBAAmB,CACjB,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAElC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErD;;;;;;;;OAQG;IACH,uBAAuB,CACrB,KAAK,EAAE,MAAM,EACb,yBAAyB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9C,OAAO,CAAC,EAAE;QAAE,cAAc,EAAE,cAAc,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,GACvE,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * The narrow seam the copy engine uses to reach into a database.
4
+ *
5
+ * `BaseMindooDB` is already very large, so the copy engine lives in its own
6
+ * module and receives only what it needs through {@link CopyEngineHost} rather
7
+ * than being written as more methods on the class. A handful of the things it
8
+ * needs (entry signing, the attachment-ref snapshot) are private on
9
+ * `BaseMindooDB`; the class exposes them here through one `@internal` accessor
10
+ * instead of widening their visibility.
11
+ *
12
+ * @internal
13
+ * @module
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../../src/core/copy/host.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The write-policy preflight for a history copy.
3
+ *
4
+ * Access-control rules are keyed by database id, so a set of entries that was
5
+ * perfectly legal in the source database is not automatically legal in the
6
+ * target. This bites hardest when sharding: a graft carries the *original*
7
+ * authors' signatures, and the server witness evaluates Tier 1 against the
8
+ * **target** `dbid` when the shard is pushed. If the new database's policy does
9
+ * not admit those authors, the entries are accepted locally and then rejected
10
+ * at the witness — after the copy has already run.
11
+ *
12
+ * So the copy checks up front, once per distinct (signer, operation) pair
13
+ * rather than once per entry, since the Tier 1 verdict depends only on that
14
+ * pair. What it deliberately does NOT do is decide Tier 2 (content) rules: a
15
+ * graft never decrypts anything, so it cannot evaluate them, and it reports
16
+ * them as undecidable rather than guessing.
17
+ *
18
+ * @module
19
+ */
20
+ import type { AccessDecision, RuleType } from "../accesscontrol/types";
21
+ import type { StoreEntryMetadata } from "../types";
22
+ import type { CopyEngineHost } from "./host";
23
+ /** One (signer, operation) pair the target database was asked about. */
24
+ export interface CopyAdmissionCheck {
25
+ /** The key that will sign the copied entries of this kind in the target. */
26
+ signerPublicKey: string;
27
+ /** The operation those entries represent. */
28
+ op: RuleType;
29
+ /** How many entries in the copy fall under this pair. */
30
+ entryCount: number;
31
+ /** The target's verdict, or `null` when access control is not enforced. */
32
+ decision: AccessDecision | null;
33
+ }
34
+ /** What the preflight established about a copy. */
35
+ export interface CopyAdmissionPreflight {
36
+ /** True when nothing the copy will write is denied by the target policy. */
37
+ admitted: boolean;
38
+ /** The pairs the target denied. Empty when {@link admitted} is true. */
39
+ denied: CopyAdmissionCheck[];
40
+ /**
41
+ * Operations governed by a Tier 2 content rule on the target. The copy cannot
42
+ * evaluate those without decrypting, so the witness may still reject entries
43
+ * this preflight passed. Advisory only.
44
+ */
45
+ undecidableOps: RuleType[];
46
+ /** Every pair that was checked, for diagnostics. */
47
+ checked: CopyAdmissionCheck[];
48
+ }
49
+ /**
50
+ * Ask the target database whether it admits everything the copy will write.
51
+ *
52
+ * @param target The database being written to.
53
+ * @param entries The source entries about to be copied.
54
+ * @param signerFor Resolves the key that will sign a copied entry: the original
55
+ * author under a graft, the copying user under a replay.
56
+ * @param documentCreatorKey The source document's creator key, used to answer
57
+ * `$author` ownership rules.
58
+ */
59
+ export declare function preflightCopyAdmission(target: CopyEngineHost, entries: StoreEntryMetadata[], signerFor: (entry: StoreEntryMetadata) => string, documentCreatorKey: string | null): Promise<CopyAdmissionPreflight>;
60
+ /** Render a preflight refusal as an actionable message. */
61
+ export declare function describeAdmissionFailure(preflight: CopyAdmissionPreflight, targetDbId: string): string;
62
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../../src/core/copy/preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,wEAAwE;AACxE,MAAM,WAAW,kBAAkB;IACjC,4EAA4E;IAC5E,eAAe,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,EAAE,EAAE,QAAQ,CAAC;IACb,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;CACjC;AAED,mDAAmD;AACnD,MAAM,WAAW,sBAAsB;IACrC,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;IAClB,wEAAwE;IACxE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B;;;;OAIG;IACH,cAAc,EAAE,QAAQ,EAAE,CAAC;IAC3B,oDAAoD;IACpD,OAAO,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAWD;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,kBAAkB,EAAE,EAC7B,SAAS,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,MAAM,EAChD,kBAAkB,EAAE,MAAM,GAAG,IAAI,GAChC,OAAO,CAAC,sBAAsB,CAAC,CA0CjC;AAED,2DAA2D;AAC3D,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,sBAAsB,EACjC,UAAU,EAAE,MAAM,GACjB,MAAM,CAgBR"}
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ /**
3
+ * The write-policy preflight for a history copy.
4
+ *
5
+ * Access-control rules are keyed by database id, so a set of entries that was
6
+ * perfectly legal in the source database is not automatically legal in the
7
+ * target. This bites hardest when sharding: a graft carries the *original*
8
+ * authors' signatures, and the server witness evaluates Tier 1 against the
9
+ * **target** `dbid` when the shard is pushed. If the new database's policy does
10
+ * not admit those authors, the entries are accepted locally and then rejected
11
+ * at the witness — after the copy has already run.
12
+ *
13
+ * So the copy checks up front, once per distinct (signer, operation) pair
14
+ * rather than once per entry, since the Tier 1 verdict depends only on that
15
+ * pair. What it deliberately does NOT do is decide Tier 2 (content) rules: a
16
+ * graft never decrypts anything, so it cannot evaluate them, and it reports
17
+ * them as undecidable rather than guessing.
18
+ *
19
+ * @module
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.preflightCopyAdmission = preflightCopyAdmission;
23
+ exports.describeAdmissionFailure = describeAdmissionFailure;
24
+ /** Entry types that are subject to a write rule. */
25
+ const RULE_OPS = new Set([
26
+ "doc_create",
27
+ "doc_change",
28
+ "doc_delete",
29
+ "doc_undelete",
30
+ "doc_snapshot",
31
+ ]);
32
+ /**
33
+ * Ask the target database whether it admits everything the copy will write.
34
+ *
35
+ * @param target The database being written to.
36
+ * @param entries The source entries about to be copied.
37
+ * @param signerFor Resolves the key that will sign a copied entry: the original
38
+ * author under a graft, the copying user under a replay.
39
+ * @param documentCreatorKey The source document's creator key, used to answer
40
+ * `$author` ownership rules.
41
+ */
42
+ async function preflightCopyAdmission(target, entries, signerFor, documentCreatorKey) {
43
+ const pairs = new Map();
44
+ for (const entry of entries) {
45
+ if (!RULE_OPS.has(entry.entryType))
46
+ continue;
47
+ const op = entry.entryType;
48
+ const signerPublicKey = signerFor(entry);
49
+ const key = `${op}\u0000${signerPublicKey}`;
50
+ const existing = pairs.get(key);
51
+ if (existing) {
52
+ existing.entryCount++;
53
+ }
54
+ else {
55
+ pairs.set(key, { signerPublicKey, op, entryCount: 1 });
56
+ }
57
+ }
58
+ const checked = [];
59
+ const denied = [];
60
+ const undecidableOps = new Set();
61
+ for (const pair of pairs.values()) {
62
+ const decision = await target.evaluateWriteAccess(pair.op, pair.signerPublicKey, documentCreatorKey !== null && pair.signerPublicKey === documentCreatorKey);
63
+ const check = { ...pair, decision };
64
+ checked.push(check);
65
+ if (decision && !decision.allowed) {
66
+ denied.push(check);
67
+ continue;
68
+ }
69
+ if (decision && (await target.hasWriteContentRules(pair.op))) {
70
+ undecidableOps.add(pair.op);
71
+ }
72
+ }
73
+ return {
74
+ admitted: denied.length === 0,
75
+ denied,
76
+ undecidableOps: [...undecidableOps],
77
+ checked,
78
+ };
79
+ }
80
+ /** Render a preflight refusal as an actionable message. */
81
+ function describeAdmissionFailure(preflight, targetDbId) {
82
+ const details = preflight.denied
83
+ .map((check) => `${check.op} signed by ${shortKey(check.signerPublicKey)} ` +
84
+ `(${check.entryCount} ${check.entryCount === 1 ? "entry" : "entries"}): ` +
85
+ `${check.decision?.reason ?? "denied"}`)
86
+ .join("; ");
87
+ return (`The target database '${targetDbId}' does not admit every entry this copy would ` +
88
+ `write, so the server witness would reject them after the copy ran. ${details}. ` +
89
+ "Grant the original authors the corresponding rights on the target database, copy " +
90
+ "with authorship 'reauthor' so the entries are signed by the copying user, or pass " +
91
+ "bypassAccessControlPrecheck to write anyway.");
92
+ }
93
+ /** The identifying tail of a PEM key, enough to recognize it in a message. */
94
+ function shortKey(publicKeyPem) {
95
+ const body = publicKeyPem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
96
+ return body.length > 12 ? `…${body.slice(-12)}` : body;
97
+ }
98
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../../src/core/copy/preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Building and verifying the provenance record attached to copied entries.
3
+ *
4
+ * When a document is copied with `mode: "history"` but the original signatures
5
+ * cannot be carried over (see `feasibility.ts` for exactly when that is), every
6
+ * produced entry is re-signed by the copying user. That would normally erase
7
+ * all trace of who actually wrote each revision. The provenance record puts it
8
+ * back — and does so verifiably rather than as an unbacked assertion, by
9
+ * embedding the source entry's own signed projection together with the original
10
+ * author's signature over it.
11
+ *
12
+ * @module
13
+ */
14
+ import { type EntryProvenance } from "../crypto/EntrySignature";
15
+ import type { StoreEntryMetadata } from "../types";
16
+ /** Outcome of {@link verifyEntryProvenance}. */
17
+ export type ProvenanceVerificationStatus =
18
+ /** The entry carries no provenance record: it was not produced by a copy. */
19
+ "absent"
20
+ /** The original author's signature over the embedded projection verifies. */
21
+ | "verified"
22
+ /**
23
+ * The record is present and well-formed but carries no source signature,
24
+ * because the source entry predates `metadataSignature`. Informative, not
25
+ * provable.
26
+ */
27
+ | "unverifiable"
28
+ /** A source signature is present and does NOT verify. Treat as tampering. */
29
+ | "invalid";
30
+ /** Result of verifying an entry's provenance record. */
31
+ export interface ProvenanceVerification {
32
+ /** What could be established about the record. */
33
+ status: ProvenanceVerificationStatus;
34
+ /** The record itself, when the entry has one. */
35
+ provenance?: EntryProvenance;
36
+ /**
37
+ * True when this entry's `contentHash` equals the source entry's, i.e. the
38
+ * copy holds byte-identical ciphertext and the verified original signature
39
+ * therefore covers *this* entry's payload too.
40
+ *
41
+ * False when the copy was re-encrypted (a different tenant or key). The
42
+ * provenance then still proves the named author signed the original payload,
43
+ * but nothing ties that payload to this entry's ciphertext beyond the copying
44
+ * user's own signature.
45
+ */
46
+ payloadUnchanged: boolean;
47
+ /** Human-readable explanation for a non-`verified` status. */
48
+ reason?: string;
49
+ }
50
+ /**
51
+ * Capture a source entry as a provenance record for its copy.
52
+ *
53
+ * The projection is taken with the same canonicalization the signer used, so
54
+ * rebuilding it reproduces the original signing input exactly.
55
+ */
56
+ export declare function buildProvenanceFromEntry(sourceEntry: StoreEntryMetadata, sourceTenantId: string, sourceDbId: string): EntryProvenance;
57
+ /**
58
+ * Verify that the original author named in an entry's provenance record really
59
+ * signed the payload the record describes.
60
+ *
61
+ * This is a purely cryptographic check against the public key embedded in the
62
+ * record, which is what makes it work across tenants where the source author is
63
+ * absent from the local directory. It deliberately does NOT establish that the
64
+ * key belongs to the person it claims to — confirm that against the tenant
65
+ * directory in-tenant, or out of band across tenants.
66
+ *
67
+ * A caller that also wants to know the copy itself is intact should verify the
68
+ * entry's own `metadataSignature` as usual; that signature binds this record,
69
+ * so a relay cannot attach, alter or remove it.
70
+ */
71
+ export declare function verifyEntryProvenance(entry: Pick<StoreEntryMetadata, "contentHash"> & {
72
+ provenance?: EntryProvenance;
73
+ }, subtle: SubtleCrypto): Promise<ProvenanceVerification>;
74
+ //# sourceMappingURL=provenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../../src/core/copy/provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,gDAAgD;AAChD,MAAM,MAAM,4BAA4B;AACtC,6EAA6E;AAC3E,QAAQ;AACV,6EAA6E;GAC3E,UAAU;AACZ;;;;GAIG;GACD,cAAc;AAChB,6EAA6E;GAC3E,SAAS,CAAC;AAEd,wDAAwD;AACxD,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,MAAM,EAAE,4BAA4B,CAAC;IACrC,iDAAiD;IACjD,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;;;OASG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,kBAAkB,EAC/B,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,eAAe,CASjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,GAAG;IAAE,UAAU,CAAC,EAAE,eAAe,CAAA;CAAE,EACjF,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,sBAAsB,CAAC,CAyDjC"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * Building and verifying the provenance record attached to copied entries.
4
+ *
5
+ * When a document is copied with `mode: "history"` but the original signatures
6
+ * cannot be carried over (see `feasibility.ts` for exactly when that is), every
7
+ * produced entry is re-signed by the copying user. That would normally erase
8
+ * all trace of who actually wrote each revision. The provenance record puts it
9
+ * back — and does so verifiably rather than as an unbacked assertion, by
10
+ * embedding the source entry's own signed projection together with the original
11
+ * author's signature over it.
12
+ *
13
+ * @module
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.buildProvenanceFromEntry = buildProvenanceFromEntry;
17
+ exports.verifyEntryProvenance = verifyEntryProvenance;
18
+ const EntrySignature_1 = require("../crypto/EntrySignature");
19
+ /**
20
+ * Capture a source entry as a provenance record for its copy.
21
+ *
22
+ * The projection is taken with the same canonicalization the signer used, so
23
+ * rebuilding it reproduces the original signing input exactly.
24
+ */
25
+ function buildProvenanceFromEntry(sourceEntry, sourceTenantId, sourceDbId) {
26
+ return {
27
+ sourceTenantId,
28
+ sourceDbId,
29
+ source: (0, EntrySignature_1.entrySignatureFieldsFromEntry)(sourceEntry),
30
+ sourceMetadataSignature: sourceEntry.metadataSignature
31
+ ? (0, EntrySignature_1.bytesToBase64)(sourceEntry.metadataSignature)
32
+ : undefined,
33
+ };
34
+ }
35
+ /**
36
+ * Verify that the original author named in an entry's provenance record really
37
+ * signed the payload the record describes.
38
+ *
39
+ * This is a purely cryptographic check against the public key embedded in the
40
+ * record, which is what makes it work across tenants where the source author is
41
+ * absent from the local directory. It deliberately does NOT establish that the
42
+ * key belongs to the person it claims to — confirm that against the tenant
43
+ * directory in-tenant, or out of band across tenants.
44
+ *
45
+ * A caller that also wants to know the copy itself is intact should verify the
46
+ * entry's own `metadataSignature` as usual; that signature binds this record,
47
+ * so a relay cannot attach, alter or remove it.
48
+ */
49
+ async function verifyEntryProvenance(entry, subtle) {
50
+ const provenance = entry.provenance;
51
+ if (!provenance) {
52
+ return { status: "absent", payloadUnchanged: false };
53
+ }
54
+ const payloadUnchanged = entry.contentHash === provenance.source.contentHash;
55
+ if (!provenance.sourceMetadataSignature) {
56
+ return {
57
+ status: "unverifiable",
58
+ provenance,
59
+ payloadUnchanged,
60
+ reason: "The source entry predates metadataSignature, so the origin claim cannot " +
61
+ "be checked cryptographically. It is still bound to the copying user's " +
62
+ "own signature.",
63
+ };
64
+ }
65
+ const signingBytes = (0, EntrySignature_1.buildEntrySigningBytes)(provenance.source);
66
+ let verified;
67
+ try {
68
+ const cryptoKey = await (0, EntrySignature_1.importEd25519PublicKeyFromPem)(provenance.source.createdByPublicKey, subtle);
69
+ verified = await subtle.verify({ name: "Ed25519" }, cryptoKey, (0, EntrySignature_1.base64ToBytes)(provenance.sourceMetadataSignature).buffer, signingBytes.buffer);
70
+ }
71
+ catch (error) {
72
+ return {
73
+ status: "invalid",
74
+ provenance,
75
+ payloadUnchanged,
76
+ reason: `Provenance verification failed: ${error instanceof Error ? error.message : String(error)}`,
77
+ };
78
+ }
79
+ if (!verified) {
80
+ return {
81
+ status: "invalid",
82
+ provenance,
83
+ payloadUnchanged,
84
+ reason: "The embedded source signature does not match the embedded source " +
85
+ "projection. The provenance record has been tampered with or was " +
86
+ "fabricated.",
87
+ };
88
+ }
89
+ return { status: "verified", provenance, payloadUnchanged };
90
+ }
91
+ //# sourceMappingURL=provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../../src/core/copy/provenance.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAsDH,4DAaC;AAgBD,sDA4DC;AA7ID,6DAOkC;AAuClC;;;;;GAKG;AACH,SAAgB,wBAAwB,CACtC,WAA+B,EAC/B,cAAsB,EACtB,UAAkB;IAElB,OAAO;QACL,cAAc;QACd,UAAU;QACV,MAAM,EAAE,IAAA,8CAA6B,EAAC,WAAW,CAAC;QAClD,uBAAuB,EAAE,WAAW,CAAC,iBAAiB;YACpD,CAAC,CAAC,IAAA,8BAAa,EAAC,WAAW,CAAC,iBAAiB,CAAC;YAC9C,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,qBAAqB,CACzC,KAAiF,EACjF,MAAoB;IAEpB,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACpC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW,KAAK,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC;IAE7E,IAAI,CAAC,UAAU,CAAC,uBAAuB,EAAE,CAAC;QACxC,OAAO;YACL,MAAM,EAAE,cAAc;YACtB,UAAU;YACV,gBAAgB;YAChB,MAAM,EACJ,0EAA0E;gBAC1E,wEAAwE;gBACxE,gBAAgB;SACnB,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,IAAA,uCAAsB,EAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC/D,IAAI,QAAiB,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,IAAA,8CAA6B,EACnD,UAAU,CAAC,MAAM,CAAC,kBAAkB,EACpC,MAAM,CACP,CAAC;QACF,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAC5B,EAAE,IAAI,EAAE,SAAS,EAAE,EACnB,SAAS,EACT,IAAA,8BAAa,EAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC,MAAqB,EACvE,YAAY,CAAC,MAAqB,CACnC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,UAAU;YACV,gBAAgB;YAChB,MAAM,EAAE,mCACN,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE;SACH,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,MAAM,EAAE,SAAS;YACjB,UAAU;YACV,gBAAgB;YAChB,MAAM,EACJ,mEAAmE;gBACnE,kEAAkE;gBAClE,aAAa;SAChB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC;AAC9D,CAAC"}