@wildo-ai/saas-backend-lib 1.1.12 → 1.1.13
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/dist/esm/base-classes/models/manual-controllers.backend.definition.d.ts.map +1 -1
- package/dist/esm/base-classes/models/manual-controllers.backend.definition.js +2 -0
- package/dist/esm/base-classes/models/manual-controllers.backend.definition.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.d.ts +4 -0
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.js +20 -5
- package/dist/esm/base-classes/services/repositories/stored-field-omission.backend.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.d.ts +3 -0
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.js +10 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation-registry.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.d.ts +22 -0
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.js +25 -1
- package/dist/esm/base-classes/services/services/resource-operation-custom-implementation.factory.backend.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.d.ts +38 -3
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.js +68 -5
- package/dist/esm/base-classes/services/services/resource-operation-transaction-persistence.backend.utils.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.d.ts +2 -2
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.js +6 -3
- package/dist/esm/base-classes/services/services/resource-status-transition.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts +83 -0
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js +217 -14
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.d.ts +32 -0
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.js +36 -0
- package/dist/esm/compliance/approval-seals/approval-seal-currency.backend.js.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.d.ts +38 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.js +83 -0
- package/dist/esm/compliance/approval-seals/approval-seal-on-record.backend.js.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.d.ts +36 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.js +51 -0
- package/dist/esm/compliance/approval-seals/approval-seal-presentation-read.backend.js.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.d.ts +46 -0
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.js +68 -0
- package/dist/esm/compliance/approval-seals/approval-seal-relay-store.backend.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.d.ts +27 -0
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.js +42 -3
- package/dist/esm/compliance/approval-seals/approval-seal-store.backend.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.d.ts +26 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.js +10 -0
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.contracts.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.d.ts +10 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.js +60 -28
- package/dist/esm/compliance/approval-seals/approval-seals-admin.backend.controller.js.map +1 -1
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.d.ts +90 -0
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.d.ts.map +1 -0
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.js +373 -0
- package/dist/esm/compliance/approval-seals/approval-seals-on-record.backend.controller.js.map +1 -0
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.d.ts +29 -2
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.d.ts.map +1 -1
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.js +40 -3
- package/dist/esm/compliance/approval-seals/decision-presentation.backend.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/pdf-signature-structure.backend.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/pdf-signature-structure.backend.js +34 -6
- package/dist/esm/compliance/electronic-signatures/pdf-signature-structure.backend.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.d.ts +30 -6
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.js +67 -22
- package/dist/esm/compliance/electronic-signatures/signature-request-evidence-files.backend.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-poll.batch.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-poll.batch.backend.service.js +18 -1
- package/dist/esm/compliance/electronic-signatures/signature-request-poll.batch.backend.service.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.d.ts +66 -1
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.js +161 -14
- package/dist/esm/compliance/electronic-signatures/signature-requests.backend.service.js.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signed-document-validation.backend.d.ts +47 -0
- package/dist/esm/compliance/electronic-signatures/signed-document-validation.backend.d.ts.map +1 -1
- package/dist/esm/compliance/electronic-signatures/signed-document-validation.backend.js.map +1 -1
- package/dist/esm/external-providers/services/content-safety.backend.service.d.ts +27 -2
- package/dist/esm/external-providers/services/content-safety.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/content-safety.backend.service.js +46 -1
- package/dist/esm/external-providers/services/content-safety.backend.service.js.map +1 -1
- package/dist/esm/external-providers/services/embeddings.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/embeddings.backend.service.js +2 -0
- package/dist/esm/external-providers/services/embeddings.backend.service.js.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +11 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +23 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.d.ts +19 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.d.ts.map +1 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.js +41 -1
- package/dist/esm/external-providers/services/provider-service-account-token.backend.js.map +1 -1
- package/dist/esm/files/utils/file-parent-read-context.backend.d.ts +27 -2
- package/dist/esm/files/utils/file-parent-read-context.backend.d.ts.map +1 -1
- package/dist/esm/files/utils/file-parent-read-context.backend.js +23 -4
- package/dist/esm/files/utils/file-parent-read-context.backend.js.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.d.ts +26 -0
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.d.ts.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.js +100 -8
- package/dist/esm/flows-actors/content-guardrails/classifier-availability.backend.utils.js.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.d.ts +3 -1
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.d.ts.map +1 -1
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.js +8 -2
- package/dist/esm/flows-actors/content-guardrails/record-owner-policy.backend.js.map +1 -1
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.d.ts +43 -0
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.d.ts.map +1 -1
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.js +37 -0
- package/dist/esm/flows-actors/models/function-tools.backend.definitions.js.map +1 -1
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.d.ts +7 -0
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.js +8 -3
- package/dist/esm/flows-actors/services/agent-work-decisions.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts +31 -6
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.js +78 -30
- package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/autonomous-agent-turn.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/autonomous-agent-turn.backend.service.js +4 -0
- package/dist/esm/flows-actors/services/autonomous-agent-turn.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.d.ts +28 -2
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.js +132 -22
- package/dist/esm/flows-actors/services/conversational-execution.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/flow-actor-executions.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/flow-actor-executions.custom-impl.backend.service.js +12 -8
- package/dist/esm/flows-actors/services/flow-actor-executions.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/flows-actors-registry.service.d.ts +5 -0
- package/dist/esm/flows-actors/services/flows-actors-registry.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/flows-actors-registry.service.js +18 -2
- package/dist/esm/flows-actors/services/flows-actors-registry.service.js.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +33 -0
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +58 -0
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.d.ts +12 -1
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.js +23 -4
- package/dist/esm/inbound/services/inbound-delivery-dispatch.backend.service.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.d.ts +12 -1
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.js +55 -17
- package/dist/esm/inbound/services/inbound-delivery-receiver.backend.service.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.d.ts +22 -1
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.js +52 -9
- package/dist/esm/inbound/services/inbound-delivery-store.backend.service.js.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.d.ts +79 -0
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.d.ts.map +1 -0
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.js +211 -0
- package/dist/esm/inbound/services/inbound-delivery-stored-body.backend.service.js.map +1 -0
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.d.ts +2 -2
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.d.ts.map +1 -1
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.js +9 -1
- package/dist/esm/inbound/services/inbound-delivery-subject-erasure-severance.backend.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/inject/models/container.d.ts.map +1 -1
- package/dist/esm/inject/models/container.js +6 -0
- package/dist/esm/inject/models/container.js.map +1 -1
- package/dist/esm/inject/models/service-types.d.ts +5 -0
- package/dist/esm/inject/models/service-types.d.ts.map +1 -1
- package/dist/esm/inject/models/service-types.js +3 -0
- package/dist/esm/inject/models/service-types.js.map +1 -1
- package/dist/esm/monitoring/observability/models/observability.backend.schemas.d.ts +5 -0
- package/dist/esm/monitoring/observability/models/observability.backend.schemas.d.ts.map +1 -1
- package/dist/esm/monitoring/observability/models/observability.backend.schemas.js.map +1 -1
- package/dist/esm/monitoring/observability/services/metrics.service.d.ts +34 -1
- package/dist/esm/monitoring/observability/services/metrics.service.d.ts.map +1 -1
- package/dist/esm/monitoring/observability/services/metrics.service.js +49 -0
- package/dist/esm/monitoring/observability/services/metrics.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.d.ts +6 -1
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.js +15 -4
- package/dist/esm/notifications/webhooks/controllers/webhook-auto-controller.backend.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/m2m-webhook-delivery-log.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/m2m-webhook-delivery-log.custom-impl.backend.service.js +3 -1
- package/dist/esm/notifications/webhooks/outbound/services/m2m-webhook-delivery-log.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/http-server.backend.service.d.ts +3 -1
- package/dist/esm/runtime/services/http-server.backend.service.d.ts.map +1 -1
- package/dist/esm/runtime/services/http-server.backend.service.js +11 -3
- package/dist/esm/runtime/services/http-server.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.d.ts +22 -0
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.d.ts.map +1 -1
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.js +54 -0
- package/dist/esm/runtime/services/utils/inbound-endpoint-raw-body.backend.utils.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.d.ts +51 -0
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.js +87 -15
- package/dist/esm/security/authentications/services/auth-methods/auth-password-reset.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/siem-delivery-dlq.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/siem-delivery-dlq.custom-impl.backend.service.js +5 -2
- package/dist/esm/security/authentications/services/custom-impls/siem-delivery-dlq.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/sso-config-domain-verification.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/custom-impls/sso-config-domain-verification.custom-impl.backend.service.js +65 -24
- package/dist/esm/security/authentications/services/custom-impls/sso-config-domain-verification.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.d.ts +64 -1
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.d.ts.map +1 -1
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.js +84 -2
- package/dist/esm/security/authorizations/super-admin-floor.backend.utils.js.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.d.ts +14 -0
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.js +13 -5
- package/dist/esm/security/oauth-clients/oauth-clients.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/anonymous-consent.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/anonymous-consent.custom-impl.backend.service.js +6 -2
- package/dist/esm/users/services/anonymous-consent.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.d.ts +7 -0
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.js +65 -20
- package/dist/esm/users/services/user-force-password-reset.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/user-role-administration.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/user-role-administration.custom-impl.backend.service.js +11 -4
- package/dist/esm/users/services/user-role-administration.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/users/services/user-self-custom-implementations.backend.d.ts.map +1 -1
- package/dist/esm/users/services/user-self-custom-implementations.backend.js +4 -2
- package/dist/esm/users/services/user-self-custom-implementations.backend.js.map +1 -1
- package/dist/esm/users/services/user-super-admin-floor.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/users/services/user-super-admin-floor.custom-impl.backend.service.js +16 -0
- package/dist/esm/users/services/user-super-admin-floor.custom-impl.backend.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-presentation.backend.d.ts","sourceRoot":"","sources":["../../../../src/compliance/approval-seals/decision-presentation.backend.ts"],"names":[],"mappings":"AA4BA,OAAO,EAGL,6BAA6B,
|
|
1
|
+
{"version":3,"file":"decision-presentation.backend.d.ts","sourceRoot":"","sources":["../../../../src/compliance/approval-seals/decision-presentation.backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* operation's requested values, an admission's snapshot), so the row the check reads is the row the answer settles.
|
|
27
27
|
*/
|
|
28
28
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
29
|
-
import { ApprovalPresentationProvenance, HumanDecision_DeliverableBinding, HumanDecision_SentCardContent, buildHumanDecisionPresentation, humanDecisionAsSentCard, humanDecisionPresentedFieldsOf, humanDecisionPresentedValueOf, } from '@wildo-ai/saas-models';
|
|
29
|
+
import { ApprovalPresentationProvenance, HumanDecision_DeliverableBinding, HumanDecision_SentCardContent, buildHumanDecisionPresentation, buildHumanDecisionPresentedRecord, humanDecisionAsSentCard, humanDecisionPresentedFieldsOf, humanDecisionPresentedValueOf, } from '@wildo-ai/saas-models';
|
|
30
30
|
import { approvalSealFileCommitmentOf, base64url, canonicalizeJson } from '@wildo-ai/platform-config-lib';
|
|
31
31
|
import { CryptoKey_Purpose } from '@wildo-ai/saas-models';
|
|
32
32
|
import { applicationSymmetricKeyOf } from '../../security/crypto-keys/application-symmetric-keys.backend.js';
|
|
@@ -71,8 +71,10 @@ export function decisionPresentationIdOf(presentation, key) {
|
|
|
71
71
|
* {@link resolveDecisionDeliverables}, joins the presentation and only the presentation: the card has no field of its own
|
|
72
72
|
* for it, so nothing reads it anywhere but where the id covers it.
|
|
73
73
|
*/
|
|
74
|
-
export function withDecisionPresentation(card, key, deliverables
|
|
75
|
-
|
|
74
|
+
export function withDecisionPresentation(card, key, deliverables,
|
|
75
|
+
/** #2914: the record the call acts on, with the fields its approval covers ({@link resolveDecisionSubjectRecord}). */
|
|
76
|
+
subjectRecord) {
|
|
77
|
+
const presentation = buildHumanDecisionPresentation({ ...card, ...(deliverables ? { deliverables } : {}), ...(subjectRecord ? { subjectRecord } : {}) });
|
|
76
78
|
return { ...card, presentation, presentationId: decisionPresentationIdOf(presentation, key) };
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
@@ -141,6 +143,41 @@ export async function resolveDecisionDeliverables(params) {
|
|
|
141
143
|
bound: { fileId, salt },
|
|
142
144
|
};
|
|
143
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* The record a tool approval is about, with the fields its tool declares the approval covers (#2914,
|
|
148
|
+
* `FunctionTool.sealedRecordFields`): the row named by the call's `rowIdArgument`, read as the system when the card is
|
|
149
|
+
* built, its declared values projected like presented arguments. The announcement, the history, the inbox and the act all
|
|
150
|
+
* resolve it here, so they agree on the presentation and its id: a card read before one of those values changed carries
|
|
151
|
+
* another id, and its answer is refused as stale.
|
|
152
|
+
*
|
|
153
|
+
* Undefined, never a guess, when the tool declares no field set, the call names no row, the row cannot be read, or the
|
|
154
|
+
* row belongs to another organization than the decision (a card must not show another tenant's values). Each reads
|
|
155
|
+
* alike, so the card discloses nothing about a row its reader may not see.
|
|
156
|
+
*
|
|
157
|
+
* assurance-control: WILDO.AI.HUMAN_OVERSIGHT, WILDO.AUDIT.EVIDENCE_INTEGRITY — the record values an approval covers are shown to the person and sealed with their answer, so whether the approval still holds can be computed later.
|
|
158
|
+
*/
|
|
159
|
+
export async function resolveDecisionSubjectRecord(params) {
|
|
160
|
+
const { sealedFields, resourceType, rowIdArgument } = params;
|
|
161
|
+
if (!sealedFields || sealedFields.length === 0 || !resourceType || !rowIdArgument)
|
|
162
|
+
return undefined;
|
|
163
|
+
const rowId = params.toolArgs?.[rowIdArgument];
|
|
164
|
+
if (typeof rowId !== 'string' || rowId.length === 0)
|
|
165
|
+
return undefined;
|
|
166
|
+
let row;
|
|
167
|
+
try {
|
|
168
|
+
row = await params.readRow(resourceType, rowId);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
params.onReadFailure?.(error);
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
if (!row)
|
|
175
|
+
return undefined;
|
|
176
|
+
const rowOrganizationId = row.organizationId !== undefined && row.organizationId !== null && String(row.organizationId).length > 0 ? String(row.organizationId) : undefined;
|
|
177
|
+
if (rowOrganizationId !== undefined && rowOrganizationId !== params.organizationId)
|
|
178
|
+
return undefined;
|
|
179
|
+
return buildHumanDecisionPresentedRecord({ resourceType, resourceId: rowId, sealedFields, record: row });
|
|
180
|
+
}
|
|
144
181
|
/** What checking an answer's echoed presentation came to. */
|
|
145
182
|
export var DecisionPresentationCheckKind;
|
|
146
183
|
(function (DecisionPresentationCheckKind) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-presentation.backend.js","sourceRoot":"","sources":["../../../../src/compliance/approval-seals/decision-presentation.backend.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * # A decision's presentation, as the server binds an answer to it (#2903 S6)\n *\n * `buildHumanDecisionPresentation` (`@wildo-ai/saas-models`) says what a person is shown; this file is what the server\n * does with it:\n *\n * - **an id** ({@link decisionPresentationIdOf}): HMAC-SHA-256 of the presentation's RFC 8785 canonical form, under a key\n * derived from the decision-presentation purpose key (#2893). Deterministic, so every replica gives one decision one id until\n * something the person decides on changes, with no store to keep in step; and keyed, so the id a seal carries cannot\n * confirm a guessed presentation to someone holding only the seal. The key label is versioned: a new derivation is a\n * new key, never a reinterpretation of the old one;\n * - **the check an answer passes** ({@link acknowledgeDecisionPresentation}): a door where the engine renders the\n * decision echoes the id it showed, and the act rebuilds the presentation from the decision's row and compares. An\n * echo that no longer matches is STALE and refused, so a person never approves something other than what they read.\n * An answer with no echo comes through a door that sends the decision and cannot watch it render (a chat card); its\n * seal records the presentation as SENT, what the engine built and sent;\n * - **the approver's statement** ({@link decisionStatementOf}): what the person answered, its values projected the way\n * presented values are, so the seal binds the answer as well as what it answered;\n * - **a tool's file deliverable** ({@link resolveDecisionDeliverables}, #2904): the file the call names through the\n * argument its tool declares, bound by `H(salt ‖ contentSha256)` with the hash computed now when the file has none\n * (#2900's `ensureContentHash`). The salt is derived from the presentation-id key, so every build of one decision\n * carries one commitment until the file's recorded hash changes, and then the id changes with it.\n *\n * The comparison runs after the decision's route admitted the person, from the row the settle then writes under its\n * compare-and-swap. Everything a presentation holds is fixed while the decision waits (a tool call's arguments, a held\n * operation's requested values, an admission's snapshot), so the row the check reads is the row the answer settles.\n */\nimport { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport {\n ApprovalPresentationProvenance,\n HumanDecision_DeliverableBinding,\n HumanDecision_SentCardContent,\n buildHumanDecisionPresentation,\n humanDecisionAsSentCard,\n humanDecisionPresentedFieldsOf,\n humanDecisionPresentedValueOf,\n type HumanDecision_Answer,\n type HumanDecision_PendingCard,\n type HumanDecision_Presentation,\n type ApprovalSealRecordPresentation,\n type HumanDecision_PresentedDeliverable,\n type HumanDecision_SealedPresentation,\n type HumanDecision_Statement,\n type HumanDecisionPresentationSource,\n} from '@wildo-ai/saas-models';\nimport { approvalSealFileCommitmentOf, base64url, canonicalizeJson } from '@wildo-ai/platform-config-lib';\nimport { CryptoKey_Purpose } from '@wildo-ai/saas-models';\nimport type { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { applicationSymmetricKeyOf } from '~wildo-backend-lib/security/crypto-keys/application-symmetric-keys.backend';\n\n/**\n * Domain separation for the presentation-id key, derived from the decision-presentation purpose key (#2893; until then,\n * from the application's primary secret, under the same label).\n *\n * semantic-literal-boundary: key material, not a vocabulary member. Changing it changes every presentation id, which\n * refuses every answer given on a card read before the change: version it, never tidy it.\n */\nexport const DECISION_PRESENTATION_ID_KEY_LABEL = 'wildo:decision-presentation-id:v1';\n\n/** The presentation-id key derived from the decision-presentation purpose key, or `undefined` when there is none. */\nexport function decisionPresentationKeyOf(purposeKey: Buffer | undefined): Buffer | undefined {\n if (!purposeKey || purposeKey.length === 0) return undefined;\n return createHmac('sha256', purposeKey).update(DECISION_PRESENTATION_ID_KEY_LABEL).digest();\n}\n\n/**\n * A key for a process that holds no decision-presentation key (a host that loaded no application secrets): random,\n * drawn once per process. Ids then hold only for this process's life, so a restart makes every open card stale and its\n * next answer is refused and read again, which is the safe direction. The platform delivers the purpose key to the\n * backend and the minions alike (#2893), so no two runtimes of an application answer with keys that differ.\n */\nlet processPresentationKey: Buffer | undefined;\n\n/** The presentation-id key of THIS runtime: derived from its delivered purpose key, else drawn once for the process. */\nexport function decisionPresentationKeyOfRuntime(appConfig: Pick<AppConfigurationService, 'isInitialized' | 'secrets'> | null | undefined): Buffer {\n const derived = decisionPresentationKeyOf(applicationSymmetricKeyOf(appConfig, CryptoKey_Purpose.DECISION_PRESENTATION_ID));\n if (derived) return derived;\n processPresentationKey ??= randomBytes(32);\n return processPresentationKey;\n}\n\n/**\n * The id of one presentation under `key`: base64url HMAC-SHA-256 of its canonical form. A card's presentation, or a\n * decision an operation settles presented as its record (#2906), whose id a step-up proof is bound to (#2923).\n */\nexport function decisionPresentationIdOf(presentation: HumanDecision_Presentation | ApprovalSealRecordPresentation, key: Buffer): string {\n return base64url(createHmac('sha256', key).update(canonicalizeJson(presentation), 'utf8').digest());\n}\n\n/**\n * A decision card carrying what it presents and the id an answer echoes. A tool approval's file deliverable, resolved by\n * {@link resolveDecisionDeliverables}, joins the presentation and only the presentation: the card has no field of its own\n * for it, so nothing reads it anywhere but where the id covers it.\n */\nexport function withDecisionPresentation<TCard extends HumanDecisionPresentationSource>(\n card: TCard,\n key: Buffer,\n deliverables?: readonly HumanDecision_PresentedDeliverable[],\n): TCard & Required<Pick<HumanDecision_PendingCard, 'presentation' | 'presentationId'>> {\n const presentation = buildHumanDecisionPresentation(deliverables ? { ...card, deliverables } : card);\n return { ...card, presentation, presentationId: decisionPresentationIdOf(presentation, key) };\n}\n\n/**\n * Domain separation for a file deliverable's salt (#2904), derived under the presentation-id key.\n *\n * semantic-literal-boundary: key material, not a vocabulary member. Changing it changes every deliverable's commitment,\n * which refuses every answer given on a card read before the change: version it, never tidy it.\n */\nexport const DECISION_DELIVERABLE_SALT_LABEL = 'wildo:decision-deliverable-salt:v1';\n\n/**\n * The salt a decision's file deliverable is committed with (#2904): base64url HMAC-SHA-256 under the presentation-id key,\n * over the decision, the argument and the file. DERIVED rather than drawn, because the presentation it joins must be\n * one value on every build of the decision (its id is what an answer echoes); keyed, so nobody without the application's\n * key can predict it and test a guessed document against a commitment. The seal's row keeps the value, so a seal opens\n * after the key rotates, and a reopened decision's later seal of the same file carries the same salt and commitment,\n * which says nothing the first did not.\n */\nexport function decisionDeliverableSaltOf(key: Buffer, deliverable: { readonly decisionRef: string; readonly argument: string; readonly fileId: string }): string {\n return base64url(\n createHmac('sha256', key).update(DECISION_DELIVERABLE_SALT_LABEL).update('\\u0000').update(deliverable.decisionRef).update('\\u0000')\n .update(deliverable.argument).update('\\u0000').update(deliverable.fileId).digest(),\n );\n}\n\n/** What binding a file deliverable reads of the file (#2904): its row, and its hash, computed now when it has none. */\nexport interface DecisionDeliverableFiles {\n /** The file's row, read as the system; null when there is none. */\n readFile(fileId: string): Promise<{ readonly organizationId?: string | null } | null>;\n /** `FilesBackendService.ensureContentHash`: the recorded hash, else the bytes hashed now and written once (#2900). */\n ensureContentHash(fileId: string): Promise<string | undefined>;\n}\n\n/** A decision's file deliverable as its presentation binds it, and the salt a seal keeps for a bound one. */\nexport interface ResolvedDecisionDeliverables {\n /** The presentation's `deliverables`; absent when the tool declares none or the call names no file. */\n readonly deliverables?: readonly HumanDecision_PresentedDeliverable[];\n /** The bound file and its salt: what the seal attests and keeps. Absent when nothing is bound. */\n readonly bound?: { readonly fileId: string; readonly salt: string };\n}\n\n/**\n * Resolve a tool approval's file deliverable (#2904): the file the call names through the argument its tool declares\n * (`FunctionTool.deliverableFileArgument`), bound by its content hash, computed now when the file has none\n * (`ensureContentHash`, #2900's obligation on the first binding of a file), BEFORE the commitment is made.\n *\n * NOT_BOUND, never an empty hash, when nothing can be hashed: no file service, no such file, an id the files store\n * refuses, a remote reference whose vendor keeps the bytes, a deleted file, a file of an organization the decision does\n * not belong to (a card must not show, or a seal freeze, another tenant's file), or a read that failed. Each of those\n * reads alike, so the card discloses nothing about a file its reader may not see. Every surface building the card and\n * the act checking the answer call this one function, so they agree, and an answer given on a card built before the\n * file's hash changed is refused as stale.\n *\n * assurance-control: WILDO.AI.HUMAN_OVERSIGHT, WILDO.AUDIT.EVIDENCE_INTEGRITY — the file a person approves is bound to its bytes by the presentation and the seal; an approval never binds a file it could not hash.\n */\nexport async function resolveDecisionDeliverables(params: {\n readonly decisionRef: string;\n /** The argument the tool declares names its file deliverable; absent when it declares none. */\n readonly deliverableArgument: string | undefined;\n readonly toolArgs: Readonly<Record<string, unknown>> | undefined;\n /** The organization the decision belongs to; absent for a decision of the application. */\n readonly organizationId?: string;\n readonly files: DecisionDeliverableFiles | undefined;\n readonly key: Buffer;\n /** Told when a read failed, so a NOT_BOUND that hides an outage is still seen. */\n readonly onReadFailure?: (error: unknown) => void;\n}): Promise<ResolvedDecisionDeliverables> {\n const argument = params.deliverableArgument;\n if (!argument) return {};\n const fileId = params.toolArgs?.[argument];\n if (typeof fileId !== 'string' || fileId.length === 0) return {};\n const notBound: ResolvedDecisionDeliverables = { deliverables: [{ argument, fileId, binding: HumanDecision_DeliverableBinding.NOT_BOUND }] };\n if (!params.files) return notBound;\n let contentSha256: string | undefined;\n try {\n const file = await params.files.readFile(fileId);\n if (!file) return notBound;\n // A file of an organization is bound only by a decision of that organization; a file of the application by any.\n const fileOrganizationId = typeof file.organizationId === 'string' && file.organizationId.length > 0 ? file.organizationId : undefined;\n if (fileOrganizationId !== undefined && fileOrganizationId !== params.organizationId) return notBound;\n contentSha256 = await params.files.ensureContentHash(fileId);\n } catch (error) {\n params.onReadFailure?.(error);\n return notBound;\n }\n if (!contentSha256) return notBound;\n const salt = decisionDeliverableSaltOf(params.key, { decisionRef: params.decisionRef, argument, fileId });\n return {\n deliverables: [{ argument, fileId, binding: HumanDecision_DeliverableBinding.BOUND, commitment: approvalSealFileCommitmentOf(salt, contentSha256) }],\n bound: { fileId, salt },\n };\n}\n\n/** The presentation a seal binds: what was built, its id when one could be derived, and how its rendering is known. */\nexport type SealedDecisionPresentation = HumanDecision_SealedPresentation;\n\n/** What checking an answer's echoed presentation came to. */\nexport enum DecisionPresentationCheckKind {\n /** The answer is bound to the decision's presentation as it is now. */\n BOUND = 'bound',\n /** The echo is not the decision's presentation now: the answer is refused. */\n STALE = 'stale',\n}\n\nexport type DecisionPresentationCheck =\n | { readonly kind: DecisionPresentationCheckKind.BOUND; readonly presentation: SealedDecisionPresentation }\n | { readonly kind: DecisionPresentationCheckKind.STALE };\n\n/**\n * Check an answer against the decision's presentation as its row holds it now. An echo is compared with the id rebuilt\n * from the row, in constant time over the two ids' digests; a match binds the answer PRESENTED. No echo binds it SENT:\n * every door that renders the decision refuses an answer without one before it gets here, so only a door that sends the\n * decision elsewhere (a chat card) reaches this without one. A SENT presentation is what that card CARRIED\n * (`sentCardContent`, set by the chat door from the application's notice handler): a chat card without the proposed\n * values seals none (#2903 review F2). An in-process door that names nothing seals the whole presentation.\n *\n * assurance-control: WILDO.AI.HUMAN_OVERSIGHT, WILDO.AUDIT.EVIDENCE_INTEGRITY — a person's answer is bound to the presentation they were shown, rebuilt from the decision's row; an answer given on what is no longer the decision is refused.\n */\nexport function acknowledgeDecisionPresentation(params: {\n readonly source: HumanDecisionPresentationSource;\n readonly echoedPresentationId?: string;\n /** What the card the answer came from carried, when the engine sent it elsewhere. Absent: the whole presentation. */\n readonly sentCardContent?: HumanDecision_SentCardContent;\n /** The runtime's presentation-id key ({@link decisionPresentationKeyOfRuntime}). */\n readonly key: Buffer;\n}): DecisionPresentationCheck {\n const key = params.key;\n if (params.echoedPresentationId === undefined) {\n const sent = buildHumanDecisionPresentation(humanDecisionAsSentCard(params.source, params.sentCardContent ?? HumanDecision_SentCardContent.COMPLETE));\n return { kind: DecisionPresentationCheckKind.BOUND, presentation: { content: sent, id: decisionPresentationIdOf(sent, key), provenance: ApprovalPresentationProvenance.SENT } };\n }\n const content = buildHumanDecisionPresentation(params.source);\n const id = decisionPresentationIdOf(content, key);\n if (!sameId(id, params.echoedPresentationId)) return { kind: DecisionPresentationCheckKind.STALE };\n return { kind: DecisionPresentationCheckKind.BOUND, presentation: { content, id, provenance: ApprovalPresentationProvenance.PRESENTED } };\n}\n\nfunction sameId(expected: string, given: string): boolean {\n // Both sides digested first, so the comparison reads equal-length buffers and its timing says nothing about either.\n const digest = (id: string) => createHash('sha256').update(id, 'utf8').digest();\n return timingSafeEqual(digest(expected), digest(given));\n}\n\n/** What a person answered, as their seal binds it. */\nexport type DecisionStatement = HumanDecision_Statement;\n\n/**\n * The approver's statement: the option, the values, the arguments they authored and the reason code. The free-text note\n * is left out on purpose: it is sent to the agent and the trail records only whether one was given, so a seal keeping its\n * words would keep what nothing else does.\n */\nexport function decisionStatementOf(params: {\n readonly optionRef: string;\n readonly values?: HumanDecision_Answer['values'];\n readonly modifiedToolArgs?: Readonly<Record<string, unknown>>;\n readonly reasonCode?: string;\n}): DecisionStatement {\n return {\n optionRef: params.optionRef,\n values: [...(params.values ?? [])]\n .sort((first, second) => first.ref.localeCompare(second.ref))\n .map((answered) => ({ key: answered.ref, value: humanDecisionPresentedValueOf(answered.value) })),\n ...(params.modifiedToolArgs ? { modifiedArguments: humanDecisionPresentedFieldsOf(params.modifiedToolArgs) } : {}),\n ...(params.reasonCode ? { reasonCode: params.reasonCode } : {}),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"decision-presentation.backend.js","sourceRoot":"","sources":["../../../../src/compliance/approval-seals/decision-presentation.backend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EACL,8BAA8B,EAC9B,gCAAgC,EAChC,6BAA6B,EAC7B,8BAA8B,EAC9B,iCAAiC,EACjC,uBAAuB,EACvB,8BAA8B,EAC9B,6BAA6B,GAU9B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,4BAA4B,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAC1G,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,4EAA4E,CAAC;AAEvH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAAG,mCAAmC,CAAC;AAEtF,qHAAqH;AACrH,MAAM,UAAU,yBAAyB,CAAC,UAA8B;IACtE,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7D,OAAO,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,MAAM,CAAC,kCAAkC,CAAC,CAAC,MAAM,EAAE,CAAC;AAC9F,CAAC;AAED;;;;;GAKG;AACH,IAAI,sBAA0C,CAAC;AAE/C,wHAAwH;AACxH,MAAM,UAAU,gCAAgC,CAAC,SAAwF;IACvI,MAAM,OAAO,GAAG,yBAAyB,CAAC,yBAAyB,CAAC,SAAS,EAAE,iBAAiB,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAC5H,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,sBAAsB,KAAK,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3C,OAAO,sBAAsB,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,YAAyE,EAAE,GAAW;IAC7H,OAAO,SAAS,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;AACtG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,IAAW,EACX,GAAW,EACX,YAA4D;AAC5D,sHAAsH;AACtH,aAA6C;IAE7C,MAAM,YAAY,GAAG,8BAA8B,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACzJ,OAAO,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,cAAc,EAAE,wBAAwB,CAAC,YAAY,EAAE,GAAG,CAAC,EAAE,CAAC;AAChG,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,oCAAoC,CAAC;AAEpF;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAW,EAAE,WAAiG;IACtJ,OAAO,SAAS,CACd,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,+BAA+B,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;SAChI,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,CACrF,CAAC;AACJ,CAAC;AAkBD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,MAWjD;IACC,MAAM,QAAQ,GAAG,MAAM,CAAC,mBAAmB,CAAC;IAC5C,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjE,MAAM,QAAQ,GAAiC,EAAE,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gCAAgC,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC;IAC7I,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IACnC,IAAI,aAAiC,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI;YAAE,OAAO,QAAQ,CAAC;QAC3B,gHAAgH;QAChH,MAAM,kBAAkB,GAAG,OAAO,IAAI,CAAC,cAAc,KAAK,QAAQ,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;QACvI,IAAI,kBAAkB,KAAK,SAAS,IAAI,kBAAkB,KAAK,MAAM,CAAC,cAAc;YAAE,OAAO,QAAQ,CAAC;QACtG,aAAa,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC/D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,CAAC;QAC9B,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,CAAC,aAAa;QAAE,OAAO,QAAQ,CAAC;IACpC,MAAM,IAAI,GAAG,yBAAyB,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1G,OAAO;QACL,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gCAAgC,CAAC,KAAK,EAAE,UAAU,EAAE,4BAA4B,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC;QACpJ,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;KACxB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,MAWlD;IACC,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IAC7D,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACpG,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,aAAa,CAAC,CAAC;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACtE,IAAI,GAA6C,CAAC;IAClD,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,aAAa,EAAE,CAAC,KAAK,CAAC,CAAC;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,iBAAiB,GAAG,GAAG,CAAC,cAAc,KAAK,SAAS,IAAI,GAAG,CAAC,cAAc,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5K,IAAI,iBAAiB,KAAK,SAAS,IAAI,iBAAiB,KAAK,MAAM,CAAC,cAAc;QAAE,OAAO,SAAS,CAAC;IACrG,OAAO,iCAAiC,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AAC3G,CAAC;AAKD,6DAA6D;AAC7D,MAAM,CAAN,IAAY,6BAKX;AALD,WAAY,6BAA6B;IACvC,uEAAuE;IACvE,gDAAe,CAAA;IACf,8EAA8E;IAC9E,gDAAe,CAAA;AACjB,CAAC,EALW,6BAA6B,KAA7B,6BAA6B,QAKxC;AAMD;;;;;;;;;GASG;AACH,MAAM,UAAU,+BAA+B,CAAC,MAO/C;IACC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;IACvB,IAAI,MAAM,CAAC,oBAAoB,KAAK,SAAS,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,8BAA8B,CAAC,uBAAuB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,IAAI,6BAA6B,CAAC,QAAQ,CAAC,CAAC,CAAC;QACtJ,OAAO,EAAE,IAAI,EAAE,6BAA6B,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,wBAAwB,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,8BAA8B,CAAC,IAAI,EAAE,EAAE,CAAC;IAClL,CAAC;IACD,MAAM,OAAO,GAAG,8BAA8B,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,EAAE,GAAG,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,oBAAoB,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,6BAA6B,CAAC,KAAK,EAAE,CAAC;IACnG,OAAO,EAAE,IAAI,EAAE,6BAA6B,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,8BAA8B,CAAC,SAAS,EAAE,EAAE,CAAC;AAC5I,CAAC;AAED,SAAS,MAAM,CAAC,QAAgB,EAAE,KAAa;IAC7C,oHAAoH;IACpH,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IAChF,OAAO,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAC1D,CAAC;AAKD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAKnC;IACC,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;aAC/B,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;aAC5D,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,6BAA6B,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACnG,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,8BAA8B,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClH,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * # A decision's presentation, as the server binds an answer to it (#2903 S6)\n *\n * `buildHumanDecisionPresentation` (`@wildo-ai/saas-models`) says what a person is shown; this file is what the server\n * does with it:\n *\n * - **an id** ({@link decisionPresentationIdOf}): HMAC-SHA-256 of the presentation's RFC 8785 canonical form, under a key\n * derived from the decision-presentation purpose key (#2893). Deterministic, so every replica gives one decision one id until\n * something the person decides on changes, with no store to keep in step; and keyed, so the id a seal carries cannot\n * confirm a guessed presentation to someone holding only the seal. The key label is versioned: a new derivation is a\n * new key, never a reinterpretation of the old one;\n * - **the check an answer passes** ({@link acknowledgeDecisionPresentation}): a door where the engine renders the\n * decision echoes the id it showed, and the act rebuilds the presentation from the decision's row and compares. An\n * echo that no longer matches is STALE and refused, so a person never approves something other than what they read.\n * An answer with no echo comes through a door that sends the decision and cannot watch it render (a chat card); its\n * seal records the presentation as SENT, what the engine built and sent;\n * - **the approver's statement** ({@link decisionStatementOf}): what the person answered, its values projected the way\n * presented values are, so the seal binds the answer as well as what it answered;\n * - **a tool's file deliverable** ({@link resolveDecisionDeliverables}, #2904): the file the call names through the\n * argument its tool declares, bound by `H(salt ‖ contentSha256)` with the hash computed now when the file has none\n * (#2900's `ensureContentHash`). The salt is derived from the presentation-id key, so every build of one decision\n * carries one commitment until the file's recorded hash changes, and then the id changes with it.\n *\n * The comparison runs after the decision's route admitted the person, from the row the settle then writes under its\n * compare-and-swap. Everything a presentation holds is fixed while the decision waits (a tool call's arguments, a held\n * operation's requested values, an admission's snapshot), so the row the check reads is the row the answer settles.\n */\nimport { createHash, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';\nimport {\n ApprovalPresentationProvenance,\n HumanDecision_DeliverableBinding,\n HumanDecision_SentCardContent,\n buildHumanDecisionPresentation,\n buildHumanDecisionPresentedRecord,\n humanDecisionAsSentCard,\n humanDecisionPresentedFieldsOf,\n humanDecisionPresentedValueOf,\n type HumanDecision_Answer,\n type HumanDecision_PendingCard,\n type HumanDecision_Presentation,\n type ApprovalSealRecordPresentation,\n type HumanDecision_PresentedDeliverable,\n type HumanDecision_PresentedRecord,\n type HumanDecision_SealedPresentation,\n type HumanDecision_Statement,\n type HumanDecisionPresentationSource,\n} from '@wildo-ai/saas-models';\nimport { approvalSealFileCommitmentOf, base64url, canonicalizeJson } from '@wildo-ai/platform-config-lib';\nimport { CryptoKey_Purpose } from '@wildo-ai/saas-models';\nimport type { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { applicationSymmetricKeyOf } from '~wildo-backend-lib/security/crypto-keys/application-symmetric-keys.backend';\n\n/**\n * Domain separation for the presentation-id key, derived from the decision-presentation purpose key (#2893; until then,\n * from the application's primary secret, under the same label).\n *\n * semantic-literal-boundary: key material, not a vocabulary member. Changing it changes every presentation id, which\n * refuses every answer given on a card read before the change: version it, never tidy it.\n */\nexport const DECISION_PRESENTATION_ID_KEY_LABEL = 'wildo:decision-presentation-id:v1';\n\n/** The presentation-id key derived from the decision-presentation purpose key, or `undefined` when there is none. */\nexport function decisionPresentationKeyOf(purposeKey: Buffer | undefined): Buffer | undefined {\n if (!purposeKey || purposeKey.length === 0) return undefined;\n return createHmac('sha256', purposeKey).update(DECISION_PRESENTATION_ID_KEY_LABEL).digest();\n}\n\n/**\n * A key for a process that holds no decision-presentation key (a host that loaded no application secrets): random,\n * drawn once per process. Ids then hold only for this process's life, so a restart makes every open card stale and its\n * next answer is refused and read again, which is the safe direction. The platform delivers the purpose key to the\n * backend and the minions alike (#2893), so no two runtimes of an application answer with keys that differ.\n */\nlet processPresentationKey: Buffer | undefined;\n\n/** The presentation-id key of THIS runtime: derived from its delivered purpose key, else drawn once for the process. */\nexport function decisionPresentationKeyOfRuntime(appConfig: Pick<AppConfigurationService, 'isInitialized' | 'secrets'> | null | undefined): Buffer {\n const derived = decisionPresentationKeyOf(applicationSymmetricKeyOf(appConfig, CryptoKey_Purpose.DECISION_PRESENTATION_ID));\n if (derived) return derived;\n processPresentationKey ??= randomBytes(32);\n return processPresentationKey;\n}\n\n/**\n * The id of one presentation under `key`: base64url HMAC-SHA-256 of its canonical form. A card's presentation, or a\n * decision an operation settles presented as its record (#2906), whose id a step-up proof is bound to (#2923).\n */\nexport function decisionPresentationIdOf(presentation: HumanDecision_Presentation | ApprovalSealRecordPresentation, key: Buffer): string {\n return base64url(createHmac('sha256', key).update(canonicalizeJson(presentation), 'utf8').digest());\n}\n\n/**\n * A decision card carrying what it presents and the id an answer echoes. A tool approval's file deliverable, resolved by\n * {@link resolveDecisionDeliverables}, joins the presentation and only the presentation: the card has no field of its own\n * for it, so nothing reads it anywhere but where the id covers it.\n */\nexport function withDecisionPresentation<TCard extends HumanDecisionPresentationSource>(\n card: TCard,\n key: Buffer,\n deliverables?: readonly HumanDecision_PresentedDeliverable[],\n /** #2914: the record the call acts on, with the fields its approval covers ({@link resolveDecisionSubjectRecord}). */\n subjectRecord?: HumanDecision_PresentedRecord,\n): TCard & Required<Pick<HumanDecision_PendingCard, 'presentation' | 'presentationId'>> {\n const presentation = buildHumanDecisionPresentation({ ...card, ...(deliverables ? { deliverables } : {}), ...(subjectRecord ? { subjectRecord } : {}) });\n return { ...card, presentation, presentationId: decisionPresentationIdOf(presentation, key) };\n}\n\n/**\n * Domain separation for a file deliverable's salt (#2904), derived under the presentation-id key.\n *\n * semantic-literal-boundary: key material, not a vocabulary member. Changing it changes every deliverable's commitment,\n * which refuses every answer given on a card read before the change: version it, never tidy it.\n */\nexport const DECISION_DELIVERABLE_SALT_LABEL = 'wildo:decision-deliverable-salt:v1';\n\n/**\n * The salt a decision's file deliverable is committed with (#2904): base64url HMAC-SHA-256 under the presentation-id key,\n * over the decision, the argument and the file. DERIVED rather than drawn, because the presentation it joins must be\n * one value on every build of the decision (its id is what an answer echoes); keyed, so nobody without the application's\n * key can predict it and test a guessed document against a commitment. The seal's row keeps the value, so a seal opens\n * after the key rotates, and a reopened decision's later seal of the same file carries the same salt and commitment,\n * which says nothing the first did not.\n */\nexport function decisionDeliverableSaltOf(key: Buffer, deliverable: { readonly decisionRef: string; readonly argument: string; readonly fileId: string }): string {\n return base64url(\n createHmac('sha256', key).update(DECISION_DELIVERABLE_SALT_LABEL).update('\\u0000').update(deliverable.decisionRef).update('\\u0000')\n .update(deliverable.argument).update('\\u0000').update(deliverable.fileId).digest(),\n );\n}\n\n/** What binding a file deliverable reads of the file (#2904): its row, and its hash, computed now when it has none. */\nexport interface DecisionDeliverableFiles {\n /** The file's row, read as the system; null when there is none. */\n readFile(fileId: string): Promise<{ readonly organizationId?: string | null } | null>;\n /** `FilesBackendService.ensureContentHash`: the recorded hash, else the bytes hashed now and written once (#2900). */\n ensureContentHash(fileId: string): Promise<string | undefined>;\n}\n\n/** A decision's file deliverable as its presentation binds it, and the salt a seal keeps for a bound one. */\nexport interface ResolvedDecisionDeliverables {\n /** The presentation's `deliverables`; absent when the tool declares none or the call names no file. */\n readonly deliverables?: readonly HumanDecision_PresentedDeliverable[];\n /** The bound file and its salt: what the seal attests and keeps. Absent when nothing is bound. */\n readonly bound?: { readonly fileId: string; readonly salt: string };\n}\n\n/**\n * Resolve a tool approval's file deliverable (#2904): the file the call names through the argument its tool declares\n * (`FunctionTool.deliverableFileArgument`), bound by its content hash, computed now when the file has none\n * (`ensureContentHash`, #2900's obligation on the first binding of a file), BEFORE the commitment is made.\n *\n * NOT_BOUND, never an empty hash, when nothing can be hashed: no file service, no such file, an id the files store\n * refuses, a remote reference whose vendor keeps the bytes, a deleted file, a file of an organization the decision does\n * not belong to (a card must not show, or a seal freeze, another tenant's file), or a read that failed. Each of those\n * reads alike, so the card discloses nothing about a file its reader may not see. Every surface building the card and\n * the act checking the answer call this one function, so they agree, and an answer given on a card built before the\n * file's hash changed is refused as stale.\n *\n * assurance-control: WILDO.AI.HUMAN_OVERSIGHT, WILDO.AUDIT.EVIDENCE_INTEGRITY — the file a person approves is bound to its bytes by the presentation and the seal; an approval never binds a file it could not hash.\n */\nexport async function resolveDecisionDeliverables(params: {\n readonly decisionRef: string;\n /** The argument the tool declares names its file deliverable; absent when it declares none. */\n readonly deliverableArgument: string | undefined;\n readonly toolArgs: Readonly<Record<string, unknown>> | undefined;\n /** The organization the decision belongs to; absent for a decision of the application. */\n readonly organizationId?: string;\n readonly files: DecisionDeliverableFiles | undefined;\n readonly key: Buffer;\n /** Told when a read failed, so a NOT_BOUND that hides an outage is still seen. */\n readonly onReadFailure?: (error: unknown) => void;\n}): Promise<ResolvedDecisionDeliverables> {\n const argument = params.deliverableArgument;\n if (!argument) return {};\n const fileId = params.toolArgs?.[argument];\n if (typeof fileId !== 'string' || fileId.length === 0) return {};\n const notBound: ResolvedDecisionDeliverables = { deliverables: [{ argument, fileId, binding: HumanDecision_DeliverableBinding.NOT_BOUND }] };\n if (!params.files) return notBound;\n let contentSha256: string | undefined;\n try {\n const file = await params.files.readFile(fileId);\n if (!file) return notBound;\n // A file of an organization is bound only by a decision of that organization; a file of the application by any.\n const fileOrganizationId = typeof file.organizationId === 'string' && file.organizationId.length > 0 ? file.organizationId : undefined;\n if (fileOrganizationId !== undefined && fileOrganizationId !== params.organizationId) return notBound;\n contentSha256 = await params.files.ensureContentHash(fileId);\n } catch (error) {\n params.onReadFailure?.(error);\n return notBound;\n }\n if (!contentSha256) return notBound;\n const salt = decisionDeliverableSaltOf(params.key, { decisionRef: params.decisionRef, argument, fileId });\n return {\n deliverables: [{ argument, fileId, binding: HumanDecision_DeliverableBinding.BOUND, commitment: approvalSealFileCommitmentOf(salt, contentSha256) }],\n bound: { fileId, salt },\n };\n}\n\n/**\n * The record a tool approval is about, with the fields its tool declares the approval covers (#2914,\n * `FunctionTool.sealedRecordFields`): the row named by the call's `rowIdArgument`, read as the system when the card is\n * built, its declared values projected like presented arguments. The announcement, the history, the inbox and the act all\n * resolve it here, so they agree on the presentation and its id: a card read before one of those values changed carries\n * another id, and its answer is refused as stale.\n *\n * Undefined, never a guess, when the tool declares no field set, the call names no row, the row cannot be read, or the\n * row belongs to another organization than the decision (a card must not show another tenant's values). Each reads\n * alike, so the card discloses nothing about a row its reader may not see.\n *\n * assurance-control: WILDO.AI.HUMAN_OVERSIGHT, WILDO.AUDIT.EVIDENCE_INTEGRITY — the record values an approval covers are shown to the person and sealed with their answer, so whether the approval still holds can be computed later.\n */\nexport async function resolveDecisionSubjectRecord(params: {\n readonly sealedFields: readonly string[] | undefined;\n readonly resourceType: string | undefined;\n readonly rowIdArgument: string | undefined;\n readonly toolArgs: Readonly<Record<string, unknown>> | undefined;\n /** The organization the decision belongs to; absent for a decision of the application. */\n readonly organizationId?: string;\n /** Reads the row as the system; null when there is none. */\n readonly readRow: (resourceType: string, rowId: string) => Promise<Readonly<Record<string, unknown>> | null>;\n /** Told when a read failed, so an absent record that hides an outage is still seen. */\n readonly onReadFailure?: (error: unknown) => void;\n}): Promise<HumanDecision_PresentedRecord | undefined> {\n const { sealedFields, resourceType, rowIdArgument } = params;\n if (!sealedFields || sealedFields.length === 0 || !resourceType || !rowIdArgument) return undefined;\n const rowId = params.toolArgs?.[rowIdArgument];\n if (typeof rowId !== 'string' || rowId.length === 0) return undefined;\n let row: Readonly<Record<string, unknown>> | null;\n try {\n row = await params.readRow(resourceType, rowId);\n } catch (error) {\n params.onReadFailure?.(error);\n return undefined;\n }\n if (!row) return undefined;\n const rowOrganizationId = row.organizationId !== undefined && row.organizationId !== null && String(row.organizationId).length > 0 ? String(row.organizationId) : undefined;\n if (rowOrganizationId !== undefined && rowOrganizationId !== params.organizationId) return undefined;\n return buildHumanDecisionPresentedRecord({ resourceType, resourceId: rowId, sealedFields, record: row });\n}\n\n/** The presentation a seal binds: what was built, its id when one could be derived, and how its rendering is known. */\nexport type SealedDecisionPresentation = HumanDecision_SealedPresentation;\n\n/** What checking an answer's echoed presentation came to. */\nexport enum DecisionPresentationCheckKind {\n /** The answer is bound to the decision's presentation as it is now. */\n BOUND = 'bound',\n /** The echo is not the decision's presentation now: the answer is refused. */\n STALE = 'stale',\n}\n\nexport type DecisionPresentationCheck =\n | { readonly kind: DecisionPresentationCheckKind.BOUND; readonly presentation: SealedDecisionPresentation }\n | { readonly kind: DecisionPresentationCheckKind.STALE };\n\n/**\n * Check an answer against the decision's presentation as its row holds it now. An echo is compared with the id rebuilt\n * from the row, in constant time over the two ids' digests; a match binds the answer PRESENTED. No echo binds it SENT:\n * every door that renders the decision refuses an answer without one before it gets here, so only a door that sends the\n * decision elsewhere (a chat card) reaches this without one. A SENT presentation is what that card CARRIED\n * (`sentCardContent`, set by the chat door from the application's notice handler): a chat card without the proposed\n * values seals none (#2903 review F2). An in-process door that names nothing seals the whole presentation.\n *\n * assurance-control: WILDO.AI.HUMAN_OVERSIGHT, WILDO.AUDIT.EVIDENCE_INTEGRITY — a person's answer is bound to the presentation they were shown, rebuilt from the decision's row; an answer given on what is no longer the decision is refused.\n */\nexport function acknowledgeDecisionPresentation(params: {\n readonly source: HumanDecisionPresentationSource;\n readonly echoedPresentationId?: string;\n /** What the card the answer came from carried, when the engine sent it elsewhere. Absent: the whole presentation. */\n readonly sentCardContent?: HumanDecision_SentCardContent;\n /** The runtime's presentation-id key ({@link decisionPresentationKeyOfRuntime}). */\n readonly key: Buffer;\n}): DecisionPresentationCheck {\n const key = params.key;\n if (params.echoedPresentationId === undefined) {\n const sent = buildHumanDecisionPresentation(humanDecisionAsSentCard(params.source, params.sentCardContent ?? HumanDecision_SentCardContent.COMPLETE));\n return { kind: DecisionPresentationCheckKind.BOUND, presentation: { content: sent, id: decisionPresentationIdOf(sent, key), provenance: ApprovalPresentationProvenance.SENT } };\n }\n const content = buildHumanDecisionPresentation(params.source);\n const id = decisionPresentationIdOf(content, key);\n if (!sameId(id, params.echoedPresentationId)) return { kind: DecisionPresentationCheckKind.STALE };\n return { kind: DecisionPresentationCheckKind.BOUND, presentation: { content, id, provenance: ApprovalPresentationProvenance.PRESENTED } };\n}\n\nfunction sameId(expected: string, given: string): boolean {\n // Both sides digested first, so the comparison reads equal-length buffers and its timing says nothing about either.\n const digest = (id: string) => createHash('sha256').update(id, 'utf8').digest();\n return timingSafeEqual(digest(expected), digest(given));\n}\n\n/** What a person answered, as their seal binds it. */\nexport type DecisionStatement = HumanDecision_Statement;\n\n/**\n * The approver's statement: the option, the values, the arguments they authored and the reason code. The free-text note\n * is left out on purpose: it is sent to the agent and the trail records only whether one was given, so a seal keeping its\n * words would keep what nothing else does.\n */\nexport function decisionStatementOf(params: {\n readonly optionRef: string;\n readonly values?: HumanDecision_Answer['values'];\n readonly modifiedToolArgs?: Readonly<Record<string, unknown>>;\n readonly reasonCode?: string;\n}): DecisionStatement {\n return {\n optionRef: params.optionRef,\n values: [...(params.values ?? [])]\n .sort((first, second) => first.ref.localeCompare(second.ref))\n .map((answered) => ({ key: answered.ref, value: humanDecisionPresentedValueOf(answered.value) })),\n ...(params.modifiedToolArgs ? { modifiedArguments: humanDecisionPresentedFieldsOf(params.modifiedToolArgs) } : {}),\n ...(params.reasonCode ? { reasonCode: params.reasonCode } : {}),\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pdf-signature-structure.backend.d.ts","sourceRoot":"","sources":["../../../../src/compliance/electronic-signatures/pdf-signature-structure.backend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pdf-signature-structure.backend.d.ts","sourceRoot":"","sources":["../../../../src/compliance/electronic-signatures/pdf-signature-structure.backend.ts"],"names":[],"mappings":"AA6DA,8FAA8F;AAC9F,oBAAY,4BAA4B;IACtC,uEAAuE;IACvE,YAAY,iBAAiB;IAC7B,kHAAkH;IAClH,oBAAoB,yBAAyB;IAC7C,0EAA0E;IAC1E,mCAAmC,wCAAwC;IAC3E,0EAA0E;IAC1E,qBAAqB,0BAA0B;IAC/C,0FAA0F;IAC1F,sBAAsB,2BAA2B;IACjD,6HAA6H;IAC7H,mBAAmB,wBAAwB;CAC5C;AAED,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IAChC,kCAAkC;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9D,qHAAqH;IACrH,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,qFAAqF;IACrF,QAAQ,CAAC,kBAAkB,EAAE,IAAI,GAAG,SAAS,CAAC;IAC9C,gEAAgE;IAChE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,kGAAkG;AAClG,eAAO,MAAM,iCAAiC,EAAE,WAAW,CAAC,MAAM,CAA2D,CAAC;AAE9H,MAAM,MAAM,qBAAqB,GAC7B;IACA,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAClD,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC,GACC;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,4BAA4B,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpG,8GAA8G;AAC9G,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,GAAG,qBAAqB,CAMnH;AAinBD,8FAA8F;AAC9F,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,CAYxE;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAKzF"}
|
|
@@ -40,6 +40,16 @@
|
|
|
40
40
|
* detected; that is this analysis's stated residual. A vendor whose updates this refuses leaves the request open, never
|
|
41
41
|
* SIGNED.
|
|
42
42
|
*
|
|
43
|
+
* Why the residual stays (re-examined 2026-11-12). The published shadow attacks hide content in the document BEFORE it is
|
|
44
|
+
* signed and reveal it in a later update; here the bytes before the signature are the operator's own approved original,
|
|
45
|
+
* and every later update is confined above, so what remains is a widget whose own appearance paints over the page inside
|
|
46
|
+
* its rectangle. Telling a legitimate signature image from one that covers text needs the page RENDERED with and without
|
|
47
|
+
* the widget and compared, which is a PDF renderer in the engine; a rule on the rectangle's size or place would refuse
|
|
48
|
+
* honest visible signatures and still admit a small overlay over one figure. The deliverable that was approved is kept as
|
|
49
|
+
* its frozen original (#2900), so a reader can compare it with the signed copy.
|
|
50
|
+
* Cross-reference updates written as a HYBRID file (a classic section naming a cross-reference stream through /XRefStm)
|
|
51
|
+
* are admitted since 2026-11-12; see `analyseAppendedRevisions`.
|
|
52
|
+
*
|
|
43
53
|
* spec-cite: ISO 32000-1:2008 §7.5.6 (incremental updates), §12.8.1 (signature dictionary: `ByteRange`, `Contents`,
|
|
44
54
|
* `SubFilter`, `M`) and ETSI EN 319 142-1 §5.4 (the DSS dictionary); paraphrased from the structure they define, not
|
|
45
55
|
* quoted.
|
|
@@ -403,11 +413,27 @@ function analyseAppendedRevisions(text, originalLength, signatures, lastCovered)
|
|
|
403
413
|
return refuse(PdfSignatureStructureRefusal.NOT_A_PERMITTED_CHANGE, 'The file\'s last startxref does not name its last cross-reference section.');
|
|
404
414
|
}
|
|
405
415
|
// Each appended section chains to the one immediately before it (the first to the original's last): a /Prev that skips
|
|
406
|
-
// a revision would show a reader the document without, say, the signature that revision added.
|
|
407
|
-
//
|
|
408
|
-
//
|
|
416
|
+
// a revision would show a reader the document without, say, the signature that revision added.
|
|
417
|
+
//
|
|
418
|
+
// A HYBRID update is the one exception. Its cross-reference stream is not a revision of its own: the classic section of
|
|
419
|
+
// the same update names it through /XRefStm, and a reader reaches it only from there, searching it before that section's
|
|
420
|
+
// /Prev. ISO 32000-1 says a cross-reference stream's /Prev is "not meaningful in hybrid-reference files".
|
|
421
|
+
// spec-cite: ISO 32000-1:2008 §7.5.8.2 Table 17 (the Prev row), and §7.5.8.4 (the XRefStm search order).
|
|
422
|
+
// So a cross-reference stream that the VERY NEXT appended section names by /XRefStm is admitted without a /Prev of its
|
|
423
|
+
// own (or with one equal to that section's, which changes nothing), and does not advance the chain. Its entries are
|
|
424
|
+
// judged with every other appended entry below. Until 2026-11-12 every hybrid update was refused.
|
|
425
|
+
const hybridCompanions = new Set();
|
|
409
426
|
let expectedPrev = originalLastSection;
|
|
410
|
-
for (const { entries, sectionOffset } of region.trailers) {
|
|
427
|
+
for (const [index, { entries, sectionOffset }] of region.trailers.entries()) {
|
|
428
|
+
const namingSection = region.trailers[index + 1];
|
|
429
|
+
const hybridCompanion = entries.get('/Type') === '/XRef'
|
|
430
|
+
&& namingSection?.entries.get('/XRefStm') !== undefined
|
|
431
|
+
&& Number(namingSection.entries.get('/XRefStm')) === sectionOffset
|
|
432
|
+
&& (entries.get('/Prev') === undefined || Number(entries.get('/Prev')) === expectedPrev);
|
|
433
|
+
if (hybridCompanion) {
|
|
434
|
+
hybridCompanions.add(sectionOffset);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
411
437
|
// An incremental update chains to what it updates: a section without /Prev would hide the whole original from a reader.
|
|
412
438
|
if (entries.get('/Prev') === undefined || Number(entries.get('/Prev')) !== expectedPrev) {
|
|
413
439
|
return refuse(PdfSignatureStructureRefusal.NOT_A_PERMITTED_CHANGE, 'An appended cross-reference section does not chain to the revision it updates.');
|
|
@@ -423,9 +449,11 @@ function analyseAppendedRevisions(text, originalLength, signatures, lastCovered)
|
|
|
423
449
|
}
|
|
424
450
|
}
|
|
425
451
|
}
|
|
426
|
-
for (const { entries: trailer } of region.trailers) {
|
|
452
|
+
for (const { entries: trailer, sectionOffset } of region.trailers) {
|
|
427
453
|
const root = trailer.get('/Root');
|
|
428
|
-
|
|
454
|
+
// A hybrid companion is read only through the section naming it, whose /Root a reader takes; it may name none.
|
|
455
|
+
const rootOptional = hybridCompanions.has(sectionOffset);
|
|
456
|
+
if ((root === undefined && !rootOptional) || (root !== undefined && normalizeReference(root) !== normalizeReference(originalRoot))) {
|
|
429
457
|
return refuse(PdfSignatureStructureRefusal.NOT_A_PERMITTED_CHANGE, 'An appended trailer names another catalog than the bytes sent.');
|
|
430
458
|
}
|
|
431
459
|
const encrypt = trailer.get('/Encrypt');
|