@sema-agent/server 7.132.0 → 7.134.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 +353 -0
- package/README.md +8 -9
- package/README.zh-CN.md +7 -6
- package/USAGE.md +301 -64
- package/deploy/sema-up/README.md +35 -1
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
- package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +19 -2
- package/deploy/sema-up/chart/templates/server.yaml +19 -2
- package/deploy/sema-up/chart/values-full-single.yaml +5 -1
- package/deploy/sema-up/chart/values.yaml +34 -0
- 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 +32 -14
- 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 +83 -23
- package/dist/approval-card.js +45 -15
- package/dist/approval-content-kind.d.ts +5 -2
- package/dist/approval-content-kind.js +16 -3
- package/dist/approval-deny-reasons.d.ts +1 -1
- package/dist/approval-gate-currency.d.ts +72 -0
- package/dist/approval-gate-currency.js +94 -0
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +55 -17
- package/dist/audit.d.ts +27 -10
- package/dist/audit.js +12 -16
- package/dist/bake-runner/main.js +3 -2
- package/dist/boot/coordinators.d.ts +6 -1
- package/dist/boot/coordinators.js +6 -3
- package/dist/boot/deferred-sandbox-path-env.js +1 -1
- package/dist/boot/execution-env.js +9 -7
- package/dist/boot/execution-lane.js +11 -0
- package/dist/boot/leader.d.ts +2 -0
- package/dist/boot/leader.js +4 -1
- 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.js +2 -2
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +21 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +60 -81
- package/dist/boot/runner-deps.d.ts +18 -5
- package/dist/boot/runner-deps.js +10 -0
- package/dist/boot/session-faces.d.ts +1 -0
- package/dist/boot/session-faces.js +4 -2
- package/dist/boot/stage-02-web-admin-pull.js +4 -1
- package/dist/boot/stage-06-runners.d.ts +6 -3
- package/dist/boot/stage-06-runners.js +20 -4
- package/dist/boot/stage-07-capability-layer.d.ts +4 -1
- package/dist/boot/stage-07-capability-layer.js +24 -3
- package/dist/boot/stage-08-reapers.d.ts +4 -1
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +5 -1
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +5 -1
- package/dist/boot/stage-10-http-server.js +6 -6
- package/dist/boot/stores.d.ts +8 -0
- package/dist/boot/stores.js +10 -14
- package/dist/boot/web-admin.d.ts +10 -0
- package/dist/boot/web-admin.js +162 -65
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/brain.js +8 -3
- package/dist/breaker-key.d.ts +6 -0
- package/dist/breaker-key.js +29 -0
- package/dist/budget.js +5 -1
- package/dist/caller-text-echo.d.ts +11 -6
- package/dist/caller-text-echo.js +10 -12
- package/dist/canonical-json.d.ts +2 -41
- package/dist/canonical-json.js +2 -13
- package/dist/capabilities/center-plugins.d.ts +7 -0
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/execution-lane.js +2 -2
- package/dist/capabilities/hands-lane.d.ts +21 -0
- package/dist/capabilities/hands-lane.js +10 -0
- package/dist/capabilities/mcp-probe.d.ts +36 -4
- package/dist/capabilities/mcp-probe.js +29 -6
- package/dist/capabilities/scenarios.d.ts +63 -7
- package/dist/capabilities/scenarios.js +39 -7
- package/dist/capability-facts.d.ts +36 -4
- package/dist/capability-facts.js +30 -4
- package/dist/center-config-tier.d.ts +69 -0
- package/dist/center-config-tier.js +42 -0
- package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
- package/dist/checkpoint-ctx-seal-m28.js +19 -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 +43 -28
- 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-provider.d.ts +6 -1
- package/dist/config-provider.js +6 -2
- package/dist/config-types.d.ts +71 -14
- package/dist/config-types.js +8 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +121 -59
- 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 -23
- package/dist/elicitation.js +3 -2
- package/dist/entry-problems.d.ts +34 -0
- package/dist/entry-problems.js +14 -0
- package/dist/execution-lane-caps.d.ts +49 -10
- package/dist/execution-lane-caps.js +43 -4
- package/dist/field-conflict.d.ts +44 -0
- package/dist/field-conflict.js +21 -0
- package/dist/file-history-disclosure.d.ts +34 -5
- package/dist/file-history-disclosure.js +2 -2
- package/dist/fleet/fleet-bus.d.ts +84 -6
- package/dist/fleet/fleet-bus.js +42 -9
- package/dist/hooks/classic-stations.d.ts +10 -5
- package/dist/hooks/classic-stations.js +4 -4
- package/dist/hooks/command-spawn.d.ts +57 -0
- package/dist/hooks/command-spawn.js +139 -0
- package/dist/hooks/hook-llm.js +2 -2
- package/dist/hooks/hook-runner.d.ts +127 -25
- package/dist/hooks/hook-runner.js +668 -287
- 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.js +111 -60
- package/dist/http/approval-sweeps.js +58 -19
- package/dist/http/caller-trust.d.ts +77 -0
- package/dist/http/caller-trust.js +143 -0
- package/dist/http/checkpoint-ctx.d.ts +173 -18
- package/dist/http/checkpoint-ctx.js +171 -12
- package/dist/http/claimed-run.d.ts +4 -3
- package/dist/http/device-placement.d.ts +2 -4
- package/dist/http/device-placement.js +2 -1
- package/dist/http/dispatch.js +13 -7
- package/dist/http/durable-run-leg.d.ts +4 -32
- package/dist/http/durable-run-leg.js +37 -12
- package/dist/http/idempotency.d.ts +2 -2
- package/dist/http/idempotency.js +2 -2
- package/dist/http/owner-access.d.ts +56 -0
- package/dist/http/owner-access.js +42 -0
- package/dist/http/principal-gate.d.ts +78 -27
- package/dist/http/principal-gate.js +64 -14
- package/dist/http/readiness.d.ts +11 -5
- package/dist/http/readiness.js +10 -3
- package/dist/http/refusal-body.d.ts +12 -0
- package/dist/http/refusal-body.js +11 -0
- package/dist/http/request-guards.d.ts +16 -32
- package/dist/http/request-guards.js +31 -30
- package/dist/http/resume-ctx-read.d.ts +50 -6
- package/dist/http/resume-ctx-read.js +33 -7
- package/dist/http/resume-legs.d.ts +5 -5
- package/dist/http/resume-legs.js +163 -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 -37
- 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/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +91 -56
- package/dist/http/routes/background.js +3 -2
- package/dist/http/routes/capabilities.js +20 -4
- 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.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +6 -2
- package/dist/http/routes/fleet.js +4 -4
- 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.d.ts +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 +9 -22
- package/dist/http/routes/runs.d.ts +5 -0
- package/dist/http/routes/runs.js +63 -115
- package/dist/http/routes/session-sync.d.ts +3 -2
- package/dist/http/routes/session-sync.js +53 -48
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions-list.js +3 -4
- package/dist/http/routes/sessions.js +33 -43
- package/dist/http/routes/side-query.js +6 -4
- package/dist/http/routes/tasks.js +71 -30
- package/dist/http/routes/trace-usage.js +7 -9
- package/dist/http/routes/workflows.d.ts +351 -0
- package/dist/http/routes/workflows.js +153 -119
- package/dist/http/run-declared-families.d.ts +16 -0
- package/dist/http/run-declared-families.js +12 -0
- package/dist/http/send.d.ts +24 -3
- package/dist/http/send.js +14 -2
- package/dist/http/server.d.ts +23 -18
- package/dist/http/server.js +20 -15
- package/dist/http/stored-body-replay.js +17 -4
- package/dist/http/submit-ctx.d.ts +4 -3
- package/dist/http/submit-ctx.js +23 -6
- package/dist/http/submit-replay.d.ts +4 -3
- package/dist/http/submit-replay.js +5 -4
- package/dist/http/wire-types.d.ts +17 -10
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/lane-home-probe.d.ts +78 -9
- package/dist/lane-home-probe.js +78 -12
- package/dist/leader/endpoint.d.ts +4 -1
- package/dist/leader/endpoint.js +11 -10
- package/dist/leader/wire.d.ts +4 -0
- package/dist/leader/wire.js +8 -7
- package/dist/limit-refusal.d.ts +17 -5
- package/dist/limit-refusal.js +4 -1
- package/dist/main.js +1 -1
- package/dist/mcp-assertion-header.d.ts +31 -10
- package/dist/mcp-assertion-header.js +10 -3
- package/dist/memory-origin-fence.js +2 -2
- package/dist/memory-project-layer.d.ts +37 -0
- package/dist/memory-project-layer.js +46 -0
- package/dist/memory-scope.d.ts +23 -16
- 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-compat.d.ts +1 -1
- 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 +5 -0
- package/dist/model-provider.js +4 -2
- package/dist/model-route-endpoint.d.ts +8 -11
- package/dist/model-route-endpoint.js +4 -7
- 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/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +18 -4
- package/dist/observability/fail-open.d.ts +35 -9
- package/dist/observability/fail-open.js +35 -9
- package/dist/observability/metrics.js +1 -0
- package/dist/observability/prompt-manifest.d.ts +22 -25
- package/dist/observability/prompt-manifest.js +54 -50
- package/dist/observability/tool-trace.js +9 -10
- 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 +119 -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 +43 -11
- package/dist/parked-decide.js +100 -51
- package/dist/parked-revive-surface.d.ts +9 -2
- package/dist/parked-revive-surface.js +11 -8
- package/dist/permission-rule-list.d.ts +1 -1
- package/dist/permission-rule-list.js +2 -2
- package/dist/permission-rule-vocab.d.ts +1 -23
- package/dist/permission-rule-vocab.js +0 -13
- 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/approval-payload-fingerprint.d.ts +14 -6
- package/dist/plugins/approval-payload-fingerprint.js +1 -1
- package/dist/plugins/blob-backend.d.ts +38 -25
- package/dist/plugins/blob-backend.js +29 -19
- 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 +4 -1
- package/dist/plugins/caching-session-store.js +7 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +36 -1
- package/dist/plugins/checkpoint-store-sql.js +65 -3
- 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/local-checkpoint-store.d.ts +15 -2
- package/dist/plugins/local-checkpoint-store.js +32 -1
- package/dist/plugins/local-session-store.d.ts +13 -10
- package/dist/plugins/local-session-store.js +23 -14
- package/dist/plugins/local-session-window.d.ts +45 -0
- package/dist/plugins/local-session-window.js +38 -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 +121 -5
- package/dist/plugins/one-time-migrations.js +332 -8
- 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-file.js +10 -6
- package/dist/plugins/permission-rule-store-sql.js +8 -8
- package/dist/plugins/pg-pool.js +39 -7
- package/dist/plugins/pg-session-storage.d.ts +18 -5
- package/dist/plugins/pg-session-storage.js +68 -23
- package/dist/plugins/remote-env-adb.d.ts +6 -12
- package/dist/plugins/remote-env-adb.js +12 -2
- package/dist/plugins/remote-env-device.d.ts +14 -7
- package/dist/plugins/remote-env-device.js +23 -15
- package/dist/plugins/remote-env-k8s.d.ts +8 -12
- package/dist/plugins/remote-env-k8s.js +23 -5
- package/dist/plugins/remote-env-local-docker.d.ts +10 -15
- package/dist/plugins/remote-env-local-docker.js +12 -9
- package/dist/plugins/remote-env-ssh.d.ts +10 -16
- package/dist/plugins/remote-env-ssh.js +17 -11
- package/dist/plugins/run-store-sql.d.ts +16 -14
- package/dist/plugins/run-store-sql.js +22 -2
- 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 +32 -6
- package/dist/plugins/session-epoch-anchor.js +2 -3
- package/dist/plugins/session-ownership.d.ts +11 -0
- package/dist/plugins/session-ownership.js +11 -0
- package/dist/plugins/session-placement.d.ts +12 -14
- package/dist/plugins/session-placement.js +2 -15
- package/dist/plugins/session-store.d.ts +18 -8
- package/dist/plugins/session-store.js +15 -4
- package/dist/plugins/sql-driver.d.ts +74 -11
- package/dist/plugins/sql-driver.js +210 -12
- package/dist/plugins/sql-errors.d.ts +23 -0
- package/dist/plugins/sql-errors.js +21 -0
- package/dist/plugins/store-backend.d.ts +36 -1
- package/dist/plugins/store-backend.js +40 -14
- 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 +3 -2
- package/dist/plugins/tidb-session-storage.js +2 -2
- package/dist/plugins/tidb-session-store.d.ts +25 -5
- package/dist/plugins/tidb-session-store.js +67 -21
- 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/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 +7 -0
- package/dist/request-key-closure.js +9 -1
- package/dist/request-tool-axes.d.ts +12 -0
- package/dist/request-tool-axes.js +20 -0
- package/dist/request-value-closure.d.ts +5 -0
- package/dist/request-value-closure.js +11 -3
- package/dist/rule-entry-list.d.ts +26 -2
- package/dist/rule-entry-list.js +72 -12
- 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 +37 -6
- package/dist/runs.d.ts +12 -10
- package/dist/runs.js +25 -6
- 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/sealed-key.d.ts +32 -0
- package/dist/sealed-key.js +55 -1
- package/dist/security.d.ts +95 -15
- package/dist/security.js +52 -9
- package/dist/session-error-wire.d.ts +31 -7
- package/dist/session-error-wire.js +9 -2
- package/dist/session-sync-kernel.d.ts +37 -10
- package/dist/session-sync-kernel.js +10 -0
- 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 +15 -4
- package/dist/spec-fields.js +21 -2
- package/dist/sql-engine-posture.d.ts +3 -1
- package/dist/task-a2a.js +4 -3
- package/dist/task-cwd.d.ts +1 -1
- package/dist/task-mcp.d.ts +5 -5
- package/dist/task-mcp.js +27 -74
- package/dist/task-settings.d.ts +93 -88
- package/dist/task-settings.js +71 -63
- package/dist/text-bidi.d.ts +0 -4
- package/dist/text-bidi.js +1 -3
- package/dist/tighten-only-seats.d.ts +150 -0
- package/dist/tighten-only-seats.js +196 -0
- package/dist/tool-approval.d.ts +451 -136
- package/dist/tool-approval.js +733 -256
- package/dist/tool-rule-names.d.ts +6 -0
- package/dist/trace/core-keyset-guard.d.ts +108 -25
- package/dist/trace/exit-seat.d.ts +40 -0
- package/dist/trace/exit-seat.js +130 -0
- package/dist/trace/ledger-events.d.ts +6 -3
- package/dist/trace/project.d.ts +98 -13
- package/dist/trace/project.js +57 -32
- package/dist/trace/projection-table.d.ts +100 -0
- package/dist/trace/projection-table.js +130 -0
- package/dist/trace/redact.d.ts +57 -0
- package/dist/trace/redact.js +962 -168
- package/dist/trace/self-minted-wire.d.ts +349 -1
- package/dist/trace/self-minted-wire.js +118 -1
- package/dist/trace/wire-projection-faces.d.ts +304 -2
- package/dist/trace/wire-projection-faces.js +160 -13
- 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/facade.d.ts +1 -1
- package/dist/web-admin-client/facade.js +1 -1
- package/dist/web-admin-client/read-face.js +5 -9
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +6 -8
- package/dist/web-admin-client/types.d.ts +1 -1
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
- package/package.json +8 -4
- package/scripts/drain-check-local.mjs +34 -2
- package/scripts/drain-check.mysql.sql +20 -2
- package/scripts/drain-check.pg.sql +20 -2
- package/dist/mcp-tool-faces.d.ts +0 -69
- package/dist/mcp-tool-faces.js +0 -106
package/dist/tool-approval.js
CHANGED
|
@@ -4,12 +4,15 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
4
4
|
import { uuidv7, classifyAsk, foldArbitrationRow, policyCannotAnswer, screenAskClassMembers, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
|
|
5
5
|
import { precheckCardRuleText } from "./rules-consent.js";
|
|
6
6
|
import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact.js";
|
|
7
|
+
import { seatText } from "./trace/exit-seat.js";
|
|
7
8
|
import { hasBidiControls } from "./text-bidi.js";
|
|
8
9
|
import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerprint.js";
|
|
9
10
|
import { createLogger } from "./observability/logger.js";
|
|
11
|
+
import { errorKindOf } from "./observability/err-text.js";
|
|
10
12
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
+
import { ownerOfCheckpointScope } from "./security.js";
|
|
14
|
+
import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt } from "./approval-ask-machine.js";
|
|
15
|
+
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
16
|
import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
14
17
|
import { APPROVAL_ENDING_CAUSES, SETTLE_OUTCOME, causeOfAskRow } from "./approval-deny-reasons.js";
|
|
15
18
|
import { settleByPolicy } from "./approval-policy-settlement.js";
|
|
@@ -18,6 +21,7 @@ import { governanceOriginOf } from "./runtime-governance.js";
|
|
|
18
21
|
import { callGrantBindingOf, questionGrantBindingOf } from "./standing-answer-grant.js";
|
|
19
22
|
import { operatorAsk } from "./operator-ask.js";
|
|
20
23
|
import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
|
|
24
|
+
import { refusalBody } from "./http/refusal-body.js";
|
|
21
25
|
const defaultLogger = createLogger();
|
|
22
26
|
export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
|
|
23
27
|
export function isApprovalGateKind(k) {
|
|
@@ -32,15 +36,17 @@ const DEFAULT_APPROVAL_TTL_MS = 5 * 60_000;
|
|
|
32
36
|
const DEFAULT_UNREACHED_ASK_TTL_MS = 60 * 60_000;
|
|
33
37
|
const SUSPENDED_POLL_MAX_PER_TICK = 64;
|
|
34
38
|
const SUSPENDED_POLL_BUDGET_MS = 5_000;
|
|
39
|
+
const ADOPT_OPEN_ASKS_MAX = 1_000;
|
|
35
40
|
const EDITED_DECISION_MARK_CAP_MS = 10_000;
|
|
36
41
|
const DEFAULT_WINDOW_MARGIN_MS = 10_000;
|
|
37
42
|
const SHUTDOWN_DRAIN_POLL_MS = 50;
|
|
38
43
|
const MAX_ALLOW_SESSIONS = 4096;
|
|
39
44
|
const DEFAULT_ADMIT_MAX_PER_TASK = 32;
|
|
40
45
|
const DEFAULT_ADMIT_MAX_PER_OWNER = 256;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
46
|
+
const DEFAULT_PARK_REDEMPTION_MAX = 2000;
|
|
47
|
+
const DEFAULT_PARK_REDEMPTION_TTL_MS = 60 * 60_000;
|
|
48
|
+
const DEFAULT_PARK_REDEMPTION_PER_PRINCIPAL_MAX = 128;
|
|
49
|
+
const PARK_REDEMPTION_ADMISSION_RETRY_AFTER_SEC = 5;
|
|
44
50
|
const PARKING_RETRY_AFTER_MS = 250;
|
|
45
51
|
export function approvalDecidePath(sessionId) {
|
|
46
52
|
return `/v1/approvals/${encodeURIComponent(sessionId)}/decide`;
|
|
@@ -63,6 +69,22 @@ function durableRuleMaterial(material) {
|
|
|
63
69
|
return { ruleCommand: material.command, ruleScopeRoot: material.scopeRoot ?? null };
|
|
64
70
|
}
|
|
65
71
|
export const NOT_PENDING_UNKNOWN_CAUSE = "unknown_or_other_replica";
|
|
72
|
+
export const TOOL_APPROVAL_READ_STATES = ["pending", "decided", "parked", "void"];
|
|
73
|
+
function decidedRowFacts(row) {
|
|
74
|
+
if (row.state !== "DECIDED" || (row.decision !== "approve" && row.decision !== "deny") || row.provisional || row.decidedAtMs === null)
|
|
75
|
+
return undefined;
|
|
76
|
+
return { decision: row.decision === "approve" ? "allow" : "deny", decidedAtMs: row.decidedAtMs, ...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}) };
|
|
77
|
+
}
|
|
78
|
+
function buildAskRowReadProjection(row, nowMs) {
|
|
79
|
+
const frame = buildReplayFrame(row, nowMs);
|
|
80
|
+
return {
|
|
81
|
+
approvalId: row.askId,
|
|
82
|
+
ts: row.createdAtMs,
|
|
83
|
+
expiresAtMs: row.expiresAtMs,
|
|
84
|
+
...(row.sessionId !== row.taskId ? { sessionId: row.sessionId } : {}),
|
|
85
|
+
...(frame !== undefined ? { toolName: frame.card.toolName, frame } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
66
88
|
export const ASK_DECISION_CONSUMER_ABSENT = "absent";
|
|
67
89
|
const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
|
|
68
90
|
const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
|
|
@@ -181,11 +203,45 @@ function mandatedFromSeat(seat) {
|
|
|
181
203
|
return false;
|
|
182
204
|
}
|
|
183
205
|
}
|
|
206
|
+
function askRowVisibleTo(row, principal) {
|
|
207
|
+
return row !== null && (row.owner === null || row.owner === principal);
|
|
208
|
+
}
|
|
209
|
+
function waitingLegsReached(entry, othersReached) {
|
|
210
|
+
return (entry.adopted === true ? 0 : 1) + othersReached;
|
|
211
|
+
}
|
|
212
|
+
function withConsumerDisclosure(result, waitingLegs) {
|
|
213
|
+
return waitingLegs > 0 ? result : { status: result.status, body: { ...result.body, consumerState: ASK_DECISION_CONSUMER_ABSENT } };
|
|
214
|
+
}
|
|
215
|
+
function livePendingRowOf(e) {
|
|
216
|
+
const listFrame = e.adopted === true ? e.cardFrame : (e.cardFrame ?? e.frame);
|
|
217
|
+
const approvalCard = e.adopted === true ? e.cardFrame.approvalCard : e.frame.approvalCard;
|
|
218
|
+
return {
|
|
219
|
+
approvalId: e.approvalId,
|
|
220
|
+
toolName: e.toolName,
|
|
221
|
+
ts: e.registeredAtMs,
|
|
222
|
+
expiresAtMs: e.expiresAtMs,
|
|
223
|
+
...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
|
|
224
|
+
...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
225
|
+
...(e.governanceForced ? { governanceForced: true } : {}),
|
|
226
|
+
...(e.mandated ? { mandated: true } : {}),
|
|
227
|
+
...(standingAnswerHome(pendingEntryStandingAnswerFacts(e)) === "none" ? { rememberRefused: true } : {}),
|
|
228
|
+
...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
|
|
229
|
+
...(e.origin !== undefined ? { origin: e.origin } : {}),
|
|
230
|
+
...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
|
|
231
|
+
...(approvalCard !== undefined ? { approvalCard } : {}),
|
|
232
|
+
frame: listFrame,
|
|
233
|
+
...(e.adopted === true ? { consumerState: ASK_DECISION_CONSUMER_ABSENT } : {}),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function canPresentWithoutDurableRow(c) {
|
|
237
|
+
return c.emitCard === undefined;
|
|
238
|
+
}
|
|
184
239
|
export const PERSIST_RULE_TEXT_ERROR = `persistRule.rule must be a non-empty string of at most ${MAX_RULE_TEXT_CHARS} characters`;
|
|
185
240
|
export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a boolean when present";
|
|
186
241
|
export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
|
|
187
242
|
export const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOfferIndex is mutually exclusive with persistRule.rule / persistRule.edited — send exactly one arm";
|
|
188
243
|
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)';
|
|
244
|
+
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
245
|
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
246
|
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
247
|
export function parseToolApprovalResponse(body) {
|
|
@@ -272,9 +328,14 @@ function parseToolApprovalFields(body, retired) {
|
|
|
272
328
|
export function buildUnattendedPolicyRefusal(reason) {
|
|
273
329
|
return { allow: false, settledBy: POLICY_REFUSAL_DECISION.settledBy, reason };
|
|
274
330
|
}
|
|
275
|
-
function unattendedRefusalReason(
|
|
331
|
+
function unattendedRefusalReason(site) {
|
|
332
|
+
if (site.arm === "frame_family_undeclared") {
|
|
333
|
+
return (`UNATTENDED_APPROVAL_POLICY=deny: this run has no live approval channel — the client that started it did not declare the "${site.family}" ` +
|
|
334
|
+
"approval frame family (settings.approvalFrameFamilies), so no approval card is shown to it, and an approval request no one can answer " +
|
|
335
|
+
`is refused here instead of being parked for a person. Upgrade the client, or declare the family it renders (arm: ${site.arm}).`);
|
|
336
|
+
}
|
|
276
337
|
return ("UNATTENDED_APPROVAL_POLICY=deny: this deployment declares that no one attends its approvals, so an approval " +
|
|
277
|
-
`request no one can answer is refused here instead of being parked for a person (arm: ${arm}).`);
|
|
338
|
+
`request no one can answer is refused here instead of being parked for a person (arm: ${site.arm}).`);
|
|
278
339
|
}
|
|
279
340
|
function isChildAsk(req) {
|
|
280
341
|
return req.fromSubagent === true;
|
|
@@ -306,45 +367,113 @@ export function resolveStreamApprovalGate(input) {
|
|
|
306
367
|
return { active: false, reason: "protocol_disabled" };
|
|
307
368
|
if (!input.backend)
|
|
308
369
|
return { active: false, reason: "no_backend" };
|
|
309
|
-
if (
|
|
370
|
+
if (input.checkpointStore === undefined)
|
|
310
371
|
return { active: false, reason: "no_park_facility" };
|
|
311
|
-
return { active: true, askStore: input.backend.approvalAsk() };
|
|
372
|
+
return { active: true, askStore: input.backend.approvalAsk(), checkpointStore: input.checkpointStore };
|
|
373
|
+
}
|
|
374
|
+
export const APPROVAL_FRAME_FAMILIES = ["tool_approval", "approval_request"];
|
|
375
|
+
export function presentedApprovalFrameFamily(streamApprovalOn) {
|
|
376
|
+
return streamApprovalOn ? "approval_request" : "tool_approval";
|
|
377
|
+
}
|
|
378
|
+
const APPROVAL_FRAME_FAMILIES_FIELD = "settings.approvalFrameFamilies";
|
|
379
|
+
const APPROVAL_FRAME_FAMILY_WORDS = APPROVAL_FRAME_FAMILIES.map((w) => `"${w}"`).join(" | ");
|
|
380
|
+
export function approvalFrameFamiliesIssue(settings) {
|
|
381
|
+
if (settings === null || typeof settings !== "object" || Array.isArray(settings))
|
|
382
|
+
return null;
|
|
383
|
+
const raw = Reflect.get(settings, "approvalFrameFamilies");
|
|
384
|
+
if (raw === undefined)
|
|
385
|
+
return null;
|
|
386
|
+
if (!Array.isArray(raw)) {
|
|
387
|
+
return {
|
|
388
|
+
message: `${APPROVAL_FRAME_FAMILIES_FIELD} must be an array of approval frame family words (${APPROVAL_FRAME_FAMILY_WORDS}) — the families of approval cards this client renders. ` +
|
|
389
|
+
'Refused rather than ignored, because an ignored declaration reads as "in effect" to the caller',
|
|
390
|
+
fields: { field: APPROVAL_FRAME_FAMILIES_FIELD, reason: "bad_entry" },
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const seen = new Set();
|
|
394
|
+
for (const [index, entry] of raw.entries()) {
|
|
395
|
+
if (!APPROVAL_FRAME_FAMILIES.includes(entry)) {
|
|
396
|
+
return {
|
|
397
|
+
message: `${APPROVAL_FRAME_FAMILIES_FIELD}[${index}] is not one of ${APPROVAL_FRAME_FAMILY_WORDS} — every entry must be an approval frame family word. Refused rather than dropped, because a dropped entry reads as "in effect" to the caller`,
|
|
398
|
+
fields: { field: APPROVAL_FRAME_FAMILIES_FIELD, reason: "bad_entry", index },
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (seen.has(entry)) {
|
|
402
|
+
return {
|
|
403
|
+
message: `${APPROVAL_FRAME_FAMILIES_FIELD}[${index}] repeats a family declared earlier in the list — declare each family once`,
|
|
404
|
+
fields: { field: APPROVAL_FRAME_FAMILIES_FIELD, reason: "bad_entry", index },
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
seen.add(entry);
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
export function declaredApprovalFrameFamilies(settings) {
|
|
412
|
+
if (approvalFrameFamiliesIssue(settings) !== null || settings === null || typeof settings !== "object")
|
|
413
|
+
return new Set();
|
|
414
|
+
const raw = Reflect.get(settings, "approvalFrameFamilies");
|
|
415
|
+
return new Set(Array.isArray(raw) ? raw.filter((w) => APPROVAL_FRAME_FAMILIES.includes(w)) : []);
|
|
312
416
|
}
|
|
313
417
|
export function createApprovalCardEmitter(sinks) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
accepted = true;
|
|
320
|
-
}
|
|
321
|
-
catch {
|
|
322
|
-
}
|
|
418
|
+
const present = [sinks.appendDurable, sinks.writeLive].filter((w) => w !== undefined);
|
|
419
|
+
const attempt = (write, frame) => (async () => {
|
|
420
|
+
try {
|
|
421
|
+
await write(frame);
|
|
422
|
+
return true;
|
|
323
423
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
424
|
+
catch (err) {
|
|
425
|
+
defaultLogger.warn("approval_card_sink_write_failed", { frame: frame.type, approvalId: frame.approvalId, errKind: errorKindOf(err) });
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
})();
|
|
429
|
+
const opened = new Map();
|
|
430
|
+
return async (frame, registration) => {
|
|
431
|
+
if (frame.type === "approval_request") {
|
|
432
|
+
const results = present.map((write) => attempt(write, frame));
|
|
433
|
+
opened.set(registration, results);
|
|
434
|
+
const accepted = await new Promise((resolve) => {
|
|
435
|
+
let pending = results.length;
|
|
436
|
+
if (pending === 0)
|
|
437
|
+
resolve(false);
|
|
438
|
+
for (const r of results) {
|
|
439
|
+
void r.then((ok) => {
|
|
440
|
+
if (ok)
|
|
441
|
+
resolve(true);
|
|
442
|
+
else if (--pending === 0)
|
|
443
|
+
resolve(false);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
if (!accepted) {
|
|
448
|
+
opened.delete(registration);
|
|
449
|
+
throw new Error("approval card undeliverable — no sink accepted the frame (live stream closed and/or the durable append failed)");
|
|
330
450
|
}
|
|
451
|
+
return;
|
|
331
452
|
}
|
|
332
|
-
|
|
333
|
-
|
|
453
|
+
const results = opened.get(registration);
|
|
454
|
+
opened.delete(registration);
|
|
455
|
+
if (results === undefined)
|
|
456
|
+
return;
|
|
457
|
+
await Promise.all(present.map((write, i) => results[i].then((ok) => (ok ? attempt(write, frame) : false))));
|
|
334
458
|
};
|
|
335
459
|
}
|
|
336
460
|
export function resolveApprovalLeg(input) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const tightDeadline = input.legWalltimeMs !== undefined && input.legWalltimeMs - input.windowMarginMs <= 0;
|
|
461
|
+
const active = input.streamApprovalOn;
|
|
462
|
+
const tightDeadline = active && input.legWalltimeMs !== undefined && input.legWalltimeMs - input.windowMarginMs <= 0;
|
|
340
463
|
if (input.windowMs === 0 || tightDeadline)
|
|
341
|
-
return { active:
|
|
464
|
+
return { active, unattended: { arm: "window_zero" } };
|
|
465
|
+
const family = presentedApprovalFrameFamily(active);
|
|
466
|
+
if (!input.declaredFamilies.has(family))
|
|
467
|
+
return { active, unattended: { arm: "frame_family_undeclared", family } };
|
|
342
468
|
return {
|
|
343
|
-
active
|
|
344
|
-
|
|
345
|
-
...(input.legWalltimeMs !== undefined ? { legDeadlineMonotonic: input.nowMonotonicMs + input.legWalltimeMs } : {}),
|
|
469
|
+
active,
|
|
470
|
+
...(active && input.legWalltimeMs !== undefined ? { legDeadlineMonotonic: input.nowMonotonicMs + input.legWalltimeMs } : {}),
|
|
346
471
|
};
|
|
347
472
|
}
|
|
473
|
+
export function approvalPreambleReplays(input) {
|
|
474
|
+
const leg = resolveApprovalLeg({ ...input, nowMonotonicMs: performance.now() });
|
|
475
|
+
return leg.active && leg.unattended === undefined;
|
|
476
|
+
}
|
|
348
477
|
function effectiveAskWindowMs(ttlMs, windowMarginMs, legDeadlineMonotonic, nowMonotonicMs) {
|
|
349
478
|
if (legDeadlineMonotonic === undefined)
|
|
350
479
|
return ttlMs;
|
|
@@ -412,6 +541,7 @@ export class ToolApprovalCoordinator {
|
|
|
412
541
|
return this.terminalClaimsInFlightCount(askId);
|
|
413
542
|
}
|
|
414
543
|
pendingByAskId = new Map();
|
|
544
|
+
presentedCardWatchers = new Map();
|
|
415
545
|
allowAllSessions = new Set();
|
|
416
546
|
streams = new Map();
|
|
417
547
|
ttlMs;
|
|
@@ -423,6 +553,7 @@ export class ToolApprovalCoordinator {
|
|
|
423
553
|
admitByTask = new Map();
|
|
424
554
|
admitByOwner = new Map();
|
|
425
555
|
suspendedPollOffset = 0;
|
|
556
|
+
adoptedPollOffset = 0;
|
|
426
557
|
editedDecisionsInFlight = new Map();
|
|
427
558
|
storeErrorWarned = false;
|
|
428
559
|
storeErrorTally = 0;
|
|
@@ -430,11 +561,12 @@ export class ToolApprovalCoordinator {
|
|
|
430
561
|
ruleConsent;
|
|
431
562
|
unattendedPolicy;
|
|
432
563
|
ruleScopeRootFor;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
564
|
+
parkRedemptions = new Map();
|
|
565
|
+
parkRedemptionMax;
|
|
566
|
+
parkRedemptionTtlMs;
|
|
567
|
+
parkRedemptionPerPrincipalMax;
|
|
437
568
|
parkedRedeem;
|
|
569
|
+
parkedGate;
|
|
438
570
|
askAudit;
|
|
439
571
|
sessionGrants;
|
|
440
572
|
deploymentListAsks;
|
|
@@ -454,9 +586,10 @@ export class ToolApprovalCoordinator {
|
|
|
454
586
|
this.admitMaxPerOwner = opts?.admitMaxPerOwner ?? DEFAULT_ADMIT_MAX_PER_OWNER;
|
|
455
587
|
this.unattendedPolicy = opts?.unattendedPolicy ?? "park";
|
|
456
588
|
this.parkedRedeem = opts?.parkedRedeem;
|
|
457
|
-
this.
|
|
458
|
-
this.
|
|
459
|
-
this.
|
|
589
|
+
this.parkedGate = opts?.parkedGate;
|
|
590
|
+
this.parkRedemptionMax = opts?.parkRedemptionMax ?? DEFAULT_PARK_REDEMPTION_MAX;
|
|
591
|
+
this.parkRedemptionTtlMs = opts?.parkRedemptionTtlMs ?? DEFAULT_PARK_REDEMPTION_TTL_MS;
|
|
592
|
+
this.parkRedemptionPerPrincipalMax = opts?.parkRedemptionPerPrincipalMax ?? DEFAULT_PARK_REDEMPTION_PER_PRINCIPAL_MAX;
|
|
460
593
|
this.askAudit = opts?.askAudit;
|
|
461
594
|
this.sessionGrants = opts?.sessionGrants;
|
|
462
595
|
if (opts?.deploymentListAsks !== undefined && opts.sessionGrants === undefined) {
|
|
@@ -475,7 +608,7 @@ export class ToolApprovalCoordinator {
|
|
|
475
608
|
outcomeWord = "unavailable";
|
|
476
609
|
break;
|
|
477
610
|
case "deny":
|
|
478
|
-
outcome = buildUnattendedPolicyRefusal(unattendedRefusalReason(site
|
|
611
|
+
outcome = buildUnattendedPolicyRefusal(unattendedRefusalReason(site));
|
|
479
612
|
outcomeWord = "deny";
|
|
480
613
|
break;
|
|
481
614
|
default: {
|
|
@@ -501,12 +634,12 @@ export class ToolApprovalCoordinator {
|
|
|
501
634
|
return this.unattendedRoutesToPark ? deriveBatchId(sourceTaskId, runId, legKey) : deriveAskScopedBatchId(askId);
|
|
502
635
|
}
|
|
503
636
|
unattendedAskOutcome(site) {
|
|
504
|
-
|
|
505
|
-
arm: "window_zero",
|
|
637
|
+
const facts = {
|
|
506
638
|
...(site.taskId !== undefined ? { taskId: site.taskId } : {}),
|
|
507
639
|
...(site.owner !== undefined && site.owner !== null ? { owner: site.owner } : {}),
|
|
508
640
|
...(site.toolName !== undefined ? { toolName: site.toolName } : {}),
|
|
509
|
-
}
|
|
641
|
+
};
|
|
642
|
+
return this.unattendedOutcome(site.arm === "frame_family_undeclared" ? { arm: site.arm, family: site.family, ...facts } : { arm: site.arm, ...facts });
|
|
510
643
|
}
|
|
511
644
|
async landUnattendedOutcome(outcome, row) {
|
|
512
645
|
const store = this.askStore;
|
|
@@ -855,12 +988,12 @@ export class ToolApprovalCoordinator {
|
|
|
855
988
|
return false;
|
|
856
989
|
}
|
|
857
990
|
}
|
|
858
|
-
boundUnattendedAsk = (identity) => {
|
|
991
|
+
boundUnattendedAsk = (identity, why) => {
|
|
859
992
|
return async (req) => {
|
|
860
993
|
const origin = { owner: identity.owner, taskId: identity.taskId, ...(identity.sessionId !== undefined ? { sessionId: identity.sessionId } : {}), legKey: "" };
|
|
861
994
|
if (await this.standingGrantSettles(origin, req, this.governanceForcedOf(origin, req)))
|
|
862
995
|
return true;
|
|
863
|
-
return this.unattendedAskOutcome({ taskId: identity.taskId, owner: identity.owner, toolName: req.toolName });
|
|
996
|
+
return this.unattendedAskOutcome({ ...why, taskId: identity.taskId, owner: identity.owner, toolName: req.toolName });
|
|
864
997
|
};
|
|
865
998
|
};
|
|
866
999
|
async sessionGrantUncontestedByRow(origin, req) {
|
|
@@ -951,75 +1084,84 @@ export class ToolApprovalCoordinator {
|
|
|
951
1084
|
}
|
|
952
1085
|
}
|
|
953
1086
|
}
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
|
|
961
|
-
|
|
1087
|
+
parkRedemptionAdmission(owner) {
|
|
1088
|
+
const effectivePerPrincipalMax = Math.min(this.parkRedemptionPerPrincipalMax, this.parkRedemptionMax);
|
|
1089
|
+
let inflight = 0;
|
|
1090
|
+
let ownInflight = 0;
|
|
1091
|
+
for (const r of this.parkRedemptions.values()) {
|
|
1092
|
+
if (r.inflight === undefined)
|
|
1093
|
+
continue;
|
|
1094
|
+
inflight++;
|
|
1095
|
+
if (r.owner === owner)
|
|
1096
|
+
ownInflight++;
|
|
962
1097
|
}
|
|
963
|
-
|
|
1098
|
+
if (ownInflight < effectivePerPrincipalMax && inflight < this.parkRedemptionMax)
|
|
1099
|
+
return undefined;
|
|
1100
|
+
defaultLogger.warn("parked approval redemption refused at admission: too many redemptions in flight", { owner: owner ?? "<none>", ownInflight, inflight });
|
|
1101
|
+
return {
|
|
1102
|
+
status: 429,
|
|
1103
|
+
body: refusalBody({
|
|
1104
|
+
errorCode: "limit.rate_exceeded",
|
|
1105
|
+
message: "too many late approval answers are being applied right now — retry after the Retry-After interval",
|
|
1106
|
+
facts: { retryAfterSec: PARK_REDEMPTION_ADMISSION_RETRY_AFTER_SEC },
|
|
1107
|
+
}),
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
openParkRedemption(askId, owner) {
|
|
1111
|
+
const opened = { owner, stampedAtMs: Date.now() };
|
|
1112
|
+
this.parkRedemptions.delete(askId);
|
|
1113
|
+
this.parkRedemptions.set(askId, opened);
|
|
1114
|
+
return opened;
|
|
1115
|
+
}
|
|
1116
|
+
retainParkRedemption(askId, memo) {
|
|
964
1117
|
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)
|
|
1118
|
+
const effectivePerPrincipalMax = Math.min(this.parkRedemptionPerPrincipalMax, this.parkRedemptionMax);
|
|
1119
|
+
let receipts = 0;
|
|
1120
|
+
let ownReceipts = 0;
|
|
1121
|
+
let ownExpired;
|
|
1122
|
+
let ownOldest;
|
|
1123
|
+
let globalExpired;
|
|
1124
|
+
for (const [k, r] of this.parkRedemptions) {
|
|
1125
|
+
if (k === askId || r.redeemed === undefined || r.inflight !== undefined)
|
|
979
1126
|
continue;
|
|
980
|
-
|
|
981
|
-
|
|
1127
|
+
receipts++;
|
|
1128
|
+
const expired = now - r.stampedAtMs > this.parkRedemptionTtlMs;
|
|
1129
|
+
if (globalExpired === undefined && expired)
|
|
1130
|
+
globalExpired = k;
|
|
1131
|
+
if (r.owner !== memo.owner)
|
|
982
1132
|
continue;
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
if (
|
|
996
|
-
recordFailOpen("server.approval.park-
|
|
997
|
-
|
|
1133
|
+
ownReceipts++;
|
|
1134
|
+
if (ownOldest === undefined)
|
|
1135
|
+
ownOldest = k;
|
|
1136
|
+
if (ownExpired === undefined && expired)
|
|
1137
|
+
ownExpired = k;
|
|
1138
|
+
}
|
|
1139
|
+
if (ownReceipts >= effectivePerPrincipalMax) {
|
|
1140
|
+
const victim = ownExpired ?? ownOldest;
|
|
1141
|
+
if (victim !== undefined)
|
|
1142
|
+
this.parkRedemptions.delete(victim);
|
|
1143
|
+
}
|
|
1144
|
+
else if (receipts >= this.parkRedemptionMax) {
|
|
1145
|
+
if (globalExpired === undefined) {
|
|
1146
|
+
recordFailOpen("server.approval.park-redemption-cap-refused", `owner=${memo.owner ?? "<none>"}`);
|
|
1147
|
+
if (this.parkRedemptions.get(askId) === memo)
|
|
1148
|
+
this.parkRedemptions.delete(askId);
|
|
1149
|
+
return false;
|
|
998
1150
|
}
|
|
999
|
-
this.
|
|
1000
|
-
}
|
|
1001
|
-
else if (this.parkTombstones.size >= this.parkTombstoneMax && globalExpiredVictim !== undefined) {
|
|
1002
|
-
this.parkTombstones.delete(globalExpiredVictim);
|
|
1003
|
-
}
|
|
1004
|
-
else if (this.parkTombstones.size >= this.parkTombstoneMax && !insertingBridge && globalBridgeVictim !== undefined) {
|
|
1005
|
-
this.parkTombstones.delete(globalBridgeVictim);
|
|
1151
|
+
this.parkRedemptions.delete(globalExpired);
|
|
1006
1152
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
this.parkTombstones.set(approvalId, tomb);
|
|
1153
|
+
memo.stampedAtMs = now;
|
|
1154
|
+
return true;
|
|
1012
1155
|
}
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
if (
|
|
1156
|
+
lookupParkRedemption(askId) {
|
|
1157
|
+
const r = this.parkRedemptions.get(askId);
|
|
1158
|
+
if (r === undefined)
|
|
1016
1159
|
return undefined;
|
|
1017
|
-
if (Date.now() -
|
|
1018
|
-
|
|
1019
|
-
this.parkTombstones.delete(approvalId);
|
|
1160
|
+
if (r.inflight === undefined && Date.now() - r.stampedAtMs > this.parkRedemptionTtlMs) {
|
|
1161
|
+
this.parkRedemptions.delete(askId);
|
|
1020
1162
|
return undefined;
|
|
1021
1163
|
}
|
|
1022
|
-
return
|
|
1164
|
+
return r;
|
|
1023
1165
|
}
|
|
1024
1166
|
notifyExternalDecision(askId, outcome) {
|
|
1025
1167
|
const entries = this.pendingByAskId.get(askId);
|
|
@@ -1029,7 +1171,8 @@ export class ToolApprovalCoordinator {
|
|
|
1029
1171
|
let settled = 0;
|
|
1030
1172
|
for (const entry of [...entries]) {
|
|
1031
1173
|
entry.settle(verdictOfDecided(args));
|
|
1032
|
-
|
|
1174
|
+
if (entry.adopted !== true)
|
|
1175
|
+
settled += 1;
|
|
1033
1176
|
}
|
|
1034
1177
|
return { settled };
|
|
1035
1178
|
}
|
|
@@ -1211,7 +1354,6 @@ export class ToolApprovalCoordinator {
|
|
|
1211
1354
|
let askId;
|
|
1212
1355
|
let batchId;
|
|
1213
1356
|
const card = this.askStore ? buildApprovalCard(frame, req, req.requiresRealApproval === true) : undefined;
|
|
1214
|
-
let wireApprovalId = id;
|
|
1215
1357
|
let cardForFrame = card;
|
|
1216
1358
|
let persistedExpiresAtMs = expiresAtMs;
|
|
1217
1359
|
let lateEnsuredRow;
|
|
@@ -1257,7 +1399,7 @@ export class ToolApprovalCoordinator {
|
|
|
1257
1399
|
toolCallId: req.toolCallId,
|
|
1258
1400
|
legKey,
|
|
1259
1401
|
parentToolCallId: req.delegation?.parentToolCallId ?? null,
|
|
1260
|
-
cardJson: buildApprovalCardEnvelope(
|
|
1402
|
+
cardJson: buildApprovalCardEnvelope(askId, card),
|
|
1261
1403
|
boundInputHash: readBoundInputHash(req),
|
|
1262
1404
|
...durableRuleMaterial(ruleLaneMaterial),
|
|
1263
1405
|
schemaVersion: 1,
|
|
@@ -1289,8 +1431,6 @@ export class ToolApprovalCoordinator {
|
|
|
1289
1431
|
return existingOrCreated.inserted ? this.landUnattendedOutcome(unattended, existingOrCreated.row) : unattended;
|
|
1290
1432
|
}
|
|
1291
1433
|
persistedExpiresAtMs = existingOrCreated.row.expiresAtMs;
|
|
1292
|
-
wireApprovalId = persistedEnvelope.approvalId;
|
|
1293
|
-
frame.approvalId = wireApprovalId;
|
|
1294
1434
|
cardForFrame = persistedEnvelope.card;
|
|
1295
1435
|
if (cardForFrame.governanceForced === true)
|
|
1296
1436
|
frame.governanceForced = true;
|
|
@@ -1359,8 +1499,8 @@ export class ToolApprovalCoordinator {
|
|
|
1359
1499
|
if (!confirmedRowExists) {
|
|
1360
1500
|
askId = undefined;
|
|
1361
1501
|
batchId = undefined;
|
|
1362
|
-
if (
|
|
1363
|
-
const unattended = this.unattendedOutcome({ arm: "
|
|
1502
|
+
if (!aliveCtxs.some(canPresentWithoutDurableRow)) {
|
|
1503
|
+
const unattended = this.unattendedOutcome({ arm: "no_row_no_presenter", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1364
1504
|
const landed = abandonRow(unattended);
|
|
1365
1505
|
releaseAdmission();
|
|
1366
1506
|
return landed ?? unattended;
|
|
@@ -1369,6 +1509,8 @@ export class ToolApprovalCoordinator {
|
|
|
1369
1509
|
}
|
|
1370
1510
|
}
|
|
1371
1511
|
}
|
|
1512
|
+
const wireApprovalId = askId ?? id;
|
|
1513
|
+
frame.approvalId = wireApprovalId;
|
|
1372
1514
|
let done = false;
|
|
1373
1515
|
let emitFailed = false;
|
|
1374
1516
|
let emitSettled = false;
|
|
@@ -1427,19 +1569,24 @@ export class ToolApprovalCoordinator {
|
|
|
1427
1569
|
sig.removeEventListener("abort", fn);
|
|
1428
1570
|
this.pending.delete(id);
|
|
1429
1571
|
if (windowRouteUnavailable && askId !== undefined && batchId !== undefined && this.unattendedRoutesToPark) {
|
|
1430
|
-
this.recordParkTombstone(wireApprovalId, { kind: "park", askId, batchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1431
1572
|
parkRouted = true;
|
|
1432
1573
|
}
|
|
1574
|
+
let lastLiveRegistration = false;
|
|
1433
1575
|
if (askId !== undefined) {
|
|
1434
1576
|
const siblings = this.pendingByAskId.get(askId);
|
|
1435
1577
|
if (siblings && selfEntry) {
|
|
1436
1578
|
siblings.delete(selfEntry);
|
|
1437
|
-
if (siblings.size === 0)
|
|
1579
|
+
if (siblings.size === 0) {
|
|
1438
1580
|
this.pendingByAskId.delete(askId);
|
|
1581
|
+
lastLiveRegistration = true;
|
|
1582
|
+
}
|
|
1439
1583
|
}
|
|
1440
1584
|
}
|
|
1441
1585
|
lastOutcome = outcome;
|
|
1442
1586
|
lastCause = verdict.by === "system" ? verdict.cause : undefined;
|
|
1587
|
+
if (lastLiveRegistration && askId !== undefined) {
|
|
1588
|
+
this.notifyPresentedCardWatchers(askId, buildApprovalCompleteFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, outcome, ...(lastCause !== undefined ? { cause: lastCause } : {}), parked: parkRouted, nowMs: Date.now() }));
|
|
1589
|
+
}
|
|
1443
1590
|
releaseAdmission();
|
|
1444
1591
|
if (selfEntry)
|
|
1445
1592
|
selfEntry.settledOutcome = outcome;
|
|
@@ -1511,7 +1658,7 @@ export class ToolApprovalCoordinator {
|
|
|
1511
1658
|
settleFromClaimLoss(read, armCause);
|
|
1512
1659
|
};
|
|
1513
1660
|
const boundedClaim = (mk, label, timeoutMs, late) => {
|
|
1514
|
-
const release = this.admitTerminalClaim(
|
|
1661
|
+
const release = this.admitTerminalClaim(wireApprovalId);
|
|
1515
1662
|
if (release === undefined)
|
|
1516
1663
|
return undefined;
|
|
1517
1664
|
const raw = mk();
|
|
@@ -1583,7 +1730,6 @@ export class ToolApprovalCoordinator {
|
|
|
1583
1730
|
arm.onWon(outcome);
|
|
1584
1731
|
return;
|
|
1585
1732
|
}
|
|
1586
|
-
arm.onLostTruth?.(outcome.current);
|
|
1587
1733
|
const read = this.classifyClaimLoss(pinnedAskId, outcome.current, `claim:${arm.intent}`);
|
|
1588
1734
|
if (done) {
|
|
1589
1735
|
broadcastClaimTruth(read, arm.cause);
|
|
@@ -1673,32 +1819,23 @@ export class ToolApprovalCoordinator {
|
|
|
1673
1819
|
const store = this.askStore;
|
|
1674
1820
|
const pinnedAskId = askId;
|
|
1675
1821
|
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
1822
|
await resolveLocalOutcome({
|
|
1682
1823
|
intent: "cancel",
|
|
1683
1824
|
cause,
|
|
1684
1825
|
storeLabel: "claimTerminal(cancel)",
|
|
1685
1826
|
claim: (timeoutMs) => boundedClaim(() => store.claimTerminal(pinnedAskId, pinnedBatchId, "cancel", { cause }), "claimTerminal(cancel)", timeoutMs, (late) => {
|
|
1686
1827
|
if (!late.claimed) {
|
|
1687
|
-
cancelLostTruth(late.current);
|
|
1688
1828
|
lateClaimLossSettle(late.current, "cancel", cause);
|
|
1689
1829
|
return;
|
|
1690
1830
|
}
|
|
1691
1831
|
this.settleVoidedSiblings([pinnedAskId], cause);
|
|
1692
|
-
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1693
1832
|
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1694
1833
|
}),
|
|
1695
1834
|
onWon: () => {
|
|
1696
1835
|
settle({ by: "system", cause });
|
|
1697
1836
|
this.settleVoidedSiblings([pinnedAskId], cause);
|
|
1698
|
-
this.recordParkTombstone(wireApprovalId, { kind: "voidBridge", askId: pinnedAskId, batchId: pinnedBatchId, owner: origin.owner, parkedAtMs: Date.now() });
|
|
1699
1837
|
this.emitRevokeTo(aliveCtxs, buildRevokeFrame(pinnedBatchId, [pinnedAskId], "aborted", Date.now(), origin.taskId));
|
|
1700
1838
|
},
|
|
1701
|
-
onLostTruth: cancelLostTruth,
|
|
1702
1839
|
settleByIntent: () => settle({ by: "system", cause }),
|
|
1703
1840
|
});
|
|
1704
1841
|
};
|
|
@@ -1750,6 +1887,9 @@ export class ToolApprovalCoordinator {
|
|
|
1750
1887
|
const { rememberRefused: _stale, ...cardRest } = cardForFrame;
|
|
1751
1888
|
cardForFrame = rememberRefused ? { ...cardRest, rememberRefused: true } : cardRest;
|
|
1752
1889
|
}
|
|
1890
|
+
const cardFrame = askId !== undefined && cardForFrame !== undefined
|
|
1891
|
+
? buildApprovalRequestFrame({ askId, taskId: origin.taskId, approvalId: wireApprovalId, card: cardForFrame, expiresAtMs: persistedExpiresAtMs, nowMs: Date.now(), live: frame })
|
|
1892
|
+
: undefined;
|
|
1753
1893
|
const pendingEntry = {
|
|
1754
1894
|
settle,
|
|
1755
1895
|
forceParkNow,
|
|
@@ -1760,6 +1900,9 @@ export class ToolApprovalCoordinator {
|
|
|
1760
1900
|
...(child ? { originTaskId: req.sourceTaskId } : primary ? { targetCtxs: new Set(aliveCtxs), onTargetGone } : {}),
|
|
1761
1901
|
...(origin.sessionId ? { sessionId: origin.sessionId } : {}),
|
|
1762
1902
|
frame,
|
|
1903
|
+
...(cardFrame !== undefined ? { cardFrame } : {}),
|
|
1904
|
+
approvalId: wireApprovalId,
|
|
1905
|
+
registrationId: id,
|
|
1763
1906
|
toolName: req.toolName,
|
|
1764
1907
|
grantBinding: entryGrantBinding,
|
|
1765
1908
|
...(entryClasses !== undefined ? { classes: entryClasses } : {}),
|
|
@@ -1777,6 +1920,7 @@ export class ToolApprovalCoordinator {
|
|
|
1777
1920
|
selfEntry = pendingEntry;
|
|
1778
1921
|
this.pending.set(id, pendingEntry);
|
|
1779
1922
|
if (askId !== undefined) {
|
|
1923
|
+
this.releaseAdoptedRegistrations(askId);
|
|
1780
1924
|
let siblings = this.pendingByAskId.get(askId);
|
|
1781
1925
|
if (!siblings) {
|
|
1782
1926
|
siblings = new Set();
|
|
@@ -1798,9 +1942,6 @@ export class ToolApprovalCoordinator {
|
|
|
1798
1942
|
});
|
|
1799
1943
|
if (this.shutdownDraining)
|
|
1800
1944
|
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
1945
|
{
|
|
1805
1946
|
const serverNowMs = Date.now();
|
|
1806
1947
|
frame.expiresAtMs = persistedExpiresAtMs;
|
|
@@ -1808,28 +1949,37 @@ export class ToolApprovalCoordinator {
|
|
|
1808
1949
|
frame.expiresInMs = Math.max(0, persistedExpiresAtMs - serverNowMs);
|
|
1809
1950
|
}
|
|
1810
1951
|
const emitOne = async (c) => {
|
|
1811
|
-
let
|
|
1812
|
-
|
|
1813
|
-
|
|
1952
|
+
let family;
|
|
1953
|
+
let send;
|
|
1954
|
+
if (c.emitCard !== undefined) {
|
|
1955
|
+
if (cardFrame === undefined) {
|
|
1956
|
+
defaultLogger.warn("approval card leg cannot present this ask: it has no confirmed durable identity (no approval_request frame is minted without one)", {
|
|
1957
|
+
taskId: origin.taskId,
|
|
1958
|
+
approvalId: wireApprovalId,
|
|
1959
|
+
toolName: req.toolName,
|
|
1960
|
+
});
|
|
1961
|
+
return { delivered: false };
|
|
1962
|
+
}
|
|
1963
|
+
family = "approval_request";
|
|
1964
|
+
send = () => c.emitCard(cardFrame, id);
|
|
1814
1965
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1966
|
+
else {
|
|
1967
|
+
family = "tool_approval";
|
|
1968
|
+
send = () => c.emit(frame);
|
|
1817
1969
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
}
|
|
1970
|
+
try {
|
|
1971
|
+
await send();
|
|
1972
|
+
return { delivered: true };
|
|
1973
|
+
}
|
|
1974
|
+
catch (err) {
|
|
1975
|
+
defaultLogger.warn("approval_card_delivery_failed", { taskId: origin.taskId, approvalId: wireApprovalId, family, errKind: errorKindOf(err) });
|
|
1976
|
+
return { delivered: false };
|
|
1826
1977
|
}
|
|
1827
|
-
return { legacy: delivered, card: cardDelivered };
|
|
1828
1978
|
};
|
|
1829
1979
|
const openByCtx = new Map(aliveCtxs.map((c) => [c, emitOne(c)]));
|
|
1830
1980
|
const emitAllP = (async () => {
|
|
1831
1981
|
const results = await Promise.all(openByCtx.values());
|
|
1832
|
-
const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.
|
|
1982
|
+
const anySucceeded = reachability === "unreached" || this.hasOtherSuspendedOwner(askId, selfEntry) || results.some((r) => r.delivered);
|
|
1833
1983
|
emitSettled = true;
|
|
1834
1984
|
if (!anySucceeded && !done) {
|
|
1835
1985
|
if (this.askStore && askId !== undefined && batchId !== undefined) {
|
|
@@ -1873,15 +2023,30 @@ export class ToolApprovalCoordinator {
|
|
|
1873
2023
|
for (const c of aliveCtxs) {
|
|
1874
2024
|
void openByCtx
|
|
1875
2025
|
.get(c)
|
|
1876
|
-
.then((delivered) =>
|
|
1877
|
-
|
|
2026
|
+
.then(({ delivered }) => {
|
|
2027
|
+
if (!delivered)
|
|
2028
|
+
return undefined;
|
|
2029
|
+
if (c.emitCard !== undefined) {
|
|
2030
|
+
if (cardFrame === undefined)
|
|
2031
|
+
return undefined;
|
|
2032
|
+
return c.emitCard(buildApprovalCompleteFrame({
|
|
2033
|
+
askId: cardFrame.askId,
|
|
2034
|
+
taskId: cardFrame.taskId,
|
|
2035
|
+
approvalId: wireApprovalId,
|
|
2036
|
+
outcome: lastOutcome,
|
|
2037
|
+
...(lastCause !== undefined ? { cause: lastCause } : {}),
|
|
2038
|
+
parked: parkRouted,
|
|
2039
|
+
nowMs: Date.now(),
|
|
2040
|
+
}), id);
|
|
2041
|
+
}
|
|
2042
|
+
return c.emit({
|
|
1878
2043
|
type: "tool_approval_complete",
|
|
1879
2044
|
approvalId: wireApprovalId,
|
|
1880
2045
|
outcome: lastOutcome,
|
|
1881
2046
|
...(lastCause !== undefined ? { cause: lastCause } : {}),
|
|
1882
2047
|
...(parkRouted ? { parked: true } : {}),
|
|
1883
|
-
})
|
|
1884
|
-
|
|
2048
|
+
});
|
|
2049
|
+
})
|
|
1885
2050
|
.catch(() => undefined);
|
|
1886
2051
|
}
|
|
1887
2052
|
if (emitFailed)
|
|
@@ -1892,24 +2057,47 @@ export class ToolApprovalCoordinator {
|
|
|
1892
2057
|
return this.unattendedOutcome({ arm: "emit_unsettled", taskId: origin.taskId, ...(origin.owner !== null ? { owner: origin.owner } : {}), toolName: req.toolName, reachability });
|
|
1893
2058
|
return shapeOutcome(settled);
|
|
1894
2059
|
}
|
|
1895
|
-
respond(id, principal, body, httpReq) {
|
|
2060
|
+
respond(id, principal, body, httpReq, deployment) {
|
|
1896
2061
|
const parsed = parseToolApprovalResponse(body);
|
|
1897
2062
|
if (!parsed.ok)
|
|
1898
|
-
return { status: 400, body: {
|
|
1899
|
-
|
|
2063
|
+
return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: parsed.error, facts: { ...(parsed.field !== undefined ? { field: parsed.field } : {}) } }) };
|
|
2064
|
+
if (parsed.readRootGrant !== undefined && deployment?.readRootGrantAccepted !== true) {
|
|
2065
|
+
return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: READ_ROOT_GRANT_NOT_ACCEPTED }) };
|
|
2066
|
+
}
|
|
2067
|
+
const entry = this.liveEntryFor(id);
|
|
2068
|
+
if (entry?.adopted === true)
|
|
2069
|
+
return this.respondAdopted(entry, id, principal, parsed, httpReq);
|
|
1900
2070
|
if (!entry || (entry.owner !== null && entry.owner !== principal)) {
|
|
1901
2071
|
return this.respondLate(id, principal, parsed, httpReq);
|
|
1902
2072
|
}
|
|
2073
|
+
return this.answerRegistration(entry, id, principal, parsed, httpReq);
|
|
2074
|
+
}
|
|
2075
|
+
async respondAdopted(entry, id, principal, parsed, httpReq) {
|
|
2076
|
+
const store = this.askStore;
|
|
2077
|
+
if (!store)
|
|
2078
|
+
return this.notPending();
|
|
2079
|
+
const row = await this.askRowFor(store, id, "getAsk(respond-adopted)");
|
|
2080
|
+
if (!askRowVisibleTo(row, principal))
|
|
2081
|
+
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2082
|
+
if (row.state !== "STREAM_PENDING")
|
|
2083
|
+
return this.dispatchRowTruth(row, id, parsed, httpReq, principal);
|
|
2084
|
+
return this.answerRegistration(entry, id, principal, parsed, httpReq);
|
|
2085
|
+
}
|
|
2086
|
+
answerRegistration(entry, id, principal, parsed, httpReq) {
|
|
1903
2087
|
const preRule = this.precheckEditedRuleBeforeDecision(entry, parsed);
|
|
1904
2088
|
if (preRule !== undefined)
|
|
1905
2089
|
return preRule;
|
|
1906
2090
|
if (parsed.value !== "deny" && policyCannotAnswer(heldAskOf(entry.requiresRealApproval === true), parsed.decidedBy)) {
|
|
1907
|
-
return { status: 400, body: {
|
|
2091
|
+
return { status: 400, body: refusalBody({ errorCode: "request.body_shape", message: POLICY_CANNOT_ALLOW_PERSON_ASK }) };
|
|
1908
2092
|
}
|
|
1909
2093
|
const ruleEligibility = { eligible: true };
|
|
1910
|
-
const
|
|
1911
|
-
|
|
1912
|
-
|
|
2094
|
+
const store = this.askStore;
|
|
2095
|
+
if (store && entry.askId !== undefined && entry.batchId !== undefined) {
|
|
2096
|
+
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));
|
|
2097
|
+
}
|
|
2098
|
+
return this.afterLiveDecision(this.finishRespond(entry, parsed), entry, parsed, principal, ruleEligibility);
|
|
2099
|
+
}
|
|
2100
|
+
afterLiveDecision(decided, entry, parsed, principal, ruleEligibility) {
|
|
1913
2101
|
const settled = this.recordDeploymentListAnswer(decided, entry, parsed, principal, ruleEligibility);
|
|
1914
2102
|
if (parsed.persistRule === undefined)
|
|
1915
2103
|
return settled;
|
|
@@ -1956,11 +2144,13 @@ export class ToolApprovalCoordinator {
|
|
|
1956
2144
|
return undefined;
|
|
1957
2145
|
return {
|
|
1958
2146
|
status: 400,
|
|
1959
|
-
body: {
|
|
1960
|
-
error: verdict.message,
|
|
2147
|
+
body: refusalBody({
|
|
1961
2148
|
errorCode: "request.body_shape",
|
|
1962
|
-
|
|
1963
|
-
|
|
2149
|
+
message: verdict.message,
|
|
2150
|
+
facts: {
|
|
2151
|
+
...(verdict.code !== undefined ? { ruleRejectCode: verdict.code } : {}),
|
|
2152
|
+
},
|
|
2153
|
+
}),
|
|
1964
2154
|
};
|
|
1965
2155
|
}
|
|
1966
2156
|
editedArmEcho(persistRule, persisted) {
|
|
@@ -1971,45 +2161,194 @@ export class ToolApprovalCoordinator {
|
|
|
1971
2161
|
notPending(cause) {
|
|
1972
2162
|
return {
|
|
1973
2163
|
status: 404,
|
|
1974
|
-
body: {
|
|
1975
|
-
error: "no pending tool approval for this id (settled, expired, or not on this replica)",
|
|
2164
|
+
body: refusalBody({
|
|
1976
2165
|
errorCode: "tool_approval.not_pending",
|
|
1977
|
-
|
|
1978
|
-
|
|
2166
|
+
message: "no pending tool approval for this id (settled, expired, or not on this replica)",
|
|
2167
|
+
facts: {
|
|
2168
|
+
...(cause !== undefined ? { cause } : {}),
|
|
2169
|
+
},
|
|
2170
|
+
}),
|
|
1979
2171
|
};
|
|
1980
2172
|
}
|
|
1981
|
-
|
|
2173
|
+
readApproval(id, principal) {
|
|
2174
|
+
const entry = this.liveEntryFor(id);
|
|
2175
|
+
if (entry?.adopted === true)
|
|
2176
|
+
return this.readAdopted(entry, id, principal);
|
|
2177
|
+
if (entry !== undefined && (entry.owner === null || entry.owner === principal)) {
|
|
2178
|
+
const body = { state: "pending", ...livePendingRowOf(entry) };
|
|
2179
|
+
return { status: 200, body };
|
|
2180
|
+
}
|
|
1982
2181
|
const store = this.askStore;
|
|
1983
2182
|
if (!store)
|
|
1984
2183
|
return this.notPending();
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2184
|
+
return this.readAskRowState(store, id, principal);
|
|
2185
|
+
}
|
|
2186
|
+
async readAdopted(entry, id, principal) {
|
|
2187
|
+
const store = this.askStore;
|
|
2188
|
+
if (!store)
|
|
2189
|
+
return this.notPending();
|
|
2190
|
+
const row = await this.askRowFor(store, id, "getAsk(read-adopted)");
|
|
2191
|
+
if (askRowVisibleTo(row, principal) && row.state === "STREAM_PENDING") {
|
|
2192
|
+
const body = { state: "pending", ...livePendingRowOf(entry) };
|
|
2193
|
+
return { status: 200, body };
|
|
1988
2194
|
}
|
|
1989
|
-
return this.
|
|
2195
|
+
return this.readStateOfRow(row, principal);
|
|
1990
2196
|
}
|
|
1991
|
-
async
|
|
1992
|
-
|
|
2197
|
+
async readAskRowState(store, id, principal) {
|
|
2198
|
+
return this.readStateOfRow(await this.askRowFor(store, id, "getAsk(read)"), principal);
|
|
2199
|
+
}
|
|
2200
|
+
async readParkedGate(row) {
|
|
2201
|
+
const gate = this.parkedGate;
|
|
2202
|
+
if (gate === undefined || row.gateToken === null)
|
|
2203
|
+
return { kind: "open" };
|
|
1993
2204
|
try {
|
|
1994
|
-
|
|
2205
|
+
return await this.withStoreDeadline(gate({ gateToken: row.gateToken, gateBoundCallId: row.gateBoundCallId }), "readParkedGate", DURABLE_CONVERGE_READ_TIMEOUT_MS);
|
|
1995
2206
|
}
|
|
1996
2207
|
catch (err) {
|
|
1997
|
-
this.noteStoreError(err, "
|
|
1998
|
-
return
|
|
2208
|
+
this.noteStoreError(err, "readParkedGate");
|
|
2209
|
+
return { kind: "open" };
|
|
1999
2210
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
if (row
|
|
2003
|
-
defaultLogger.warn("tool approval late decision rejected: the ask row changed owner since it parked", { askId: row.askId });
|
|
2211
|
+
}
|
|
2212
|
+
async readStateOfRow(row, principal) {
|
|
2213
|
+
if (!askRowVisibleTo(row, principal))
|
|
2004
2214
|
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2215
|
+
const projection = buildAskRowReadProjection(row, Date.now());
|
|
2216
|
+
let body;
|
|
2217
|
+
switch (row.state) {
|
|
2218
|
+
case "DECIDED": {
|
|
2219
|
+
const decided = decidedRowFacts(row);
|
|
2220
|
+
if (decided === undefined)
|
|
2221
|
+
return this.notPending();
|
|
2222
|
+
body = { state: "decided", ...projection, ...decided };
|
|
2223
|
+
break;
|
|
2224
|
+
}
|
|
2225
|
+
case "PARKING":
|
|
2226
|
+
case "PARKED": {
|
|
2227
|
+
const gate = row.state === "PARKED" ? await this.readParkedGate(row) : { kind: "open" };
|
|
2228
|
+
switch (gate.kind) {
|
|
2229
|
+
case "open": {
|
|
2230
|
+
const parkSession = parkSessionOf(row);
|
|
2231
|
+
body = { state: "parked", ...projection, ...(parkSession !== undefined ? { decidePath: approvalDecidePath(parkSession) } : {}) };
|
|
2232
|
+
break;
|
|
2233
|
+
}
|
|
2234
|
+
case "decided":
|
|
2235
|
+
body = { state: "decided", ...projection, decision: gate.decision, decidedAtMs: gate.decidedAtMs, ...(gate.decisionNote !== undefined ? { decisionNote: gate.decisionNote } : {}) };
|
|
2236
|
+
break;
|
|
2237
|
+
case "void":
|
|
2238
|
+
body = { state: "void", ...projection };
|
|
2239
|
+
break;
|
|
2240
|
+
case "unknown":
|
|
2241
|
+
if (gate.why === "decision_unreadable") {
|
|
2242
|
+
defaultLogger.error("tool approval read saw a PARKED row whose gate is settled but carries no usable decision", {
|
|
2243
|
+
askId: row.askId,
|
|
2244
|
+
defect: seatText("operator-log-text", gate.defects[0]),
|
|
2245
|
+
defectCount: gate.defects.length,
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
return this.notPending();
|
|
2249
|
+
default: {
|
|
2250
|
+
const unhandled = gate;
|
|
2251
|
+
defaultLogger.error("tool approval read hit an unknown gate reading", { askId: row.askId, gate: unhandled });
|
|
2252
|
+
return this.notPending();
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
break;
|
|
2256
|
+
}
|
|
2257
|
+
case "DENIED":
|
|
2258
|
+
case "VOID": {
|
|
2259
|
+
const rowCause = causeOfAskRow(row);
|
|
2260
|
+
body = { state: "void", ...projection, ...(rowCause?.kind === "ending" ? { cause: rowCause.cause } : {}) };
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2263
|
+
case "STREAM_PENDING":
|
|
2264
|
+
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2265
|
+
default: {
|
|
2266
|
+
const unhandled = row.state;
|
|
2267
|
+
defaultLogger.error("tool approval read hit an unknown ask state", { askId: row.askId, state: unhandled });
|
|
2268
|
+
return this.notPending();
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
return { status: 200, body };
|
|
2272
|
+
}
|
|
2273
|
+
respondLate(id, principal, parsed, httpReq) {
|
|
2274
|
+
const store = this.askStore;
|
|
2275
|
+
if (!store)
|
|
2276
|
+
return this.notPending();
|
|
2277
|
+
return this.dispatchLateByRow(store, id, parsed, httpReq, principal);
|
|
2278
|
+
}
|
|
2279
|
+
watchPresentedCards(askIds, deliver) {
|
|
2280
|
+
const watcher = { deliver, askIds: new Set() };
|
|
2281
|
+
for (const askId of askIds) {
|
|
2282
|
+
if (!this.pendingByAskId.has(askId))
|
|
2283
|
+
continue;
|
|
2284
|
+
watcher.askIds.add(askId);
|
|
2285
|
+
let set = this.presentedCardWatchers.get(askId);
|
|
2286
|
+
if (set === undefined) {
|
|
2287
|
+
set = new Set();
|
|
2288
|
+
this.presentedCardWatchers.set(askId, set);
|
|
2289
|
+
}
|
|
2290
|
+
set.add(watcher);
|
|
2291
|
+
}
|
|
2292
|
+
return () => {
|
|
2293
|
+
for (const askId of watcher.askIds)
|
|
2294
|
+
this.dropPresentedCardWatcher(askId, watcher);
|
|
2295
|
+
watcher.askIds.clear();
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
dropPresentedCardWatcher(askId, watcher) {
|
|
2299
|
+
const set = this.presentedCardWatchers.get(askId);
|
|
2300
|
+
if (set === undefined)
|
|
2301
|
+
return;
|
|
2302
|
+
set.delete(watcher);
|
|
2303
|
+
if (set.size === 0)
|
|
2304
|
+
this.presentedCardWatchers.delete(askId);
|
|
2305
|
+
}
|
|
2306
|
+
notifyPresentedCardWatchers(askId, frame) {
|
|
2307
|
+
const set = this.presentedCardWatchers.get(askId);
|
|
2308
|
+
if (set === undefined)
|
|
2309
|
+
return;
|
|
2310
|
+
this.presentedCardWatchers.delete(askId);
|
|
2311
|
+
for (const watcher of set) {
|
|
2312
|
+
watcher.askIds.delete(askId);
|
|
2313
|
+
try {
|
|
2314
|
+
watcher.deliver(frame);
|
|
2315
|
+
}
|
|
2316
|
+
catch (err) {
|
|
2317
|
+
defaultLogger.warn("presented-card completion notice could not be written to its connection", { askId, error: err instanceof Error ? err.message : String(err) });
|
|
2318
|
+
}
|
|
2005
2319
|
}
|
|
2006
|
-
|
|
2007
|
-
|
|
2320
|
+
}
|
|
2321
|
+
liveEntryFor(id) {
|
|
2322
|
+
const local = this.pending.get(id);
|
|
2323
|
+
if (local !== undefined)
|
|
2324
|
+
return local.askId === undefined ? local : undefined;
|
|
2325
|
+
for (const e of this.pendingByAskId.get(id) ?? [])
|
|
2326
|
+
return e;
|
|
2327
|
+
return undefined;
|
|
2328
|
+
}
|
|
2329
|
+
async dispatchLateByRow(store, id, parsed, httpReq, principal) {
|
|
2330
|
+
return this.dispatchRowTruth(await this.askRowFor(store, id, "getAsk(respond-late)"), id, parsed, httpReq, principal);
|
|
2331
|
+
}
|
|
2332
|
+
async askRowFor(store, id, label) {
|
|
2333
|
+
try {
|
|
2334
|
+
return await this.withStoreDeadline(store.getAsk(id), label, DURABLE_CONVERGE_READ_TIMEOUT_MS);
|
|
2335
|
+
}
|
|
2336
|
+
catch (err) {
|
|
2337
|
+
this.noteStoreError(err, label);
|
|
2338
|
+
return null;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
async dispatchRowTruth(row, id, parsed, httpReq, principal) {
|
|
2342
|
+
if (!askRowVisibleTo(row, principal))
|
|
2343
|
+
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2344
|
+
const receipt = this.lookupParkRedemption(row.askId);
|
|
2345
|
+
if (receipt?.redeemed !== undefined)
|
|
2346
|
+
return this.replayRedeemReceipt(id, receipt, row, receipt.redeemed, parsed);
|
|
2008
2347
|
switch (row.state) {
|
|
2009
2348
|
case "DECIDED":
|
|
2010
2349
|
return this.replayDecidedAsk(id, row, parsed);
|
|
2011
2350
|
case "PARKED":
|
|
2012
|
-
return this.redeemParkedAsk(id,
|
|
2351
|
+
return this.redeemParkedAsk(id, row, parsed, httpReq, principal);
|
|
2013
2352
|
case "PARKING": {
|
|
2014
2353
|
const parkSession = parkSessionOf(row);
|
|
2015
2354
|
return {
|
|
@@ -2036,7 +2375,7 @@ export class ToolApprovalCoordinator {
|
|
|
2036
2375
|
return this.notPending(NOT_PENDING_UNKNOWN_CAUSE);
|
|
2037
2376
|
default: {
|
|
2038
2377
|
const unhandled = row.state;
|
|
2039
|
-
defaultLogger.error("tool approval respond hit an unknown ask state", { askId:
|
|
2378
|
+
defaultLogger.error("tool approval respond hit an unknown ask state", { askId: row.askId, state: unhandled });
|
|
2040
2379
|
return this.notPending();
|
|
2041
2380
|
}
|
|
2042
2381
|
}
|
|
@@ -2047,7 +2386,8 @@ export class ToolApprovalCoordinator {
|
|
|
2047
2386
|
return { rulePersisted: false, ruleRefusal: "rule_lane_unavailable" };
|
|
2048
2387
|
}
|
|
2049
2388
|
replayDecidedAsk(id, row, parsed) {
|
|
2050
|
-
|
|
2389
|
+
const decided = decidedRowFacts(row);
|
|
2390
|
+
if (decided === undefined) {
|
|
2051
2391
|
defaultLogger.error("tool approval late replay saw a DECIDED row that is not a usable human decision", {
|
|
2052
2392
|
askId: row.askId,
|
|
2053
2393
|
decision: row.decision,
|
|
@@ -2063,15 +2403,14 @@ export class ToolApprovalCoordinator {
|
|
|
2063
2403
|
approvalId: id,
|
|
2064
2404
|
askId: row.askId,
|
|
2065
2405
|
idempotent: true,
|
|
2066
|
-
|
|
2067
|
-
decidedAtMs: row.decidedAtMs,
|
|
2068
|
-
...(row.decisionNote !== null ? { decisionNote: row.decisionNote } : {}),
|
|
2406
|
+
...decided,
|
|
2069
2407
|
...(parsed.value === "allow_session" ? { rememberApplied: false } : {}),
|
|
2408
|
+
...noteRecordedReceipt(parsed.note, noteLandedOnRow(parsed.note, row.decisionNote)),
|
|
2070
2409
|
...this.lateRuleRefusal(parsed),
|
|
2071
2410
|
},
|
|
2072
2411
|
};
|
|
2073
2412
|
}
|
|
2074
|
-
async redeemParkedAsk(id,
|
|
2413
|
+
async redeemParkedAsk(id, row, parsed, httpReq, principal) {
|
|
2075
2414
|
const redeem = this.parkedRedeem?.();
|
|
2076
2415
|
if (redeem === undefined)
|
|
2077
2416
|
return this.notPending();
|
|
@@ -2079,14 +2418,19 @@ export class ToolApprovalCoordinator {
|
|
|
2079
2418
|
defaultLogger.error("tool approval parked row carries no gate token — nothing to redeem", { askId: row.askId });
|
|
2080
2419
|
return this.notPending();
|
|
2081
2420
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2421
|
+
const prior = this.lookupParkRedemption(row.askId);
|
|
2422
|
+
if (prior?.redeemed !== undefined)
|
|
2423
|
+
return this.replayRedeemReceipt(id, prior, row, prior.redeemed, parsed);
|
|
2424
|
+
if (prior?.inflight !== undefined) {
|
|
2425
|
+
const shared = await prior.inflight;
|
|
2086
2426
|
if (shared.status < 200 || shared.status >= 300)
|
|
2087
2427
|
return shared;
|
|
2088
|
-
return this.replayRedeemReceipt(id,
|
|
2428
|
+
return this.replayRedeemReceipt(id, prior, row, { decision: shared.decision, body: shared.body, ...(shared.rule?.terminal === true ? { rule: shared.rule.body } : {}) }, parsed);
|
|
2089
2429
|
}
|
|
2430
|
+
const admission = this.parkRedemptionAdmission(row.owner);
|
|
2431
|
+
if (admission !== undefined)
|
|
2432
|
+
return admission;
|
|
2433
|
+
const memo = this.openParkRedemption(row.askId, row.owner);
|
|
2090
2434
|
const decision = parsed.value === "deny" ? "deny" : "approve";
|
|
2091
2435
|
const gateToken = row.gateToken;
|
|
2092
2436
|
const flight = (async () => {
|
|
@@ -2107,25 +2451,29 @@ export class ToolApprovalCoordinator {
|
|
|
2107
2451
|
}
|
|
2108
2452
|
catch (err) {
|
|
2109
2453
|
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"
|
|
2454
|
+
return { status: 500, body: refusalBody({ errorCode: "internal.error", message: "the parked approval could not be redeemed right now — retry" }), decision };
|
|
2111
2455
|
}
|
|
2112
2456
|
if (res.status < 200 || res.status >= 300)
|
|
2113
2457
|
return { ...res, decision };
|
|
2114
2458
|
const rule = await this.persistRuleFromRow(row, parsed);
|
|
2115
2459
|
return { ...res, decision, ...(rule !== undefined ? { rule } : {}) };
|
|
2116
2460
|
})();
|
|
2117
|
-
|
|
2461
|
+
memo.inflight = flight;
|
|
2118
2462
|
let out;
|
|
2119
2463
|
try {
|
|
2120
2464
|
out = await flight;
|
|
2121
2465
|
}
|
|
2122
2466
|
finally {
|
|
2123
|
-
|
|
2467
|
+
memo.inflight = undefined;
|
|
2124
2468
|
}
|
|
2125
|
-
if (out.status < 200 || out.status >= 300)
|
|
2469
|
+
if (out.status < 200 || out.status >= 300) {
|
|
2470
|
+
if (this.parkRedemptions.get(row.askId) === memo)
|
|
2471
|
+
this.parkRedemptions.delete(row.askId);
|
|
2126
2472
|
return { status: out.status, body: out.body };
|
|
2473
|
+
}
|
|
2127
2474
|
const rule = out.rule;
|
|
2128
|
-
|
|
2475
|
+
if (this.retainParkRedemption(row.askId, memo))
|
|
2476
|
+
memo.redeemed = { decision, body: out.body, ...(rule?.terminal === true ? { rule: rule.body } : {}) };
|
|
2129
2477
|
return {
|
|
2130
2478
|
status: out.status,
|
|
2131
2479
|
body: {
|
|
@@ -2216,24 +2564,24 @@ export class ToolApprovalCoordinator {
|
|
|
2216
2564
|
return { ...flag(false, "rule_store_error"), terminal: false };
|
|
2217
2565
|
}
|
|
2218
2566
|
}
|
|
2219
|
-
async replayRedeemReceipt(id,
|
|
2220
|
-
let rule = receipt.rule;
|
|
2221
|
-
if (rule === undefined && parsed.persistRule !== undefined) {
|
|
2222
|
-
const flight =
|
|
2223
|
-
const isOwner =
|
|
2567
|
+
async replayRedeemReceipt(id, memo, row, receipt, parsed) {
|
|
2568
|
+
let rule = receipt.decision === "approve" ? receipt.rule : this.lateRuleRefusal(parsed);
|
|
2569
|
+
if (receipt.decision === "approve" && rule === undefined && parsed.persistRule !== undefined) {
|
|
2570
|
+
const flight = memo.ruleInflight ?? this.persistRuleFromRow(row, parsed);
|
|
2571
|
+
const isOwner = memo.ruleInflight === undefined;
|
|
2224
2572
|
if (isOwner)
|
|
2225
|
-
|
|
2573
|
+
memo.ruleInflight = flight;
|
|
2226
2574
|
let fresh;
|
|
2227
2575
|
try {
|
|
2228
2576
|
fresh = await flight;
|
|
2229
2577
|
}
|
|
2230
2578
|
finally {
|
|
2231
2579
|
if (isOwner)
|
|
2232
|
-
|
|
2580
|
+
memo.ruleInflight = undefined;
|
|
2233
2581
|
}
|
|
2234
2582
|
rule = fresh?.body;
|
|
2235
|
-
if (fresh?.terminal === true &&
|
|
2236
|
-
|
|
2583
|
+
if (fresh?.terminal === true && memo.redeemed !== undefined)
|
|
2584
|
+
memo.redeemed.rule = fresh.body;
|
|
2237
2585
|
}
|
|
2238
2586
|
return {
|
|
2239
2587
|
status: 200,
|
|
@@ -2328,7 +2676,7 @@ export class ToolApprovalCoordinator {
|
|
|
2328
2676
|
return withFlag(false, "rule_store_error");
|
|
2329
2677
|
}
|
|
2330
2678
|
}
|
|
2331
|
-
async respondWithCas(askStore, askId, batchId,
|
|
2679
|
+
async respondWithCas(askStore, askId, batchId, entry, parsed, ruleEligibility) {
|
|
2332
2680
|
let winnerRow;
|
|
2333
2681
|
let ambiguousConfirmedRow;
|
|
2334
2682
|
let noteLanded = false;
|
|
@@ -2347,7 +2695,7 @@ export class ToolApprovalCoordinator {
|
|
|
2347
2695
|
noteLanded = true;
|
|
2348
2696
|
}
|
|
2349
2697
|
if (!decided.ok) {
|
|
2350
|
-
return
|
|
2698
|
+
return { lost: true, row: decided.row };
|
|
2351
2699
|
}
|
|
2352
2700
|
}
|
|
2353
2701
|
catch (err) {
|
|
@@ -2358,7 +2706,7 @@ export class ToolApprovalCoordinator {
|
|
|
2358
2706
|
if (row?.state === "DECIDED" && row.decision === (parsed.value === "deny" ? "deny" : "approve")) {
|
|
2359
2707
|
winnerRow = row;
|
|
2360
2708
|
ambiguousConfirmedRow = row;
|
|
2361
|
-
noteLanded = parsed.note
|
|
2709
|
+
noteLanded = noteLandedOnRow(parsed.note, row.decisionNote);
|
|
2362
2710
|
}
|
|
2363
2711
|
}
|
|
2364
2712
|
catch (confirmErr) {
|
|
@@ -2367,27 +2715,27 @@ export class ToolApprovalCoordinator {
|
|
|
2367
2715
|
}
|
|
2368
2716
|
const wantAllowed = parsed.value !== "deny";
|
|
2369
2717
|
const decidedWon = winnerRow !== undefined;
|
|
2370
|
-
if (
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const
|
|
2718
|
+
if (entry.adopted === true && !decidedWon)
|
|
2719
|
+
return { lost: false, result: this.notPending() };
|
|
2720
|
+
if (this.pending.get(entry.registrationId) !== entry) {
|
|
2721
|
+
const othersReached = winnerRow !== undefined ? this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow)).settled : 0;
|
|
2722
|
+
const converged = decidedWon && (entry.adopted === true || entry.settledOutcome === (wantAllowed ? SETTLE_OUTCOME.ALLOWED : SETTLE_OUTCOME.DENIED));
|
|
2374
2723
|
if (!converged) {
|
|
2375
|
-
return this.notPending();
|
|
2724
|
+
return { lost: false, result: this.notPending() };
|
|
2376
2725
|
}
|
|
2377
|
-
return this.finishRespond(
|
|
2726
|
+
return { lost: false, result: withConsumerDisclosure(this.finishRespond(entry, parsed, { updatedInputDelivered: false, noteRecorded: noteLanded }), waitingLegsReached(entry, othersReached)) };
|
|
2378
2727
|
}
|
|
2379
2728
|
const payloadIsMine = ambiguousConfirmedRow === undefined ||
|
|
2380
2729
|
approvalPayloadFingerprint({ updatedInput: ambiguousConfirmedRow.updatedInput }) === approvalPayloadFingerprint({ updatedInput: parsed.updatedInput });
|
|
2381
|
-
const result = this.finishRespond(
|
|
2730
|
+
const result = this.finishRespond(entry, parsed, {
|
|
2382
2731
|
noteRecorded: noteLanded,
|
|
2383
2732
|
...(winnerRow !== undefined ? { settleOutcome: outcomeOfDecidedRow(winnerRow), settleOutcomeIsThisRequest: ambiguousConfirmedRow === undefined } : {}),
|
|
2384
|
-
...(ambiguousConfirmedRow !== undefined ? { updatedInputDelivered: payloadIsMine } : {}),
|
|
2733
|
+
...(entry.adopted === true ? { updatedInputDelivered: false } : ambiguousConfirmedRow !== undefined ? { updatedInputDelivered: payloadIsMine } : {}),
|
|
2385
2734
|
});
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
return result;
|
|
2735
|
+
const othersReached = winnerRow !== undefined ? this.notifyExternalDecision(askId, outcomeOfDecidedRow(winnerRow)).settled : 0;
|
|
2736
|
+
return { lost: false, result: withConsumerDisclosure(result, waitingLegsReached(entry, othersReached)) };
|
|
2389
2737
|
}
|
|
2390
|
-
finishRespond(
|
|
2738
|
+
finishRespond(entry, parsed, opts) {
|
|
2391
2739
|
let rememberApplied;
|
|
2392
2740
|
const home = standingAnswerHome(pendingEntryStandingAnswerFacts(entry));
|
|
2393
2741
|
let grantable;
|
|
@@ -2430,42 +2778,146 @@ export class ToolApprovalCoordinator {
|
|
|
2430
2778
|
return {
|
|
2431
2779
|
status: 200,
|
|
2432
2780
|
body: {
|
|
2433
|
-
approvalId:
|
|
2781
|
+
approvalId: entry.approvalId,
|
|
2434
2782
|
delivery: "applied",
|
|
2435
2783
|
decision: parsed.value,
|
|
2436
2784
|
...(rememberApplied !== undefined ? { rememberApplied } : {}),
|
|
2437
2785
|
...(parsed.updatedInput !== undefined && allowed && updatedInputDelivered ? { updatedInputForwarded: true } : {}),
|
|
2438
|
-
...(parsed.note
|
|
2786
|
+
...noteRecordedReceipt(parsed.note, noteRecorded),
|
|
2439
2787
|
},
|
|
2440
2788
|
};
|
|
2441
2789
|
}
|
|
2790
|
+
async adoptOpenAsks() {
|
|
2791
|
+
const store = this.askStore;
|
|
2792
|
+
if (!store)
|
|
2793
|
+
return { adopted: 0, alreadyLive: 0, cardUnreadable: 0, capped: false };
|
|
2794
|
+
let rows;
|
|
2795
|
+
try {
|
|
2796
|
+
rows = await this.withStoreDeadline(store.listByState("STREAM_PENDING", ADOPT_OPEN_ASKS_MAX + 1), "listByState(adopt)");
|
|
2797
|
+
}
|
|
2798
|
+
catch (err) {
|
|
2799
|
+
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)}) — ` +
|
|
2800
|
+
"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.");
|
|
2801
|
+
}
|
|
2802
|
+
const capped = rows.length > ADOPT_OPEN_ASKS_MAX;
|
|
2803
|
+
const nowMs = Date.now();
|
|
2804
|
+
let adopted = 0;
|
|
2805
|
+
let alreadyLive = 0;
|
|
2806
|
+
let cardUnreadable = 0;
|
|
2807
|
+
for (const row of capped ? rows.slice(0, ADOPT_OPEN_ASKS_MAX) : rows) {
|
|
2808
|
+
if (this.pendingByAskId.has(row.askId)) {
|
|
2809
|
+
alreadyLive += 1;
|
|
2810
|
+
continue;
|
|
2811
|
+
}
|
|
2812
|
+
const replayFrame = buildReplayFrame(row, nowMs);
|
|
2813
|
+
if (replayFrame === undefined) {
|
|
2814
|
+
cardUnreadable += 1;
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
this.registerAdopted(row, replayFrame);
|
|
2818
|
+
adopted += 1;
|
|
2819
|
+
}
|
|
2820
|
+
return { adopted, alreadyLive, cardUnreadable, capped };
|
|
2821
|
+
}
|
|
2822
|
+
registerAdopted(row, replayFrame) {
|
|
2823
|
+
const card = replayFrame.card;
|
|
2824
|
+
const registrationId = uuidv7();
|
|
2825
|
+
const facts = {
|
|
2826
|
+
...(card.governanceForced === true ? { governanceForced: true } : {}),
|
|
2827
|
+
...(card.risk.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
2828
|
+
grantBinding: { kind: "unreadable" },
|
|
2829
|
+
...(card.classes !== undefined ? { classes: card.classes } : {}),
|
|
2830
|
+
};
|
|
2831
|
+
const rememberRefused = standingAnswerHome(pendingEntryStandingAnswerFacts(facts)) === "none";
|
|
2832
|
+
const cardFrame = (card.rememberRefused === true) === rememberRefused
|
|
2833
|
+
? replayFrame
|
|
2834
|
+
: { ...replayFrame, card: rememberRefused ? { ...card, rememberRefused: true } : (({ rememberRefused: _stale, ...rest }) => rest)(card) };
|
|
2835
|
+
let done = false;
|
|
2836
|
+
const leave = (ended) => {
|
|
2837
|
+
if (done)
|
|
2838
|
+
return;
|
|
2839
|
+
done = true;
|
|
2840
|
+
this.pending.delete(registrationId);
|
|
2841
|
+
entry.settledOutcome = ended?.outcome ?? SETTLE_OUTCOME.EXPIRED;
|
|
2842
|
+
const siblings = this.pendingByAskId.get(row.askId);
|
|
2843
|
+
siblings?.delete(entry);
|
|
2844
|
+
const lastRegistration = siblings !== undefined && siblings.size === 0;
|
|
2845
|
+
if (lastRegistration)
|
|
2846
|
+
this.pendingByAskId.delete(row.askId);
|
|
2847
|
+
if (ended !== undefined && lastRegistration) {
|
|
2848
|
+
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() }));
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
const release = () => leave(undefined);
|
|
2852
|
+
const entry = {
|
|
2853
|
+
adopted: true,
|
|
2854
|
+
cardFrame,
|
|
2855
|
+
release,
|
|
2856
|
+
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 }),
|
|
2857
|
+
forceParkNow: release,
|
|
2858
|
+
settleParkRoute: (cause) => leave({ outcome: SETTLE_OUTCOME.EXPIRED, cause, parked: this.unattendedRoutesToPark }),
|
|
2859
|
+
owner: row.owner,
|
|
2860
|
+
...(row.sessionId !== row.taskId ? { sessionId: row.sessionId } : {}),
|
|
2861
|
+
...(card.fromSubagent === true ? { originTaskId: row.sourceTaskId } : {}),
|
|
2862
|
+
toolName: card.toolName,
|
|
2863
|
+
...facts,
|
|
2864
|
+
approvalId: row.askId,
|
|
2865
|
+
registrationId,
|
|
2866
|
+
registeredAtMs: row.createdAtMs,
|
|
2867
|
+
expiresAtMs: row.expiresAtMs,
|
|
2868
|
+
askId: row.askId,
|
|
2869
|
+
batchId: row.batchId,
|
|
2870
|
+
};
|
|
2871
|
+
this.pending.set(registrationId, entry);
|
|
2872
|
+
const siblings = this.pendingByAskId.get(row.askId) ?? new Set();
|
|
2873
|
+
siblings.add(entry);
|
|
2874
|
+
this.pendingByAskId.set(row.askId, siblings);
|
|
2875
|
+
}
|
|
2876
|
+
releaseAdoptedRegistrations(askId) {
|
|
2877
|
+
for (const e of [...(this.pendingByAskId.get(askId) ?? [])])
|
|
2878
|
+
if (e.adopted === true)
|
|
2879
|
+
e.release();
|
|
2880
|
+
}
|
|
2881
|
+
forgetAdoptedAsk(askId) {
|
|
2882
|
+
this.releaseAdoptedRegistrations(askId);
|
|
2883
|
+
if (this.pendingByAskId.has(askId))
|
|
2884
|
+
return;
|
|
2885
|
+
const watchers = this.presentedCardWatchers.get(askId);
|
|
2886
|
+
if (watchers === undefined)
|
|
2887
|
+
return;
|
|
2888
|
+
this.presentedCardWatchers.delete(askId);
|
|
2889
|
+
for (const watcher of watchers)
|
|
2890
|
+
watcher.askIds.delete(askId);
|
|
2891
|
+
}
|
|
2892
|
+
followAdoptedOwner(askId, owner) {
|
|
2893
|
+
for (const e of this.pendingByAskId.get(askId) ?? [])
|
|
2894
|
+
if (e.adopted === true)
|
|
2895
|
+
e.owner = owner;
|
|
2896
|
+
}
|
|
2897
|
+
waitingRegistrationCount(askId) {
|
|
2898
|
+
let n = 0;
|
|
2899
|
+
for (const e of this.pendingByAskId.get(askId) ?? [])
|
|
2900
|
+
if (e.adopted !== true)
|
|
2901
|
+
n += 1;
|
|
2902
|
+
return n;
|
|
2903
|
+
}
|
|
2442
2904
|
listLivePending(scope) {
|
|
2443
2905
|
const rows = [];
|
|
2444
|
-
|
|
2445
|
-
|
|
2906
|
+
const owner = scope === undefined ? undefined : ownerOfCheckpointScope(scope);
|
|
2907
|
+
const seen = new Set();
|
|
2908
|
+
for (const e of this.pending.values()) {
|
|
2909
|
+
if (owner !== undefined && e.owner !== owner)
|
|
2446
2910
|
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
|
-
});
|
|
2911
|
+
if (seen.has(e.approvalId))
|
|
2912
|
+
continue;
|
|
2913
|
+
seen.add(e.approvalId);
|
|
2914
|
+
rows.push(livePendingRowOf(e));
|
|
2463
2915
|
}
|
|
2464
2916
|
return rows;
|
|
2465
2917
|
}
|
|
2466
2918
|
hasLivePendingFrom(originTaskId) {
|
|
2467
2919
|
for (const e of this.pending.values())
|
|
2468
|
-
if (e.originTaskId === originTaskId)
|
|
2920
|
+
if (e.originTaskId === originTaskId && e.adopted !== true)
|
|
2469
2921
|
return true;
|
|
2470
2922
|
return false;
|
|
2471
2923
|
}
|
|
@@ -2473,27 +2925,42 @@ export class ToolApprovalCoordinator {
|
|
|
2473
2925
|
const store = this.askStore;
|
|
2474
2926
|
if (!store)
|
|
2475
2927
|
return { polled: 0, settled: 0, backlog: 0, truncated: false };
|
|
2476
|
-
const
|
|
2928
|
+
const waiting = [];
|
|
2929
|
+
const adopted = [];
|
|
2477
2930
|
const seen = new Set();
|
|
2478
2931
|
for (const entry of this.pending.values()) {
|
|
2479
|
-
if (entry.
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2932
|
+
if (entry.askId === undefined || entry.settledOutcome !== undefined || seen.has(entry.askId))
|
|
2933
|
+
continue;
|
|
2934
|
+
if (entry.suspended === true)
|
|
2935
|
+
waiting.push(entry.askId);
|
|
2936
|
+
else if (entry.adopted === true)
|
|
2937
|
+
adopted.push(entry.askId);
|
|
2938
|
+
else
|
|
2939
|
+
continue;
|
|
2940
|
+
seen.add(entry.askId);
|
|
2941
|
+
}
|
|
2942
|
+
const waitingStart = waiting.length > 0 ? this.suspendedPollOffset % waiting.length : 0;
|
|
2943
|
+
const adoptedStart = adopted.length > 0 ? this.adoptedPollOffset % adopted.length : 0;
|
|
2944
|
+
const ordered = [
|
|
2945
|
+
...[...waiting.slice(waitingStart), ...waiting.slice(0, waitingStart)].map((askId) => ({ askId, tier: "waiting" })),
|
|
2946
|
+
...[...adopted.slice(adoptedStart), ...adopted.slice(0, adoptedStart)].map((askId) => ({ askId, tier: "adopted" })),
|
|
2947
|
+
];
|
|
2486
2948
|
const deadlineMs = Date.now() + SUSPENDED_POLL_BUDGET_MS;
|
|
2487
2949
|
let settled = 0;
|
|
2488
2950
|
let polled = 0;
|
|
2951
|
+
let polledWaiting = 0;
|
|
2952
|
+
let polledAdopted = 0;
|
|
2489
2953
|
let truncated = false;
|
|
2490
|
-
for (const askId of ordered) {
|
|
2954
|
+
for (const { askId, tier } of ordered) {
|
|
2491
2955
|
if (polled >= SUSPENDED_POLL_MAX_PER_TICK || Date.now() >= deadlineMs) {
|
|
2492
2956
|
truncated = true;
|
|
2493
2957
|
break;
|
|
2494
2958
|
}
|
|
2495
2959
|
polled++;
|
|
2496
|
-
|
|
2960
|
+
if (tier === "waiting")
|
|
2961
|
+
this.suspendedPollOffset = waitingStart + ++polledWaiting;
|
|
2962
|
+
else
|
|
2963
|
+
this.adoptedPollOffset = adoptedStart + ++polledAdopted;
|
|
2497
2964
|
let row;
|
|
2498
2965
|
try {
|
|
2499
2966
|
row = await this.withStoreDeadline(store.getAsk(askId), "getAsk(poll-suspended)", DURABLE_CONVERGE_READ_TIMEOUT_MS);
|
|
@@ -2502,20 +2969,26 @@ export class ToolApprovalCoordinator {
|
|
|
2502
2969
|
this.noteStoreError(err, "getAsk(poll-suspended)");
|
|
2503
2970
|
continue;
|
|
2504
2971
|
}
|
|
2505
|
-
if (row === null
|
|
2972
|
+
if (row === null) {
|
|
2973
|
+
this.forgetAdoptedAsk(askId);
|
|
2974
|
+
continue;
|
|
2975
|
+
}
|
|
2976
|
+
if (row.state === "STREAM_PENDING") {
|
|
2977
|
+
this.followAdoptedOwner(askId, row.owner);
|
|
2506
2978
|
continue;
|
|
2979
|
+
}
|
|
2507
2980
|
if (this.editedDecisionInFlight(askId))
|
|
2508
2981
|
continue;
|
|
2509
2982
|
const rowCause = causeOfAskRow(row);
|
|
2510
2983
|
if (rowCause?.kind === "ending") {
|
|
2511
|
-
const before = this.
|
|
2984
|
+
const before = this.waitingRegistrationCount(askId);
|
|
2512
2985
|
this.settleVoidedSiblings([askId], rowCause.cause);
|
|
2513
2986
|
settled += before;
|
|
2514
2987
|
continue;
|
|
2515
2988
|
}
|
|
2516
2989
|
const outcome = replayTerminalAskRow(row);
|
|
2517
2990
|
if (outcome === "unavailable") {
|
|
2518
|
-
const before = this.
|
|
2991
|
+
const before = this.waitingRegistrationCount(askId);
|
|
2519
2992
|
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
2993
|
this.settleSameAskIdParkRoute(askId, parkCause);
|
|
2521
2994
|
settled += before;
|
|
@@ -2524,7 +2997,7 @@ export class ToolApprovalCoordinator {
|
|
|
2524
2997
|
const delivered = this.notifyExternalDecision(askId, outcome).settled;
|
|
2525
2998
|
settled += delivered;
|
|
2526
2999
|
}
|
|
2527
|
-
const backlog = Math.max(0,
|
|
3000
|
+
const backlog = Math.max(0, waiting.length + adopted.length - polled);
|
|
2528
3001
|
if (truncated) {
|
|
2529
3002
|
defaultLogger.warn("approval_suspended_poll_truncated", {
|
|
2530
3003
|
polled,
|
|
@@ -2533,6 +3006,7 @@ export class ToolApprovalCoordinator {
|
|
|
2533
3006
|
maxPerTick: SUSPENDED_POLL_MAX_PER_TICK,
|
|
2534
3007
|
budgetMs: SUSPENDED_POLL_BUDGET_MS,
|
|
2535
3008
|
resumeFromOffset: this.suspendedPollOffset,
|
|
3009
|
+
resumeAdoptedFromOffset: this.adoptedPollOffset,
|
|
2536
3010
|
});
|
|
2537
3011
|
}
|
|
2538
3012
|
return { polled, settled, backlog, truncated };
|
|
@@ -2582,8 +3056,11 @@ export class ToolApprovalCoordinator {
|
|
|
2582
3056
|
sessionAllowedCount() {
|
|
2583
3057
|
return this.allowAllSessions.size;
|
|
2584
3058
|
}
|
|
2585
|
-
|
|
2586
|
-
return this.
|
|
3059
|
+
presentedCardWatcherCount() {
|
|
3060
|
+
return this.presentedCardWatchers.size;
|
|
3061
|
+
}
|
|
3062
|
+
parkRedemptionCount() {
|
|
3063
|
+
return this.parkRedemptions.size;
|
|
2587
3064
|
}
|
|
2588
3065
|
}
|
|
2589
3066
|
//# sourceMappingURL=tool-approval.js.map
|