@sema-agent/server 1.322.0 → 2.0.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/dist/approval-hmac.d.ts +40 -0
- package/dist/approval-hmac.js +67 -0
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +72 -4
- package/dist/audit.d.ts +26 -0
- package/dist/audit.js +40 -1
- package/dist/auth-bridge.d.ts +10 -0
- package/dist/auth-bridge.js +47 -4
- package/dist/auth-keys.d.ts +19 -0
- package/dist/auth-keys.js +13 -0
- package/dist/bake-runner/main.d.ts +5 -0
- package/dist/bake-runner/main.js +43 -4
- package/dist/bake-runner/protocol.d.ts +57 -0
- package/dist/bake-runner/protocol.js +58 -3
- package/dist/bake-runner/runner.d.ts +54 -0
- package/dist/bake-runner/runner.js +87 -5
- package/dist/bench/l8/artifact.d.ts +63 -0
- package/dist/bench/l8/artifact.js +61 -2
- package/dist/bench/l8/escape.d.ts +36 -0
- package/dist/bench/l8/escape.js +25 -0
- package/dist/bench/l8/index.d.ts +14 -0
- package/dist/bench/l8/index.js +14 -0
- package/dist/bench/l8/probes.d.ts +74 -0
- package/dist/bench/l8/probes.js +60 -0
- package/dist/bench/l8/run-probes.d.ts +43 -0
- package/dist/bench/l8/run-probes.js +45 -2
- package/dist/bench/s1/arms.d.ts +126 -0
- package/dist/bench/s1/arms.js +188 -13
- package/dist/bench/s1/live-deps.d.ts +66 -0
- package/dist/bench/s1/live-deps.js +249 -11
- package/dist/bench/s1/oracle.d.ts +49 -0
- package/dist/bench/s1/oracle.js +36 -1
- package/dist/bench/s1/repair-oracle-adapter.d.ts +33 -0
- package/dist/bench/s1/repair-oracle-adapter.js +30 -0
- package/dist/bench/s1/reviewer.d.ts +38 -0
- package/dist/bench/s1/reviewer.js +34 -0
- package/dist/bench/s1/row.d.ts +77 -0
- package/dist/bench/s1/row.js +62 -0
- package/dist/bench/s1/run-firm.d.ts +47 -0
- package/dist/bench/s1/run-firm.js +70 -4
- package/dist/bench/s1/runner-ctx.d.ts +71 -0
- package/dist/bench/s1/runner-ctx.js +36 -0
- package/dist/bench/s1/tasks.d.ts +108 -0
- package/dist/bench/s1/tasks.js +115 -0
- package/dist/boot-reclaim.d.ts +19 -0
- package/dist/boot-reclaim.js +20 -0
- package/dist/brain.d.ts +25 -0
- package/dist/brain.js +61 -1
- package/dist/budget.d.ts +66 -0
- package/dist/budget.js +128 -4
- package/dist/capabilities/builtin-tools.d.ts +3 -0
- package/dist/capabilities/builtin-tools.js +3 -0
- package/dist/capabilities/center-plugins.d.ts +17 -1
- package/dist/capabilities/center-plugins.js +45 -4
- package/dist/capabilities/center-prompts.d.ts +46 -0
- package/dist/capabilities/center-prompts.js +57 -3
- package/dist/capabilities/code-review-council.d.ts +28 -0
- package/dist/capabilities/code-review-council.js +40 -7
- package/dist/capabilities/collab-wire.d.ts +2 -0
- package/dist/capabilities/collab-workflows.d.ts +7 -0
- package/dist/capabilities/collab-workflows.js +44 -1
- package/dist/capabilities/oa-tools.d.ts +6 -0
- package/dist/capabilities/oa-tools.js +10 -0
- package/dist/capabilities/prompt.d.ts +14 -0
- package/dist/capabilities/prompt.js +14 -0
- package/dist/capabilities/prompts/code-review.d.ts +14 -0
- package/dist/capabilities/prompts/code-review.js +14 -0
- package/dist/capabilities/prompts/identity.d.ts +6 -0
- package/dist/capabilities/prompts/identity.js +6 -0
- package/dist/capabilities/prompts/team.d.ts +4 -0
- package/dist/capabilities/prompts/team.js +4 -0
- package/dist/capabilities/repo-tools.d.ts +11 -0
- package/dist/capabilities/repo-tools.js +8 -0
- package/dist/capabilities/sandbox-file-send.d.ts +95 -0
- package/dist/capabilities/sandbox-file-send.js +91 -3
- package/dist/capabilities/scenarios.d.ts +130 -0
- package/dist/capabilities/scenarios.js +180 -7
- package/dist/capabilities/select-environment-tool.d.ts +7 -0
- package/dist/capabilities/select-environment-tool.js +30 -0
- package/dist/capabilities/send-user-file-tool.d.ts +33 -0
- package/dist/capabilities/send-user-file-tool.js +65 -1
- package/dist/capabilities/skills.d.ts +29 -0
- package/dist/capabilities/skills.js +13 -2
- package/dist/capabilities/team.d.ts +22 -0
- package/dist/capabilities/team.js +22 -2
- package/dist/capabilities/tool-defer.d.ts +26 -0
- package/dist/capabilities/tool-defer.js +3 -0
- package/dist/config-center/apply-effective.d.ts +65 -0
- package/dist/config-center/apply-effective.js +204 -12
- package/dist/config-center/http-client.d.ts +34 -0
- package/dist/config-center/http-client.js +68 -0
- package/dist/config-center/restart-signal.d.ts +22 -0
- package/dist/config-center/restart-signal.js +39 -0
- package/dist/config-center/skills-mcp.d.ts +20 -1
- package/dist/config-center/skills-mcp.js +42 -7
- package/dist/config-center/types.d.ts +92 -1
- package/dist/config-lkg.d.ts +12 -0
- package/dist/config-lkg.js +41 -0
- package/dist/config-provider.d.ts +78 -0
- package/dist/config-provider.js +165 -2
- package/dist/config-types.d.ts +546 -0
- package/dist/config.d.ts +64 -0
- package/dist/config.js +455 -60
- package/dist/degenerate-instrument.d.ts +38 -0
- package/dist/degenerate-instrument.js +55 -3
- package/dist/elicitation.d.ts +39 -0
- package/dist/elicitation.js +87 -3
- package/dist/env-facts.d.ts +61 -0
- package/dist/env-facts.js +73 -7
- package/dist/fleet/fleet-bus.d.ts +185 -0
- package/dist/fleet/fleet-bus.js +333 -27
- package/dist/fleet/subagent-tail-bus.d.ts +11 -0
- package/dist/fleet/subagent-tail-bus.js +33 -2
- package/dist/fleet-client.d.ts +38 -0
- package/dist/fleet-client.js +44 -9
- package/dist/fleet-lease.d.ts +46 -0
- package/dist/fleet-lease.js +73 -5
- package/dist/hooks/branch-transcript.d.ts +8 -0
- package/dist/hooks/branch-transcript.js +33 -0
- package/dist/hooks/cc-stop-prompt.d.ts +42 -0
- package/dist/hooks/cc-stop-prompt.js +43 -1
- package/dist/hooks/hook-llm.d.ts +15 -0
- package/dist/hooks/hook-llm.js +47 -0
- package/dist/hooks/hook-runner.d.ts +89 -0
- package/dist/hooks/hook-runner.js +327 -18
- package/dist/http/idempotency.d.ts +31 -0
- package/dist/http/idempotency.js +34 -0
- package/dist/http/principal-gate.d.ts +17 -0
- package/dist/http/principal-gate.js +35 -3
- package/dist/http/route-ctx.d.ts +36 -0
- package/dist/http/routes/approvals-assistant.d.ts +25 -0
- package/dist/http/routes/approvals-assistant.js +253 -23
- package/dist/http/routes/attachments.js +22 -4
- package/dist/http/routes/capabilities.d.ts +7 -0
- package/dist/http/routes/capabilities.js +202 -1
- package/dist/http/routes/fleet.d.ts +6 -0
- package/dist/http/routes/fleet.js +94 -5
- package/dist/http/routes/images.d.ts +17 -0
- package/dist/http/routes/images.js +222 -14
- package/dist/http/routes/leader.d.ts +7 -0
- package/dist/http/routes/leader.js +10 -0
- package/dist/http/routes/memory-policy.d.ts +6 -0
- package/dist/http/routes/memory-policy.js +38 -6
- package/dist/http/routes/notify-wake.d.ts +10 -0
- package/dist/http/routes/notify-wake.js +19 -3
- package/dist/http/routes/observability.d.ts +6 -0
- package/dist/http/routes/observability.js +19 -2
- package/dist/http/routes/runs.d.ts +15 -0
- package/dist/http/routes/runs.js +464 -41
- package/dist/http/routes/session-sync.d.ts +15 -0
- package/dist/http/routes/session-sync.js +229 -19
- package/dist/http/routes/sessions-list.d.ts +6 -0
- package/dist/http/routes/sessions-list.js +35 -4
- package/dist/http/routes/sessions.d.ts +14 -0
- package/dist/http/routes/sessions.js +176 -31
- package/dist/http/routes/side-query.d.ts +8 -0
- package/dist/http/routes/side-query.js +26 -1
- package/dist/http/routes/tasks.d.ts +18 -0
- package/dist/http/routes/tasks.js +501 -38
- package/dist/http/routes/trace-usage.d.ts +10 -0
- package/dist/http/routes/trace-usage.js +66 -14
- package/dist/http/routes/workflows.d.ts +12 -0
- package/dist/http/routes/workflows.js +180 -19
- package/dist/http/run-meta.d.ts +11 -0
- package/dist/http/run-meta.js +6 -0
- package/dist/http/send.d.ts +19 -0
- package/dist/http/send.js +11 -0
- package/dist/http/server.d.ts +255 -0
- package/dist/http/server.js +921 -75
- package/dist/http/sse-log.d.ts +15 -0
- package/dist/http/sse-log.js +16 -0
- package/dist/http/tar.d.ts +9 -0
- package/dist/http/tar.js +21 -5
- package/dist/http/wire-gate.d.ts +9 -0
- package/dist/http/wire-gate.js +9 -0
- package/dist/http/wire-types.d.ts +187 -0
- package/dist/http/workspace-content.d.ts +8 -0
- package/dist/http/workspace-content.js +10 -0
- package/dist/images/bake-validate.d.ts +63 -0
- package/dist/images/bake-validate.js +71 -1
- package/dist/images/manifest.d.ts +19 -0
- package/dist/images/manifest.js +6 -1
- package/dist/index.d.ts +1 -5
- package/dist/index.js +10 -5
- package/dist/key-resolver.d.ts +23 -0
- package/dist/key-resolver.js +27 -2
- package/dist/leader/diffout.d.ts +23 -0
- package/dist/leader/diffout.js +8 -1
- package/dist/leader/diffup.d.ts +32 -0
- package/dist/leader/diffup.js +43 -0
- package/dist/leader/endpoint.d.ts +20 -0
- package/dist/leader/endpoint.js +25 -2
- package/dist/leader/fanout.d.ts +60 -0
- package/dist/leader/fanout.js +29 -3
- package/dist/leader/grader-env-factory.d.ts +74 -0
- package/dist/leader/grader-env-factory.js +62 -3
- package/dist/leader/leader.d.ts +97 -0
- package/dist/leader/leader.js +151 -4
- package/dist/leader/merge.d.ts +94 -0
- package/dist/leader/merge.js +112 -9
- package/dist/leader/planner.d.ts +52 -0
- package/dist/leader/planner.js +47 -3
- package/dist/leader/repair-oracle.d.ts +61 -0
- package/dist/leader/repair-oracle.js +59 -1
- package/dist/leader/repair-wire.d.ts +78 -0
- package/dist/leader/repair-wire.js +79 -2
- package/dist/leader/wire.d.ts +79 -0
- package/dist/leader/wire.js +255 -8
- package/dist/lsp/e2b-bridge.d.ts +16 -0
- package/dist/lsp/e2b-bridge.js +32 -4
- package/dist/lsp/e2b-manager.d.ts +10 -0
- package/dist/lsp/e2b-manager.js +18 -2
- package/dist/lsp/lsp-frames.d.ts +12 -0
- package/dist/lsp/lsp-frames.js +12 -0
- package/dist/lsp/manager.d.ts +12 -0
- package/dist/lsp/manager.js +35 -2
- package/dist/lsp/types.d.ts +10 -0
- package/dist/lsp/ws-transport.d.ts +12 -0
- package/dist/lsp/ws-transport.js +53 -5
- package/dist/lsp-evict.d.ts +14 -0
- package/dist/lsp-evict.js +15 -1
- package/dist/main.js +2132 -96
- package/dist/memory-export.d.ts +1 -0
- package/dist/memory-export.js +4 -0
- package/dist/memory-scope.d.ts +49 -0
- package/dist/memory-scope.js +87 -4
- package/dist/memory-sync-client.d.ts +24 -0
- package/dist/memory-sync-client.js +41 -2
- package/dist/memory-sync.d.ts +23 -0
- package/dist/memory-sync.js +70 -1
- package/dist/model-select.d.ts +39 -0
- package/dist/model-select.js +42 -1
- package/dist/observability/cost-quota.d.ts +20 -0
- package/dist/observability/cost-quota.js +4 -0
- package/dist/observability/cost-taxonomy.d.ts +45 -0
- package/dist/observability/cost-taxonomy.js +34 -0
- package/dist/observability/logger.d.ts +1 -0
- package/dist/observability/logger.js +6 -0
- package/dist/observability/metrics.d.ts +13 -0
- package/dist/observability/metrics.js +63 -0
- package/dist/observability/otel-exporter.d.ts +9 -0
- package/dist/observability/otel-exporter.js +11 -3
- package/dist/observability/principal-context.d.ts +9 -0
- package/dist/observability/principal-context.js +9 -0
- package/dist/observability/prompt-manifest.d.ts +37 -0
- package/dist/observability/prompt-manifest.js +35 -1
- package/dist/observability/rate-limit.d.ts +10 -0
- package/dist/observability/rate-limit.js +1 -0
- package/dist/observability/tool-trace.d.ts +24 -0
- package/dist/observability/tool-trace.js +41 -1
- package/dist/orchestration/hardened-vm-runner.d.ts +107 -0
- package/dist/orchestration/hardened-vm-runner.js +119 -4
- package/dist/orchestration/hardened-vm-worker-runner.d.ts +14 -0
- package/dist/orchestration/hardened-vm-worker-runner.js +26 -1
- package/dist/orchestration/hardened-vm-worker.js +27 -0
- package/dist/orchestration/subagent-steer.d.ts +44 -0
- package/dist/orchestration/subagent-steer.js +45 -1
- package/dist/orchestration/workflow-agent-steer.d.ts +54 -0
- package/dist/orchestration/workflow-agent-steer.js +80 -1
- package/dist/orchestration/workflow-completion-inbox.d.ts +189 -2
- package/dist/orchestration/workflow-completion-inbox.js +283 -32
- package/dist/orchestration/workflow-notify-journal.d.ts +132 -1
- package/dist/orchestration/workflow-notify-journal.js +187 -13
- package/dist/parked-decide.d.ts +61 -0
- package/dist/parked-decide.js +58 -1
- package/dist/per-task-image.d.ts +54 -0
- package/dist/per-task-image.js +41 -0
- package/dist/plan-cache-probe.d.ts +35 -0
- package/dist/plan-cache-probe.js +18 -3
- package/dist/plugins/approval-exemption-store.d.ts +7 -0
- package/dist/plugins/approval-exemption-store.js +28 -2
- package/dist/plugins/approval-store-sql.d.ts +64 -0
- package/dist/plugins/approval-store-sql.js +32 -0
- package/dist/plugins/background-agent-store-sql.d.ts +55 -0
- package/dist/plugins/background-agent-store-sql.js +91 -4
- package/dist/plugins/background-shell-support.d.ts +98 -0
- package/dist/plugins/background-shell-support.js +117 -13
- package/dist/plugins/blob-backend.d.ts +104 -0
- package/dist/plugins/blob-backend.js +163 -6
- package/dist/plugins/breaker-state-sql.d.ts +65 -1
- package/dist/plugins/breaker-state-sql.js +46 -8
- package/dist/plugins/caching-session-store.d.ts +42 -0
- package/dist/plugins/caching-session-store.js +63 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +179 -0
- package/dist/plugins/checkpoint-store-sql.js +239 -11
- package/dist/plugins/file-outcome-sink.d.ts +4 -0
- package/dist/plugins/file-outcome-sink.js +9 -0
- package/dist/plugins/file-resume-anchor-store.d.ts +20 -0
- package/dist/plugins/file-resume-anchor-store.js +43 -4
- package/dist/plugins/file-run-store.d.ts +90 -0
- package/dist/plugins/file-run-store.js +145 -13
- package/dist/plugins/file-snapshot-store-sql.d.ts +133 -1
- package/dist/plugins/file-snapshot-store-sql.js +178 -10
- package/dist/plugins/file-workflow-journal-store.d.ts +10 -0
- package/dist/plugins/file-workflow-journal-store.js +10 -0
- package/dist/plugins/fork-routing-session-store.d.ts +47 -0
- package/dist/plugins/fork-routing-session-store.js +51 -3
- package/dist/plugins/host-platform.d.ts +69 -0
- package/dist/plugins/host-platform.js +81 -2
- package/dist/plugins/image-bake-store-sql.d.ts +136 -1
- package/dist/plugins/image-bake-store-sql.js +241 -7
- package/dist/plugins/image-index-sql.d.ts +46 -0
- package/dist/plugins/image-index-sql.js +113 -4
- package/dist/plugins/k8s-bg-scripts.d.ts +34 -0
- package/dist/plugins/k8s-bg-scripts.js +88 -4
- package/dist/plugins/k8s-exec-protocol.d.ts +33 -0
- package/dist/plugins/k8s-exec-protocol.js +41 -0
- package/dist/plugins/local-checkpoint-store.d.ts +40 -0
- package/dist/plugins/local-checkpoint-store.js +86 -7
- package/dist/plugins/local-session-store.d.ts +157 -0
- package/dist/plugins/local-session-store.js +289 -34
- package/dist/plugins/local-task-attachment-store.js +14 -2
- package/dist/plugins/mailbox-store-sql.d.ts +38 -0
- package/dist/plugins/mailbox-store-sql.js +39 -6
- package/dist/plugins/memory-engine-pg.d.ts +30 -0
- package/dist/plugins/memory-engine-pg.js +122 -4
- package/dist/plugins/memory-engine-tidb.d.ts +26 -0
- package/dist/plugins/memory-engine-tidb.js +115 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +5 -0
- package/dist/plugins/memory-engine-vector-util.js +5 -0
- package/dist/plugins/memory-resume-anchor-store.d.ts +8 -0
- package/dist/plugins/memory-resume-anchor-store.js +17 -0
- package/dist/plugins/memory-run-store.d.ts +63 -0
- package/dist/plugins/memory-run-store.js +73 -8
- package/dist/plugins/memory-session-policy-store.d.ts +17 -0
- package/dist/plugins/memory-session-policy-store.js +17 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +42 -0
- package/dist/plugins/memory-sync-store-pg.js +45 -2
- package/dist/plugins/memory-sync-store-tidb.d.ts +9 -0
- package/dist/plugins/memory-sync-store-tidb.js +31 -1
- package/dist/plugins/outcome-ledger-sql.d.ts +63 -0
- package/dist/plugins/outcome-ledger-sql.js +97 -3
- package/dist/plugins/pg-approval-store.d.ts +7 -0
- package/dist/plugins/pg-approval-store.js +7 -0
- package/dist/plugins/pg-breaker-state.d.ts +6 -0
- package/dist/plugins/pg-breaker-state.js +6 -0
- package/dist/plugins/pg-checkpoint-store.d.ts +8 -0
- package/dist/plugins/pg-checkpoint-store.js +8 -0
- package/dist/plugins/pg-cost-quota.d.ts +17 -0
- package/dist/plugins/pg-cost-quota.js +6 -0
- package/dist/plugins/pg-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/pg-file-snapshot-store.js +6 -0
- package/dist/plugins/pg-image-bake.d.ts +9 -0
- package/dist/plugins/pg-image-bake.js +9 -0
- package/dist/plugins/pg-image-index.d.ts +9 -0
- package/dist/plugins/pg-image-index.js +9 -0
- package/dist/plugins/pg-outcome-ledger.d.ts +9 -0
- package/dist/plugins/pg-outcome-ledger.js +9 -0
- package/dist/plugins/pg-pool.d.ts +50 -0
- package/dist/plugins/pg-pool.js +78 -0
- package/dist/plugins/pg-query.d.ts +11 -0
- package/dist/plugins/pg-rate-limiter.d.ts +23 -0
- package/dist/plugins/pg-rate-limiter.js +12 -2
- package/dist/plugins/pg-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/pg-resume-anchor-store.js +5 -0
- package/dist/plugins/pg-run-store.d.ts +7 -0
- package/dist/plugins/pg-run-store.js +7 -0
- package/dist/plugins/pg-safe-json.d.ts +13 -0
- package/dist/plugins/pg-safe-json.js +40 -4
- package/dist/plugins/pg-session-policy-store.d.ts +5 -0
- package/dist/plugins/pg-session-policy-store.js +5 -0
- package/dist/plugins/pg-session-storage.d.ts +72 -0
- package/dist/plugins/pg-session-storage.js +152 -20
- package/dist/plugins/pg-session-store.d.ts +10 -0
- package/dist/plugins/pg-session-store.js +10 -0
- package/dist/plugins/pg-tool-result-store.d.ts +7 -0
- package/dist/plugins/pg-tool-result-store.js +7 -0
- package/dist/plugins/pg-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-journal-store.js +7 -0
- package/dist/plugins/pg-workflow-run-store.d.ts +7 -0
- package/dist/plugins/pg-workflow-run-store.js +7 -0
- package/dist/plugins/posix-shell-fs.d.ts +3 -0
- package/dist/plugins/posix-shell-fs.js +31 -1
- package/dist/plugins/remote-env-adb.d.ts +25 -0
- package/dist/plugins/remote-env-adb.js +95 -12
- package/dist/plugins/remote-env-e2b.d.ts +146 -0
- package/dist/plugins/remote-env-e2b.js +371 -40
- package/dist/plugins/remote-env-file-error.d.ts +34 -0
- package/dist/plugins/remote-env-file-error.js +37 -0
- package/dist/plugins/remote-env-host.d.ts +151 -0
- package/dist/plugins/remote-env-host.js +468 -70
- package/dist/plugins/remote-env-k8s.d.ts +135 -0
- package/dist/plugins/remote-env-k8s.js +318 -32
- package/dist/plugins/remote-env-local-docker.d.ts +65 -0
- package/dist/plugins/remote-env-local-docker.js +149 -24
- package/dist/plugins/remote-env-ssh.d.ts +35 -0
- package/dist/plugins/remote-env-ssh.js +117 -20
- package/dist/plugins/remote-scratchpad.d.ts +38 -0
- package/dist/plugins/remote-scratchpad.js +32 -2
- package/dist/plugins/remote-shell.d.ts +22 -0
- package/dist/plugins/remote-shell.js +27 -1
- package/dist/plugins/resume-anchor-store-sql.d.ts +28 -0
- package/dist/plugins/resume-anchor-store-sql.js +11 -0
- package/dist/plugins/roster-store-sql.d.ts +27 -0
- package/dist/plugins/roster-store-sql.js +54 -0
- package/dist/plugins/run-store-sql.d.ts +194 -0
- package/dist/plugins/run-store-sql.js +210 -9
- package/dist/plugins/s3-presign.d.ts +20 -0
- package/dist/plugins/s3-presign.js +49 -2
- package/dist/plugins/scheduler-support.d.ts +12 -0
- package/dist/plugins/scheduler-support.js +75 -3
- package/dist/plugins/send-file-ledger.d.ts +15 -0
- package/dist/plugins/send-file-ledger.js +53 -4
- package/dist/plugins/send-user-file.d.ts +77 -0
- package/dist/plugins/send-user-file.js +92 -4
- package/dist/plugins/session-policy-store-sql.d.ts +15 -0
- package/dist/plugins/session-policy-store-sql.js +75 -3
- package/dist/plugins/session-store.d.ts +45 -0
- package/dist/plugins/session-store.js +51 -0
- package/dist/plugins/sql-driver.d.ts +77 -0
- package/dist/plugins/sql-driver.js +4 -0
- package/dist/plugins/sql-escape.d.ts +8 -0
- package/dist/plugins/sql-escape.js +8 -0
- package/dist/plugins/sql-row-helpers.d.ts +25 -0
- package/dist/plugins/sql-row-helpers.js +25 -0
- package/dist/plugins/store-backend.d.ts +103 -0
- package/dist/plugins/store-backend.js +120 -27
- package/dist/plugins/store-contracts.d.ts +62 -0
- package/dist/plugins/store-contracts.js +3 -0
- package/dist/plugins/task-attachment-store.d.ts +60 -0
- package/dist/plugins/task-attachment-store.js +49 -3
- package/dist/plugins/task-list-store-sql.d.ts +25 -0
- package/dist/plugins/task-list-store-sql.js +21 -0
- package/dist/plugins/tidb-approval-store.d.ts +6 -0
- package/dist/plugins/tidb-approval-store.js +6 -0
- package/dist/plugins/tidb-breaker-state.d.ts +5 -0
- package/dist/plugins/tidb-breaker-state.js +5 -0
- package/dist/plugins/tidb-checkpoint-store.d.ts +7 -0
- package/dist/plugins/tidb-checkpoint-store.js +7 -0
- package/dist/plugins/tidb-cost-quota.d.ts +6 -0
- package/dist/plugins/tidb-cost-quota.js +1 -0
- package/dist/plugins/tidb-file-snapshot-store.d.ts +6 -0
- package/dist/plugins/tidb-file-snapshot-store.js +6 -0
- package/dist/plugins/tidb-image-bake.d.ts +8 -0
- package/dist/plugins/tidb-image-bake.js +8 -0
- package/dist/plugins/tidb-image-index.d.ts +8 -0
- package/dist/plugins/tidb-image-index.js +8 -0
- package/dist/plugins/tidb-outcome-ledger.d.ts +10 -0
- package/dist/plugins/tidb-outcome-ledger.js +10 -0
- package/dist/plugins/tidb-pool.d.ts +56 -0
- package/dist/plugins/tidb-pool.js +181 -1
- package/dist/plugins/tidb-rate-limiter.d.ts +11 -0
- package/dist/plugins/tidb-rate-limiter.js +5 -2
- package/dist/plugins/tidb-resume-anchor-store.d.ts +5 -0
- package/dist/plugins/tidb-resume-anchor-store.js +5 -0
- package/dist/plugins/tidb-run-store.d.ts +7 -0
- package/dist/plugins/tidb-run-store.js +7 -0
- package/dist/plugins/tidb-session-policy-store.d.ts +5 -0
- package/dist/plugins/tidb-session-policy-store.js +5 -0
- package/dist/plugins/tidb-session-storage.d.ts +43 -0
- package/dist/plugins/tidb-session-storage.js +62 -3
- package/dist/plugins/tidb-session-store.d.ts +142 -0
- package/dist/plugins/tidb-session-store.js +264 -16
- package/dist/plugins/tidb-tool-result-store.d.ts +5 -0
- package/dist/plugins/tidb-tool-result-store.js +7 -0
- package/dist/plugins/tidb-workflow-journal-store.d.ts +7 -0
- package/dist/plugins/tidb-workflow-journal-store.js +7 -0
- package/dist/plugins/tidb-workflow-run-store.d.ts +8 -0
- package/dist/plugins/tidb-workflow-run-store.js +8 -0
- package/dist/plugins/tool-result-store-sql.d.ts +58 -0
- package/dist/plugins/tool-result-store-sql.js +38 -2
- package/dist/plugins/web-search.d.ts +35 -0
- package/dist/plugins/web-search.js +45 -9
- package/dist/plugins/workflow-journal-limits.d.ts +10 -0
- package/dist/plugins/workflow-journal-limits.js +10 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +78 -0
- package/dist/plugins/workflow-journal-store-sql.js +59 -3
- package/dist/plugins/workflow-run-store-sql.d.ts +84 -0
- package/dist/plugins/workflow-run-store-sql.js +84 -6
- package/dist/plugins/worktree-isolation.d.ts +45 -0
- package/dist/plugins/worktree-isolation.js +105 -6
- package/dist/plugins/write-behind-counter.d.ts +56 -1
- package/dist/plugins/write-behind-counter.js +53 -14
- package/dist/principal-jwt.d.ts +24 -0
- package/dist/principal-jwt.js +60 -5
- package/dist/project-memory.d.ts +22 -0
- package/dist/project-memory.js +144 -15
- package/dist/prompts-domain-validate.d.ts +28 -0
- package/dist/prompts-domain-validate.js +42 -1
- package/dist/question.d.ts +33 -0
- package/dist/question.js +69 -2
- package/dist/resource-suspend.d.ts +21 -0
- package/dist/resource-suspend.js +16 -0
- package/dist/router/route-orchestration.d.ts +78 -0
- package/dist/router/route-orchestration.js +77 -0
- package/dist/run-local.d.ts +32 -0
- package/dist/run-local.js +172 -8
- package/dist/runs.d.ts +189 -4
- package/dist/runs.js +465 -24
- package/dist/runtime-caps-resolver.d.ts +106 -0
- package/dist/runtime-caps-resolver.js +89 -4
- package/dist/runtime-governance.d.ts +84 -0
- package/dist/runtime-governance.js +127 -1
- package/dist/sandbox-pkg-source.d.ts +23 -0
- package/dist/sandbox-pkg-source.js +37 -0
- package/dist/sealed-key.d.ts +54 -0
- package/dist/sealed-key.js +68 -3
- package/dist/security.d.ts +108 -0
- package/dist/security.js +145 -6
- package/dist/sema-registry.d.ts +21 -0
- package/dist/sema-registry.js +21 -0
- package/dist/session-leaf-bus.d.ts +31 -0
- package/dist/session-leaf-bus.js +32 -0
- package/dist/session-sync-content.d.ts +46 -0
- package/dist/session-sync-content.js +47 -1
- package/dist/session-sync-kernel.d.ts +89 -0
- package/dist/session-sync-kernel.js +48 -3
- package/dist/session-sync.d.ts +125 -0
- package/dist/session-sync.js +181 -8
- package/dist/session-titler.d.ts +26 -0
- package/dist/session-titler.js +41 -7
- package/dist/session-watch.d.ts +53 -1
- package/dist/session-watch.js +73 -9
- package/dist/sighup-idle.d.ts +30 -0
- package/dist/sighup-idle.js +12 -1
- package/dist/spec-fields.d.ts +91 -3
- package/dist/spec-fields.js +116 -4
- package/dist/task-cwd.d.ts +50 -0
- package/dist/task-cwd.js +68 -1
- package/dist/task-mcp.d.ts +47 -0
- package/dist/task-mcp.js +29 -0
- package/dist/task-settings.d.ts +175 -0
- package/dist/task-settings.js +226 -11
- package/dist/task-workflow.d.ts +67 -0
- package/dist/task-workflow.js +75 -8
- package/dist/tool-approval.d.ts +91 -0
- package/dist/tool-approval.js +215 -0
- package/dist/trace/artifacts.d.ts +9 -0
- package/dist/trace/artifacts.js +30 -5
- package/dist/trace/core-keyset-guard.d.ts +18 -0
- package/dist/trace/ledger-sink.d.ts +43 -0
- package/dist/trace/ledger-sink.js +64 -5
- package/dist/trace/project.d.ts +133 -0
- package/dist/trace/project.js +189 -2
- package/dist/trace/redact.d.ts +19 -0
- package/dist/trace/redact.js +66 -9
- package/dist/usage-analytics.d.ts +26 -0
- package/dist/usage-analytics.js +26 -3
- package/package.json +1 -1
|
@@ -1,13 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/158 A9 域模块 —— sandbox-image-pool 控制面(IMAGE-API-DESIGN.md)。
|
|
3
|
+
*
|
|
4
|
+
* 路由:P2 bake 作业面 `/v1/images/bakes*`(提交/列表/详情/事件 SSE/取消/claim/ingest)与 P1 镜像索引
|
|
5
|
+
* `/v1/images*`(digests/profile/select)。**bakes 段必须排在 images 段之前**——P1 的单段 profile 正则会把
|
|
6
|
+
* `bakes` 当成 profile 吞掉(注释原文见路由体内),两段的先后由本文件内部顺序保住。
|
|
7
|
+
*
|
|
8
|
+
* 单域独占状态:`bakeSubmitLimiter`(per-principal 提交限速),按 server 实例存活于 `ctx.local.images`。
|
|
9
|
+
* 本域另收编了原先散在 `server.ts` 模块级的镜像面私产:路由正则、bake 视图投影、claim 应答、ingest 行摄取、
|
|
10
|
+
* bake SSE 包装、以及两个导出的状态/错误码映射(`server.ts` 保留 re-export,导出面不变)。
|
|
11
|
+
*/
|
|
1
12
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
13
|
import { RateLimiter } from "../../observability/rate-limit.js";
|
|
3
14
|
import type { BakeState, BakeErrorCode } from "../../plugins/store-contracts.js";
|
|
4
15
|
import type { FlatServiceDeps } from "../server.js";
|
|
5
16
|
import type { RouteCtx } from "../route-ctx.js";
|
|
17
|
+
/** 单域独占状态(lens1 §C2 的 12 条之一)。每个 server 实例一份——模块级会把限速状态跨实例串味
|
|
18
|
+
* (一个进程里建几十个 server 的测试会互相污染)。 */
|
|
6
19
|
export interface ImagesLocal {
|
|
7
20
|
bakeSubmitLimiter: RateLimiter;
|
|
8
21
|
}
|
|
9
22
|
export declare function createImagesLocal(deps: FlatServiceDeps): ImagesLocal;
|
|
23
|
+
/** build.sh's 8-value `state` → the COARSE 4-value lifecycle the SSE reader polls for terminality (§P2.3). Every
|
|
24
|
+
* NON-terminal state maps to `running` (a PUSHING/VERIFYING state must NOT truncate the stream before `done`). */
|
|
10
25
|
export declare function coarseStatusForState(state: BakeState): "running" | "done" | "failed";
|
|
26
|
+
/** build.sh's structured exit code → the center band-composer UI's terminal `errorCode` (§P2.14 #2). null for an
|
|
27
|
+
* uncategorized failure (then `error` free-text + `exitCode` drive the UI). 10=dup-tag, 124=timeout, 507=disk. */
|
|
11
28
|
export declare function errorCodeForExit(exitCode: number | null | undefined, cancelled: boolean): BakeErrorCode;
|
|
12
29
|
export declare const IMAGE_DIGEST_RE: RegExp;
|
|
13
30
|
export declare const IMAGE_PROFILE_RE: RegExp;
|
|
@@ -8,8 +8,14 @@ import { sendJson, sendError } from "../send.js";
|
|
|
8
8
|
import { headerStr, safeEqual, gatedPrincipal, explicitOperatorOk, explicitOperator } from "../principal-gate.js";
|
|
9
9
|
import { scopedIdempotencyKey } from "../idempotency.js";
|
|
10
10
|
export function createImagesLocal(deps) {
|
|
11
|
+
// Per-principal bake-submission rate guard (IMAGE-API-DESIGN.md §P2.4c — fills the register DoS gap the images
|
|
12
|
+
// block never guarded). Separate from the global rateLimiter (which may be off): an unbounded POST /bakes flood
|
|
13
|
+
// is real resource-exhaustion churn even though the single-flight lease caps concurrent BUILDS at 1. Off when
|
|
14
|
+
// submitRateMax<=0. Swept lazily by `check`'s window roll-over (no timer needed for a small operator set).
|
|
11
15
|
return { bakeSubmitLimiter: new RateLimiter(deps.config.imageBakes?.submitRateMax ?? 0, (deps.config.imageBakes?.submitRateWindowSec ?? 60) * 1000) };
|
|
12
16
|
}
|
|
17
|
+
/** build.sh's 8-value `state` → the COARSE 4-value lifecycle the SSE reader polls for terminality (§P2.3). Every
|
|
18
|
+
* NON-terminal state maps to `running` (a PUSHING/VERIFYING state must NOT truncate the stream before `done`). */
|
|
13
19
|
export function coarseStatusForState(state) {
|
|
14
20
|
if (state === "COMPLETE")
|
|
15
21
|
return "done";
|
|
@@ -17,26 +23,40 @@ export function coarseStatusForState(state) {
|
|
|
17
23
|
return "failed";
|
|
18
24
|
return "running";
|
|
19
25
|
}
|
|
26
|
+
/** build.sh's structured exit code → the center band-composer UI's terminal `errorCode` (§P2.14 #2). null for an
|
|
27
|
+
* uncategorized failure (then `error` free-text + `exitCode` drive the UI). 10=dup-tag, 124=timeout, 507=disk. */
|
|
20
28
|
export function errorCodeForExit(exitCode, cancelled) {
|
|
21
29
|
if (cancelled)
|
|
22
30
|
return "cancelled";
|
|
23
31
|
switch (exitCode) {
|
|
24
32
|
case 9:
|
|
25
|
-
return "band-conflict";
|
|
33
|
+
return "band-conflict"; // build.sh's generate-time inter-band collision (gen-dockerfile exit 6/7/8 → 9).
|
|
26
34
|
case 10:
|
|
27
35
|
return "duplicate";
|
|
28
36
|
case 124:
|
|
29
37
|
return "timeout";
|
|
30
38
|
case 507:
|
|
31
|
-
return "disk";
|
|
39
|
+
return "disk"; // belt: the runner's pre-flight disk guard (runner.ts) exits 507 — categorize it even if the
|
|
40
|
+
// structured line.errorCode were ever absent (M1: line.errorCode is the primary source).
|
|
32
41
|
default:
|
|
33
42
|
return null;
|
|
34
43
|
}
|
|
35
44
|
}
|
|
45
|
+
/** The closed set of structured terminal `errorCode`s a runner/build.sh frame may carry (§P2.14 #2). A
|
|
46
|
+
* runner-supplied code is a UI hint only, but it CANNOT reconstruct from the exit code alone (e.g. the disk
|
|
47
|
+
* guard's 507, or a future band-conflict 6/7/8), so ingestBakeLine PREFERS a validated `line.errorCode` over
|
|
48
|
+
* the exit-code recompute. Set-membership keeps it inside the BakeErrorCode union (never trust a free-text). */
|
|
36
49
|
const RUNNER_ERROR_CODES = new Set(["band-conflict", "duplicate", "disk", "timeout", "cancelled"]);
|
|
50
|
+
/** The index `profile` under which the FROM base (`sandbox-base`) is published — the ONLY profile a bake's
|
|
51
|
+
* `--base-ref` may resolve to (a published code-* profile digest is a built layer, never a valid FROM base).
|
|
52
|
+
* Both the caller-supplied-baseRef allow-list AND the default-base fallback resolve against this profile. */
|
|
37
53
|
const SANDBOX_BASE_PROFILE = "sandbox-base";
|
|
54
|
+
// sandbox-image-pool query routes (IMAGE-API-DESIGN.md §2). `/digests/:digest` is checked BEFORE `/:profile`
|
|
55
|
+
// (the single-segment profile RE would otherwise swallow "digests"); both are GET-only + visibility-scoped.
|
|
38
56
|
export const IMAGE_DIGEST_RE = /^\/v1\/images\/digests\/([^/]+)$/;
|
|
39
57
|
export const IMAGE_PROFILE_RE = /^\/v1\/images\/([^/]+)$/;
|
|
58
|
+
// sandbox-image-pool BAKE routes (IMAGE-API-DESIGN.md §P2.1). No trailing-slash variant (`/v1/images/bakes/` →
|
|
59
|
+
// 404, matching the live `/v1/images/` behavior). The `:bakeId` group + sub-action are both-or-neither.
|
|
40
60
|
export const BAKE_ID_RE = /^\/v1\/images\/bakes\/([^/]+)$/;
|
|
41
61
|
export const BAKE_EVENTS_RE = /^\/v1\/images\/bakes\/([^/]+)\/events$/;
|
|
42
62
|
export const BAKE_CANCEL_RE = /^\/v1\/images\/bakes\/([^/]+)\/cancel$/;
|
|
@@ -47,24 +67,40 @@ export async function handleImages(req, res, url, ctx) {
|
|
|
47
67
|
await handleImagesBody(req, res, url, ctx, miss);
|
|
48
68
|
return !miss.fell;
|
|
49
69
|
}
|
|
70
|
+
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
71
|
+
* 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
|
|
72
|
+
* 跨域的东西(deps / 助手 / 单域状态 / 每请求量)一律在首行解构出来,于是下面每一行都逐字不变。 */
|
|
50
73
|
async function handleImagesBody(req, res, url, ctx, miss) {
|
|
51
74
|
const { deps } = ctx;
|
|
52
75
|
const { readJson } = ctx.helpers;
|
|
53
76
|
const { source } = ctx.req;
|
|
54
77
|
const { bakeSubmitLimiter } = ctx.local.images;
|
|
78
|
+
// ── sandbox-image-pool BAKE control plane (IMAGE-API-DESIGN.md §P2). MUST precede the P1 images block: the P1
|
|
79
|
+
// profile RE `/^\/v1\/images\/([^/]+)$/` would otherwise swallow `/v1/images/bakes` as profile="bakes" (404),
|
|
80
|
+
// and a two-segment `/v1/images/bakes/:id` would fall through P1 with NO response (hung socket). image-api is
|
|
81
|
+
// the UNPRIVILEGED controller: it never bakes — it owns the durable job row/event log/lease, the operator gate,
|
|
82
|
+
// and the input whitelist; the privileged build runs on the bake-runner (build-host) over the internal claim/ingest.
|
|
55
83
|
if (deps.imageBakes && url.startsWith("/v1/images/bakes")) {
|
|
56
84
|
const bakes = deps.imageBakes;
|
|
57
|
-
const idx = deps.imageIndex;
|
|
58
|
-
|
|
85
|
+
const idx = deps.imageIndex; // the auto-register target (wired together with imageBakes in main.ts)
|
|
86
|
+
// Identity resolved EXACTLY as P1 (direct-door JWT-without-cnf.bnd, else principalFrom).
|
|
87
|
+
const principal = gatedPrincipal(req, deps.config); // direct-door safe — identity-only proof; single source of truth (see gatedPrincipal)
|
|
59
88
|
const operators = deps.config.operatorPrincipals;
|
|
60
89
|
const explicitOperator = explicitOperatorOk(principal, operators);
|
|
61
90
|
const cfg = deps.config.imageBakes;
|
|
91
|
+
// 🔴 H1: the bake-runner identity is DERIVED from its bearer CREDENTIAL (the token-derived `source`,
|
|
92
|
+
// resolved above from SERVICE_AUTH_TOKENS), NOT a self-asserted x-agent-principal/JWT the runner never
|
|
93
|
+
// sends. `source` is independent of the directDoorActive principal ternary, so this works identically in
|
|
94
|
+
// direct-door AND plain mode. The config boot invariant guarantees a runner-credential→runnerPrincipal
|
|
95
|
+
// mapping exists when bakes are enabled.
|
|
62
96
|
const isRunner = source !== undefined && source !== null && source === cfg.runnerPrincipal;
|
|
97
|
+
// POST /v1/images/bakes — operator-only submit. EXPLICIT operator gate (NOT the bare isOperator; §P2.4a).
|
|
63
98
|
if (req.method === "POST" && url === "/v1/images/bakes") {
|
|
64
99
|
if (!explicitOperator) {
|
|
65
100
|
sendError(res, 403, "auth.operator_only", "operator only");
|
|
66
101
|
return;
|
|
67
102
|
}
|
|
103
|
+
// Per-principal submission rate guard (§P2.4c) — caps queue-churn even though the lease caps live builds at 1.
|
|
68
104
|
const rl = bakeSubmitLimiter.check(`bake:${principal}`);
|
|
69
105
|
if (!rl.allowed) {
|
|
70
106
|
res.setHeader("retry-after", String(rl.retryAfterSec));
|
|
@@ -72,11 +108,22 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
72
108
|
return;
|
|
73
109
|
}
|
|
74
110
|
const body = (await readJson(req));
|
|
111
|
+
// Input WHITELIST — set-membership BEFORE any argv is built (§P2.4d). Off-list → 400/501, never coerced.
|
|
75
112
|
const v = validateBake(body);
|
|
113
|
+
// B7:errorCode 恒在——band 冲突保留既有 `band-conflict` 机器码(1.x 契约,勿改);其余 400=字段白名单
|
|
114
|
+
// 拒收、501=该形 bake 尚未开面(custom/tenant P4)。`error` 文案与键序逐字不变。
|
|
76
115
|
if (!v.ok) {
|
|
77
116
|
sendError(res, v.status, v.conflictBands ? "band-conflict" : v.status === 501 ? "capability.unavailable" : "request.field_invalid", v.error, { ...(v.conflictBands ? { conflictBands: v.conflictBands } : {}) });
|
|
78
117
|
return;
|
|
79
118
|
}
|
|
119
|
+
// baseRef allow-list (§P2.4c): a caller CANNOT pass an arbitrary FROM. A present baseRef must be a KNOWN
|
|
120
|
+
// PUBLISHED `sandbox-base` digest the index has seen (a syntactically-valid attacker digest is still
|
|
121
|
+
// arbitrary; a published code-* PROFILE digest is also not a valid FROM base — the profile must be
|
|
122
|
+
// sandbox-base). Absent ⇒ the pinned default base = `SANDBOX_BASE_REF` OR the index's latest published
|
|
123
|
+
// sandbox-base (design §P2.4c "SANDBOX_BASE_REF / the index's latest published base"). The shape was
|
|
124
|
+
// already checked in validateBake. The digest the server resolves is the SAME for push:true and
|
|
125
|
+
// push:false — a node-local (no-push) build's host docker resolves a sandbox-base@sha256 FROM against
|
|
126
|
+
// the locally-present base image by its config digest (verified live), so one digest serves both paths.
|
|
80
127
|
let effectiveBaseRef;
|
|
81
128
|
if (v.baseRef) {
|
|
82
129
|
const baseEntry = idx ? await idx.getByDigest(v.baseRef) : null;
|
|
@@ -87,15 +134,25 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
87
134
|
effectiveBaseRef = v.baseRef;
|
|
88
135
|
}
|
|
89
136
|
else {
|
|
137
|
+
// SANDBOX_BASE_REF pin first; else the index's latest published sandbox-base (operator viewer — the base
|
|
138
|
+
// pool is server-internal, never tenant-scoped). The server still WHOLLY chooses the base (the caller
|
|
139
|
+
// never influences it), so the anti-RCE property holds.
|
|
90
140
|
effectiveBaseRef = cfg.defaultBaseRef ?? (idx ? (await idx.latestPublished(SANDBOX_BASE_PROFILE, { operator: true }))?.digest ?? null : null);
|
|
91
141
|
}
|
|
142
|
+
// A real (non-dryRun) build MUST FROM a concrete base — build.sh dies (exit 3) on a missing --base-ref.
|
|
143
|
+
// Fail CLOSED here with an honest config error rather than queue an argv that can never build (a dryRun is
|
|
144
|
+
// resolve-only and legitimately previews with build.sh's floating base, so it's exempt).
|
|
92
145
|
if (!v.dryRun && !effectiveBaseRef) {
|
|
93
146
|
sendError(res, 503, "state.no_sandbox_base", "no sandbox-base available to build from (set SANDBOX_BASE_REF or publish a sandbox-base image)");
|
|
94
147
|
return;
|
|
95
148
|
}
|
|
96
149
|
const argv = buildBakeArgv(v, { baseRef: effectiveBaseRef, cacheBase: cfg.cacheBase });
|
|
97
150
|
const rawIdem = headerStr(req.headers["idempotency-key"]) ?? (typeof body.idempotencyKey === "string" ? body.idempotencyKey : undefined);
|
|
151
|
+
// Durable submit-idempotency: scope the key to the submitting principal (BL-3 parity) so a replay is the
|
|
152
|
+
// SAME caller only; the store's `idem_key` UNIQUE is the durable dedupe.
|
|
98
153
|
const idemKey = rawIdem ? scopedIdempotencyKey(rawIdem, source, principal) ?? null : null;
|
|
154
|
+
// §P2.1/§P2.7 single-flight at SUBMIT (concurrency 1; no auto-queue). A `dryRun` is resolve-only — it never
|
|
155
|
+
// takes the lease, so it bypasses the busy guard entirely (parallel-safe) and uses the plain createBake.
|
|
99
156
|
const createInput = {
|
|
100
157
|
profile: v.profile,
|
|
101
158
|
bands: v.bands,
|
|
@@ -108,11 +165,18 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
108
165
|
requestedBy: principal ?? null,
|
|
109
166
|
};
|
|
110
167
|
if (!v.dryRun) {
|
|
168
|
+
// (a) an IDENTICAL-argv in-flight bake ⇒ attach (idempotent re-submit, 202) — no second build.
|
|
111
169
|
const sameArgv = await bakes.findActiveByArgv(argv);
|
|
112
170
|
if (sameArgv) {
|
|
113
171
|
sendJson(res, 202, { bakeId: sameArgv.bakeId, eventsUrl: `/v1/images/bakes/${sameArgv.bakeId}/events`, status: sameArgv.status, state: sameArgv.state });
|
|
114
172
|
return;
|
|
115
173
|
}
|
|
174
|
+
// (b) M4: ATOMIC admission — the busy-check and the create are ONE DB statement (`INSERT … SELECT …
|
|
175
|
+
// WHERE NOT EXISTS (a non-dryRun in-flight bake)`), so two concurrent keyless POSTs can never both queue
|
|
176
|
+
// (the old findActiveAny()→createBake() check-then-act interleaved). On `created:false` a DIFFERENT bake
|
|
177
|
+
// holds (or will hold) the single-flight lease ⇒ re-read the active bake. Do NOT silently queue a 45-min
|
|
178
|
+
// build; surface the active bake + its events URL so the UI attaches to the live one (or attach if a
|
|
179
|
+
// concurrent identical-argv submit won the race in the gap).
|
|
116
180
|
const r = await bakes.createBakeIfIdle(createInput);
|
|
117
181
|
if (!r.created) {
|
|
118
182
|
const sameNow = await bakes.findActiveByArgv(argv);
|
|
@@ -131,6 +195,7 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
131
195
|
sendJson(res, 202, { bakeId: bake.bakeId, eventsUrl: `/v1/images/bakes/${bake.bakeId}/events`, status: bake.status, state: bake.state });
|
|
132
196
|
return;
|
|
133
197
|
}
|
|
198
|
+
// GET /v1/images/bakes/:bakeId/events — resumable SSE (the live "baking…" view), run-trace-SSE-shaped (§P2.2).
|
|
134
199
|
const evMatch = req.method === "GET" ? BAKE_EVENTS_RE.exec(url) : null;
|
|
135
200
|
if (evMatch) {
|
|
136
201
|
if (!explicitOperator) {
|
|
@@ -146,6 +211,7 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
146
211
|
await streamBakeEvents(req, res, bakes, bakeId, cfg.staleMs);
|
|
147
212
|
return;
|
|
148
213
|
}
|
|
214
|
+
// POST /v1/images/bakes/:bakeId/cancel — cooperative cancel (durable flag; the runner kills the pgid, §P2.11).
|
|
149
215
|
const cancelMatch = req.method === "POST" ? BAKE_CANCEL_RE.exec(url) : null;
|
|
150
216
|
if (cancelMatch) {
|
|
151
217
|
if (!explicitOperator) {
|
|
@@ -158,31 +224,39 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
158
224
|
sendError(res, 404, "not_found.bake", "bake not found");
|
|
159
225
|
return;
|
|
160
226
|
}
|
|
161
|
-
const flagged = await bakes.requestCancel(bakeId);
|
|
227
|
+
const flagged = await bakes.requestCancel(bakeId); // only flags a non-terminal row
|
|
162
228
|
sendJson(res, 202, { bakeId, cancelRequested: true, note: flagged ? "cancel requested — the runner kills the build at the next heartbeat" : "bake already terminal — cancel is a no-op" });
|
|
163
229
|
return;
|
|
164
230
|
}
|
|
231
|
+
// POST /v1/images/bakes/claim — RUNNER-ONLY: long-poll claim (§P2.12). Find the oldest queued bake and claim
|
|
232
|
+
// it in one call (the discovery + the single-flight CAS). Checked BEFORE the per-bake `…/:id/claim` so the
|
|
233
|
+
// bare `claim` is not swallowed as `:bakeId="claim"`. 204 when the queue is empty (the runner idle-polls).
|
|
165
234
|
if (req.method === "POST" && url === "/v1/images/bakes/claim") {
|
|
166
235
|
if (!isRunner) {
|
|
167
236
|
sendError(res, 403, "auth.bake_runner_only", "bake-runner only");
|
|
168
237
|
return;
|
|
169
238
|
}
|
|
170
239
|
const body = (await readJson(req));
|
|
240
|
+
// H1: the runner is credential-authed (no x-agent-principal), so `principal` may be undefined — fall back
|
|
241
|
+
// to the credential-derived `source` (== runnerPrincipal, isRunner guaranteed it) as the runner identity.
|
|
171
242
|
const runnerId = typeof body.runnerId === "string" && body.runnerId.length > 0 ? body.runnerId : (principal ?? source ?? cfg.runnerPrincipal);
|
|
243
|
+
// Bounded find-then-CAS: another runner racing us may win the id we just read, so retry the next queued id
|
|
244
|
+
// a few times before giving up (the queue is normally short + replicas=1, so 1 pass is the common case).
|
|
172
245
|
let claimed = null;
|
|
173
246
|
for (let attempt = 0; attempt < 4 && !claimed; attempt++) {
|
|
174
247
|
const nextId = await bakes.findNextQueuedId();
|
|
175
248
|
if (!nextId)
|
|
176
|
-
break;
|
|
249
|
+
break; // queue empty
|
|
177
250
|
claimed = await bakes.claimBake(nextId, runnerId, cfg.leaseMs);
|
|
178
251
|
}
|
|
179
252
|
if (!claimed) {
|
|
180
253
|
res.writeHead(204).end();
|
|
181
254
|
return;
|
|
182
|
-
}
|
|
255
|
+
} // nothing claimable right now (the runner idle-polls)
|
|
183
256
|
sendJson(res, 200, claimResponse(claimed));
|
|
184
257
|
return;
|
|
185
258
|
}
|
|
259
|
+
// POST /v1/images/bakes/:bakeId/claim — RUNNER-ONLY: single-flight lease of THIS queued bake (§P2.7).
|
|
186
260
|
const claimMatch = req.method === "POST" ? BAKE_CLAIM_RE.exec(url) : null;
|
|
187
261
|
if (claimMatch) {
|
|
188
262
|
if (!isRunner) {
|
|
@@ -194,12 +268,16 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
194
268
|
const runnerId = typeof body.runnerId === "string" && body.runnerId.length > 0 ? body.runnerId : (principal ?? source ?? cfg.runnerPrincipal);
|
|
195
269
|
const claimed = await bakes.claimBake(bakeId, runnerId, cfg.leaseMs);
|
|
196
270
|
if (!claimed) {
|
|
271
|
+
// Lost the single-flight (already claimed, or the pool lease is held by a live build) → 409.
|
|
197
272
|
sendError(res, 409, "conflict.bake_not_claimable", "bake not claimable (already claimed or the build lease is held)");
|
|
198
273
|
return;
|
|
199
274
|
}
|
|
275
|
+
// Return the VETTED argv + the per-bake ingest secret (minted at claim) the runner echoes on ingest.
|
|
200
276
|
sendJson(res, 200, claimResponse(claimed));
|
|
201
277
|
return;
|
|
202
278
|
}
|
|
279
|
+
// POST /v1/images/bakes/:bakeId/ingest — RUNNER-ONLY: append one build.sh line + heartbeat the lease.
|
|
280
|
+
// Maps build.sh `{event}` → an SSE kind + the coarse status; on a COMPLETE `done` triggers auto-register.
|
|
203
281
|
const ingestMatch = req.method === "POST" ? BAKE_INGEST_RE.exec(url) : null;
|
|
204
282
|
if (ingestMatch) {
|
|
205
283
|
if (!isRunner) {
|
|
@@ -212,12 +290,16 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
212
290
|
sendError(res, 404, "not_found.bake", "bake not found");
|
|
213
291
|
return;
|
|
214
292
|
}
|
|
293
|
+
// Per-bake ingest secret (minted at claim) — so a stale/rogue runner can't inject frames into another bake.
|
|
215
294
|
const presented = headerStr(req.headers["x-bake-ingest-secret"]);
|
|
216
295
|
if (!bake.ingestSecret || !presented || !safeEqual(presented, bake.ingestSecret)) {
|
|
217
296
|
sendError(res, 403, "auth.ingest_secret_invalid", "invalid ingest secret");
|
|
218
297
|
return;
|
|
219
298
|
}
|
|
220
299
|
const line = (await readJson(req));
|
|
300
|
+
// A `heartbeat` frame (§P2.7/§P2.11): advance the lease, append NO event, and echo the durable cancel flag +
|
|
301
|
+
// lease validity so the runner kills the build.sh process group at the next tick. A lost lease (the reaper
|
|
302
|
+
// stole it as stale, or a terminal flip released it) → 409 so the runner aborts (it no longer owns the build).
|
|
221
303
|
if (line.event === "heartbeat") {
|
|
222
304
|
const fresh = await bakes.getBake(bakeId);
|
|
223
305
|
if (!fresh || fresh.status !== "running") {
|
|
@@ -232,11 +314,17 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
232
314
|
sendJson(res, 200, { accepted: true, leaseValid: true, cancelRequested: fresh.cancelRequested });
|
|
233
315
|
return;
|
|
234
316
|
}
|
|
317
|
+
// 🔴 H3 (zombie/reaped runner) — PRIMARY guard: a terminal/reaped bake accepts NO frames. Re-read the row
|
|
318
|
+
// (the `bake` fetched above may be stale) and reject 409 for ALL non-heartbeat frames when it is no longer
|
|
319
|
+
// running, mirroring the heartbeat branch. Otherwise a zombie/duplicate runner that still holds a valid
|
|
320
|
+
// ingest_secret could inject `building` index rows + out-of-band SSE frames into a dead bake's stream.
|
|
235
321
|
const fresh = await bakes.getBake(bakeId);
|
|
236
322
|
if (!fresh || fresh.status !== "running") {
|
|
237
323
|
sendJson(res, 409, { accepted: false, leaseValid: false, cancelRequested: fresh?.cancelRequested ?? false, errorCode: "conflict.bake_not_running" });
|
|
238
324
|
return;
|
|
239
325
|
}
|
|
326
|
+
// A real build.sh frame: also advance the lease (the runner heartbeats via its frame cadence too) and echo
|
|
327
|
+
// the cancel flag so a cancel observed between heartbeats still reaches the runner promptly.
|
|
240
328
|
if (fresh.runnerId) {
|
|
241
329
|
await bakes.heartbeatLease(bakeId, fresh.runnerId, cfg.leaseMs).catch(() => undefined);
|
|
242
330
|
}
|
|
@@ -245,6 +333,8 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
245
333
|
sendJson(res, result.status, { ...result.body, cancelRequested: after?.cancelRequested ?? false, leaseValid: true });
|
|
246
334
|
return;
|
|
247
335
|
}
|
|
336
|
+
// GET /v1/images/bakes/:bakeId — poll fallback, `{bake:}` envelope, null-not-omitted (§P2.1). Checked LAST so
|
|
337
|
+
// the `/events|/cancel|/claim|/ingest` sub-actions (two-segment) win first.
|
|
248
338
|
const idMatch = req.method === "GET" ? BAKE_ID_RE.exec(url) : null;
|
|
249
339
|
if (idMatch) {
|
|
250
340
|
if (!explicitOperator) {
|
|
@@ -260,15 +350,25 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
260
350
|
sendJson(res, 200, { bake: bakeView(bake) });
|
|
261
351
|
return;
|
|
262
352
|
}
|
|
353
|
+
// No trailing-slash variant, no other bake route → 404 (parity with `/v1/images/` live behavior).
|
|
263
354
|
sendError(res, 404, "not_found.route", "not found");
|
|
264
355
|
return;
|
|
265
356
|
}
|
|
357
|
+
// ── sandbox-image-pool control API (IMAGE-API-DESIGN.md §2, pool design §8) — P1: query + select. The image
|
|
358
|
+
// INDEX is queried/selected here (bake/publish = P2/P3, later). Visibility is viewer-scoped (Codex A1): a
|
|
359
|
+
// tenant principal sees `public` + its OWN tenant images; an operator sees all. Identity is resolved the same
|
|
360
|
+
// way as the approval reads (direct-door JWT-without-cnf.bnd, else principalFrom); an absent principal still
|
|
361
|
+
// sees the public catalog (anonymous public-pull parity — pool = docker.io/claybobby; SWR deprecated) but no tenant rows.
|
|
266
362
|
if (deps.imageIndex && url.startsWith("/v1/images")) {
|
|
267
363
|
const idx = deps.imageIndex;
|
|
268
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
364
|
+
const principal = gatedPrincipal(req, deps.config); // direct-door safe — identity-only proof; single source of truth (see gatedPrincipal)
|
|
365
|
+
// explicitOperator (NOT isOperator): for image VISIBILITY, an empty OPERATOR_PRINCIPALS must NOT make every
|
|
366
|
+
// caller an operator (that would leak tenant-scoped images). Adversarial-review HIGH — same fix as the P0.5
|
|
367
|
+
// per-task resolve in main.ts. (Approval operator gates below keep isOperator's legacy empty=all boundary.)
|
|
269
368
|
const operator = explicitOperator(principal, deps.config.operatorPrincipals);
|
|
270
369
|
const viewer = { operator, tenantId: principal ?? null };
|
|
271
370
|
const q = new URL(req.url ?? "", "http://x").searchParams;
|
|
371
|
+
// GET /v1/images — the paginated catalog. ?profile=&status=&capability=&latest=true&limit=&cursor=
|
|
272
372
|
if (req.method === "GET" && url === "/v1/images") {
|
|
273
373
|
const limRaw = q.get("limit");
|
|
274
374
|
const { entries, nextCursor } = await idx.list({
|
|
@@ -283,6 +383,9 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
283
383
|
sendJson(res, 200, { images: entries, nextCursor });
|
|
284
384
|
return;
|
|
285
385
|
}
|
|
386
|
+
// POST /v1/images/select — the §7 per-sandbox resolution: profile → newest published visible digest +
|
|
387
|
+
// podContract, with capability admission (Codex B3). Pure resolution (no build); the caller threads the
|
|
388
|
+
// returned `ref` (repo@digest) into the kata adapter's per-sandbox image (today a worker-global env).
|
|
286
389
|
if (req.method === "POST" && url === "/v1/images/select") {
|
|
287
390
|
const body = (await readJson(req));
|
|
288
391
|
const profile = typeof body.profile === "string" ? body.profile : "";
|
|
@@ -315,7 +418,12 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
315
418
|
});
|
|
316
419
|
return;
|
|
317
420
|
}
|
|
421
|
+
// POST /v1/images/register — operator-only ingest (P1: seed from the live bake manifests; P3 atomic-publish
|
|
422
|
+
// drives it for real). Minimal validation here; the publish pipeline supplies a full manifest projection.
|
|
318
423
|
if (req.method === "POST" && url === "/v1/images/register") {
|
|
424
|
+
// EXPLICIT operator gate (§P2.4a MUST-FIX, the P1 follow-up): NOT the bare `operator`/isOperator, whose
|
|
425
|
+
// empty-OPERATOR_PRINCIPALS "true-for-all" makes this index-mutating door world-writable. (The block-level
|
|
426
|
+
// `operator` stays for read visibility; the MUTATION must require a concrete configured operator.)
|
|
319
427
|
if (!explicitOperatorOk(principal, deps.config.operatorPrincipals)) {
|
|
320
428
|
sendError(res, 403, "auth.operator_only", "operator only");
|
|
321
429
|
return;
|
|
@@ -325,6 +433,11 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
325
433
|
sendError(res, 400, "request.field_invalid", "profile, repo, digest are required");
|
|
326
434
|
return;
|
|
327
435
|
}
|
|
436
|
+
// 🔴 STATUS-MONOTONIC register (adversarial-review HIGH finding): the unconditional `upsert` let an
|
|
437
|
+
// operator POST status:'building' for an already-`published` digest and SILENTLY DE-PUBLISH a live image
|
|
438
|
+
// (latestPublished then 404s → sandbox-create fails — the P2.10 bug class, LIVE on this door). registerBuilding
|
|
439
|
+
// never downgrades a published row (building→published still works; published→building is a no-op on the
|
|
440
|
+
// lifecycle). A deliberate take-down is the explicit deprecate path (P3), never a side effect of register.
|
|
328
441
|
const id = await idx.registerBuilding({
|
|
329
442
|
profile: b.profile,
|
|
330
443
|
bands: Array.isArray(b.bands) ? b.bands : [],
|
|
@@ -348,6 +461,7 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
348
461
|
sendJson(res, 200, { id });
|
|
349
462
|
return;
|
|
350
463
|
}
|
|
464
|
+
// GET /v1/images/digests/:digest — one entry by immutable digest (what a running sandbox was built from).
|
|
351
465
|
const digMatch = req.method === "GET" ? IMAGE_DIGEST_RE.exec(url) : null;
|
|
352
466
|
if (digMatch) {
|
|
353
467
|
const entry = await idx.getByDigest(decodeURIComponent(digMatch[1]));
|
|
@@ -358,6 +472,7 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
358
472
|
sendJson(res, 200, { image: entry });
|
|
359
473
|
return;
|
|
360
474
|
}
|
|
475
|
+
// GET /v1/images/:profile — the newest published visible entry for a profile (latest tag + versions).
|
|
361
476
|
const profMatch = req.method === "GET" ? IMAGE_PROFILE_RE.exec(url) : null;
|
|
362
477
|
if (profMatch) {
|
|
363
478
|
const entry = await idx.latestPublished(decodeURIComponent(profMatch[1]), viewer);
|
|
@@ -371,6 +486,9 @@ async function handleImagesBody(req, res, url, ctx, miss) {
|
|
|
371
486
|
}
|
|
372
487
|
miss.fell = true;
|
|
373
488
|
}
|
|
489
|
+
/** The image_bake SSE (P2.2/P2.8) — the SAME pump, with the run-trace-SSE-shaped bake frame
|
|
490
|
+
* (`data: {kind, seq, ts, ...kindFields}`) + a synthetic `done{failed}` stale frame so a reader settles
|
|
491
|
+
* (never hangs) when the runner is lost before the reaper's terminal event lands. */
|
|
374
492
|
async function streamBakeEvents(req, res, bakes, bakeId, staleMs) {
|
|
375
493
|
await streamSseLog(req, res, {
|
|
376
494
|
statusOf: async (id) => {
|
|
@@ -379,6 +497,8 @@ async function streamBakeEvents(req, res, bakes, bakeId, staleMs) {
|
|
|
379
497
|
},
|
|
380
498
|
getEvents: (id, after) => bakes.getEvents(id, after),
|
|
381
499
|
retainedFrom: (id) => bakes.retainedFrom(id),
|
|
500
|
+
// The bake frame carries kind+seq+ts INSIDE data (center locks its relay to this), distinct from the
|
|
501
|
+
// task_run `{type, ...data}` shape. `level` rides along so an opt-in `raw` log frame is distinguishable.
|
|
382
502
|
formatEvent: (ev) => ({
|
|
383
503
|
id: ev.seq,
|
|
384
504
|
event: ev.kind,
|
|
@@ -387,6 +507,9 @@ async function streamBakeEvents(req, res, bakes, bakeId, staleMs) {
|
|
|
387
507
|
staleFrame: () => ({ event: "done", data: { kind: "done", status: "FAILED", error: "bake-runner lost (no heartbeat)", digest: null, indexId: null, repo: null, ref: null } }),
|
|
388
508
|
}, bakeId, staleMs);
|
|
389
509
|
}
|
|
510
|
+
/** The operator poll view of a bake (§P2.1 `{bake:}` envelope, null-not-omitted). Projects the durable row MINUS
|
|
511
|
+
* the runner-internal secret/lease fields (`ingestSecret`/`runnerId`/`leaseUntil`/`cancelRequested`/`argv`) — an
|
|
512
|
+
* ops surface, but the per-bake ingest secret is a credential and the argv is build-host detail. */
|
|
390
513
|
function bakeView(b) {
|
|
391
514
|
return {
|
|
392
515
|
bakeId: b.bakeId,
|
|
@@ -412,6 +535,9 @@ function bakeView(b) {
|
|
|
412
535
|
updatedAt: b.updatedAt,
|
|
413
536
|
};
|
|
414
537
|
}
|
|
538
|
+
/** The RUNNER claim response (§P2.7/§P2.12): the VETTED argv + the per-bake ingest secret (minted at claim, echoed
|
|
539
|
+
* on every ingest) + the profile/bands the runner needs for its profile-aware hard deadline. This is the ONLY
|
|
540
|
+
* place the ingest secret leaves image-api — to the runner that just won the lease, over the internal claim. */
|
|
415
541
|
function claimResponse(claimed) {
|
|
416
542
|
return {
|
|
417
543
|
bakeId: claimed.bakeId,
|
|
@@ -425,76 +551,143 @@ function claimResponse(claimed) {
|
|
|
425
551
|
leaseUntil: claimed.leaseUntil,
|
|
426
552
|
};
|
|
427
553
|
}
|
|
554
|
+
/** Map ONE build.sh JSON line (runner-forwarded, with `lineOrd`) → the durable bake event log + the coarse status
|
|
555
|
+
* transition, and on a `done:COMPLETE` trigger the status-monotonic auto-register (§P2.3/§P2.10).
|
|
556
|
+
*
|
|
557
|
+
* build.sh emits `resolved | state | step | image | manifest | done` (read, not guessed). The runner forwards each
|
|
558
|
+
* verbatim plus a per-line `lineOrd` (at-least-once dedupe, uq_line). `done`/`manifest` carry a build-host FS path, not
|
|
559
|
+
* the body, so the runner reads the manifest body off the host and forwards it as `manifestBody` on the `done`.
|
|
560
|
+
*
|
|
561
|
+
* The COARSE status (the reader's terminality column) is driven ONLY by `state`/`done` (§P2.3 MUST-FIX): every
|
|
562
|
+
* non-terminal state → `running`; COMPLETE→done, FAILED/CANCELLED→failed. A `state` line that is non-terminal only
|
|
563
|
+
* denormalizes the UI progress bar (never the coarse status). The terminal `done` is appended BEFORE the coarse
|
|
564
|
+
* row flips (event-before-row latch) so the SSE reader always observes it.
|
|
565
|
+
*/
|
|
428
566
|
async function ingestBakeLine(bakes, idx, bake, line, registry) {
|
|
429
567
|
const bakeId = bake.bakeId;
|
|
430
568
|
const event = typeof line.event === "string" ? line.event : "";
|
|
431
569
|
const lineOrd = typeof line.lineOrd === "number" ? line.lineOrd : typeof line.line_ord === "number" ? line.line_ord : null;
|
|
570
|
+
// A non-`done` build.sh line: append it as the matching kind + denormalize `state` for the UI progress bar.
|
|
571
|
+
// (`resolved | state | step | image | manifest | log` — `manifest` is folded; the body rides the `done`.)
|
|
432
572
|
if (event !== "done") {
|
|
433
573
|
if (event === "state") {
|
|
434
574
|
const state = typeof line.state === "string" ? line.state : null;
|
|
435
575
|
if (state)
|
|
436
|
-
await bakes.setState(bakeId, state);
|
|
576
|
+
await bakes.setState(bakeId, state); // denormalize only — NEVER the coarse terminality (§P2.3)
|
|
437
577
|
const seq = await bakes.appendEvent(bakeId, "state", { state }, { lineOrd });
|
|
438
578
|
return { status: 200, body: { accepted: true, seq } };
|
|
439
579
|
}
|
|
580
|
+
// resolved | step | image | log | manifest → store under the build.sh event name as the SSE kind (the runner
|
|
581
|
+
// already shapes the data fields; manifest is kept as a `step`-adjacent frame for the trace, body on `done`).
|
|
440
582
|
const kind = event === "manifest" ? "manifest" : event;
|
|
441
583
|
const level = event === "log" && line.level === "raw" ? "raw" : null;
|
|
442
584
|
const { event: _e, lineOrd: _lo, line_ord: _li, ...data } = line;
|
|
585
|
+
// L3 (defense-in-depth): redactDeep the ingested non-terminal frame data before it's stored/streamed, mirroring
|
|
586
|
+
// streamTaskTrace's tool-arg redaction. build.sh keeps raw cred output off stdout (only resolved.mergedEnv —
|
|
587
|
+
// frozen server recipes — is unredacted today), but this closes the resolved.mergedEnv tail + any future
|
|
588
|
+
// `?logs=1` raw-tail before it's wired. (redactSecrets is non-exhaustive — hardening, not a guarantee.)
|
|
443
589
|
const seq = await bakes.appendEvent(bakeId, kind, redactDeep(data), { lineOrd, level });
|
|
444
590
|
return { status: 200, body: { accepted: true, seq } };
|
|
445
591
|
}
|
|
446
|
-
|
|
592
|
+
// ── terminal `done` — drive the auto-register + the coarse terminal flip (§P2.10).
|
|
593
|
+
const doneStatus = typeof line.status === "string" ? line.status : "FAILED"; // COMPLETE | FAILED | CANCELLED
|
|
447
594
|
const exitCode = typeof line.exitCode === "number" ? line.exitCode : doneStatus === "COMPLETE" ? 0 : null;
|
|
448
595
|
const cancelled = doneStatus === "CANCELLED";
|
|
449
596
|
const errorText = typeof line.error === "string" ? redactSecrets(line.error) : null;
|
|
450
597
|
const tag = typeof line.tag === "string" ? line.tag : bake.tag;
|
|
451
|
-
let digest = typeof line.digest === "string" && line.digest.length > 0 ? line.digest : null;
|
|
598
|
+
let digest = typeof line.digest === "string" && line.digest.length > 0 ? line.digest : null; // let:R22 身份折叠可置空
|
|
452
599
|
let indexId = bake.indexId;
|
|
453
600
|
let repo = bake.repo;
|
|
454
601
|
let ref = bake.ref;
|
|
455
602
|
let manifestSha = bake.manifestSha;
|
|
603
|
+
// The done STATUS/error can be DOWNGRADED to FAILED on a register-after-build failure (the partial-publish
|
|
604
|
+
// contract below) — start from build.sh's reported status and mutate iff the register throws.
|
|
456
605
|
let finalStatus = doneStatus;
|
|
457
606
|
let finalError = errorText;
|
|
458
607
|
let finalExit = exitCode;
|
|
608
|
+
// M1: a register-after-build downgrade (COMPLETE→FAILED) must OWN the terminal errorCode — a stale COMPLETE-time
|
|
609
|
+
// runner code (e.g. none) must not mask the partial-publish failure. Tracked here, consulted at the errorCode pick.
|
|
459
610
|
let registerDowngraded = false;
|
|
611
|
+
// Auto-register (only a COMPLETE with a manifest body + a digest is registerable). EVENT-BEFORE-ROW: we register
|
|
612
|
+
// (idx write) and append the `done` SSE event BEFORE the coarse terminal flip, so a `done` is always observable
|
|
613
|
+
// even if the terminal write retries.
|
|
614
|
+
//
|
|
615
|
+
// PARTIAL-PUBLISH CONTRACT (§P2.2/§P2.10): a register-after-build failure (a TiDB blip AFTER build.sh COMPLETE)
|
|
616
|
+
// is surfaced as `digest` present + `indexId` null + `status:"FAILED"` — a built-but-unregistered partial, NOT a
|
|
617
|
+
// successful COMPLETE and NOT a re-bake trigger (a P3 reconciliation tick re-registers it). So the done status is
|
|
618
|
+
// downgraded COMPLETE→FAILED on the catch, with the digest/repo/ref still carried so the artifact is findable.
|
|
619
|
+
// M3: the `register` step frames are EVENT-LOG writes that must follow the SAME terminality gate as the done
|
|
620
|
+
// event — buffer them here and replay only when THIS call wins the setTerminal CAS (affectedRows>=1). The idx
|
|
621
|
+
// writes (registerBuilding/setIndexId) are status-monotonic and idempotent, so they may run before the CAS.
|
|
460
622
|
const registerSteps = [];
|
|
461
623
|
if (doneStatus === "COMPLETE" && digest && idx) {
|
|
462
624
|
const manifestBody = (line.manifestBody ?? line.manifest);
|
|
625
|
+
// 🔴 H3 (zombie/reaped runner): re-read the bake row HERE (the passed `bake` may be stale) and ONLY register
|
|
626
|
+
// when it is still non-terminal. A reaped/terminal row must never inject a fresh `building` index row.
|
|
463
627
|
const fresh = await bakes.getBake(bakeId);
|
|
464
628
|
const nonTerminal = !!fresh && (fresh.status === "queued" || fresh.status === "running");
|
|
465
629
|
if (manifestBody && typeof manifestBody === "object" && nonTerminal) {
|
|
630
|
+
// 🔴 H3/H5: derive repo/profile from the VETTED control-plane bake row (+ the manifest's profile only as a
|
|
631
|
+
// fallback for a --bands "custom" bake) — NEVER trust the runner-supplied manifestBody for the server-fixed
|
|
632
|
+
// repo identity. H5: prefix the configured registry host (BAKE_IMAGE_REGISTRY — historically SWR, now
|
|
633
|
+
// docker.io/claybobby; SWR deprecated) so the bake row's repo/ref/index-id match the P1 seed
|
|
634
|
+
// convention (`${registry}/${profile}`); null registry ⇒ the host-less node-local name (documented
|
|
635
|
+
// non-pullable). For a no-push bake the digest is the build.sh LOCAL_ID (node-local, NOT selectable —
|
|
636
|
+
// registerBuilding writes status:'building', and latestPublished filters status='published').
|
|
466
637
|
const profile = bake.profile && bake.profile !== "custom" ? bake.profile : (manifestBody.profile && manifestBody.profile !== "custom" ? manifestBody.profile : "custom");
|
|
467
638
|
repo = registry ? `${registry}/${profile}` : `sema-images/${profile}`;
|
|
468
639
|
manifestSha = typeof line.manifestSha === "string" ? line.manifestSha : (typeof line.manifest?.sha256 === "string" ? String(line.manifest.sha256) : bake.manifestSha);
|
|
640
|
+
// codex R23:身份候选(digest/repo/manifestSha)先验**后写**——node-postgres 的 UTF-8 编码会把
|
|
641
|
+
// lone surrogate 折成 U+FFFD,registerBuilding 可能**成功**落一条身份已变形的 index 行,随后
|
|
642
|
+
// 帧级 identityInvalid 折叠才拒帧 → index 已被污染且与拒帧后的 null 身份终态互相矛盾。脏身份
|
|
643
|
+
// ⇒ 零 index 写(indexId 强制 null),repo/manifestSha 已按本帧生效值赋出,下方折叠必然命中
|
|
644
|
+
// 并独占 FAILED/null 身份终态(该臂不是 register 失败,不走 partial-publish 契约/P3 补注册)。
|
|
469
645
|
if (pgHasUnstorable(digest) || pgHasUnstorable(repo) || pgHasUnstorable(manifestSha)) {
|
|
470
646
|
indexId = null;
|
|
471
647
|
registerSteps.push({ step: "register", status: "skipped", reason: "identity fields contained unstorable bytes" });
|
|
472
648
|
}
|
|
473
649
|
else {
|
|
650
|
+
// Synthesize a `step:"register"` start around the auto-register so a resuming reader sees the register phase
|
|
651
|
+
// (§P2.2: the `register` step is image-api-synthesized — build.sh stops at COMPLETE + the manifest line).
|
|
474
652
|
registerSteps.push({ step: "register", status: "running" });
|
|
475
653
|
try {
|
|
476
654
|
const entry = manifestToIndexEntry(manifestBody, { repo, digest, status: "building", manifestSha, sizeBytes: manifestBody.sizeBytes ?? null, signed: false });
|
|
655
|
+
// 🔴 H3: FORCE visibility/tenantId from the control plane — a P2 standard-pool bake is ALWAYS a public,
|
|
656
|
+
// tenant-less row. NEVER let the runner-supplied manifestBody (owner/visibility) decide the index row's
|
|
657
|
+
// tenancy (a zombie runner could otherwise write a `tenant`-visibility row with an attacker-chosen tenant).
|
|
477
658
|
entry.visibility = "public";
|
|
478
659
|
entry.tenantId = null;
|
|
660
|
+
// STATUS-MONOTONIC register (§P2.10 MUST-FIX): registerBuilding NEVER downgrades a `published` row — a
|
|
661
|
+
// late/duplicate `done` for an already-promoted digest is a no-op on the lifecycle, never a regression.
|
|
479
662
|
indexId = await idx.registerBuilding(entry);
|
|
480
663
|
ref = `${repo}@${digest}`;
|
|
664
|
+
// codex R24:setIndexId 是 non-terminal 门写(见 store 注释)——本请求悬在 registerBuilding 期间
|
|
665
|
+
// 另一 done(如 R23 的 dirty 折叠帧)可能已赢 setTerminal;未绑定⇒indexId 不进本帧 terminal 对象
|
|
666
|
+
// (本请求随后必输 CAS,不落行不落事件,index 里的孤 building 行不 publish 不可选、由生命周期兜底)。
|
|
481
667
|
const bound = await bakes.setIndexId(bakeId, indexId);
|
|
482
668
|
if (!bound)
|
|
483
669
|
indexId = null;
|
|
484
670
|
registerSteps.push({ step: "register", status: "ok" });
|
|
485
671
|
}
|
|
486
672
|
catch (err) {
|
|
673
|
+
// built-but-unregistered partial (a TiDB blip post-build): downgrade the done to FAILED, keep digest+repo
|
|
674
|
+
// (+ ref so the artifact is findable), leave indexId null. The P3 reconciliation tick re-registers it.
|
|
487
675
|
indexId = null;
|
|
488
676
|
ref = `${repo}@${digest}`;
|
|
489
677
|
finalStatus = "FAILED";
|
|
490
|
-
registerDowngraded = true;
|
|
491
|
-
finalExit = exitCode ?? 0;
|
|
678
|
+
registerDowngraded = true; // M1: this FAILED is owned by the register failure, not the runner's code.
|
|
679
|
+
finalExit = exitCode ?? 0; // build.sh itself succeeded (exit 0); the register is the post-build failure.
|
|
492
680
|
finalError = redactSecrets(`index register failed after a COMPLETE build (built-but-unregistered partial): ${String(err?.message ?? err)}`);
|
|
493
681
|
registerSteps.push({ step: "register", status: "fail", reason: "index register failed (TiDB?)" });
|
|
494
682
|
}
|
|
495
683
|
}
|
|
496
684
|
}
|
|
497
685
|
}
|
|
686
|
+
// errorCode + coarse status are computed from the FINAL (possibly-downgraded) status, not build.sh's raw one.
|
|
687
|
+
// M1 (§P2.14 #2): PREFER a validated runner-supplied `line.errorCode` (the disk-guard's "disk", a future
|
|
688
|
+
// build-time "band-conflict" — codes the exit-code mapper cannot reconstruct) over the recompute. A `cancelled`
|
|
689
|
+
// terminal always wins (the kill is authoritative); a register-downgraded FAILED owns its own code (the runner's
|
|
690
|
+
// COMPLETE-time code is stale), so it falls back to the exit mapper.
|
|
498
691
|
const suppliedCode = typeof line.errorCode === "string" && RUNNER_ERROR_CODES.has(line.errorCode)
|
|
499
692
|
? line.errorCode
|
|
500
693
|
: undefined;
|
|
@@ -503,9 +696,14 @@ async function ingestBakeLine(bakes, idx, bake, line, registry) {
|
|
|
503
696
|
: suppliedCode !== undefined && !registerDowngraded
|
|
504
697
|
? suppliedCode
|
|
505
698
|
: errorCodeForExit(finalExit, cancelled);
|
|
699
|
+
// M1: a band-conflict terminal carries the offending band ids so the composer UI can red-highlight the chips
|
|
700
|
+
// (§P2.14 #2). Only honored when the FINAL code is band-conflict (a downgraded/other terminal carries none).
|
|
506
701
|
const conflictBands = errorCode === "band-conflict" && Array.isArray(line.conflictBands)
|
|
507
702
|
? line.conflictBands.filter((b) => typeof b === "string")
|
|
508
703
|
: null;
|
|
704
|
+
// codex R22:身份位(digest/repo/ref/manifestSha)带不可存字节=帧无效——在**构造前**统一折叠
|
|
705
|
+
// (row/SSE done/HTTP 三面同源一份 effective 对象;store 内折叠留作双保险,值恒等)。COMPLETE 折
|
|
706
|
+
// FAILED(null 身份的成功=谎),错误面补一句归因。
|
|
509
707
|
const identityInvalid = (typeof digest === "string" && pgHasUnstorable(digest)) ||
|
|
510
708
|
(typeof repo === "string" && pgHasUnstorable(repo)) ||
|
|
511
709
|
(typeof ref === "string" && pgHasUnstorable(ref)) ||
|
|
@@ -516,11 +714,17 @@ async function ingestBakeLine(bakes, idx, bake, line, registry) {
|
|
|
516
714
|
repo = null;
|
|
517
715
|
ref = null;
|
|
518
716
|
manifestSha = null;
|
|
519
|
-
indexId = null;
|
|
717
|
+
indexId = null; // codex R23:null 身份终态不得声称 index 绑定(register 门已跳过全部 index 写)
|
|
520
718
|
finalError = finalError ? `${finalError}; identity fields contained unstorable bytes` : "terminal frame identity fields contained unstorable bytes";
|
|
521
719
|
}
|
|
522
720
|
const finalState = (finalStatus === "COMPLETE" ? "COMPLETE" : finalStatus === "CANCELLED" ? "CANCELLED" : "FAILED");
|
|
523
721
|
const coarse = coarseStatusForState(finalState);
|
|
722
|
+
// M3: flip the coarse terminal row FIRST and gate the terminal `done` EVENT (+ the register `step` frames) on
|
|
723
|
+
// WINNING that queued/running→terminal CAS. A late/contradictory terminal (cancel-vs-complete, reaper-vs-recovery)
|
|
724
|
+
// sees affectedRows===0 and writes NO event, so the SSE log never replays a second, conflicting terminal `done`.
|
|
725
|
+
// Invariant: at most one terminal `done` event per bake, always matching the coarse row outcome (§P2.14).
|
|
726
|
+
// L2: the flip is idempotent (`WHERE status IN ('queued','running')`), so a transient throw is safe to retry
|
|
727
|
+
// rather than leaving the row `running` with a `done` event the reaper would later contradict.
|
|
524
728
|
let affectedRows = 0;
|
|
525
729
|
const terminal = {
|
|
526
730
|
status: coarse === "done" ? "done" : "failed",
|
|
@@ -547,9 +751,13 @@ async function ingestBakeLine(bakes, idx, bake, line, registry) {
|
|
|
547
751
|
}
|
|
548
752
|
}
|
|
549
753
|
if (lastErr !== undefined) {
|
|
754
|
+
// L2: exhausted the bounded retry — DON'T 500 (safeIngest swallows it and the row would stay `running` with no
|
|
755
|
+
// terminal event the reaper later contradicts). Return a non-terminal ack so the runner re-POSTs the
|
|
756
|
+
// (lineOrd-idempotent) done to re-drive the flip.
|
|
550
757
|
return { status: 200, body: { accepted: true, terminal: false, needsFlip: true } };
|
|
551
758
|
}
|
|
552
759
|
if (affectedRows >= 1) {
|
|
760
|
+
// We won the transition: replay the buffered register `step` frames, then append the single terminal `done`.
|
|
553
761
|
for (const s of registerSteps)
|
|
554
762
|
await bakes.appendEvent(bakeId, "step", s);
|
|
555
763
|
await bakes.appendEvent(bakeId, "done", {
|