mindoodb 0.0.45 → 0.0.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.js +4 -0
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
- package/dist/browser/index.mjs +31 -0
- package/dist/core/BaseMindooDB.d.ts +75 -0
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +204 -0
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +22 -0
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +38 -0
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/DocumentDagAnalysis.d.ts +32 -0
- package/dist/core/DocumentDagAnalysis.d.ts.map +1 -1
- package/dist/core/DocumentDagAnalysis.js +46 -6
- package/dist/core/DocumentDagAnalysis.js.map +1 -1
- package/dist/core/copy/attachments.d.ts +53 -0
- package/dist/core/copy/attachments.d.ts.map +1 -0
- package/dist/core/copy/attachments.js +186 -0
- package/dist/core/copy/attachments.js.map +1 -0
- package/dist/core/copy/copyDocument.d.ts +86 -0
- package/dist/core/copy/copyDocument.d.ts.map +1 -0
- package/dist/core/copy/copyDocument.js +369 -0
- package/dist/core/copy/copyDocument.js.map +1 -0
- package/dist/core/copy/copyDocuments.d.ts +57 -0
- package/dist/core/copy/copyDocuments.d.ts.map +1 -0
- package/dist/core/copy/copyDocuments.js +167 -0
- package/dist/core/copy/copyDocuments.js.map +1 -0
- package/dist/core/copy/entryRewrite.d.ts +49 -0
- package/dist/core/copy/entryRewrite.d.ts.map +1 -0
- package/dist/core/copy/entryRewrite.js +74 -0
- package/dist/core/copy/entryRewrite.js.map +1 -0
- package/dist/core/copy/feasibility.d.ts +73 -0
- package/dist/core/copy/feasibility.d.ts.map +1 -0
- package/dist/core/copy/feasibility.js +169 -0
- package/dist/core/copy/feasibility.js.map +1 -0
- package/dist/core/copy/flatten.d.ts +55 -0
- package/dist/core/copy/flatten.d.ts.map +1 -0
- package/dist/core/copy/flatten.js +155 -0
- package/dist/core/copy/flatten.js.map +1 -0
- package/dist/core/copy/host.d.ts +92 -0
- package/dist/core/copy/host.d.ts.map +1 -0
- package/dist/core/copy/host.js +16 -0
- package/dist/core/copy/host.js.map +1 -0
- package/dist/core/copy/preflight.d.ts +62 -0
- package/dist/core/copy/preflight.d.ts.map +1 -0
- package/dist/core/copy/preflight.js +98 -0
- package/dist/core/copy/preflight.js.map +1 -0
- package/dist/core/copy/provenance.d.ts +74 -0
- package/dist/core/copy/provenance.d.ts.map +1 -0
- package/dist/core/copy/provenance.js +91 -0
- package/dist/core/copy/provenance.js.map +1 -0
- package/dist/core/copy/types.d.ts +418 -0
- package/dist/core/copy/types.d.ts.map +1 -0
- package/dist/core/copy/types.js +21 -0
- package/dist/core/copy/types.js.map +1 -0
- package/dist/core/crypto/DomainStatement.d.ts +44 -0
- package/dist/core/crypto/DomainStatement.d.ts.map +1 -0
- package/dist/core/crypto/DomainStatement.js +71 -0
- package/dist/core/crypto/DomainStatement.js.map +1 -0
- package/dist/core/crypto/EntrySignature.d.ts +109 -15
- package/dist/core/crypto/EntrySignature.d.ts.map +1 -1
- package/dist/core/crypto/EntrySignature.js +83 -22
- package/dist/core/crypto/EntrySignature.js.map +1 -1
- package/dist/core/index.d.ts +21 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +54 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/timestamps/timestampClient.d.ts +88 -0
- package/dist/core/timestamps/timestampClient.d.ts.map +1 -0
- package/dist/core/timestamps/timestampClient.js +157 -0
- package/dist/core/timestamps/timestampClient.js.map +1 -0
- package/dist/core/types.d.ts +118 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/node/server/MindooDBServer.d.ts +37 -0
- package/dist/node/server/MindooDBServer.d.ts.map +1 -1
- package/dist/node/server/MindooDBServer.js +157 -0
- package/dist/node/server/MindooDBServer.js.map +1 -1
- package/dist/node/server/config.d.ts.map +1 -1
- package/dist/node/server/config.js +16 -0
- package/dist/node/server/config.js.map +1 -1
- package/dist/node/server/timestampProxy.d.ts +129 -0
- package/dist/node/server/timestampProxy.d.ts.map +1 -0
- package/dist/node/server/timestampProxy.js +361 -0
- package/dist/node/server/timestampProxy.js.map +1 -0
- package/dist/node/server/types.d.ts +20 -0
- package/dist/node/server/types.d.ts.map +1 -1
- package/dist/node/server/types.js +12 -0
- package/dist/node/server/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -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"}
|