@kya-os/mcp 1.10.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUDITABILITY.md +441 -0
- package/CHANGELOG.md +108 -0
- package/CONFORMANCE.md +600 -0
- package/README.md +69 -6
- package/SPEC.md +1820 -0
- package/dist/audit/adapters/legacy-audit-log.d.ts +20 -0
- package/dist/audit/adapters/legacy-audit-log.d.ts.map +1 -0
- package/dist/audit/adapters/legacy-audit-log.js +56 -0
- package/dist/audit/adapters/legacy-audit-log.js.map +1 -0
- package/dist/audit/adapters/mcp.d.ts +105 -0
- package/dist/audit/adapters/mcp.d.ts.map +1 -0
- package/dist/audit/adapters/mcp.js +213 -0
- package/dist/audit/adapters/mcp.js.map +1 -0
- package/dist/audit/assurance.d.ts +22 -0
- package/dist/audit/assurance.d.ts.map +1 -0
- package/dist/audit/assurance.js +39 -0
- package/dist/audit/assurance.js.map +1 -0
- package/dist/audit/checkpoint-coordinator.d.ts +51 -0
- package/dist/audit/checkpoint-coordinator.d.ts.map +1 -0
- package/dist/audit/checkpoint-coordinator.js +112 -0
- package/dist/audit/checkpoint-coordinator.js.map +1 -0
- package/dist/audit/checkpoint.d.ts +59 -0
- package/dist/audit/checkpoint.d.ts.map +1 -0
- package/dist/audit/checkpoint.js +219 -0
- package/dist/audit/checkpoint.js.map +1 -0
- package/dist/audit/cli.d.ts +8 -0
- package/dist/audit/cli.d.ts.map +1 -0
- package/dist/audit/cli.js +85 -0
- package/dist/audit/cli.js.map +1 -0
- package/dist/audit/crypto.d.ts +37 -0
- package/dist/audit/crypto.d.ts.map +1 -0
- package/dist/audit/crypto.js +68 -0
- package/dist/audit/crypto.js.map +1 -0
- package/dist/audit/errors.d.ts +29 -0
- package/dist/audit/errors.d.ts.map +1 -0
- package/dist/audit/errors.js +33 -0
- package/dist/audit/errors.js.map +1 -0
- package/dist/audit/evidence.d.ts +45 -0
- package/dist/audit/evidence.d.ts.map +1 -0
- package/dist/audit/evidence.js +151 -0
- package/dist/audit/evidence.js.map +1 -0
- package/dist/audit/index.d.ts +30 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +30 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/integrity.d.ts +19 -0
- package/dist/audit/integrity.d.ts.map +1 -0
- package/dist/audit/integrity.js +64 -0
- package/dist/audit/integrity.js.map +1 -0
- package/dist/audit/merkle.d.ts +31 -0
- package/dist/audit/merkle.d.ts.map +1 -0
- package/dist/audit/merkle.js +195 -0
- package/dist/audit/merkle.js.map +1 -0
- package/dist/audit/mirror-service.d.ts +19 -0
- package/dist/audit/mirror-service.d.ts.map +1 -0
- package/dist/audit/mirror-service.js +54 -0
- package/dist/audit/mirror-service.js.map +1 -0
- package/dist/audit/projection.d.ts +90 -0
- package/dist/audit/projection.d.ts.map +1 -0
- package/dist/audit/projection.js +142 -0
- package/dist/audit/projection.js.map +1 -0
- package/dist/audit/providers/anchor.d.ts +23 -0
- package/dist/audit/providers/anchor.d.ts.map +1 -0
- package/dist/audit/providers/anchor.js +25 -0
- package/dist/audit/providers/anchor.js.map +1 -0
- package/dist/audit/providers/evidence.d.ts +36 -0
- package/dist/audit/providers/evidence.d.ts.map +1 -0
- package/dist/audit/providers/evidence.js +2 -0
- package/dist/audit/providers/evidence.js.map +1 -0
- package/dist/audit/providers/journal.d.ts +35 -0
- package/dist/audit/providers/journal.d.ts.map +1 -0
- package/dist/audit/providers/journal.js +2 -0
- package/dist/audit/providers/journal.js.map +1 -0
- package/dist/audit/providers/memory-journal.d.ts +24 -0
- package/dist/audit/providers/memory-journal.d.ts.map +1 -0
- package/dist/audit/providers/memory-journal.js +73 -0
- package/dist/audit/providers/memory-journal.js.map +1 -0
- package/dist/audit/providers/observer.d.ts +30 -0
- package/dist/audit/providers/observer.d.ts.map +1 -0
- package/dist/audit/providers/observer.js +100 -0
- package/dist/audit/providers/observer.js.map +1 -0
- package/dist/audit/providers/outbox.d.ts +37 -0
- package/dist/audit/providers/outbox.d.ts.map +1 -0
- package/dist/audit/providers/outbox.js +52 -0
- package/dist/audit/providers/outbox.js.map +1 -0
- package/dist/audit/providers/recorder-client.d.ts +22 -0
- package/dist/audit/providers/recorder-client.d.ts.map +1 -0
- package/dist/audit/providers/recorder-client.js +17 -0
- package/dist/audit/providers/recorder-client.js.map +1 -0
- package/dist/audit/providers/source-state.d.ts +37 -0
- package/dist/audit/providers/source-state.d.ts.map +1 -0
- package/dist/audit/providers/source-state.js +94 -0
- package/dist/audit/providers/source-state.js.map +1 -0
- package/dist/audit/read-contract.d.ts +445 -0
- package/dist/audit/read-contract.d.ts.map +1 -0
- package/dist/audit/read-contract.js +126 -0
- package/dist/audit/read-contract.js.map +1 -0
- package/dist/audit/read-service.d.ts +99 -0
- package/dist/audit/read-service.d.ts.map +1 -0
- package/dist/audit/read-service.js +124 -0
- package/dist/audit/read-service.js.map +1 -0
- package/dist/audit/recorder-service.d.ts +78 -0
- package/dist/audit/recorder-service.d.ts.map +1 -0
- package/dist/audit/recorder-service.js +331 -0
- package/dist/audit/recorder-service.js.map +1 -0
- package/dist/audit/reference-recorder.d.ts +43 -0
- package/dist/audit/reference-recorder.d.ts.map +1 -0
- package/dist/audit/reference-recorder.js +131 -0
- package/dist/audit/reference-recorder.js.map +1 -0
- package/dist/audit/replay-bundle.d.ts +52 -0
- package/dist/audit/replay-bundle.d.ts.map +1 -0
- package/dist/audit/replay-bundle.js +551 -0
- package/dist/audit/replay-bundle.js.map +1 -0
- package/dist/audit/schemas.d.ts +1145 -0
- package/dist/audit/schemas.d.ts.map +1 -0
- package/dist/audit/schemas.js +483 -0
- package/dist/audit/schemas.js.map +1 -0
- package/dist/audit/service.d.ts +91 -0
- package/dist/audit/service.d.ts.map +1 -0
- package/dist/audit/service.js +222 -0
- package/dist/audit/service.js.map +1 -0
- package/dist/audit/testing/index.d.ts +2 -0
- package/dist/audit/testing/index.d.ts.map +1 -0
- package/dist/audit/testing/index.js +2 -0
- package/dist/audit/testing/index.js.map +1 -0
- package/dist/audit/testing/provider-contracts.d.ts +51 -0
- package/dist/audit/testing/provider-contracts.d.ts.map +1 -0
- package/dist/audit/testing/provider-contracts.js +391 -0
- package/dist/audit/testing/provider-contracts.js.map +1 -0
- package/dist/audit/types.d.ts +356 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +49 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/audit/verifier.d.ts +73 -0
- package/dist/audit/verifier.d.ts.map +1 -0
- package/dist/audit/verifier.js +322 -0
- package/dist/audit/verifier.js.map +1 -0
- package/dist/card/builder.d.ts +1 -1
- package/dist/card/builder.js +1 -1
- package/dist/card/emit.d.ts +3 -3
- package/dist/card/emit.d.ts.map +1 -1
- package/dist/card/emit.js.map +1 -1
- package/dist/card/index.d.ts +1 -1
- package/dist/card/index.js +1 -1
- package/dist/card/middleware.d.ts +6 -4
- package/dist/card/middleware.d.ts.map +1 -1
- package/dist/card/middleware.js +11 -7
- package/dist/card/middleware.js.map +1 -1
- package/dist/card/proof/build.d.ts +3 -3
- package/dist/card/proof/build.js +3 -3
- package/dist/card/proof/canonical.d.ts.map +1 -1
- package/dist/card/proof/canonical.js +19 -1
- package/dist/card/proof/canonical.js.map +1 -1
- package/dist/card/proof/http-sig.d.ts +1 -1
- package/dist/card/proof/http-sig.js +1 -1
- package/dist/card/proof/index.d.ts +2 -2
- package/dist/card/proof/index.js +2 -2
- package/dist/card/proof/nonce-cache.d.ts +1 -1
- package/dist/card/proof/nonce-cache.js +1 -1
- package/dist/card/proof/types.d.ts +16 -7
- package/dist/card/proof/types.d.ts.map +1 -1
- package/dist/card/proof/types.js +16 -7
- package/dist/card/proof/types.js.map +1 -1
- package/dist/card/proof/verify.d.ts +2 -2
- package/dist/card/proof/verify.js +2 -2
- package/dist/card/schema.d.ts +14 -6
- package/dist/card/schema.d.ts.map +1 -1
- package/dist/card/schema.js +14 -3
- package/dist/card/schema.js.map +1 -1
- package/dist/card/verify.d.ts +1 -1
- package/dist/card/verify.d.ts.map +1 -1
- package/dist/delegation/delegation-graph.d.ts +4 -0
- package/dist/delegation/delegation-graph.d.ts.map +1 -1
- package/dist/delegation/delegation-graph.js +28 -2
- package/dist/delegation/delegation-graph.js.map +1 -1
- package/dist/delegation/statuslist-manager.d.ts +5 -0
- package/dist/delegation/statuslist-manager.d.ts.map +1 -1
- package/dist/delegation/statuslist-manager.js.map +1 -1
- package/dist/delegation/storage/memory-graph-storage.d.ts +4 -2
- package/dist/delegation/storage/memory-graph-storage.d.ts.map +1 -1
- package/dist/delegation/storage/memory-graph-storage.js +18 -0
- package/dist/delegation/storage/memory-graph-storage.js.map +1 -1
- package/dist/delegation/storage/memory-statuslist-storage.d.ts +5 -2
- package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +1 -1
- package/dist/delegation/storage/memory-statuslist-storage.js +21 -2
- package/dist/delegation/storage/memory-statuslist-storage.js.map +1 -1
- package/dist/delegation/utils.d.ts.map +1 -1
- package/dist/delegation/utils.js +2 -42
- package/dist/delegation/utils.js.map +1 -1
- package/dist/delegation/vc-verification-checks.d.ts.map +1 -1
- package/dist/delegation/vc-verification-checks.js +16 -1
- package/dist/delegation/vc-verification-checks.js.map +1 -1
- package/dist/delegation/vc-verifier.d.ts +4 -0
- package/dist/delegation/vc-verifier.d.ts.map +1 -1
- package/dist/delegation/vc-verifier.js +44 -13
- package/dist/delegation/vc-verifier.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/extension/declaration.d.ts +60 -0
- package/dist/extension/declaration.d.ts.map +1 -0
- package/dist/extension/declaration.js +75 -0
- package/dist/extension/declaration.js.map +1 -0
- package/dist/extension/gate.d.ts +110 -0
- package/dist/extension/gate.d.ts.map +1 -0
- package/dist/extension/gate.js +133 -0
- package/dist/extension/gate.js.map +1 -0
- package/dist/extension/index.d.ts +14 -0
- package/dist/extension/index.d.ts.map +1 -0
- package/dist/extension/index.js +14 -0
- package/dist/extension/index.js.map +1 -0
- package/dist/extension/settings.d.ts +70 -0
- package/dist/extension/settings.d.ts.map +1 -0
- package/dist/extension/settings.js +96 -0
- package/dist/extension/settings.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/kya-os-transport.d.ts +1 -1
- package/dist/middleware/kya-os-transport.d.ts.map +1 -1
- package/dist/middleware/kya-os-transport.js +11 -4
- package/dist/middleware/kya-os-transport.js.map +1 -1
- package/dist/middleware/with-kya-os-server.d.ts +6 -0
- package/dist/middleware/with-kya-os-server.d.ts.map +1 -1
- package/dist/middleware/with-kya-os-server.js +2 -0
- package/dist/middleware/with-kya-os-server.js.map +1 -1
- package/dist/middleware/with-kya-os.config-types.d.ts +15 -0
- package/dist/middleware/with-kya-os.config-types.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.config-types.js.map +1 -1
- package/dist/middleware/with-kya-os.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.delegation-gate.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.delegation-gate.js +96 -3
- package/dist/middleware/with-kya-os.delegation-gate.js.map +1 -1
- package/dist/middleware/with-kya-os.delegation-verify.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.delegation-verify.js +2 -2
- package/dist/middleware/with-kya-os.delegation-verify.js.map +1 -1
- package/dist/middleware/with-kya-os.deps.d.ts +2 -0
- package/dist/middleware/with-kya-os.deps.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.js +23 -0
- package/dist/middleware/with-kya-os.js.map +1 -1
- package/dist/middleware/with-kya-os.policy-gate.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.policy-gate.js +29 -4
- package/dist/middleware/with-kya-os.policy-gate.js.map +1 -1
- package/dist/middleware/with-kya-os.protocol.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.protocol.js +10 -0
- package/dist/middleware/with-kya-os.protocol.js.map +1 -1
- package/dist/middleware/with-kya-os.session.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.session.js +208 -2
- package/dist/middleware/with-kya-os.session.js.map +1 -1
- package/dist/middleware/with-kya-os.types.d.ts +6 -0
- package/dist/middleware/with-kya-os.types.d.ts.map +1 -1
- package/dist/middleware/with-kya-os.types.js.map +1 -1
- package/dist/proof/generator.d.ts +14 -2
- package/dist/proof/generator.d.ts.map +1 -1
- package/dist/proof/generator.js +20 -7
- package/dist/proof/generator.js.map +1 -1
- package/dist/proof/index.d.ts +1 -1
- package/dist/proof/index.d.ts.map +1 -1
- package/dist/proof/index.js +1 -1
- package/dist/proof/index.js.map +1 -1
- package/dist/proof/verifier.d.ts +15 -2
- package/dist/proof/verifier.d.ts.map +1 -1
- package/dist/proof/verifier.js +48 -12
- package/dist/proof/verifier.js.map +1 -1
- package/dist/session/manager.js +1 -1
- package/dist/session/manager.js.map +1 -1
- package/dist/types/protocol.d.ts +22 -0
- package/dist/types/protocol.d.ts.map +1 -1
- package/dist/types/protocol.js +31 -5
- package/dist/types/protocol.js.map +1 -1
- package/dist/utils/canonical-json.d.ts +15 -0
- package/dist/utils/canonical-json.d.ts.map +1 -0
- package/dist/utils/canonical-json.js +109 -0
- package/dist/utils/canonical-json.js.map +1 -0
- package/dist/utils/crypto-service.d.ts.map +1 -1
- package/dist/utils/crypto-service.js +5 -0
- package/dist/utils/crypto-service.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +29 -11
- package/schemas/README.md +28 -1
- package/schemas/audit-anchor-receipt.schema.json +16 -0
- package/schemas/audit-bundle-manifest.schema.json +71 -0
- package/schemas/audit-checkpoint.schema.json +37 -0
- package/schemas/audit-consistency-proof.schema.json +17 -0
- package/schemas/audit-entry.schema.json +33 -0
- package/schemas/audit-event.schema.json +168 -0
- package/schemas/audit-inclusion-proof.schema.json +17 -0
- package/schemas/audit-observation.schema.json +40 -0
- package/schemas/audit-receipt.schema.json +32 -0
- package/schemas/audit-verification-policy.schema.json +71 -0
- package/schemas/audit-verification-report.schema.json +29 -0
- package/schemas/kya-os-card.schema.json +6 -6
- package/schemas/mcp-extension-settings.json +37 -0
- package/schemas/well-known-mcpi.json +17 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { AuditCheckpointBuilder, AuditCheckpointStore } from './checkpoint.js';
|
|
2
|
+
import type { AuditJournalProvider } from './providers/journal.js';
|
|
3
|
+
import type { AuditBundleConsistencyProofV1, AuditBundleInclusionProofV1, AuditHead, AuditLedgerRef, DecimalString, SignedAuditCheckpointV1, SignedAuditEntryV1 } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Operator-facing read surface over an authoritative audit ledger.
|
|
6
|
+
*
|
|
7
|
+
* This is the counterpart to the producer-facing {@link AuditRecorderClient}:
|
|
8
|
+
* where that writes signed entries, this reads them back for browsing, replay,
|
|
9
|
+
* and independent verification. It is deliberately transport-agnostic - an HTTP
|
|
10
|
+
* or RPC boundary serialises to and from these methods - so the same contract
|
|
11
|
+
* serves the in-process reference recorder, a hosted recorder, and a
|
|
12
|
+
* dashboard client.
|
|
13
|
+
*
|
|
14
|
+
* Every returned artifact is the recorder's own signed object; a caller can
|
|
15
|
+
* re-verify it with {@link AuditArtifactVerifier} without trusting this service.
|
|
16
|
+
* Checkpoint-derived proofs are optional: a service built without checkpoint
|
|
17
|
+
* access rejects those calls with {@link AUDIT_ERROR_CODES.INVALID_CONFIGURATION}
|
|
18
|
+
* rather than fabricating a result.
|
|
19
|
+
*/
|
|
20
|
+
export interface AuditReadService {
|
|
21
|
+
/** Current chain head (highest sequence + its entry digest), or null when empty. */
|
|
22
|
+
getHead(ledger: AuditLedgerRef): Promise<AuditHead | null>;
|
|
23
|
+
/**
|
|
24
|
+
* A forward, sequence-ordered page of signed entries. The page echoes the
|
|
25
|
+
* ledger {@link AuditHead} so a caller can tell whether it has reached the
|
|
26
|
+
* tip, and a `nextAfterSequence` cursor for the following page (null at end).
|
|
27
|
+
*/
|
|
28
|
+
listEntries(query: AuditReadEntriesQuery): Promise<AuditReadEntriesPage>;
|
|
29
|
+
/** The latest signed checkpoint for the ledger, or null when none exists yet. */
|
|
30
|
+
getLatestCheckpoint(ledger: AuditLedgerRef): Promise<SignedAuditCheckpointV1 | null>;
|
|
31
|
+
/**
|
|
32
|
+
* A self-contained Merkle inclusion proof binding one entry to the latest
|
|
33
|
+
* checkpoint's signed root - the artifact a client uses to prove an entry is
|
|
34
|
+
* in the ledger without downloading the whole ledger.
|
|
35
|
+
*/
|
|
36
|
+
getInclusionProof(query: AuditInclusionProofQuery): Promise<AuditBundleInclusionProofV1>;
|
|
37
|
+
/**
|
|
38
|
+
* A Merkle consistency proof that the ledger at `oldTreeSize` is an append-only
|
|
39
|
+
* prefix of the latest checkpoint - the artifact that proves history was never
|
|
40
|
+
* rewritten between two checkpoints.
|
|
41
|
+
*/
|
|
42
|
+
getConsistencyProof(query: AuditConsistencyProofQuery): Promise<AuditBundleConsistencyProofV1>;
|
|
43
|
+
}
|
|
44
|
+
export interface AuditReadEntriesQuery extends AuditLedgerRef {
|
|
45
|
+
/** Exclusive lower bound: return entries whose sequence is strictly greater. */
|
|
46
|
+
afterSequence?: DecimalString;
|
|
47
|
+
/** Maximum entries to return; clamped to [1, {@link MAX_READ_ENTRIES_LIMIT}]. */
|
|
48
|
+
limit?: number;
|
|
49
|
+
}
|
|
50
|
+
export interface AuditReadEntriesPage {
|
|
51
|
+
entries: SignedAuditEntryV1[];
|
|
52
|
+
/** The ledger head at read time, so a caller knows if more entries exist beyond this page. */
|
|
53
|
+
head: AuditHead | null;
|
|
54
|
+
/** Cursor for the next page (pass as `afterSequence`), or null when this page reached the head. */
|
|
55
|
+
nextAfterSequence: DecimalString | null;
|
|
56
|
+
}
|
|
57
|
+
export interface AuditInclusionProofQuery extends AuditLedgerRef {
|
|
58
|
+
sequence: DecimalString;
|
|
59
|
+
}
|
|
60
|
+
export interface AuditConsistencyProofQuery extends AuditLedgerRef {
|
|
61
|
+
/** Tree size of an earlier signed checkpoint to prove the latest is consistent with. */
|
|
62
|
+
oldTreeSize: DecimalString;
|
|
63
|
+
}
|
|
64
|
+
/** Checkpoint read access, injected to enable proof operations. */
|
|
65
|
+
export interface AuditCheckpointReadAccess {
|
|
66
|
+
builder: AuditCheckpointBuilder;
|
|
67
|
+
store: AuditCheckpointStore;
|
|
68
|
+
}
|
|
69
|
+
export interface AuditReadServiceDeps {
|
|
70
|
+
journal: AuditJournalProvider;
|
|
71
|
+
/** Enables `getLatestCheckpoint`/`getInclusionProof`/`getConsistencyProof`. */
|
|
72
|
+
checkpoints?: AuditCheckpointReadAccess;
|
|
73
|
+
}
|
|
74
|
+
export declare const DEFAULT_READ_ENTRIES_LIMIT = 100;
|
|
75
|
+
export declare const MAX_READ_ENTRIES_LIMIT = 1000;
|
|
76
|
+
/**
|
|
77
|
+
* Reference read service backed by an {@link AuditJournalProvider} and, when
|
|
78
|
+
* proofs are needed, an {@link AuditCheckpointReadAccess}.
|
|
79
|
+
*
|
|
80
|
+
* Reads ride the journal's ordered `readRange`, so paging cost is independent of
|
|
81
|
+
* ledger size. Pagination fetches one extra entry to detect a further page
|
|
82
|
+
* without a second round trip.
|
|
83
|
+
*/
|
|
84
|
+
export declare class LocalAuditReadService implements AuditReadService {
|
|
85
|
+
private readonly journal;
|
|
86
|
+
private readonly checkpoints?;
|
|
87
|
+
constructor(deps: AuditReadServiceDeps);
|
|
88
|
+
getHead(ledger: AuditLedgerRef): Promise<AuditHead | null>;
|
|
89
|
+
listEntries(query: AuditReadEntriesQuery): Promise<AuditReadEntriesPage>;
|
|
90
|
+
getLatestCheckpoint(ledger: AuditLedgerRef): Promise<SignedAuditCheckpointV1 | null>;
|
|
91
|
+
getInclusionProof(query: AuditInclusionProofQuery): Promise<AuditBundleInclusionProofV1>;
|
|
92
|
+
getConsistencyProof(query: AuditConsistencyProofQuery): Promise<AuditBundleConsistencyProofV1>;
|
|
93
|
+
private requireCheckpoints;
|
|
94
|
+
private requireLatestCheckpoint;
|
|
95
|
+
private entryAtSequence;
|
|
96
|
+
}
|
|
97
|
+
/** One-call composition helper mirroring {@link createLocalAuditRecorder}. */
|
|
98
|
+
export declare function createLocalAuditReadService(deps: AuditReadServiceDeps): AuditReadService;
|
|
99
|
+
//# sourceMappingURL=read-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-service.d.ts","sourceRoot":"","sources":["../../src/audit/read-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EACV,6BAA6B,EAC7B,2BAA2B,EAC3B,SAAS,EACT,cAAc,EACd,aAAa,EACb,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAE3D;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEzE,iFAAiF;IACjF,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;IAErF;;;;OAIG;IACH,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAEzF;;;;OAIG;IACH,mBAAmB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAAC;CAChG;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,gFAAgF;IAChF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,8FAA8F;IAC9F,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,mGAAmG;IACnG,iBAAiB,EAAE,aAAa,GAAG,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,wBAAyB,SAAQ,cAAc;IAC9D,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,0BAA2B,SAAQ,cAAc;IAChE,wFAAwF;IACxF,WAAW,EAAE,aAAa,CAAC;CAC5B;AAED,mEAAmE;AACnE,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,sBAAsB,CAAC;IAChC,KAAK,EAAE,oBAAoB,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,+EAA+E;IAC/E,WAAW,CAAC,EAAE,yBAAyB,CAAC;CACzC;AAED,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAsB3C;;;;;;;GAOG;AACH,qBAAa,qBAAsB,YAAW,gBAAgB;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAA4B;gBAE7C,IAAI,EAAE,oBAAoB;IAKtC,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAIpD,WAAW,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAuBxE,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC;IAIpF,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAgBxF,mBAAmB,CACvB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,6BAA6B,CAAC;IA0BzC,OAAO,CAAC,kBAAkB;YAUZ,uBAAuB;YAevB,eAAe;CAqB9B;AAED,8EAA8E;AAC9E,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAExF"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { AUDIT_ERROR_CODES, AuditProtocolError } from './errors.js';
|
|
2
|
+
export const DEFAULT_READ_ENTRIES_LIMIT = 100;
|
|
3
|
+
export const MAX_READ_ENTRIES_LIMIT = 1000;
|
|
4
|
+
function clampLimit(limit) {
|
|
5
|
+
if (limit === undefined || !Number.isFinite(limit))
|
|
6
|
+
return DEFAULT_READ_ENTRIES_LIMIT;
|
|
7
|
+
return Math.max(1, Math.min(MAX_READ_ENTRIES_LIMIT, Math.trunc(limit)));
|
|
8
|
+
}
|
|
9
|
+
function ledgerOf(ref) {
|
|
10
|
+
return { ledgerId: ref.ledgerId, ledgerEpochId: ref.ledgerEpochId };
|
|
11
|
+
}
|
|
12
|
+
function requireDecimal(value, label) {
|
|
13
|
+
if (!/^(0|[1-9][0-9]*)$/.test(value)) {
|
|
14
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.CHECKPOINT_INVALID, `${label} must be a canonical non-negative decimal string`, { [label]: value });
|
|
15
|
+
}
|
|
16
|
+
return BigInt(value);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Reference read service backed by an {@link AuditJournalProvider} and, when
|
|
20
|
+
* proofs are needed, an {@link AuditCheckpointReadAccess}.
|
|
21
|
+
*
|
|
22
|
+
* Reads ride the journal's ordered `readRange`, so paging cost is independent of
|
|
23
|
+
* ledger size. Pagination fetches one extra entry to detect a further page
|
|
24
|
+
* without a second round trip.
|
|
25
|
+
*/
|
|
26
|
+
export class LocalAuditReadService {
|
|
27
|
+
journal;
|
|
28
|
+
checkpoints;
|
|
29
|
+
constructor(deps) {
|
|
30
|
+
this.journal = deps.journal;
|
|
31
|
+
this.checkpoints = deps.checkpoints;
|
|
32
|
+
}
|
|
33
|
+
getHead(ledger) {
|
|
34
|
+
return this.journal.getHead(ledgerOf(ledger));
|
|
35
|
+
}
|
|
36
|
+
async listEntries(query) {
|
|
37
|
+
const limit = clampLimit(query.limit);
|
|
38
|
+
const ledger = ledgerOf(query);
|
|
39
|
+
// Over-fetch by one: if the journal yields limit+1, a further page exists.
|
|
40
|
+
const collected = [];
|
|
41
|
+
for await (const entry of this.journal.readRange({
|
|
42
|
+
...ledger,
|
|
43
|
+
...(query.afterSequence === undefined ? {} : { afterSequence: query.afterSequence }),
|
|
44
|
+
limit: limit + 1,
|
|
45
|
+
})) {
|
|
46
|
+
collected.push(entry);
|
|
47
|
+
if (collected.length > limit)
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
const hasMore = collected.length > limit;
|
|
51
|
+
const entries = hasMore ? collected.slice(0, limit) : collected;
|
|
52
|
+
const nextAfterSequence = hasMore ? entries[entries.length - 1].core.sequence : null;
|
|
53
|
+
const head = await this.journal.getHead(ledger);
|
|
54
|
+
return { entries, head, nextAfterSequence };
|
|
55
|
+
}
|
|
56
|
+
async getLatestCheckpoint(ledger) {
|
|
57
|
+
return this.requireCheckpoints().store.getLatest(ledgerOf(ledger));
|
|
58
|
+
}
|
|
59
|
+
async getInclusionProof(query) {
|
|
60
|
+
const checkpoints = this.requireCheckpoints();
|
|
61
|
+
const ledger = ledgerOf(query);
|
|
62
|
+
const checkpoint = await this.requireLatestCheckpoint(checkpoints, ledger);
|
|
63
|
+
const entry = await this.entryAtSequence(ledger, query.sequence);
|
|
64
|
+
const proof = await checkpoints.builder.inclusionProof(ledger, query.sequence, checkpoint);
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
ledgerId: ledger.ledgerId,
|
|
67
|
+
ledgerEpochId: ledger.ledgerEpochId,
|
|
68
|
+
sequence: query.sequence,
|
|
69
|
+
entryDigest: entry.entryDigest,
|
|
70
|
+
checkpointDigest: checkpoint.checkpointDigest,
|
|
71
|
+
proof,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async getConsistencyProof(query) {
|
|
75
|
+
const checkpoints = this.requireCheckpoints();
|
|
76
|
+
const ledger = ledgerOf(query);
|
|
77
|
+
const newCheckpoint = await this.requireLatestCheckpoint(checkpoints, ledger);
|
|
78
|
+
const oldCheckpoint = await checkpoints.store.getByTreeSize(ledger, query.oldTreeSize);
|
|
79
|
+
if (oldCheckpoint === null) {
|
|
80
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.CHECKPOINT_INVALID, 'No signed checkpoint exists at the requested old tree size', { oldTreeSize: query.oldTreeSize });
|
|
81
|
+
}
|
|
82
|
+
const proof = await checkpoints.builder.consistencyProof(ledger, query.oldTreeSize, newCheckpoint);
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
ledgerId: ledger.ledgerId,
|
|
85
|
+
ledgerEpochId: ledger.ledgerEpochId,
|
|
86
|
+
oldCheckpointDigest: oldCheckpoint.checkpointDigest,
|
|
87
|
+
newCheckpointDigest: newCheckpoint.checkpointDigest,
|
|
88
|
+
proof,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
requireCheckpoints() {
|
|
92
|
+
if (this.checkpoints === undefined) {
|
|
93
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.INVALID_CONFIGURATION, 'This read service was built without checkpoint access; proofs are unavailable');
|
|
94
|
+
}
|
|
95
|
+
return this.checkpoints;
|
|
96
|
+
}
|
|
97
|
+
async requireLatestCheckpoint(checkpoints, ledger) {
|
|
98
|
+
const checkpoint = await checkpoints.store.getLatest(ledger);
|
|
99
|
+
if (checkpoint === null) {
|
|
100
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.CHECKPOINT_INVALID, 'No signed checkpoint exists for this ledger yet', { ledgerId: ledger.ledgerId });
|
|
101
|
+
}
|
|
102
|
+
return checkpoint;
|
|
103
|
+
}
|
|
104
|
+
async entryAtSequence(ledger, sequence) {
|
|
105
|
+
const value = requireDecimal(sequence, 'sequence');
|
|
106
|
+
// `readRange` is exclusive on afterSequence, so start one below the target.
|
|
107
|
+
const afterSequence = value > 1n ? String(value - 1n) : undefined;
|
|
108
|
+
for await (const entry of this.journal.readRange({
|
|
109
|
+
...ledger,
|
|
110
|
+
...(afterSequence === undefined ? {} : { afterSequence }),
|
|
111
|
+
limit: 1,
|
|
112
|
+
})) {
|
|
113
|
+
if (entry.core.sequence === sequence)
|
|
114
|
+
return entry;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.CHECKPOINT_INVALID, 'No entry exists at the requested sequence', { sequence });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** One-call composition helper mirroring {@link createLocalAuditRecorder}. */
|
|
121
|
+
export function createLocalAuditReadService(deps) {
|
|
122
|
+
return new LocalAuditReadService(deps);
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=read-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-service.js","sourceRoot":"","sources":["../../src/audit/read-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type AuditHasher, type AuditSigner } from './crypto.js';
|
|
2
|
+
import type { AuditEvidenceProvider } from './providers/evidence.js';
|
|
3
|
+
import type { AuditJournalProvider } from './providers/journal.js';
|
|
4
|
+
import type { AuditRecorderSubmission } from './providers/recorder-client.js';
|
|
5
|
+
import type { Digest, PartyRef, ProtocolBindingId, SignedAuditEntryV1 } from './types.js';
|
|
6
|
+
export interface AuditClock {
|
|
7
|
+
now(): number;
|
|
8
|
+
}
|
|
9
|
+
export interface AuthenticatedAuditSubmissionContext {
|
|
10
|
+
producerAuthority: string;
|
|
11
|
+
tenantAuthority: string;
|
|
12
|
+
/** Exact authenticated producer binding for non-public or non-DID identifiers. */
|
|
13
|
+
producerRef?: PartyRef;
|
|
14
|
+
/** Exact authenticated tenant binding for opaque or commitment-based tenant identifiers. */
|
|
15
|
+
tenantRef?: PartyRef;
|
|
16
|
+
}
|
|
17
|
+
export interface AuditSubmissionAuthorizer {
|
|
18
|
+
authorize(input: {
|
|
19
|
+
submission: AuditRecorderSubmission;
|
|
20
|
+
context: AuthenticatedAuditSubmissionContext;
|
|
21
|
+
}): Promise<boolean> | boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Authoritative epoch-transition port. A successful result means the predecessor terminal
|
|
25
|
+
* checkpoint was verified and the predecessor epoch was atomically sealed against new appends.
|
|
26
|
+
* Implementations must be idempotent for identical transition inputs.
|
|
27
|
+
*/
|
|
28
|
+
export interface AuditEpochTransitionGuard {
|
|
29
|
+
verifyAndSeal(input: {
|
|
30
|
+
ledgerId: string;
|
|
31
|
+
previousEpochId: string;
|
|
32
|
+
nextEpochId: string;
|
|
33
|
+
previousTerminalCheckpointDigest: Digest;
|
|
34
|
+
}): Promise<boolean>;
|
|
35
|
+
}
|
|
36
|
+
export interface AuditRecorderServiceConfig {
|
|
37
|
+
ledgerId: string;
|
|
38
|
+
ledgerEpochId: string;
|
|
39
|
+
tenantRef: PartyRef;
|
|
40
|
+
binding: ProtocolBindingId;
|
|
41
|
+
sourceId: string;
|
|
42
|
+
journal: AuditJournalProvider;
|
|
43
|
+
signer: AuditSigner;
|
|
44
|
+
hasher: AuditHasher;
|
|
45
|
+
clock: AuditClock;
|
|
46
|
+
evidence?: AuditEvidenceProvider;
|
|
47
|
+
authorizer?: AuditSubmissionAuthorizer;
|
|
48
|
+
previousEpochId?: string;
|
|
49
|
+
previousTerminalCheckpointDigest?: Digest;
|
|
50
|
+
epochTransitionGuard?: AuditEpochTransitionGuard;
|
|
51
|
+
maxAppendConflicts?: number;
|
|
52
|
+
}
|
|
53
|
+
export declare class AuditRecorderService {
|
|
54
|
+
private readonly config;
|
|
55
|
+
private readonly maxAppendConflicts;
|
|
56
|
+
private initialization?;
|
|
57
|
+
constructor(config: AuditRecorderServiceConfig);
|
|
58
|
+
submitAuthenticated(submission: AuditRecorderSubmission, context: AuthenticatedAuditSubmissionContext): Promise<SignedAuditEntryV1>;
|
|
59
|
+
/**
|
|
60
|
+
* Best-effort compensation for write-ahead evidence whose append failed: the
|
|
61
|
+
* ciphertext is disposed only after re-confirming that no committed entry
|
|
62
|
+
* claims this event identity, so a raced idempotent commit is never stripped
|
|
63
|
+
* of its evidence. Failures here leave orphans for operator retention tooling.
|
|
64
|
+
*/
|
|
65
|
+
private disposeUncommittedEvidence;
|
|
66
|
+
private validateSubmissionEnvelope;
|
|
67
|
+
private validateEventAuthority;
|
|
68
|
+
private persistSubmittedEvidence;
|
|
69
|
+
private ensureInitialized;
|
|
70
|
+
private validateExistingGenesis;
|
|
71
|
+
private genesisEvent;
|
|
72
|
+
private appendEvent;
|
|
73
|
+
private identifyEvent;
|
|
74
|
+
private resolveDuplicate;
|
|
75
|
+
private buildCandidate;
|
|
76
|
+
private ledgerRef;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=recorder-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder-service.d.ts","sourceRoot":"","sources":["../../src/audit/recorder-service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AASjF,OAAO,KAAK,EAAE,qBAAqB,EAA0B,MAAM,yBAAyB,CAAC;AAC7F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAS9E,OAAO,KAAK,EAIV,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,UAAU;IACzB,GAAG,IAAI,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mCAAmC;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,4FAA4F;IAC5F,SAAS,CAAC,EAAE,QAAQ,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,KAAK,EAAE;QACf,UAAU,EAAE,uBAAuB,CAAC;QACpC,OAAO,EAAE,mCAAmC,CAAC;KAC9C,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,aAAa,CAAC,KAAK,EAAE;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,gCAAgC,EAAE,MAAM,CAAC;KAC1C,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,QAAQ,CAAC;IACpB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,oBAAoB,CAAC,EAAE,yBAAyB,CAAC;IACjD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AA+BD,qBAAa,oBAAoB;IAInB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,cAAc,CAAC,CAA8B;gBAExB,MAAM,EAAE,0BAA0B;IAiBzD,mBAAmB,CACvB,UAAU,EAAE,uBAAuB,EACnC,OAAO,EAAE,mCAAmC,GAC3C,OAAO,CAAC,kBAAkB,CAAC;IAqC9B;;;;;OAKG;YACW,0BAA0B;IAwBxC,OAAO,CAAC,0BAA0B;IAelC,OAAO,CAAC,sBAAsB;YAuBhB,wBAAwB;YAuCxB,iBAAiB;IAuC/B,OAAO,CAAC,uBAAuB;IAgC/B,OAAO,CAAC,YAAY;YAiCN,WAAW;YAwEX,aAAa;IAkB3B,OAAO,CAAC,gBAAgB;YAWV,cAAc;IAuC5B,OAAO,CAAC,SAAS;CAMlB"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { canonicalizeJson, canonicalizeJsonBytes } from '../utils/canonical-json.js';
|
|
2
|
+
import { AUDIT_ERROR_CODES, AuditProtocolError } from './errors.js';
|
|
3
|
+
import { hashAuditValue } from './crypto.js';
|
|
4
|
+
import { AUDIT_DIGEST_DOMAINS, buildAuditRecorderReceiptCore, collectAuditEvidenceRefs, digestAuditEntry, digestAuditEvidenceManifest, digestAuditEvent, } from './integrity.js';
|
|
5
|
+
import { AUDIT_ENTRY_SCHEMA_ID, AUDIT_EVENT_SCHEMA_ID, AUDIT_INTEGRITY_SUITE, parseAuditEntryCore, parseAuditProducerEvent, parseSignedAuditEntry, } from './schemas.js';
|
|
6
|
+
function deepFreeze(value) {
|
|
7
|
+
if (typeof value !== 'object' || value === null || Object.isFrozen(value))
|
|
8
|
+
return value;
|
|
9
|
+
for (const child of Object.values(value))
|
|
10
|
+
deepFreeze(child);
|
|
11
|
+
return Object.freeze(value);
|
|
12
|
+
}
|
|
13
|
+
function sameHead(left, right) {
|
|
14
|
+
if (left === null || right === null)
|
|
15
|
+
return left === right;
|
|
16
|
+
return left.entryDigest === right.entryDigest && left.sequence === right.sequence;
|
|
17
|
+
}
|
|
18
|
+
function sameSigner(left, right) {
|
|
19
|
+
return left.did === right.did && left.kid === right.kid && left.alg === right.alg;
|
|
20
|
+
}
|
|
21
|
+
function sameParty(left, right) {
|
|
22
|
+
return canonicalizeJson(left) === canonicalizeJson(right);
|
|
23
|
+
}
|
|
24
|
+
function authorityMatchesParty(authority, boundRef, claimedRef) {
|
|
25
|
+
if (boundRef !== undefined)
|
|
26
|
+
return sameParty(boundRef, claimedRef);
|
|
27
|
+
return (claimedRef.kind === 'public_did' || claimedRef.kind === 'pairwise_did') &&
|
|
28
|
+
claimedRef.did === authority;
|
|
29
|
+
}
|
|
30
|
+
export class AuditRecorderService {
|
|
31
|
+
config;
|
|
32
|
+
maxAppendConflicts;
|
|
33
|
+
initialization;
|
|
34
|
+
constructor(config) {
|
|
35
|
+
this.config = config;
|
|
36
|
+
if ((config.previousEpochId === undefined) !==
|
|
37
|
+
(config.previousTerminalCheckpointDigest === undefined)) {
|
|
38
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.INVALID_CONFIGURATION, 'Previous epoch ID and terminal checkpoint digest must be supplied together');
|
|
39
|
+
}
|
|
40
|
+
if (config.previousEpochId !== undefined && config.epochTransitionGuard === undefined) {
|
|
41
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.INVALID_CONFIGURATION, 'Epoch transitions require an authoritative verify-and-seal guard');
|
|
42
|
+
}
|
|
43
|
+
this.maxAppendConflicts = config.maxAppendConflicts ?? 256;
|
|
44
|
+
}
|
|
45
|
+
async submitAuthenticated(submission, context) {
|
|
46
|
+
this.validateSubmissionEnvelope(submission, context);
|
|
47
|
+
const producerEvent = parseAuditProducerEvent(submission.producerEvent);
|
|
48
|
+
this.validateEventAuthority(producerEvent, context);
|
|
49
|
+
if (this.config.authorizer !== undefined &&
|
|
50
|
+
!(await this.config.authorizer.authorize({ submission, context }))) {
|
|
51
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.UNAUTHORIZED_SUBMISSION, 'Audit producer is not authorized for this ledger');
|
|
52
|
+
}
|
|
53
|
+
const identity = await this.identifyEvent(producerEvent, context);
|
|
54
|
+
const duplicate = await this.config.journal.getByIdempotencyKey(this.config.ledgerId, identity.idempotencyKey);
|
|
55
|
+
if (duplicate !== null)
|
|
56
|
+
return this.resolveDuplicate(duplicate, identity.eventDigest);
|
|
57
|
+
// Only a new append must target the active epoch. Logical-ledger idempotency
|
|
58
|
+
// is resolved first so a redelivered submission pinned to a retained earlier
|
|
59
|
+
// epoch returns its original entry instead of wedging on EPOCH_MISMATCH.
|
|
60
|
+
if (submission.expectedLedgerEpochId !== undefined &&
|
|
61
|
+
submission.expectedLedgerEpochId !== this.config.ledgerEpochId) {
|
|
62
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.EPOCH_MISMATCH, 'Wrong audit ledger epoch');
|
|
63
|
+
}
|
|
64
|
+
await this.ensureInitialized();
|
|
65
|
+
await this.persistSubmittedEvidence(submission.encryptedEvidence, producerEvent);
|
|
66
|
+
try {
|
|
67
|
+
return await this.appendEvent(producerEvent, context, false, identity);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
await this.disposeUncommittedEvidence(submission.encryptedEvidence, identity);
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Best-effort compensation for write-ahead evidence whose append failed: the
|
|
76
|
+
* ciphertext is disposed only after re-confirming that no committed entry
|
|
77
|
+
* claims this event identity, so a raced idempotent commit is never stripped
|
|
78
|
+
* of its evidence. Failures here leave orphans for operator retention tooling.
|
|
79
|
+
*/
|
|
80
|
+
async disposeUncommittedEvidence(evidence, identity) {
|
|
81
|
+
if (evidence.length === 0 || this.config.evidence === undefined)
|
|
82
|
+
return;
|
|
83
|
+
try {
|
|
84
|
+
const committed = await this.config.journal.getByIdempotencyKey(this.config.ledgerId, identity.idempotencyKey);
|
|
85
|
+
if (committed !== null)
|
|
86
|
+
return;
|
|
87
|
+
for (const item of evidence) {
|
|
88
|
+
await this.config.evidence.applyRetention({
|
|
89
|
+
kind: 'dispose',
|
|
90
|
+
ref: item.ref,
|
|
91
|
+
authorizedBy: 'audit-recorder',
|
|
92
|
+
reason: 'append_failed_uncommitted',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Retaining an orphan is preferable to disposing racing committed evidence.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
validateSubmissionEnvelope(submission, context) {
|
|
101
|
+
if (!context.producerAuthority || !context.tenantAuthority) {
|
|
102
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.UNAUTHORIZED_SUBMISSION, 'Authenticated producer and tenant authority are required');
|
|
103
|
+
}
|
|
104
|
+
if (submission.ledgerId !== this.config.ledgerId) {
|
|
105
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.LEDGER_MISMATCH, 'Wrong audit ledger');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
validateEventAuthority(event, context) {
|
|
109
|
+
if (!authorityMatchesParty(context.producerAuthority, context.producerRef, event.source.producer)) {
|
|
110
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.UNAUTHORIZED_SUBMISSION, 'Event producer does not match the authenticated producer authority');
|
|
111
|
+
}
|
|
112
|
+
if (!sameParty(event.tenantRef, this.config.tenantRef) ||
|
|
113
|
+
!authorityMatchesParty(context.tenantAuthority, context.tenantRef, event.tenantRef)) {
|
|
114
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.UNAUTHORIZED_SUBMISSION, 'Event tenant does not match the authenticated tenant authority');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async persistSubmittedEvidence(evidence, event) {
|
|
118
|
+
if (evidence.length === 0)
|
|
119
|
+
return;
|
|
120
|
+
if (this.config.evidence === undefined) {
|
|
121
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.EVIDENCE_FAILURE, 'Encrypted evidence was submitted but no evidence provider is configured');
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const refs = new Map(collectAuditEvidenceRefs(event).map((ref) => [ref.objectId, ref]));
|
|
125
|
+
const submitted = new Set();
|
|
126
|
+
for (const item of evidence) {
|
|
127
|
+
const referenced = refs.get(item.ref.objectId);
|
|
128
|
+
if (submitted.has(item.ref.objectId) || referenced === undefined ||
|
|
129
|
+
canonicalizeJson(referenced) !== canonicalizeJson(item.ref)) {
|
|
130
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.EVIDENCE_FAILURE, 'Submitted evidence does not exactly match a unique frozen event reference', { objectId: item.ref.objectId });
|
|
131
|
+
}
|
|
132
|
+
submitted.add(item.ref.objectId);
|
|
133
|
+
await this.config.evidence.putIfAbsent(item);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof AuditProtocolError &&
|
|
138
|
+
error.code === AUDIT_ERROR_CODES.EVIDENCE_FAILURE)
|
|
139
|
+
throw error;
|
|
140
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.EVIDENCE_FAILURE, 'Required evidence could not be persisted', undefined, { cause: error });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async ensureInitialized() {
|
|
144
|
+
const ledger = this.ledgerRef();
|
|
145
|
+
const existingHead = await this.config.journal.getHead(ledger);
|
|
146
|
+
if (existingHead !== null) {
|
|
147
|
+
const first = this.config.journal.readRange({ ...ledger, limit: 1 });
|
|
148
|
+
for await (const entry of first)
|
|
149
|
+
return this.validateExistingGenesis(entry);
|
|
150
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.JOURNAL_FAILURE, 'Ledger head exists without genesis');
|
|
151
|
+
}
|
|
152
|
+
if (this.config.previousEpochId !== undefined) {
|
|
153
|
+
const verifiedAndSealed = await this.config.epochTransitionGuard.verifyAndSeal({
|
|
154
|
+
ledgerId: this.config.ledgerId,
|
|
155
|
+
previousEpochId: this.config.previousEpochId,
|
|
156
|
+
nextEpochId: this.config.ledgerEpochId,
|
|
157
|
+
previousTerminalCheckpointDigest: this.config.previousTerminalCheckpointDigest,
|
|
158
|
+
});
|
|
159
|
+
if (!verifiedAndSealed) {
|
|
160
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.INVALID_CONFIGURATION, 'Predecessor checkpoint could not be verified and sealed');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
this.initialization ??= this.appendEvent(this.genesisEvent(), {
|
|
164
|
+
producerAuthority: this.config.signer.ref.did,
|
|
165
|
+
tenantAuthority: 'audit-recorder',
|
|
166
|
+
producerRef: { kind: 'public_did', did: this.config.signer.ref.did },
|
|
167
|
+
tenantRef: this.config.tenantRef,
|
|
168
|
+
}, true).finally(() => {
|
|
169
|
+
this.initialization = undefined;
|
|
170
|
+
});
|
|
171
|
+
return this.initialization;
|
|
172
|
+
}
|
|
173
|
+
validateExistingGenesis(candidate) {
|
|
174
|
+
try {
|
|
175
|
+
const entry = parseSignedAuditEntry(candidate);
|
|
176
|
+
const details = entry.core.event.details;
|
|
177
|
+
const valid = entry.core.sequence === '0' &&
|
|
178
|
+
entry.core.previousEntryDigest === null &&
|
|
179
|
+
entry.core.ledgerId === this.config.ledgerId &&
|
|
180
|
+
entry.core.ledgerEpochId === this.config.ledgerEpochId &&
|
|
181
|
+
sameSigner(entry.core.recorder, this.config.signer.ref) &&
|
|
182
|
+
entry.core.event.eventType === 'ledger.epoch.started' &&
|
|
183
|
+
entry.core.event.binding === this.config.binding &&
|
|
184
|
+
entry.core.event.source.sourceId === this.config.sourceId &&
|
|
185
|
+
entry.core.event.source.producer.kind === 'public_did' &&
|
|
186
|
+
entry.core.event.source.producer.did === this.config.signer.ref.did &&
|
|
187
|
+
sameParty(entry.core.event.tenantRef, this.config.tenantRef) &&
|
|
188
|
+
details.family === 'ledger' &&
|
|
189
|
+
details.phase === 'epoch_started' &&
|
|
190
|
+
details.previousEpochId === this.config.previousEpochId &&
|
|
191
|
+
details.previousTerminalCheckpointDigest ===
|
|
192
|
+
this.config.previousTerminalCheckpointDigest;
|
|
193
|
+
if (!valid)
|
|
194
|
+
throw new Error('Genesis does not match recorder epoch configuration');
|
|
195
|
+
return entry;
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.JOURNAL_FAILURE, 'Existing ledger genesis does not match the authoritative recorder configuration', undefined, { cause: error });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
genesisEvent() {
|
|
202
|
+
return parseAuditProducerEvent({
|
|
203
|
+
schema: AUDIT_EVENT_SCHEMA_ID,
|
|
204
|
+
eventId: `genesis:${this.config.ledgerId}:${this.config.ledgerEpochId}`,
|
|
205
|
+
eventType: 'ledger.epoch.started',
|
|
206
|
+
eventVersion: '1.0.0',
|
|
207
|
+
binding: this.config.binding,
|
|
208
|
+
occurredAt: this.config.clock.now(),
|
|
209
|
+
tenantRef: this.config.tenantRef,
|
|
210
|
+
source: {
|
|
211
|
+
producer: { kind: 'public_did', did: this.config.signer.ref.did },
|
|
212
|
+
sourceId: this.config.sourceId,
|
|
213
|
+
sourceSequence: '0',
|
|
214
|
+
},
|
|
215
|
+
resource: { kind: 'public_did', did: this.config.signer.ref.did },
|
|
216
|
+
action: { category: 'ledger.epoch' },
|
|
217
|
+
outcome: 'succeeded',
|
|
218
|
+
evidence: [],
|
|
219
|
+
details: {
|
|
220
|
+
family: 'ledger',
|
|
221
|
+
phase: 'epoch_started',
|
|
222
|
+
...(this.config.previousEpochId
|
|
223
|
+
? {
|
|
224
|
+
previousEpochId: this.config.previousEpochId,
|
|
225
|
+
previousTerminalCheckpointDigest: this.config.previousTerminalCheckpointDigest,
|
|
226
|
+
}
|
|
227
|
+
: {}),
|
|
228
|
+
},
|
|
229
|
+
privacy: { classification: 'internal', retentionClass: 'integrity-ledger' },
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async appendEvent(event, context, genesis = false, knownIdentity) {
|
|
233
|
+
const { eventDigest, idempotencyKey } = knownIdentity ??
|
|
234
|
+
await this.identifyEvent(event, context);
|
|
235
|
+
const duplicate = await this.config.journal.getByIdempotencyKey(this.config.ledgerId, idempotencyKey);
|
|
236
|
+
if (duplicate !== null) {
|
|
237
|
+
// A raced replica genesis differs only in recorder-authored timing, so it
|
|
238
|
+
// resolves through epoch-configuration validation, not content identity.
|
|
239
|
+
return genesis
|
|
240
|
+
? this.validateExistingGenesis(duplicate)
|
|
241
|
+
: this.resolveDuplicate(duplicate, eventDigest);
|
|
242
|
+
}
|
|
243
|
+
const ledger = this.ledgerRef();
|
|
244
|
+
for (let attempt = 0; attempt <= this.maxAppendConflicts; attempt += 1) {
|
|
245
|
+
const head = await this.config.journal.getHead(ledger);
|
|
246
|
+
if (genesis && head !== null) {
|
|
247
|
+
const raced = await this.config.journal.getByIdempotencyKey(this.config.ledgerId, idempotencyKey);
|
|
248
|
+
if (raced !== null)
|
|
249
|
+
return this.validateExistingGenesis(raced);
|
|
250
|
+
}
|
|
251
|
+
const candidate = await this.buildCandidate(event, eventDigest, head, genesis);
|
|
252
|
+
let result;
|
|
253
|
+
try {
|
|
254
|
+
result = await this.config.journal.compareAndAppend({
|
|
255
|
+
ledger,
|
|
256
|
+
expectedHead: head,
|
|
257
|
+
entry: candidate,
|
|
258
|
+
idempotencyKey,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.JOURNAL_FAILURE, 'Audit journal append failed', undefined, { cause: error });
|
|
263
|
+
}
|
|
264
|
+
if (result.kind === 'appended' || result.kind === 'duplicate') {
|
|
265
|
+
return result.kind === 'duplicate'
|
|
266
|
+
? this.resolveDuplicate(result.entry, eventDigest)
|
|
267
|
+
: result.entry;
|
|
268
|
+
}
|
|
269
|
+
if (result.kind === 'idempotency_conflict') {
|
|
270
|
+
return this.resolveDuplicate(result.existing, eventDigest);
|
|
271
|
+
}
|
|
272
|
+
if (sameHead(head, result.actualHead)) {
|
|
273
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.JOURNAL_FAILURE, 'Audit journal reported a head conflict without advancing its head');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.APPEND_CONFLICT_EXHAUSTED, 'Audit append conflict retry budget exhausted');
|
|
277
|
+
}
|
|
278
|
+
async identifyEvent(event, context) {
|
|
279
|
+
const eventDigest = await digestAuditEvent(this.config.hasher, event);
|
|
280
|
+
const idempotencyKey = await hashAuditValue(this.config.hasher, AUDIT_DIGEST_DOMAINS.idempotency, {
|
|
281
|
+
ledgerId: this.config.ledgerId,
|
|
282
|
+
producerAuthority: context.producerAuthority,
|
|
283
|
+
sourceId: event.source.sourceId,
|
|
284
|
+
eventId: event.eventId,
|
|
285
|
+
});
|
|
286
|
+
return { eventDigest, idempotencyKey };
|
|
287
|
+
}
|
|
288
|
+
resolveDuplicate(existing, digest) {
|
|
289
|
+
if (existing.eventDigest !== digest) {
|
|
290
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.EVENT_ID_CONFLICT, 'Authenticated producer event identity was reused with different content', { eventId: existing.core.event.eventId });
|
|
291
|
+
}
|
|
292
|
+
return existing;
|
|
293
|
+
}
|
|
294
|
+
async buildCandidate(event, eventDigest, head, genesis) {
|
|
295
|
+
const sequence = head === null ? '0' : (BigInt(head.sequence) + 1n).toString();
|
|
296
|
+
if (genesis !== (sequence === '0')) {
|
|
297
|
+
throw new AuditProtocolError(AUDIT_ERROR_CODES.JOURNAL_FAILURE, genesis ? 'Genesis must be the first epoch entry' : 'Producer event cannot replace genesis');
|
|
298
|
+
}
|
|
299
|
+
const recordedAt = this.config.clock.now();
|
|
300
|
+
const evidenceManifestDigest = await digestAuditEvidenceManifest(this.config.hasher, event);
|
|
301
|
+
const core = parseAuditEntryCore({
|
|
302
|
+
schema: AUDIT_ENTRY_SCHEMA_ID,
|
|
303
|
+
ledgerId: this.config.ledgerId,
|
|
304
|
+
ledgerEpochId: this.config.ledgerEpochId,
|
|
305
|
+
sequence,
|
|
306
|
+
previousEntryDigest: head?.entryDigest ?? null,
|
|
307
|
+
recordedAt,
|
|
308
|
+
recorder: this.config.signer.ref,
|
|
309
|
+
eventDigest,
|
|
310
|
+
event,
|
|
311
|
+
evidenceManifestDigest,
|
|
312
|
+
integritySuite: AUDIT_INTEGRITY_SUITE,
|
|
313
|
+
});
|
|
314
|
+
const entryDigest = await digestAuditEntry(this.config.hasher, core);
|
|
315
|
+
const receiptCore = buildAuditRecorderReceiptCore(core, entryDigest);
|
|
316
|
+
const jws = await this.config.signer.sign(canonicalizeJsonBytes(receiptCore));
|
|
317
|
+
return deepFreeze({
|
|
318
|
+
core,
|
|
319
|
+
eventDigest,
|
|
320
|
+
entryDigest,
|
|
321
|
+
recorderReceipt: { core: receiptCore, jws },
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
ledgerRef() {
|
|
325
|
+
return {
|
|
326
|
+
ledgerId: this.config.ledgerId,
|
|
327
|
+
ledgerEpochId: this.config.ledgerEpochId,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=recorder-service.js.map
|