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 @@
|
|
|
1
|
+
{"version":3,"file":"legal-agent.js","sourceRoot":"","sources":["../../src/agent/legal-agent.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,sFAAsF;AACtF,qFAAqF;AACrF,oFAAoF;AACpF,yBAAyB;AACzB,sEAAsE;AACtE,yFAAyF;AACzF,yFAAyF;AACzF,iFAAiF;AACjF,8EAA8E;AAC9E,kFAAkF;AAClF,0FAA0F;AAC1F,mBAAmB;AACnB,uFAAuF;AACvF,wFAAwF;AACxF,kEAAkE;AAMlE,MAAM,CAAC,MAAM,cAAc,GAAG,4BAA4B,CAAC;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAE3C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,QAAQ,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,cAAc,CAAU,CAAC;AAGtH,mFAAmF;AACnF,4EAA4E;AAC5E,MAAM,sCAAsC,GAC1C,oGAAoG,CAAC;AAEvG;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,YAAoB;IAC1D,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,IAAI,EAAE,qCAAqC;QAC3C,WAAW,EAAE,CAAC,GAAG,sBAAsB,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,CAAC;QACzF,WAAW,EAAE,wBAAwB;QACrC,UAAU,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE;QAChD,YAAY,EAAE,sCAAsC;KACrD,CAAC;AACJ,CAAC;AAgBD;;;GAGG;AACH,MAAM,OAAO,UAAU;IACQ;IAA7B,YAA6B,IAAuB;QAAvB,SAAI,GAAJ,IAAI,CAAmB;IAAG,CAAC;IAExD;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,CAAC,GAAkB,EAAE,KAA0B;QACtD,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC;QACpC,MAAM,OAAO,GACX,OAAO,KAAK,SAAS;YACpB,sBAAgD,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAEtE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACzB,OAAO,EAAE,cAAc;YACvB,UAAU,EAAE,qBAAqB;YACjC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;YACxC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CACP,6EAA6E;gBAC7E,iFAAiF,OAAO,IAAI,WAAW,GAAG,CAC3G,EACD,EAAE,IAAI,EAAE,8BAA8B,EAAE,CACzC,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Clock, MintedContext } from "../auth/types.js";
|
|
2
|
+
import type { QueryContractsInput, ContextQueryResult } from "../surface/evidence-backed-context-query.js";
|
|
3
|
+
import type { TrustedAgentRegistryEntry } from "./trusted-agent-registry.js";
|
|
4
|
+
export declare const MEDICAL_AGENT_ID = "life-as-api/medical-agent-v1";
|
|
5
|
+
export declare const MEDICAL_AGENT_VERSION = "1.0.0";
|
|
6
|
+
export declare const ALLOWED_MEDICAL_PURPOSES: readonly ["diagnosis", "treatment", "emergency"];
|
|
7
|
+
export type MedicalPurpose = typeof ALLOWED_MEDICAL_PURPOSES[number];
|
|
8
|
+
/**
|
|
9
|
+
* Builds the TrustedAgentRegistryEntry for the certified Medical Agent.
|
|
10
|
+
* MEDICAL_AGENT_REGISTRY_ENTRY_001: certifiedBy is always "life-as-api-foundation".
|
|
11
|
+
* Permissions include every allowed purpose (the minting boundary passes
|
|
12
|
+
* decision.purpose as the scope argument to isAgentTrusted) plus declarative
|
|
13
|
+
* read:* scopes for operator documentation.
|
|
14
|
+
*/
|
|
15
|
+
export declare function medicalAgentRegistryEntry(validUntilMs: number): TrustedAgentRegistryEntry;
|
|
16
|
+
export interface MedicalAgentAuditEvent {
|
|
17
|
+
readonly at: string;
|
|
18
|
+
readonly agentId: string;
|
|
19
|
+
readonly reasonCode: "MEDICAL_AGENT_RUN_001";
|
|
20
|
+
readonly decision: "allowed" | "denied";
|
|
21
|
+
readonly declaredPurpose?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface MedicalAgentOptions {
|
|
24
|
+
readonly query: (ctx: MintedContext, input: QueryContractsInput) => Promise<ContextQueryResult>;
|
|
25
|
+
readonly clock: Clock;
|
|
26
|
+
readonly onAudit?: (event: MedicalAgentAuditEvent) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Certified first-party Medical Agent.
|
|
30
|
+
* MEDICAL_AGENT_READ_ONLY_001: query-capability only, no propose/write capability.
|
|
31
|
+
*/
|
|
32
|
+
export declare class MedicalAgent {
|
|
33
|
+
private readonly opts;
|
|
34
|
+
constructor(opts: MedicalAgentOptions);
|
|
35
|
+
/**
|
|
36
|
+
* MEDICAL_AGENT_SCOPE_ENFORCED_001 / MEDICAL_AGENT_PURPOSE_BOUND_001: run() only
|
|
37
|
+
* delegates to the configured query function when ctx.declaredPurpose is one of
|
|
38
|
+
* ALLOWED_MEDICAL_PURPOSES. Any other value, or an absent declaredPurpose, denies
|
|
39
|
+
* fail-closed before the query function is ever invoked.
|
|
40
|
+
* MEDICAL_AGENT_AUDIT_TRAIL_001: the onAudit hook (if configured) always fires,
|
|
41
|
+
* for both the allowed and denied outcome.
|
|
42
|
+
*/
|
|
43
|
+
run(ctx: MintedContext, query: QueryContractsInput): Promise<ContextQueryResult>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Platform-05 — Certified First-Party Agents: Medical Agent.
|
|
2
|
+
//
|
|
3
|
+
// MEDICAL_AGENT_PURPOSE_BOUND_001: this agent acts ONLY when the caller's MintedContext
|
|
4
|
+
// carries declaredPurpose "diagnosis" | "treatment" | "emergency". Any other declared
|
|
5
|
+
// purpose (or none) is denied before the query function is invoked.
|
|
6
|
+
// MEDICAL_AGENT_REGISTRY_ENTRY_001: medicalAgentRegistryEntry() returns a
|
|
7
|
+
// TrustedAgentRegistryEntry certifiedBy "life-as-api-foundation" so hosts can register
|
|
8
|
+
// this agent with a TrustedAgentRegistry (Platform-01) without hand-rolling the entry.
|
|
9
|
+
// MEDICAL_AGENT_SCOPE_ENFORCED_001: run() throws MEDICAL_AGENT_SCOPE_DENIED_001 when
|
|
10
|
+
// declaredPurpose is absent or not in ALLOWED_MEDICAL_PURPOSES — fail-closed.
|
|
11
|
+
// MEDICAL_AGENT_READ_ONLY_001: the agent has query-capability only. The constructor
|
|
12
|
+
// accepts exclusively a `query` function; there is no propose/write capability anywhere
|
|
13
|
+
// on this class.
|
|
14
|
+
// MEDICAL_AGENT_AUDIT_TRAIL_001: every run() call invokes the optional onAudit hook with
|
|
15
|
+
// reasonCode "MEDICAL_AGENT_RUN_001" (allowed or denied). Actual audit persistence is a
|
|
16
|
+
// host responsibility — this hook is a notification point only.
|
|
17
|
+
export const MEDICAL_AGENT_ID = "life-as-api/medical-agent-v1";
|
|
18
|
+
export const MEDICAL_AGENT_VERSION = "1.0.0";
|
|
19
|
+
export const ALLOWED_MEDICAL_PURPOSES = ["diagnosis", "treatment", "emergency"];
|
|
20
|
+
// TRUSTED_AGENT_REGISTRY_PUBKEY_ENFORCEMENT_DEFERRED_001: placeholder public key —
|
|
21
|
+
// cryptographic signature verification at mint time is not yet implemented.
|
|
22
|
+
const MEDICAL_AGENT_PUBLIC_KEY_PEM_PLACEHOLDER = "-----BEGIN PUBLIC KEY-----\nPLACEHOLDER-MEDICAL-AGENT-KEY-NOT-YET-ENFORCED\n-----END PUBLIC KEY-----";
|
|
23
|
+
/**
|
|
24
|
+
* Builds the TrustedAgentRegistryEntry for the certified Medical Agent.
|
|
25
|
+
* MEDICAL_AGENT_REGISTRY_ENTRY_001: certifiedBy is always "life-as-api-foundation".
|
|
26
|
+
* Permissions include every allowed purpose (the minting boundary passes
|
|
27
|
+
* decision.purpose as the scope argument to isAgentTrusted) plus declarative
|
|
28
|
+
* read:* scopes for operator documentation.
|
|
29
|
+
*/
|
|
30
|
+
export function medicalAgentRegistryEntry(validUntilMs) {
|
|
31
|
+
return {
|
|
32
|
+
agentId: MEDICAL_AGENT_ID,
|
|
33
|
+
name: "Certified Medical Agent (first-party)",
|
|
34
|
+
permissions: [...ALLOWED_MEDICAL_PURPOSES, "read:health", "read:diagnosis", "read:treatment"],
|
|
35
|
+
certifiedBy: "life-as-api-foundation",
|
|
36
|
+
validUntil: new Date(validUntilMs).toISOString(),
|
|
37
|
+
publicKeyPem: MEDICAL_AGENT_PUBLIC_KEY_PEM_PLACEHOLDER,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Certified first-party Medical Agent.
|
|
42
|
+
* MEDICAL_AGENT_READ_ONLY_001: query-capability only, no propose/write capability.
|
|
43
|
+
*/
|
|
44
|
+
export class MedicalAgent {
|
|
45
|
+
opts;
|
|
46
|
+
constructor(opts) {
|
|
47
|
+
this.opts = opts;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* MEDICAL_AGENT_SCOPE_ENFORCED_001 / MEDICAL_AGENT_PURPOSE_BOUND_001: run() only
|
|
51
|
+
* delegates to the configured query function when ctx.declaredPurpose is one of
|
|
52
|
+
* ALLOWED_MEDICAL_PURPOSES. Any other value, or an absent declaredPurpose, denies
|
|
53
|
+
* fail-closed before the query function is ever invoked.
|
|
54
|
+
* MEDICAL_AGENT_AUDIT_TRAIL_001: the onAudit hook (if configured) always fires,
|
|
55
|
+
* for both the allowed and denied outcome.
|
|
56
|
+
*/
|
|
57
|
+
async run(ctx, query) {
|
|
58
|
+
const purpose = ctx.declaredPurpose;
|
|
59
|
+
const allowed = purpose !== undefined &&
|
|
60
|
+
ALLOWED_MEDICAL_PURPOSES.includes(purpose);
|
|
61
|
+
this.opts.onAudit?.({
|
|
62
|
+
at: this.opts.clock.now(),
|
|
63
|
+
agentId: MEDICAL_AGENT_ID,
|
|
64
|
+
reasonCode: "MEDICAL_AGENT_RUN_001",
|
|
65
|
+
decision: allowed ? "allowed" : "denied",
|
|
66
|
+
...(purpose !== undefined ? { declaredPurpose: purpose } : {}),
|
|
67
|
+
});
|
|
68
|
+
if (!allowed) {
|
|
69
|
+
throw Object.assign(new Error("MEDICAL_AGENT_SCOPE_DENIED_001: MedicalAgent may only run with declaredPurpose " +
|
|
70
|
+
`"diagnosis" | "treatment" | "emergency" (got: ${purpose ?? "undefined"})`), { code: "MEDICAL_AGENT_SCOPE_DENIED_001" });
|
|
71
|
+
}
|
|
72
|
+
return this.opts.query(ctx, query);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=medical-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"medical-agent.js","sourceRoot":"","sources":["../../src/agent/medical-agent.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,wFAAwF;AACxF,wFAAwF;AACxF,sEAAsE;AACtE,0EAA0E;AAC1E,yFAAyF;AACzF,yFAAyF;AACzF,qFAAqF;AACrF,gFAAgF;AAChF,oFAAoF;AACpF,0FAA0F;AAC1F,mBAAmB;AACnB,yFAAyF;AACzF,0FAA0F;AAC1F,kEAAkE;AAMlE,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAC;AAC/D,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAE7C,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,CAAU,CAAC;AAGzF,mFAAmF;AACnF,4EAA4E;AAC5E,MAAM,wCAAwC,GAC5C,sGAAsG,CAAC;AAEzG;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,YAAoB;IAC5D,OAAO;QACL,OAAO,EAAE,gBAAgB;QACzB,IAAI,EAAE,uCAAuC;QAC7C,WAAW,EAAE,CAAC,GAAG,wBAAwB,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,CAAC;QAC7F,WAAW,EAAE,wBAAwB;QACrC,UAAU,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE;QAChD,YAAY,EAAE,wCAAwC;KACvD,CAAC;AACJ,CAAC;AAgBD;;;GAGG;AACH,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,IAAyB;QAAzB,SAAI,GAAJ,IAAI,CAAqB;IAAG,CAAC;IAE1D;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,CAAC,GAAkB,EAAE,KAA0B;QACtD,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC;QACpC,MAAM,OAAO,GACX,OAAO,KAAK,SAAS;YACpB,wBAAkD,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAExE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACzB,OAAO,EAAE,gBAAgB;YACzB,UAAU,EAAE,uBAAuB;YACnC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;YACxC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,MAAM,CACjB,IAAI,KAAK,CACP,iFAAiF;gBACjF,iDAAiD,OAAO,IAAI,WAAW,GAAG,CAC3E,EACD,EAAE,IAAI,EAAE,gCAAgC,EAAE,CAC3C,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** A registry record for a certified agent. */
|
|
2
|
+
export interface TrustedAgentRegistryEntry {
|
|
3
|
+
/** Stable agent identifier — matches VerifiedPrincipal.principalId for agent principals. */
|
|
4
|
+
readonly agentId: string;
|
|
5
|
+
/** Human-readable name for operator reference. */
|
|
6
|
+
readonly name: string;
|
|
7
|
+
/**
|
|
8
|
+
* Scope strings this agent is certified for. Vocabulary is host-defined.
|
|
9
|
+
* The minting boundary passes decision.purpose (a PurposeTag) as the scope argument
|
|
10
|
+
* to isAgentTrusted; entries should include the relevant PurposeTag values.
|
|
11
|
+
* Fine-grained data scopes (e.g. "read:emergency") belong in ConsentGrant.scope
|
|
12
|
+
* (predicate allow-list) and may additionally be declared here for documentation.
|
|
13
|
+
*/
|
|
14
|
+
readonly permissions: ReadonlyArray<string>;
|
|
15
|
+
/** ID of the certifying authority (institution, CA, operator). */
|
|
16
|
+
readonly certifiedBy: string;
|
|
17
|
+
/** ISO-8601 instant after which this certification is no longer valid. */
|
|
18
|
+
readonly validUntil: string;
|
|
19
|
+
/**
|
|
20
|
+
* PEM public key for cryptographic agent identity proof (challenge-response).
|
|
21
|
+
* Declared now; enforcement (signature verification at mint time) is deferred.
|
|
22
|
+
* TRUSTED_AGENT_REGISTRY_PUBKEY_ENFORCEMENT_DEFERRED_001
|
|
23
|
+
*/
|
|
24
|
+
readonly publicKeyPem: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Trust gate for agent-context minting.
|
|
28
|
+
* TRUSTED_AGENT_REGISTRY_GATES_GRANT_001: configured → checked before every AgentConsentContext.
|
|
29
|
+
* Implementations must be fail-closed: any exception → treated as untrusted.
|
|
30
|
+
*/
|
|
31
|
+
export interface TrustedAgentRegistry {
|
|
32
|
+
/** Returns true iff agentId is registered, not expired, and certified for scope. */
|
|
33
|
+
isAgentTrusted(agentId: string, scope: string): Promise<boolean>;
|
|
34
|
+
}
|
|
35
|
+
/** Development / test implementation of TrustedAgentRegistry. */
|
|
36
|
+
export declare class InMemoryTrustedAgentRegistry implements TrustedAgentRegistry {
|
|
37
|
+
private readonly _entries;
|
|
38
|
+
private readonly _now;
|
|
39
|
+
constructor(opts?: {
|
|
40
|
+
readonly now?: () => string;
|
|
41
|
+
});
|
|
42
|
+
/** Register or replace an entry. Re-registering the same agentId overwrites. */
|
|
43
|
+
register(entry: TrustedAgentRegistryEntry): this;
|
|
44
|
+
/**
|
|
45
|
+
* TRUSTED_AGENT_REGISTRY_FAIL_CLOSED_001:
|
|
46
|
+
* - Unknown agentId → false
|
|
47
|
+
* - validUntil unparseable → false
|
|
48
|
+
* - validUntil instant <= now instant (epoch-ms comparison) → false
|
|
49
|
+
* - scope not in permissions → false
|
|
50
|
+
* - Never throws; caller catches and treats exception as untrusted anyway.
|
|
51
|
+
*/
|
|
52
|
+
isAgentTrusted(agentId: string, scope: string): Promise<boolean>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Platform-01 — Trusted Agent Registry.
|
|
2
|
+
//
|
|
3
|
+
// TRUSTED_AGENT_REGISTRY_GATES_GRANT_001: When a TrustedAgentRegistry is configured in
|
|
4
|
+
// the minting boundary, no AgentConsentContext is minted without isAgentTrusted(agentId,
|
|
5
|
+
// scope) returning true. Fail-closed: throws or unknown → denied.
|
|
6
|
+
// AGENT_SCOPE_CHECKED_BEFORE_DATA_ACCESS_001: The registry scope-check fires in the
|
|
7
|
+
// minting boundary BEFORE issuer.mintAgent — no context exists that could access data
|
|
8
|
+
// if the check fails.
|
|
9
|
+
// TRUSTED_AGENT_REGISTRY_FAIL_CLOSED_001: Any registry exception, unknown agentId,
|
|
10
|
+
// unrecognised scope, or expired certification → deny. Never fail-open.
|
|
11
|
+
// TRUSTED_AGENT_REGISTRY_OPTIONAL_NO_CHECK_WHEN_ABSENT_001: Without a configured
|
|
12
|
+
// registry the agent-mint path is unchanged (backward-compatible).
|
|
13
|
+
// TRUSTED_AGENT_REGISTRY_INTERFACE_AGENT_MODULE_ONLY_001: This interface is defined
|
|
14
|
+
// exclusively in src/agent/; auth/ imports type-only; no implementation is imported
|
|
15
|
+
// by production src code other than the re-export in src/index.ts.
|
|
16
|
+
// ─── InMemory implementation ─────────────────────────────────────────────────────
|
|
17
|
+
/** Development / test implementation of TrustedAgentRegistry. */
|
|
18
|
+
export class InMemoryTrustedAgentRegistry {
|
|
19
|
+
_entries = new Map();
|
|
20
|
+
_now;
|
|
21
|
+
constructor(opts) {
|
|
22
|
+
this._now = opts?.now ?? (() => new Date().toISOString());
|
|
23
|
+
}
|
|
24
|
+
/** Register or replace an entry. Re-registering the same agentId overwrites. */
|
|
25
|
+
register(entry) {
|
|
26
|
+
this._entries.set(entry.agentId, entry);
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* TRUSTED_AGENT_REGISTRY_FAIL_CLOSED_001:
|
|
31
|
+
* - Unknown agentId → false
|
|
32
|
+
* - validUntil unparseable → false
|
|
33
|
+
* - validUntil instant <= now instant (epoch-ms comparison) → false
|
|
34
|
+
* - scope not in permissions → false
|
|
35
|
+
* - Never throws; caller catches and treats exception as untrusted anyway.
|
|
36
|
+
*/
|
|
37
|
+
async isAgentTrusted(agentId, scope) {
|
|
38
|
+
const entry = this._entries.get(agentId);
|
|
39
|
+
if (entry === undefined)
|
|
40
|
+
return false;
|
|
41
|
+
// Expiry check via epoch-ms — handles UTC offsets correctly, not lexicographic.
|
|
42
|
+
const validUntilMs = Date.parse(entry.validUntil);
|
|
43
|
+
if (Number.isNaN(validUntilMs))
|
|
44
|
+
return false;
|
|
45
|
+
const nowMs = Date.parse(this._now());
|
|
46
|
+
if (Number.isNaN(nowMs))
|
|
47
|
+
return false;
|
|
48
|
+
// Expired: validUntil instant is in the past or exactly now.
|
|
49
|
+
if (validUntilMs <= nowMs)
|
|
50
|
+
return false;
|
|
51
|
+
// Scope check: exact match in permissions array.
|
|
52
|
+
if (!entry.permissions.includes(scope))
|
|
53
|
+
return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=trusted-agent-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trusted-agent-registry.js","sourceRoot":"","sources":["../../src/agent/trusted-agent-registry.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,uFAAuF;AACvF,2FAA2F;AAC3F,oEAAoE;AACpE,oFAAoF;AACpF,wFAAwF;AACxF,wBAAwB;AACxB,mFAAmF;AACnF,0EAA0E;AAC1E,iFAAiF;AACjF,qEAAqE;AACrE,oFAAoF;AACpF,sFAAsF;AACtF,qEAAqE;AA0CrE,oFAAoF;AAEpF,iEAAiE;AACjE,MAAM,OAAO,4BAA4B;IACtB,QAAQ,GAAG,IAAI,GAAG,EAAqC,CAAC;IACxD,IAAI,CAAe;IAEpC,YAAY,IAAsC;QAChD,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,gFAAgF;IAChF,QAAQ,CAAC,KAAgC;QACvC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,cAAc,CAAC,OAAe,EAAE,KAAa;QACjD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAEtC,gFAAgF;QAChF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC;YAAE,OAAO,KAAK,CAAC;QAE7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAEtC,6DAA6D;QAC7D,IAAI,YAAY,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QAExC,iDAAiD;QACjD,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAErD,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Database } from "better-sqlite3";
|
|
2
|
+
export type AuditChainInvalidReason = "sequence_gap" | "event_hash_mismatch" | "prev_hash_mismatch" | "event_hash_and_prev_hash_mismatch";
|
|
3
|
+
export type AuditChainVerifyResult = {
|
|
4
|
+
readonly valid: true;
|
|
5
|
+
} | {
|
|
6
|
+
readonly valid: false;
|
|
7
|
+
readonly firstInvalidSequence: number;
|
|
8
|
+
readonly reason: AuditChainInvalidReason;
|
|
9
|
+
};
|
|
10
|
+
export declare function verifyAuditChain(db: Database): AuditChainVerifyResult;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Audit chain verifier — Audit Hardening C4 (Local Hash Chain).
|
|
2
|
+
//
|
|
3
|
+
// PRODUCTION_AUDIT_C4_VERIFIER_SANITIZED_001: result carries only `valid`, an integer
|
|
4
|
+
// `firstInvalidSequence`, and a closed-enum `reason`. NO event payload, actor, selector,
|
|
5
|
+
// reason_code values, IDs, hash values, Error.message, or stack trace ever appear.
|
|
6
|
+
// PRODUCTION_AUDIT_C4_LOCAL_ONLY_NOT_COURT_GRADE_001: this is a lightweight LOCAL consistency
|
|
7
|
+
// check. It detects row deletion, reordering, and naive field modification after the fact.
|
|
8
|
+
// It is a local-only integrity check, NOT external tamper evidence — a privileged attacker
|
|
9
|
+
// with DB write access and SHA-256 can reconstruct a valid chain.
|
|
10
|
+
//
|
|
11
|
+
// Internal utility (Q1): named export from this module ONLY. NOT root-exported from
|
|
12
|
+
// src/index.ts, NOT on ProductionApiBundle / ProductionScopedLifeApi, NOT wired into the
|
|
13
|
+
// bundle's health-check path (offline operator use only).
|
|
14
|
+
import { sha256hexSync } from "../crypto/sha256.js";
|
|
15
|
+
import { buildChainCanonical } from "./sqlite-audit-log.js";
|
|
16
|
+
export function verifyAuditChain(db) {
|
|
17
|
+
const rows = db
|
|
18
|
+
.prepare(`SELECT sequence, prev_hash, event_hash,
|
|
19
|
+
id, recorded_at, actor, grant_id, purpose, decision, reason_code,
|
|
20
|
+
selector_fingerprint, result_count, result_claim_ids, contract_ids
|
|
21
|
+
FROM audit_events ORDER BY sequence ASC`)
|
|
22
|
+
.all();
|
|
23
|
+
if (rows.length === 0)
|
|
24
|
+
return { valid: true };
|
|
25
|
+
for (let i = 0; i < rows.length; i++) {
|
|
26
|
+
const row = rows[i];
|
|
27
|
+
const expectedSeq = i + 1;
|
|
28
|
+
// Sequence continuity (1-indexed, no gaps). Catches deletion / insertion / reorder.
|
|
29
|
+
if (row.sequence !== expectedSeq) {
|
|
30
|
+
return { valid: false, firstInvalidSequence: row.sequence, reason: "sequence_gap" };
|
|
31
|
+
}
|
|
32
|
+
const canonical = buildChainCanonical({
|
|
33
|
+
actor: row.actor,
|
|
34
|
+
contract_ids: row.contract_ids,
|
|
35
|
+
decision: row.decision,
|
|
36
|
+
grant_id: row.grant_id,
|
|
37
|
+
id: row.id,
|
|
38
|
+
prev_hash: row.prev_hash,
|
|
39
|
+
purpose: row.purpose,
|
|
40
|
+
reason_code: row.reason_code,
|
|
41
|
+
recorded_at: row.recorded_at,
|
|
42
|
+
result_claim_ids: row.result_claim_ids,
|
|
43
|
+
result_count: row.result_count,
|
|
44
|
+
selector_fingerprint: row.selector_fingerprint,
|
|
45
|
+
sequence: row.sequence,
|
|
46
|
+
});
|
|
47
|
+
const recomputedHash = sha256hexSync(canonical);
|
|
48
|
+
const hashOk = recomputedHash === row.event_hash;
|
|
49
|
+
// Genesis (i === 0): prev_hash must be null. Otherwise: link to prior row's event_hash.
|
|
50
|
+
const linkOk = i === 0 ? row.prev_hash === null : row.prev_hash === rows[i - 1].event_hash;
|
|
51
|
+
if (!hashOk && !linkOk) {
|
|
52
|
+
return {
|
|
53
|
+
valid: false,
|
|
54
|
+
firstInvalidSequence: row.sequence,
|
|
55
|
+
reason: "event_hash_and_prev_hash_mismatch",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!hashOk) {
|
|
59
|
+
return { valid: false, firstInvalidSequence: row.sequence, reason: "event_hash_mismatch" };
|
|
60
|
+
}
|
|
61
|
+
if (!linkOk) {
|
|
62
|
+
return { valid: false, firstInvalidSequence: row.sequence, reason: "prev_hash_mismatch" };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { valid: true };
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=audit-chain-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-chain-verifier.js","sourceRoot":"","sources":["../../src/audit/audit-chain-verifier.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,sFAAsF;AACtF,2FAA2F;AAC3F,qFAAqF;AACrF,8FAA8F;AAC9F,6FAA6F;AAC7F,6FAA6F;AAC7F,oEAAoE;AACpE,EAAE;AACF,oFAAoF;AACpF,yFAAyF;AACzF,0DAA0D;AAG1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAiC5D,MAAM,UAAU,gBAAgB,CAAC,EAAY;IAC3C,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;;;iDAG2C,CAC5C;SACA,GAAG,EAAgB,CAAC;IAEvB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAE9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACrB,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC;QAE1B,oFAAoF;QACpF,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;YACjC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;QACtF,CAAC;QAED,MAAM,SAAS,GAAG,mBAAmB,CAAC;YACpC,KAAK,EAAiB,GAAG,CAAC,KAAK;YAC/B,YAAY,EAAU,GAAG,CAAC,YAAY;YACtC,QAAQ,EAAc,GAAG,CAAC,QAA4B;YACtD,QAAQ,EAAc,GAAG,CAAC,QAAQ;YAClC,EAAE,EAAoB,GAAG,CAAC,EAAE;YAC5B,SAAS,EAAa,GAAG,CAAC,SAAS;YACnC,OAAO,EAAe,GAAG,CAAC,OAAO;YACjC,WAAW,EAAW,GAAG,CAAC,WAAW;YACrC,WAAW,EAAW,GAAG,CAAC,WAAW;YACrC,gBAAgB,EAAM,GAAG,CAAC,gBAAgB;YAC1C,YAAY,EAAU,GAAG,CAAC,YAAY;YACtC,oBAAoB,EAAE,GAAG,CAAC,oBAAoB;YAC9C,QAAQ,EAAc,GAAG,CAAC,QAAQ;SACnC,CAAC,CAAC;QACH,MAAM,cAAc,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;QAEhD,MAAM,MAAM,GAAG,cAAc,KAAK,GAAG,CAAC,UAAU,CAAC;QACjD,wFAAwF;QACxF,MAAM,MAAM,GACV,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,UAAU,CAAC;QAE/E,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;YACvB,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,oBAAoB,EAAE,GAAG,CAAC,QAAQ;gBAClC,MAAM,EAAE,mCAAmC;aAC5C,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QAC7F,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;QAC5F,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AuditId, ClaimId, ContractId, GrantId, ISOTime } from "../types/ids.js";
|
|
2
|
+
import type { PurposeTag } from "../types/access-context.js";
|
|
3
|
+
import type { ConsentDenyReason } from "../types/consent.js";
|
|
4
|
+
export type AuditReasonCode = "ALLOWED" | "GRANT_NOT_FOUND" | "GRANT_EXPIRED" | "GRANT_NOT_YET_ACTIVE" | "GRANT_REVOKED" | "PURPOSE_MISMATCH" | "CAPABILITY_NOT_GRANTED" | "SCOPE_INTERSECTION_EMPTY" | "SUBJECT_NOT_GRANTED" | "PROPOSAL_ALLOWED" | "PROPOSAL_CROSS_SUBJECT_DENIED" | "PROPOSAL_BROAD_GRANT_DENIED" | "PROPOSAL_UNSUPPORTED_CONTEXT_DENIED" | "PROPOSAL_MULTI_SUBJECT_DENIED" | "PROPOSAL_EMPTY_CANDIDATES_DENIED" | "PROPOSAL_NOT_FOUND" | "ERASURE_AGENT_DENIED" | "ERASURE_CROSS_SUBJECT_DENIED" | "PROPOSAL_READ_ALLOWED" | "PROPOSAL_READ_SAFE_NOT_FOUND" | "PROPOSAL_READ_AGENT_DENIED" | "PROPOSAL_READ_UNSUPPORTED_CONTEXT_DENIED" | "ERASURE_STATUS_ALLOWED" | "PROPOSAL_AUTO_ACCEPTED_BY_POLICY" | "PROPOSAL_AUTO_REJECTED_BY_POLICY" | "PROPOSAL_AUTO_ACCEPT_FAILED_MANUAL_FALLBACK" | "GUARDIAN_PROPOSAL_ACCEPTED" | "GUARDIAN_GRANT_CREATED" | "GUARDIAN_ACTION_UNAUTHORIZED";
|
|
5
|
+
export declare function denyReasonToAuditCode(r: ConsentDenyReason): AuditReasonCode;
|
|
6
|
+
export interface AuditEvent {
|
|
7
|
+
readonly id: AuditId;
|
|
8
|
+
readonly recordedAt: ISOTime;
|
|
9
|
+
readonly actor: string;
|
|
10
|
+
readonly grantId?: GrantId;
|
|
11
|
+
readonly purpose?: PurposeTag;
|
|
12
|
+
readonly decision: "allow" | "deny";
|
|
13
|
+
readonly reasonCode: AuditReasonCode;
|
|
14
|
+
readonly selectorFingerprint: string;
|
|
15
|
+
readonly resultCount: number;
|
|
16
|
+
readonly resultClaimIds: ReadonlyArray<ClaimId>;
|
|
17
|
+
readonly contractIds?: ReadonlyArray<ContractId>;
|
|
18
|
+
readonly actingAsGuardian?: true;
|
|
19
|
+
readonly wardId?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AuditLog {
|
|
22
|
+
write(event: AuditEvent): Promise<void>;
|
|
23
|
+
readAll(): ReadonlyArray<AuditEvent>;
|
|
24
|
+
}
|
|
25
|
+
export declare class InMemoryAuditLog implements AuditLog {
|
|
26
|
+
private readonly _events;
|
|
27
|
+
private _failOnWrite;
|
|
28
|
+
simulateWriteFailure(fail: boolean): void;
|
|
29
|
+
write(event: AuditEvent): Promise<void>;
|
|
30
|
+
readAll(): ReadonlyArray<AuditEvent>;
|
|
31
|
+
}
|
|
32
|
+
export declare function buildAuditEvent(params: {
|
|
33
|
+
actor: string;
|
|
34
|
+
grantId?: GrantId;
|
|
35
|
+
purpose?: PurposeTag;
|
|
36
|
+
decision: "allow" | "deny";
|
|
37
|
+
reasonCode: AuditReasonCode;
|
|
38
|
+
selectorFingerprint: string;
|
|
39
|
+
resultCount: number;
|
|
40
|
+
resultClaimIds: ReadonlyArray<ClaimId>;
|
|
41
|
+
contractIds?: ReadonlyArray<ContractId>;
|
|
42
|
+
now: ISOTime;
|
|
43
|
+
actingAsGuardian?: true;
|
|
44
|
+
wardId?: string;
|
|
45
|
+
}): AuditEvent;
|
|
46
|
+
export interface FactoryCompatibleAuditLog extends AuditLog {
|
|
47
|
+
readonly factoryCompatible: "durable-audit-log";
|
|
48
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Slice-03: AuditLog — every agent query attempt produces an AuditEvent.
|
|
2
|
+
// AUDIT_EVERY_QUERY_001 — written for allow, deny, empty, expired, revoked
|
|
3
|
+
// AUDIT_FAIL_CLOSED_001 — if write fails, caller must return no data
|
|
4
|
+
// AUDIT_NO_PAYLOADS_001 — AuditEvent contains no claim payload values
|
|
5
|
+
//
|
|
6
|
+
// C1 Audit Coverage codes (Post-MVP Audit Hardening):
|
|
7
|
+
// PRODUCTION_AUDIT_PROPOSAL_MUTATION_RECORDED_001 — allow/deny for createProposal/acceptProposal/rejectProposal
|
|
8
|
+
// PRODUCTION_AUDIT_PROPOSAL_DENIAL_RECORDED_001 — pre-fetch + scope denials
|
|
9
|
+
// PRODUCTION_AUDIT_FACADE_DENIAL_RECORDED_001 — unsupported-context/broad-grant denials
|
|
10
|
+
// PRODUCTION_AUDIT_ERASURE_DENIAL_RECORDED_001 — erasure deny paths
|
|
11
|
+
//
|
|
12
|
+
// C3 Audit Coverage codes (Read Allow-Path Audit):
|
|
13
|
+
// PRODUCTION_AUDIT_READ_ALLOW_PATH_RECORDED_001 — allow paths for getProposal/listProposals/getErasureStatus (fail-closed)
|
|
14
|
+
// PRODUCTION_AUDIT_READ_SAFE_NOT_FOUND_RECORDED_001 — safe-not-found paths for getProposal (best-effort)
|
|
15
|
+
//
|
|
16
|
+
// Platform-02 (PolicyAwareProposalQueue) codes:
|
|
17
|
+
// PROPOSAL_AUTO_ACCEPTED_BY_POLICY_001 — auto-accepted proposals produce a durable audit
|
|
18
|
+
// event with reasonCode PROPOSAL_AUTO_ACCEPTED_BY_POLICY and the resulting claim ids.
|
|
19
|
+
// PROPOSAL_AUTO_REJECTED_BY_POLICY_001 — same for auto-reject, decision "deny".
|
|
20
|
+
// PROPOSAL_POLICY_AUTO_ACCEPT_FAILURE_FALLS_BACK_TO_MANUAL_001 — accept-path failure writes
|
|
21
|
+
// a best-effort deny audit event with reasonCode PROPOSAL_AUTO_ACCEPT_FAILED_MANUAL_FALLBACK.
|
|
22
|
+
import { asAuditId } from "../types/ids.js";
|
|
23
|
+
export function denyReasonToAuditCode(r) {
|
|
24
|
+
const map = {
|
|
25
|
+
grant_not_found: "GRANT_NOT_FOUND",
|
|
26
|
+
grant_expired: "GRANT_EXPIRED",
|
|
27
|
+
grant_not_yet_active: "GRANT_NOT_YET_ACTIVE",
|
|
28
|
+
grant_revoked: "GRANT_REVOKED",
|
|
29
|
+
purpose_mismatch: "PURPOSE_MISMATCH",
|
|
30
|
+
capability_not_granted: "CAPABILITY_NOT_GRANTED",
|
|
31
|
+
scope_empty: "SCOPE_INTERSECTION_EMPTY",
|
|
32
|
+
subject_not_granted: "SUBJECT_NOT_GRANTED",
|
|
33
|
+
};
|
|
34
|
+
return map[r];
|
|
35
|
+
}
|
|
36
|
+
// InMemoryAuditLog — for Slice-03 in-memory tests.
|
|
37
|
+
// Can be configured to throw on write (for AUDIT_FAIL_CLOSED_001 tests).
|
|
38
|
+
export class InMemoryAuditLog {
|
|
39
|
+
_events = [];
|
|
40
|
+
_failOnWrite = false;
|
|
41
|
+
// Test hook: make the next N writes fail
|
|
42
|
+
simulateWriteFailure(fail) {
|
|
43
|
+
this._failOnWrite = fail;
|
|
44
|
+
}
|
|
45
|
+
async write(event) {
|
|
46
|
+
if (this._failOnWrite) {
|
|
47
|
+
throw new Error("InMemoryAuditLog: simulated write failure (AUDIT_FAIL_CLOSED_001 test)");
|
|
48
|
+
}
|
|
49
|
+
this._events.push(event);
|
|
50
|
+
}
|
|
51
|
+
readAll() {
|
|
52
|
+
return [...this._events];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// AuditEvent factory — ensures consistent shape and prevents payload leakage
|
|
56
|
+
export function buildAuditEvent(params) {
|
|
57
|
+
return {
|
|
58
|
+
id: asAuditId(globalThis.crypto.randomUUID()),
|
|
59
|
+
recordedAt: params.now,
|
|
60
|
+
actor: params.actor,
|
|
61
|
+
decision: params.decision,
|
|
62
|
+
reasonCode: params.reasonCode,
|
|
63
|
+
selectorFingerprint: params.selectorFingerprint,
|
|
64
|
+
resultCount: params.resultCount,
|
|
65
|
+
resultClaimIds: params.resultClaimIds,
|
|
66
|
+
// exactOptionalPropertyTypes: only set optional fields when present
|
|
67
|
+
...(params.grantId !== undefined ? { grantId: params.grantId } : {}),
|
|
68
|
+
...(params.purpose !== undefined ? { purpose: params.purpose } : {}),
|
|
69
|
+
...(params.contractIds !== undefined ? { contractIds: params.contractIds } : {}),
|
|
70
|
+
...(params.actingAsGuardian === true ? { actingAsGuardian: true } : {}),
|
|
71
|
+
...(params.wardId !== undefined ? { wardId: params.wardId } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=audit-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../../src/audit/audit-log.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,4EAA4E;AAC5E,sEAAsE;AACtE,uEAAuE;AACvE,EAAE;AACF,sDAAsD;AACtD,gHAAgH;AAChH,8EAA8E;AAC9E,4FAA4F;AAC5F,uEAAuE;AACvE,EAAE;AACF,mDAAmD;AACnD,2HAA2H;AAC3H,yGAAyG;AACzG,EAAE;AACF,gDAAgD;AAChD,yFAAyF;AACzF,wFAAwF;AACxF,gFAAgF;AAChF,4FAA4F;AAC5F,gGAAgG;AAKhG,OAAO,EAAE,SAAS,EAAa,MAAM,iBAAiB,CAAC;AAmCvD,MAAM,UAAU,qBAAqB,CAAC,CAAoB;IACxD,MAAM,GAAG,GAA+C;QACtD,eAAe,EAAW,iBAAiB;QAC3C,aAAa,EAAa,eAAe;QACzC,oBAAoB,EAAM,sBAAsB;QAChD,aAAa,EAAa,eAAe;QACzC,gBAAgB,EAAU,kBAAkB;QAC5C,sBAAsB,EAAI,wBAAwB;QAClD,WAAW,EAAe,0BAA0B;QACpD,mBAAmB,EAAO,qBAAqB;KAChD,CAAC;IACF,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;AAChB,CAAC;AA6BD,mDAAmD;AACnD,yEAAyE;AACzE,MAAM,OAAO,gBAAgB;IACV,OAAO,GAAiB,EAAE,CAAC;IACpC,YAAY,GAAG,KAAK,CAAC;IAE7B,yCAAyC;IACzC,oBAAoB,CAAC,IAAa;QAChC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAiB;QAC3B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,UAAU,eAAe,CAAC,MAc/B;IACC,OAAO;QACL,EAAE,EAAoB,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC/D,UAAU,EAAY,MAAM,CAAC,GAAG;QAChC,KAAK,EAAiB,MAAM,CAAC,KAAK;QAClC,QAAQ,EAAc,MAAM,CAAC,QAAQ;QACrC,UAAU,EAAY,MAAM,CAAC,UAAU;QACvC,mBAAmB,EAAG,MAAM,CAAC,mBAAmB;QAChD,WAAW,EAAW,MAAM,CAAC,WAAW;QACxC,cAAc,EAAQ,MAAM,CAAC,cAAc;QAC3C,oEAAoE;QACpE,GAAG,CAAC,MAAM,CAAC,OAAO,KAAc,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAW,MAAM,CAAC,OAAO,EAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,GAAG,CAAC,MAAM,CAAC,OAAO,KAAc,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAW,MAAM,CAAC,OAAO,EAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,GAAG,CAAC,MAAM,CAAC,WAAW,KAAU,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAO,MAAM,CAAC,WAAW,EAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,GAAG,CAAC,MAAM,CAAC,gBAAgB,KAAK,IAAI,CAAM,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAa,EAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9F,GAAG,CAAC,MAAM,CAAC,MAAM,KAAe,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAY,MAAM,CAAC,MAAM,EAAY,CAAC,CAAC,CAAC,EAAE,CAAC;KAChG,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import type { AuditEvent, FactoryCompatibleAuditLog } from "./audit-log.js";
|
|
3
|
+
export interface ChainCanonicalFields {
|
|
4
|
+
readonly actor: string;
|
|
5
|
+
readonly contract_ids: string | null;
|
|
6
|
+
readonly decision: "allow" | "deny";
|
|
7
|
+
readonly grant_id: string | null;
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly prev_hash: string | null;
|
|
10
|
+
readonly purpose: string | null;
|
|
11
|
+
readonly reason_code: string;
|
|
12
|
+
readonly recorded_at: string;
|
|
13
|
+
readonly result_claim_ids: string;
|
|
14
|
+
readonly result_count: number;
|
|
15
|
+
readonly selector_fingerprint: string;
|
|
16
|
+
readonly sequence: number;
|
|
17
|
+
readonly acting_as_guardian?: number | null;
|
|
18
|
+
readonly ward_id?: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function buildChainCanonical(fields: ChainCanonicalFields): string;
|
|
21
|
+
export declare class SQLiteAuditLog implements FactoryCompatibleAuditLog {
|
|
22
|
+
readonly factoryCompatible: "durable-audit-log";
|
|
23
|
+
private readonly _db;
|
|
24
|
+
private readonly _ownsConnection;
|
|
25
|
+
constructor(dbPathOrDb: string | Database.Database);
|
|
26
|
+
close(): void;
|
|
27
|
+
write(event: AuditEvent): Promise<void>;
|
|
28
|
+
readAll(): ReadonlyArray<AuditEvent>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// SQLiteAuditLog — Slice-17 durable audit sink.
|
|
2
|
+
//
|
|
3
|
+
// AUDIT_DURABLE_SINK_EXISTS_001: implements AuditLog with durable SQLite storage.
|
|
4
|
+
// AUDIT_SQLITE_PERSISTS_EVENTS_001: events survive process exit — stored in the DB file.
|
|
5
|
+
// AUDIT_SQLITE_REOPEN_ROUND_TRIP_001: closing and reopening the DB restores all events.
|
|
6
|
+
// AUDIT_EVENTS_APPEND_ONLY_001: INSERT only — no UPDATE or DELETE methods exist.
|
|
7
|
+
// AUDIT_EVENT_NO_PAYLOAD_VALUES_001: no column in audit_events carries claim payload data.
|
|
8
|
+
// AUDIT_EVENT_NO_RAW_DOCUMENT_BYTES_001: no column carries raw document bytes or decrypted content.
|
|
9
|
+
// AUDIT_EVENT_SELECTOR_FINGERPRINT_ONLY_001: selector_fingerprint is a string summary, not the full selector.
|
|
10
|
+
// AUDIT_SINK_NO_CRYPTO_PRIMITIVES_001: no direct low-level crypto primitive usage here — SHA-256
|
|
11
|
+
// is delegated to the src/crypto/sha256.js helper (CRYPTO_BOUNDARY_ONLY_001).
|
|
12
|
+
// AUDIT_SINK_NO_DOMAIN_MUTATION_001: writes only to audit_events — no FactStore, SourceRegistry, or RawDocumentStore calls.
|
|
13
|
+
//
|
|
14
|
+
// PRODUCTION_AUDIT_C4_HASH_CHAIN_SEQUENCE_001: each write computes a monotonically increasing
|
|
15
|
+
// sequence, a prev_hash link, and a SHA-256 event_hash over the canonical encoding.
|
|
16
|
+
// PRODUCTION_AUDIT_C4_WRITE_ATOMIC_BEGIN_IMMEDIATE_001: write() runs in a BEGIN IMMEDIATE
|
|
17
|
+
// transaction so the read-tip / compute / insert is atomic against concurrent writers.
|
|
18
|
+
import Database from "better-sqlite3";
|
|
19
|
+
import { asAuditId, asClaimId, asContractId, asGrantId, asISOTime } from "../types/ids.js";
|
|
20
|
+
import { initializeSchema } from "../persistence/sqlite/schema.js";
|
|
21
|
+
import { sha256hexSync } from "../crypto/sha256.js";
|
|
22
|
+
export function buildChainCanonical(fields) {
|
|
23
|
+
// Object literal written in alphabetical key order; JSON.stringify preserves insertion order.
|
|
24
|
+
// GUARDIAN_AUDIT_HASH_CHAIN_BACKWARD_COMPATIBLE_001: acting_as_guardian and ward_id are
|
|
25
|
+
// included only when non-null — pre-guardian events produce byte-identical hashes.
|
|
26
|
+
const obj = {
|
|
27
|
+
...(fields.acting_as_guardian != null ? { acting_as_guardian: fields.acting_as_guardian } : {}),
|
|
28
|
+
actor: fields.actor,
|
|
29
|
+
contract_ids: fields.contract_ids,
|
|
30
|
+
decision: fields.decision,
|
|
31
|
+
grant_id: fields.grant_id,
|
|
32
|
+
id: fields.id,
|
|
33
|
+
prev_hash: fields.prev_hash,
|
|
34
|
+
purpose: fields.purpose,
|
|
35
|
+
reason_code: fields.reason_code,
|
|
36
|
+
recorded_at: fields.recorded_at,
|
|
37
|
+
result_claim_ids: fields.result_claim_ids,
|
|
38
|
+
result_count: fields.result_count,
|
|
39
|
+
selector_fingerprint: fields.selector_fingerprint,
|
|
40
|
+
sequence: fields.sequence,
|
|
41
|
+
...(fields.ward_id != null ? { ward_id: fields.ward_id } : {}),
|
|
42
|
+
};
|
|
43
|
+
return JSON.stringify(obj);
|
|
44
|
+
}
|
|
45
|
+
function rowToAuditEvent(row) {
|
|
46
|
+
return {
|
|
47
|
+
id: asAuditId(row.id),
|
|
48
|
+
recordedAt: asISOTime(row.recorded_at),
|
|
49
|
+
actor: row.actor,
|
|
50
|
+
decision: row.decision,
|
|
51
|
+
reasonCode: row.reason_code,
|
|
52
|
+
selectorFingerprint: row.selector_fingerprint,
|
|
53
|
+
resultCount: row.result_count,
|
|
54
|
+
resultClaimIds: JSON.parse(row.result_claim_ids).map(id => asClaimId(id)),
|
|
55
|
+
...(row.grant_id != null ? { grantId: asGrantId(row.grant_id) } : {}),
|
|
56
|
+
...(row.purpose != null ? { purpose: row.purpose } : {}),
|
|
57
|
+
...(row.contract_ids != null ? { contractIds: JSON.parse(row.contract_ids).map(id => asContractId(id)) } : {}),
|
|
58
|
+
...(row.acting_as_guardian === 1 ? { actingAsGuardian: true } : {}),
|
|
59
|
+
...(row.ward_id != null ? { wardId: row.ward_id } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export class SQLiteAuditLog {
|
|
63
|
+
// AUDIT_LOG_FACTORY_COMPATIBLE_REQUIRED_001 / AUDIT_LOG_DURABLE_REQUIRED_001
|
|
64
|
+
factoryCompatible = "durable-audit-log";
|
|
65
|
+
_db;
|
|
66
|
+
_ownsConnection;
|
|
67
|
+
// Accepts a file path (opens own connection) or a shared Database instance.
|
|
68
|
+
// Mirrors SQLiteRawDocumentStore dual-mode pattern (Production-06c).
|
|
69
|
+
constructor(dbPathOrDb) {
|
|
70
|
+
if (typeof dbPathOrDb === "string") {
|
|
71
|
+
this._db = new Database(dbPathOrDb);
|
|
72
|
+
this._ownsConnection = true;
|
|
73
|
+
initializeSchema(this._db);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
this._db = dbPathOrDb;
|
|
77
|
+
this._ownsConnection = false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
close() {
|
|
81
|
+
if (this._ownsConnection)
|
|
82
|
+
this._db.close();
|
|
83
|
+
}
|
|
84
|
+
// AUDIT_EVENTS_APPEND_ONLY_001: INSERT only — no UPDATE or DELETE.
|
|
85
|
+
// PRODUCTION_AUDIT_C4_HASH_CHAIN_SEQUENCE_001 / PRODUCTION_AUDIT_C4_PREV_HASH_LINKAGE_001 /
|
|
86
|
+
// PRODUCTION_AUDIT_C4_WRITE_ATOMIC_BEGIN_IMMEDIATE_001: read tip → compute seq/prev/hash →
|
|
87
|
+
// INSERT, all inside one BEGIN IMMEDIATE transaction (synchronous better-sqlite3 callback).
|
|
88
|
+
async write(event) {
|
|
89
|
+
const grantId = event.grantId ?? null;
|
|
90
|
+
const purpose = event.purpose ?? null;
|
|
91
|
+
const resultClaims = JSON.stringify([...event.resultClaimIds]);
|
|
92
|
+
const contractIds = event.contractIds != null ? JSON.stringify([...event.contractIds]) : null;
|
|
93
|
+
// Platform-03 — Guardian columns (GUARDIAN_AUDIT_MARKED_001)
|
|
94
|
+
const actingAsGuardian = event.actingAsGuardian === true ? 1 : null;
|
|
95
|
+
const wardId = event.wardId ?? null;
|
|
96
|
+
const insertChained = this._db.transaction(() => {
|
|
97
|
+
const tip = this._db
|
|
98
|
+
.prepare(`SELECT sequence AS prev_seq, event_hash AS prev_event_hash
|
|
99
|
+
FROM audit_events
|
|
100
|
+
WHERE sequence = (SELECT MAX(sequence) FROM audit_events)`)
|
|
101
|
+
.get();
|
|
102
|
+
const nextSeq = (tip?.prev_seq ?? 0) + 1;
|
|
103
|
+
const prevHash = tip?.prev_event_hash ?? null;
|
|
104
|
+
const canonical = buildChainCanonical({
|
|
105
|
+
actor: event.actor,
|
|
106
|
+
contract_ids: contractIds,
|
|
107
|
+
decision: event.decision,
|
|
108
|
+
grant_id: grantId,
|
|
109
|
+
id: event.id,
|
|
110
|
+
prev_hash: prevHash,
|
|
111
|
+
purpose: purpose,
|
|
112
|
+
reason_code: event.reasonCode,
|
|
113
|
+
recorded_at: event.recordedAt,
|
|
114
|
+
result_claim_ids: resultClaims,
|
|
115
|
+
result_count: event.resultCount,
|
|
116
|
+
selector_fingerprint: event.selectorFingerprint,
|
|
117
|
+
sequence: nextSeq,
|
|
118
|
+
// Guardian fields — null entries omitted from canonical (GUARDIAN_AUDIT_HASH_CHAIN_BACKWARD_COMPATIBLE_001)
|
|
119
|
+
acting_as_guardian: actingAsGuardian,
|
|
120
|
+
ward_id: wardId,
|
|
121
|
+
});
|
|
122
|
+
const eventHash = sha256hexSync(canonical);
|
|
123
|
+
this._db.prepare(`
|
|
124
|
+
INSERT INTO audit_events
|
|
125
|
+
(id, recorded_at, actor, grant_id, purpose, decision, reason_code,
|
|
126
|
+
selector_fingerprint, result_count, result_claim_ids, contract_ids,
|
|
127
|
+
sequence, prev_hash, event_hash, acting_as_guardian, ward_id)
|
|
128
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
129
|
+
`).run(event.id, event.recordedAt, event.actor, grantId, purpose, event.decision, event.reasonCode, event.selectorFingerprint, event.resultCount, resultClaims, contractIds, nextSeq, prevHash, eventHash, actingAsGuardian, wardId);
|
|
130
|
+
});
|
|
131
|
+
// BEGIN IMMEDIATE: reserve the write lock up front so two writers cannot both read the
|
|
132
|
+
// same MAX(sequence). The unique index on sequence is the second safety net.
|
|
133
|
+
insertChained.immediate();
|
|
134
|
+
}
|
|
135
|
+
readAll() {
|
|
136
|
+
return this._db
|
|
137
|
+
.prepare("SELECT * FROM audit_events ORDER BY rowid ASC")
|
|
138
|
+
.all()
|
|
139
|
+
.map(rowToAuditEvent);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=sqlite-audit-log.js.map
|