@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,8 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox-image-pool INDEX store (IMAGE-API-DESIGN.md §1, pool design §8) — SINGLE-FILE DUAL-DIALECT
|
|
3
|
+
* (design/158 A12 定型半场). ONE implementation, TWO dialects; the historical `TiDBImageIndex` /
|
|
4
|
+
* `PgImageIndex` class names survive as thin ctor subclasses so every consumer (store-backend.ts,
|
|
5
|
+
* pg-pool.ts, the unit + real-DB suites) is untouched.
|
|
6
|
+
*
|
|
7
|
+
* One row per published/building image DIGEST — the immutable runtime ref (design §6: refs are @sha256:...).
|
|
8
|
+
* A queryable projection of build.sh's replay-sufficient `sandbox-manifest.json` + the publish lifecycle
|
|
9
|
+
* `status` (building → published → deprecated / failed). This is the SINGLE SOURCE OF TRUTH for "what can a
|
|
10
|
+
* sandbox run"; `POST /v1/images/select` resolves a profile → digest off it (the §7 per-sandbox hot path).
|
|
11
|
+
*
|
|
12
|
+
* Visibility is enforced IN THE QUERY (Codex A1, mirrors the assistant per-user scoping): a tenant principal
|
|
13
|
+
* sees `public` rows + its OWN `tenant` rows only; an operator sees all. mapRow/ImageRow are the row-mapping
|
|
14
|
+
* surface shared by BOTH dialects — they live in the backend-neutral store-contracts.ts (design/158 A3 沉底).
|
|
15
|
+
*
|
|
16
|
+
* ── Dialect deltas, kept EXPLICIT ────────────────────────────────────────────────────────────────────
|
|
17
|
+
* - `?` placeholders vs positional `$n` (and the placeholder-arity fallout in `list`/`setStatus`, which
|
|
18
|
+
* track a running `$n` index; TiDB's `?` needs no such bookkeeping).
|
|
19
|
+
* - upsert form: `ON DUPLICATE KEY UPDATE … VALUES(col)` vs `ON CONFLICT (id) DO UPDATE SET … EXCLUDED.col`.
|
|
20
|
+
* - the MONOTONIC de-publish guard (IMAGE-API-DESIGN.md §P2.10 / wq64gmm5e): `IF(status='published', old,
|
|
21
|
+
* new)` vs `CASE WHEN … status = 'published' THEN old ELSE new END` — status/visibility/tenant_id/signed
|
|
22
|
+
* are ALL preserved on a live published row in BOTH dialects.
|
|
23
|
+
* - JSON columns: TiDB `JSON.stringify` verbatim vs PG `pgSafeJsonStringify` + `$n::jsonb` casts (lossy —
|
|
24
|
+
* this is an observation-layer blob, not a trust-boundary payload, so `pgSafeJsonStringify`'s U+FFFD
|
|
25
|
+
* replacement is acceptable; codex R15).
|
|
26
|
+
* - PG-only identity-axis validation (codex R17-H1/R19): `repo` (the `idFor` hash input + the `(repo,digest)`
|
|
27
|
+
* unique-key equivalence premise) and `tenantId` reject unstorable bytes UP FRONT (`pgHasUnstorable` →
|
|
28
|
+
* `PgUnstorableError`) rather than silently mangling them — a rewrite would fork the hash id from the
|
|
29
|
+
* stored column (same `(repo,digest)` collides the unique key while `ON CONFLICT (id)` doesn't match,
|
|
30
|
+
* wedging registration permanently). TiDB has no equivalent NUL/lone-surrogate constraint, so it stores
|
|
31
|
+
* verbatim. Same-family scalar sanitize (tag/recipeGitSha/generatorVersion/supersedes — codex R16) is
|
|
32
|
+
* PG-only for the same reason; identity columns (repo/tenantId/digest/manifestSha) are reject-not-rewrite.
|
|
33
|
+
* - null-safe ordering trick: `ORDER BY (build_date IS NULL), build_date DESC` vs `ORDER BY build_date DESC
|
|
34
|
+
* NULLS LAST` (same semantics, dialect-native syntax).
|
|
35
|
+
* - capability-flag predicate: `JSON_EXTRACT(capabilities, '$.key') = true` vs `capabilities -> 'key' =
|
|
36
|
+
* 'true'::jsonb` (NOT a `::boolean` cast, which hard-errors on non-boolean jsonb and aborts the query;
|
|
37
|
+
* the jsonb equality yields false instead, matching MySQL's exclude-don't-throw semantics for a present
|
|
38
|
+
* but non-boolean capability value, e.g. `nestedBuildMode`).
|
|
39
|
+
* - affectedRows vs rowCount, BIGINT (`size_bytes`) read back as a STRING on PG (via SqlDriver / mapImageRow).
|
|
40
|
+
* - schema ownership: TiDB DDL lives in tidb-pool.ts; the PG DDL is exported from HERE
|
|
41
|
+
* (PG_IMAGE_INDEX_SCHEMA + ensureSchema, self-contained for the integration test).
|
|
42
|
+
*/
|
|
1
43
|
import { createHash } from "node:crypto";
|
|
2
44
|
import { pgSafeJsonStringify, pgSanitizeText, pgHasUnstorable, PgUnstorableError } from "./pg-safe-json.js";
|
|
3
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
// mapRow/ImageRow are the row-mapping surface shared by BOTH SQL twins — they live in the
|
|
47
|
+
// backend-neutral store-contracts.ts (design/158 A3 沉底; no adapter imports its twin).
|
|
48
|
+
mapImageRow as mapRow, } from "./store-contracts.js";
|
|
4
49
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
5
50
|
export { mapImageRow as mapRow } from "./store-contracts.js";
|
|
51
|
+
/** PG translation of the `sandbox_image_index` DDL in tidb-pool.ts (JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3),
|
|
52
|
+
* TINYINT(1)→SMALLINT(布尔列 PG 映射归一,clay 批 2026-07-26 —— 两方言读回同为 number,写入 0/1,
|
|
53
|
+
* 读侧 `!!` 保持,出口 JS 类型不变), inline UNIQUE KEY/KEY → separate CREATE UNIQUE INDEX / CREATE INDEX). */
|
|
6
54
|
export const PG_IMAGE_INDEX_SCHEMA = [
|
|
7
55
|
`CREATE TABLE IF NOT EXISTS sandbox_image_index (
|
|
8
56
|
id VARCHAR(64) NOT NULL,
|
|
@@ -33,12 +81,14 @@ export const PG_IMAGE_INDEX_SCHEMA = [
|
|
|
33
81
|
`CREATE INDEX IF NOT EXISTS idx_digest ON sandbox_image_index (digest)`,
|
|
34
82
|
`CREATE INDEX IF NOT EXISTS idx_tenant ON sandbox_image_index (tenant_id, status)`,
|
|
35
83
|
];
|
|
84
|
+
/** Idempotent self-contained schema apply (for the integration test; central aggregation is done separately). */
|
|
36
85
|
export async function ensureSchema(pool) {
|
|
37
86
|
for (const stmt of PG_IMAGE_INDEX_SCHEMA)
|
|
38
87
|
await pool.query(stmt);
|
|
39
88
|
}
|
|
40
89
|
const MAX_LIMIT = 200;
|
|
41
90
|
const DEFAULT_LIMIT = 50;
|
|
91
|
+
/** `(visibility='public' OR tenant_id=?)` unless operator. TiDB form — `?` needs no index bookkeeping. */
|
|
42
92
|
function visibilityClauseTidb(viewer) {
|
|
43
93
|
if (viewer.operator)
|
|
44
94
|
return { sql: "1=1", params: [] };
|
|
@@ -46,6 +96,7 @@ function visibilityClauseTidb(viewer) {
|
|
|
46
96
|
return { sql: "(visibility='public' OR tenant_id=?)", params: [viewer.tenantId] };
|
|
47
97
|
return { sql: "visibility='public'", params: [] };
|
|
48
98
|
}
|
|
99
|
+
/** PG form — positional `$n`, with a running index the caller continues from (PG has no anonymous `?`). */
|
|
49
100
|
function visibilityClausePg(viewer, startIdx) {
|
|
50
101
|
if (viewer.operator)
|
|
51
102
|
return { sql: "1=1", params: [], nextIdx: startIdx };
|
|
@@ -71,6 +122,7 @@ function decodeCursor(c) {
|
|
|
71
122
|
return null;
|
|
72
123
|
}
|
|
73
124
|
}
|
|
125
|
+
/** TiDB upsert/registerBuilding params — the array literal is IDENTICAL text at both TiDB call sites. */
|
|
74
126
|
function tidbUpsertParams(e, id, now) {
|
|
75
127
|
return [
|
|
76
128
|
id, e.profile, JSON.stringify(e.bands ?? []), e.repo, e.tag, e.digest,
|
|
@@ -85,12 +137,17 @@ const PG_UPSERT_COLS = "(id, profile, bands, repo, tag, digest, toolchain_versio
|
|
|
85
137
|
"status, visibility, tenant_id, manifest_sha, recipe_git_sha, generator_version, build_date, supersedes, " +
|
|
86
138
|
"signed, created_at, updated_at)";
|
|
87
139
|
const PG_UPSERT_VALUES = "($1,$2,$3::jsonb,$4,$5,$6,$7::jsonb,$8::jsonb,$9::jsonb,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21)";
|
|
140
|
+
/** PG upsert/registerBuilding params — deep-sanitized JSON (observation-layer, R15) + same-family scalar
|
|
141
|
+
* sanitize (R16); identity columns (repo/tenantId/digest/manifestSha) are reject-not-rewrite (validated by
|
|
142
|
+
* the caller BEFORE this runs — see the `pgHasUnstorable` guards in `upsert`/`registerBuilding`). */
|
|
88
143
|
function pgUpsertParams(e, id, now) {
|
|
89
144
|
return [
|
|
90
145
|
id,
|
|
91
146
|
pgSanitizeText(e.profile),
|
|
92
|
-
pgSafeJsonStringify(e.bands ?? []),
|
|
93
|
-
|
|
147
|
+
pgSafeJsonStringify(e.bands ?? []), // R15:元数据 jsonb 四位=观测层(嵌套值可含外部构建产物文本)
|
|
148
|
+
// codex R16:register 面标量只做类型检查——tag/tenantId/generatorVersion/supersedes 可携脏字节,
|
|
149
|
+
// 同族观测标量一并 sanitize(digest/manifestSha 恒 hex 恒等;identity 值 sanitize 恒等=零行为差)。
|
|
150
|
+
e.repo, // R17-H1:repo=身份(idFor 哈希输入)——入口拒绝式,列存原文(见 upsert/registerBuilding)
|
|
94
151
|
pgSanitizeText(e.tag),
|
|
95
152
|
e.digest,
|
|
96
153
|
pgSafeJsonStringify(e.toolchainVersions ?? {}),
|
|
@@ -99,7 +156,7 @@ function pgUpsertParams(e, id, now) {
|
|
|
99
156
|
e.sizeBytes,
|
|
100
157
|
e.status,
|
|
101
158
|
e.visibility,
|
|
102
|
-
e.tenantId ?? null,
|
|
159
|
+
e.tenantId ?? null, // R19:tenantId=租户身份轴——入口拒绝式(改写=可见性归属漂移),列存原文
|
|
103
160
|
e.manifestSha ?? null,
|
|
104
161
|
e.recipeGitSha == null ? null : pgSanitizeText(e.recipeGitSha),
|
|
105
162
|
e.generatorVersion == null ? null : pgSanitizeText(e.generatorVersion),
|
|
@@ -110,18 +167,24 @@ function pgUpsertParams(e, id, now) {
|
|
|
110
167
|
now,
|
|
111
168
|
];
|
|
112
169
|
}
|
|
170
|
+
/** Dual-dialect INDEX store. See the file header for the dialect-delta ledger. */
|
|
113
171
|
export class SqlImageIndex {
|
|
114
172
|
db;
|
|
115
173
|
constructor(db) {
|
|
116
174
|
this.db = db;
|
|
117
175
|
}
|
|
176
|
+
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
118
177
|
q(tidb, pg) {
|
|
119
178
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
120
179
|
}
|
|
180
|
+
/** Stable index id = sha256(repo@digest) — identity is the digest, not the mutable tag. */
|
|
121
181
|
static idFor(repo, digest) {
|
|
122
182
|
return createHash("sha256").update(`${repo}@${digest}`).digest("hex").slice(0, 64);
|
|
123
183
|
}
|
|
184
|
+
/** Register or update an index row (idempotent on repo+digest). Returns the row id. */
|
|
124
185
|
async upsert(e) {
|
|
186
|
+
// 🔴 PG-only identity-axis validation (codex R17-H1/R19) — TiDB has no NUL/lone-surrogate column
|
|
187
|
+
// constraint, so it stores repo/tenantId verbatim; PG rejects up front (see file-header dialect delta).
|
|
125
188
|
if (this.db.dialect === "pg") {
|
|
126
189
|
if (pgHasUnstorable(e.repo))
|
|
127
190
|
throw new PgUnstorableError("image repo (identity)");
|
|
@@ -153,7 +216,31 @@ export class SqlImageIndex {
|
|
|
153
216
|
supersedes = EXCLUDED.supersedes, signed = EXCLUDED.signed, updated_at = EXCLUDED.updated_at`), this.db.dialect === "tidb" ? tidbUpsertParams(e, id, now) : pgUpsertParams(e, id, now));
|
|
154
217
|
return id;
|
|
155
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* STATUS-MONOTONIC register (IMAGE-API-DESIGN.md §P2.10 MUST-FIX) — the auto-register path the bake runner uses.
|
|
221
|
+
*
|
|
222
|
+
* `upsert` does an UNCONDITIONAL upsert: a re-ingested `done` (at-least-once from the runner) or a P2 bake of
|
|
223
|
+
* a digest P3 already PROMOTED would flip a live `published` row back to `building`, and `latestPublished`
|
|
224
|
+
* (`WHERE status='published'`) then DE-PUBLISHES a live, in-use image → sandbox-create 404s. This guarded path
|
|
225
|
+
* NEVER downgrades a `published` row: on a fresh row it inserts (`status:'building'` by default); on an
|
|
226
|
+
* existing row it updates the manifest projection + bumps `updated_at` but writes `status` only when the row
|
|
227
|
+
* isn't already `published` — a late/duplicate `done` for an already-promoted digest is a no-op on the
|
|
228
|
+
* lifecycle, never a regression. Returns the row id.
|
|
229
|
+
*
|
|
230
|
+
* Distinct from the unconditional `upsert` (kept for the seed + the operator `/v1/images/register` path); this
|
|
231
|
+
* is the bake auto-register's ONLY write into the index. Default status is `building` (P2 registers
|
|
232
|
+
* node-local/quarantine `building` artifacts; P3's verify gate is what flips `building → published`).
|
|
233
|
+
*
|
|
234
|
+
* The `IF(...published...)` (TiDB) / `CASE WHEN...published...` (PG) guards are the monotonic de-publish
|
|
235
|
+
* defense: once a row is `published` and live, a re-register must NOT downgrade `status` NOR flip the
|
|
236
|
+
* columns `latestPublished` filters on. wq64gmm5e: guarding only `status` left `visibility`/`tenant_id`
|
|
237
|
+
* (and `signed`) overwritable, so a re-register with visibility=private/tenant or a different tenant_id
|
|
238
|
+
* silently dropped a live row out of every viewer's `latestPublished` — an effective de-publish. ALL FOUR
|
|
239
|
+
* (status/visibility/tenant_id/signed) are gated identically in BOTH dialects; non-published rows still
|
|
240
|
+
* take the freshest projection.
|
|
241
|
+
*/
|
|
156
242
|
async registerBuilding(e) {
|
|
243
|
+
// 🔴 PG-only identity-axis validation (codex R17-H1/R19) — same rationale as `upsert` above.
|
|
157
244
|
if (this.db.dialect === "pg") {
|
|
158
245
|
if (pgHasUnstorable(e.repo))
|
|
159
246
|
throw new PgUnstorableError("image repo (identity)");
|
|
@@ -199,10 +286,15 @@ export class SqlImageIndex {
|
|
|
199
286
|
const { rows } = await this.db.query(this.q("SELECT * FROM sandbox_image_index WHERE id=? LIMIT 1", "SELECT * FROM sandbox_image_index WHERE id = $1 LIMIT 1"), [id]);
|
|
200
287
|
return rows[0] ? mapRow(rows[0]) : null;
|
|
201
288
|
}
|
|
289
|
+
/** By immutable digest — what a running sandbox was built from. */
|
|
202
290
|
async getByDigest(digest) {
|
|
203
291
|
const { rows } = await this.db.query(this.q("SELECT * FROM sandbox_image_index WHERE digest=? ORDER BY updated_at DESC LIMIT 1", "SELECT * FROM sandbox_image_index WHERE digest = $1 ORDER BY updated_at DESC LIMIT 1"), [digest]);
|
|
204
292
|
return rows[0] ? mapRow(rows[0]) : null;
|
|
205
293
|
}
|
|
294
|
+
/** The newest PUBLISHED, viewer-visible digest for a profile — the `select` resolution.
|
|
295
|
+
* 🔴 genuinely-divergent shape (not just SQL text): TiDB's `visibilityClauseTidb` needs no placeholder
|
|
296
|
+
* index (anonymous `?`); PG's `visibilityClausePg` threads a running `$n` index the caller continues
|
|
297
|
+
* from. Also the null-safe ORDER BY trick differs ((build_date IS NULL) vs NULLS LAST) — same semantics. */
|
|
206
298
|
async latestPublished(profile, viewer) {
|
|
207
299
|
if (this.db.dialect === "tidb") {
|
|
208
300
|
const vis = visibilityClauseTidb(viewer);
|
|
@@ -217,6 +309,13 @@ export class SqlImageIndex {
|
|
|
217
309
|
ORDER BY build_date DESC NULLS LAST, created_at DESC LIMIT 1`, [profile, ...vis.params]);
|
|
218
310
|
return rows[0] ? mapRow(rows[0]) : null;
|
|
219
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Paginated, viewer-scoped catalog query (the UI's list). Keyset cursor on (created_at, id) desc.
|
|
314
|
+
* 🔴 genuinely-divergent shape: the PG arm threads an explicit `$n` index through every optional predicate
|
|
315
|
+
* (node-pg has no anonymous placeholder); the TiDB arm just appends `?` in call order. Kept as two full
|
|
316
|
+
* branches rather than a shared placeholder-numbering helper (sql-driver.ts STEP 3/4) — a reviewer sees each
|
|
317
|
+
* dialect's real query shape, not a generated abstraction over it.
|
|
318
|
+
*/
|
|
220
319
|
async list(filter) {
|
|
221
320
|
const limit = Math.min(Math.max(1, filter.limit ?? DEFAULT_LIMIT), MAX_LIMIT);
|
|
222
321
|
let rawRows;
|
|
@@ -233,6 +332,7 @@ export class SqlImageIndex {
|
|
|
233
332
|
params.push(filter.status);
|
|
234
333
|
}
|
|
235
334
|
if (filter.capability) {
|
|
335
|
+
// JSON boolean true for the capability key.
|
|
236
336
|
where.push("JSON_EXTRACT(capabilities, ?) = true");
|
|
237
337
|
params.push(`$.${filter.capability}`);
|
|
238
338
|
}
|
|
@@ -262,6 +362,11 @@ export class SqlImageIndex {
|
|
|
262
362
|
params.push(filter.status);
|
|
263
363
|
}
|
|
264
364
|
if (filter.capability) {
|
|
365
|
+
// JSON boolean true for the capability key. Compare at the jsonb layer (NOT a ::boolean cast): a
|
|
366
|
+
// `::boolean` cast HARD-ERRORS on any non-boolean jsonb value (string/number/JSON null) and aborts the
|
|
367
|
+
// whole query, whereas `= 'true'::jsonb` yields false for those — faithfully reproducing MySQL's
|
|
368
|
+
// `JSON_EXTRACT(...) = true` semantics (a present-but-non-boolean cap, e.g.
|
|
369
|
+
// nestedBuildMode="rootless-buildkit", simply excludes the row).
|
|
265
370
|
where.push(`capabilities -> $${idx++} = 'true'::jsonb`);
|
|
266
371
|
params.push(filter.capability);
|
|
267
372
|
}
|
|
@@ -287,11 +392,13 @@ export class SqlImageIndex {
|
|
|
287
392
|
nextCursor = encodeCursor({ createdAt: last.createdAt, id: last.id });
|
|
288
393
|
}
|
|
289
394
|
if (filter.latestOnly) {
|
|
395
|
+
// collapse to the newest row per profile (rows are already build/created-desc within the page).
|
|
290
396
|
const seen = new Set();
|
|
291
397
|
entries = entries.filter((e) => (seen.has(e.profile) ? false : (seen.add(e.profile), true)));
|
|
292
398
|
}
|
|
293
399
|
return { entries, nextCursor };
|
|
294
400
|
}
|
|
401
|
+
/** Lifecycle transition (atomic-publish: building→published, or →failed/deprecated). */
|
|
295
402
|
async setStatus(id, status, opts) {
|
|
296
403
|
if (this.db.dialect === "tidb") {
|
|
297
404
|
const sets = ["status=?", "updated_at=?"];
|
|
@@ -315,11 +422,13 @@ export class SqlImageIndex {
|
|
|
315
422
|
await this.db.query(`UPDATE sandbox_image_index SET ${sets.join(", ")} WHERE id = $${idx}`, params);
|
|
316
423
|
}
|
|
317
424
|
}
|
|
425
|
+
/** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
|
|
318
426
|
export class TiDBImageIndex extends SqlImageIndex {
|
|
319
427
|
constructor(pool) {
|
|
320
428
|
super(mysqlDriver(pool));
|
|
321
429
|
}
|
|
322
430
|
}
|
|
431
|
+
/** PostgreSQL binding — historical class name + ctor shape preserved. */
|
|
323
432
|
export class PgImageIndex extends SqlImageIndex {
|
|
324
433
|
constructor(pool) {
|
|
325
434
|
super(pgDriver(pool));
|
|
@@ -1,9 +1,42 @@
|
|
|
1
|
+
/** The detached runner (base64'd into the pod, run under `setsid`). Self-reports pgid; streams output to out/err. */
|
|
1
2
|
export declare function buildBgRunnerScript(dir: string): string;
|
|
3
|
+
/** Short foreground launcher: writes cmd.sh + runner.sh, then `setsid`s the runner detached and returns immediately. */
|
|
2
4
|
export declare function buildBgLauncherScript(dir: string, cmdShB64: string, runnerShB64: string): string;
|
|
5
|
+
/** Poll script: emit totals + liveness + exit, then the new bytes per stream (base64, bounded by `readCap`). */
|
|
3
6
|
export declare function buildBgPollScript(dir: string, stdoutCursor: number, stderrCursor: number, readCap: number): string;
|
|
7
|
+
/** KillShell: graceful SIGTERM to the group, then a forced SIGKILL (idempotent — a dead group just errors out).
|
|
8
|
+
* The `|| kill <pid>` fallback covers an ADOPTED exec whose pgid prelude self-reported `$$` without a `setsid`
|
|
9
|
+
* (whether the k8s-exec'd sh is a group leader is runtime-dependent); a launch-mode runner is always setsid'd,
|
|
10
|
+
* so its group kill succeeds and the fallback never fires there. */
|
|
4
11
|
export declare function buildBgKillScript(dir: string): string;
|
|
12
|
+
/** dispose: forced SIGKILL to the group + remove the job dir (no grace — teardown). Best-effort.
|
|
13
|
+
* ⚠️ Deliberately NO pgid-wait (unlike {@link buildBgKillScript}, review #4): a create-then-abort adopt disposed
|
|
14
|
+
* in the pre-mkdir window escapes THIS script — but every dispose path ends in pod deletion (task-end destroy;
|
|
15
|
+
* suspendVM deletes the pod after the snapshot), so the escape is pod-lifetime-bounded, and a wait here would
|
|
16
|
+
* add 2s × N shells to the suspend/teardown path for a sub-second window. Kill (the model-facing wall) waits. */
|
|
5
17
|
export declare function buildBgDisposeScript(dir: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* design/116 detach: wrap a foreground exec so it is ADOPTABLE mid-flight, running the command as a real
|
|
20
|
+
* process-group LEADER so an adopted shell's KillShell / BG hard wall reap the WHOLE subtree.
|
|
21
|
+
*
|
|
22
|
+
* 🔴 review MED (why `setsid`, not bare `$$`): the wrapper `sh` that `kubectl exec` starts has a RUNTIME-DEPENDENT
|
|
23
|
+
* process group — under many container runtimes it is NOT its own group leader (its pgid is some ancestor's), so
|
|
24
|
+
* writing `$$` as the pgid and later `kill -$$` targets a group that does not exist → the fallback `kill $$` reaps
|
|
25
|
+
* only the wrapper, orphaning the command's children PAST the §3.6 hard wall (they keep streaming into the adopted
|
|
26
|
+
* shell's buffer until the pod's `activeDeadlineSeconds`). `setsid` starts the command in a NEW session ⇒ a new
|
|
27
|
+
* process group whose leader pid IS the backgrounded job pid, so `kill -<pid>` reaps the command and every
|
|
28
|
+
* descendant. The command's stdout/stderr fds (the exec socket's channels) are INHERITED across setsid — only the
|
|
29
|
+
* controlling terminal is dropped (there is none: kubectl-exec here is non-tty) — so output/exit still flow on the
|
|
30
|
+
* SAME WebSocket, which IS the adopted shell's feed. `wait` propagates the real exit code.
|
|
31
|
+
*
|
|
32
|
+
* The command is base64'd into a pod-side `cmd.sh` (no quoting hazard, mirrors the launch path). The pgid is written
|
|
33
|
+
* atomically (tmp+mv) AFTER the job forks, so a poll never reads a half-written id. Every step is best-effort: a pod
|
|
34
|
+
* without `setsid` (rare on the kata base image) degrades to the `$$` path (the pre-fix behavior), and a read-only
|
|
35
|
+
* /tmp degrades to a pgid-less adopt (kill no-ops; the disposable pod's teardown reaps) — never a broken exec. The
|
|
36
|
+
* EXIT trap removes the dir when the command ends normally, so non-detached execs don't litter the pod.
|
|
37
|
+
*/
|
|
6
38
|
export declare function buildDetachCapableExec(dir: string, command: string): string;
|
|
39
|
+
/** Parsed background poll response. `gone` = the job dir vanished (abnormal). Byte counts drive cursor/dropped accounting. */
|
|
7
40
|
export interface ParsedBgPoll {
|
|
8
41
|
gone: boolean;
|
|
9
42
|
outTotal: number;
|
|
@@ -15,5 +48,6 @@ export interface ParsedBgPoll {
|
|
|
15
48
|
stderr: string;
|
|
16
49
|
stderrBytes: number;
|
|
17
50
|
}
|
|
51
|
+
/** Parse {@link buildBgPollScript} stdout. Markers contain `_` (absent from the base64 alphabet) so they never collide. */
|
|
18
52
|
export declare function parseBgPollOutput(raw: string): ParsedBgPoll | undefined;
|
|
19
53
|
//# sourceMappingURL=k8s-bg-scripts.d.ts.map
|
|
@@ -1,13 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/103 background-shell pod-side scripts (pure, exported for testing) + design/116 detach-capable exec —
|
|
3
|
+
* the k8s adapter's text generation/parsing layer for background shells (`buildBg*Script` ×5 +
|
|
4
|
+
* `buildDetachCapableExec` + `parseBgPollOutput`). Split out of `remote-env-k8s.ts` (design/158 A13,
|
|
5
|
+
* internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
|
|
6
|
+
*
|
|
7
|
+
* The pod-side runner is `setsid`'d into its OWN session so the whole job (cmd + its grandchildren) shares one
|
|
8
|
+
* process group we can group-kill (`kill -<pgid>`); the session leader self-reports its pgid (`$$`) so we never
|
|
9
|
+
* depend on `setsid &`'s `$!` (which is the short-lived parent). The command writes DIRECTLY to pod files (out/err),
|
|
10
|
+
* re-read by byte cursor each poll (path ① — survives the consume-once exec seam).
|
|
11
|
+
*
|
|
12
|
+
* 🔴 Why DIRECT redirect (`> out 2> err`), not a FIFO+`head` cap: on-box kata verification proved a FIFO+`head`
|
|
13
|
+
* pipeline block-buffers `head`'s stdout → the `out` file stays EMPTY until head's stdio buffer fills (KBs) or EOF,
|
|
14
|
+
* so `pollBackground` sees nothing for a slow streamer (the whole dev-server use case). Direct redirect makes the
|
|
15
|
+
* shell write each `echo` straight to the file → immediately pollable (the command's OWN buffering still applies,
|
|
16
|
+
* same as TOC/E2B's pipe). DISK bound: the pod has no native ephemeral cap, so a runaway producer is bounded by
|
|
17
|
+
* (a) the BG-timeout hard wall + pod `activeDeadlineSeconds`, and (b) a control-plane size guard in the driver's
|
|
18
|
+
* read() that reaps a job whose output exceeds `K8S_BG_FILE_CAP` (kata VM isolation already contains the blast to
|
|
19
|
+
* this one disposable pod). per-poll TRANSFER stays bounded by the readCap in buildBgPollScript regardless.
|
|
20
|
+
*/
|
|
1
21
|
import { shellQuote } from "./remote-shell.js";
|
|
22
|
+
/** The detached runner (base64'd into the pod, run under `setsid`). Self-reports pgid; streams output to out/err. */
|
|
2
23
|
export function buildBgRunnerScript(dir) {
|
|
3
24
|
const q = (name) => shellQuote(`${dir}/${name}`);
|
|
4
25
|
return [
|
|
26
|
+
// 🔴 self-report the pgid: $$ is this (session-leader) shell's pid == the session/group id. Atomic write.
|
|
5
27
|
`echo $$ > ${q("pgid.tmp")} 2>/dev/null && mv ${q("pgid.tmp")} ${q("pgid")} 2>/dev/null`,
|
|
6
|
-
`sh ${q("cmd.sh")} > ${q("out")} 2> ${q("err")} < /dev/null`,
|
|
28
|
+
`sh ${q("cmd.sh")} > ${q("out")} 2> ${q("err")} < /dev/null`, // direct redirect → streaming-visible output
|
|
7
29
|
`__RC=$?`,
|
|
8
|
-
`echo $__RC > ${q("exit.tmp")} 2>/dev/null && mv ${q("exit.tmp")} ${q("exit")} 2>/dev/null`,
|
|
30
|
+
`echo $__RC > ${q("exit.tmp")} 2>/dev/null && mv ${q("exit.tmp")} ${q("exit")} 2>/dev/null`, // exit sentinel = atomic
|
|
9
31
|
].join("\n");
|
|
10
32
|
}
|
|
33
|
+
/** Short foreground launcher: writes cmd.sh + runner.sh, then `setsid`s the runner detached and returns immediately. */
|
|
11
34
|
export function buildBgLauncherScript(dir, cmdShB64, runnerShB64) {
|
|
12
35
|
const d = shellQuote(dir);
|
|
13
36
|
const q = (name) => shellQuote(`${dir}/${name}`);
|
|
@@ -15,14 +38,28 @@ export function buildBgLauncherScript(dir, cmdShB64, runnerShB64) {
|
|
|
15
38
|
`mkdir -p ${d} && chmod 700 ${d}`,
|
|
16
39
|
`printf %s '${cmdShB64}' | base64 -d > ${q("cmd.sh")}`,
|
|
17
40
|
`printf %s '${runnerShB64}' | base64 -d > ${q("runner.sh")}`,
|
|
41
|
+
// detach into a new session (survives the exec socket close — disposable-pod posture / startBackground precedent)
|
|
18
42
|
`setsid sh ${q("runner.sh")} >/dev/null 2>&1 &`,
|
|
43
|
+
// 🔴 Do NOT report launched until the runner has self-reported its pgid — otherwise a killBackground
|
|
44
|
+
// / timeout landing in the gap finds no pgid, no-ops, yet the manager marks the shell killed → a LEAKED process
|
|
45
|
+
// group. Bounded (~2s); the runner writes pgid as its very FIRST action, so this is one iteration in practice.
|
|
19
46
|
`__i=0; while [ ! -s ${q("pgid")} ] && [ $__i -lt 40 ]; do __i=$((__i+1)); sleep 0.05; done`,
|
|
47
|
+
// 🔴 (workflow MED) Only report launched if the runner actually started (pgid present). If setsid is missing or the
|
|
48
|
+
// runner crashed before writing pgid, report failure → the driver surfaces spawn_failed instead of a phantom
|
|
49
|
+
// "running" shell that never ends until the BG timeout.
|
|
20
50
|
`if [ -s ${q("pgid")} ]; then echo SEMABG_LAUNCHED; else echo SEMABG_NOPGID; fi`,
|
|
21
51
|
].join("\n");
|
|
22
52
|
}
|
|
53
|
+
/** Poll script: emit totals + liveness + exit, then the new bytes per stream (base64, bounded by `readCap`). */
|
|
23
54
|
export function buildBgPollScript(dir, stdoutCursor, stderrCursor, readCap) {
|
|
24
55
|
const d = shellQuote(dir);
|
|
25
|
-
const slice = (file, cursor) =>
|
|
56
|
+
const slice = (file, cursor) =>
|
|
57
|
+
// 🔴 (workflow MED) Read is bounded to the wc SNAPSHOT (__T), not to the live EOF — `head -c` caps the tail so a
|
|
58
|
+
// producer writing between `wc -c` and `tail` cannot return bytes past __T (the driver advances the cursor to __T,
|
|
59
|
+
// so reading past it would DUPLICATE next poll AND defeat the readCap transfer bound). ≤readCap new bytes → the
|
|
60
|
+
// whole increment [cursor, __T); otherwise the LAST readCap bytes [__T-readCap, __T) (recent tail, like TOC) and
|
|
61
|
+
// the control plane records the gap as bytesDroppedBeforeCursor.
|
|
62
|
+
[
|
|
26
63
|
`__N=$((__T - ${cursor}))`,
|
|
27
64
|
`if [ "$__N" -gt 0 ]; then`,
|
|
28
65
|
` if [ "$__N" -le ${readCap} ]; then tail -c +$(( ${cursor} + 1 )) ${d}/${file} 2>/dev/null | head -c "$__N" | base64;`,
|
|
@@ -31,6 +68,8 @@ export function buildBgPollScript(dir, stdoutCursor, stderrCursor, readCap) {
|
|
|
31
68
|
].join("\n");
|
|
32
69
|
return [
|
|
33
70
|
`if [ ! -d ${d} ]; then echo SEMABG_GONE; exit 0; fi`,
|
|
71
|
+
// `$(( ... + 0 ))` normalizes the byte count: it both defaults empty→0 AND strips the leading whitespace that
|
|
72
|
+
// BSD/macOS `wc -c < file` emits (GNU/Debian pods don't pad, but keep the META line parseable on either).
|
|
34
73
|
`__OT=$(wc -c < ${d}/out 2>/dev/null); __OT=$(( \${__OT:-0} + 0 ))`,
|
|
35
74
|
`__ET=$(wc -c < ${d}/err 2>/dev/null); __ET=$(( \${__ET:-0} + 0 ))`,
|
|
36
75
|
`__EX=$( [ -f ${d}/exit ] && cat ${d}/exit 2>/dev/null || echo - )`,
|
|
@@ -46,15 +85,33 @@ export function buildBgPollScript(dir, stdoutCursor, stderrCursor, readCap) {
|
|
|
46
85
|
`echo SEMABG_X`,
|
|
47
86
|
].join("\n");
|
|
48
87
|
}
|
|
88
|
+
/** KillShell: graceful SIGTERM to the group, then a forced SIGKILL (idempotent — a dead group just errors out).
|
|
89
|
+
* The `|| kill <pid>` fallback covers an ADOPTED exec whose pgid prelude self-reported `$$` without a `setsid`
|
|
90
|
+
* (whether the k8s-exec'd sh is a group leader is runtime-dependent); a launch-mode runner is always setsid'd,
|
|
91
|
+
* so its group kill succeeds and the fallback never fires there. */
|
|
49
92
|
export function buildBgKillScript(dir) {
|
|
50
93
|
const d = shellQuote(dir);
|
|
51
94
|
return [
|
|
95
|
+
// 🔴 review LOW (Fable-5 #4 tightened): an ADOPTED shell (create-then-abort detach) can be killed in the
|
|
96
|
+
// sub-second window BEFORE its detach-capable exec even ran `mkdir` — let alone forked the job + wrote pgid.
|
|
97
|
+
// A bare read (or a dir-existence gate: the dir does not exist YET in that window, indistinguishable from
|
|
98
|
+
// "already ended + EXIT-trap-removed") would skip the kill yet report SEMABG_KILLED — a silent no-op, the
|
|
99
|
+
// command escapes the wall. So wait UNCONDITIONALLY for the pgid file (bounded ~2s, mirroring the LAUNCH
|
|
100
|
+
// path's pre-report pgid gate). Cost of the removed dir-gate: a kill racing the command's natural end (dir
|
|
101
|
+
// already EXIT-trap-removed) idles the full ~2s before no-op'ing — acceptable, because driver.kill only runs
|
|
102
|
+
// while the manager still believes the shell is running (that overlap window is sub-second), and the pod is
|
|
103
|
+
// disposable. For a launch-mode shell pgid is already present → first iteration exits (no-op).
|
|
52
104
|
`__i=0; while [ ! -s ${d}/pgid ] && [ $__i -lt 40 ]; do __i=$((__i+1)); sleep 0.05; done`,
|
|
53
105
|
`__PG=$(cat ${d}/pgid 2>/dev/null); __PG=\${__PG:--}`,
|
|
54
106
|
`if [ "$__PG" != "-" ]; then kill -TERM -"$__PG" 2>/dev/null || kill -TERM "$__PG" 2>/dev/null; sleep 1; kill -KILL -"$__PG" 2>/dev/null || kill -KILL "$__PG" 2>/dev/null; fi`,
|
|
55
107
|
`echo SEMABG_KILLED`,
|
|
56
108
|
].join("\n");
|
|
57
109
|
}
|
|
110
|
+
/** dispose: forced SIGKILL to the group + remove the job dir (no grace — teardown). Best-effort.
|
|
111
|
+
* ⚠️ Deliberately NO pgid-wait (unlike {@link buildBgKillScript}, review #4): a create-then-abort adopt disposed
|
|
112
|
+
* in the pre-mkdir window escapes THIS script — but every dispose path ends in pod deletion (task-end destroy;
|
|
113
|
+
* suspendVM deletes the pod after the snapshot), so the escape is pod-lifetime-bounded, and a wait here would
|
|
114
|
+
* add 2s × N shells to the suspend/teardown path for a sub-second window. Kill (the model-facing wall) waits. */
|
|
58
115
|
export function buildBgDisposeScript(dir) {
|
|
59
116
|
const d = shellQuote(dir);
|
|
60
117
|
return [
|
|
@@ -64,27 +121,54 @@ export function buildBgDisposeScript(dir) {
|
|
|
64
121
|
`echo SEMABG_DISPOSED`,
|
|
65
122
|
].join("\n");
|
|
66
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* design/116 detach: wrap a foreground exec so it is ADOPTABLE mid-flight, running the command as a real
|
|
126
|
+
* process-group LEADER so an adopted shell's KillShell / BG hard wall reap the WHOLE subtree.
|
|
127
|
+
*
|
|
128
|
+
* 🔴 review MED (why `setsid`, not bare `$$`): the wrapper `sh` that `kubectl exec` starts has a RUNTIME-DEPENDENT
|
|
129
|
+
* process group — under many container runtimes it is NOT its own group leader (its pgid is some ancestor's), so
|
|
130
|
+
* writing `$$` as the pgid and later `kill -$$` targets a group that does not exist → the fallback `kill $$` reaps
|
|
131
|
+
* only the wrapper, orphaning the command's children PAST the §3.6 hard wall (they keep streaming into the adopted
|
|
132
|
+
* shell's buffer until the pod's `activeDeadlineSeconds`). `setsid` starts the command in a NEW session ⇒ a new
|
|
133
|
+
* process group whose leader pid IS the backgrounded job pid, so `kill -<pid>` reaps the command and every
|
|
134
|
+
* descendant. The command's stdout/stderr fds (the exec socket's channels) are INHERITED across setsid — only the
|
|
135
|
+
* controlling terminal is dropped (there is none: kubectl-exec here is non-tty) — so output/exit still flow on the
|
|
136
|
+
* SAME WebSocket, which IS the adopted shell's feed. `wait` propagates the real exit code.
|
|
137
|
+
*
|
|
138
|
+
* The command is base64'd into a pod-side `cmd.sh` (no quoting hazard, mirrors the launch path). The pgid is written
|
|
139
|
+
* atomically (tmp+mv) AFTER the job forks, so a poll never reads a half-written id. Every step is best-effort: a pod
|
|
140
|
+
* without `setsid` (rare on the kata base image) degrades to the `$$` path (the pre-fix behavior), and a read-only
|
|
141
|
+
* /tmp degrades to a pgid-less adopt (kill no-ops; the disposable pod's teardown reaps) — never a broken exec. The
|
|
142
|
+
* EXIT trap removes the dir when the command ends normally, so non-detached execs don't litter the pod.
|
|
143
|
+
*/
|
|
67
144
|
export function buildDetachCapableExec(dir, command) {
|
|
68
145
|
const d = shellQuote(dir);
|
|
69
146
|
const q = (name) => shellQuote(`${dir}/${name}`);
|
|
70
147
|
const cmdB64 = Buffer.from(command, "utf8").toString("base64");
|
|
71
148
|
const writePgid = (val) => `printf %s ${val} > ${q("pgid.tmp")} 2>/dev/null && mv ${q("pgid.tmp")} ${q("pgid")} 2>/dev/null`;
|
|
72
149
|
return [
|
|
150
|
+
// Decode the command into a VARIABLE (not a pod-side file): a read-only-/tmp degrade must still RUN the command
|
|
151
|
+
// (review's degrade-safety case) — a file target under an un-creatable dir would leave nothing to run. `sh -c
|
|
152
|
+
// "$__cmd"` is byte-equivalent to sourcing a cmd.sh whose content is $__cmd. base64 has no quoting hazard.
|
|
73
153
|
`__cmd=$(printf %s '${cmdB64}' | base64 -d)`,
|
|
74
154
|
`mkdir -p ${d} 2>/dev/null`,
|
|
75
155
|
`trap 'rm -rf ${d} 2>/dev/null' EXIT`,
|
|
76
156
|
`if command -v setsid >/dev/null 2>&1; then`,
|
|
157
|
+
// setsid backgrounds the job as a new-session group leader; $! is that leader's pid == its pgid. Output fds are
|
|
158
|
+
// inherited (the exec socket), only the tty is dropped. `wait` blocks the wrapper until the job ends (or is
|
|
159
|
+
// group-killed) and yields its exit code — so exec/adopt semantics are unchanged, only the reap target is fixed.
|
|
77
160
|
` setsid sh -c "$__cmd" &`,
|
|
78
161
|
` __pg=$!`,
|
|
79
162
|
` ${writePgid('"$__pg"')}`,
|
|
80
163
|
` wait "$__pg"; __rc=$?`,
|
|
81
164
|
`else`,
|
|
82
|
-
` ${writePgid('"$$"')}`,
|
|
165
|
+
` ${writePgid('"$$"')}`, // no setsid → best-effort $$ (pre-fix behavior; the pod base image ships setsid)
|
|
83
166
|
` sh -c "$__cmd"; __rc=$?`,
|
|
84
167
|
`fi`,
|
|
85
168
|
`exit $__rc`,
|
|
86
169
|
].join("\n");
|
|
87
170
|
}
|
|
171
|
+
/** Parse {@link buildBgPollScript} stdout. Markers contain `_` (absent from the base64 alphabet) so they never collide. */
|
|
88
172
|
export function parseBgPollOutput(raw) {
|
|
89
173
|
if (raw.includes("SEMABG_GONE")) {
|
|
90
174
|
return { gone: true, outTotal: 0, errTotal: 0, alive: false, stdout: "", stdoutBytes: 0, stderr: "", stderrBytes: 0 };
|
|
@@ -1,17 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The confined, ADDITIVE pod-spec patch ({@link PodSpecPatch}/{@link applyPodSpecPatch}) + the exec-socket TLS
|
|
3
|
+
* shape ({@link execSocketTlsOptions}) + the v4 channel status-frame parse ({@link exitCodeFromStatus}) — the
|
|
4
|
+
* k8s adapter's protocol/patch layer, independent of pod lifecycle. Split out of `remote-env-k8s.ts` (design/158
|
|
5
|
+
* A13, internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The confined, ADDITIVE pod-spec patch. Every field is purely additive —
|
|
9
|
+
* none can override the hard isolation invariants. This is the seam the RSI L8 immutable-mount probe + the
|
|
10
|
+
* graderEnvFactory need.
|
|
11
|
+
*/
|
|
1
12
|
export interface PodSpecPatch {
|
|
13
|
+
/** Appended to spec.volumes (e.g. a read-only configMap/secret/emptyDir for the immutable-oracle mount). */
|
|
2
14
|
volumes?: unknown[];
|
|
15
|
+
/** Appended to the sandbox container's volumeMounts (e.g. { name, mountPath, readOnly:true } — the RO mount). */
|
|
3
16
|
volumeMounts?: unknown[];
|
|
17
|
+
/** Merged into the POD-level securityContext (runAsNonRoot, fsGroup, seccompProfile, …). */
|
|
4
18
|
podSecurityContext?: Record<string, unknown>;
|
|
19
|
+
/** Merged into the sandbox CONTAINER's securityContext (readOnlyRootFilesystem, capabilities.drop,
|
|
20
|
+
* allowPrivilegeEscalation:false, …) — the hardening the L8 immutable-mount/isolation probes assert. */
|
|
5
21
|
containerSecurityContext?: Record<string, unknown>;
|
|
22
|
+
/** Extra labels merged into metadata.labels (e.g. a grader/probe tag). */
|
|
6
23
|
labels?: Record<string, string>;
|
|
7
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE. Volumes + volumeMounts are
|
|
27
|
+
* APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are merged. It
|
|
28
|
+
* deliberately touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
|
|
29
|
+
* metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken,
|
|
30
|
+
* restartPolicy, activeDeadlineSeconds, image, command) are preserved by construction. Pure + exported for testing.
|
|
31
|
+
*/
|
|
8
32
|
export declare function applyPodSpecPatch(pod: {
|
|
9
33
|
metadata: {
|
|
10
34
|
labels?: Record<string, string>;
|
|
11
35
|
};
|
|
12
36
|
spec: Record<string, unknown>;
|
|
13
37
|
}, patch: PodSpecPatch): void;
|
|
38
|
+
/**
|
|
39
|
+
* TLS options for the exec WebSocket, shaped for BOTH runtimes (2026-06-11 in-cluster drill finding):
|
|
40
|
+
* Bun's `ws` shim IGNORES node-ws's top-level `ca`/`rejectUnauthorized` (handshake fails against a
|
|
41
|
+
* cluster CA even with rejectUnauthorized:false) and reads a Bun-specific `tls` sub-object instead;
|
|
42
|
+
* Node's real `ws` uses the top-level keys and passes the unknown `tls` key harmlessly through to
|
|
43
|
+
* tls.connect. One merged shape serves both. Live-proven on build-host (probe: top-level ca → TLS handshake
|
|
44
|
+
* failed; tls.ca → OPEN).
|
|
45
|
+
*/
|
|
14
46
|
export declare function execSocketTlsOptions(ca: string | undefined, insecureTls: boolean): Record<string, unknown>;
|
|
47
|
+
/** Parse the v4.channel.k8s.io status frame (channel 3) into an exit code, or a typed failure. */
|
|
15
48
|
export declare function exitCodeFromStatus(statusJson: string): {
|
|
16
49
|
exitCode: number;
|
|
17
50
|
} | {
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The confined, ADDITIVE pod-spec patch ({@link PodSpecPatch}/{@link applyPodSpecPatch}) + the exec-socket TLS
|
|
3
|
+
* shape ({@link execSocketTlsOptions}) + the v4 channel status-frame parse ({@link exitCodeFromStatus}) — the
|
|
4
|
+
* k8s adapter's protocol/patch layer, independent of pod lifecycle. Split out of `remote-env-k8s.ts` (design/158
|
|
5
|
+
* A13, internal-lossless — `remote-env-k8s.ts` re-exports every symbol below unchanged).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Apply a {@link PodSpecPatch} to a sandbox pod object IN PLACE — purely ADDITIVE. Volumes + volumeMounts are
|
|
9
|
+
* APPENDED (never replaced); securityContext objects are shallow-MERGED onto any existing; labels are merged. It
|
|
10
|
+
* deliberately touches ONLY spec.volumes / spec.securityContext / containers[0].{volumeMounts,securityContext} /
|
|
11
|
+
* metadata.labels — so the hard isolation invariants (runtimeClassName, automountServiceAccountToken,
|
|
12
|
+
* restartPolicy, activeDeadlineSeconds, image, command) are preserved by construction. Pure + exported for testing.
|
|
13
|
+
*/
|
|
1
14
|
export function applyPodSpecPatch(pod, patch) {
|
|
15
|
+
// 🔴 wq64gmm5e: a securityContext patch is a HARDENING seam — it may only TIGHTEN, never weaken the isolation
|
|
16
|
+
// the L8 gradeP1 premise depends on. A blind spread let privileged:true / allowPrivilegeEscalation:true /
|
|
17
|
+
// capabilities.add[SYS_ADMIN] / readOnlyRootFilesystem:false through verbatim (and the "cannot weaken" test was
|
|
18
|
+
// fake-green). Fail CLOSED on any privilege-escalating key so the docstring claim is actually enforced.
|
|
2
19
|
assertHardeningOnly(patch.podSecurityContext, "podSecurityContext");
|
|
3
20
|
assertHardeningOnly(patch.containerSecurityContext, "containerSecurityContext");
|
|
4
21
|
if (patch.labels) {
|
|
22
|
+
// Additive + identity-preserving: merge the patch labels, then re-assert the base identity labels LAST so a
|
|
23
|
+
// patch can never clobber the app/managed-by labels the RBAC selectors + reaper key on (wq64gmm5e).
|
|
5
24
|
const identity = {};
|
|
6
25
|
for (const k of ["app", "managed-by"]) {
|
|
7
26
|
const v = pod.metadata.labels?.[k];
|
|
@@ -11,6 +30,12 @@ export function applyPodSpecPatch(pod, patch) {
|
|
|
11
30
|
pod.metadata.labels = { ...pod.metadata.labels, ...patch.labels, ...identity };
|
|
12
31
|
}
|
|
13
32
|
const spec = pod.spec;
|
|
33
|
+
// 🔴 Volumes are the SAME isolation-breach class as a privileged securityContext (review: workflow HIGH). Kata
|
|
34
|
+
// passes a hostPath through into the guest VM (virtio-fs/9p host share — NOT blocked by the VM boundary), so a
|
|
35
|
+
// `hostPath:{path:"/"}` volume mounts the k3s NODE root (containerd sock, other pods, host secrets) into the
|
|
36
|
+
// sandbox — defeating the L8 immutable-oracle integrity premise as completely as privileged:true. Reject any
|
|
37
|
+
// host-backed volume source; only pod-local sources (emptyDir/configMap/secret/projected/downwardAPI/ephemeral)
|
|
38
|
+
// are additive-safe.
|
|
14
39
|
assertVolumesPodLocal(patch.volumes, "volumes");
|
|
15
40
|
if (patch.volumes?.length)
|
|
16
41
|
spec.volumes = [...(spec.volumes ?? []), ...patch.volumes];
|
|
@@ -24,6 +49,8 @@ export function applyPodSpecPatch(pod, patch) {
|
|
|
24
49
|
container.securityContext = { ...(container.securityContext ?? {}), ...patch.containerSecurityContext };
|
|
25
50
|
}
|
|
26
51
|
}
|
|
52
|
+
/** Fail-closed direction guard for a securityContext patch: reject any key that WEAKENS isolation. A patch may
|
|
53
|
+
* drop capabilities + tighten, never escalate. Throws (caller surfaces as a provisioning failure). */
|
|
27
54
|
function assertHardeningOnly(sc, where) {
|
|
28
55
|
if (!sc)
|
|
29
56
|
return;
|
|
@@ -43,12 +70,16 @@ function assertHardeningOnly(sc, where) {
|
|
|
43
70
|
const caps = sc.capabilities;
|
|
44
71
|
if (caps && Array.isArray(caps.add) && caps.add.length > 0)
|
|
45
72
|
bad(`capabilities.add ${JSON.stringify(caps.add)} (a hardening patch may only .drop)`);
|
|
73
|
+
// procMount:"Unmasked" re-exposes the masked /proc paths; seccompProfile:"Unconfined" disables syscall filtering —
|
|
74
|
+
// both WEAKEN confinement (review: workflow HIGH secondary), so they belong on the fail-closed list.
|
|
46
75
|
if (sc.procMount === "Unmasked")
|
|
47
76
|
bad('procMount:"Unmasked" (re-exposes masked /proc)');
|
|
48
77
|
const seccomp = sc.seccompProfile;
|
|
49
78
|
if (seccomp && seccomp.type === "Unconfined")
|
|
50
79
|
bad('seccompProfile.type:"Unconfined" (disables syscall filtering)');
|
|
51
80
|
}
|
|
81
|
+
/** Fail-closed guard for patch volumes: a Kata guest shares hostPath/csi/flexVolume sources from the NODE, so only
|
|
82
|
+
* POD-LOCAL sources are additive-safe. Reject anything host-backed. Throws (caller surfaces as a provisioning fail). */
|
|
52
83
|
function assertVolumesPodLocal(volumes, where) {
|
|
53
84
|
if (!volumes?.length)
|
|
54
85
|
return;
|
|
@@ -63,10 +94,19 @@ function assertVolumesPodLocal(volumes, where) {
|
|
|
63
94
|
}
|
|
64
95
|
}
|
|
65
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* TLS options for the exec WebSocket, shaped for BOTH runtimes (2026-06-11 in-cluster drill finding):
|
|
99
|
+
* Bun's `ws` shim IGNORES node-ws's top-level `ca`/`rejectUnauthorized` (handshake fails against a
|
|
100
|
+
* cluster CA even with rejectUnauthorized:false) and reads a Bun-specific `tls` sub-object instead;
|
|
101
|
+
* Node's real `ws` uses the top-level keys and passes the unknown `tls` key harmlessly through to
|
|
102
|
+
* tls.connect. One merged shape serves both. Live-proven on build-host (probe: top-level ca → TLS handshake
|
|
103
|
+
* failed; tls.ca → OPEN).
|
|
104
|
+
*/
|
|
66
105
|
export function execSocketTlsOptions(ca, insecureTls) {
|
|
67
106
|
const caList = ca ? [ca] : undefined;
|
|
68
107
|
return { ca: caList, rejectUnauthorized: !insecureTls, tls: { ca: caList, rejectUnauthorized: !insecureTls } };
|
|
69
108
|
}
|
|
109
|
+
/** Parse the v4.channel.k8s.io status frame (channel 3) into an exit code, or a typed failure. */
|
|
70
110
|
export function exitCodeFromStatus(statusJson) {
|
|
71
111
|
try {
|
|
72
112
|
const s = JSON.parse(statusJson);
|
|
@@ -74,6 +114,7 @@ export function exitCodeFromStatus(statusJson) {
|
|
|
74
114
|
return { exitCode: 0 };
|
|
75
115
|
const cause = s.details?.causes?.find((c) => c.reason === "ExitCode");
|
|
76
116
|
if (cause?.message?.trim()) {
|
|
117
|
+
// trim-guard: Number("")/Number(" ") === 0 — an empty message must NOT read as a fake exit 0 on a Failure
|
|
77
118
|
const code = Number(cause.message);
|
|
78
119
|
if (Number.isFinite(code))
|
|
79
120
|
return { exitCode: code };
|