life-as-api 0.2.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/LICENSE +21 -0
- package/README.md +377 -0
- package/dist/agent/finance-agent.d.ts +44 -0
- package/dist/agent/finance-agent.js +75 -0
- package/dist/agent/finance-agent.js.map +1 -0
- package/dist/agent/legal-agent.d.ts +44 -0
- package/dist/agent/legal-agent.js +76 -0
- package/dist/agent/legal-agent.js.map +1 -0
- package/dist/agent/medical-agent.d.ts +44 -0
- package/dist/agent/medical-agent.js +75 -0
- package/dist/agent/medical-agent.js.map +1 -0
- package/dist/agent/trusted-agent-registry.d.ts +53 -0
- package/dist/agent/trusted-agent-registry.js +57 -0
- package/dist/agent/trusted-agent-registry.js.map +1 -0
- package/dist/audit/audit-chain-verifier.d.ts +10 -0
- package/dist/audit/audit-chain-verifier.js +67 -0
- package/dist/audit/audit-chain-verifier.js.map +1 -0
- package/dist/audit/audit-log.d.ts +48 -0
- package/dist/audit/audit-log.js +74 -0
- package/dist/audit/audit-log.js.map +1 -0
- package/dist/audit/sqlite-audit-log.d.ts +29 -0
- package/dist/audit/sqlite-audit-log.js +142 -0
- package/dist/audit/sqlite-audit-log.js.map +1 -0
- package/dist/auth/access-context-issuer.d.ts +18 -0
- package/dist/auth/access-context-issuer.js +88 -0
- package/dist/auth/access-context-issuer.js.map +1 -0
- package/dist/auth/assert-authorized-for-subject.d.ts +2 -0
- package/dist/auth/assert-authorized-for-subject.js +20 -0
- package/dist/auth/assert-authorized-for-subject.js.map +1 -0
- package/dist/auth/backend-context-minting-boundary.d.ts +23 -0
- package/dist/auth/backend-context-minting-boundary.js +281 -0
- package/dist/auth/backend-context-minting-boundary.js.map +1 -0
- package/dist/auth/default-authorization-policy.d.ts +7 -0
- package/dist/auth/default-authorization-policy.js +64 -0
- package/dist/auth/default-authorization-policy.js.map +1 -0
- package/dist/auth/fake-auth-adapter.d.ts +7 -0
- package/dist/auth/fake-auth-adapter.js +27 -0
- package/dist/auth/fake-auth-adapter.js.map +1 -0
- package/dist/auth/http-client.d.ts +12 -0
- package/dist/auth/http-client.js +7 -0
- package/dist/auth/http-client.js.map +1 -0
- package/dist/auth/in-memory-grant-resolver.d.ts +21 -0
- package/dist/auth/in-memory-grant-resolver.js +73 -0
- package/dist/auth/in-memory-grant-resolver.js.map +1 -0
- package/dist/auth/in-memory-tenant-boundary.d.ts +16 -0
- package/dist/auth/in-memory-tenant-boundary.js +37 -0
- package/dist/auth/in-memory-tenant-boundary.js.map +1 -0
- package/dist/auth/jwks-key-ring-auth-adapter.d.ts +38 -0
- package/dist/auth/jwks-key-ring-auth-adapter.js +123 -0
- package/dist/auth/jwks-key-ring-auth-adapter.js.map +1 -0
- package/dist/auth/jwks-key-ring.d.ts +25 -0
- package/dist/auth/jwks-key-ring.js +111 -0
- package/dist/auth/jwks-key-ring.js.map +1 -0
- package/dist/auth/jwt-auth-adapter.d.ts +15 -0
- package/dist/auth/jwt-auth-adapter.js +59 -0
- package/dist/auth/jwt-auth-adapter.js.map +1 -0
- package/dist/auth/jwt-core.d.ts +32 -0
- package/dist/auth/jwt-core.js +226 -0
- package/dist/auth/jwt-core.js.map +1 -0
- package/dist/auth/jwt-key-ring-auth-adapter.d.ts +16 -0
- package/dist/auth/jwt-key-ring-auth-adapter.js +58 -0
- package/dist/auth/jwt-key-ring-auth-adapter.js.map +1 -0
- package/dist/auth/oidc-discovery.d.ts +15 -0
- package/dist/auth/oidc-discovery.js +46 -0
- package/dist/auth/oidc-discovery.js.map +1 -0
- package/dist/auth/production-scoped-life-api.d.ts +58 -0
- package/dist/auth/production-scoped-life-api.js +802 -0
- package/dist/auth/production-scoped-life-api.js.map +1 -0
- package/dist/auth/proposal-scope-inspector.d.ts +3 -0
- package/dist/auth/proposal-scope-inspector.js +42 -0
- package/dist/auth/proposal-scope-inspector.js.map +1 -0
- package/dist/auth/proposal-scoped-life-api.d.ts +21 -0
- package/dist/auth/proposal-scoped-life-api.js +168 -0
- package/dist/auth/proposal-scoped-life-api.js.map +1 -0
- package/dist/auth/rate-limited-context-minting-boundary.d.ts +27 -0
- package/dist/auth/rate-limited-context-minting-boundary.js +77 -0
- package/dist/auth/rate-limited-context-minting-boundary.js.map +1 -0
- package/dist/auth/revocation-aware-auth-adapter.d.ts +21 -0
- package/dist/auth/revocation-aware-auth-adapter.js +88 -0
- package/dist/auth/revocation-aware-auth-adapter.js.map +1 -0
- package/dist/auth/sqlite-grant-resolver.d.ts +8 -0
- package/dist/auth/sqlite-grant-resolver.js +78 -0
- package/dist/auth/sqlite-grant-resolver.js.map +1 -0
- package/dist/auth/sqlite-tenant-boundary.d.ts +7 -0
- package/dist/auth/sqlite-tenant-boundary.js +41 -0
- package/dist/auth/sqlite-tenant-boundary.js.map +1 -0
- package/dist/auth/static-jwt-key-ring.d.ts +13 -0
- package/dist/auth/static-jwt-key-ring.js +73 -0
- package/dist/auth/static-jwt-key-ring.js.map +1 -0
- package/dist/auth/subject-scoped-life-api.d.ts +14 -0
- package/dist/auth/subject-scoped-life-api.js +51 -0
- package/dist/auth/subject-scoped-life-api.js.map +1 -0
- package/dist/auth/test-context-minting-boundary.d.ts +13 -0
- package/dist/auth/test-context-minting-boundary.js +74 -0
- package/dist/auth/test-context-minting-boundary.js.map +1 -0
- package/dist/auth/types.d.ts +77 -0
- package/dist/auth/types.js +16 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/compliance/subject-data-report.d.ts +43 -0
- package/dist/compliance/subject-data-report.js +155 -0
- package/dist/compliance/subject-data-report.js.map +1 -0
- package/dist/consent/consent-guard.d.ts +4 -0
- package/dist/consent/consent-guard.js +37 -0
- package/dist/consent/consent-guard.js.map +1 -0
- package/dist/consent/grant-registry.d.ts +7 -0
- package/dist/consent/grant-registry.js +13 -0
- package/dist/consent/grant-registry.js.map +1 -0
- package/dist/consent/intersect-selector.d.ts +3 -0
- package/dist/consent/intersect-selector.js +66 -0
- package/dist/consent/intersect-selector.js.map +1 -0
- package/dist/contract/diagnostics.d.ts +32 -0
- package/dist/contract/diagnostics.js +112 -0
- package/dist/contract/diagnostics.js.map +1 -0
- package/dist/contract/project-contracts.d.ts +5 -0
- package/dist/contract/project-contracts.js +159 -0
- package/dist/contract/project-contracts.js.map +1 -0
- package/dist/contract/types.d.ts +32 -0
- package/dist/contract/types.js +5 -0
- package/dist/contract/types.js.map +1 -0
- package/dist/crypto/aes-gcm.d.ts +11 -0
- package/dist/crypto/aes-gcm.js +78 -0
- package/dist/crypto/aes-gcm.js.map +1 -0
- package/dist/crypto/claim-serializer.d.ts +26 -0
- package/dist/crypto/claim-serializer.js +59 -0
- package/dist/crypto/claim-serializer.js.map +1 -0
- package/dist/crypto/sha256.d.ts +12 -0
- package/dist/crypto/sha256.js +40 -0
- package/dist/crypto/sha256.js.map +1 -0
- package/dist/crypto/static-key.d.ts +11 -0
- package/dist/crypto/static-key.js +60 -0
- package/dist/crypto/static-key.js.map +1 -0
- package/dist/crypto/types.d.ts +20 -0
- package/dist/crypto/types.js +8 -0
- package/dist/crypto/types.js.map +1 -0
- package/dist/datasource/trusted-data-source-registry.d.ts +51 -0
- package/dist/datasource/trusted-data-source-registry.js +57 -0
- package/dist/datasource/trusted-data-source-registry.js.map +1 -0
- package/dist/erasure/tombstone.d.ts +13 -0
- package/dist/erasure/tombstone.js +20 -0
- package/dist/erasure/tombstone.js.map +1 -0
- package/dist/evidence/evidence-backed-claim-creator.d.ts +48 -0
- package/dist/evidence/evidence-backed-claim-creator.js +107 -0
- package/dist/evidence/evidence-backed-claim-creator.js.map +1 -0
- package/dist/evidence/in-memory-raw-document-store.d.ts +26 -0
- package/dist/evidence/in-memory-raw-document-store.js +84 -0
- package/dist/evidence/in-memory-raw-document-store.js.map +1 -0
- package/dist/evidence/raw-document-store.d.ts +12 -0
- package/dist/evidence/raw-document-store.js +8 -0
- package/dist/evidence/raw-document-store.js.map +1 -0
- package/dist/evidence/source-evidence-validator.d.ts +4 -0
- package/dist/evidence/source-evidence-validator.js +14 -0
- package/dist/evidence/source-evidence-validator.js.map +1 -0
- package/dist/evidence/sqlite-raw-document-store.d.ts +19 -0
- package/dist/evidence/sqlite-raw-document-store.js +122 -0
- package/dist/evidence/sqlite-raw-document-store.js.map +1 -0
- package/dist/guardian/guardian-registry.d.ts +96 -0
- package/dist/guardian/guardian-registry.js +95 -0
- package/dist/guardian/guardian-registry.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/observability/health.d.ts +12 -0
- package/dist/observability/health.js +16 -0
- package/dist/observability/health.js.map +1 -0
- package/dist/observability/logger.d.ts +21 -0
- package/dist/observability/logger.js +40 -0
- package/dist/observability/logger.js.map +1 -0
- package/dist/observability/metrics.d.ts +15 -0
- package/dist/observability/metrics.js +52 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/persistence/backup.d.ts +24 -0
- package/dist/persistence/backup.js +251 -0
- package/dist/persistence/backup.js.map +1 -0
- package/dist/persistence/decryptability-scan.d.ts +10 -0
- package/dist/persistence/decryptability-scan.js +144 -0
- package/dist/persistence/decryptability-scan.js.map +1 -0
- package/dist/persistence/in-memory-unit-of-work.d.ts +11 -0
- package/dist/persistence/in-memory-unit-of-work.js +32 -0
- package/dist/persistence/in-memory-unit-of-work.js.map +1 -0
- package/dist/persistence/key-availability.d.ts +9 -0
- package/dist/persistence/key-availability.js +125 -0
- package/dist/persistence/key-availability.js.map +1 -0
- package/dist/persistence/sqlite/migrations.d.ts +9 -0
- package/dist/persistence/sqlite/migrations.js +535 -0
- package/dist/persistence/sqlite/migrations.js.map +1 -0
- package/dist/persistence/sqlite/schema.d.ts +10 -0
- package/dist/persistence/sqlite/schema.js +101 -0
- package/dist/persistence/sqlite/schema.js.map +1 -0
- package/dist/persistence/sqlite/sqlite-fact-store.d.ts +24 -0
- package/dist/persistence/sqlite/sqlite-fact-store.js +207 -0
- package/dist/persistence/sqlite/sqlite-fact-store.js.map +1 -0
- package/dist/persistence/sqlite/sqlite-source-registry.d.ts +17 -0
- package/dist/persistence/sqlite/sqlite-source-registry.js +132 -0
- package/dist/persistence/sqlite/sqlite-source-registry.js.map +1 -0
- package/dist/persistence/sqlite/sqlite-unit-of-work.d.ts +8 -0
- package/dist/persistence/sqlite/sqlite-unit-of-work.js +41 -0
- package/dist/persistence/sqlite/sqlite-unit-of-work.js.map +1 -0
- package/dist/persistence/sqlite/types.d.ts +24 -0
- package/dist/persistence/sqlite/types.js +28 -0
- package/dist/persistence/sqlite/types.js.map +1 -0
- package/dist/persistence/unit-of-work.d.ts +6 -0
- package/dist/persistence/unit-of-work.js +15 -0
- package/dist/persistence/unit-of-work.js.map +1 -0
- package/dist/proposal/accepted-claim-appender.d.ts +7 -0
- package/dist/proposal/accepted-claim-appender.js +16 -0
- package/dist/proposal/accepted-claim-appender.js.map +1 -0
- package/dist/proposal/in-memory-proposal-queue.d.ts +21 -0
- package/dist/proposal/in-memory-proposal-queue.js +218 -0
- package/dist/proposal/in-memory-proposal-queue.js.map +1 -0
- package/dist/proposal/policy-aware-proposal-queue.d.ts +23 -0
- package/dist/proposal/policy-aware-proposal-queue.js +162 -0
- package/dist/proposal/policy-aware-proposal-queue.js.map +1 -0
- package/dist/proposal/proposal-policy.d.ts +37 -0
- package/dist/proposal/proposal-policy.js +72 -0
- package/dist/proposal/proposal-policy.js.map +1 -0
- package/dist/proposal/proposal-queue.d.ts +13 -0
- package/dist/proposal/proposal-queue.js +15 -0
- package/dist/proposal/proposal-queue.js.map +1 -0
- package/dist/proposal/source-aware-claim-appender.d.ts +13 -0
- package/dist/proposal/source-aware-claim-appender.js +47 -0
- package/dist/proposal/source-aware-claim-appender.js.map +1 -0
- package/dist/proposal/sqlite-proposal-queue.d.ts +26 -0
- package/dist/proposal/sqlite-proposal-queue.js +282 -0
- package/dist/proposal/sqlite-proposal-queue.js.map +1 -0
- package/dist/resolution/resolve-claims.d.ts +4 -0
- package/dist/resolution/resolve-claims.js +101 -0
- package/dist/resolution/resolve-claims.js.map +1 -0
- package/dist/resolution/trust-profile.d.ts +12 -0
- package/dist/resolution/trust-profile.js +23 -0
- package/dist/resolution/trust-profile.js.map +1 -0
- package/dist/resolution/types.d.ts +15 -0
- package/dist/resolution/types.js +8 -0
- package/dist/resolution/types.js.map +1 -0
- package/dist/sdk/create-backend-integrated-life-api-for-test.d.ts +23 -0
- package/dist/sdk/create-backend-integrated-life-api-for-test.js +240 -0
- package/dist/sdk/create-backend-integrated-life-api-for-test.js.map +1 -0
- package/dist/sdk/create-life-api.d.ts +55 -0
- package/dist/sdk/create-life-api.js +251 -0
- package/dist/sdk/create-life-api.js.map +1 -0
- package/dist/sdk/create-sqlite-life-api.d.ts +33 -0
- package/dist/sdk/create-sqlite-life-api.js +654 -0
- package/dist/sdk/create-sqlite-life-api.js.map +1 -0
- package/dist/sdk/life-api.d.ts +27 -0
- package/dist/sdk/life-api.js +17 -0
- package/dist/sdk/life-api.js.map +1 -0
- package/dist/sdk/validation.d.ts +8 -0
- package/dist/sdk/validation.js +216 -0
- package/dist/sdk/validation.js.map +1 -0
- package/dist/source/in-memory-source-registry.d.ts +15 -0
- package/dist/source/in-memory-source-registry.js +104 -0
- package/dist/source/in-memory-source-registry.js.map +1 -0
- package/dist/source/source-aware-fact-store.d.ts +19 -0
- package/dist/source/source-aware-fact-store.js +40 -0
- package/dist/source/source-aware-fact-store.js.map +1 -0
- package/dist/source/source-kind-predicate-policy.d.ts +4 -0
- package/dist/source/source-kind-predicate-policy.js +62 -0
- package/dist/source/source-kind-predicate-policy.js.map +1 -0
- package/dist/source/source-registry.d.ts +11 -0
- package/dist/source/source-registry.js +12 -0
- package/dist/source/source-registry.js.map +1 -0
- package/dist/store/in-memory-fact-store.d.ts +25 -0
- package/dist/store/in-memory-fact-store.js +192 -0
- package/dist/store/in-memory-fact-store.js.map +1 -0
- package/dist/surface/context-query.d.ts +21 -0
- package/dist/surface/context-query.js +113 -0
- package/dist/surface/context-query.js.map +1 -0
- package/dist/surface/evidence-backed-context-query.d.ts +62 -0
- package/dist/surface/evidence-backed-context-query.js +240 -0
- package/dist/surface/evidence-backed-context-query.js.map +1 -0
- package/dist/types/access-context.d.ts +25 -0
- package/dist/types/access-context.js +6 -0
- package/dist/types/access-context.js.map +1 -0
- package/dist/types/claim.d.ts +48 -0
- package/dist/types/claim.js +5 -0
- package/dist/types/claim.js.map +1 -0
- package/dist/types/consent.d.ts +41 -0
- package/dist/types/consent.js +3 -0
- package/dist/types/consent.js.map +1 -0
- package/dist/types/crypto-boundary.d.ts +12 -0
- package/dist/types/crypto-boundary.js +15 -0
- package/dist/types/crypto-boundary.js.map +1 -0
- package/dist/types/evidence.d.ts +31 -0
- package/dist/types/evidence.js +8 -0
- package/dist/types/evidence.js.map +1 -0
- package/dist/types/fact-store.d.ts +24 -0
- package/dist/types/fact-store.js +12 -0
- package/dist/types/fact-store.js.map +1 -0
- package/dist/types/ids.d.ts +36 -0
- package/dist/types/ids.js +11 -0
- package/dist/types/ids.js.map +1 -0
- package/dist/types/proposal.d.ts +38 -0
- package/dist/types/proposal.js +7 -0
- package/dist/types/proposal.js.map +1 -0
- package/dist/types/source.d.ts +33 -0
- package/dist/types/source.js +6 -0
- package/dist/types/source.js.map +1 -0
- package/dist/types/trust.d.ts +24 -0
- package/dist/types/trust.js +5 -0
- package/dist/types/trust.js.map +1 -0
- package/dist/utilities/deep-freeze.d.ts +1 -0
- package/dist/utilities/deep-freeze.js +17 -0
- package/dist/utilities/deep-freeze.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// D2b Observability — HealthCheck types.
|
|
2
|
+
//
|
|
3
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_SURFACE_SPECIFIED_001
|
|
4
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_NO_DATA_EXPOSURE_001
|
|
5
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_ITEM_NAMES_STABLE_001
|
|
6
|
+
//
|
|
7
|
+
// HealthStatus semantics:
|
|
8
|
+
// "ok" — all critical local checks passed.
|
|
9
|
+
// "degraded" — at least one non-critical check degraded (reserved for future checks).
|
|
10
|
+
// "failed" — at least one critical local check failed.
|
|
11
|
+
//
|
|
12
|
+
// "ok" does NOT mean operational production readiness, backup readiness,
|
|
13
|
+
// GDPR/legal readiness, or external IdP/KMS readiness.
|
|
14
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_NOT_OPERATIONAL_READINESS_CLAIM_001
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/observability/health.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,6DAA6D;AAC7D,4DAA4D;AAC5D,6DAA6D;AAC7D,EAAE;AACF,0BAA0B;AAC1B,mDAAmD;AACnD,wFAAwF;AACxF,2DAA2D;AAC3D,EAAE;AACF,yEAAyE;AACzE,uDAAuD;AACvD,2EAA2E"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
2
|
+
export interface LogEvent {
|
|
3
|
+
readonly level: LogLevel;
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly component: string;
|
|
6
|
+
readonly operation: string;
|
|
7
|
+
readonly message: string;
|
|
8
|
+
readonly timestamp: string;
|
|
9
|
+
readonly errorName?: string;
|
|
10
|
+
readonly reasonCode?: string;
|
|
11
|
+
readonly contextKind?: string;
|
|
12
|
+
readonly correlationId?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface Logger {
|
|
15
|
+
debug?(event: LogEvent): void | Promise<void>;
|
|
16
|
+
info?(event: LogEvent): void | Promise<void>;
|
|
17
|
+
warn?(event: LogEvent): void | Promise<void>;
|
|
18
|
+
error?(event: LogEvent): void | Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare const noopLogger: Logger;
|
|
21
|
+
export declare function safeLog(logger: Logger | undefined, event: LogEvent): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// D2a — Logger and Operator Diagnostics.
|
|
2
|
+
//
|
|
3
|
+
// PRODUCTION_OBSERVABILITY_LOGGER_INTERFACE_SPECIFIED_001: Logger has four optional level methods.
|
|
4
|
+
// PRODUCTION_OBSERVABILITY_NOOP_LOGGER_DEFAULT_001: noopLogger is frozen with no methods.
|
|
5
|
+
// PRODUCTION_OBSERVABILITY_NO_CONSOLE_DEFAULT_001: no console output emitted by default.
|
|
6
|
+
// PRODUCTION_OBSERVABILITY_LOGGER_FAILURE_NON_INTERFERING_001: safeLog swallows all logger failures.
|
|
7
|
+
// PRODUCTION_OBSERVABILITY_LOGGER_NOT_AUDIT_SOURCE_001: Logger is not tamper-evident and must not
|
|
8
|
+
// be used as compliance evidence or to prove authorization decisions.
|
|
9
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_DEFERRED_001: healthCheck is not part of this module (D2b).
|
|
10
|
+
// PRODUCTION_OBSERVABILITY_METRICS_DEFERRED_001: metrics are not part of this module (D2c).
|
|
11
|
+
//
|
|
12
|
+
// Sensitive data policy: LogEvent fields must not contain rawBytes, JWTs, tokens, personId,
|
|
13
|
+
// subjectId, candidateClaims, decrypted payloads, raw documents, Error.message, or Error.stack.
|
|
14
|
+
// See docs/specs/post-mvp-observability-d2a-logger-spec.md §8 for the full policy.
|
|
15
|
+
// noopLogger: library default. No methods — no console output — frozen.
|
|
16
|
+
// PRODUCTION_OBSERVABILITY_NOOP_LOGGER_DEFAULT_001
|
|
17
|
+
// PRODUCTION_OBSERVABILITY_NO_CONSOLE_DEFAULT_001
|
|
18
|
+
export const noopLogger = Object.freeze({});
|
|
19
|
+
// safeLog: emit a sanitized LogEvent to the logger at the matching level.
|
|
20
|
+
// If the logger is absent, the matching method does not exist, the method throws synchronously,
|
|
21
|
+
// or the returned Promise rejects — all failures are swallowed.
|
|
22
|
+
// Never throws. Never masks the caller's error. Never affects transaction outcome.
|
|
23
|
+
// PRODUCTION_OBSERVABILITY_LOGGER_FAILURE_NON_INTERFERING_001
|
|
24
|
+
export function safeLog(logger, event) {
|
|
25
|
+
if (logger === undefined)
|
|
26
|
+
return;
|
|
27
|
+
const method = logger[event.level];
|
|
28
|
+
if (typeof method !== "function")
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
const result = method.call(logger, event);
|
|
32
|
+
if (result instanceof Promise) {
|
|
33
|
+
result.catch(() => { });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* sync logger failure swallowed */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/observability/logger.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,mGAAmG;AACnG,0FAA0F;AAC1F,yFAAyF;AACzF,qGAAqG;AACrG,kGAAkG;AAClG,wEAAwE;AACxE,mGAAmG;AACnG,4FAA4F;AAC5F,EAAE;AACF,4FAA4F;AAC5F,gGAAgG;AAChG,mFAAmF;AA6BnF,wEAAwE;AACxE,mDAAmD;AACnD,kDAAkD;AAClD,MAAM,CAAC,MAAM,UAAU,GAAW,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAEpD,0EAA0E;AAC1E,gGAAgG;AAChG,gEAAgE;AAChE,mFAAmF;AACnF,8DAA8D;AAC9D,MAAM,UAAU,OAAO,CACrB,MAA0B,EAC1B,KAAe;IAEf,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO;IACjC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,OAAO,MAAM,KAAK,UAAU;QAAE,OAAO;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAI,MAAgD,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACrF,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAwC,CAAC,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type MetricName = "factory.create.failure" | "audit.write.failure" | "allow_path.rollback" | "health_check.outcome" | "proposal.mutation.outcome";
|
|
2
|
+
export interface MetricLabels {
|
|
3
|
+
readonly component?: string;
|
|
4
|
+
readonly operation?: string;
|
|
5
|
+
readonly status?: "ok" | "failed" | "degraded" | "success" | "denied";
|
|
6
|
+
readonly errorCode?: string;
|
|
7
|
+
readonly checkName?: string;
|
|
8
|
+
readonly contextKind?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface MetricsRecorder {
|
|
11
|
+
increment?(name: MetricName, value?: number, labels?: MetricLabels): void | Promise<void>;
|
|
12
|
+
observe?(name: MetricName, value: number, labels?: MetricLabels): void | Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare const noopMetricsRecorder: MetricsRecorder;
|
|
15
|
+
export declare function safeRecord(metrics: MetricsRecorder | undefined, name: MetricName, value: number, labels?: MetricLabels): void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// D2c — Metrics Boundary.
|
|
2
|
+
//
|
|
3
|
+
// PRODUCTION_METRICS_BOUNDARY_SPECIFIED_001: D2c defines the metrics boundary.
|
|
4
|
+
// MetricsRecorder is the push-style adapter contract; noopMetricsRecorder is
|
|
5
|
+
// the library default. No external sink, HTTP endpoint, or OpenTelemetry added.
|
|
6
|
+
// PRODUCTION_METRICS_NO_EXTERNAL_SINK_001: no external sink in this library.
|
|
7
|
+
// PRODUCTION_METRICS_NO_HIGH_CARDINALITY_LABELS_001: labels use only the closed
|
|
8
|
+
// low-cardinality set — component, operation, status, errorCode, checkName, contextKind.
|
|
9
|
+
// PRODUCTION_METRICS_NO_PERSONAL_DATA_LABELS_001: labels never contain personId,
|
|
10
|
+
// subjectId, proposalId, claimId, documentId, agentId, keyId, rawBytes,
|
|
11
|
+
// Error.message, Error.stack, SQL, file paths, or decrypted payload content.
|
|
12
|
+
// PRODUCTION_METRICS_FAILURE_NON_INTERFERING_001: safeRecord swallows all recorder
|
|
13
|
+
// failures. A failing recorder never masks a business error, changes a transaction,
|
|
14
|
+
// or alters HealthCheckResult.
|
|
15
|
+
// PRODUCTION_METRICS_NOT_AUDIT_SOURCE_001: metrics are not tamper-evident. Must not
|
|
16
|
+
// be used as compliance evidence or to prove authorization decisions.
|
|
17
|
+
// PRODUCTION_METRICS_NOT_OPERATIONAL_READINESS_CLAIM_001: metric counts do not imply
|
|
18
|
+
// operational production readiness, SLA/SLO, or GDPR/legal readiness.
|
|
19
|
+
// PRODUCTION_METRICS_NO_PRODUCT_API_SURFACE_001: no metrics method on
|
|
20
|
+
// ProductionScopedLifeApi. MetricsRecorder is a factory config input only.
|
|
21
|
+
// PRODUCTION_METRICS_NAMES_STABLE_001: MetricName is a closed union. Five stable names.
|
|
22
|
+
// PRODUCTION_METRICS_NOOP_DEFAULT_001: noopMetricsRecorder is the library default.
|
|
23
|
+
//
|
|
24
|
+
// See docs/specs/post-mvp-observability-d2c-metrics-spec.md for full policy.
|
|
25
|
+
// noopMetricsRecorder: library default. Frozen. No external call. No output.
|
|
26
|
+
// PRODUCTION_METRICS_NOOP_DEFAULT_001
|
|
27
|
+
export const noopMetricsRecorder = Object.freeze({
|
|
28
|
+
increment() { },
|
|
29
|
+
observe() { },
|
|
30
|
+
});
|
|
31
|
+
// safeRecord: emit a counter increment to the recorder.
|
|
32
|
+
// PRODUCTION_METRICS_FAILURE_NON_INTERFERING_001: swallows all recorder failures
|
|
33
|
+
// (sync throw and async reject). Never throws. Never masks the caller's error.
|
|
34
|
+
// Never affects transaction outcome. Not exported from src/index.ts.
|
|
35
|
+
export function safeRecord(metrics, name, value, labels) {
|
|
36
|
+
if (metrics === undefined)
|
|
37
|
+
return;
|
|
38
|
+
const method = metrics.increment;
|
|
39
|
+
if (typeof method !== "function")
|
|
40
|
+
return;
|
|
41
|
+
try {
|
|
42
|
+
const result = method
|
|
43
|
+
.call(metrics, name, value, labels);
|
|
44
|
+
if (result instanceof Promise) {
|
|
45
|
+
result.catch(() => { });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* sync recorder failure swallowed */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/observability/metrics.ts"],"names":[],"mappings":"AAAA,0BAA0B;AAC1B,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,kFAAkF;AAClF,6EAA6E;AAC7E,gFAAgF;AAChF,2FAA2F;AAC3F,iFAAiF;AACjF,0EAA0E;AAC1E,+EAA+E;AAC/E,mFAAmF;AACnF,sFAAsF;AACtF,iCAAiC;AACjC,oFAAoF;AACpF,wEAAwE;AACxE,qFAAqF;AACrF,wEAAwE;AACxE,sEAAsE;AACtE,6EAA6E;AAC7E,wFAAwF;AACxF,mFAAmF;AACnF,EAAE;AACF,6EAA6E;AAkC7E,6EAA6E;AAC7E,sCAAsC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAoB,MAAM,CAAC,MAAM,CAAC;IAChE,SAAS,KAAI,CAAC;IACd,OAAO,KAAM,CAAC;CACf,CAAC,CAAC;AAEH,wDAAwD;AACxD,iFAAiF;AACjF,iFAAiF;AACjF,uEAAuE;AACvE,MAAM,UAAU,UAAU,CACxB,OAAoC,EACpC,IAAmB,EACnB,KAAe,EACf,MAAqB;IAErB,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IACjC,IAAI,OAAO,MAAM,KAAK,UAAU;QAAE,OAAO;IACzC,IAAI,CAAC;QACH,MAAM,MAAM,GAAI,MAAgF;aAC7F,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACtC,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;YAC9B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAA0C,CAAC,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;IACvC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
type SemanticCheckCode = "RESTORE_SEMANTIC_ORPHANED_CLAIM_SOURCES_001" | "RESTORE_SEMANTIC_ORPHANED_EVIDENCE_REFS_001" | "RESTORE_SEMANTIC_ORPHANED_PROPOSAL_GRANTS_001" | "RESTORE_SEMANTIC_AUDIT_SEQUENCE_DUPLICATE_001";
|
|
3
|
+
export interface SemanticValidationResult {
|
|
4
|
+
readonly status: "ok" | "failed";
|
|
5
|
+
readonly checkedAt: string;
|
|
6
|
+
readonly checkCount?: number;
|
|
7
|
+
readonly failedChecks?: readonly SemanticCheckCode[];
|
|
8
|
+
readonly errorName?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface BackupResult {
|
|
11
|
+
readonly status: "ok" | "failed";
|
|
12
|
+
readonly createdAt: string;
|
|
13
|
+
readonly errorName?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ValidationResult {
|
|
16
|
+
readonly status: "ok" | "failed";
|
|
17
|
+
readonly checkedAt: string;
|
|
18
|
+
readonly schemaVersion?: number;
|
|
19
|
+
readonly errorName?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function backupSQLiteDatabase(db: Database.Database, destinationPath: string): Promise<BackupResult>;
|
|
22
|
+
export declare function validateBackup(sourcePath: string): Promise<ValidationResult>;
|
|
23
|
+
export declare function validateRestoreSemantics(sourcePath: string): Promise<SemanticValidationResult>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// D3a — SQLite Backup and Restore Validation boundary.
|
|
2
|
+
//
|
|
3
|
+
// PRODUCTION_BACKUP_BOUNDARY_SPECIFIED_001: all 8 schema tables are included
|
|
4
|
+
// in a backup via the SQLite backup API (db.backup). The backup is a
|
|
5
|
+
// consistent, self-contained SQLite file — no .wal/.shm files required.
|
|
6
|
+
// PRODUCTION_BACKUP_SQLITE_CONSISTENCY_STRATEGY_SPECIFIED_001: uses
|
|
7
|
+
// db.backup(dest) — online-safe, WAL-consistent, SAVEPOINT-safe. Never
|
|
8
|
+
// copies .db/.wal/.shm files manually; never uses VACUUM INTO.
|
|
9
|
+
// PRODUCTION_BACKUP_NO_DECRYPTED_PAYLOAD_EXPORT_001: backup copies encrypted
|
|
10
|
+
// ciphertext columns only. No encrypted payload is decrypted, read, logged,
|
|
11
|
+
// or returned. No key provider is required.
|
|
12
|
+
// PRODUCTION_BACKUP_NO_KEY_MATERIAL_CLAIM_001: backup does not include or
|
|
13
|
+
// manage external key material. AES-256 keys reside in StaticKeyProvider
|
|
14
|
+
// process memory only — never in the DB. Restore requires the original key
|
|
15
|
+
// provider to read encrypted payloads.
|
|
16
|
+
// PRODUCTION_BACKUP_POST_BACKUP_VERIFICATION_REQUIRED_001: after db.backup()
|
|
17
|
+
// resolves, verifyCurrentSchema is run on the destination file. BackupResult
|
|
18
|
+
// reflects both steps.
|
|
19
|
+
// PRODUCTION_BACKUP_NEVER_THROWS_001: backupSQLiteDatabase never throws.
|
|
20
|
+
// Failures are expressed as BackupResult { status: "failed" }.
|
|
21
|
+
// PRODUCTION_RESTORE_VALIDATION_SPECIFIED_001: validateBackup opens the backup
|
|
22
|
+
// read-only, runs verifyCurrentSchema, and confirms migration_version equals
|
|
23
|
+
// CURRENT_SCHEMA_VERSION.
|
|
24
|
+
// PRODUCTION_RESTORE_NO_ACTIVE_DB_OVERWRITE_001: validateBackup opens a
|
|
25
|
+
// separate read-only connection. It does not overwrite or modify any
|
|
26
|
+
// currently-open production database.
|
|
27
|
+
// PRODUCTION_BACKUP_NOT_OPERATIONAL_READINESS_CLAIM_001: a successful backup
|
|
28
|
+
// does not constitute operational production readiness, SLA, or disaster
|
|
29
|
+
// recovery guarantee.
|
|
30
|
+
// PRODUCTION_BACKUP_NOT_GDPR_LEGAL_CLAIM_001: backup/restore does not
|
|
31
|
+
// constitute GDPR-valid erasure or legal compliance. Restoring from backup
|
|
32
|
+
// before an erasure event reconstitutes erased data — this is a known
|
|
33
|
+
// limitation.
|
|
34
|
+
//
|
|
35
|
+
// Semantic Restore Validation (Rank 3):
|
|
36
|
+
// RESTORE_SEMANTIC_VALIDATION_OFFLINE_ONLY_001: validateRestoreSemantics opens
|
|
37
|
+
// backup read-only; makes no writes; emits no audit events.
|
|
38
|
+
// RESTORE_SEMANTIC_VALIDATION_SANITIZED_001: result carries only status,
|
|
39
|
+
// checkedAt, checkCount, failedChecks (error codes), and errorName. No entity
|
|
40
|
+
// IDs, no row counts, no DB path, no Error.message or Error.stack in result.
|
|
41
|
+
// RESTORE_SEMANTIC_VALIDATION_NEVER_THROWS_001: validateRestoreSemantics never
|
|
42
|
+
// throws. All failures expressed as { status: "failed", ... }.
|
|
43
|
+
// RESTORE_SEMANTIC_VALIDATION_NOT_OPERATIONAL_READINESS_001: { status: "ok" }
|
|
44
|
+
// does NOT constitute DR readiness, SLA, or legal erasure compliance.
|
|
45
|
+
// RESTORE_SEMANTIC_VALIDATION_NOT_PRODUCT_API_001: not on ProductionApiBundle,
|
|
46
|
+
// ProductionScopedLifeApi, or root-exported from src/index.ts.
|
|
47
|
+
import Database from "better-sqlite3";
|
|
48
|
+
import { verifyCurrentSchema, CURRENT_SCHEMA_VERSION, } from "./sqlite/migrations.js";
|
|
49
|
+
// ─── backupSQLiteDatabase ──────────────────────────────────────────────────────
|
|
50
|
+
// Copies the database using the SQLite backup API (sqlite3_backup_*).
|
|
51
|
+
// Online-safe: runs while other connections write; WAL-consistent; SAVEPOINT-safe.
|
|
52
|
+
// After the backup resolves, verifyCurrentSchema is run on the destination file.
|
|
53
|
+
// Returns { status: "failed" } on any failure — never throws.
|
|
54
|
+
// Does not write audit events. Does not require key material. Does not read or
|
|
55
|
+
// return any encrypted payload column content.
|
|
56
|
+
export async function backupSQLiteDatabase(db, destinationPath) {
|
|
57
|
+
const createdAt = new Date().toISOString();
|
|
58
|
+
// Step 1: SQLite backup API — online-safe, WAL-consistent.
|
|
59
|
+
try {
|
|
60
|
+
await db.backup(destinationPath);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return {
|
|
64
|
+
status: "failed",
|
|
65
|
+
createdAt,
|
|
66
|
+
...(err instanceof Error ? { errorName: err.name } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Step 2: Post-backup structural verification on destination (read-only).
|
|
70
|
+
let verifyDb;
|
|
71
|
+
try {
|
|
72
|
+
verifyDb = new Database(destinationPath, { readonly: true, fileMustExist: true });
|
|
73
|
+
const structResult = verifyCurrentSchema(verifyDb);
|
|
74
|
+
if (!structResult.ok) {
|
|
75
|
+
return { status: "failed", createdAt, errorName: "BackupVerificationError" };
|
|
76
|
+
}
|
|
77
|
+
return { status: "ok", createdAt };
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
status: "failed",
|
|
82
|
+
createdAt,
|
|
83
|
+
...(err instanceof Error ? { errorName: err.name } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
try {
|
|
88
|
+
verifyDb?.close();
|
|
89
|
+
}
|
|
90
|
+
catch { /* ignore close errors */ }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// ─── validateBackup ────────────────────────────────────────────────────────────
|
|
94
|
+
// Opens the backup file read-only and verifies:
|
|
95
|
+
// 1. All required tables, columns, and indexes exist (verifyCurrentSchema)
|
|
96
|
+
// 2. migration_version === CURRENT_SCHEMA_VERSION
|
|
97
|
+
// Returns { status: "ok", schemaVersion } on success.
|
|
98
|
+
// Returns { status: "failed", errorName } on failure — never throws.
|
|
99
|
+
// Does not decrypt payloads. Does not require key material. Does not write to
|
|
100
|
+
// any table. Closes the read-only connection before returning.
|
|
101
|
+
export async function validateBackup(sourcePath) {
|
|
102
|
+
const checkedAt = new Date().toISOString();
|
|
103
|
+
let db;
|
|
104
|
+
try {
|
|
105
|
+
db = new Database(sourcePath, { readonly: true, fileMustExist: true });
|
|
106
|
+
// Structural check — tables, columns, indexes.
|
|
107
|
+
const structResult = verifyCurrentSchema(db);
|
|
108
|
+
if (!structResult.ok) {
|
|
109
|
+
return { status: "failed", checkedAt, errorName: "SchemaVerificationError" };
|
|
110
|
+
}
|
|
111
|
+
// Migration version check.
|
|
112
|
+
const row = db
|
|
113
|
+
.prepare("SELECT value FROM schema_meta WHERE key = 'migration_version'")
|
|
114
|
+
.get();
|
|
115
|
+
const version = row !== undefined ? parseInt(row.value, 10) : NaN;
|
|
116
|
+
if (!Number.isFinite(version) || version !== CURRENT_SCHEMA_VERSION) {
|
|
117
|
+
return {
|
|
118
|
+
status: "failed",
|
|
119
|
+
checkedAt,
|
|
120
|
+
errorName: "SchemaMigrationVersionError",
|
|
121
|
+
...(Number.isFinite(version) ? { schemaVersion: version } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return { status: "ok", checkedAt, schemaVersion: version };
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return {
|
|
128
|
+
status: "failed",
|
|
129
|
+
checkedAt,
|
|
130
|
+
...(err instanceof Error ? { errorName: err.name } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
try {
|
|
135
|
+
db?.close();
|
|
136
|
+
}
|
|
137
|
+
catch { /* ignore close errors */ }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// ─── validateRestoreSemantics ─────────────────────────────────────────────────
|
|
141
|
+
// Opens the backup file (or any migrated SQLite file) read-only and runs four
|
|
142
|
+
// application-layer referential integrity checks that structural and cryptographic
|
|
143
|
+
// validation cannot detect:
|
|
144
|
+
//
|
|
145
|
+
// 1. Orphaned claim→source references
|
|
146
|
+
// (claims.source_id NOT IN sources.id)
|
|
147
|
+
// 2. Orphaned source evidence references
|
|
148
|
+
// (sources.evidence_id references a raw_documents.id that does not exist)
|
|
149
|
+
// 3. Orphaned proposal→grant references
|
|
150
|
+
// (proposals.under_grant NOT IN auth_grants.grant_id)
|
|
151
|
+
// 4. Duplicate audit event sequence numbers
|
|
152
|
+
// (audit_events.sequence uniqueness violated post-restore)
|
|
153
|
+
//
|
|
154
|
+
// Returns { status: "ok" } when all four checks pass.
|
|
155
|
+
// Returns { status: "failed", failedChecks: [...] } listing which checks failed
|
|
156
|
+
// (error codes only — no entity IDs, no row counts, no hash values).
|
|
157
|
+
// Returns { status: "failed", errorName } on DB open or schema setup failure.
|
|
158
|
+
//
|
|
159
|
+
// Never throws (RESTORE_SEMANTIC_VALIDATION_NEVER_THROWS_001).
|
|
160
|
+
// Opens a separate read-only connection — does not modify any open DB
|
|
161
|
+
// (RESTORE_SEMANTIC_VALIDATION_OFFLINE_ONLY_001).
|
|
162
|
+
// { status: "ok" } is NOT a DR/SLA/GDPR claim
|
|
163
|
+
// (RESTORE_SEMANTIC_VALIDATION_NOT_OPERATIONAL_READINESS_001).
|
|
164
|
+
export async function validateRestoreSemantics(sourcePath) {
|
|
165
|
+
const checkedAt = new Date().toISOString();
|
|
166
|
+
let db;
|
|
167
|
+
try {
|
|
168
|
+
db = new Database(sourcePath, { readonly: true, fileMustExist: true });
|
|
169
|
+
// Structural guard — fail fast if schema is invalid or wrong version.
|
|
170
|
+
// Reuses the same verifyCurrentSchema guard as validateBackup.
|
|
171
|
+
const structResult = verifyCurrentSchema(db);
|
|
172
|
+
if (!structResult.ok) {
|
|
173
|
+
return { status: "failed", checkedAt, errorName: "SchemaVerificationError" };
|
|
174
|
+
}
|
|
175
|
+
const failed = [];
|
|
176
|
+
// ── Check 1: Orphaned claim→source references ──────────────────────────
|
|
177
|
+
// RESTORE_SEMANTIC_ORPHANED_CLAIM_SOURCES_001
|
|
178
|
+
// Every claims.source_id must resolve to a sources.id in the same backup.
|
|
179
|
+
// An orphaned source_id indicates a truncated or partial backup.
|
|
180
|
+
const orphanedClaims = db
|
|
181
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM claims
|
|
182
|
+
WHERE source_id NOT IN (SELECT id FROM sources)`)
|
|
183
|
+
.get();
|
|
184
|
+
if (orphanedClaims.cnt > 0) {
|
|
185
|
+
failed.push("RESTORE_SEMANTIC_ORPHANED_CLAIM_SOURCES_001");
|
|
186
|
+
}
|
|
187
|
+
// ── Check 2: Orphaned source→evidence references ───────────────────────
|
|
188
|
+
// RESTORE_SEMANTIC_ORPHANED_EVIDENCE_REFS_001
|
|
189
|
+
// Every non-null sources.evidence_id must resolve to a raw_documents.id.
|
|
190
|
+
// NULL evidence_id is allowed (source without attached document).
|
|
191
|
+
const orphanedEvidence = db
|
|
192
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM sources
|
|
193
|
+
WHERE evidence_id IS NOT NULL
|
|
194
|
+
AND evidence_id NOT IN (SELECT id FROM raw_documents)`)
|
|
195
|
+
.get();
|
|
196
|
+
if (orphanedEvidence.cnt > 0) {
|
|
197
|
+
failed.push("RESTORE_SEMANTIC_ORPHANED_EVIDENCE_REFS_001");
|
|
198
|
+
}
|
|
199
|
+
// ── Check 3: Orphaned proposal→grant references ────────────────────────
|
|
200
|
+
// RESTORE_SEMANTIC_ORPHANED_PROPOSAL_GRANTS_001
|
|
201
|
+
// Every proposals.under_grant must resolve to an auth_grants.grant_id.
|
|
202
|
+
const orphanedGrants = db
|
|
203
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM proposals
|
|
204
|
+
WHERE under_grant NOT IN (SELECT grant_id FROM auth_grants)`)
|
|
205
|
+
.get();
|
|
206
|
+
if (orphanedGrants.cnt > 0) {
|
|
207
|
+
failed.push("RESTORE_SEMANTIC_ORPHANED_PROPOSAL_GRANTS_001");
|
|
208
|
+
}
|
|
209
|
+
// ── Check 4: Duplicate audit event sequence numbers ────────────────────
|
|
210
|
+
// RESTORE_SEMANTIC_AUDIT_SEQUENCE_DUPLICATE_001
|
|
211
|
+
// The audit_events_sequence_unique index enforces uniqueness at write time.
|
|
212
|
+
// This check is defense-in-depth after restore — a corrupt restore could
|
|
213
|
+
// violate the constraint without triggering an index rebuild.
|
|
214
|
+
const dupSequences = db
|
|
215
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM (
|
|
216
|
+
SELECT sequence FROM audit_events
|
|
217
|
+
GROUP BY sequence HAVING COUNT(*) > 1
|
|
218
|
+
)`)
|
|
219
|
+
.get();
|
|
220
|
+
if (dupSequences.cnt > 0) {
|
|
221
|
+
failed.push("RESTORE_SEMANTIC_AUDIT_SEQUENCE_DUPLICATE_001");
|
|
222
|
+
}
|
|
223
|
+
// ── Aggregate result ───────────────────────────────────────────────────
|
|
224
|
+
const TOTAL_CHECKS = 4;
|
|
225
|
+
if (failed.length === 0) {
|
|
226
|
+
return { status: "ok", checkedAt, checkCount: TOTAL_CHECKS };
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
status: "failed",
|
|
230
|
+
checkedAt,
|
|
231
|
+
checkCount: TOTAL_CHECKS,
|
|
232
|
+
failedChecks: failed,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
// DB open, schema verification, or SQL threw. checkCount is omitted —
|
|
237
|
+
// setup itself failed before any individual check could run.
|
|
238
|
+
return {
|
|
239
|
+
status: "failed",
|
|
240
|
+
checkedAt,
|
|
241
|
+
...(err instanceof Error ? { errorName: err.name } : {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
try {
|
|
246
|
+
db?.close();
|
|
247
|
+
}
|
|
248
|
+
catch { /* ignore close errors */ }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/persistence/backup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CryptoBoundary } from "../crypto/types.js";
|
|
2
|
+
export interface DecryptabilityScanResult {
|
|
3
|
+
readonly status: "ok" | "partial" | "failed";
|
|
4
|
+
readonly scannedAt: string;
|
|
5
|
+
readonly totalCount?: number;
|
|
6
|
+
readonly decryptedCount?: number;
|
|
7
|
+
readonly failedCount?: number;
|
|
8
|
+
readonly errorName?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function scanBackupDecryptability(sourcePath: string, cryptoBoundary: CryptoBoundary): Promise<DecryptabilityScanResult>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// D4c — Full Decryptability Scan boundary.
|
|
2
|
+
//
|
|
3
|
+
// PRODUCTION_DECRYPTABILITY_SCAN_OFFLINE_ONLY_001:
|
|
4
|
+
// Opens backup file read-only. Makes no writes to any SQLite table.
|
|
5
|
+
// Does not emit audit events. Does not call logger.
|
|
6
|
+
// PRODUCTION_DECRYPTABILITY_SCAN_NO_PLAINTEXT_EXPOSURE_001:
|
|
7
|
+
// Decrypted bytes are discarded immediately after each decrypt call.
|
|
8
|
+
// Plaintext never appears in result, errors, logs, metrics, or audit.
|
|
9
|
+
// PRODUCTION_DECRYPTABILITY_SCAN_AGGREGATE_RESULT_ONLY_001:
|
|
10
|
+
// Result contains only status, scannedAt, totalCount, decryptedCount,
|
|
11
|
+
// failedCount, and errorName. No entity IDs, no keyId strings, no
|
|
12
|
+
// ciphertext fragments, no per-row breakdown, no per-table breakdown.
|
|
13
|
+
// PRODUCTION_DECRYPTABILITY_SCAN_THREE_TABLES_001:
|
|
14
|
+
// Covers all three encrypted tables: claims.encrypted_payload,
|
|
15
|
+
// raw_documents.encrypted_blob, proposals.encrypted_payload.
|
|
16
|
+
// PRODUCTION_DECRYPTABILITY_SCAN_NOT_PRODUCT_API_001:
|
|
17
|
+
// Not on ProductionApiBundle, ProductionScopedLifeApi, or package root.
|
|
18
|
+
// PRODUCTION_DECRYPTABILITY_SCAN_NEVER_THROWS_001:
|
|
19
|
+
// scanBackupDecryptability never throws. All errors expressed as
|
|
20
|
+
// DecryptabilityScanResult { status: "failed", scannedAt, errorName }.
|
|
21
|
+
//
|
|
22
|
+
// IMPORTANT: failedCount does NOT distinguish between wrong key bytes,
|
|
23
|
+
// corrupt ciphertext, malformed blob, or any other decrypt failure.
|
|
24
|
+
// This utility proves only that AES-GCM tag verification succeeds (or fails)
|
|
25
|
+
// for each row with the provided CryptoBoundary. It is NOT a recovery claim.
|
|
26
|
+
//
|
|
27
|
+
// See docs/specs/post-mvp-d4c-decryptability-scan-spec.md
|
|
28
|
+
import Database from "better-sqlite3";
|
|
29
|
+
import { verifyCurrentSchema } from "./sqlite/migrations.js";
|
|
30
|
+
// ─── Inventory SQL ────────────────────────────────────────────────────────────
|
|
31
|
+
// Retrieves raw JSON blobs from all three encrypted payload columns.
|
|
32
|
+
// Results are ordered to enable deterministic test assertions.
|
|
33
|
+
// No entity IDs, primary keys, or foreign keys are selected.
|
|
34
|
+
const DECRYPT_INVENTORY_SQL = `
|
|
35
|
+
SELECT encrypted_payload AS blob FROM claims
|
|
36
|
+
UNION ALL
|
|
37
|
+
SELECT encrypted_blob AS blob FROM raw_documents
|
|
38
|
+
UNION ALL
|
|
39
|
+
SELECT encrypted_payload AS blob FROM proposals
|
|
40
|
+
ORDER BY 1
|
|
41
|
+
`;
|
|
42
|
+
// ─── scanBackupDecryptability ─────────────────────────────────────────────────
|
|
43
|
+
// Opens a backup file (or any migrated SQLite file) read-only and attempts to
|
|
44
|
+
// decrypt every encrypted payload using the provided CryptoBoundary.
|
|
45
|
+
//
|
|
46
|
+
// All decryption is delegated to the provided CryptoBoundary parameter.
|
|
47
|
+
// This file contains no raw WebCrypto API calls (CRYPTO_BOUNDARY_ONLY_001).
|
|
48
|
+
//
|
|
49
|
+
// Decrypted bytes are discarded immediately after the decrypt call. They are
|
|
50
|
+
// never assigned to a persistent variable, returned, logged, or audited.
|
|
51
|
+
//
|
|
52
|
+
// Algorithm:
|
|
53
|
+
// 1. Open sourcePath read-only (fileMustExist: true).
|
|
54
|
+
// 2. Run verifyCurrentSchema — fail fast if not a valid, current-version DB.
|
|
55
|
+
// 3. Run DECRYPT_INVENTORY_SQL to enumerate all encrypted blobs.
|
|
56
|
+
// 4. For each blob:
|
|
57
|
+
// a. Parse JSON → EncryptedBlob (malformed → failedCount++; continue)
|
|
58
|
+
// b. Call cryptoBoundary.decryptBytes(blob) → discard plaintext
|
|
59
|
+
// c. On throw (CRYPTO_TAMPER_DETECTED_001 or CRYPTO_WRONG_KEY_FAIL_CLOSED_001)
|
|
60
|
+
// → failedCount++; continue
|
|
61
|
+
// 5. Close DB.
|
|
62
|
+
// 6. Return aggregate DecryptabilityScanResult — counts only.
|
|
63
|
+
//
|
|
64
|
+
// Status semantics:
|
|
65
|
+
// "ok" — failedCount === 0; all rows decrypted.
|
|
66
|
+
// "partial" — failedCount > 0 && decryptedCount > 0; some rows failed.
|
|
67
|
+
// "failed" — DB open/schema/inventory failed (counts omitted); OR
|
|
68
|
+
// decryptedCount === 0 (all rows failed; counts present).
|
|
69
|
+
export async function scanBackupDecryptability(sourcePath, cryptoBoundary) {
|
|
70
|
+
const scannedAt = new Date().toISOString();
|
|
71
|
+
let db;
|
|
72
|
+
try {
|
|
73
|
+
db = new Database(sourcePath, { readonly: true, fileMustExist: true });
|
|
74
|
+
// Structural check — fail early if schema is invalid or wrong version.
|
|
75
|
+
const structResult = verifyCurrentSchema(db);
|
|
76
|
+
if (!structResult.ok) {
|
|
77
|
+
return { status: "failed", scannedAt, errorName: "SchemaVerificationError" };
|
|
78
|
+
}
|
|
79
|
+
// Enumerate all encrypted blobs. No entity IDs are selected — only blob text.
|
|
80
|
+
const rows = db
|
|
81
|
+
.prepare(DECRYPT_INVENTORY_SQL)
|
|
82
|
+
.all();
|
|
83
|
+
let decryptedCount = 0;
|
|
84
|
+
let failedCount = 0;
|
|
85
|
+
for (const row of rows) {
|
|
86
|
+
// Malformed or NULL blob — count as failed; no plaintext involved.
|
|
87
|
+
if (row.blob === null || row.blob === "") {
|
|
88
|
+
failedCount++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = JSON.parse(row.blob);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Blob is not valid JSON — cannot construct EncryptedBlob.
|
|
97
|
+
failedCount++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (typeof parsed !== "object" ||
|
|
101
|
+
parsed === null ||
|
|
102
|
+
Array.isArray(parsed) ||
|
|
103
|
+
parsed["algorithm"] !== "AES-256-GCM") {
|
|
104
|
+
failedCount++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const blob = parsed;
|
|
108
|
+
try {
|
|
109
|
+
// Attempt AES-GCM decrypt — the only thing we verify is that the tag
|
|
110
|
+
// passes. Returned Uint8Array is discarded immediately.
|
|
111
|
+
await cryptoBoundary.decryptBytes(blob);
|
|
112
|
+
decryptedCount++;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// CRYPTO_TAMPER_DETECTED_001: tag verification failed (corrupt ciphertext,
|
|
116
|
+
// wrong key bytes, or wrong IV). CRYPTO_WRONG_KEY_FAIL_CLOSED_001: keyId
|
|
117
|
+
// unknown to provider. Both are unrecoverable per-row failures.
|
|
118
|
+
// failedCount does not distinguish these failure modes — it is an aggregate.
|
|
119
|
+
failedCount++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const totalCount = decryptedCount + failedCount;
|
|
123
|
+
const status = failedCount === 0 ? "ok"
|
|
124
|
+
: decryptedCount > 0 ? "partial"
|
|
125
|
+
: /* all rows failed */ "failed";
|
|
126
|
+
return { status, scannedAt, totalCount, decryptedCount, failedCount };
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
// DB open, schema verification, or inventory SQL threw.
|
|
130
|
+
// Counts are omitted — the inventory step itself failed.
|
|
131
|
+
return {
|
|
132
|
+
status: "failed",
|
|
133
|
+
scannedAt,
|
|
134
|
+
...(err instanceof Error ? { errorName: err.name } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
try {
|
|
139
|
+
db?.close();
|
|
140
|
+
}
|
|
141
|
+
catch { /* ignore close errors */ }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=decryptability-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decryptability-scan.js","sourceRoot":"","sources":["../../src/persistence/decryptability-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { UnitOfWork } from "./unit-of-work.js";
|
|
2
|
+
import { InMemoryRawDocumentStore } from "../evidence/in-memory-raw-document-store.js";
|
|
3
|
+
import { InMemorySourceRegistry } from "../source/in-memory-source-registry.js";
|
|
4
|
+
import { InMemoryFactStore } from "../store/in-memory-fact-store.js";
|
|
5
|
+
export declare class InMemoryUnitOfWork implements UnitOfWork {
|
|
6
|
+
private readonly _docStore;
|
|
7
|
+
private readonly _srcReg;
|
|
8
|
+
private readonly _factStore;
|
|
9
|
+
constructor(_docStore: InMemoryRawDocumentStore, _srcReg: InMemorySourceRegistry, _factStore: InMemoryFactStore);
|
|
10
|
+
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
11
|
+
}
|