@sema-agent/server 7.131.0 → 7.133.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/MIGRATION.md +575 -0
- package/README.md +12 -9
- package/README.zh-CN.md +9 -6
- package/USAGE.md +326 -98
- package/deploy/sema-up/README.md +19 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
- package/deploy/sema-up/chart/templates/server.yaml +5 -2
- package/deploy/sema-up/stack/compose.yaml +3 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
- package/dist/adoption/plan.d.ts +3 -2
- package/dist/adoption/plan.js +7 -5
- package/dist/adoption/runner.js +3 -10
- package/dist/adoption/sql.d.ts +3 -2
- package/dist/adoption/sql.js +3 -5
- package/dist/adoption/wire.d.ts +4 -1
- package/dist/adoption/wire.js +1 -0
- package/dist/approval-ask-audit-store.d.ts +31 -13
- package/dist/approval-ask-audit-store.js +272 -74
- package/dist/approval-ask-machine.d.ts +15 -0
- package/dist/approval-ask-machine.js +6 -0
- package/dist/approval-ask-retention.d.ts +136 -0
- package/dist/approval-ask-retention.js +102 -0
- package/dist/approval-card.d.ts +82 -20
- package/dist/approval-card.js +46 -16
- package/dist/approval-deny-reasons.d.ts +1 -1
- package/dist/approval.d.ts +9 -0
- package/dist/approval.js +24 -3
- package/dist/audit.d.ts +33 -19
- package/dist/audit.js +12 -16
- package/dist/auth-bridge.d.ts +4 -0
- package/dist/auth-bridge.js +10 -11
- package/dist/bake-runner/main.js +3 -2
- package/dist/boot/deferred-sandbox-path-env.js +1 -1
- package/dist/boot/deployment-mods.d.ts +31 -0
- package/dist/boot/deployment-mods.js +26 -0
- package/dist/boot/execution-env.d.ts +1 -1
- package/dist/boot/execution-env.js +27 -12
- package/dist/boot/execution-lane.js +11 -0
- package/dist/boot/leader.js +2 -0
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/memory-boundary.js +2 -2
- package/dist/boot/memory-consolidation.d.ts +5 -4
- package/dist/boot/memory-consolidation.js +7 -6
- package/dist/boot/org-delivery.d.ts +28 -0
- package/dist/boot/org-delivery.js +20 -0
- package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
- package/dist/boot/read-deny-anchor-audit.js +36 -0
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +21 -1
- package/dist/boot/resolve-spec.d.ts +4 -5
- package/dist/boot/resolve-spec.js +74 -123
- package/dist/boot/runner-deps.d.ts +19 -8
- package/dist/boot/runner-deps.js +15 -2
- package/dist/boot/session-faces.d.ts +2 -7
- package/dist/boot/session-faces.js +4 -2
- package/dist/boot/stage-02-web-admin-pull.js +4 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -4
- package/dist/boot/stage-05-execution-env.js +7 -10
- package/dist/boot/stage-06-runners.d.ts +4 -2
- package/dist/boot/stage-06-runners.js +11 -7
- package/dist/boot/stage-07-capability-layer.d.ts +4 -2
- package/dist/boot/stage-07-capability-layer.js +27 -6
- package/dist/boot/stage-08-reapers.d.ts +4 -2
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +6 -9
- package/dist/boot/stage-10-http-server.d.ts +6 -9
- package/dist/boot/stage-10-http-server.js +7 -9
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +7 -8
- package/dist/boot/web-admin.d.ts +4 -3
- package/dist/boot/web-admin.js +186 -98
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/brain.js +25 -25
- package/dist/breaker-key.d.ts +6 -0
- package/dist/breaker-key.js +29 -0
- package/dist/budget.js +5 -4
- package/dist/caller-text-echo.d.ts +23 -0
- package/dist/caller-text-echo.js +21 -0
- package/dist/capabilities/brand-identity.d.ts +26 -0
- package/dist/capabilities/brand-identity.js +27 -0
- package/dist/capabilities/center-plugins.d.ts +7 -0
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/center-prompts.d.ts +32 -59
- package/dist/capabilities/center-prompts.js +18 -32
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/engine-capabilities.js +2 -1
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +30 -6
- package/dist/capabilities/hands-lane.js +11 -1
- package/dist/capabilities/mcp-probe.d.ts +48 -3
- package/dist/capabilities/mcp-probe.js +47 -135
- package/dist/capabilities/scenarios.d.ts +95 -45
- package/dist/capabilities/scenarios.js +53 -38
- package/dist/capabilities/skills.d.ts +19 -4
- package/dist/capabilities/skills.js +30 -10
- package/dist/capability-facts.d.ts +34 -4
- package/dist/capability-facts.js +28 -2
- package/dist/center-config-tier.d.ts +69 -0
- package/dist/center-config-tier.js +42 -0
- package/dist/checkpoint-identity.d.ts +16 -0
- package/dist/checkpoint-identity.js +19 -0
- package/dist/config-catalog.d.ts +17 -10
- package/dist/config-catalog.js +47 -32
- package/dist/config-d-migration.d.ts +3 -1
- package/dist/config-d-migration.js +2 -2
- package/dist/config-dir-trust.d.ts +114 -0
- package/dist/config-dir-trust.js +388 -0
- package/dist/config-invariants.d.ts +18 -5
- package/dist/config-invariants.js +35 -3
- package/dist/config-lkg.d.ts +9 -9
- package/dist/config-lkg.js +20 -4
- package/dist/config-provider.d.ts +9 -2
- package/dist/config-provider.js +18 -6
- package/dist/config-types.d.ts +75 -20
- package/dist/config-types.js +8 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +137 -62
- package/dist/delegated-session-owner.d.ts +185 -0
- package/dist/delegated-session-owner.js +119 -0
- package/dist/device-protocol.d.ts +5 -3
- package/dist/device-protocol.js +1 -1
- package/dist/device-ws-hub.js +21 -2
- package/dist/device-ws-protocol.d.ts +70 -6
- package/dist/device-ws-protocol.js +50 -22
- package/dist/elicitation.js +3 -2
- package/dist/entry-problems.d.ts +34 -0
- package/dist/entry-problems.js +14 -0
- package/dist/env-name-allowlist-knobs.d.ts +12 -2
- package/dist/env-name-allowlist-knobs.js +5 -4
- package/dist/exec-bounds.d.ts +15 -3
- package/dist/exec-bounds.js +4 -0
- package/dist/execution-lane-caps.d.ts +56 -10
- package/dist/execution-lane-caps.js +50 -0
- package/dist/field-conflict.d.ts +43 -0
- package/dist/field-conflict.js +20 -0
- package/dist/fleet/fleet-bus.d.ts +80 -6
- package/dist/fleet/fleet-bus.js +41 -9
- package/dist/hooks/classic-stations.d.ts +91 -0
- package/dist/hooks/classic-stations.js +27 -0
- package/dist/hooks/command-spawn.d.ts +57 -0
- package/dist/hooks/command-spawn.js +139 -0
- package/dist/hooks/hook-llm.d.ts +8 -5
- package/dist/hooks/hook-llm.js +79 -36
- package/dist/hooks/hook-runner.d.ts +349 -95
- package/dist/hooks/hook-runner.js +855 -568
- package/dist/hooks/hook-source.d.ts +44 -0
- package/dist/hooks/hook-source.js +40 -0
- package/dist/http/active-run-conflict.d.ts +5 -13
- package/dist/http/active-run-conflict.js +17 -27
- package/dist/http/admission.d.ts +8 -2
- package/dist/http/admission.js +188 -101
- package/dist/http/approval-sweeps.js +99 -21
- package/dist/http/caller-trust.d.ts +77 -0
- package/dist/http/caller-trust.js +143 -0
- package/dist/http/checkpoint-ctx.d.ts +41 -24
- package/dist/http/checkpoint-ctx.js +23 -16
- package/dist/http/claimed-run.d.ts +14 -0
- package/dist/http/claimed-run.js +15 -0
- package/dist/http/device-placement.d.ts +2 -4
- package/dist/http/device-placement.js +7 -11
- package/dist/http/dispatch.d.ts +0 -4
- package/dist/http/dispatch.js +15 -13
- package/dist/http/durable-run-leg.d.ts +6 -33
- package/dist/http/durable-run-leg.js +36 -10
- package/dist/http/health-self-report.d.ts +7 -5
- package/dist/http/health-self-report.js +4 -18
- package/dist/http/owner-access.d.ts +56 -0
- package/dist/http/owner-access.js +42 -0
- package/dist/http/principal-gate.d.ts +70 -28
- package/dist/http/principal-gate.js +61 -15
- package/dist/http/readiness.d.ts +11 -5
- package/dist/http/readiness.js +10 -3
- package/dist/http/refusal-body.d.ts +21 -0
- package/dist/http/refusal-body.js +4 -0
- package/dist/http/request-guards.d.ts +43 -23
- package/dist/http/request-guards.js +53 -17
- package/dist/http/resume-ctx-read.d.ts +2 -5
- package/dist/http/resume-ctx-read.js +7 -4
- package/dist/http/resume-legs.d.ts +28 -4
- package/dist/http/resume-legs.js +169 -91
- package/dist/http/retired-service-deps.d.ts +43 -0
- package/dist/http/retired-service-deps.js +18 -0
- package/dist/http/route-ctx.d.ts +44 -28
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/a2a-serve.js +3 -4
- package/dist/http/routes/admin-config-refresh.js +1 -1
- package/dist/http/routes/admin-drain.js +1 -1
- package/dist/http/routes/admin-memory-optout.js +5 -1
- package/dist/http/routes/adoption.js +2 -9
- package/dist/http/routes/approvals-assistant.js +49 -48
- package/dist/http/routes/background.js +3 -2
- package/dist/http/routes/capabilities.js +21 -5
- package/dist/http/routes/config-catalog.d.ts +3 -3
- package/dist/http/routes/config-catalog.js +1 -1
- package/dist/http/routes/devices.js +3 -17
- package/dist/http/routes/diagnostics.js +6 -2
- package/dist/http/routes/images.js +6 -7
- package/dist/http/routes/leader.js +1 -8
- package/dist/http/routes/mcp-reconnect.d.ts +1 -1
- package/dist/http/routes/mcp-reconnect.js +3 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-consolidation.js +1 -1
- package/dist/http/routes/memory-origin.js +1 -1
- package/dist/http/routes/memory-policy.d.ts +1 -1
- package/dist/http/routes/memory-policy.js +3 -2
- package/dist/http/routes/notify-wake.js +8 -21
- package/dist/http/routes/observability.js +2 -2
- package/dist/http/routes/principal-inbox.d.ts +39 -0
- package/dist/http/routes/principal-inbox.js +76 -0
- package/dist/http/routes/retention-ops.js +2 -9
- package/dist/http/routes/rules.js +5 -18
- package/dist/http/routes/runs.d.ts +5 -0
- package/dist/http/routes/runs.js +34 -107
- package/dist/http/routes/session-sync.js +15 -26
- package/dist/http/routes/sessions.js +30 -40
- package/dist/http/routes/shared-memory.js +2 -13
- package/dist/http/routes/side-query.js +8 -7
- package/dist/http/routes/tasks.js +67 -21
- package/dist/http/routes/workflows.d.ts +351 -0
- package/dist/http/routes/workflows.js +153 -119
- package/dist/http/send.d.ts +33 -10
- package/dist/http/send.js +21 -4
- package/dist/http/server.d.ts +27 -18
- package/dist/http/server.js +34 -6
- package/dist/http/stored-body-replay.js +17 -4
- package/dist/http/submit-ctx.d.ts +25 -0
- package/dist/http/submit-ctx.js +40 -0
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +5 -4
- package/dist/http/wire-types.d.ts +11 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -2
- package/dist/lane-home-probe.d.ts +112 -0
- package/dist/lane-home-probe.js +101 -0
- package/dist/leader/endpoint.d.ts +4 -1
- package/dist/leader/endpoint.js +11 -10
- package/dist/limit-refusal.d.ts +59 -0
- package/dist/limit-refusal.js +15 -0
- package/dist/main.js +1 -1
- package/dist/mcp-assertion-header.d.ts +32 -19
- package/dist/mcp-assertion-header.js +10 -9
- package/dist/mcp-tool-faces.d.ts +5 -0
- package/dist/mcp-tool-faces.js +6 -4
- package/dist/memory-project-layer.d.ts +37 -0
- package/dist/memory-project-layer.js +46 -0
- package/dist/memory-scope.d.ts +24 -17
- package/dist/memory-scope.js +24 -8
- package/dist/memory-sync-client.d.ts +27 -13
- package/dist/memory-sync-client.js +107 -18
- package/dist/model-entry-refusal.d.ts +9 -1
- package/dist/model-entry-refusal.js +6 -1
- package/dist/model-plane-face.d.ts +51 -0
- package/dist/model-plane-face.js +24 -0
- package/dist/model-provider.d.ts +44 -12
- package/dist/model-provider.js +21 -2
- package/dist/model-route-endpoint.d.ts +9 -12
- package/dist/model-route-endpoint.js +8 -11
- package/dist/model-route-env.d.ts +22 -0
- package/dist/model-route-env.js +2 -0
- package/dist/model-route-gap.d.ts +60 -30
- package/dist/model-route-gap.js +21 -15
- package/dist/observability/corrupt-read-seat.d.ts +7 -0
- package/dist/observability/corrupt-read-seat.js +9 -0
- package/dist/observability/cost-taxonomy.d.ts +7 -7
- package/dist/observability/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +18 -4
- package/dist/observability/fail-open.d.ts +76 -14
- package/dist/observability/fail-open.js +76 -14
- package/dist/observability/metrics.js +4 -1
- package/dist/observability/prompt-manifest.d.ts +33 -82
- package/dist/observability/prompt-manifest.js +122 -17
- package/dist/observability/run-terminal.js +14 -6
- package/dist/observability/secret-env-scrub.d.ts +13 -1
- package/dist/observability/secret-env-scrub.js +13 -1
- package/dist/observability/tool-trace.d.ts +16 -21
- package/dist/observability/tool-trace.js +58 -49
- package/dist/operator-ask.d.ts +10 -0
- package/dist/operator-ask.js +3 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
- package/dist/orchestration/workflow-agent-session-index.js +9 -13
- package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
- package/dist/orchestration/workflow-completion-inbox.js +13 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
- package/dist/orchestration/workflow-notify-journal.js +27 -34
- package/dist/orchestration/workflow-park-decision.d.ts +89 -0
- package/dist/orchestration/workflow-park-decision.js +118 -0
- package/dist/org-delivery-gate.d.ts +81 -0
- package/dist/org-delivery-gate.js +59 -0
- package/dist/panel-tool-names.d.ts +23 -0
- package/dist/panel-tool-names.js +8 -0
- package/dist/parked-decide.d.ts +39 -6
- package/dist/parked-decide.js +98 -51
- package/dist/parked-revive-surface.d.ts +9 -2
- package/dist/parked-revive-surface.js +11 -8
- package/dist/per-task-image.d.ts +7 -1
- package/dist/per-task-image.js +3 -3
- package/dist/permission-rule-list.d.ts +1 -1
- package/dist/permission-rule-list.js +2 -2
- package/dist/plain-string-record.d.ts +25 -0
- package/dist/plain-string-record.js +32 -0
- package/dist/plugins/approval-ask-store-file.d.ts +16 -1
- package/dist/plugins/approval-ask-store-file.js +21 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
- package/dist/plugins/approval-ask-store-memory.js +34 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
- package/dist/plugins/approval-ask-store-sql.js +37 -1
- package/dist/plugins/blob-backend.d.ts +83 -15
- package/dist/plugins/blob-backend.js +68 -15
- package/dist/plugins/breaker-state-sql.d.ts +2 -2
- package/dist/plugins/breaker-state-sql.js +4 -4
- package/dist/plugins/caching-session-store.d.ts +3 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +28 -4
- package/dist/plugins/checkpoint-store-sql.js +59 -28
- package/dist/plugins/file-history-store-sql.d.ts +5 -2
- package/dist/plugins/file-history-store-sql.js +3 -1
- package/dist/plugins/file-run-store.d.ts +5 -13
- package/dist/plugins/file-run-store.js +9 -0
- package/dist/plugins/fork-routing-session-store.d.ts +9 -1
- package/dist/plugins/fork-routing-session-store.js +12 -1
- package/dist/plugins/host-platform.d.ts +2 -0
- package/dist/plugins/host-platform.js +2 -1
- package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
- package/dist/plugins/k8s-bg-scripts.js +22 -0
- package/dist/plugins/local-checkpoint-store.d.ts +2 -0
- package/dist/plugins/local-checkpoint-store.js +11 -3
- package/dist/plugins/local-session-store.d.ts +1 -1
- package/dist/plugins/local-session-store.js +1 -1
- package/dist/plugins/local-session-window.d.ts +39 -0
- package/dist/plugins/local-session-window.js +54 -0
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
- package/dist/plugins/mailbox-store-sql.d.ts +30 -48
- package/dist/plugins/mailbox-store-sql.js +77 -52
- package/dist/plugins/memory-embedder-fingerprint.js +2 -1
- package/dist/plugins/memory-run-store.d.ts +5 -13
- package/dist/plugins/memory-run-store.js +8 -0
- package/dist/plugins/one-time-migrations.d.ts +245 -93
- package/dist/plugins/one-time-migrations.js +668 -207
- package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
- package/dist/plugins/org-delivery-quota-store.js +228 -0
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
- package/dist/plugins/permission-rule-store-sql.js +4 -18
- package/dist/plugins/pg-pool.js +39 -7
- package/dist/plugins/pg-session-storage.d.ts +20 -8
- package/dist/plugins/pg-session-storage.js +34 -21
- package/dist/plugins/remote-env-adb.d.ts +12 -12
- package/dist/plugins/remote-env-adb.js +21 -6
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +1 -1
- package/dist/plugins/remote-env-e2b.js +21 -9
- package/dist/plugins/remote-env-host.d.ts +9 -9
- package/dist/plugins/remote-env-host.js +10 -32
- package/dist/plugins/remote-env-k8s.d.ts +103 -38
- package/dist/plugins/remote-env-k8s.js +288 -95
- package/dist/plugins/remote-env-local-docker.d.ts +27 -13
- package/dist/plugins/remote-env-local-docker.js +82 -11
- package/dist/plugins/remote-env-ssh.d.ts +21 -12
- package/dist/plugins/remote-env-ssh.js +117 -5
- package/dist/plugins/remote-shell.d.ts +0 -1
- package/dist/plugins/remote-shell.js +1 -2
- package/dist/plugins/retention-store-sql.js +6 -2
- package/dist/plugins/run-store-sql.d.ts +16 -14
- package/dist/plugins/run-store-sql.js +22 -2
- package/dist/plugins/schema-consistency.d.ts +54 -0
- package/dist/plugins/schema-consistency.js +52 -0
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/send-user-file.js +2 -2
- package/dist/plugins/session-epoch-anchor.d.ts +39 -0
- package/dist/plugins/session-epoch-anchor.js +22 -0
- package/dist/plugins/session-store.d.ts +18 -8
- package/dist/plugins/session-store.js +15 -4
- package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
- package/dist/plugins/shared-memory-store-sql.js +5 -16
- package/dist/plugins/sql-driver.d.ts +205 -1
- package/dist/plugins/sql-driver.js +325 -13
- package/dist/plugins/sql-errors.d.ts +64 -0
- package/dist/plugins/sql-errors.js +57 -0
- package/dist/plugins/store-backend.d.ts +36 -1
- package/dist/plugins/store-backend.js +42 -15
- package/dist/plugins/store-contracts.d.ts +42 -0
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/tidb-pool.d.ts +6 -1
- package/dist/plugins/tidb-pool.js +44 -10
- package/dist/plugins/tidb-session-storage.d.ts +8 -11
- package/dist/plugins/tidb-session-storage.js +6 -16
- package/dist/plugins/tidb-session-store.d.ts +24 -4
- package/dist/plugins/tidb-session-store.js +29 -5
- package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
- package/dist/plugins/workflow-journal-store-sql.js +19 -6
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
- package/dist/plugins/workflow-run-store-sql.js +50 -9
- package/dist/posture-source.d.ts +18 -10
- package/dist/posture-source.js +4 -1
- package/dist/project-identity.d.ts +45 -6
- package/dist/project-identity.js +162 -8
- package/dist/project-layer-key.d.ts +28 -0
- package/dist/project-layer-key.js +29 -0
- package/dist/prompts-domain-validate.d.ts +17 -61
- package/dist/prompts-domain-validate.js +9 -119
- package/dist/question.js +3 -2
- package/dist/read-deny-entry.d.ts +25 -0
- package/dist/read-deny-entry.js +12 -0
- package/dist/read-face-posture.d.ts +37 -11
- package/dist/read-face-posture.js +32 -3
- package/dist/request-key-closure.d.ts +9 -2
- package/dist/request-key-closure.js +11 -5
- package/dist/request-value-closure.d.ts +5 -0
- package/dist/request-value-closure.js +11 -3
- package/dist/rule-entry-list.d.ts +38 -11
- package/dist/rule-entry-list.js +106 -30
- package/dist/rules-consent.js +3 -4
- package/dist/run-acceptance.d.ts +19 -3
- package/dist/run-acceptance.js +13 -1
- package/dist/run-local.js +54 -24
- package/dist/runs.d.ts +15 -8
- package/dist/runs.js +35 -1
- package/dist/runtime-caps-resolver.d.ts +16 -6
- package/dist/runtime-caps-resolver.js +18 -5
- package/dist/sandbox-pkg-source.d.ts +19 -1
- package/dist/sandbox-pkg-source.js +29 -7
- package/dist/security.d.ts +87 -9
- package/dist/security.js +52 -9
- package/dist/server-secret-env.d.ts +44 -18
- package/dist/server-secret-env.js +49 -8
- package/dist/session-error-wire.d.ts +19 -2
- package/dist/session-error-wire.js +5 -1
- package/dist/session-incarnation.d.ts +15 -5
- package/dist/session-incarnation.js +16 -9
- package/dist/session-titled-frame.d.ts +33 -0
- package/dist/session-titled-frame.js +4 -0
- package/dist/session-titler.d.ts +9 -1
- package/dist/session-titler.js +27 -6
- package/dist/spec-fields.d.ts +28 -16
- package/dist/spec-fields.js +66 -54
- package/dist/sql-engine-posture.d.ts +31 -4
- package/dist/sql-engine-posture.js +36 -1
- package/dist/stored-request-tool-axes-m19.d.ts +19 -4
- package/dist/stored-request-tool-axes-m19.js +30 -2
- package/dist/task-a2a.d.ts +9 -1
- package/dist/task-a2a.js +73 -47
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-mcp.d.ts +46 -58
- package/dist/task-mcp.js +82 -107
- package/dist/task-settings.d.ts +95 -108
- package/dist/task-settings.js +77 -98
- package/dist/text-bidi.d.ts +1 -48
- package/dist/text-bidi.js +3 -5
- package/dist/tighten-only-seats.d.ts +150 -0
- package/dist/tighten-only-seats.js +196 -0
- package/dist/tool-approval.d.ts +304 -88
- package/dist/tool-approval.js +623 -239
- package/dist/trace/core-keyset-guard.d.ts +122 -20
- package/dist/trace/engine-notice-wire.d.ts +11 -3
- package/dist/trace/engine-notice-wire.js +8 -1
- package/dist/trace/exit-seat.d.ts +28 -0
- package/dist/trace/exit-seat.js +76 -0
- package/dist/trace/ledger-events.d.ts +6 -3
- package/dist/trace/ledger-sink.js +4 -1
- package/dist/trace/model-segment.js +1 -1
- package/dist/trace/project.d.ts +98 -17
- package/dist/trace/project.js +98 -50
- package/dist/trace/projection-table.d.ts +100 -0
- package/dist/trace/projection-table.js +130 -0
- package/dist/trace/redact.d.ts +54 -0
- package/dist/trace/redact.js +778 -152
- package/dist/trace/self-minted-wire.d.ts +345 -1
- package/dist/trace/self-minted-wire.js +117 -1
- package/dist/trace/wire-projection-faces.d.ts +377 -3
- package/dist/trace/wire-projection-faces.js +164 -1
- package/dist/url-refusal.js +2 -0
- package/dist/web-admin-client/apply-effective.d.ts +12 -0
- package/dist/web-admin-client/apply-effective.js +56 -5
- package/dist/web-admin-client/candidate-gate.d.ts +6 -6
- package/dist/web-admin-client/candidate-gate.js +3 -3
- package/dist/web-admin-client/facade.d.ts +1 -1
- package/dist/web-admin-client/facade.js +1 -1
- package/dist/web-admin-client/http-client.js +0 -6
- package/dist/web-admin-client/read-face.js +5 -9
- package/dist/web-admin-client/read-warnings.d.ts +13 -0
- package/dist/web-admin-client/read-warnings.js +10 -0
- package/dist/web-admin-client/skills-mcp.d.ts +3 -0
- package/dist/web-admin-client/skills-mcp.js +11 -9
- package/dist/web-admin-client/types.d.ts +2 -3
- package/dist/workspace-cache.d.ts +35 -0
- package/dist/workspace-cache.js +36 -0
- package/dist/workspace-space.d.ts +3 -2
- package/dist/workspace-space.js +2 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +65 -10
- package/package.json +8 -4
- package/scripts/drain-check-local.mjs +34 -1
- package/scripts/drain-check.mysql.sql +30 -2
- package/scripts/drain-check.pg.sql +29 -2
- package/dist/capabilities/memory-notice.d.ts +0 -138
- package/dist/capabilities/memory-notice.js +0 -64
- package/dist/capabilities/prompt.d.ts +0 -17
- package/dist/capabilities/prompt.js +0 -6
- package/dist/mcp-content-origin.d.ts +0 -34
- package/dist/mcp-content-origin.js +0 -10
- package/dist/tool-axis-words.d.ts +0 -27
- package/dist/tool-axis-words.js +0 -9
- package/dist/wire-name-bound.d.ts +0 -14
- package/dist/wire-name-bound.js +0 -13
package/dist/tool-approval.js
CHANGED
|
@@ -7,9 +7,11 @@ import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact
|
|
|
7
7
|
import { hasBidiControls } from "./text-bidi.js";
|
|
8
8
|
import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerprint.js";
|
|
9
9
|
import { createLogger } from "./observability/logger.js";
|
|
10
|
+
import { errorKindOf } from "./observability/err-text.js";
|
|
10
11
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import { ownerOfCheckpointScope } from "./security.js";
|
|
13
|
+
import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt } from "./approval-ask-machine.js";
|
|
14
|
+
import { currentParkArm, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildReplayFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readStoredCardEnvelope, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, buildApprovalCompleteFrame, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
13
15
|
import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
14
16
|
import { APPROVAL_ENDING_CAUSES, SETTLE_OUTCOME, causeOfAskRow } from "./approval-deny-reasons.js";
|
|
15
17
|
import { settleByPolicy } from "./approval-policy-settlement.js";
|
|
@@ -18,6 +20,7 @@ import { governanceOriginOf } from "./runtime-governance.js";
|
|
|
18
20
|
import { callGrantBindingOf, questionGrantBindingOf } from "./standing-answer-grant.js";
|
|
19
21
|
import { operatorAsk } from "./operator-ask.js";
|
|
20
22
|
import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
|
|
23
|
+
import { refusalBody } from "./http/refusal-body.js";
|
|
21
24
|
const defaultLogger = createLogger();
|
|
22
25
|
export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
|
|
23
26
|
export function isApprovalGateKind(k) {
|
|
@@ -32,15 +35,17 @@ const DEFAULT_APPROVAL_TTL_MS = 5 * 60_000;
|
|
|
32
35
|
const DEFAULT_UNREACHED_ASK_TTL_MS = 60 * 60_000;
|
|
33
36
|
const SUSPENDED_POLL_MAX_PER_TICK = 64;
|
|
34
37
|
const SUSPENDED_POLL_BUDGET_MS = 5_000;
|
|
38
|
+
const ADOPT_OPEN_ASKS_MAX = 1_000;
|
|
35
39
|
const EDITED_DECISION_MARK_CAP_MS = 10_000;
|
|
36
40
|
const DEFAULT_WINDOW_MARGIN_MS = 10_000;
|
|
37
41
|
const SHUTDOWN_DRAIN_POLL_MS = 50;
|
|
38
42
|
const MAX_ALLOW_SESSIONS = 4096;
|
|
39
43
|
const DEFAULT_ADMIT_MAX_PER_TASK = 32;
|
|
40
44
|
const DEFAULT_ADMIT_MAX_PER_OWNER = 256;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
45
|
+
const DEFAULT_PARK_REDEMPTION_MAX = 2000;
|
|
46
|
+
const DEFAULT_PARK_REDEMPTION_TTL_MS = 60 * 60_000;
|
|
47
|
+
const DEFAULT_PARK_REDEMPTION_PER_PRINCIPAL_MAX = 128;
|
|
48
|
+
const PARK_REDEMPTION_ADMISSION_RETRY_AFTER_SEC = 5;
|
|
44
49
|
const PARKING_RETRY_AFTER_MS = 250;
|
|
45
50
|
export function approvalDecidePath(sessionId) {
|
|
46
51
|
return `/v1/approvals/${encodeURIComponent(sessionId)}/decide`;
|
|
@@ -63,6 +68,22 @@ function durableRuleMaterial(material) {
|
|
|
63
68
|
return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
|
|
64
69
|
}
|
|
65
70
|
export const NOT_PENDING_UNKNOWN_CAUSE = "unknown_or_other_replica";
|
|
71
|
+
export const TOOL_APPROVAL_READ_STATES = ["pending", "decided", "parked", "void"];
|
|
72
|
+
function decidedRowFacts(row) {
|
|
73
|
+
if (row.state !== "DECIDED" || (row.decision !== "approve" && row.decision !== "deny") || row.provisional || row.decidedAtMs === null)
|
|
74
|
+
return undefined;
|
|
75
|
+
return { decision: row.decision === "approve" ? "allow" : "deny", decidedAtMs: row.decidedAtMs, ...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}) };
|
|
76
|
+
}
|
|
77
|
+
function buildAskRowReadProjection(row, nowMs) {
|
|
78
|
+
const frame = buildReplayFrame(row, nowMs);
|
|
79
|
+
return {
|
|
80
|
+
approvalId: row.askId,
|
|
81
|
+
ts: row.createdAtMs,
|
|
82
|
+
expiresAtMs: row.expiresAtMs,
|
|
83
|
+
...(row.sessionId !== row.taskId ? { sessionId: row.sessionId } : {}),
|
|
84
|
+
...(frame !== undefined ? { toolName: frame.card.toolName, frame } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
66
87
|
export const ASK_DECISION_CONSUMER_ABSENT = "absent";
|
|
67
88
|
const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
|
|
68
89
|
const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
|
|
@@ -181,11 +202,45 @@ function mandatedFromSeat(seat) {
|
|
|
181
202
|
return false;
|
|
182
203
|
}
|
|
183
204
|
}
|
|
205
|
+
function askRowVisibleTo(row, principal) {
|
|
206
|
+
return row !== null && (row.owner === null || row.owner === principal);
|
|
207
|
+
}
|
|
208
|
+
function waitingLegsReached(entry, othersReached) {
|
|
209
|
+
return (entry.adopted === true ? 0 : 1) + othersReached;
|
|
210
|
+
}
|
|
211
|
+
function withConsumerDisclosure(result, waitingLegs) {
|
|
212
|
+
return waitingLegs > 0 ? result : { status: result.status, body: { ...result.body, consumerState: ASK_DECISION_CONSUMER_ABSENT } };
|
|
213
|
+
}
|
|
214
|
+
function livePendingRowOf(e) {
|
|
215
|
+
const listFrame = e.adopted === true ? e.cardFrame : (e.cardFrame ?? e.frame);
|
|
216
|
+
const approvalCard = e.adopted === true ? e.cardFrame.approvalCard : e.frame.approvalCard;
|
|
217
|
+
return {
|
|
218
|
+
approvalId: e.approvalId,
|
|
219
|
+
toolName: e.toolName,
|
|
220
|
+
ts: e.registeredAtMs,
|
|
221
|
+
expiresAtMs: e.expiresAtMs,
|
|
222
|
+
...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
|
|
223
|
+
...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
224
|
+
...(e.governanceForced ? { governanceForced: true } : {}),
|
|
225
|
+
...(e.mandated ? { mandated: true } : {}),
|
|
226
|
+
...(standingAnswerHome(pendingEntryStandingAnswerFacts(e)) === "none" ? { rememberRefused: true } : {}),
|
|
227
|
+
...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
|
|
228
|
+
...(e.origin !== undefined ? { origin: e.origin } : {}),
|
|
229
|
+
...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
|
|
230
|
+
...(approvalCard !== undefined ? { approvalCard } : {}),
|
|
231
|
+
frame: listFrame,
|
|
232
|
+
...(e.adopted === true ? { consumerState: ASK_DECISION_CONSUMER_ABSENT } : {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function canPresentWithoutDurableRow(c) {
|
|
236
|
+
return c.emitCard === undefined;
|
|
237
|
+
}
|
|
184
238
|
export const PERSIST_RULE_TEXT_ERROR = `persistRule.rule must be a non-empty string of at most ${MAX_RULE_TEXT_CHARS} characters`;
|
|
185
239
|
export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a boolean when present";
|
|
186
240
|
export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
|
|
187
241
|
export const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOfferIndex is mutually exclusive with persistRule.rule / persistRule.edited — send exactly one arm";
|
|
188
242
|
export const READ_ROOT_GRANT_ALLOW_ONLY = 'readRootGrant is only meaningful with decision "allow" — it is the card\'s "Yes, and add directory" answer (allow this call and add the card\'s readRootCandidate.dir to this run\'s read directories)';
|
|
243
|
+
export const READ_ROOT_GRANT_NOT_ACCEPTED = 'readRootGrant is not accepted on this deployment — a read-directory grant is honored only on a single-user host-lane deployment (GET /v1/capabilities approvalReadRootGrant is false here); answer with plain decision "allow" instead';
|
|
189
244
|
export const READ_ROOT_GRANT_SHAPE = `readRootGrant must be the card's readRootCandidate.dir sent back byte for byte — a non-empty string of at most ${READ_ROOT_CANDIDATE_DIR_MAX} characters`;
|
|
190
245
|
export const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope — the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
|
|
191
246
|
export function parseToolApprovalResponse(body) {
|
|
@@ -310,27 +365,48 @@ export function resolveStreamApprovalGate(input) {
|
|
|
310
365
|
return { active: false, reason: "no_park_facility" };
|
|
311
366
|
return { active: true, askStore: input.backend.approvalAsk() };
|
|
312
367
|
}
|
|
368
|
+
export const APPROVAL_FRAME_FAMILIES = ["tool_approval", "approval_request"];
|
|
313
369
|
export function createApprovalCardEmitter(sinks) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
accepted = true;
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
}
|
|
370
|
+
const present = [sinks.appendDurable, sinks.writeLive].filter((w) => w !== undefined);
|
|
371
|
+
const attempt = (write, frame) => (async () => {
|
|
372
|
+
try {
|
|
373
|
+
await write(frame);
|
|
374
|
+
return true;
|
|
323
375
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
376
|
+
catch (err) {
|
|
377
|
+
defaultLogger.warn("approval_card_sink_write_failed", { frame: frame.type, approvalId: frame.approvalId, errKind: errorKindOf(err) });
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
})();
|
|
381
|
+
const opened = new Map();
|
|
382
|
+
return async (frame, registration) => {
|
|
383
|
+
if (frame.type === "approval_request") {
|
|
384
|
+
const results = present.map((write) => attempt(write, frame));
|
|
385
|
+
opened.set(registration, results);
|
|
386
|
+
const accepted = await new Promise((resolve) => {
|
|
387
|
+
let pending = results.length;
|
|
388
|
+
if (pending === 0)
|
|
389
|
+
resolve(false);
|
|
390
|
+
for (const r of results) {
|
|
391
|
+
void r.then((ok) => {
|
|
392
|
+
if (ok)
|
|
393
|
+
resolve(true);
|
|
394
|
+
else if (--pending === 0)
|
|
395
|
+
resolve(false);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
if (!accepted) {
|
|
400
|
+
opened.delete(registration);
|
|
401
|
+
throw new Error("approval card undeliverable — no sink accepted the frame (live stream closed and/or the durable append failed)");
|
|
330
402
|
}
|
|
403
|
+
return;
|
|
331
404
|
}
|
|
332
|
-
|
|
333
|
-
|
|
405
|
+
const results = opened.get(registration);
|
|
406
|
+
opened.delete(registration);
|
|
407
|
+
if (results === undefined)
|
|
408
|
+
return;
|
|
409
|
+
await Promise.all(present.map((write, i) => results[i].then((ok) => (ok ? attempt(write, frame) : false))));
|
|
334
410
|
};
|
|
335
411
|
}
|
|
336
412
|
export function resolveApprovalLeg(input) {
|
|
@@ -412,6 +488,7 @@ export class ToolApprovalCoordinator {
|
|
|
412
488
|
return this.terminalClaimsInFlightCount(askId);
|
|
413
489
|
}
|
|
414
490
|
pendingByAskId = new Map();
|
|
491
|
+
presentedCardWatchers = new Map();
|
|
415
492
|
allowAllSessions = new Set();
|
|
416
493
|
streams = new Map();
|
|
417
494
|
ttlMs;
|
|
@@ -423,6 +500,7 @@ export class ToolApprovalCoordinator {
|
|
|
423
500
|
admitByTask = new Map();
|
|
424
501
|
admitByOwner = new Map();
|
|
425
502
|
suspendedPollOffset = 0;
|
|
503
|
+
adoptedPollOffset = 0;
|
|
426
504
|
editedDecisionsInFlight = new Map();
|
|
427
505
|
storeErrorWarned = false;
|
|
428
506
|
storeErrorTally = 0;
|
|
@@ -430,10 +508,10 @@ export class ToolApprovalCoordinator {
|
|
|
430
508
|
ruleConsent;
|
|
431
509
|
unattendedPolicy;
|
|
432
510
|
ruleScopeRootFor;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
511
|
+
parkRedemptions = new Map();
|
|
512
|
+
parkRedemptionMax;
|
|
513
|
+
parkRedemptionTtlMs;
|
|
514
|
+
parkRedemptionPerPrincipalMax;
|
|
437
515
|
parkedRedeem;
|
|
438
516
|
askAudit;
|
|
439
517
|
sessionGrants;
|
|
@@ -454,9 +532,9 @@ export class ToolApprovalCoordinator {
|
|
|
454
532
|
this.admitMaxPerOwner = opts?.admitMaxPerOwner ?? DEFAULT_ADMIT_MAX_PER_OWNER;
|
|
455
533
|
this.unattendedPolicy = opts?.unattendedPolicy ?? "park";
|
|
456
534
|
this.parkedRedeem = opts?.parkedRedeem;
|
|
457
|
-
this.
|
|
458
|
-
this.
|
|
459
|
-
this.
|
|
535
|
+
this.parkRedemptionMax = opts?.parkRedemptionMax ?? DEFAULT_PARK_REDEMPTION_MAX;
|
|
536
|
+
this.parkRedemptionTtlMs = opts?.parkRedemptionTtlMs ?? DEFAULT_PARK_REDEMPTION_TTL_MS;
|
|
537
|
+
this.parkRedemptionPerPrincipalMax = opts?.parkRedemptionPerPrincipalMax ?? DEFAULT_PARK_REDEMPTION_PER_PRINCIPAL_MAX;
|
|
460
538
|
this.askAudit = opts?.askAudit;
|
|
461
539
|
this.sessionGrants = opts?.sessionGrants;
|
|
462
540
|
if (opts?.deploymentListAsks !== undefined && opts.sessionGrants === undefined) {
|
|
@@ -951,75 +1029,84 @@ export class ToolApprovalCoordinator {
|
|
|
951
1029
|
}
|
|
952
1030
|
}
|
|
953
1031
|
}
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
|
|
961
|
-
|
|
1032
|
+
parkRedemptionAdmission(owner) {
|
|
1033
|
+
const effectivePerPrincipalMax = Math.min(this.parkRedemptionPerPrincipalMax, this.parkRedemptionMax);
|
|
1034
|
+
let inflight = 0;
|
|
1035
|
+
let ownInflight = 0;
|
|
1036
|
+
for (const r of this.parkRedemptions.values()) {
|
|
1037
|
+
if (r.inflight === undefined)
|
|
1038
|
+
continue;
|
|
1039
|
+
inflight++;
|
|
1040
|
+
if (r.owner === owner)
|
|
1041
|
+
ownInflight++;
|
|
962
1042
|
}
|
|
963
|
-
|
|
1043
|
+
if (ownInflight < effectivePerPrincipalMax && inflight < this.parkRedemptionMax)
|
|
1044
|
+
return undefined;
|
|
1045
|
+
defaultLogger.warn("parked approval redemption refused at admission: too many redemptions in flight", { owner: owner ?? "<none>", ownInflight, inflight });
|
|
1046
|
+
return {
|
|
1047
|
+
status: 429,
|
|
1048
|
+
body: refusalBody({
|
|
1049
|
+
errorCode: "limit.rate_exceeded",
|
|
1050
|
+
message: "too many late approval answers are being applied right now — retry after the Retry-After interval",
|
|
1051
|
+
facts: { retryAfterSec: PARK_REDEMPTION_ADMISSION_RETRY_AFTER_SEC },
|
|
1052
|
+
}),
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
openParkRedemption(askId, owner) {
|
|
1056
|
+
const opened = { owner, stampedAtMs: Date.now() };
|
|
1057
|
+
this.parkRedemptions.delete(askId);
|
|
1058
|
+
this.parkRedemptions.set(askId, opened);
|
|
1059
|
+
return opened;
|
|
1060
|
+
}
|
|
1061
|
+
retainParkRedemption(askId, memo) {
|
|
964
1062
|
const now = Date.now();
|
|
965
|
-
|
|
966
|
-
let
|
|
967
|
-
let
|
|
968
|
-
let
|
|
969
|
-
let
|
|
970
|
-
let
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
const expired = now - t.parkedAtMs > this.parkTombstoneTtlMs;
|
|
974
|
-
if (globalExpiredVictim === undefined && expired && t.inflight === undefined)
|
|
975
|
-
globalExpiredVictim = k;
|
|
976
|
-
if (globalBridgeVictim === undefined && t.kind === "voidBridge" && t.inflight === undefined)
|
|
977
|
-
globalBridgeVictim = k;
|
|
978
|
-
if (t.owner !== tomb.owner)
|
|
1063
|
+
const effectivePerPrincipalMax = Math.min(this.parkRedemptionPerPrincipalMax, this.parkRedemptionMax);
|
|
1064
|
+
let receipts = 0;
|
|
1065
|
+
let ownReceipts = 0;
|
|
1066
|
+
let ownExpired;
|
|
1067
|
+
let ownOldest;
|
|
1068
|
+
let globalExpired;
|
|
1069
|
+
for (const [k, r] of this.parkRedemptions) {
|
|
1070
|
+
if (k === askId || r.redeemed === undefined || r.inflight !== undefined)
|
|
979
1071
|
continue;
|
|
980
|
-
|
|
981
|
-
|
|
1072
|
+
receipts++;
|
|
1073
|
+
const expired = now - r.stampedAtMs > this.parkRedemptionTtlMs;
|
|
1074
|
+
if (globalExpired === undefined && expired)
|
|
1075
|
+
globalExpired = k;
|
|
1076
|
+
if (r.owner !== memo.owner)
|
|
982
1077
|
continue;
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
if (
|
|
996
|
-
recordFailOpen("server.approval.park-
|
|
997
|
-
|
|
1078
|
+
ownReceipts++;
|
|
1079
|
+
if (ownOldest === undefined)
|
|
1080
|
+
ownOldest = k;
|
|
1081
|
+
if (ownExpired === undefined && expired)
|
|
1082
|
+
ownExpired = k;
|
|
1083
|
+
}
|
|
1084
|
+
if (ownReceipts >= effectivePerPrincipalMax) {
|
|
1085
|
+
const victim = ownExpired ?? ownOldest;
|
|
1086
|
+
if (victim !== undefined)
|
|
1087
|
+
this.parkRedemptions.delete(victim);
|
|
1088
|
+
}
|
|
1089
|
+
else if (receipts >= this.parkRedemptionMax) {
|
|
1090
|
+
if (globalExpired === undefined) {
|
|
1091
|
+
recordFailOpen("server.approval.park-redemption-cap-refused", `owner=${memo.owner ?? "<none>"}`);
|
|
1092
|
+
if (this.parkRedemptions.get(askId) === memo)
|
|
1093
|
+
this.parkRedemptions.delete(askId);
|
|
1094
|
+
return false;
|
|
998
1095
|
}
|
|
999
|
-
this.
|
|
1096
|
+
this.parkRedemptions.delete(globalExpired);
|
|
1000
1097
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1004
|
-
else if (this.parkTombstones.size >= this.parkTombstoneMax && !insertingBridge && globalBridgeVictim !== undefined) {
|
|
1005
|
-
this.parkTombstones.delete(globalBridgeVictim);
|
|
1006
|
-
}
|
|
1007
|
-
else if (this.parkTombstones.size >= this.parkTombstoneMax) {
|
|
1008
|
-
recordFailOpen("server.approval.park-tombstone-global-cap-refused", `owner=${tomb.owner ?? "<none>"}${insertingBridge ? " reason=bridge-refused-at-global-cap" : ""}`);
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
this.parkTombstones.set(approvalId, tomb);
|
|
1098
|
+
memo.stampedAtMs = now;
|
|
1099
|
+
return true;
|
|
1012
1100
|
}
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
if (
|
|
1101
|
+
lookupParkRedemption(askId) {
|
|
1102
|
+
const r = this.parkRedemptions.get(askId);
|
|
1103
|
+
if (r === undefined)
|
|
1016
1104
|
return undefined;
|
|
1017
|
-
if (Date.now() -
|
|
1018
|
-
|
|
1019
|
-
this.parkTombstones.delete(approvalId);
|
|
1105
|
+
if (r.inflight === undefined && Date.now() - r.stampedAtMs > this.parkRedemptionTtlMs) {
|
|
1106
|
+
this.parkRedemptions.delete(askId);
|
|
1020
1107
|
return undefined;
|
|
1021
1108
|
}
|
|
1022
|
-
return
|
|
1109
|
+
return r;
|
|
1023
1110
|
}
|
|
1024
1111
|
notifyExternalDecision(askId, outcome) {
|
|
1025
1112
|
const entries = this.pendingByAskId.get(askId);
|
|
@@ -1029,7 +1116,8 @@ export class ToolApprovalCoordinator {
|
|
|
1029
1116
|
let settled = 0;
|
|
1030
1117
|
for (const entry of [...entries]) {
|
|
1031
1118
|
entry.settle(verdictOfDecided(args));
|
|
1032
|
-
|
|
1119
|
+
if (entry.adopted !== true)
|
|
1120
|
+
settled += 1;
|
|
1033
1121
|
}
|
|
1034
1122
|
return { settled };
|
|
1035
1123
|
}
|
|
@@ -1211,7 +1299,6 @@ export class ToolApprovalCoordinator {
|
|
|
1211
1299
|
let askId;
|
|
1212
1300
|
let batchId;
|
|
1213
1301
|
const card = this.askStore ? buildApprovalCard(frame, req, req.requiresRealApproval === true) : undefined;
|
|
1214
|
-
let wireApprovalId = id;
|
|
1215
1302
|
let cardForFrame = card;
|
|
1216
1303
|
let persistedExpiresAtMs = expiresAtMs;
|
|
1217
1304
|
let lateEnsuredRow;
|
|
@@ -1257,7 +1344,7 @@ export class ToolApprovalCoordinator {
|
|
|
1257
1344
|
toolCallId: req.toolCallId,
|
|
1258
1345
|
legKey,
|
|
1259
1346
|
parentToolCallId: req.delegation?.parentToolCallId ?? null,
|
|
1260
|
-
cardJson: buildApprovalCardEnvelope(
|
|
1347
|
+
cardJson: buildApprovalCardEnvelope(askId, card),
|
|
1261
1348
|
boundInputHash: readBoundInputHash(req),
|
|
1262
1349
|
...durableRuleMaterial(ruleLaneMaterial),
|
|
1263
1350
|
schemaVersion: 1,
|
|
@@ -1289,8 +1376,6 @@ export class ToolApprovalCoordinator {
|
|
|
1289
1376
|
return existingOrCreated.inserted ? this.landUnattendedOutcome(unattended, existingOrCreated.row) : unattended;
|
|
1290
1377
|
}
|
|
1291
1378
|
persistedExpiresAtMs = existingOrCreated.row.expiresAtMs;
|
|
1292
|
-
wireApprovalId = persistedEnvelope.approvalId;
|
|
1293
|
-
frame.approvalId = wireApprovalId;
|
|
1294
1379
|
cardForFrame = persistedEnvelope.card;
|
|
1295
1380
|
if (cardForFrame.governanceForced === true)
|
|
1296
1381
|
frame.governanceForced = true;
|
|
@@ -1359,8 +1444,8 @@ export class ToolApprovalCoordinator {
|
|
|
1359
1444
|
if (!confirmedRowExists) {
|
|
1360
1445
|
askId = undefined;
|
|
1361
1446
|
batchId = undefined;
|
|
1362
|
-
if (
|
|
1363
|
-
const unattended = this.unattendedOutcome({ arm: "
|
|
1447
|
+
if (!aliveCtxs.some(canPresentWithoutDurableRow)) {
|
|
1448
|
+
const unattended = this.unattendedOutcome({ arm: "no_row_no_presenter", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1364
1449
|
const landed = abandonRow(unattended);
|
|
1365
1450
|
releaseAdmission();
|
|
1366
1451
|
return landed ?? unattended;
|
|
@@ -1369,6 +1454,8 @@ export class ToolApprovalCoordinator {
|
|
|
1369
1454
|
}
|
|
1370
1455
|
}
|
|
1371
1456
|
}
|
|
1457
|
+
const wireApprovalId = askId ?? id;
|
|
1458
|
+
frame.approvalId = wireApprovalId;
|
|
1372
1459
|
let done = false;
|
|
1373
1460
|
let emitFailed = false;
|
|
1374
1461
|
let emitSettled = false;
|
|
@@ -1427,19 +1514,24 @@ export class ToolApprovalCoordinator {
|
|
|
1427
1514
|
sig.removeEventListener("abort", fn);
|
|
1428
1515
|
this.pending.delete(id);
|
|
1429
1516
|
if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.unattendedRoutesToPark) {
|
|
1430
|
-
this.recordParkTombstone(wireApprovalId, { kind: "park", askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1431
1517
|
parkRouted = true;
|
|
1432
1518
|
}
|
|
1519
|
+
let lastLiveRegistration = false;
|
|
1433
1520
|
if (askId !== undefined) {
|
|
1434
1521
|
const siblings = this.pendingByAskId.get(askId);
|
|
1435
1522
|
if (siblings && selfEntry) {
|
|
1436
1523
|
siblings.delete(selfEntry);
|
|
1437
|
-
if (siblings.size === 0)
|
|
1524
|
+
if (siblings.size === 0) {
|
|
1438
1525
|
this.pendingByAskId.delete(askId);
|
|
1526
|
+
lastLiveRegistration = true;
|
|
1527
|
+
}
|
|
1439
1528
|
}
|
|
1440
1529
|
}
|
|
1441
1530
|
lastOutcome = outcome;
|
|
1442
1531
|
lastCause = verdict.by === "system" ? verdict.cause : undefined;
|
|
1532
|
+
if (lastLiveRegistration && askId !== undefined) {
|
|
1533
|
+
this.notifyPresentedCardWatchers(askId, buildApprovalCompleteFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, outcome, ...(lastCause !== undefined ? { cause: lastCause } : {}), parked: parkRouted, nowMs: Date.now() }));
|
|
1534
|
+
}
|
|
1443
1535
|
releaseAdmission();
|
|
1444
1536
|
if (selfEntry)
|
|
1445
1537
|
selfEntry.settledOutcome = outcome;
|
|
@@ -1511,7 +1603,7 @@ export class ToolApprovalCoordinator {
|
|
|
1511
1603
|
settleFromClaimLoss(read, armCause);
|
|
1512
1604
|
};
|
|
1513
1605
|
const boundedClaim = (mk, label, timeoutMs, late) => {
|
|
1514
|
-
const release = this.admitTerminalClaim(
|
|
1606
|
+
const release = this.admitTerminalClaim(wireApprovalId);
|
|
1515
1607
|
if (release === undefined)
|
|
1516
1608
|
return undefined;
|
|
1517
1609
|
const raw = mk();
|
|
@@ -1583,7 +1675,6 @@ export class ToolApprovalCoordinator {
|
|
|
1583
1675
|
arm.onWon(outcome);
|
|
1584
1676
|
return;
|
|
1585
1677
|
}
|
|
1586
|
-
arm.onLostTruth?.(outcome.current);
|
|
1587
1678
|
const read = this.classifyClaimLoss(pinnedAskId, outcome.current, `claim:${arm.intent}`);
|
|
1588
1679
|
if (done) {
|
|
1589
1680
|
broadcastClaimTruth(read, arm.cause);
|
|
@@ -1673,32 +1764,23 @@ export class ToolApprovalCoordinator {
|
|
|
1673
1764
|
const store = this.askStore;
|
|
1674
1765
|
const pinnedAskId = askId;
|
|
1675
1766
|
const pinnedBatchId = batchId;
|
|
1676
|
-
const cancelLostTruth = (current) => {
|
|
1677
|
-
if (current.state !== "VOID")
|
|
1678
|
-
return;
|
|
1679
|
-
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1680
|
-
};
|
|
1681
1767
|
await resolveLocalOutcome({
|
|
1682
1768
|
intent: "cancel",
|
|
1683
1769
|
cause,
|
|
1684
1770
|
storeLabel: "claimTerminal(cancel)",
|
|
1685
1771
|
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "cancel", { cause }), "claimTerminal(cancel)", timeoutMs, (late) => {
|
|
1686
1772
|
if (!late.claimed) {
|
|
1687
|
-
cancelLostTruth(late.current);
|
|
1688
1773
|
lateClaimLossSettle(late.current, "cancel", cause);
|
|
1689
1774
|
return;
|
|
1690
1775
|
}
|
|
1691
1776
|
this.settleVoidedSiblings([pinnedAskId], cause);
|
|
1692
|
-
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1693
1777
|
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1694
1778
|
}),
|
|
1695
1779
|
onWon: () => {
|
|
1696
1780
|
settle({ by: "system", cause });
|
|
1697
1781
|
this.settleVoidedSiblings([pinnedAskId], cause);
|
|
1698
|
-
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1699
1782
|
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1700
1783
|
},
|
|
1701
|
-
onLostTruth: cancelLostTruth,
|
|
1702
1784
|
settleByIntent: () => settle({ by: "system", cause }),
|
|
1703
1785
|
});
|
|
1704
1786
|
};
|
|
@@ -1750,6 +1832,9 @@ export class ToolApprovalCoordinator {
|
|
|
1750
1832
|
const { rememberRefused: _stale, ...cardRest } = cardForFrame;
|
|
1751
1833
|
cardForFrame = rememberRefused ? { ...cardRest, rememberRefused: true } : cardRest;
|
|
1752
1834
|
}
|
|
1835
|
+
const cardFrame = askId !== undefined && cardForFrame !== undefined
|
|
1836
|
+
? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now(), live: frame })
|
|
1837
|
+
: undefined;
|
|
1753
1838
|
const pendingEntry = {
|
|
1754
1839
|
settle,
|
|
1755
1840
|
forceParkNow,
|
|
@@ -1760,6 +1845,9 @@ export class ToolApprovalCoordinator {
|
|
|
1760
1845
|
...(child ? { originTaskId: req.sourceTaskId } : primary ? { targetCtxs: new Set(aliveCtxs), onTargetGone } : {}),
|
|
1761
1846
|
...(origin.sessionId ? { sessionId: origin.sessionId } : {}),
|
|
1762
1847
|
frame,
|
|
1848
|
+
...(cardFrame !== undefined ? { cardFrame } : {}),
|
|
1849
|
+
approvalId: wireApprovalId,
|
|
1850
|
+
registrationId: id,
|
|
1763
1851
|
toolName: req.toolName,
|
|
1764
1852
|
grantBinding: entryGrantBinding,
|
|
1765
1853
|
...(entryClasses !== undefined ? { classes: entryClasses } : {}),
|
|
@@ -1777,6 +1865,7 @@ export class ToolApprovalCoordinator {
|
|
|
1777
1865
|
selfEntry = pendingEntry;
|
|
1778
1866
|
this.pending.set(id, pendingEntry);
|
|
1779
1867
|
if (askId !== undefined) {
|
|
1868
|
+
this.releaseAdoptedRegistrations(askId);
|
|
1780
1869
|
let siblings = this.pendingByAskId.get(askId);
|
|
1781
1870
|
if (!siblings) {
|
|
1782
1871
|
siblings = new Set();
|
|
@@ -1798,9 +1887,6 @@ export class ToolApprovalCoordinator {
|
|
|
1798
1887
|
});
|
|
1799
1888
|
if (this.shutdownDraining)
|
|
1800
1889
|
forceParkNow(APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
|
|
1801
|
-
const cardFrame = askId !== undefined && cardForFrame !== undefined
|
|
1802
|
-
? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now() })
|
|
1803
|
-
: undefined;
|
|
1804
1890
|
{
|
|
1805
1891
|
const serverNowMs = Date.now();
|
|
1806
1892
|
frame.expiresAtMs = persistedExpiresAtMs;
|
|
@@ -1808,28 +1894,37 @@ export class ToolApprovalCoordinator {
|
|
|
1808
1894
|
frame.expiresInMs = Math.max(0, persistedExpiresAtMs - serverNowMs);
|
|
1809
1895
|
}
|
|
1810
1896
|
const emitOne = async (c) => {
|
|
1811
|
-
let
|
|
1812
|
-
|
|
1813
|
-
|
|
1897
|
+
let family;
|
|
1898
|
+
let send;
|
|
1899
|
+
if (c.emitCard !== undefined) {
|
|
1900
|
+
if (cardFrame === undefined) {
|
|
1901
|
+
defaultLogger.warn("approval card leg cannot present this ask: it has no confirmed durable identity (no approval_request frame is minted without one)", {
|
|
1902
|
+
taskId: origin.taskId,
|
|
1903
|
+
approvalId: wireApprovalId,
|
|
1904
|
+
toolName: req.toolName,
|
|
1905
|
+
});
|
|
1906
|
+
return { delivered: false };
|
|
1907
|
+
}
|
|
1908
|
+
family = "approval_request";
|
|
1909
|
+
send = () => c.emitCard(cardFrame, id);
|
|
1814
1910
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1911
|
+
else {
|
|
1912
|
+
family = "tool_approval";
|
|
1913
|
+
send = () => c.emit(frame);
|
|
1817
1914
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
}
|
|
1915
|
+
try {
|
|
1916
|
+
await send();
|
|
1917
|
+
return { delivered: true };
|
|
1918
|
+
}
|
|
1919
|
+
catch (err) {
|
|
1920
|
+
defaultLogger.warn("approval_card_delivery_failed", { taskId: origin.taskId, approvalId: wireApprovalId, family, errKind: errorKindOf(err) });
|
|
1921
|
+
return { delivered: false };
|
|
1826
1922
|
}
|
|
1827
|
-
return { legacy: delivered, card: cardDelivered };
|
|
1828
1923
|
};
|
|
1829
1924
|
const openByCtx = new Map(aliveCtxs.map((c) => [c, emitOne(c)]));
|
|
1830
1925
|
const emitAllP = (async () => {
|
|
1831
1926
|
const results = await Promise.all(openByCtx.values());
|
|
1832
|
-
const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.
|
|
1927
|
+
const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.delivered);
|
|
1833
1928
|
emitSettled = true;
|
|
1834
1929
|
if (!anySucceeded && !done) {
|
|
1835
1930
|
if (this.askStore && askId !== undefined && batchId !== undefined) {
|
|
@@ -1873,15 +1968,30 @@ export class ToolApprovalCoordinator {
|
|
|
1873
1968
|
for (const c of aliveCtxs) {
|
|
1874
1969
|
void openByCtx
|
|
1875
1970
|
.get(c)
|
|
1876
|
-
.then((delivered) =>
|
|
1877
|
-
|
|
1971
|
+
.then(({ delivered }) => {
|
|
1972
|
+
if (!delivered)
|
|
1973
|
+
return undefined;
|
|
1974
|
+
if (c.emitCard !== undefined) {
|
|
1975
|
+
if (cardFrame === undefined)
|
|
1976
|
+
return undefined;
|
|
1977
|
+
return c.emitCard(buildApprovalCompleteFrame({
|
|
1978
|
+
askId: cardFrame.askId,
|
|
1979
|
+
taskId: cardFrame.taskId,
|
|
1980
|
+
approvalId: wireApprovalId,
|
|
1981
|
+
outcome: lastOutcome,
|
|
1982
|
+
...(lastCause !== undefined ? { cause: lastCause } : {}),
|
|
1983
|
+
parked: parkRouted,
|
|
1984
|
+
nowMs: Date.now(),
|
|
1985
|
+
}), id);
|
|
1986
|
+
}
|
|
1987
|
+
return c.emit({
|
|
1878
1988
|
type: "tool_approval_complete",
|
|
1879
1989
|
approvalId: wireApprovalId,
|
|
1880
1990
|
outcome: lastOutcome,
|
|
1881
1991
|
...(lastCause !== undefined ? { cause: lastCause } : {}),
|
|
1882
1992
|
...(parkRouted ? { parked: true } : {}),
|
|
1883
|
-
})
|
|
1884
|
-
|
|
1993
|
+
});
|
|
1994
|
+
})
|
|
1885
1995
|
.catch(() => undefined);
|
|
1886
1996
|
}
|
|
1887
1997
|
if (emitFailed)
|
|
@@ -1892,24 +2002,47 @@ export class ToolApprovalCoordinator {
|
|
|
1892
2002
|
return this.unattendedOutcome({ arm: "emit_unsettled", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1893
2003
|
return shapeOutcome(settled);
|
|
1894
2004
|
}
|
|
1895
|
-
respond(id, principal, body, httpReq) {
|
|
2005
|
+
respond(id, principal, body, httpReq, deployment) {
|
|
1896
2006
|
const parsed = parseToolApprovalResponse(body);
|
|
1897
2007
|
if (!parsed.ok)
|
|
1898
|
-
return { status: 400, body: {
|
|
1899
|
-
|
|
2008
|
+
return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: parsed.error, facts: { ...(parsed.field !== undefined ? { field: parsed.field } : {}) } }) };
|
|
2009
|
+
if (parsed.readRootGrant !== undefined && deployment?.readRootGrantAccepted !== true) {
|
|
2010
|
+
return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: READ_ROOT_GRANT_NOT_ACCEPTED }) };
|
|
2011
|
+
}
|
|
2012
|
+
const entry = this.liveEntryFor(id);
|
|
2013
|
+
if (entry?.adopted === true)
|
|
2014
|
+
return this.respondAdopted(entry, id, principal, parsed, httpReq);
|
|
1900
2015
|
if (!entry || (entry.owner !== null && entry.owner !== principal)) {
|
|
1901
2016
|
return this.respondLate(id, principal, parsed, httpReq);
|
|
1902
2017
|
}
|
|
2018
|
+
return this.answerRegistration(entry, id, principal, parsed, httpReq);
|
|
2019
|
+
}
|
|
2020
|
+
async respondAdopted(entry, id, principal, parsed, httpReq) {
|
|
2021
|
+
const store = this.askStore;
|
|
2022
|
+
if (!store)
|
|
2023
|
+
return this.notPending();
|
|
2024
|
+
const row = await this.askRowFor(store, id, "getAsk(respond-adopted)");
|
|
2025
|
+
if (!askRowVisibleTo(row, principal))
|
|
2026
|
+
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2027
|
+
if (row.state !== "STREAM_PENDING")
|
|
2028
|
+
return this.dispatchRowTruth(row, id, parsed, httpReq, principal);
|
|
2029
|
+
return this.answerRegistration(entry, id, principal, parsed, httpReq);
|
|
2030
|
+
}
|
|
2031
|
+
answerRegistration(entry, id, principal, parsed, httpReq) {
|
|
1903
2032
|
const preRule = this.precheckEditedRuleBeforeDecision(entry, parsed);
|
|
1904
2033
|
if (preRule !== undefined)
|
|
1905
2034
|
return preRule;
|
|
1906
2035
|
if (parsed.value !== "deny" && policyCannotAnswer(heldAskOf(entry.requiresRealApproval === true), parsed.decidedBy)) {
|
|
1907
|
-
return { status: 400, body: {
|
|
2036
|
+
return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: POLICY_CANNOT_ALLOW_PERSON_ASK }) };
|
|
1908
2037
|
}
|
|
1909
2038
|
const ruleEligibility = { eligible: true };
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
|
|
2039
|
+
const store = this.askStore;
|
|
2040
|
+
if (store && entry.askId !== undefined && entry.batchId !== undefined) {
|
|
2041
|
+
return this.respondWithCas(store, entry.askId, entry.batchId, entry, parsed, ruleEligibility).then((cas) => cas.lost ? this.dispatchRowTruth(cas.row, id, parsed, httpReq, principal) : this.afterLiveDecision(cas.result, entry, parsed, principal, ruleEligibility));
|
|
2042
|
+
}
|
|
2043
|
+
return this.afterLiveDecision(this.finishRespond(entry, parsed), entry, parsed, principal, ruleEligibility);
|
|
2044
|
+
}
|
|
2045
|
+
afterLiveDecision(decided, entry, parsed, principal, ruleEligibility) {
|
|
1913
2046
|
const settled = this.recordDeploymentListAnswer(decided, entry, parsed, principal, ruleEligibility);
|
|
1914
2047
|
if (parsed.persistRule === undefined)
|
|
1915
2048
|
return settled;
|
|
@@ -1956,11 +2089,13 @@ export class ToolApprovalCoordinator {
|
|
|
1956
2089
|
return undefined;
|
|
1957
2090
|
return {
|
|
1958
2091
|
status: 400,
|
|
1959
|
-
body: {
|
|
1960
|
-
error: verdict.message,
|
|
2092
|
+
body: refusalBody({
|
|
1961
2093
|
errorCode: "request.body_shape",
|
|
1962
|
-
|
|
1963
|
-
|
|
2094
|
+
message: verdict.message,
|
|
2095
|
+
facts: {
|
|
2096
|
+
...(verdict.code !== undefined ? { ruleRejectCode: verdict.code } : {}),
|
|
2097
|
+
},
|
|
2098
|
+
}),
|
|
1964
2099
|
};
|
|
1965
2100
|
}
|
|
1966
2101
|
editedArmEcho(persistRule, persisted) {
|
|
@@ -1971,45 +2106,156 @@ export class ToolApprovalCoordinator {
|
|
|
1971
2106
|
notPending(cause) {
|
|
1972
2107
|
return {
|
|
1973
2108
|
status: 404,
|
|
1974
|
-
body: {
|
|
1975
|
-
error: "no pending tool approval for this id (settled, expired, or not on this replica)",
|
|
2109
|
+
body: refusalBody({
|
|
1976
2110
|
errorCode: "tool_approval.not_pending",
|
|
1977
|
-
|
|
1978
|
-
|
|
2111
|
+
message: "no pending tool approval for this id (settled, expired, or not on this replica)",
|
|
2112
|
+
facts: {
|
|
2113
|
+
...(cause !== undefined ? { cause } : {}),
|
|
2114
|
+
},
|
|
2115
|
+
}),
|
|
1979
2116
|
};
|
|
1980
2117
|
}
|
|
1981
|
-
|
|
2118
|
+
readApproval(id, principal) {
|
|
2119
|
+
const entry = this.liveEntryFor(id);
|
|
2120
|
+
if (entry?.adopted === true)
|
|
2121
|
+
return this.readAdopted(entry, id, principal);
|
|
2122
|
+
if (entry !== undefined && (entry.owner === null || entry.owner === principal)) {
|
|
2123
|
+
const body = { state: "pending", ...livePendingRowOf(entry) };
|
|
2124
|
+
return { status: 200, body };
|
|
2125
|
+
}
|
|
1982
2126
|
const store = this.askStore;
|
|
1983
2127
|
if (!store)
|
|
1984
2128
|
return this.notPending();
|
|
1985
|
-
|
|
1986
|
-
|
|
2129
|
+
return this.readAskRowState(store, id, principal);
|
|
2130
|
+
}
|
|
2131
|
+
async readAdopted(entry, id, principal) {
|
|
2132
|
+
const store = this.askStore;
|
|
2133
|
+
if (!store)
|
|
2134
|
+
return this.notPending();
|
|
2135
|
+
const row = await this.askRowFor(store, id, "getAsk(read-adopted)");
|
|
2136
|
+
if (askRowVisibleTo(row, principal) && row.state === "STREAM_PENDING") {
|
|
2137
|
+
const body = { state: "pending", ...livePendingRowOf(entry) };
|
|
2138
|
+
return { status: 200, body };
|
|
2139
|
+
}
|
|
2140
|
+
return this.readStateOfRow(row, principal);
|
|
2141
|
+
}
|
|
2142
|
+
async readAskRowState(store, id, principal) {
|
|
2143
|
+
return this.readStateOfRow(await this.askRowFor(store, id, "getAsk(read)"), principal);
|
|
2144
|
+
}
|
|
2145
|
+
readStateOfRow(row, principal) {
|
|
2146
|
+
if (!askRowVisibleTo(row, principal))
|
|
1987
2147
|
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2148
|
+
const projection = buildAskRowReadProjection(row, Date.now());
|
|
2149
|
+
let body;
|
|
2150
|
+
switch (row.state) {
|
|
2151
|
+
case "DECIDED": {
|
|
2152
|
+
const decided = decidedRowFacts(row);
|
|
2153
|
+
if (decided === undefined)
|
|
2154
|
+
return this.notPending();
|
|
2155
|
+
body = { state: "decided", ...projection, ...decided };
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
case "PARKING":
|
|
2159
|
+
case "PARKED": {
|
|
2160
|
+
const parkSession = parkSessionOf(row);
|
|
2161
|
+
body = { state: "parked", ...projection, ...(parkSession !== undefined ? { decidePath: approvalDecidePath(parkSession) } : {}) };
|
|
2162
|
+
break;
|
|
2163
|
+
}
|
|
2164
|
+
case "DENIED":
|
|
2165
|
+
case "VOID": {
|
|
2166
|
+
const rowCause = causeOfAskRow(row);
|
|
2167
|
+
body = { state: "void", ...projection, ...(rowCause?.kind === "ending" ? { cause: rowCause.cause } : {}) };
|
|
2168
|
+
break;
|
|
2169
|
+
}
|
|
2170
|
+
case "STREAM_PENDING":
|
|
2171
|
+
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2172
|
+
default: {
|
|
2173
|
+
const unhandled = row.state;
|
|
2174
|
+
defaultLogger.error("tool approval read hit an unknown ask state", { askId: row.askId, state: unhandled });
|
|
2175
|
+
return this.notPending();
|
|
2176
|
+
}
|
|
1988
2177
|
}
|
|
1989
|
-
return
|
|
2178
|
+
return { status: 200, body };
|
|
1990
2179
|
}
|
|
1991
|
-
|
|
1992
|
-
|
|
2180
|
+
respondLate(id, principal, parsed, httpReq) {
|
|
2181
|
+
const store = this.askStore;
|
|
2182
|
+
if (!store)
|
|
2183
|
+
return this.notPending();
|
|
2184
|
+
return this.dispatchLateByRow(store, id, parsed, httpReq, principal);
|
|
2185
|
+
}
|
|
2186
|
+
watchPresentedCards(askIds, deliver) {
|
|
2187
|
+
const watcher = { deliver, askIds: new Set() };
|
|
2188
|
+
for (const askId of askIds) {
|
|
2189
|
+
if (!this.pendingByAskId.has(askId))
|
|
2190
|
+
continue;
|
|
2191
|
+
watcher.askIds.add(askId);
|
|
2192
|
+
let set = this.presentedCardWatchers.get(askId);
|
|
2193
|
+
if (set === undefined) {
|
|
2194
|
+
set = new Set();
|
|
2195
|
+
this.presentedCardWatchers.set(askId, set);
|
|
2196
|
+
}
|
|
2197
|
+
set.add(watcher);
|
|
2198
|
+
}
|
|
2199
|
+
return () => {
|
|
2200
|
+
for (const askId of watcher.askIds)
|
|
2201
|
+
this.dropPresentedCardWatcher(askId, watcher);
|
|
2202
|
+
watcher.askIds.clear();
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
dropPresentedCardWatcher(askId, watcher) {
|
|
2206
|
+
const set = this.presentedCardWatchers.get(askId);
|
|
2207
|
+
if (set === undefined)
|
|
2208
|
+
return;
|
|
2209
|
+
set.delete(watcher);
|
|
2210
|
+
if (set.size === 0)
|
|
2211
|
+
this.presentedCardWatchers.delete(askId);
|
|
2212
|
+
}
|
|
2213
|
+
notifyPresentedCardWatchers(askId, frame) {
|
|
2214
|
+
const set = this.presentedCardWatchers.get(askId);
|
|
2215
|
+
if (set === undefined)
|
|
2216
|
+
return;
|
|
2217
|
+
this.presentedCardWatchers.delete(askId);
|
|
2218
|
+
for (const watcher of set) {
|
|
2219
|
+
watcher.askIds.delete(askId);
|
|
2220
|
+
try {
|
|
2221
|
+
watcher.deliver(frame);
|
|
2222
|
+
}
|
|
2223
|
+
catch (err) {
|
|
2224
|
+
defaultLogger.warn("presented-card completion notice could not be written to its connection", { askId, error: err instanceof Error ? err.message : String(err) });
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
liveEntryFor(id) {
|
|
2229
|
+
const local = this.pending.get(id);
|
|
2230
|
+
if (local !== undefined)
|
|
2231
|
+
return local.askId === undefined ? local : undefined;
|
|
2232
|
+
for (const e of this.pendingByAskId.get(id) ?? [])
|
|
2233
|
+
return e;
|
|
2234
|
+
return undefined;
|
|
2235
|
+
}
|
|
2236
|
+
async dispatchLateByRow(store, id, parsed, httpReq, principal) {
|
|
2237
|
+
return this.dispatchRowTruth(await this.askRowFor(store, id, "getAsk(respond-late)"), id, parsed, httpReq, principal);
|
|
2238
|
+
}
|
|
2239
|
+
async askRowFor(store, id, label) {
|
|
1993
2240
|
try {
|
|
1994
|
-
|
|
2241
|
+
return await this.withStoreDeadline(store.getAsk(id), label, DURABLE_CONVERGE_READ_TIMEOUT_MS);
|
|
1995
2242
|
}
|
|
1996
2243
|
catch (err) {
|
|
1997
|
-
this.noteStoreError(err,
|
|
1998
|
-
return
|
|
2244
|
+
this.noteStoreError(err, label);
|
|
2245
|
+
return null;
|
|
1999
2246
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
if (row
|
|
2003
|
-
defaultLogger.warn("tool approval late decision rejected: the ask row changed owner since it parked", { askId: row.askId });
|
|
2247
|
+
}
|
|
2248
|
+
async dispatchRowTruth(row, id, parsed, httpReq, principal) {
|
|
2249
|
+
if (!askRowVisibleTo(row, principal))
|
|
2004
2250
|
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2005
|
-
|
|
2006
|
-
if (
|
|
2007
|
-
return this.replayRedeemReceipt(id,
|
|
2251
|
+
const receipt = this.lookupParkRedemption(row.askId);
|
|
2252
|
+
if (receipt?.redeemed !== undefined)
|
|
2253
|
+
return this.replayRedeemReceipt(id, receipt, row, receipt.redeemed, parsed);
|
|
2008
2254
|
switch (row.state) {
|
|
2009
2255
|
case "DECIDED":
|
|
2010
2256
|
return this.replayDecidedAsk(id, row, parsed);
|
|
2011
2257
|
case "PARKED":
|
|
2012
|
-
return this.redeemParkedAsk(id,
|
|
2258
|
+
return this.redeemParkedAsk(id, row, parsed, httpReq, principal);
|
|
2013
2259
|
case "PARKING": {
|
|
2014
2260
|
const parkSession = parkSessionOf(row);
|
|
2015
2261
|
return {
|
|
@@ -2036,7 +2282,7 @@ export class ToolApprovalCoordinator {
|
|
|
2036
2282
|
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2037
2283
|
default: {
|
|
2038
2284
|
const unhandled = row.state;
|
|
2039
|
-
defaultLogger.error("tool approval respond hit an unknown ask state", { askId:
|
|
2285
|
+
defaultLogger.error("tool approval respond hit an unknown ask state", { askId: row.askId, state: unhandled });
|
|
2040
2286
|
return this.notPending();
|
|
2041
2287
|
}
|
|
2042
2288
|
}
|
|
@@ -2047,7 +2293,8 @@ export class ToolApprovalCoordinator {
|
|
|
2047
2293
|
return { rulePersisted: false, ruleRefusal: "rule_lane_unavailable" };
|
|
2048
2294
|
}
|
|
2049
2295
|
replayDecidedAsk(id, row, parsed) {
|
|
2050
|
-
|
|
2296
|
+
const decided = decidedRowFacts(row);
|
|
2297
|
+
if (decided === undefined) {
|
|
2051
2298
|
defaultLogger.error("tool approval late replay saw a DECIDED row that is not a usable human decision", {
|
|
2052
2299
|
askId: row.askId,
|
|
2053
2300
|
decision: row.decision,
|
|
@@ -2063,15 +2310,14 @@ export class ToolApprovalCoordinator {
|
|
|
2063
2310
|
approvalId: id,
|
|
2064
2311
|
askId: row.askId,
|
|
2065
2312
|
idempotent: true,
|
|
2066
|
-
|
|
2067
|
-
decidedAtMs: row.decidedAtMs,
|
|
2068
|
-
...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}),
|
|
2313
|
+
...decided,
|
|
2069
2314
|
...(parsed.value === "allow_session" ? { rememberApplied: false } : {}),
|
|
2315
|
+
...noteRecordedReceipt(parsed.note, noteLandedOnRow(parsed.note, row.decisionNote)),
|
|
2070
2316
|
...this.lateRuleRefusal(parsed),
|
|
2071
2317
|
},
|
|
2072
2318
|
};
|
|
2073
2319
|
}
|
|
2074
|
-
async redeemParkedAsk(id,
|
|
2320
|
+
async redeemParkedAsk(id, row, parsed, httpReq, principal) {
|
|
2075
2321
|
const redeem = this.parkedRedeem?.();
|
|
2076
2322
|
if (redeem === undefined)
|
|
2077
2323
|
return this.notPending();
|
|
@@ -2079,14 +2325,19 @@ export class ToolApprovalCoordinator {
|
|
|
2079
2325
|
defaultLogger.error("tool approval parked row carries no gate token — nothing to redeem", { askId: row.askId });
|
|
2080
2326
|
return this.notPending();
|
|
2081
2327
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2328
|
+
const prior = this.lookupParkRedemption(row.askId);
|
|
2329
|
+
if (prior?.redeemed !== undefined)
|
|
2330
|
+
return this.replayRedeemReceipt(id, prior, row, prior.redeemed, parsed);
|
|
2331
|
+
if (prior?.inflight !== undefined) {
|
|
2332
|
+
const shared = await prior.inflight;
|
|
2086
2333
|
if (shared.status < 200 || shared.status >= 300)
|
|
2087
2334
|
return shared;
|
|
2088
|
-
return this.replayRedeemReceipt(id,
|
|
2335
|
+
return this.replayRedeemReceipt(id, prior, row, { decision: shared.decision, body: shared.body, ...(shared.rule?.terminal === true ? { rule: shared.rule.body } : {}) }, parsed);
|
|
2089
2336
|
}
|
|
2337
|
+
const admission = this.parkRedemptionAdmission(row.owner);
|
|
2338
|
+
if (admission !== undefined)
|
|
2339
|
+
return admission;
|
|
2340
|
+
const memo = this.openParkRedemption(row.askId, row.owner);
|
|
2090
2341
|
const decision = parsed.value === "deny" ? "deny" : "approve";
|
|
2091
2342
|
const gateToken = row.gateToken;
|
|
2092
2343
|
const flight = (async () => {
|
|
@@ -2107,25 +2358,29 @@ export class ToolApprovalCoordinator {
|
|
|
2107
2358
|
}
|
|
2108
2359
|
catch (err) {
|
|
2109
2360
|
defaultLogger.error("tool approval parked redemption threw", { askId: row.askId, error: err instanceof Error ? err.message : String(err) });
|
|
2110
|
-
return { status: 500, body: { error: "the parked approval could not be redeemed right now — retry"
|
|
2361
|
+
return { status: 500, body: refusalBody({ errorCode: "internal.error", message: "the parked approval could not be redeemed right now — retry" }), decision };
|
|
2111
2362
|
}
|
|
2112
2363
|
if (res.status < 200 || res.status >= 300)
|
|
2113
2364
|
return { ...res, decision };
|
|
2114
2365
|
const rule = await this.persistRuleFromRow(row, parsed);
|
|
2115
2366
|
return { ...res, decision, ...(rule !== undefined ? { rule } : {}) };
|
|
2116
2367
|
})();
|
|
2117
|
-
|
|
2368
|
+
memo.inflight = flight;
|
|
2118
2369
|
let out;
|
|
2119
2370
|
try {
|
|
2120
2371
|
out = await flight;
|
|
2121
2372
|
}
|
|
2122
2373
|
finally {
|
|
2123
|
-
|
|
2374
|
+
memo.inflight = undefined;
|
|
2124
2375
|
}
|
|
2125
|
-
if (out.status < 200 || out.status >= 300)
|
|
2376
|
+
if (out.status < 200 || out.status >= 300) {
|
|
2377
|
+
if (this.parkRedemptions.get(row.askId) === memo)
|
|
2378
|
+
this.parkRedemptions.delete(row.askId);
|
|
2126
2379
|
return { status: out.status, body: out.body };
|
|
2380
|
+
}
|
|
2127
2381
|
const rule = out.rule;
|
|
2128
|
-
|
|
2382
|
+
if (this.retainParkRedemption(row.askId, memo))
|
|
2383
|
+
memo.redeemed = { decision, body: out.body, ...(rule?.terminal === true ? { rule: rule.body } : {}) };
|
|
2129
2384
|
return {
|
|
2130
2385
|
status: out.status,
|
|
2131
2386
|
body: {
|
|
@@ -2216,24 +2471,24 @@ export class ToolApprovalCoordinator {
|
|
|
2216
2471
|
return { ...flag(false, "rule_store_error"), terminal: false };
|
|
2217
2472
|
}
|
|
2218
2473
|
}
|
|
2219
|
-
async replayRedeemReceipt(id,
|
|
2220
|
-
let rule = receipt.rule;
|
|
2221
|
-
if (rule === undefined && parsed.persistRule !== undefined) {
|
|
2222
|
-
const flight =
|
|
2223
|
-
const isOwner =
|
|
2474
|
+
async replayRedeemReceipt(id, memo, row, receipt, parsed) {
|
|
2475
|
+
let rule = receipt.decision === "approve" ? receipt.rule : this.lateRuleRefusal(parsed);
|
|
2476
|
+
if (receipt.decision === "approve" && rule === undefined && parsed.persistRule !== undefined) {
|
|
2477
|
+
const flight = memo.ruleInflight ?? this.persistRuleFromRow(row, parsed);
|
|
2478
|
+
const isOwner = memo.ruleInflight === undefined;
|
|
2224
2479
|
if (isOwner)
|
|
2225
|
-
|
|
2480
|
+
memo.ruleInflight = flight;
|
|
2226
2481
|
let fresh;
|
|
2227
2482
|
try {
|
|
2228
2483
|
fresh = await flight;
|
|
2229
2484
|
}
|
|
2230
2485
|
finally {
|
|
2231
2486
|
if (isOwner)
|
|
2232
|
-
|
|
2487
|
+
memo.ruleInflight = undefined;
|
|
2233
2488
|
}
|
|
2234
2489
|
rule = fresh?.body;
|
|
2235
|
-
if (fresh?.terminal === true &&
|
|
2236
|
-
|
|
2490
|
+
if (fresh?.terminal === true && memo.redeemed !== undefined)
|
|
2491
|
+
memo.redeemed.rule = fresh.body;
|
|
2237
2492
|
}
|
|
2238
2493
|
return {
|
|
2239
2494
|
status: 200,
|
|
@@ -2328,7 +2583,7 @@ export class ToolApprovalCoordinator {
|
|
|
2328
2583
|
return withFlag(false, "rule_store_error");
|
|
2329
2584
|
}
|
|
2330
2585
|
}
|
|
2331
|
-
async respondWithCas(askStore, askId, batchId,
|
|
2586
|
+
async respondWithCas(askStore, askId, batchId, entry, parsed, ruleEligibility) {
|
|
2332
2587
|
let winnerRow;
|
|
2333
2588
|
let ambiguousConfirmedRow;
|
|
2334
2589
|
let noteLanded = false;
|
|
@@ -2347,7 +2602,7 @@ export class ToolApprovalCoordinator {
|
|
|
2347
2602
|
noteLanded = true;
|
|
2348
2603
|
}
|
|
2349
2604
|
if (!decided.ok) {
|
|
2350
|
-
return
|
|
2605
|
+
return { lost: true, row: decided.row };
|
|
2351
2606
|
}
|
|
2352
2607
|
}
|
|
2353
2608
|
catch (err) {
|
|
@@ -2358,7 +2613,7 @@ export class ToolApprovalCoordinator {
|
|
|
2358
2613
|
if (row?.state === "DECIDED" && row.decision === (parsed.value === "deny" ? "deny" : "approve")) {
|
|
2359
2614
|
winnerRow = row;
|
|
2360
2615
|
ambiguousConfirmedRow = row;
|
|
2361
|
-
noteLanded = parsed.note
|
|
2616
|
+
noteLanded = noteLandedOnRow(parsed.note, row.decisionNote);
|
|
2362
2617
|
}
|
|
2363
2618
|
}
|
|
2364
2619
|
catch (confirmErr) {
|
|
@@ -2367,27 +2622,27 @@ export class ToolApprovalCoordinator {
|
|
|
2367
2622
|
}
|
|
2368
2623
|
const wantAllowed = parsed.value !== "deny";
|
|
2369
2624
|
const decidedWon = winnerRow !== undefined;
|
|
2370
|
-
if (
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const
|
|
2625
|
+
if (entry.adopted === true && !decidedWon)
|
|
2626
|
+
return { lost: false, result: this.notPending() };
|
|
2627
|
+
if (this.pending.get(entry.registrationId) !== entry) {
|
|
2628
|
+
const othersReached = winnerRow !== undefined ? this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow)).settled : 0;
|
|
2629
|
+
const converged = decidedWon && (entry.adopted === true || entry.settledOutcome === (wantAllowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED));
|
|
2374
2630
|
if (!converged) {
|
|
2375
|
-
return this.notPending();
|
|
2631
|
+
return { lost: false, result: this.notPending() };
|
|
2376
2632
|
}
|
|
2377
|
-
return this.finishRespond(
|
|
2633
|
+
return { lost: false, result: withConsumerDisclosure(this.finishRespond(entry, parsed, { updatedInputDelivered: false, noteRecorded: noteLanded }), waitingLegsReached(entry, othersReached)) };
|
|
2378
2634
|
}
|
|
2379
2635
|
const payloadIsMine = ambiguousConfirmedRow === undefined ||
|
|
2380
2636
|
approvalPayloadFingerprint({ updatedInput: ambiguousConfirmedRow.updatedInput }) === approvalPayloadFingerprint({ updatedInput: parsed.updatedInput });
|
|
2381
|
-
const result = this.finishRespond(
|
|
2637
|
+
const result = this.finishRespond(entry, parsed, {
|
|
2382
2638
|
noteRecorded: noteLanded,
|
|
2383
2639
|
...(winnerRow !== undefined ? { settleOutcome: outcomeOfDecidedRow(winnerRow), settleOutcomeIsThisRequest: ambiguousConfirmedRow === undefined } : {}),
|
|
2384
|
-
...(ambiguousConfirmedRow !== undefined ? { updatedInputDelivered: payloadIsMine } : {}),
|
|
2640
|
+
...(entry.adopted === true ? { updatedInputDelivered: false } : ambiguousConfirmedRow !== undefined ? { updatedInputDelivered: payloadIsMine } : {}),
|
|
2385
2641
|
});
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
return result;
|
|
2642
|
+
const othersReached = winnerRow !== undefined ? this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow)).settled : 0;
|
|
2643
|
+
return { lost: false, result: withConsumerDisclosure(result, waitingLegsReached(entry, othersReached)) };
|
|
2389
2644
|
}
|
|
2390
|
-
finishRespond(
|
|
2645
|
+
finishRespond(entry, parsed, opts) {
|
|
2391
2646
|
let rememberApplied;
|
|
2392
2647
|
const home = standingAnswerHome(pendingEntryStandingAnswerFacts(entry));
|
|
2393
2648
|
let grantable;
|
|
@@ -2430,42 +2685,146 @@ export class ToolApprovalCoordinator {
|
|
|
2430
2685
|
return {
|
|
2431
2686
|
status: 200,
|
|
2432
2687
|
body: {
|
|
2433
|
-
approvalId:
|
|
2688
|
+
approvalId: entry.approvalId,
|
|
2434
2689
|
delivery: "applied",
|
|
2435
2690
|
decision: parsed.value,
|
|
2436
2691
|
...(rememberApplied !== undefined ? { rememberApplied } : {}),
|
|
2437
2692
|
...(parsed.updatedInput !== undefined && allowed && updatedInputDelivered ? { updatedInputForwarded: true } : {}),
|
|
2438
|
-
...(parsed.note
|
|
2693
|
+
...noteRecordedReceipt(parsed.note, noteRecorded),
|
|
2439
2694
|
},
|
|
2440
2695
|
};
|
|
2441
2696
|
}
|
|
2697
|
+
async adoptOpenAsks() {
|
|
2698
|
+
const store = this.askStore;
|
|
2699
|
+
if (!store)
|
|
2700
|
+
return { adopted: 0, alreadyLive: 0, cardUnreadable: 0, capped: false };
|
|
2701
|
+
let rows;
|
|
2702
|
+
try {
|
|
2703
|
+
rows = await this.withStoreDeadline(store.listByState("STREAM_PENDING", ADOPT_OPEN_ASKS_MAX + 1), "listByState(adopt)");
|
|
2704
|
+
}
|
|
2705
|
+
catch (err) {
|
|
2706
|
+
throw new Error(`approval ledger adoption: could not read the approval asks that are still pending in the ask ledger (${err instanceof Error ? err.message : String(err)}) — ` +
|
|
2707
|
+
"refusing to start, because serving now would hide asks that are still waiting for an answer from GET /v1/approvals. Check the database and start again.");
|
|
2708
|
+
}
|
|
2709
|
+
const capped = rows.length > ADOPT_OPEN_ASKS_MAX;
|
|
2710
|
+
const nowMs = Date.now();
|
|
2711
|
+
let adopted = 0;
|
|
2712
|
+
let alreadyLive = 0;
|
|
2713
|
+
let cardUnreadable = 0;
|
|
2714
|
+
for (const row of capped ? rows.slice(0, ADOPT_OPEN_ASKS_MAX) : rows) {
|
|
2715
|
+
if (this.pendingByAskId.has(row.askId)) {
|
|
2716
|
+
alreadyLive += 1;
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
const replayFrame = buildReplayFrame(row, nowMs);
|
|
2720
|
+
if (replayFrame === undefined) {
|
|
2721
|
+
cardUnreadable += 1;
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2724
|
+
this.registerAdopted(row, replayFrame);
|
|
2725
|
+
adopted += 1;
|
|
2726
|
+
}
|
|
2727
|
+
return { adopted, alreadyLive, cardUnreadable, capped };
|
|
2728
|
+
}
|
|
2729
|
+
registerAdopted(row, replayFrame) {
|
|
2730
|
+
const card = replayFrame.card;
|
|
2731
|
+
const registrationId = uuidv7();
|
|
2732
|
+
const facts = {
|
|
2733
|
+
...(card.governanceForced === true ? { governanceForced: true } : {}),
|
|
2734
|
+
...(card.risk.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
2735
|
+
grantBinding: { kind: "unreadable" },
|
|
2736
|
+
...(card.classes !== undefined ? { classes: card.classes } : {}),
|
|
2737
|
+
};
|
|
2738
|
+
const rememberRefused = standingAnswerHome(pendingEntryStandingAnswerFacts(facts)) === "none";
|
|
2739
|
+
const cardFrame = (card.rememberRefused === true) === rememberRefused
|
|
2740
|
+
? replayFrame
|
|
2741
|
+
: { ...replayFrame, card: rememberRefused ? { ...card, rememberRefused: true } : (({ rememberRefused: _stale, ...rest }) => rest)(card) };
|
|
2742
|
+
let done = false;
|
|
2743
|
+
const leave = (ended) => {
|
|
2744
|
+
if (done)
|
|
2745
|
+
return;
|
|
2746
|
+
done = true;
|
|
2747
|
+
this.pending.delete(registrationId);
|
|
2748
|
+
entry.settledOutcome = ended?.outcome ?? SETTLE_OUTCOME.EXPIRED;
|
|
2749
|
+
const siblings = this.pendingByAskId.get(row.askId);
|
|
2750
|
+
siblings?.delete(entry);
|
|
2751
|
+
const lastRegistration = siblings !== undefined && siblings.size === 0;
|
|
2752
|
+
if (lastRegistration)
|
|
2753
|
+
this.pendingByAskId.delete(row.askId);
|
|
2754
|
+
if (ended !== undefined && lastRegistration) {
|
|
2755
|
+
this.notifyPresentedCardWatchers(row.askId, buildApprovalCompleteFrame({ askId: row.askId, taskId: row.taskId, approvalId: row.askId, outcome: ended.outcome, ...(ended.cause !== undefined ? { cause: ended.cause } : {}), parked: ended.parked, nowMs: Date.now() }));
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
const release = () => leave(undefined);
|
|
2759
|
+
const entry = {
|
|
2760
|
+
adopted: true,
|
|
2761
|
+
cardFrame,
|
|
2762
|
+
release,
|
|
2763
|
+
settle: (verdict) => leave(verdict.by === "system" ? { outcome: SETTLE_OUTCOME.EXPIRED, cause: verdict.cause, parked: false } : { outcome: verdict.allowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED, parked: false }),
|
|
2764
|
+
forceParkNow: release,
|
|
2765
|
+
settleParkRoute: (cause) => leave({ outcome: SETTLE_OUTCOME.EXPIRED, cause, parked: this.unattendedRoutesToPark }),
|
|
2766
|
+
owner: row.owner,
|
|
2767
|
+
...(row.sessionId !== row.taskId ? { sessionId: row.sessionId } : {}),
|
|
2768
|
+
...(card.fromSubagent === true ? { originTaskId: row.sourceTaskId } : {}),
|
|
2769
|
+
toolName: card.toolName,
|
|
2770
|
+
...facts,
|
|
2771
|
+
approvalId: row.askId,
|
|
2772
|
+
registrationId,
|
|
2773
|
+
registeredAtMs: row.createdAtMs,
|
|
2774
|
+
expiresAtMs: row.expiresAtMs,
|
|
2775
|
+
askId: row.askId,
|
|
2776
|
+
batchId: row.batchId,
|
|
2777
|
+
};
|
|
2778
|
+
this.pending.set(registrationId, entry);
|
|
2779
|
+
const siblings = this.pendingByAskId.get(row.askId) ?? new Set();
|
|
2780
|
+
siblings.add(entry);
|
|
2781
|
+
this.pendingByAskId.set(row.askId, siblings);
|
|
2782
|
+
}
|
|
2783
|
+
releaseAdoptedRegistrations(askId) {
|
|
2784
|
+
for (const e of [...(this.pendingByAskId.get(askId) ?? [])])
|
|
2785
|
+
if (e.adopted === true)
|
|
2786
|
+
e.release();
|
|
2787
|
+
}
|
|
2788
|
+
forgetAdoptedAsk(askId) {
|
|
2789
|
+
this.releaseAdoptedRegistrations(askId);
|
|
2790
|
+
if (this.pendingByAskId.has(askId))
|
|
2791
|
+
return;
|
|
2792
|
+
const watchers = this.presentedCardWatchers.get(askId);
|
|
2793
|
+
if (watchers === undefined)
|
|
2794
|
+
return;
|
|
2795
|
+
this.presentedCardWatchers.delete(askId);
|
|
2796
|
+
for (const watcher of watchers)
|
|
2797
|
+
watcher.askIds.delete(askId);
|
|
2798
|
+
}
|
|
2799
|
+
followAdoptedOwner(askId, owner) {
|
|
2800
|
+
for (const e of this.pendingByAskId.get(askId) ?? [])
|
|
2801
|
+
if (e.adopted === true)
|
|
2802
|
+
e.owner = owner;
|
|
2803
|
+
}
|
|
2804
|
+
waitingRegistrationCount(askId) {
|
|
2805
|
+
let n = 0;
|
|
2806
|
+
for (const e of this.pendingByAskId.get(askId) ?? [])
|
|
2807
|
+
if (e.adopted !== true)
|
|
2808
|
+
n += 1;
|
|
2809
|
+
return n;
|
|
2810
|
+
}
|
|
2442
2811
|
listLivePending(scope) {
|
|
2443
2812
|
const rows = [];
|
|
2444
|
-
|
|
2445
|
-
|
|
2813
|
+
const owner = scope === undefined ? undefined : ownerOfCheckpointScope(scope);
|
|
2814
|
+
const seen = new Set();
|
|
2815
|
+
for (const e of this.pending.values()) {
|
|
2816
|
+
if (owner !== undefined && e.owner !== owner)
|
|
2446
2817
|
continue;
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
expiresAtMs: e.expiresAtMs,
|
|
2452
|
-
...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
|
|
2453
|
-
...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
2454
|
-
...(e.governanceForced ? { governanceForced: true } : {}),
|
|
2455
|
-
...(e.mandated ? { mandated: true } : {}),
|
|
2456
|
-
...(standingAnswerHome(pendingEntryStandingAnswerFacts(e)) === "none" ? { rememberRefused: true } : {}),
|
|
2457
|
-
...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
|
|
2458
|
-
...(e.origin !== undefined ? { origin: e.origin } : {}),
|
|
2459
|
-
...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
|
|
2460
|
-
...(e.frame.approvalCard !== undefined ? { approvalCard: e.frame.approvalCard } : {}),
|
|
2461
|
-
frame: e.frame.approvalId === id ? e.frame : { ...e.frame, approvalId: id },
|
|
2462
|
-
});
|
|
2818
|
+
if (seen.has(e.approvalId))
|
|
2819
|
+
continue;
|
|
2820
|
+
seen.add(e.approvalId);
|
|
2821
|
+
rows.push(livePendingRowOf(e));
|
|
2463
2822
|
}
|
|
2464
2823
|
return rows;
|
|
2465
2824
|
}
|
|
2466
2825
|
hasLivePendingFrom(originTaskId) {
|
|
2467
2826
|
for (const e of this.pending.values())
|
|
2468
|
-
if (e.originTaskId === originTaskId)
|
|
2827
|
+
if (e.originTaskId === originTaskId && e.adopted !== true)
|
|
2469
2828
|
return true;
|
|
2470
2829
|
return false;
|
|
2471
2830
|
}
|
|
@@ -2473,27 +2832,42 @@ export class ToolApprovalCoordinator {
|
|
|
2473
2832
|
const store = this.askStore;
|
|
2474
2833
|
if (!store)
|
|
2475
2834
|
return { polled: 0, settled: 0, backlog: 0, truncated: false };
|
|
2476
|
-
const
|
|
2835
|
+
const waiting = [];
|
|
2836
|
+
const adopted = [];
|
|
2477
2837
|
const seen = new Set();
|
|
2478
2838
|
for (const entry of this.pending.values()) {
|
|
2479
|
-
if (entry.
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2839
|
+
if (entry.askId === undefined || entry.settledOutcome !== undefined || seen.has(entry.askId))
|
|
2840
|
+
continue;
|
|
2841
|
+
if (entry.suspended === true)
|
|
2842
|
+
waiting.push(entry.askId);
|
|
2843
|
+
else if (entry.adopted === true)
|
|
2844
|
+
adopted.push(entry.askId);
|
|
2845
|
+
else
|
|
2846
|
+
continue;
|
|
2847
|
+
seen.add(entry.askId);
|
|
2848
|
+
}
|
|
2849
|
+
const waitingStart = waiting.length > 0 ? this.suspendedPollOffset % waiting.length : 0;
|
|
2850
|
+
const adoptedStart = adopted.length > 0 ? this.adoptedPollOffset % adopted.length : 0;
|
|
2851
|
+
const ordered = [
|
|
2852
|
+
...[...waiting.slice(waitingStart), ...waiting.slice(0, waitingStart)].map((askId) => ({ askId, tier: "waiting" })),
|
|
2853
|
+
...[...adopted.slice(adoptedStart), ...adopted.slice(0, adoptedStart)].map((askId) => ({ askId, tier: "adopted" })),
|
|
2854
|
+
];
|
|
2486
2855
|
const deadlineMs = Date.now() + SUSPENDED_POLL_BUDGET_MS;
|
|
2487
2856
|
let settled = 0;
|
|
2488
2857
|
let polled = 0;
|
|
2858
|
+
let polledWaiting = 0;
|
|
2859
|
+
let polledAdopted = 0;
|
|
2489
2860
|
let truncated = false;
|
|
2490
|
-
for (const askId of ordered) {
|
|
2861
|
+
for (const { askId, tier } of ordered) {
|
|
2491
2862
|
if (polled >= SUSPENDED_POLL_MAX_PER_TICK || Date.now() >= deadlineMs) {
|
|
2492
2863
|
truncated = true;
|
|
2493
2864
|
break;
|
|
2494
2865
|
}
|
|
2495
2866
|
polled++;
|
|
2496
|
-
|
|
2867
|
+
if (tier === "waiting")
|
|
2868
|
+
this.suspendedPollOffset = waitingStart + ++polledWaiting;
|
|
2869
|
+
else
|
|
2870
|
+
this.adoptedPollOffset = adoptedStart + ++polledAdopted;
|
|
2497
2871
|
let row;
|
|
2498
2872
|
try {
|
|
2499
2873
|
row = await this.withStoreDeadline(store.getAsk(askId), "getAsk(poll-suspended)", DURABLE_CONVERGE_READ_TIMEOUT_MS);
|
|
@@ -2502,20 +2876,26 @@ export class ToolApprovalCoordinator {
|
|
|
2502
2876
|
this.noteStoreError(err, "getAsk(poll-suspended)");
|
|
2503
2877
|
continue;
|
|
2504
2878
|
}
|
|
2505
|
-
if (row === null
|
|
2879
|
+
if (row === null) {
|
|
2880
|
+
this.forgetAdoptedAsk(askId);
|
|
2881
|
+
continue;
|
|
2882
|
+
}
|
|
2883
|
+
if (row.state === "STREAM_PENDING") {
|
|
2884
|
+
this.followAdoptedOwner(askId, row.owner);
|
|
2506
2885
|
continue;
|
|
2886
|
+
}
|
|
2507
2887
|
if (this.editedDecisionInFlight(askId))
|
|
2508
2888
|
continue;
|
|
2509
2889
|
const rowCause = causeOfAskRow(row);
|
|
2510
2890
|
if (rowCause?.kind === "ending") {
|
|
2511
|
-
const before = this.
|
|
2891
|
+
const before = this.waitingRegistrationCount(askId);
|
|
2512
2892
|
this.settleVoidedSiblings([askId], rowCause.cause);
|
|
2513
2893
|
settled += before;
|
|
2514
2894
|
continue;
|
|
2515
2895
|
}
|
|
2516
2896
|
const outcome = replayTerminalAskRow(row);
|
|
2517
2897
|
if (outcome === "unavailable") {
|
|
2518
|
-
const before = this.
|
|
2898
|
+
const before = this.waitingRegistrationCount(askId);
|
|
2519
2899
|
const parkCause = (rowCause?.kind === "park_trigger" ? rowCause.cause : undefined) ?? (Date.now() >= row.expiresAtMs ? APPROVAL_ENDING_CAUSES.TTL_EXPIRED : APPROVAL_ENDING_CAUSES.SHUTDOWN_BEFORE_PARK);
|
|
2520
2900
|
this.settleSameAskIdParkRoute(askId, parkCause);
|
|
2521
2901
|
settled += before;
|
|
@@ -2524,7 +2904,7 @@ export class ToolApprovalCoordinator {
|
|
|
2524
2904
|
const delivered = this.notifyExternalDecision(askId, outcome).settled;
|
|
2525
2905
|
settled += delivered;
|
|
2526
2906
|
}
|
|
2527
|
-
const backlog = Math.max(0,
|
|
2907
|
+
const backlog = Math.max(0, waiting.length + adopted.length - polled);
|
|
2528
2908
|
if (truncated) {
|
|
2529
2909
|
defaultLogger.warn("approval_suspended_poll_truncated", {
|
|
2530
2910
|
polled,
|
|
@@ -2533,6 +2913,7 @@ export class ToolApprovalCoordinator {
|
|
|
2533
2913
|
maxPerTick: SUSPENDED_POLL_MAX_PER_TICK,
|
|
2534
2914
|
budgetMs: SUSPENDED_POLL_BUDGET_MS,
|
|
2535
2915
|
resumeFromOffset: this.suspendedPollOffset,
|
|
2916
|
+
resumeAdoptedFromOffset: this.adoptedPollOffset,
|
|
2536
2917
|
});
|
|
2537
2918
|
}
|
|
2538
2919
|
return { polled, settled, backlog, truncated };
|
|
@@ -2582,8 +2963,11 @@ export class ToolApprovalCoordinator {
|
|
|
2582
2963
|
sessionAllowedCount() {
|
|
2583
2964
|
return this.allowAllSessions.size;
|
|
2584
2965
|
}
|
|
2585
|
-
|
|
2586
|
-
return this.
|
|
2966
|
+
presentedCardWatcherCount() {
|
|
2967
|
+
return this.presentedCardWatchers.size;
|
|
2968
|
+
}
|
|
2969
|
+
parkRedemptionCount() {
|
|
2970
|
+
return this.parkRedemptions.size;
|
|
2587
2971
|
}
|
|
2588
2972
|
}
|
|
2589
2973
|
//# sourceMappingURL=tool-approval.js.map
|