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,654 @@
|
|
|
1
|
+
// Production factory — readiness gate + final assembly.
|
|
2
|
+
// Production-01a (AUTH_HOST_ADAPTER_REQUIRED_001)
|
|
3
|
+
// Production-02c-a (AUTH_TENANT_BOUNDARY_REQUIRED_001, AUTH_GRANT_RESOLVER_REQUIRED_001)
|
|
4
|
+
// Production-05-a (db guard, encryptionKey guard, DB-version assertion, readiness-blocked)
|
|
5
|
+
// Production-06d-c-a (async durable ProposalQueue construction proof, ASSEMBLY_DEFERRED_001)
|
|
6
|
+
// Production-06d-d-d (final assembly: steps 15–19, Promise<ProductionApiBundle>)
|
|
7
|
+
//
|
|
8
|
+
// AUTH_HOST_ADAPTER_REQUIRED_001: factory requires an AuthAdapter; missing config or
|
|
9
|
+
// missing authAdapter throws before any other guard fires.
|
|
10
|
+
// AUTH_TENANT_BOUNDARY_REQUIRED_001: requires a TenantBoundary; thrown after authAdapter
|
|
11
|
+
// guard.
|
|
12
|
+
// AUTH_GRANT_RESOLVER_REQUIRED_001: requires a GrantResolver; thrown after tenantBoundary
|
|
13
|
+
// guard.
|
|
14
|
+
// PRODUCTION_FACTORY_DB_REQUIRED_001: requires a pre-migrated Database; thrown after
|
|
15
|
+
// grantResolver guard.
|
|
16
|
+
// PRODUCTION_FACTORY_ENCRYPTION_KEY_REQUIRED_001: requires an encryptionKey; thrown after
|
|
17
|
+
// db guard.
|
|
18
|
+
// PRODUCTION_FACTORY_ENCRYPTION_KEY_TOO_SHORT_001: rawBytes must be >= 32 bytes; thrown
|
|
19
|
+
// after encryptionKey-present check.
|
|
20
|
+
// PRODUCTION_FACTORY_ENCRYPTION_KEY_ID_INVALID_001: keyId must be a non-empty string;
|
|
21
|
+
// thrown after rawBytes length check.
|
|
22
|
+
// PRODUCTION_FACTORY_ENCRYPTION_SOURCE_AMBIGUOUS_001: thrown when both keyProvider and
|
|
23
|
+
// encryptionKey are provided simultaneously. Fail-closed — no partial construction.
|
|
24
|
+
// PRODUCTION_FACTORY_KEY_PROVIDER_INVALID_001: thrown when keyProvider is present but null,
|
|
25
|
+
// non-object, or missing getCurrentKey/getKey methods.
|
|
26
|
+
// PRODUCTION_FACTORY_DB_NOT_READY_001: thrown after keyId check when (a) migration_version
|
|
27
|
+
// is absent or ≠ CURRENT_SCHEMA_VERSION, or (b) verifyCurrentSchema() detects missing
|
|
28
|
+
// tables or columns (e.g. auth_tenant_memberships, auth_grants). migration_version alone
|
|
29
|
+
// is insufficient — structural verification is always required.
|
|
30
|
+
// RAW_DOCUMENT_STORE_FACTORY_COMPATIBLE_REQUIRED_001: rawDocumentStore must be a
|
|
31
|
+
// FactoryCompatibleRawDocumentStore; missing or non-compatible instance throws at step 9
|
|
32
|
+
// before READINESS_BLOCKED (Production-06c).
|
|
33
|
+
// RAW_DOCUMENT_STORE_INMEMORY_REJECTED_001: thrown at step 9.5 when rawDocumentStore lacks
|
|
34
|
+
// the factory-compatible durable marker — defense-in-depth against TS casts.
|
|
35
|
+
// AUDIT_LOG_FACTORY_COMPATIBLE_REQUIRED_001: auditLog must be a FactoryCompatibleAuditLog;
|
|
36
|
+
// missing or non-compatible instance throws at step 10 (Production-06c).
|
|
37
|
+
// AUDIT_LOG_INMEMORY_OR_NOOP_REJECTED_001: thrown at step 10.5 when auditLog lacks the
|
|
38
|
+
// durable marker — InMemoryAuditLog, noop, and test-only sinks not accepted.
|
|
39
|
+
// PRODUCTION_FACTORY_READINESS_BLOCKED_001: historical — was thrown at step 11
|
|
40
|
+
// (Production-06c / Production-05-a) when all core deps were valid but ProposalQueue was
|
|
41
|
+
// not yet wired. Superseded at step 11 by Production-06d-c-a (durable construction proof
|
|
42
|
+
// + PRODUCTION_FACTORY_ASSEMBLY_DEFERRED_001).
|
|
43
|
+
// PRODUCTION_FACTORY_PROPOSAL_QUEUE_COMPATIBLE_REQUIRED_001: thrown at step 11 when the
|
|
44
|
+
// constructed SQLiteProposalQueue does not carry the factoryCompatible marker
|
|
45
|
+
// ("durable-proposal-queue"). Defense-in-depth against casts and non-standard impls.
|
|
46
|
+
// PRODUCTION_FACTORY_ASSEMBLY_DEFERRED_001: superseded by Production-06d-d-d.
|
|
47
|
+
// Was thrown at step 11 after all deps validated. Now replaced by bundle return.
|
|
48
|
+
// See PRODUCTION_FACTORY_ASSEMBLY_DEFERRED_SUPERSEDED_001 in INVARIANTS.md.
|
|
49
|
+
// PRODUCTION_FACTORY_SCOPED_API_TRUST_GATED_001: createScopedApi calls
|
|
50
|
+
// issuer.assertTrusted(mintedCtx.context) before constructing ProductionScopedLifeApiImpl.
|
|
51
|
+
// Rejects manually constructed AccessContext objects and contexts from other factory
|
|
52
|
+
// invocations at capability creation time — not deferred to first method call.
|
|
53
|
+
// PRODUCTION_FACTORY_ISSUER_API_PAIRING_001: AccessContextIssuer and LifeApiImpl are
|
|
54
|
+
// co-created in the same factory call; the issuer WeakSet is private to this invocation.
|
|
55
|
+
// PRODUCTION_FACTORY_RAW_API_NOT_RETURNED_001: LifeApiImpl (rawApi) is sealed in the
|
|
56
|
+
// createScopedApi closure and never returned as a public value.
|
|
57
|
+
// PRODUCTION_FACTORY_ASYNC_DEFERRED_BOUNDARY_001: createSQLiteLifeApiForProduction is
|
|
58
|
+
// async (Promise<ProductionApiBundle>); required by StaticKeyProvider.fromRawBytes
|
|
59
|
+
// (WebCrypto importKey) and SQLiteProposalQueue.open (static async).
|
|
60
|
+
//
|
|
61
|
+
// PRODUCTION_FACTORY_STILL_STUB_001: superseded by Production-05-a. The factory is no
|
|
62
|
+
// longer a stub — it is a readiness gate. See docs/INVARIANTS.md for historical record.
|
|
63
|
+
//
|
|
64
|
+
// Validation order (SPEC-b §6 + Production-06c + Production-06d-c-a):
|
|
65
|
+
// 1. missing config → AUTH_HOST_ADAPTER_REQUIRED_001
|
|
66
|
+
// 2. missing authAdapter → AUTH_HOST_ADAPTER_REQUIRED_001
|
|
67
|
+
// 3. missing tenantBoundary → AUTH_TENANT_BOUNDARY_REQUIRED_001
|
|
68
|
+
// 4. missing grantResolver → AUTH_GRANT_RESOLVER_REQUIRED_001
|
|
69
|
+
// 5. missing db → PRODUCTION_FACTORY_DB_REQUIRED_001
|
|
70
|
+
// 6. both keyProvider and encryptionKey → PRODUCTION_FACTORY_ENCRYPTION_SOURCE_AMBIGUOUS_001
|
|
71
|
+
// 6.5 keyProvider present, invalid → PRODUCTION_FACTORY_KEY_PROVIDER_INVALID_001
|
|
72
|
+
// 6.6 neither provided → PRODUCTION_FACTORY_ENCRYPTION_KEY_REQUIRED_001
|
|
73
|
+
// 7. rawBytes.length < 32 → PRODUCTION_FACTORY_ENCRYPTION_KEY_TOO_SHORT_001
|
|
74
|
+
// 7.5 keyId not non-empty → PRODUCTION_FACTORY_ENCRYPTION_KEY_ID_INVALID_001
|
|
75
|
+
// 8. DB not structurally current → PRODUCTION_FACTORY_DB_NOT_READY_001
|
|
76
|
+
// 9. missing rawDocumentStore → RAW_DOCUMENT_STORE_FACTORY_COMPATIBLE_REQUIRED_001
|
|
77
|
+
// 9.5 rawDocumentStore wrong marker → RAW_DOCUMENT_STORE_INMEMORY_REJECTED_001
|
|
78
|
+
// 10. missing auditLog → AUDIT_LOG_FACTORY_COMPATIBLE_REQUIRED_001
|
|
79
|
+
// 10.5 auditLog wrong marker → AUDIT_LOG_INMEMORY_OR_NOOP_REJECTED_001
|
|
80
|
+
// 11. construct durable chain + open SQLiteProposalQueue:
|
|
81
|
+
// marker fail → PRODUCTION_FACTORY_PROPOSAL_QUEUE_COMPATIBLE_REQUIRED_001
|
|
82
|
+
// all valid → steps 15–19 (assembly) → return ProductionApiBundle
|
|
83
|
+
//
|
|
84
|
+
// AUTH_FACTORY_AUTH_ADAPTER_OPAQUE_001: this file must not import JwtAuthAdapter,
|
|
85
|
+
// JwtKeyRingAuthAdapter, StaticJwtKeyRing, or jwt-core. The factory is AuthAdapter-opaque.
|
|
86
|
+
// PRODUCTION_FACTORY_DB_PRE_MIGRATED_001: the factory never calls runMigrations; the host
|
|
87
|
+
// must call runMigrations(db) before passing db to this factory.
|
|
88
|
+
// PRODUCTION_FACTORY_NO_SECRET_LOGGING_001: rawBytes is never included in error messages,
|
|
89
|
+
// error properties, log statements, or serialized forms.
|
|
90
|
+
// PRODUCTION_FACTORY_JWT_AND_AT_REST_KEYS_SEPARATE_001: encryptionKey.rawBytes is never
|
|
91
|
+
// passed to any JWT component.
|
|
92
|
+
//
|
|
93
|
+
// Platform-02 (TrustedDataSourceRegistry + ProposalPolicy) codes:
|
|
94
|
+
// PROPOSAL_POLICY_OPTIONAL_NO_CHECK_WHEN_ABSENT_001: neither trustedDataSourceRegistry nor
|
|
95
|
+
// proposalPolicy set → effectiveQueue === queue; behavior is byte-identical to today.
|
|
96
|
+
// PROPOSAL_POLICY_EXPLICIT_WINS_OVER_REGISTRY_001: explicit config.proposalPolicy wins over
|
|
97
|
+
// the trustedDataSourceRegistry convenience wiring when both are provided.
|
|
98
|
+
import { safeLog } from "../observability/logger.js";
|
|
99
|
+
import { safeRecord } from "../observability/metrics.js";
|
|
100
|
+
import { CURRENT_SCHEMA_VERSION, verifyCurrentSchema } from "../persistence/sqlite/migrations.js";
|
|
101
|
+
import { StaticKeyProvider } from "../crypto/static-key.js";
|
|
102
|
+
import { AesGcmCryptoBoundary } from "../crypto/aes-gcm.js";
|
|
103
|
+
import { ClaimSerializer } from "../crypto/claim-serializer.js";
|
|
104
|
+
import { SQLiteFactStore } from "../persistence/sqlite/sqlite-fact-store.js";
|
|
105
|
+
import { SQLiteSourceRegistry } from "../persistence/sqlite/sqlite-source-registry.js";
|
|
106
|
+
import { SQLiteUnitOfWork } from "../persistence/sqlite/sqlite-unit-of-work.js";
|
|
107
|
+
import { SQLiteAuditLog } from "../audit/sqlite-audit-log.js";
|
|
108
|
+
import { SourceAwareFactStore } from "../source/source-aware-fact-store.js";
|
|
109
|
+
import { SourceAwareClaimAppender } from "../proposal/source-aware-claim-appender.js";
|
|
110
|
+
import { GrantRegistry } from "../consent/grant-registry.js";
|
|
111
|
+
import { SQLiteProposalQueue } from "../proposal/sqlite-proposal-queue.js";
|
|
112
|
+
import { EvidenceBackedContextQueryService } from "../surface/evidence-backed-context-query.js";
|
|
113
|
+
import { EvidenceBackedClaimCreator } from "../evidence/evidence-backed-claim-creator.js";
|
|
114
|
+
import { AccessContextIssuer } from "../auth/access-context-issuer.js";
|
|
115
|
+
import { LifeApiImpl } from "./create-life-api.js";
|
|
116
|
+
import { DefaultAuthorizationPolicy } from "../auth/default-authorization-policy.js";
|
|
117
|
+
import { BackendContextMintingBoundary } from "../auth/backend-context-minting-boundary.js";
|
|
118
|
+
import { StaticTrustTable } from "../resolution/trust-profile.js";
|
|
119
|
+
import { ProductionScopedLifeApiImpl } from "../auth/production-scoped-life-api.js";
|
|
120
|
+
import { RegistryBackedProposalPolicy } from "../proposal/proposal-policy.js";
|
|
121
|
+
import { PolicyAwareProposalQueue } from "../proposal/policy-aware-proposal-queue.js";
|
|
122
|
+
export async function createSQLiteLifeApiForProduction(config) {
|
|
123
|
+
// Steps 1–2: authAdapter guard.
|
|
124
|
+
if (config == null || config.authAdapter == null) {
|
|
125
|
+
// If config is non-null, logger/metrics may be present — emit diagnostics before throwing.
|
|
126
|
+
if (config != null) {
|
|
127
|
+
safeLog(config.logger, {
|
|
128
|
+
level: "error", code: "LOG_AUTH_ADAPTER_MISSING_001",
|
|
129
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
130
|
+
message: "factory readiness failed: authAdapter missing",
|
|
131
|
+
timestamp: new Date().toISOString(),
|
|
132
|
+
});
|
|
133
|
+
safeRecord(config.metrics, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
134
|
+
status: "failed", errorCode: "AUTH_HOST_ADAPTER_REQUIRED_001" });
|
|
135
|
+
}
|
|
136
|
+
throw Object.assign(new Error("AUTH_HOST_ADAPTER_REQUIRED_001: createSQLiteLifeApiForProduction requires " +
|
|
137
|
+
"an AuthAdapter. Provide an implementation that verifies external credentials " +
|
|
138
|
+
"before minting trusted AccessContexts. Without an AuthAdapter, the minting " +
|
|
139
|
+
"boundary cannot enforce authentication — do not omit this argument."), { code: "AUTH_HOST_ADAPTER_REQUIRED_001" });
|
|
140
|
+
}
|
|
141
|
+
// Extract logger and metrics recorder once — available from step 3 onward.
|
|
142
|
+
const logger = config.logger;
|
|
143
|
+
const metricsRecorder = config.metrics;
|
|
144
|
+
// Step 3: tenantBoundary guard.
|
|
145
|
+
if (config.tenantBoundary == null) {
|
|
146
|
+
safeLog(logger, {
|
|
147
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
148
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
149
|
+
message: "factory readiness failed: tenantBoundary missing",
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
});
|
|
152
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
153
|
+
status: "failed", errorCode: "AUTH_TENANT_BOUNDARY_REQUIRED_001" });
|
|
154
|
+
throw Object.assign(new Error("AUTH_TENANT_BOUNDARY_REQUIRED_001: createSQLiteLifeApiForProduction requires " +
|
|
155
|
+
"a TenantBoundary. Every OwnerContext mint must pass a pre-mint tenant-ownership " +
|
|
156
|
+
"check. Without a TenantBoundary, the factory cannot enforce tenant isolation."), { code: "AUTH_TENANT_BOUNDARY_REQUIRED_001" });
|
|
157
|
+
}
|
|
158
|
+
// Step 4: grantResolver guard.
|
|
159
|
+
if (config.grantResolver == null) {
|
|
160
|
+
safeLog(logger, {
|
|
161
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
162
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
163
|
+
message: "factory readiness failed: grantResolver missing",
|
|
164
|
+
timestamp: new Date().toISOString(),
|
|
165
|
+
});
|
|
166
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
167
|
+
status: "failed", errorCode: "AUTH_GRANT_RESOLVER_REQUIRED_001" });
|
|
168
|
+
throw Object.assign(new Error("AUTH_GRANT_RESOLVER_REQUIRED_001: createSQLiteLifeApiForProduction requires " +
|
|
169
|
+
"a GrantResolver. Every AgentConsentContext mint requires a resolved, unexpired, " +
|
|
170
|
+
"tenant-scoped grant. Without a GrantResolver, the factory cannot enforce agent " +
|
|
171
|
+
"grant scope — do not omit this argument."), { code: "AUTH_GRANT_RESOLVER_REQUIRED_001" });
|
|
172
|
+
}
|
|
173
|
+
// Step 5: db guard (PRODUCTION_FACTORY_DB_REQUIRED_001).
|
|
174
|
+
if (config.db == null) {
|
|
175
|
+
safeLog(logger, {
|
|
176
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
177
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
178
|
+
message: "factory readiness failed: db missing",
|
|
179
|
+
timestamp: new Date().toISOString(),
|
|
180
|
+
});
|
|
181
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
182
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_DB_REQUIRED_001" });
|
|
183
|
+
throw Object.assign(new Error("PRODUCTION_FACTORY_DB_REQUIRED_001: createSQLiteLifeApiForProduction requires " +
|
|
184
|
+
"a pre-migrated Database instance. Call runMigrations(db) on a better-sqlite3 " +
|
|
185
|
+
"Database before passing it to the factory. The factory does not open database " +
|
|
186
|
+
"files by path and does not run migrations itself."), { code: "PRODUCTION_FACTORY_DB_REQUIRED_001" });
|
|
187
|
+
}
|
|
188
|
+
// Step 6: encryption source — exactly one of keyProvider or encryptionKey must be provided.
|
|
189
|
+
// PRODUCTION_KMS_ADAPTER_AMBIGUOUS_CONFIG_REJECTED_001: both present → fail closed.
|
|
190
|
+
// PRODUCTION_KMS_ADAPTER_FACTORY_ACCEPTS_KEYPROVIDER_001: keyProvider path.
|
|
191
|
+
// PRODUCTION_FACTORY_NO_SECRET_LOGGING_001: no key material in any error or log event.
|
|
192
|
+
const hasKeyProvider = config.keyProvider != null;
|
|
193
|
+
const hasEncryptionKey = config.encryptionKey != null;
|
|
194
|
+
if (hasKeyProvider && hasEncryptionKey) {
|
|
195
|
+
safeLog(logger, {
|
|
196
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
197
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
198
|
+
message: "factory readiness failed: both keyProvider and encryptionKey supplied — provide exactly one",
|
|
199
|
+
timestamp: new Date().toISOString(),
|
|
200
|
+
// No key material, no keyId — PRODUCTION_FACTORY_NO_SECRET_LOGGING_001
|
|
201
|
+
});
|
|
202
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
203
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_ENCRYPTION_SOURCE_AMBIGUOUS_001" });
|
|
204
|
+
throw Object.assign(new Error("PRODUCTION_FACTORY_ENCRYPTION_SOURCE_AMBIGUOUS_001: " +
|
|
205
|
+
"createSQLiteLifeApiForProduction received both keyProvider and encryptionKey. " +
|
|
206
|
+
"Supply exactly one encryption source: keyProvider (for an externally managed " +
|
|
207
|
+
"KeyProvider) or encryptionKey (for a StaticKeyProvider from raw bytes). " +
|
|
208
|
+
"Providing both is ambiguous and is rejected fail-closed."), { code: "PRODUCTION_FACTORY_ENCRYPTION_SOURCE_AMBIGUOUS_001" });
|
|
209
|
+
}
|
|
210
|
+
// Step 6.5: keyProvider structural validation.
|
|
211
|
+
// PRODUCTION_KMS_ADAPTER_INVALID_KEYPROVIDER_REJECTED_001
|
|
212
|
+
if (hasKeyProvider) {
|
|
213
|
+
const kp = config.keyProvider;
|
|
214
|
+
if (typeof kp !== "object" ||
|
|
215
|
+
typeof kp.getCurrentKey !== "function" ||
|
|
216
|
+
typeof kp.getKey !== "function") {
|
|
217
|
+
safeLog(logger, {
|
|
218
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
219
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
220
|
+
message: "factory readiness failed: keyProvider is not a valid KeyProvider",
|
|
221
|
+
timestamp: new Date().toISOString(),
|
|
222
|
+
// No key material, no keyId — PRODUCTION_FACTORY_NO_SECRET_LOGGING_001
|
|
223
|
+
});
|
|
224
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
225
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_KEY_PROVIDER_INVALID_001" });
|
|
226
|
+
throw Object.assign(new Error("PRODUCTION_FACTORY_KEY_PROVIDER_INVALID_001: the supplied keyProvider is not a " +
|
|
227
|
+
"valid KeyProvider. A KeyProvider must be a non-null object with getCurrentKey() " +
|
|
228
|
+
"and getKey() methods. Provide an implementation of the KeyProvider interface."), { code: "PRODUCTION_FACTORY_KEY_PROVIDER_INVALID_001" });
|
|
229
|
+
}
|
|
230
|
+
// keyProvider is structurally valid — will be used at assembly time.
|
|
231
|
+
}
|
|
232
|
+
else if (!hasEncryptionKey) {
|
|
233
|
+
// Step 6.6: neither provided — PRODUCTION_FACTORY_ENCRYPTION_KEY_REQUIRED_001.
|
|
234
|
+
safeLog(logger, {
|
|
235
|
+
level: "error", code: "LOG_ENCRYPTION_KEY_INVALID_001",
|
|
236
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
237
|
+
message: "factory readiness failed: neither keyProvider nor encryptionKey provided",
|
|
238
|
+
timestamp: new Date().toISOString(),
|
|
239
|
+
});
|
|
240
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
241
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_ENCRYPTION_KEY_REQUIRED_001" });
|
|
242
|
+
throw Object.assign(new Error("PRODUCTION_FACTORY_ENCRYPTION_KEY_REQUIRED_001: createSQLiteLifeApiForProduction " +
|
|
243
|
+
"requires an encryption source. Provide either: keyProvider (a KeyProvider " +
|
|
244
|
+
"implementation) or encryptionKey ({ keyId: string, rawBytes: Uint8Array } with " +
|
|
245
|
+
"rawBytes of at least 32 bytes). The factory uses AES-256-GCM encryption at rest."), { code: "PRODUCTION_FACTORY_ENCRYPTION_KEY_REQUIRED_001" });
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// Step 7: rawBytes length guard (PRODUCTION_FACTORY_ENCRYPTION_KEY_TOO_SHORT_001).
|
|
249
|
+
// PRODUCTION_FACTORY_NO_SECRET_LOGGING_001: rawBytes never included in message or log.
|
|
250
|
+
const encKey = config.encryptionKey;
|
|
251
|
+
if (!(encKey.rawBytes instanceof Uint8Array) ||
|
|
252
|
+
encKey.rawBytes.length < 32) {
|
|
253
|
+
safeLog(logger, {
|
|
254
|
+
level: "error", code: "LOG_ENCRYPTION_KEY_INVALID_001",
|
|
255
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
256
|
+
message: "factory readiness failed: encryptionKey.rawBytes validation failed",
|
|
257
|
+
timestamp: new Date().toISOString(),
|
|
258
|
+
// No rawBytes, no key length, no keyId — PRODUCTION_FACTORY_NO_SECRET_LOGGING_001
|
|
259
|
+
});
|
|
260
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
261
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_ENCRYPTION_KEY_TOO_SHORT_001" });
|
|
262
|
+
throw Object.assign(new Error("PRODUCTION_FACTORY_ENCRYPTION_KEY_TOO_SHORT_001: encryptionKey.rawBytes must " +
|
|
263
|
+
"be a Uint8Array of at least 32 bytes for AES-256-GCM. Supply a 32-byte or " +
|
|
264
|
+
"longer Uint8Array derived from a cryptographically secure key source."), { code: "PRODUCTION_FACTORY_ENCRYPTION_KEY_TOO_SHORT_001" });
|
|
265
|
+
}
|
|
266
|
+
// Step 7.5: keyId must be a non-empty string (PRODUCTION_FACTORY_ENCRYPTION_KEY_ID_INVALID_001).
|
|
267
|
+
if (typeof encKey.keyId !== "string" || encKey.keyId.length === 0) {
|
|
268
|
+
safeLog(logger, {
|
|
269
|
+
level: "error", code: "LOG_ENCRYPTION_KEY_INVALID_001",
|
|
270
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
271
|
+
message: "factory readiness failed: encryptionKey.keyId validation failed",
|
|
272
|
+
timestamp: new Date().toISOString(),
|
|
273
|
+
// No keyId value emitted — PRODUCTION_FACTORY_NO_SECRET_LOGGING_001
|
|
274
|
+
});
|
|
275
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
276
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_ENCRYPTION_KEY_ID_INVALID_001" });
|
|
277
|
+
throw Object.assign(new Error("PRODUCTION_FACTORY_ENCRYPTION_KEY_ID_INVALID_001: encryptionKey.keyId must be a " +
|
|
278
|
+
"non-empty string identifying the key version (e.g. 'key-v1', 'prod-2026-01'). " +
|
|
279
|
+
"Provide a stable, non-empty string identifier for the supplied rawBytes material."), { code: "PRODUCTION_FACTORY_ENCRYPTION_KEY_ID_INVALID_001" });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Step 8: DB readiness — migration_version assertion + full structural verification.
|
|
283
|
+
// migration_version alone is insufficient: a DB claiming current version but missing
|
|
284
|
+
// auth_tenant_memberships or auth_grants must fail closed.
|
|
285
|
+
// The factory does NOT run migrations — the caller must have called runMigrations(db).
|
|
286
|
+
// PRODUCTION_FACTORY_DB_NOT_READY_001 — PRODUCTION_FACTORY_DB_PRE_MIGRATED_001
|
|
287
|
+
const dbVersionRow = (() => {
|
|
288
|
+
try {
|
|
289
|
+
return config.db
|
|
290
|
+
.prepare("SELECT value FROM schema_meta WHERE key = 'migration_version'")
|
|
291
|
+
.get();
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
})();
|
|
297
|
+
const dbVersion = dbVersionRow !== undefined ? parseInt(dbVersionRow.value, 10) : NaN;
|
|
298
|
+
if (!Number.isFinite(dbVersion) || dbVersion !== CURRENT_SCHEMA_VERSION) {
|
|
299
|
+
const found = Number.isFinite(dbVersion) ? String(dbVersion) : "(absent)";
|
|
300
|
+
safeLog(logger, {
|
|
301
|
+
level: "error", code: "LOG_SCHEMA_NOT_READY_001",
|
|
302
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
303
|
+
message: "factory readiness failed: schema version mismatch",
|
|
304
|
+
timestamp: new Date().toISOString(),
|
|
305
|
+
});
|
|
306
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
307
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_DB_NOT_READY_001" });
|
|
308
|
+
throw Object.assign(new Error(`PRODUCTION_FACTORY_DB_NOT_READY_001: The supplied database has ` +
|
|
309
|
+
`migration_version ${found}, but this factory requires ` +
|
|
310
|
+
`migration_version ${CURRENT_SCHEMA_VERSION}. ` +
|
|
311
|
+
`Call runMigrations(db) before passing the database to the production factory.`), { code: "PRODUCTION_FACTORY_DB_NOT_READY_001", found, required: CURRENT_SCHEMA_VERSION });
|
|
312
|
+
}
|
|
313
|
+
// Full structural verification — migration_version alone is insufficient.
|
|
314
|
+
// A DB claiming migration_version 6 but missing auth_tenant_memberships or auth_grants
|
|
315
|
+
// must fail closed (SQLITE_MIGRATION_CURRENT_SCHEMA_STRUCTURAL_VERIFY_001).
|
|
316
|
+
const structuralCheck = verifyCurrentSchema(config.db);
|
|
317
|
+
if (!structuralCheck.ok) {
|
|
318
|
+
safeLog(logger, {
|
|
319
|
+
level: "error", code: "LOG_SCHEMA_NOT_READY_001",
|
|
320
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
321
|
+
message: "factory readiness failed: schema structural verification failed",
|
|
322
|
+
timestamp: new Date().toISOString(),
|
|
323
|
+
});
|
|
324
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
325
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_DB_NOT_READY_001" });
|
|
326
|
+
throw Object.assign(new Error(`PRODUCTION_FACTORY_DB_NOT_READY_001: The supplied database claims ` +
|
|
327
|
+
`migration_version ${CURRENT_SCHEMA_VERSION} but fails structural verification. ` +
|
|
328
|
+
`Missing tables or columns: ${structuralCheck.missing.join(", ")}. ` +
|
|
329
|
+
`Call runMigrations(db) before passing the database to the production factory.`), {
|
|
330
|
+
code: "PRODUCTION_FACTORY_DB_NOT_READY_001",
|
|
331
|
+
found: String(CURRENT_SCHEMA_VERSION),
|
|
332
|
+
required: CURRENT_SCHEMA_VERSION,
|
|
333
|
+
missing: [...structuralCheck.missing],
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
// Step 9: rawDocumentStore must be present and factory-compatible.
|
|
337
|
+
// RAW_DOCUMENT_STORE_FACTORY_COMPATIBLE_REQUIRED_001
|
|
338
|
+
if (config.rawDocumentStore == null) {
|
|
339
|
+
safeLog(logger, {
|
|
340
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
341
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
342
|
+
message: "factory readiness failed: rawDocumentStore missing",
|
|
343
|
+
timestamp: new Date().toISOString(),
|
|
344
|
+
});
|
|
345
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
346
|
+
status: "failed", errorCode: "RAW_DOCUMENT_STORE_FACTORY_COMPATIBLE_REQUIRED_001" });
|
|
347
|
+
throw Object.assign(new Error("RAW_DOCUMENT_STORE_FACTORY_COMPATIBLE_REQUIRED_001: createSQLiteLifeApiForProduction " +
|
|
348
|
+
"requires a FactoryCompatibleRawDocumentStore. Provide a SQLiteRawDocumentStore or a " +
|
|
349
|
+
"custom implementation that declares factoryCompatible: \"durable-raw-document-store\". " +
|
|
350
|
+
"InMemoryRawDocumentStore and test-only implementations are not accepted."), { code: "RAW_DOCUMENT_STORE_FACTORY_COMPATIBLE_REQUIRED_001" });
|
|
351
|
+
}
|
|
352
|
+
// Step 9.5: validate the durable marker — defense-in-depth against TypeScript casts.
|
|
353
|
+
// RAW_DOCUMENT_STORE_INMEMORY_REJECTED_001
|
|
354
|
+
if (config.rawDocumentStore.factoryCompatible !==
|
|
355
|
+
"durable-raw-document-store") {
|
|
356
|
+
safeLog(logger, {
|
|
357
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
358
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
359
|
+
message: "factory readiness failed: rawDocumentStore not durable-compatible",
|
|
360
|
+
timestamp: new Date().toISOString(),
|
|
361
|
+
});
|
|
362
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
363
|
+
status: "failed", errorCode: "RAW_DOCUMENT_STORE_INMEMORY_REJECTED_001" });
|
|
364
|
+
throw Object.assign(new Error("RAW_DOCUMENT_STORE_INMEMORY_REJECTED_001: the supplied rawDocumentStore does not carry " +
|
|
365
|
+
"the factory-compatible durable marker (factoryCompatible: \"durable-raw-document-store\"). " +
|
|
366
|
+
"InMemoryRawDocumentStore, noop, and test-only implementations are not accepted. " +
|
|
367
|
+
"Supply a SQLiteRawDocumentStore or a custom durable implementation with the marker."), { code: "RAW_DOCUMENT_STORE_INMEMORY_REJECTED_001" });
|
|
368
|
+
}
|
|
369
|
+
// Step 10: auditLog must be present and factory-compatible.
|
|
370
|
+
// AUDIT_LOG_FACTORY_COMPATIBLE_REQUIRED_001
|
|
371
|
+
if (config.auditLog == null) {
|
|
372
|
+
safeLog(logger, {
|
|
373
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
374
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
375
|
+
message: "factory readiness failed: auditLog missing",
|
|
376
|
+
timestamp: new Date().toISOString(),
|
|
377
|
+
});
|
|
378
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
379
|
+
status: "failed", errorCode: "AUDIT_LOG_FACTORY_COMPATIBLE_REQUIRED_001" });
|
|
380
|
+
throw Object.assign(new Error("AUDIT_LOG_FACTORY_COMPATIBLE_REQUIRED_001: createSQLiteLifeApiForProduction " +
|
|
381
|
+
"requires a FactoryCompatibleAuditLog. Provide a SQLiteAuditLog or a custom " +
|
|
382
|
+
"implementation that declares factoryCompatible: \"durable-audit-log\". " +
|
|
383
|
+
"InMemoryAuditLog, noop audit sinks, and test-only implementations are not accepted."), { code: "AUDIT_LOG_FACTORY_COMPATIBLE_REQUIRED_001" });
|
|
384
|
+
}
|
|
385
|
+
// Step 10.5: validate the durable marker — defense-in-depth against TypeScript casts.
|
|
386
|
+
// AUDIT_LOG_INMEMORY_OR_NOOP_REJECTED_001
|
|
387
|
+
if (config.auditLog.factoryCompatible !==
|
|
388
|
+
"durable-audit-log") {
|
|
389
|
+
safeLog(logger, {
|
|
390
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
391
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
392
|
+
message: "factory readiness failed: auditLog not durable-compatible",
|
|
393
|
+
timestamp: new Date().toISOString(),
|
|
394
|
+
});
|
|
395
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
396
|
+
status: "failed", errorCode: "AUDIT_LOG_INMEMORY_OR_NOOP_REJECTED_001" });
|
|
397
|
+
throw Object.assign(new Error("AUDIT_LOG_INMEMORY_OR_NOOP_REJECTED_001: the supplied auditLog does not carry " +
|
|
398
|
+
"the factory-compatible durable marker (factoryCompatible: \"durable-audit-log\"). " +
|
|
399
|
+
"InMemoryAuditLog, noop audit sinks, and test-only implementations are not accepted. " +
|
|
400
|
+
"Supply a SQLiteAuditLog or a custom durable implementation with the marker."), { code: "AUDIT_LOG_INMEMORY_OR_NOOP_REJECTED_001" });
|
|
401
|
+
}
|
|
402
|
+
// Step 11: durable internal ProposalQueue construction proof (Production-06d-c-a).
|
|
403
|
+
//
|
|
404
|
+
// PRODUCTION_FACTORY_ASYNC_DEFERRED_BOUNDARY_001
|
|
405
|
+
// PRODUCTION_FACTORY_PROPOSAL_QUEUE_INTERNAL_CONSTRUCTION_REVIEWED_001
|
|
406
|
+
// PROPOSAL_QUEUE_DURABLE_APPENDER_REQUIRED_001
|
|
407
|
+
// PROPOSAL_QUEUE_NO_INMEMORY_FACTSTORE_APPENDER_001
|
|
408
|
+
// PROPOSAL_QUEUE_NO_NOOP_UNIT_OF_WORK_ASSEMBLY_001
|
|
409
|
+
//
|
|
410
|
+
// All core deps valid (steps 1–10.5). Resolve encryption source and construct crypto boundary.
|
|
411
|
+
// PRODUCTION_FACTORY_NO_SECRET_LOGGING_001: rawBytes is never included in any error.
|
|
412
|
+
// PRODUCTION_FACTORY_JWT_AND_AT_REST_KEYS_SEPARATE_001: resolvedKeyProvider never passed to JWT.
|
|
413
|
+
// PRODUCTION_KMS_ADAPTER_FACTORY_ACCEPTS_KEYPROVIDER_001: external keyProvider used directly.
|
|
414
|
+
// PRODUCTION_KMS_ADAPTER_STATIC_KEY_PATH_UNCHANGED_001: encryptionKey path unchanged.
|
|
415
|
+
let resolvedKeyProvider;
|
|
416
|
+
if (config.keyProvider != null) {
|
|
417
|
+
resolvedKeyProvider = config.keyProvider;
|
|
418
|
+
safeLog(logger, {
|
|
419
|
+
level: "info", code: "LOG_KEY_PROVIDER_WIRED_001",
|
|
420
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
421
|
+
message: "external keyProvider wired — encryptionKey path skipped",
|
|
422
|
+
timestamp: new Date().toISOString(),
|
|
423
|
+
// No key material, no keyId — PRODUCTION_FACTORY_NO_SECRET_LOGGING_001
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
// config.encryptionKey is guaranteed non-null here: validated at step 7/7.5.
|
|
428
|
+
const ek = config.encryptionKey;
|
|
429
|
+
resolvedKeyProvider = await StaticKeyProvider.fromRawBytes(ek.keyId, ek.rawBytes);
|
|
430
|
+
}
|
|
431
|
+
const crypto = new AesGcmCryptoBoundary(resolvedKeyProvider);
|
|
432
|
+
const claimSerializer = new ClaimSerializer(crypto);
|
|
433
|
+
const sqliteFactStore = new SQLiteFactStore(config.db, claimSerializer);
|
|
434
|
+
const sqliteSourceReg = new SQLiteSourceRegistry(config.db);
|
|
435
|
+
const sourceAwareStore = new SourceAwareFactStore(sqliteFactStore, sqliteSourceReg);
|
|
436
|
+
const sqliteUoW = new SQLiteUnitOfWork(config.db);
|
|
437
|
+
const appender = new SourceAwareClaimAppender(sourceAwareStore, sqliteSourceReg, sqliteUoW);
|
|
438
|
+
// C2a: internal audit log for allow-path mutation audit — shares config.db connection.
|
|
439
|
+
// PRODUCTION_AUDIT_INTERNAL_LOG_SHARED_CONNECTION_001: audit_events table guaranteed by
|
|
440
|
+
// schema migration (schema v5+). Passing the Database instance sets _ownsConnection = false,
|
|
441
|
+
// so dispose does not close config.db. Distinct from the caller-supplied config.auditLog.
|
|
442
|
+
const internalMutationAuditLog = new SQLiteAuditLog(config.db);
|
|
443
|
+
// Internal GrantRegistry — in-memory, zero deps; distinct from config.grantResolver.
|
|
444
|
+
const grantRegistry = new GrantRegistry();
|
|
445
|
+
// Open SQLiteProposalQueue with the full durable appender chain.
|
|
446
|
+
// sqliteFactStore, sqliteSourceReg, and sqliteUoW all share config.db — single transaction
|
|
447
|
+
// scope (UNIT_OF_WORK_SQLITE_TRANSACTION_001).
|
|
448
|
+
const queue = await SQLiteProposalQueue.open(config.db, crypto, appender, grantRegistry, sqliteSourceReg);
|
|
449
|
+
// PRODUCTION_FACTORY_PROPOSAL_QUEUE_COMPATIBLE_REQUIRED_001
|
|
450
|
+
// Runtime two-layer defense: TypeScript structural check + runtime marker assertion.
|
|
451
|
+
if (queue.factoryCompatible !==
|
|
452
|
+
"durable-proposal-queue") {
|
|
453
|
+
safeLog(logger, {
|
|
454
|
+
level: "error", code: "LOG_FACTORY_READINESS_FAILED_001",
|
|
455
|
+
component: "SQLiteLifeApiFactory", operation: "createSQLiteLifeApiForProduction",
|
|
456
|
+
message: "factory readiness failed: proposalQueue not durable-compatible",
|
|
457
|
+
timestamp: new Date().toISOString(),
|
|
458
|
+
});
|
|
459
|
+
safeRecord(metricsRecorder, "factory.create.failure", 1, { component: "factory", operation: "createSQLiteLifeApiForProduction",
|
|
460
|
+
status: "failed", errorCode: "PRODUCTION_FACTORY_PROPOSAL_QUEUE_COMPATIBLE_REQUIRED_001" });
|
|
461
|
+
throw Object.assign(new Error("PRODUCTION_FACTORY_PROPOSAL_QUEUE_COMPATIBLE_REQUIRED_001: the constructed " +
|
|
462
|
+
"ProposalQueue does not carry the expected factory-compatible marker " +
|
|
463
|
+
"(factoryCompatible: \"durable-proposal-queue\"). SQLiteProposalQueue must declare " +
|
|
464
|
+
"this marker via FactoryCompatibleProposalQueue."), { code: "PRODUCTION_FACTORY_PROPOSAL_QUEUE_COMPATIBLE_REQUIRED_001" });
|
|
465
|
+
}
|
|
466
|
+
// Platform-02: resolve ProposalPolicy wiring. Explicit config.proposalPolicy wins over
|
|
467
|
+
// the trustedDataSourceRegistry convenience wiring (PROPOSAL_POLICY_EXPLICIT_WINS_OVER_REGISTRY_001).
|
|
468
|
+
// Neither field set → resolvedPolicy is undefined → effectiveQueue === queue, byte-identical
|
|
469
|
+
// to pre-Platform-02 behavior (PROPOSAL_POLICY_OPTIONAL_NO_CHECK_WHEN_ABSENT_001).
|
|
470
|
+
const resolvedPolicy = config.proposalPolicy ??
|
|
471
|
+
(config.trustedDataSourceRegistry != null
|
|
472
|
+
? new RegistryBackedProposalPolicy(config.trustedDataSourceRegistry)
|
|
473
|
+
: undefined);
|
|
474
|
+
const effectiveQueue = resolvedPolicy !== undefined
|
|
475
|
+
? new PolicyAwareProposalQueue(queue, resolvedPolicy, sqliteSourceReg, internalMutationAuditLog)
|
|
476
|
+
: queue;
|
|
477
|
+
// PRODUCTION_FACTORY_BLOCKER_LIST_EMPTY_REVIEWED_001
|
|
478
|
+
// All durable dependency blockers resolved. Proceeding to final assembly.
|
|
479
|
+
//
|
|
480
|
+
// Step 15: clock — config.clock is optional; fall back to system wall clock.
|
|
481
|
+
// PRODUCTION_FACTORY_CLOCK_FALLBACK_SAFE_001
|
|
482
|
+
const clock = config.clock ?? { now: () => new Date().toISOString() };
|
|
483
|
+
// Step 16: EvidenceBackedContextQueryService.
|
|
484
|
+
// PRODUCTION_FACTORY_QUERY_SVC_EMPTY_TRUST_001: StaticTrustTable.empty() — no
|
|
485
|
+
// cross-source trust chains at factory level.
|
|
486
|
+
const querySvc = new EvidenceBackedContextQueryService(sourceAwareStore, // FactStore
|
|
487
|
+
grantRegistry, // GrantRegistry
|
|
488
|
+
config.auditLog, // AuditLog
|
|
489
|
+
sqliteSourceReg, // SourceRegistry
|
|
490
|
+
config.rawDocumentStore, // RawDocumentStore
|
|
491
|
+
StaticTrustTable.empty());
|
|
492
|
+
// Step 17: EvidenceBackedClaimCreator.
|
|
493
|
+
// PRODUCTION_FACTORY_UOW_SHARED_001: sqliteUoW is shared with appender — claim writes
|
|
494
|
+
// and proposal-accept writes share a single SQLite transaction scope.
|
|
495
|
+
const claimCreator = new EvidenceBackedClaimCreator(config.rawDocumentStore, // RawDocumentStore
|
|
496
|
+
sqliteSourceReg, // SourceRegistry
|
|
497
|
+
sourceAwareStore, // FactStore
|
|
498
|
+
sqliteUoW);
|
|
499
|
+
// Step 18: co-create AccessContextIssuer and LifeApiImpl.
|
|
500
|
+
// PRODUCTION_FACTORY_ISSUER_API_PAIRING_001: issuer and rawApi are created together in
|
|
501
|
+
// this factory call. The issuer's WeakSet is private to this invocation — contexts minted
|
|
502
|
+
// by this bundle's auth are accepted by rawApi; all others are rejected.
|
|
503
|
+
// PRODUCTION_FACTORY_RAW_API_NOT_RETURNED_001: rawApi is captured in createScopedApi
|
|
504
|
+
// closure only; it is never returned as a public value.
|
|
505
|
+
const issuer = new AccessContextIssuer();
|
|
506
|
+
const rawApi = new LifeApiImpl(querySvc, // EvidenceBackedContextQueryService
|
|
507
|
+
claimCreator, // EvidenceBackedClaimCreator
|
|
508
|
+
effectiveQueue, // ProposalQueue (SQLiteProposalQueue, optionally policy-wrapped — Platform-02)
|
|
509
|
+
config.rawDocumentStore, // RawDocumentStore
|
|
510
|
+
sqliteSourceReg, // SourceRegistry
|
|
511
|
+
grantRegistry, // GrantRegistry
|
|
512
|
+
issuer, // AccessContextIssuer — paired with BackendContextMintingBoundary below
|
|
513
|
+
sourceAwareStore, // FactStore
|
|
514
|
+
config.auditLog, // AuditLog — general/deny/query audit (caller-supplied)
|
|
515
|
+
internalMutationAuditLog);
|
|
516
|
+
// Step 19: DefaultAuthorizationPolicy + BackendContextMintingBoundary + bundle.
|
|
517
|
+
// BackendContextMintingBoundary is a factory-internal production dependency here.
|
|
518
|
+
// Not exported from src/index.ts; callers receive ContextMintingBoundary only.
|
|
519
|
+
const policy = new DefaultAuthorizationPolicy(clock);
|
|
520
|
+
const auth = new BackendContextMintingBoundary({
|
|
521
|
+
adapter: config.authAdapter,
|
|
522
|
+
policy,
|
|
523
|
+
issuer, // SAME AccessContextIssuer as rawApi — paired
|
|
524
|
+
clock,
|
|
525
|
+
tenantBoundary: config.tenantBoundary,
|
|
526
|
+
grantResolver: config.grantResolver,
|
|
527
|
+
...(config.trustedAgentRegistry != null
|
|
528
|
+
? { trustedAgentRegistry: config.trustedAgentRegistry }
|
|
529
|
+
: {}),
|
|
530
|
+
// Platform-03: guardianRegistry optional; absent = mintGuardianContext throws GUARDIAN_REGISTRY_NOT_CONFIGURED_001.
|
|
531
|
+
// GUARDIAN_REGISTRY_OPTIONAL_NO_CHECK_WHEN_ABSENT_001
|
|
532
|
+
...(config.guardianRegistry != null
|
|
533
|
+
? { guardianRegistry: config.guardianRegistry, guardianAuditLog: internalMutationAuditLog }
|
|
534
|
+
: {}),
|
|
535
|
+
});
|
|
536
|
+
// PRODUCTION_FACTORY_SCOPED_API_TRUST_GATED_001: createScopedApi calls
|
|
537
|
+
// issuer.assertTrusted(mintedCtx.context) before constructing ProductionScopedLifeApiImpl.
|
|
538
|
+
// Trust is enforced at capability creation — not deferred to the first method call.
|
|
539
|
+
// Rejects: manually constructed AccessContext objects; contexts from a different factory
|
|
540
|
+
// invocation's issuer (cross-bundle replay). Accepts: contexts minted by this bundle's auth.
|
|
541
|
+
// PRODUCTION_FACTORY_BUNDLE_COMPLETE_001
|
|
542
|
+
const createScopedApi = (mintedCtx) => {
|
|
543
|
+
issuer.assertTrusted(mintedCtx.context);
|
|
544
|
+
return new ProductionScopedLifeApiImpl(rawApi, mintedCtx, config.auditLog, internalMutationAuditLog, sqliteUoW, logger, metricsRecorder);
|
|
545
|
+
};
|
|
546
|
+
// Step 20: healthCheck closure — D2b local readiness diagnostic.
|
|
547
|
+
//
|
|
548
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_SURFACE_SPECIFIED_001
|
|
549
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_NO_DATA_EXPOSURE_001: no DB path, SQL values,
|
|
550
|
+
// keyId, rawBytes, user data, Error.message, or Error.stack in result or log events.
|
|
551
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_NO_AUDIT_SIDE_EFFECT_001: no audit writes.
|
|
552
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_LOGGER_NON_INTERFERING_001: logger failure is
|
|
553
|
+
// swallowed (safeLog); result is valid regardless of logger state.
|
|
554
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_DOES_NOT_THROW_001: ordinary check failures
|
|
555
|
+
// return { status: "failed" } — never throw to the caller.
|
|
556
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_NO_CONSOLE_001: output only via safeLog.
|
|
557
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_ITEM_NAMES_STABLE_001: seven stable names.
|
|
558
|
+
const healthCheck = async () => {
|
|
559
|
+
const checkedAt = new Date().toISOString();
|
|
560
|
+
const items = [];
|
|
561
|
+
// Inner helper: run one synchronous check; catch failures; emit a sanitized log event.
|
|
562
|
+
// Never throws — always returns a HealthCheckItem.
|
|
563
|
+
const runCheck = (name, okCode, failCode, fn) => {
|
|
564
|
+
try {
|
|
565
|
+
fn();
|
|
566
|
+
safeRecord(metricsRecorder, "health_check.outcome", 1, { component: "healthCheck", operation: "healthCheck", checkName: name, status: "ok" });
|
|
567
|
+
return { name, status: "ok", code: okCode };
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
safeLog(logger, {
|
|
571
|
+
level: "warn",
|
|
572
|
+
code: "LOG_HEALTHCHECK_CHECK_FAILED_001",
|
|
573
|
+
component: "ProductionApiBundle",
|
|
574
|
+
operation: "healthCheck",
|
|
575
|
+
message: "health check failed: " + name,
|
|
576
|
+
timestamp: new Date().toISOString(),
|
|
577
|
+
// No error message or stack — errorName only.
|
|
578
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_NO_DATA_EXPOSURE_001
|
|
579
|
+
...(err instanceof Error ? { errorName: err.name } : {}),
|
|
580
|
+
});
|
|
581
|
+
safeRecord(metricsRecorder, "health_check.outcome", 1, { component: "healthCheck", operation: "healthCheck", checkName: name, status: "failed" });
|
|
582
|
+
return {
|
|
583
|
+
name,
|
|
584
|
+
status: "failed",
|
|
585
|
+
code: failCode,
|
|
586
|
+
// errorName only — no message, no stack, no sensitive data.
|
|
587
|
+
...(err instanceof Error ? { errorName: err.name } : {}),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
// 1. db-connectivity — SELECT 1; no DML, no DDL, no user data.
|
|
592
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_DB_CHECK_STATELESS_001
|
|
593
|
+
items.push(runCheck("db-connectivity", "HEALTHCHECK_DB_CONNECTIVITY_OK_001", "HEALTHCHECK_DB_CONNECTIVITY_FAILED_001", () => { config.db.prepare("SELECT 1").get(); }));
|
|
594
|
+
// 2. schema-version — migration_version === CURRENT_SCHEMA_VERSION.
|
|
595
|
+
items.push(runCheck("schema-version", "HEALTHCHECK_SCHEMA_VERSION_OK_001", "HEALTHCHECK_SCHEMA_VERSION_FAILED_001", () => {
|
|
596
|
+
const row = config.db
|
|
597
|
+
.prepare("SELECT value FROM schema_meta WHERE key = 'migration_version'")
|
|
598
|
+
.get();
|
|
599
|
+
const v = row !== undefined ? parseInt(row.value, 10) : NaN;
|
|
600
|
+
if (!Number.isFinite(v) || v !== CURRENT_SCHEMA_VERSION) {
|
|
601
|
+
throw new Error("HEALTHCHECK_SCHEMA_VERSION_FAILED_001");
|
|
602
|
+
}
|
|
603
|
+
}));
|
|
604
|
+
// 3. schema-structural — verifyCurrentSchema reused; no new verification logic.
|
|
605
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_SCHEMA_USES_EXISTING_VERIFY_001
|
|
606
|
+
items.push(runCheck("schema-structural", "HEALTHCHECK_SCHEMA_STRUCTURAL_OK_001", "HEALTHCHECK_SCHEMA_STRUCTURAL_FAILED_001", () => {
|
|
607
|
+
const structResult = verifyCurrentSchema(config.db);
|
|
608
|
+
if (!structResult.ok) {
|
|
609
|
+
// Do not include missing table names in the item — use a stable code only.
|
|
610
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_NO_DATA_EXPOSURE_001
|
|
611
|
+
throw new Error("HEALTHCHECK_SCHEMA_STRUCTURAL_FAILED_001");
|
|
612
|
+
}
|
|
613
|
+
}));
|
|
614
|
+
// 4. key-provider-loaded — resolved KeyProvider is non-null after factory assembly.
|
|
615
|
+
// Works for both StaticKeyProvider (encryptionKey path) and external keyProvider.
|
|
616
|
+
items.push(runCheck("key-provider-loaded", "HEALTHCHECK_KEY_PROVIDER_LOADED_OK_001", "HEALTHCHECK_KEY_PROVIDER_LOADED_FAILED_001", () => {
|
|
617
|
+
if (resolvedKeyProvider == null) {
|
|
618
|
+
throw new Error("HEALTHCHECK_KEY_PROVIDER_LOADED_FAILED_001");
|
|
619
|
+
}
|
|
620
|
+
}));
|
|
621
|
+
// 5–7. Opaque adapter reference checks — non-null only; no functional probe.
|
|
622
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_OPAQUE_ADAPTERS_STRUCTURAL_ONLY_001
|
|
623
|
+
items.push(runCheck("auth-adapter-present", "HEALTHCHECK_AUTH_ADAPTER_PRESENT_OK_001", "HEALTHCHECK_AUTH_ADAPTER_PRESENT_FAILED_001", () => {
|
|
624
|
+
if (config.authAdapter == null) {
|
|
625
|
+
throw new Error("HEALTHCHECK_AUTH_ADAPTER_PRESENT_FAILED_001");
|
|
626
|
+
}
|
|
627
|
+
}));
|
|
628
|
+
items.push(runCheck("tenant-boundary-present", "HEALTHCHECK_TENANT_BOUNDARY_PRESENT_OK_001", "HEALTHCHECK_TENANT_BOUNDARY_PRESENT_FAILED_001", () => {
|
|
629
|
+
if (config.tenantBoundary == null) {
|
|
630
|
+
throw new Error("HEALTHCHECK_TENANT_BOUNDARY_PRESENT_FAILED_001");
|
|
631
|
+
}
|
|
632
|
+
}));
|
|
633
|
+
items.push(runCheck("grant-resolver-present", "HEALTHCHECK_GRANT_RESOLVER_PRESENT_OK_001", "HEALTHCHECK_GRANT_RESOLVER_PRESENT_FAILED_001", () => {
|
|
634
|
+
if (config.grantResolver == null) {
|
|
635
|
+
throw new Error("HEALTHCHECK_GRANT_RESOLVER_PRESENT_FAILED_001");
|
|
636
|
+
}
|
|
637
|
+
}));
|
|
638
|
+
// Aggregate status — worst-case of all items.
|
|
639
|
+
// PRODUCTION_OBSERVABILITY_HEALTHCHECK_STATUS_AGGREGATE_001
|
|
640
|
+
let status = "ok";
|
|
641
|
+
for (const item of items) {
|
|
642
|
+
if (item.status === "failed") {
|
|
643
|
+
status = "failed";
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
if (item.status === "degraded") {
|
|
647
|
+
status = "degraded";
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return { status, checkedAt, checks: items };
|
|
651
|
+
};
|
|
652
|
+
return { auth, createScopedApi, healthCheck };
|
|
653
|
+
}
|
|
654
|
+
//# sourceMappingURL=create-sqlite-life-api.js.map
|