smol-symphony 0.2.0 → 0.3.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/AGENTS.md +41 -22
- package/DESIGN.md +494 -273
- package/README.md +109 -57
- package/SPEC.md +33 -24
- package/WORKFLOW.minimal.yaml +34 -0
- package/{WORKFLOW.template.md → WORKFLOW.template.yaml} +409 -256
- package/WORKFLOW.yaml +487 -0
- package/assets/skills/symphony-issues/SKILL.md +136 -0
- package/assets/symphony-mise.system.toml +68 -0
- package/dist/src/bin/symphony.js +30 -0
- package/dist/src/bin/symphony.js.map +1 -0
- package/dist/src/core/actions/context.js +109 -0
- package/dist/src/core/actions/context.js.map +1 -0
- package/dist/{actions/parsing.js → src/core/actions/parse.js} +33 -114
- package/dist/src/core/actions/parse.js.map +1 -0
- package/dist/src/core/actions/plan.js +197 -0
- package/dist/src/core/actions/plan.js.map +1 -0
- package/dist/src/core/actions/predicates.js +111 -0
- package/dist/src/core/actions/predicates.js.map +1 -0
- package/dist/src/core/actions/run-fold.js +248 -0
- package/dist/src/core/actions/run-fold.js.map +1 -0
- package/dist/src/core/actions/template.js +118 -0
- package/dist/src/core/actions/template.js.map +1 -0
- package/dist/src/core/cli/args.js +116 -0
- package/dist/src/core/cli/args.js.map +1 -0
- package/dist/src/core/coerce.js +75 -0
- package/dist/src/core/coerce.js.map +1 -0
- package/dist/src/core/credential/account-id.js +20 -0
- package/dist/src/core/credential/account-id.js.map +1 -0
- package/dist/src/core/credential/adapter-config.js +136 -0
- package/dist/src/core/credential/adapter-config.js.map +1 -0
- package/dist/src/core/credential/availability.js +98 -0
- package/dist/src/core/credential/availability.js.map +1 -0
- package/dist/src/core/credential/extract.js +228 -0
- package/dist/src/core/credential/extract.js.map +1 -0
- package/dist/src/core/credential/fake-creds.js +171 -0
- package/dist/src/core/credential/fake-creds.js.map +1 -0
- package/dist/src/core/credential/identity.js +125 -0
- package/dist/src/core/credential/identity.js.map +1 -0
- package/dist/src/core/credential/shape.js +230 -0
- package/dist/src/core/credential/shape.js.map +1 -0
- package/dist/src/core/credential/strings.js +15 -0
- package/dist/src/core/credential/strings.js.map +1 -0
- package/dist/src/core/doctor/checks.js +303 -0
- package/dist/src/core/doctor/checks.js.map +1 -0
- package/dist/src/core/git/result.js +107 -0
- package/dist/src/core/git/result.js.map +1 -0
- package/dist/src/core/http/decisions.js +225 -0
- package/dist/src/core/http/decisions.js.map +1 -0
- package/dist/{http.js → src/core/http/render.js} +472 -738
- package/dist/src/core/http/render.js.map +1 -0
- package/dist/{http-handlers.js → src/core/http/routes.js} +52 -87
- package/dist/src/core/http/routes.js.map +1 -0
- package/dist/src/core/http/views.js +181 -0
- package/dist/src/core/http/views.js.map +1 -0
- package/dist/src/core/image/managed-image.js +95 -0
- package/dist/src/core/image/managed-image.js.map +1 -0
- package/dist/src/core/issue/file.js +149 -0
- package/dist/src/core/issue/file.js.map +1 -0
- package/dist/src/core/issue/parse.js +210 -0
- package/dist/src/core/issue/parse.js.map +1 -0
- package/dist/src/core/mcp/dispatch.js +239 -0
- package/dist/src/core/mcp/dispatch.js.map +1 -0
- package/dist/src/core/mcp/post-move.js +92 -0
- package/dist/src/core/mcp/post-move.js.map +1 -0
- package/dist/src/core/mcp/protocol.js +293 -0
- package/dist/src/core/mcp/protocol.js.map +1 -0
- package/dist/src/core/mcp/url.js +162 -0
- package/dist/src/core/mcp/url.js.map +1 -0
- package/dist/src/core/path.js +63 -0
- package/dist/src/core/path.js.map +1 -0
- package/dist/src/core/reconcile/image-decide.js +48 -0
- package/dist/src/core/reconcile/image-decide.js.map +1 -0
- package/dist/src/core/reconcile/ledger.js +142 -0
- package/dist/src/core/reconcile/ledger.js.map +1 -0
- package/dist/src/core/reconcile/pr-classify.js +62 -0
- package/dist/src/core/reconcile/pr-classify.js.map +1 -0
- package/dist/{reconciler → src/core/reconcile}/pr-decide.js +25 -12
- package/dist/src/core/reconcile/pr-decide.js.map +1 -0
- package/dist/src/core/reconcile/pr-loop.js +161 -0
- package/dist/src/core/reconcile/pr-loop.js.map +1 -0
- package/dist/src/core/reconcile/pr-notes.js +35 -0
- package/dist/src/core/reconcile/pr-notes.js.map +1 -0
- package/dist/src/core/reconcile/vm-decide.js +70 -0
- package/dist/src/core/reconcile/vm-decide.js.map +1 -0
- package/dist/src/core/reconcile/vm-reap.js +207 -0
- package/dist/src/core/reconcile/vm-reap.js.map +1 -0
- package/dist/src/core/reconcile/workspace-decide.js +162 -0
- package/dist/src/core/reconcile/workspace-decide.js.map +1 -0
- package/dist/src/core/runlog/summary.js +231 -0
- package/dist/src/core/runlog/summary.js.map +1 -0
- package/dist/src/core/runner/dispatch-config.js +95 -0
- package/dist/src/core/runner/dispatch-config.js.map +1 -0
- package/dist/src/core/runner/injection.js +61 -0
- package/dist/src/core/runner/injection.js.map +1 -0
- package/dist/src/core/runner/mise.js +210 -0
- package/dist/src/core/runner/mise.js.map +1 -0
- package/dist/src/core/runner/prompt.js +720 -0
- package/dist/src/core/runner/prompt.js.map +1 -0
- package/dist/src/core/runner/turn.js +242 -0
- package/dist/src/core/runner/turn.js.map +1 -0
- package/dist/src/core/runner/vm-plan.js +390 -0
- package/dist/src/core/runner/vm-plan.js.map +1 -0
- package/dist/src/core/schedule/admission.js +123 -0
- package/dist/src/core/schedule/admission.js.map +1 -0
- package/dist/src/core/schedule/circuit-breaker.js +111 -0
- package/dist/src/core/schedule/circuit-breaker.js.map +1 -0
- package/dist/src/core/schedule/eligibility.js +83 -0
- package/dist/src/core/schedule/eligibility.js.map +1 -0
- package/dist/src/core/schedule/reconcile-issue.js +82 -0
- package/dist/src/core/schedule/reconcile-issue.js.map +1 -0
- package/dist/src/core/schedule/retry.js +96 -0
- package/dist/src/core/schedule/retry.js.map +1 -0
- package/dist/src/core/schedule/sleep-cycle.js +133 -0
- package/dist/src/core/schedule/sleep-cycle.js.map +1 -0
- package/dist/src/core/schedule/slots.js +124 -0
- package/dist/src/core/schedule/slots.js.map +1 -0
- package/dist/src/core/schedule/tick.js +553 -0
- package/dist/src/core/schedule/tick.js.map +1 -0
- package/dist/src/core/schedule/token-fold.js +181 -0
- package/dist/src/core/schedule/token-fold.js.map +1 -0
- package/dist/src/core/state-resolve.js +86 -0
- package/dist/src/core/state-resolve.js.map +1 -0
- package/dist/src/core/vm-guards.js +278 -0
- package/dist/src/core/vm-guards.js.map +1 -0
- package/dist/src/core/workflow/derive.js +107 -0
- package/dist/src/core/workflow/derive.js.map +1 -0
- package/dist/src/core/workflow/parse.js +687 -0
- package/dist/src/core/workflow/parse.js.map +1 -0
- package/dist/src/core/workflow/prompt-probe.js +78 -0
- package/dist/src/core/workflow/prompt-probe.js.map +1 -0
- package/dist/src/core/workflow/validate.js +189 -0
- package/dist/src/core/workflow/validate.js.map +1 -0
- package/dist/src/core/workspace-key.js +19 -0
- package/dist/src/core/workspace-key.js.map +1 -0
- package/dist/src/shell/actions-runner.js +356 -0
- package/dist/src/shell/actions-runner.js.map +1 -0
- package/dist/src/shell/adapter/adapter-registry.js +45 -0
- package/dist/src/shell/adapter/adapter-registry.js.map +1 -0
- package/dist/src/shell/adapter/clock-random.js +96 -0
- package/dist/src/shell/adapter/clock-random.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-dispatch-helpers.js +158 -0
- package/dist/src/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-dispatch.js +385 -0
- package/dist/src/shell/adapter/gondolin-dispatch.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-image-converter.js +233 -0
- package/dist/src/shell/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/src/shell/adapter/gondolin-image-fetch.js +180 -0
- package/dist/src/shell/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/src/shell/adapter/launcher-asset.js +57 -0
- package/dist/src/shell/adapter/launcher-asset.js.map +1 -0
- package/dist/src/shell/adapter/mise-config-asset.js +65 -0
- package/dist/src/shell/adapter/mise-config-asset.js.map +1 -0
- package/dist/src/shell/adapter/workflow-loader.js +304 -0
- package/dist/src/shell/adapter/workflow-loader.js.map +1 -0
- package/dist/src/shell/cli/doctor.js +268 -0
- package/dist/src/shell/cli/doctor.js.map +1 -0
- package/dist/src/shell/effect-interpreter-families.js +314 -0
- package/dist/src/shell/effect-interpreter-families.js.map +1 -0
- package/dist/src/shell/effect-interpreter.js +29 -0
- package/dist/src/shell/effect-interpreter.js.map +1 -0
- package/dist/src/shell/interp/acp-frame.js +137 -0
- package/dist/src/shell/interp/acp-frame.js.map +1 -0
- package/dist/src/shell/interp/acp-ws-conn.js +320 -0
- package/dist/src/shell/interp/acp-ws-conn.js.map +1 -0
- package/dist/src/shell/interp/acp-ws-frames.js +159 -0
- package/dist/src/shell/interp/acp-ws-frames.js.map +1 -0
- package/dist/src/shell/interp/acp-ws.js +197 -0
- package/dist/src/shell/interp/acp-ws.js.map +1 -0
- package/dist/src/shell/interp/acp.js +319 -0
- package/dist/src/shell/interp/acp.js.map +1 -0
- package/dist/src/shell/interp/credential-defaults.js +128 -0
- package/dist/src/shell/interp/credential-defaults.js.map +1 -0
- package/dist/src/shell/interp/credential-hooks.js +149 -0
- package/dist/src/shell/interp/credential-hooks.js.map +1 -0
- package/dist/src/shell/interp/credential-registry.js +226 -0
- package/dist/src/shell/interp/credential-registry.js.map +1 -0
- package/dist/src/shell/interp/credential.js +103 -0
- package/dist/src/shell/interp/credential.js.map +1 -0
- package/dist/src/shell/interp/gh.js +163 -0
- package/dist/src/shell/interp/gh.js.map +1 -0
- package/dist/src/shell/interp/git.js +28 -0
- package/dist/src/shell/interp/git.js.map +1 -0
- package/dist/src/shell/interp/log.js +213 -0
- package/dist/src/shell/interp/log.js.map +1 -0
- package/dist/src/shell/interp/process.js +178 -0
- package/dist/src/shell/interp/process.js.map +1 -0
- package/dist/src/shell/interp/runlog.js +193 -0
- package/dist/src/shell/interp/runlog.js.map +1 -0
- package/dist/src/shell/interp/timer.js +64 -0
- package/dist/src/shell/interp/timer.js.map +1 -0
- package/dist/src/shell/interp/tracker-disk.js +99 -0
- package/dist/src/shell/interp/tracker-disk.js.map +1 -0
- package/dist/src/shell/interp/tracker-parse.js +71 -0
- package/dist/src/shell/interp/tracker-parse.js.map +1 -0
- package/dist/src/shell/interp/tracker-scan.js +238 -0
- package/dist/src/shell/interp/tracker-scan.js.map +1 -0
- package/dist/src/shell/interp/tracker-write.js +91 -0
- package/dist/src/shell/interp/tracker-write.js.map +1 -0
- package/dist/src/shell/interp/tracker.js +41 -0
- package/dist/src/shell/interp/tracker.js.map +1 -0
- package/dist/src/shell/interp/tty.js +48 -0
- package/dist/src/shell/interp/tty.js.map +1 -0
- package/dist/src/shell/interp/vm.js +199 -0
- package/dist/src/shell/interp/vm.js.map +1 -0
- package/dist/src/shell/interp/workspace.js +310 -0
- package/dist/src/shell/interp/workspace.js.map +1 -0
- package/dist/src/shell/main-acp.js +78 -0
- package/dist/src/shell/main-acp.js.map +1 -0
- package/dist/src/shell/main-adapters.js +222 -0
- package/dist/src/shell/main-adapters.js.map +1 -0
- package/dist/src/shell/main-credential.js +122 -0
- package/dist/src/shell/main-credential.js.map +1 -0
- package/dist/src/shell/main-doctor.js +22 -0
- package/dist/src/shell/main-doctor.js.map +1 -0
- package/dist/src/shell/main-entry.js +46 -0
- package/dist/src/shell/main-entry.js.map +1 -0
- package/dist/src/shell/main-http-csrf.js +45 -0
- package/dist/src/shell/main-http-csrf.js.map +1 -0
- package/dist/src/shell/main-http-handler.js +389 -0
- package/dist/src/shell/main-http-handler.js.map +1 -0
- package/dist/src/shell/main-http-mcp.js +122 -0
- package/dist/src/shell/main-http-mcp.js.map +1 -0
- package/dist/src/shell/main-http-views.js +253 -0
- package/dist/src/shell/main-http-views.js.map +1 -0
- package/dist/src/shell/main-http.js +76 -0
- package/dist/src/shell/main-http.js.map +1 -0
- package/dist/src/shell/main-loops.js +130 -0
- package/dist/src/shell/main-loops.js.map +1 -0
- package/dist/src/shell/main-mcp.js +129 -0
- package/dist/src/shell/main-mcp.js.map +1 -0
- package/dist/src/shell/main-orchestrator.js +120 -0
- package/dist/src/shell/main-orchestrator.js.map +1 -0
- package/dist/src/shell/main-preflight.js +43 -0
- package/dist/src/shell/main-preflight.js.map +1 -0
- package/dist/src/shell/main-reconcilers-helpers.js +244 -0
- package/dist/src/shell/main-reconcilers-helpers.js.map +1 -0
- package/dist/src/shell/main-reconcilers-pr.js +148 -0
- package/dist/src/shell/main-reconcilers-pr.js.map +1 -0
- package/dist/src/shell/main-reconcilers.js +225 -0
- package/dist/src/shell/main-reconcilers.js.map +1 -0
- package/dist/src/shell/main-runner.js +355 -0
- package/dist/src/shell/main-runner.js.map +1 -0
- package/dist/src/shell/main-scaffold.js +116 -0
- package/dist/src/shell/main-scaffold.js.map +1 -0
- package/dist/src/shell/main-shutdown.js +115 -0
- package/dist/src/shell/main-shutdown.js.map +1 -0
- package/dist/src/shell/main-startup.js +48 -0
- package/dist/src/shell/main-startup.js.map +1 -0
- package/dist/src/shell/main-substrates.js +43 -0
- package/dist/src/shell/main-substrates.js.map +1 -0
- package/dist/src/shell/main.js +385 -0
- package/dist/src/shell/main.js.map +1 -0
- package/dist/src/shell/orchestrator-feedback.js +69 -0
- package/dist/src/shell/orchestrator-feedback.js.map +1 -0
- package/dist/src/shell/orchestrator-image.js +167 -0
- package/dist/src/shell/orchestrator-image.js.map +1 -0
- package/dist/src/shell/orchestrator-loop.js +468 -0
- package/dist/src/shell/orchestrator-loop.js.map +1 -0
- package/dist/src/shell/orchestrator-reconcile.js +36 -0
- package/dist/src/shell/orchestrator-reconcile.js.map +1 -0
- package/dist/src/shell/reconciler-loop.js +228 -0
- package/dist/src/shell/reconciler-loop.js.map +1 -0
- package/dist/src/shell/runner-loop-turn.js +301 -0
- package/dist/src/shell/runner-loop-turn.js.map +1 -0
- package/dist/src/shell/runner-loop.js +338 -0
- package/dist/src/shell/runner-loop.js.map +1 -0
- package/dist/src/shell/server/http.js +208 -0
- package/dist/src/shell/server/http.js.map +1 -0
- package/dist/src/shell/server/mcp-runtime-effects.js +237 -0
- package/dist/src/shell/server/mcp-runtime-effects.js.map +1 -0
- package/dist/src/shell/server/mcp-runtime.js +99 -0
- package/dist/src/shell/server/mcp-runtime.js.map +1 -0
- package/dist/src/shell/workspace-key.js +14 -0
- package/dist/src/shell/workspace-key.js.map +1 -0
- package/dist/src/types/acp.js +8 -0
- package/dist/src/types/acp.js.map +1 -0
- package/dist/src/types/actions/plan.js +6 -0
- package/dist/src/types/actions/plan.js.map +1 -0
- package/dist/src/types/actions/predicates.js +6 -0
- package/dist/src/types/actions/predicates.js.map +1 -0
- package/dist/src/types/actions/run-fold.js +8 -0
- package/dist/src/types/actions/run-fold.js.map +1 -0
- package/dist/src/types/actions.js +7 -0
- package/dist/src/types/actions.js.map +1 -0
- package/dist/src/types/adapter/clock-random.js +4 -0
- package/dist/src/types/adapter/clock-random.js.map +1 -0
- package/dist/src/types/adapter/gondolin-image-converter.js +5 -0
- package/dist/src/types/adapter/gondolin-image-converter.js.map +1 -0
- package/dist/src/types/adapter/gondolin-image-fetch.js +5 -0
- package/dist/src/types/adapter/gondolin-image-fetch.js.map +1 -0
- package/dist/src/types/adapter/workflow-loader.js +4 -0
- package/dist/src/types/adapter/workflow-loader.js.map +1 -0
- package/dist/src/types/cli/args.js +8 -0
- package/dist/src/types/cli/args.js.map +1 -0
- package/dist/src/types/config.js +8 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/credential-interp.js +6 -0
- package/dist/src/types/credential-interp.js.map +1 -0
- package/dist/src/types/credentials.js +10 -0
- package/dist/src/types/credentials.js.map +1 -0
- package/dist/src/types/doctor.js +7 -0
- package/dist/src/types/doctor.js.map +1 -0
- package/dist/src/types/domain.js +7 -0
- package/dist/src/types/domain.js.map +1 -0
- package/dist/src/types/effect.js +15 -0
- package/dist/src/types/effect.js.map +1 -0
- package/dist/src/types/errors.js +39 -0
- package/dist/src/types/errors.js.map +1 -0
- package/dist/src/types/http/decisions.js +6 -0
- package/dist/src/types/http/decisions.js.map +1 -0
- package/dist/src/types/http/render.js +10 -0
- package/dist/src/types/http/render.js.map +1 -0
- package/dist/src/types/http/views.js +6 -0
- package/dist/src/types/http/views.js.map +1 -0
- package/dist/src/types/http.js +9 -0
- package/dist/src/types/http.js.map +1 -0
- package/dist/src/types/image/managed-image.js +7 -0
- package/dist/src/types/image/managed-image.js.map +1 -0
- package/dist/src/types/interp/effect-interpreter.js +8 -0
- package/dist/src/types/interp/effect-interpreter.js.map +1 -0
- package/dist/src/types/interp/tracker.js +7 -0
- package/dist/src/types/interp/tracker.js.map +1 -0
- package/dist/src/types/issue/file.js +6 -0
- package/dist/src/types/issue/file.js.map +1 -0
- package/dist/src/types/issue/parse.js +8 -0
- package/dist/src/types/issue/parse.js.map +1 -0
- package/dist/src/types/main-acp.js +13 -0
- package/dist/src/types/main-acp.js.map +1 -0
- package/dist/src/types/main-adapters.js +5 -0
- package/dist/src/types/main-adapters.js.map +1 -0
- package/dist/src/types/main-credential.js +21 -0
- package/dist/src/types/main-credential.js.map +1 -0
- package/dist/src/types/main-doctor.js +6 -0
- package/dist/src/types/main-doctor.js.map +1 -0
- package/dist/src/types/main-http-handler.js +12 -0
- package/dist/src/types/main-http-handler.js.map +1 -0
- package/dist/src/types/main-http.js +5 -0
- package/dist/src/types/main-http.js.map +1 -0
- package/dist/src/types/main-loops.js +5 -0
- package/dist/src/types/main-loops.js.map +1 -0
- package/dist/src/types/main-mcp.js +12 -0
- package/dist/src/types/main-mcp.js.map +1 -0
- package/dist/src/types/main-orchestrator.js +5 -0
- package/dist/src/types/main-orchestrator.js.map +1 -0
- package/dist/src/types/main-reconcilers.js +11 -0
- package/dist/src/types/main-reconcilers.js.map +1 -0
- package/dist/src/types/main-runner.js +13 -0
- package/dist/src/types/main-runner.js.map +1 -0
- package/dist/src/types/main-startup.js +5 -0
- package/dist/src/types/main-startup.js.map +1 -0
- package/dist/src/types/main-substrates.js +5 -0
- package/dist/src/types/main-substrates.js.map +1 -0
- package/dist/src/types/mcp/dispatch.js +4 -0
- package/dist/src/types/mcp/dispatch.js.map +1 -0
- package/dist/src/types/mcp/post-move.js +7 -0
- package/dist/src/types/mcp/post-move.js.map +1 -0
- package/dist/src/types/mcp.js +9 -0
- package/dist/src/types/mcp.js.map +1 -0
- package/dist/src/types/ports.js +12 -0
- package/dist/src/types/ports.js.map +1 -0
- package/dist/src/types/reconcile/image-decide.js +5 -0
- package/dist/src/types/reconcile/image-decide.js.map +1 -0
- package/dist/src/types/reconcile/ledger.js +7 -0
- package/dist/src/types/reconcile/ledger.js.map +1 -0
- package/dist/src/types/reconcile/pr-loop.js +8 -0
- package/dist/src/types/reconcile/pr-loop.js.map +1 -0
- package/dist/src/types/reconcile/vm-reap.js +8 -0
- package/dist/src/types/reconcile/vm-reap.js.map +1 -0
- package/dist/src/types/reconcile/workspace-decide.js +7 -0
- package/dist/src/types/reconcile/workspace-decide.js.map +1 -0
- package/dist/src/types/reconcile.js +9 -0
- package/dist/src/types/reconcile.js.map +1 -0
- package/dist/src/types/runlog.js +7 -0
- package/dist/src/types/runlog.js.map +1 -0
- package/dist/src/types/runner/actions-runner.js +12 -0
- package/dist/src/types/runner/actions-runner.js.map +1 -0
- package/dist/src/types/runner/gondolin-dispatch.js +5 -0
- package/dist/src/types/runner/gondolin-dispatch.js.map +1 -0
- package/dist/src/types/runner/injection.js +6 -0
- package/dist/src/types/runner/injection.js.map +1 -0
- package/dist/src/types/runner/runner-loop.js +5 -0
- package/dist/src/types/runner/runner-loop.js.map +1 -0
- package/dist/src/types/runner/turn.js +4 -0
- package/dist/src/types/runner/turn.js.map +1 -0
- package/dist/src/types/runner/vm-plan.js +4 -0
- package/dist/src/types/runner/vm-plan.js.map +1 -0
- package/dist/src/types/runtime.js +9 -0
- package/dist/src/types/runtime.js.map +1 -0
- package/dist/src/types/schedule/admission.js +7 -0
- package/dist/src/types/schedule/admission.js.map +1 -0
- package/dist/src/types/schedule/circuit-breaker.js +2 -0
- package/dist/src/types/schedule/circuit-breaker.js.map +1 -0
- package/dist/src/types/schedule/eligibility.js +9 -0
- package/dist/src/types/schedule/eligibility.js.map +1 -0
- package/dist/src/types/schedule/orchestrator-loop.js +10 -0
- package/dist/src/types/schedule/orchestrator-loop.js.map +1 -0
- package/dist/src/types/schedule/sleep-cycle.js +4 -0
- package/dist/src/types/schedule/sleep-cycle.js.map +1 -0
- package/dist/src/types/schedule/slots.js +8 -0
- package/dist/src/types/schedule/slots.js.map +1 -0
- package/dist/src/types/schedule/tick.js +9 -0
- package/dist/src/types/schedule/tick.js.map +1 -0
- package/dist/src/types/server/mcp-runtime.js +8 -0
- package/dist/src/types/server/mcp-runtime.js.map +1 -0
- package/dist/src/types/workflow/parse.js +4 -0
- package/dist/src/types/workflow/parse.js.map +1 -0
- package/dist/tests/core/account-id.test.js +35 -0
- package/dist/tests/core/account-id.test.js.map +1 -0
- package/dist/tests/core/actions-parse.test.js +176 -0
- package/dist/tests/core/actions-parse.test.js.map +1 -0
- package/dist/tests/core/adapter-config.test.js +133 -0
- package/dist/tests/core/adapter-config.test.js.map +1 -0
- package/dist/tests/core/admission.test.js +215 -0
- package/dist/tests/core/admission.test.js.map +1 -0
- package/dist/tests/core/args.test.js +132 -0
- package/dist/tests/core/args.test.js.map +1 -0
- package/dist/tests/core/availability.test.js +62 -0
- package/dist/tests/core/availability.test.js.map +1 -0
- package/dist/tests/core/checks.test.js +395 -0
- package/dist/tests/core/checks.test.js.map +1 -0
- package/dist/tests/core/circuit-breaker.test.js +172 -0
- package/dist/tests/core/circuit-breaker.test.js.map +1 -0
- package/dist/tests/core/coerce.test.js +87 -0
- package/dist/tests/core/coerce.test.js.map +1 -0
- package/dist/tests/core/context.test.js +228 -0
- package/dist/tests/core/context.test.js.map +1 -0
- package/dist/tests/core/decisions.test.js +310 -0
- package/dist/tests/core/decisions.test.js.map +1 -0
- package/dist/tests/core/derive.test.js +205 -0
- package/dist/tests/core/derive.test.js.map +1 -0
- package/dist/tests/core/dispatch-config.test.js +164 -0
- package/dist/tests/core/dispatch-config.test.js.map +1 -0
- package/dist/tests/core/dispatch.test.js +302 -0
- package/dist/tests/core/dispatch.test.js.map +1 -0
- package/dist/tests/core/eligibility.test.js +163 -0
- package/dist/tests/core/eligibility.test.js.map +1 -0
- package/dist/tests/core/extract.test.js +139 -0
- package/dist/tests/core/extract.test.js.map +1 -0
- package/dist/tests/core/fake-creds.test.js +134 -0
- package/dist/tests/core/fake-creds.test.js.map +1 -0
- package/dist/tests/core/file.test.js +197 -0
- package/dist/tests/core/file.test.js.map +1 -0
- package/dist/tests/core/git-result.test.js +113 -0
- package/dist/tests/core/git-result.test.js.map +1 -0
- package/dist/tests/core/identity.test.js +180 -0
- package/dist/tests/core/identity.test.js.map +1 -0
- package/dist/tests/core/image-decide.test.js +59 -0
- package/dist/tests/core/image-decide.test.js.map +1 -0
- package/dist/tests/core/injection.test.js +163 -0
- package/dist/tests/core/injection.test.js.map +1 -0
- package/dist/tests/core/ledger.test.js +218 -0
- package/dist/tests/core/ledger.test.js.map +1 -0
- package/dist/tests/core/managed-image.test.js +68 -0
- package/dist/tests/core/managed-image.test.js.map +1 -0
- package/dist/tests/core/mise.test.js +138 -0
- package/dist/tests/core/mise.test.js.map +1 -0
- package/dist/tests/core/parse.test.js +174 -0
- package/dist/tests/core/parse.test.js.map +1 -0
- package/dist/tests/core/path.test.js +50 -0
- package/dist/tests/core/path.test.js.map +1 -0
- package/dist/tests/core/plan.test.js +218 -0
- package/dist/tests/core/plan.test.js.map +1 -0
- package/dist/tests/core/post-move.test.js +162 -0
- package/dist/tests/core/post-move.test.js.map +1 -0
- package/dist/tests/core/pr-classify.test.js +117 -0
- package/dist/tests/core/pr-classify.test.js.map +1 -0
- package/dist/tests/core/pr-decide.test.js +298 -0
- package/dist/tests/core/pr-decide.test.js.map +1 -0
- package/dist/tests/core/pr-loop.test.js +301 -0
- package/dist/tests/core/pr-loop.test.js.map +1 -0
- package/dist/tests/core/pr-notes.test.js +165 -0
- package/dist/tests/core/pr-notes.test.js.map +1 -0
- package/dist/tests/core/predicates.test.js +154 -0
- package/dist/tests/core/predicates.test.js.map +1 -0
- package/dist/tests/core/prompt.test.js +189 -0
- package/dist/tests/core/prompt.test.js.map +1 -0
- package/dist/tests/core/protocol.test.js +195 -0
- package/dist/tests/core/protocol.test.js.map +1 -0
- package/dist/tests/core/reconcile-issue.test.js +116 -0
- package/dist/tests/core/reconcile-issue.test.js.map +1 -0
- package/dist/tests/core/render.test.js +549 -0
- package/dist/tests/core/render.test.js.map +1 -0
- package/dist/tests/core/retry.test.js +186 -0
- package/dist/tests/core/retry.test.js.map +1 -0
- package/dist/tests/core/routes.test.js +247 -0
- package/dist/tests/core/routes.test.js.map +1 -0
- package/dist/tests/core/run-fold.test.js +299 -0
- package/dist/tests/core/run-fold.test.js.map +1 -0
- package/dist/tests/core/shape.test.js +185 -0
- package/dist/tests/core/shape.test.js.map +1 -0
- package/dist/tests/core/sleep-cycle.test.js +150 -0
- package/dist/tests/core/sleep-cycle.test.js.map +1 -0
- package/dist/tests/core/slots.test.js +201 -0
- package/dist/tests/core/slots.test.js.map +1 -0
- package/dist/tests/core/state-resolve.test.js +80 -0
- package/dist/tests/core/state-resolve.test.js.map +1 -0
- package/dist/tests/core/summary.test.js +200 -0
- package/dist/tests/core/summary.test.js.map +1 -0
- package/dist/tests/core/template.test.js +116 -0
- package/dist/tests/core/template.test.js.map +1 -0
- package/dist/tests/core/tick.test.js +558 -0
- package/dist/tests/core/tick.test.js.map +1 -0
- package/dist/tests/core/token-fold.test.js +176 -0
- package/dist/tests/core/token-fold.test.js.map +1 -0
- package/dist/tests/core/turn.test.js +388 -0
- package/dist/tests/core/turn.test.js.map +1 -0
- package/dist/tests/core/url.test.js +118 -0
- package/dist/tests/core/url.test.js.map +1 -0
- package/dist/tests/core/validate.test.js +247 -0
- package/dist/tests/core/validate.test.js.map +1 -0
- package/dist/tests/core/views.test.js +252 -0
- package/dist/tests/core/views.test.js.map +1 -0
- package/dist/tests/core/vm-decide.test.js +110 -0
- package/dist/tests/core/vm-decide.test.js.map +1 -0
- package/dist/tests/core/vm-guards.test.js +153 -0
- package/dist/tests/core/vm-guards.test.js.map +1 -0
- package/dist/tests/core/vm-plan.test.js +332 -0
- package/dist/tests/core/vm-plan.test.js.map +1 -0
- package/dist/tests/core/vm-reap.test.js +196 -0
- package/dist/tests/core/vm-reap.test.js.map +1 -0
- package/dist/tests/core/workflow-parse.test.js +493 -0
- package/dist/tests/core/workflow-parse.test.js.map +1 -0
- package/dist/tests/core/workspace-decide.test.js +236 -0
- package/dist/tests/core/workspace-decide.test.js.map +1 -0
- package/dist/tests/helpers/fixtures.js +167 -0
- package/dist/tests/helpers/fixtures.js.map +1 -0
- package/dist/tests/shell/acp-substrate.test.js +101 -0
- package/dist/tests/shell/acp-substrate.test.js.map +1 -0
- package/dist/tests/shell/actions-runner-push.test.js +203 -0
- package/dist/tests/shell/actions-runner-push.test.js.map +1 -0
- package/dist/tests/shell/credential-hooks.test.js +36 -0
- package/dist/tests/shell/credential-hooks.test.js.map +1 -0
- package/dist/tests/shell/credential-registry.test.js +165 -0
- package/dist/tests/shell/credential-registry.test.js.map +1 -0
- package/dist/tests/shell/credential-substrate.test.js +179 -0
- package/dist/tests/shell/credential-substrate.test.js.map +1 -0
- package/dist/tests/shell/dockerfile-mise-pin.test.js +51 -0
- package/dist/tests/shell/dockerfile-mise-pin.test.js.map +1 -0
- package/dist/tests/shell/doctor.test.js +101 -0
- package/dist/tests/shell/doctor.test.js.map +1 -0
- package/dist/tests/shell/effect-vm-create.test.js +52 -0
- package/dist/tests/shell/effect-vm-create.test.js.map +1 -0
- package/dist/tests/shell/gh-port.test.js +63 -0
- package/dist/tests/shell/gh-port.test.js.map +1 -0
- package/dist/tests/shell/gondolin-dispatch-guard.test.js +144 -0
- package/dist/tests/shell/gondolin-dispatch-guard.test.js.map +1 -0
- package/dist/tests/shell/gondolin-dispatch-shquote.test.js +168 -0
- package/dist/tests/shell/gondolin-dispatch-shquote.test.js.map +1 -0
- package/dist/tests/shell/gondolin-image-converter.test.js +208 -0
- package/dist/tests/shell/gondolin-image-converter.test.js.map +1 -0
- package/dist/tests/shell/gondolin-image-fetch.test.js +93 -0
- package/dist/tests/shell/gondolin-image-fetch.test.js.map +1 -0
- package/dist/tests/shell/http-handler.test.js +608 -0
- package/dist/tests/shell/http-handler.test.js.map +1 -0
- package/dist/tests/shell/http-server.test.js +53 -0
- package/dist/tests/shell/http-server.test.js.map +1 -0
- package/dist/tests/shell/mcp-runtime.test.js +366 -0
- package/dist/tests/shell/mcp-runtime.test.js.map +1 -0
- package/dist/tests/shell/mise-config-asset.test.js +87 -0
- package/dist/tests/shell/mise-config-asset.test.js.map +1 -0
- package/dist/tests/shell/orchestrator-loop.test.js +583 -0
- package/dist/tests/shell/orchestrator-loop.test.js.map +1 -0
- package/dist/tests/shell/reconciler-passes.test.js +314 -0
- package/dist/tests/shell/reconciler-passes.test.js.map +1 -0
- package/dist/tests/shell/runner-loop-turn.test.js +97 -0
- package/dist/tests/shell/runner-loop-turn.test.js.map +1 -0
- package/dist/tests/shell/runner-slice.test.js +536 -0
- package/dist/tests/shell/runner-slice.test.js.map +1 -0
- package/dist/tests/shell/scaffold.test.js +65 -0
- package/dist/tests/shell/scaffold.test.js.map +1 -0
- package/dist/tests/shell/tick-config.test.js +83 -0
- package/dist/tests/shell/tick-config.test.js.map +1 -0
- package/dist/tests/shell/tracker-parse-dates.test.js +44 -0
- package/dist/tests/shell/tracker-parse-dates.test.js.map +1 -0
- package/dist/tests/shell/tracker-write-issue.test.js +154 -0
- package/dist/tests/shell/tracker-write-issue.test.js.map +1 -0
- package/dist/tests/shell/workflow-prompt-split.test.js +208 -0
- package/dist/tests/shell/workflow-prompt-split.test.js.map +1 -0
- package/dist/tests/shell/workspace-live-config.test.js +140 -0
- package/dist/tests/shell/workspace-live-config.test.js.map +1 -0
- package/package.json +21 -9
- package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
- package/prompts/Reflect.md +91 -0
- package/prompts/Review.md +97 -0
- package/prompts/Todo.md +96 -0
- package/prompts/_footer.md +41 -0
- package/prompts/_preamble.md +42 -0
- package/prompts-minimal/Todo.md +26 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/vm-agent.mjs +312 -90
- package/WORKFLOW.md +0 -744
- package/dist/acp-bridge.js +0 -324
- package/dist/acp-bridge.js.map +0 -1
- package/dist/actions/cache.js +0 -191
- package/dist/actions/cache.js.map +0 -1
- package/dist/actions/effects.js +0 -41
- package/dist/actions/effects.js.map +0 -1
- package/dist/actions/executor.js +0 -570
- package/dist/actions/executor.js.map +0 -1
- package/dist/actions/index.js +0 -13
- package/dist/actions/index.js.map +0 -1
- package/dist/actions/parsing.js.map +0 -1
- package/dist/actions/predicate-env.js +0 -27
- package/dist/actions/predicate-env.js.map +0 -1
- package/dist/actions/predicates.js +0 -49
- package/dist/actions/predicates.js.map +0 -1
- package/dist/actions/templating.js +0 -66
- package/dist/actions/templating.js.map +0 -1
- package/dist/actions/types.js +0 -15
- package/dist/actions/types.js.map +0 -1
- package/dist/agent/acp.js +0 -473
- package/dist/agent/acp.js.map +0 -1
- package/dist/agent/adapter-names.js +0 -159
- package/dist/agent/adapter-names.js.map +0 -1
- package/dist/agent/adapters.js +0 -511
- package/dist/agent/adapters.js.map +0 -1
- package/dist/agent/credential-extractors.js +0 -342
- package/dist/agent/credential-extractors.js.map +0 -1
- package/dist/agent/credential-secrets.js +0 -628
- package/dist/agent/credential-secrets.js.map +0 -1
- package/dist/agent/credential-ticker.js +0 -57
- package/dist/agent/credential-ticker.js.map +0 -1
- package/dist/agent/gondolin-creds-staging.js +0 -356
- package/dist/agent/gondolin-creds-staging.js.map +0 -1
- package/dist/agent/gondolin-dispatch.js +0 -375
- package/dist/agent/gondolin-dispatch.js.map +0 -1
- package/dist/agent/gondolin.js +0 -124
- package/dist/agent/gondolin.js.map +0 -1
- package/dist/agent/runner-decisions.js +0 -134
- package/dist/agent/runner-decisions.js.map +0 -1
- package/dist/agent/runner.js +0 -1456
- package/dist/agent/runner.js.map +0 -1
- package/dist/agent/tool-call-summary.js +0 -102
- package/dist/agent/tool-call-summary.js.map +0 -1
- package/dist/agent/vm-acp-mapping.js +0 -73
- package/dist/agent/vm-acp-mapping.js.map +0 -1
- package/dist/agent/vm-guards.js +0 -262
- package/dist/agent/vm-guards.js.map +0 -1
- package/dist/agent/vm-port.js +0 -22
- package/dist/agent/vm-port.js.map +0 -1
- package/dist/agent/vm-process-registry.js +0 -79
- package/dist/agent/vm-process-registry.js.map +0 -1
- package/dist/bin/cli-args.js +0 -105
- package/dist/bin/cli-args.js.map +0 -1
- package/dist/bin/symphony.js +0 -794
- package/dist/bin/symphony.js.map +0 -1
- package/dist/errors.js +0 -15
- package/dist/errors.js.map +0 -1
- package/dist/http-disk.js +0 -135
- package/dist/http-disk.js.map +0 -1
- package/dist/http-handlers.js.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/issues.js +0 -178
- package/dist/issues.js.map +0 -1
- package/dist/logging.js +0 -203
- package/dist/logging.js.map +0 -1
- package/dist/mcp.js +0 -706
- package/dist/mcp.js.map +0 -1
- package/dist/memory.js +0 -85
- package/dist/memory.js.map +0 -1
- package/dist/orchestrator-decisions.js +0 -331
- package/dist/orchestrator-decisions.js.map +0 -1
- package/dist/orchestrator.js +0 -1569
- package/dist/orchestrator.js.map +0 -1
- package/dist/prompt.js +0 -65
- package/dist/prompt.js.map +0 -1
- package/dist/reconciler/cache.js +0 -65
- package/dist/reconciler/cache.js.map +0 -1
- package/dist/reconciler/index.js +0 -448
- package/dist/reconciler/index.js.map +0 -1
- package/dist/reconciler/ledger.js +0 -131
- package/dist/reconciler/ledger.js.map +0 -1
- package/dist/reconciler/pr-adapters.js +0 -174
- package/dist/reconciler/pr-adapters.js.map +0 -1
- package/dist/reconciler/pr-decide.js.map +0 -1
- package/dist/reconciler/pr.js +0 -422
- package/dist/reconciler/pr.js.map +0 -1
- package/dist/reconciler/types.js +0 -12
- package/dist/reconciler/types.js.map +0 -1
- package/dist/reconciler/vm.js +0 -243
- package/dist/reconciler/vm.js.map +0 -1
- package/dist/reconciler/workspace-defaults.js +0 -83
- package/dist/reconciler/workspace-defaults.js.map +0 -1
- package/dist/reconciler/workspace.js +0 -272
- package/dist/reconciler/workspace.js.map +0 -1
- package/dist/runlog.js +0 -403
- package/dist/runlog.js.map +0 -1
- package/dist/scaffold.js +0 -165
- package/dist/scaffold.js.map +0 -1
- package/dist/trackers/local.js +0 -445
- package/dist/trackers/local.js.map +0 -1
- package/dist/trackers/types.js +0 -10
- package/dist/trackers/types.js.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/util/clock.js +0 -12
- package/dist/util/clock.js.map +0 -1
- package/dist/util/crypto.js +0 -25
- package/dist/util/crypto.js.map +0 -1
- package/dist/util/frontmatter.js +0 -70
- package/dist/util/frontmatter.js.map +0 -1
- package/dist/util/fs-issues.js +0 -22
- package/dist/util/fs-issues.js.map +0 -1
- package/dist/util/process.js +0 -152
- package/dist/util/process.js.map +0 -1
- package/dist/util/workspace-key.js +0 -10
- package/dist/util/workspace-key.js.map +0 -1
- package/dist/workflow-loader.js +0 -147
- package/dist/workflow-loader.js.map +0 -1
- package/dist/workflow.js +0 -822
- package/dist/workflow.js.map +0 -1
- package/dist/workspace-types.js +0 -8
- package/dist/workspace-types.js.map +0 -1
- package/dist/workspace.js +0 -443
- package/dist/workspace.js.map +0 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Focused tests for the shell-injection fix: host-controlled guest paths /
|
|
2
|
+
// adapter names interpolated into a `/bin/sh -c '…'` guest script must be
|
|
3
|
+
// shell-quoted (shQuote) so a value containing a single quote cannot break out of
|
|
4
|
+
// the single quotes and inject commands into the guest. Covers BOTH interpolation
|
|
5
|
+
// sites: writeFileCommand (staging) and the dispatcher's BYO probe script.
|
|
6
|
+
import { test } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
import { promisify } from 'node:util';
|
|
10
|
+
import { mkdtemp, mkdir, rm, readFile, access, writeFile, chmod } from 'node:fs/promises';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { Readable } from 'node:stream';
|
|
14
|
+
import { shQuote, writeFileCommand } from '../../src/shell/adapter/gondolin-dispatch-helpers.js';
|
|
15
|
+
import { GondolinDispatcher } from '../../src/shell/adapter/gondolin-dispatch.js';
|
|
16
|
+
import { buildByoProbeScript, miseInstallFailureMessage, hasProjectMiseConfig, } from '../../src/core/runner/mise.js';
|
|
17
|
+
const run = promisify(execFile);
|
|
18
|
+
test('shQuote escapes an embedded single quote with the close-escape-reopen trick', () => {
|
|
19
|
+
assert.equal(shQuote('plain'), `'plain'`);
|
|
20
|
+
assert.equal(shQuote(`a'b`), `'a'\\''b'`);
|
|
21
|
+
// A naive interpolation would let `'; touch X; '` break out — shQuote keeps it a literal.
|
|
22
|
+
assert.equal(shQuote(`'; touch X; '`), `''\\''; touch X; '\\'''`);
|
|
23
|
+
});
|
|
24
|
+
test('writeFileCommand stages to a guest path containing a single quote WITHOUT injecting', async () => {
|
|
25
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'symphony-shq-'));
|
|
26
|
+
try {
|
|
27
|
+
// A guest path with a single quote AND an injection payload. Pre-fix this broke
|
|
28
|
+
// out of the single quotes and `touch INJECTED` would have run.
|
|
29
|
+
const evilPath = path.join(dir, `wo'rk`, `; touch ${path.join(dir, 'INJECTED')} #`, 'file.txt');
|
|
30
|
+
const [shBin, dashC, script] = writeFileCommand({ guestPath: evilPath, content: 'hello-content', mode: 0o644 });
|
|
31
|
+
assert.equal(shBin, '/bin/sh');
|
|
32
|
+
assert.equal(dashC, '-c');
|
|
33
|
+
assert.ok(typeof script === 'string');
|
|
34
|
+
// Run the produced script through a REAL /bin/sh. It must land the content at the
|
|
35
|
+
// exact (quoted) path and must NOT create the injection sentinel.
|
|
36
|
+
await run('/bin/sh', ['-c', script], { cwd: dir });
|
|
37
|
+
assert.equal(await readFile(evilPath, 'utf8'), 'hello-content', 'content landed at the exact quoted path');
|
|
38
|
+
await assert.rejects(access(path.join(dir, 'INJECTED')), 'no injection sentinel was created');
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await rm(dir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
// ─── Probe-script injection coverage (gondolin-dispatch.ts) ──────────────────────
|
|
45
|
+
const egress = {
|
|
46
|
+
httpHooks: {},
|
|
47
|
+
secretManager: {},
|
|
48
|
+
secretName: 'ANTHROPIC_AUTH_TOKEN',
|
|
49
|
+
adapterId: 'claude',
|
|
50
|
+
};
|
|
51
|
+
const identityRealpath = async (p) => p;
|
|
52
|
+
const noopRegistry = { register: async () => ({ deregister: () => undefined }) };
|
|
53
|
+
const noopLog = { emit: () => undefined, withIssue: () => noopLog };
|
|
54
|
+
// Pass-through probe message mapper (we never reach a probe failure in these tests).
|
|
55
|
+
const probeMsg = (code) => `probe failed ${code}`;
|
|
56
|
+
function planWith(guestLauncherPath) {
|
|
57
|
+
return {
|
|
58
|
+
sessionLabel: 'symphony-test',
|
|
59
|
+
imagePath: '/img',
|
|
60
|
+
cpus: 1,
|
|
61
|
+
memMib: 512,
|
|
62
|
+
rootfsSize: '3G',
|
|
63
|
+
mounts: [{ host: '/srv/work', guest: '/work', readonly: false }],
|
|
64
|
+
env: {},
|
|
65
|
+
guestFiles: [{ guestPath: guestLauncherPath, content: 'launcher', mode: 0o755 }],
|
|
66
|
+
agentCommand: ['node', guestLauncherPath],
|
|
67
|
+
tunnel: { mcpGuestBaseUrl: 'http://m', acpWsGuestUrl: 'ws://m/acp', tcpHostEntry: {} },
|
|
68
|
+
allowWebSockets: false,
|
|
69
|
+
// Empty prep/install commands so those steps are no-op execs — this test exercises
|
|
70
|
+
// the shell-injection quoting of the probe script, not mise provisioning. The probe
|
|
71
|
+
// resolves node via `mise which node` (issue 233 made mise the only path); the test's
|
|
72
|
+
// fake `mise` on PATH echoes the fake `node`'s path for `which node`.
|
|
73
|
+
mise: { env: {}, prepCommand: [], installCommand: [] },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// A fake VmExec backed by a real /bin/sh run of the script the dispatcher emits, so
|
|
77
|
+
// the probe (and stage) scripts are exercised exactly as the guest would run them.
|
|
78
|
+
function execViaRealSh(opts, sandboxRoot, fakeBinDir) {
|
|
79
|
+
// The dispatcher always invokes /bin/sh -c <script>; run that script for real with
|
|
80
|
+
// PATH pointed at our fake node/adapter bin dir so `command -v` resolves.
|
|
81
|
+
const script = opts.command[0] === '/bin/sh' ? (opts.command[2] ?? '') : opts.command.join(' ');
|
|
82
|
+
const env = { ...process.env, ...opts.env, PATH: `${fakeBinDir}:${process.env.PATH ?? ''}` };
|
|
83
|
+
// A live Readable the probe taps via `'data'`; we push the real /bin/sh stdout
|
|
84
|
+
// onto it once the run completes, then end it (matching runBoundedExec's contract).
|
|
85
|
+
const stdout = new Readable({ read() { } });
|
|
86
|
+
let resolveExit;
|
|
87
|
+
const exit = new Promise((r) => { resolveExit = r; });
|
|
88
|
+
const finish = (out, code) => {
|
|
89
|
+
if (out.length > 0)
|
|
90
|
+
stdout.push(out);
|
|
91
|
+
stdout.push(null);
|
|
92
|
+
resolveExit({ code, signal: null });
|
|
93
|
+
};
|
|
94
|
+
run('/bin/sh', ['-c', script], { cwd: sandboxRoot, env })
|
|
95
|
+
.then(({ stdout: out }) => finish(out, 0))
|
|
96
|
+
.catch((err) => finish(typeof err.stdout === 'string' ? err.stdout : '', typeof err.code === 'number' ? err.code : 1));
|
|
97
|
+
return {
|
|
98
|
+
stdin: { write: () => undefined, end: () => undefined },
|
|
99
|
+
stdout,
|
|
100
|
+
stderr: new Readable({ read() { this.push(null); } }),
|
|
101
|
+
pid: 1,
|
|
102
|
+
exit,
|
|
103
|
+
kill: () => undefined,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
test('the BYO probe script is injection-safe for a launcher path containing a single quote', async () => {
|
|
107
|
+
const sandbox = await mkdtemp(path.join(os.tmpdir(), 'symphony-probe-'));
|
|
108
|
+
const fakeBin = path.join(sandbox, 'bin');
|
|
109
|
+
await mkdir(fakeBin, { recursive: true });
|
|
110
|
+
// Fake `node` that satisfies `command -v node` AND the `typeof WebSocket==="function"`
|
|
111
|
+
// check (exit 0), and a fake `claude` adapter on PATH.
|
|
112
|
+
await writeFile(path.join(fakeBin, 'node'), '#!/bin/sh\nexit 0\n', 'utf8');
|
|
113
|
+
await writeFile(path.join(fakeBin, 'claude'), '#!/bin/sh\nexit 0\n', 'utf8');
|
|
114
|
+
// Fake `mise` so the probe's `mise which node` resolves the fake node on PATH (issue
|
|
115
|
+
// 233 made mise the only node-resolution path); any other invocation is a no-op exit 0.
|
|
116
|
+
await writeFile(path.join(fakeBin, 'mise'), '#!/bin/sh\n[ "$1" = "which" ] && command -v "$2"\nexit 0\n', 'utf8');
|
|
117
|
+
await chmod(path.join(fakeBin, 'node'), 0o755);
|
|
118
|
+
await chmod(path.join(fakeBin, 'claude'), 0o755);
|
|
119
|
+
await chmod(path.join(fakeBin, 'mise'), 0o755);
|
|
120
|
+
// A launcher path with a single quote AND an injection payload. test -f must treat
|
|
121
|
+
// it as ONE literal path (which we create) — and the payload must NOT run.
|
|
122
|
+
const evilDir = path.join(sandbox, `la'unch; touch ${path.join(sandbox, 'PWNED')} #`);
|
|
123
|
+
await mkdir(evilDir, { recursive: true });
|
|
124
|
+
const launcherPath = path.join(evilDir, 'vm-agent.mjs');
|
|
125
|
+
await writeFile(launcherPath, 'x', 'utf8');
|
|
126
|
+
let probeScript = '';
|
|
127
|
+
const vm = {
|
|
128
|
+
id: 'vm-1',
|
|
129
|
+
exec: (opts) => {
|
|
130
|
+
// Stage writes return code 0; the probe is the /bin/sh script with `command -v`.
|
|
131
|
+
const script = opts.command[0] === '/bin/sh' ? (opts.command[2] ?? '') : '';
|
|
132
|
+
if (script.includes('command -v'))
|
|
133
|
+
probeScript = script;
|
|
134
|
+
return execViaRealSh(opts, sandbox, fakeBin);
|
|
135
|
+
},
|
|
136
|
+
close: async () => undefined,
|
|
137
|
+
};
|
|
138
|
+
const vmClient = {
|
|
139
|
+
createVm: async () => vm,
|
|
140
|
+
listSessions: async () => [],
|
|
141
|
+
gc: async () => 0,
|
|
142
|
+
killAllVms: () => 0,
|
|
143
|
+
};
|
|
144
|
+
const dispatcher = new GondolinDispatcher(vmClient, noopRegistry, () => undefined, identityRealpath, probeMsg,
|
|
145
|
+
// The real core probe-script builder so the shQuote interpolation is exercised
|
|
146
|
+
// exactly as the guest would run it (resolving node via the fake `mise which node`).
|
|
147
|
+
buildByoProbeScript, miseInstallFailureMessage, hasProjectMiseConfig, noopLog);
|
|
148
|
+
const opts = {
|
|
149
|
+
plan: planWith(launcherPath),
|
|
150
|
+
launchEnv: {},
|
|
151
|
+
adapterBin: 'claude',
|
|
152
|
+
workdir: sandbox,
|
|
153
|
+
egress,
|
|
154
|
+
onStderr: () => undefined,
|
|
155
|
+
};
|
|
156
|
+
// dispatch must succeed (probe passes against the real launcher) — proving the
|
|
157
|
+
// quoted `test -f '<path>'` resolved the literal path.
|
|
158
|
+
const handle = await dispatcher.dispatch(opts);
|
|
159
|
+
await handle.teardown();
|
|
160
|
+
// The probe script must contain the SHELL-QUOTED launcher path (close-escape-reopen),
|
|
161
|
+
// never the raw value sitting unescaped between single quotes.
|
|
162
|
+
assert.ok(probeScript.includes(shQuote(launcherPath)), 'probe interpolates the shQuoted launcher path');
|
|
163
|
+
assert.ok(!probeScript.includes(`test -f '${launcherPath}'`), 'raw unescaped interpolation must not appear');
|
|
164
|
+
// And the injection payload must NOT have run.
|
|
165
|
+
await assert.rejects(access(path.join(sandbox, 'PWNED')), 'no injection sentinel created by the probe');
|
|
166
|
+
await rm(sandbox, { recursive: true, force: true });
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=gondolin-dispatch-shquote.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gondolin-dispatch-shquote.test.js","sourceRoot":"","sources":["../../../tests/shell/gondolin-dispatch-shquote.test.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,0EAA0E;AAC1E,kFAAkF;AAClF,kFAAkF;AAClF,2EAA2E;AAC3E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sDAAsD,CAAC;AACjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAClF,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,+BAA+B,CAAC;AAUvC,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEhC,IAAI,CAAC,6EAA6E,EAAE,GAAG,EAAE;IACvF,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;IAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC;IAC1C,0FAA0F;IAC1F,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,yBAAyB,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qFAAqF,EAAE,KAAK,IAAI,EAAE;IACrG,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC;QACH,gFAAgF;QAChF,gEAAgE;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAChG,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,gBAAgB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAChH,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1B,MAAM,CAAC,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC;QAEtC,kFAAkF;QAClF,kEAAkE;QAClE,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACnD,MAAM,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,eAAe,EAAE,yCAAyC,CAAC,CAAC;QAC3G,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,EAAE,mCAAmC,CAAC,CAAC;IAChG,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,oFAAoF;AAEpF,MAAM,MAAM,GAA2B;IACrC,SAAS,EAAE,EAAW;IACtB,aAAa,EAAE,EAAW;IAC1B,UAAU,EAAE,sBAAsB;IAClC,SAAS,EAAE,QAAQ;CACpB,CAAC;AACF,MAAM,gBAAgB,GAAyB,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC9D,MAAM,YAAY,GAAmB,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC;AACjG,MAAM,OAAO,GAAY,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,EAAwB,CAAC;AACnG,qFAAqF;AACrF,MAAM,QAAQ,GAAG,CAAC,IAAmB,EAAU,EAAE,CAAC,gBAAgB,IAAI,EAAE,CAAC;AAEzE,SAAS,QAAQ,CAAC,iBAAyB;IACzC,OAAO;QACL,YAAY,EAAE,eAAe;QAC7B,SAAS,EAAE,MAAM;QACjB,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,GAAG;QACX,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAChE,GAAG,EAAE,EAAE;QACP,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAChF,YAAY,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC;QACzC,MAAM,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,EAAE;QACtF,eAAe,EAAE,KAAK;QACtB,mFAAmF;QACnF,oFAAoF;QACpF,sFAAsF;QACtF,sEAAsE;QACtE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;KACvD,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,mFAAmF;AACnF,SAAS,aAAa,CAAC,IAAmB,EAAE,WAAmB,EAAE,UAAkB;IACjF,mFAAmF;IACnF,0EAA0E;IAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChG,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,EAAE,CAAC;IAC7F,+EAA+E;IAC/E,oFAAoF;IACpF,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,EAAE,IAAI,KAAuB,CAAC,EAAE,CAAC,CAAC;IAC9D,IAAI,WAAyE,CAAC;IAC9E,MAAM,IAAI,GAAG,IAAI,OAAO,CAAiD,CAAC,CAAC,EAAE,EAAE,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,CAAC,GAAW,EAAE,IAAY,EAAQ,EAAE;QACjD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC,CAAC;IACF,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC;SACtD,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;SACzC,KAAK,CAAC,CAAC,GAA+D,EAAE,EAAE,CACzE,MAAM,CAAC,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3G,OAAO;QACL,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE;QACvD,MAAM;QACN,MAAM,EAAE,IAAI,QAAQ,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,EAAE,CAAC;QACN,IAAI;QACJ,IAAI,EAAE,GAAG,EAAE,CAAC,SAAS;KACtB,CAAC;AACJ,CAAC;AAED,IAAI,CAAC,sFAAsF,EAAE,KAAK,IAAI,EAAE;IACtG,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACzE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,uFAAuF;IACvF,uDAAuD;IACvD,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAC3E,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAC7E,qFAAqF;IACrF,wFAAwF;IACxF,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,4DAA4D,EAAE,MAAM,CAAC,CAAC;IAClH,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IACjD,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IAE/C,mFAAmF;IACnF,2EAA2E;IAC3E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtF,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACxD,MAAM,SAAS,CAAC,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAE3C,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,MAAM,EAAE,GAAa;QACnB,EAAE,EAAE,MAAM;QACV,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;YACb,iFAAiF;YACjF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;gBAAE,WAAW,GAAG,MAAM,CAAC;YACxD,OAAO,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;QACD,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS;KAC7B,CAAC;IACF,MAAM,QAAQ,GAAG;QACf,QAAQ,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;QACxB,YAAY,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;QAC5B,EAAE,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACjB,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;KACG,CAAC;IAEzB,MAAM,UAAU,GAAG,IAAI,kBAAkB,CACvC,QAAQ,EACR,YAAY,EACZ,GAAG,EAAE,CAAC,SAAS,EACf,gBAAgB,EAChB,QAAQ;IACR,+EAA+E;IAC/E,qFAAqF;IACrF,mBAAmB,EACnB,yBAAyB,EACzB,oBAAoB,EACpB,OAAO,CACR,CAAC;IACF,MAAM,IAAI,GAA4B;QACpC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC;QAC5B,SAAS,EAAE,EAAE;QACb,UAAU,EAAE,QAAQ;QACpB,OAAO,EAAE,OAAO;QAChB,MAAM;QACN,QAAQ,EAAE,GAAG,EAAE,CAAC,SAAS;KAC1B,CAAC;IAEF,+EAA+E;IAC/E,uDAAuD;IACvD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;IAExB,sFAAsF;IACtF,+DAA+D;IAC/D,MAAM,CAAC,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,+CAA+C,CAAC,CAAC;IACxG,MAAM,CAAC,EAAE,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,YAAY,YAAY,GAAG,CAAC,EAAE,6CAA6C,CAAC,CAAC;IAC7G,+CAA+C;IAC/C,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,4CAA4C,CAAC,CAAC;IAExG,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Unit tests for the OCI→gondolin converter (src/shell/adapter/gondolin-image-converter.ts).
|
|
2
|
+
//
|
|
3
|
+
// The real conversion needs docker + /dev/kvm and is validated host-manually (it
|
|
4
|
+
// cannot run inside the dispatched VM). Here we inject a stub ConverterDeps bundle —
|
|
5
|
+
// an in-memory gondolin image store keyed by cache ref — and assert the FCIS-testable
|
|
6
|
+
// behaviour: the cache key is the resolved OCI digest (+ overlay version), a miss
|
|
7
|
+
// builds + tags exactly once, and a second call for the same digest is a no-op
|
|
8
|
+
// (`cached: true`, no rebuild). No docker.
|
|
9
|
+
import { test } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { ensureConvertedImage, probeConvertedImage, resolveDigestForPolicy, cacheRefFor, } from '../../src/shell/adapter/gondolin-image-converter.js';
|
|
12
|
+
/** A stub ConverterDeps over an in-memory store; records build calls + the configs built. */
|
|
13
|
+
function makeDeps(opts) {
|
|
14
|
+
const store = new Map(); // cacheRef → buildId
|
|
15
|
+
const builtConfigs = [];
|
|
16
|
+
const digestPolicies = [];
|
|
17
|
+
let buildCount = 0;
|
|
18
|
+
const deps = {
|
|
19
|
+
resolveOciDigest: (_ref, _runtime, pullPolicy) => {
|
|
20
|
+
digestPolicies.push(pullPolicy);
|
|
21
|
+
return opts.digest;
|
|
22
|
+
},
|
|
23
|
+
resolveImageSelector: (selector) => {
|
|
24
|
+
const buildId = store.get(selector);
|
|
25
|
+
if (!buildId)
|
|
26
|
+
throw new Error(`not in store: ${selector}`);
|
|
27
|
+
return { source: 'ref', selector, assetDir: `/store/${buildId}`, buildId };
|
|
28
|
+
},
|
|
29
|
+
buildAssets: async (config) => {
|
|
30
|
+
buildCount += 1;
|
|
31
|
+
builtConfigs.push(config);
|
|
32
|
+
return { outputDir: '/tmp/out', manifestPath: '/tmp/out/manifest.json', manifest: {} };
|
|
33
|
+
},
|
|
34
|
+
importImageFromDirectory: (assetDir) => ({ buildId: 'build-xyz', arch: 'x86_64', assetDir, created: true }),
|
|
35
|
+
setImageRef: (reference, buildId) => {
|
|
36
|
+
store.set(reference, buildId);
|
|
37
|
+
return null;
|
|
38
|
+
},
|
|
39
|
+
defaultBuildConfig: () => ({ arch: 'x86_64', distro: 'alpine' }),
|
|
40
|
+
mkOutputDir: () => '/tmp/out',
|
|
41
|
+
};
|
|
42
|
+
return { deps, builds: () => buildCount, builtConfigs, digestPolicies, store };
|
|
43
|
+
}
|
|
44
|
+
test('ensureConvertedImage: a cache miss builds + tags by digest, returns cached:false', async () => {
|
|
45
|
+
const digest = 'sha256:abc123def456';
|
|
46
|
+
const { deps, builds, builtConfigs, digestPolicies, store } = makeDeps({ digest });
|
|
47
|
+
const r = await ensureConvertedImage('ghcr.io/acme/agent:1', { pullPolicy: 'if-not-present', deps });
|
|
48
|
+
assert.equal(r.cached, false);
|
|
49
|
+
assert.equal(r.buildId, 'build-xyz');
|
|
50
|
+
// The runtime selector the cached asset boots with (the build id when known) — the
|
|
51
|
+
// reconcile loop threads this into the VmPlan when `gondolin.image` is unset (#206).
|
|
52
|
+
assert.equal(r.selector, 'build-xyz');
|
|
53
|
+
assert.equal(builds(), 1);
|
|
54
|
+
// The OCI ref is threaded into the build config's oci source.
|
|
55
|
+
assert.equal(builtConfigs[0].oci?.image, 'ghcr.io/acme/agent:1');
|
|
56
|
+
// The FULL pull policy is preserved into the digest resolver (not reduced to a
|
|
57
|
+
// pull/skip boolean) so `always`/`never` are honored when computing the cache key.
|
|
58
|
+
assert.deepEqual(digestPolicies, ['if-not-present']);
|
|
59
|
+
// `buildAssets` is always asked to convert the LOCAL image (`if-not-present`),
|
|
60
|
+
// regardless of the operator's policy: the policy's pull already ran in the digest
|
|
61
|
+
// resolve, so the local ref IS `digest` and the asset is built from exactly the
|
|
62
|
+
// digest we keyed the cache on — never a freshly re-pulled, different digest.
|
|
63
|
+
assert.equal(builtConfigs[0].oci?.pullPolicy, 'if-not-present');
|
|
64
|
+
// It was tagged under the digest+overlay cache ref.
|
|
65
|
+
assert.ok(store.has(cacheRefFor(digest)));
|
|
66
|
+
});
|
|
67
|
+
test('ensureConvertedImage: an `always` policy is preserved into the digest resolver and keys the post-pull digest', async () => {
|
|
68
|
+
// The operator asked for `always`: the digest resolver must receive `always`
|
|
69
|
+
// (so it re-pulls even when present) and the digest IT returns — the post-pull
|
|
70
|
+
// digest — is what keys the cache. `buildAssets` still converts that local image.
|
|
71
|
+
const digest = 'sha256:postpull';
|
|
72
|
+
const { deps, builds, builtConfigs, digestPolicies, store } = makeDeps({ digest });
|
|
73
|
+
const r = await ensureConvertedImage('repo:latest', { pullPolicy: 'always', deps });
|
|
74
|
+
assert.equal(r.cached, false);
|
|
75
|
+
assert.deepEqual(digestPolicies, ['always']);
|
|
76
|
+
assert.equal(builtConfigs[0].oci?.pullPolicy, 'if-not-present');
|
|
77
|
+
assert.ok(store.has(cacheRefFor(digest)));
|
|
78
|
+
assert.equal(builds(), 1);
|
|
79
|
+
});
|
|
80
|
+
test('ensureConvertedImage: a second call for the same digest is a no-op (cached:true, no rebuild)', async () => {
|
|
81
|
+
const digest = 'sha256:abc123def456';
|
|
82
|
+
const { deps, builds } = makeDeps({ digest });
|
|
83
|
+
const first = await ensureConvertedImage('ghcr.io/acme/agent:1', { deps });
|
|
84
|
+
assert.equal(first.cached, false);
|
|
85
|
+
assert.equal(builds(), 1);
|
|
86
|
+
// Same digest resolves on the second call → cache hit, build NOT re-run.
|
|
87
|
+
const second = await ensureConvertedImage('ghcr.io/acme/agent:1', { deps });
|
|
88
|
+
assert.equal(second.cached, true);
|
|
89
|
+
assert.equal(second.buildId, 'build-xyz');
|
|
90
|
+
// The cache hit still resolves the runtime selector (no rebuild) so dispatch can boot it.
|
|
91
|
+
assert.equal(second.selector, 'build-xyz');
|
|
92
|
+
assert.equal(builds(), 1);
|
|
93
|
+
});
|
|
94
|
+
test('ensureConvertedImage: a cache hit with no known build id falls the selector back to the asset dir', async () => {
|
|
95
|
+
// A resolved asset whose content-derived build id is unknown still yields a usable
|
|
96
|
+
// runtime selector — the asset directory (a `path` selector), never empty (#206).
|
|
97
|
+
const digest = 'sha256:nobuild';
|
|
98
|
+
const { deps } = makeDeps({ digest });
|
|
99
|
+
const noBuildIdDeps = {
|
|
100
|
+
...deps,
|
|
101
|
+
resolveImageSelector: (selector) => ({ source: 'ref', selector, assetDir: '/store/asset-dir' }),
|
|
102
|
+
};
|
|
103
|
+
const r = await ensureConvertedImage('ghcr.io/acme/agent:1', { deps: noBuildIdDeps });
|
|
104
|
+
assert.equal(r.cached, true);
|
|
105
|
+
assert.equal(r.buildId, '');
|
|
106
|
+
assert.equal(r.selector, '/store/asset-dir');
|
|
107
|
+
});
|
|
108
|
+
test('ensureConvertedImage: caches on the DIGEST, not the ref string (a re-tagged image reconverts)', async () => {
|
|
109
|
+
// Same ref string, different underlying digest → distinct cache refs → a rebuild.
|
|
110
|
+
const d1 = makeDeps({ digest: 'sha256:1111' });
|
|
111
|
+
await ensureConvertedImage('repo:latest', { deps: d1.deps });
|
|
112
|
+
// A new converter view whose digest changed (the tag moved) must build afresh.
|
|
113
|
+
const d2 = makeDeps({ digest: 'sha256:2222' });
|
|
114
|
+
// Seed d2's store with the OLD digest's ref to prove only the matching digest hits.
|
|
115
|
+
d2.store.set(cacheRefFor('sha256:1111'), 'old-build');
|
|
116
|
+
const r = await ensureConvertedImage('repo:latest', { deps: d2.deps });
|
|
117
|
+
assert.equal(r.cached, false);
|
|
118
|
+
assert.equal(d2.builds(), 1);
|
|
119
|
+
});
|
|
120
|
+
test('probeConvertedImage: converted=true when a cached asset resolves; false otherwise', () => {
|
|
121
|
+
const digest = 'sha256:abc123def456';
|
|
122
|
+
const { deps, digestPolicies, store } = makeDeps({ digest });
|
|
123
|
+
// Nothing tagged yet → not converted, but locally resolvable (digest read ok).
|
|
124
|
+
// The probe carries the resolved digest + a null selector (no cached asset).
|
|
125
|
+
assert.deepEqual(probeConvertedImage('ghcr.io/acme/agent:1', { deps }), {
|
|
126
|
+
converted: false,
|
|
127
|
+
locallyResolvable: true,
|
|
128
|
+
digest,
|
|
129
|
+
selector: null,
|
|
130
|
+
});
|
|
131
|
+
// The read-only probe must NEVER pull — it resolves the CURRENT local digest with
|
|
132
|
+
// the `never` policy so it stays cheap enough for the hot poll path.
|
|
133
|
+
assert.deepEqual(digestPolicies, ['never']);
|
|
134
|
+
// Tag the cache ref → converted; the probe now carries the runtime selector
|
|
135
|
+
// (the build id) the reconcile loop boots the cached asset with (#206 rework).
|
|
136
|
+
store.set(cacheRefFor(digest), 'build-xyz');
|
|
137
|
+
assert.deepEqual(probeConvertedImage('ghcr.io/acme/agent:1', { deps }), {
|
|
138
|
+
converted: true,
|
|
139
|
+
locallyResolvable: true,
|
|
140
|
+
digest,
|
|
141
|
+
selector: 'build-xyz',
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
test('probeConvertedImage: an unresolvable digest reports neither converted nor resolvable', () => {
|
|
145
|
+
const deps = {
|
|
146
|
+
...makeDeps({ digest: 'sha256:x' }).deps,
|
|
147
|
+
resolveOciDigest: () => {
|
|
148
|
+
throw new Error('not present locally');
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
assert.deepEqual(probeConvertedImage('ghcr.io/acme/agent:1', { deps }), {
|
|
152
|
+
converted: false,
|
|
153
|
+
locallyResolvable: false,
|
|
154
|
+
digest: null,
|
|
155
|
+
selector: null,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// ── resolveDigestForPolicy: the pull-policy ordering (docker-free) ──────────────
|
|
159
|
+
//
|
|
160
|
+
// The production runtime resolver shells out to docker/podman, so we test its pure
|
|
161
|
+
// ordering helper directly with injected inspect/pull effects. `inspect`/`pull` here
|
|
162
|
+
// model the local image store: `pull` flips the inspected digest from the stale local
|
|
163
|
+
// one to the registry one, so we can assert which digest each policy keys on.
|
|
164
|
+
/** A fake local image store: `inspect()` returns the current local digest (or null);
|
|
165
|
+
* `pull()` records a call and sets the local digest to `afterPull`. */
|
|
166
|
+
function fakeRuntime(opts) {
|
|
167
|
+
let local = opts.localBeforePull;
|
|
168
|
+
let pullCount = 0;
|
|
169
|
+
return {
|
|
170
|
+
io: {
|
|
171
|
+
inspect: () => local,
|
|
172
|
+
pull: () => {
|
|
173
|
+
pullCount += 1;
|
|
174
|
+
local = opts.afterPull;
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
pulls: () => pullCount,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
test('resolveDigestForPolicy: `if-not-present` inspects first and skips the pull when present', () => {
|
|
181
|
+
const rt = fakeRuntime({ localBeforePull: 'sha256:local', afterPull: 'sha256:registry' });
|
|
182
|
+
const id = resolveDigestForPolicy('if-not-present', rt.io);
|
|
183
|
+
assert.equal(id, 'sha256:local'); // used the local digest, no pull
|
|
184
|
+
assert.equal(rt.pulls(), 0);
|
|
185
|
+
});
|
|
186
|
+
test('resolveDigestForPolicy: `if-not-present` pulls on a local miss, then re-inspects', () => {
|
|
187
|
+
const rt = fakeRuntime({ localBeforePull: null, afterPull: 'sha256:registry' });
|
|
188
|
+
const id = resolveDigestForPolicy('if-not-present', rt.io);
|
|
189
|
+
assert.equal(id, 'sha256:registry');
|
|
190
|
+
assert.equal(rt.pulls(), 1);
|
|
191
|
+
});
|
|
192
|
+
test('resolveDigestForPolicy: `always` pulls even when present and uses the post-pull digest', () => {
|
|
193
|
+
// The ref IS present locally (a stale digest), but `always` must still pull and key
|
|
194
|
+
// off the fresh post-pull digest — the bug the reviewer flagged.
|
|
195
|
+
const rt = fakeRuntime({ localBeforePull: 'sha256:stale', afterPull: 'sha256:fresh' });
|
|
196
|
+
const id = resolveDigestForPolicy('always', rt.io);
|
|
197
|
+
assert.equal(id, 'sha256:fresh');
|
|
198
|
+
assert.equal(rt.pulls(), 1);
|
|
199
|
+
});
|
|
200
|
+
test('resolveDigestForPolicy: `never` does not pull; a local miss resolves to null', () => {
|
|
201
|
+
const present = fakeRuntime({ localBeforePull: 'sha256:local', afterPull: 'sha256:registry' });
|
|
202
|
+
assert.equal(resolveDigestForPolicy('never', present.io), 'sha256:local');
|
|
203
|
+
assert.equal(present.pulls(), 0);
|
|
204
|
+
const missing = fakeRuntime({ localBeforePull: null, afterPull: 'sha256:registry' });
|
|
205
|
+
assert.equal(resolveDigestForPolicy('never', missing.io), null);
|
|
206
|
+
assert.equal(missing.pulls(), 0);
|
|
207
|
+
});
|
|
208
|
+
//# sourceMappingURL=gondolin-image-converter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gondolin-image-converter.test.js","sourceRoot":"","sources":["../../../tests/shell/gondolin-image-converter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Unit tests for the managed prebuilt-image fetcher (src/shell/adapter/gondolin-image-fetch.ts).
|
|
2
|
+
//
|
|
3
|
+
// The real fetch needs network + tar/zstd + a Gondolin store and is validated
|
|
4
|
+
// host-manually (a published asset on a clean host). Here we inject a stub FetchDeps
|
|
5
|
+
// over an in-memory store and assert the FCIS-testable behaviour: a cache hit returns
|
|
6
|
+
// WITHOUT downloading; a sha256 mismatch or a digest-less sidecar THROWS before import;
|
|
7
|
+
// a miss downloads → verifies → extracts → imports → tags exactly once.
|
|
8
|
+
import { test } from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { ensureFetchedImage, probeFetchedImage } from '../../src/shell/adapter/gondolin-image-fetch.js';
|
|
11
|
+
import { buildManagedImageWiring } from '../../src/core/image/managed-image.js';
|
|
12
|
+
const WIRING = buildManagedImageWiring({ version: '0.3.0', nodeArch: 'x64' });
|
|
13
|
+
const GOOD_SHA = 'a'.repeat(64);
|
|
14
|
+
/** A stub FetchDeps over an in-memory store. `sidecar`/`actualSha`/`extractDir` drive
|
|
15
|
+
* the verify + extract paths; downloads + imports + tags are recorded. */
|
|
16
|
+
function makeDeps(opts = {}) {
|
|
17
|
+
const store = new Map();
|
|
18
|
+
if (opts.seedRef)
|
|
19
|
+
store.set(opts.seedRef, { buildId: 'cached-bid', assetDir: '/store/cached-bid' });
|
|
20
|
+
const downloads = [];
|
|
21
|
+
const imported = [];
|
|
22
|
+
const tagged = [];
|
|
23
|
+
const deps = {
|
|
24
|
+
resolveImageSelector: (sel) => {
|
|
25
|
+
const v = store.get(sel);
|
|
26
|
+
if (!v)
|
|
27
|
+
throw new Error(`not in store: ${sel}`);
|
|
28
|
+
return { source: 'ref', selector: sel, assetDir: v.assetDir, buildId: v.buildId };
|
|
29
|
+
},
|
|
30
|
+
importImageFromDirectory: (dir) => {
|
|
31
|
+
imported.push(dir);
|
|
32
|
+
return { buildId: 'fetched-bid', arch: 'x86_64', assetDir: '/store/fetched-bid', created: true };
|
|
33
|
+
},
|
|
34
|
+
setImageRef: (ref, buildId, arch) => {
|
|
35
|
+
tagged.push([ref, buildId, arch]);
|
|
36
|
+
store.set(ref, { buildId, assetDir: `/store/${buildId}` });
|
|
37
|
+
return undefined;
|
|
38
|
+
},
|
|
39
|
+
downloadToFile: async (url) => {
|
|
40
|
+
downloads.push(url);
|
|
41
|
+
},
|
|
42
|
+
readTextFile: () => opts.sidecar ?? `${GOOD_SHA} ${WIRING.assetName}`,
|
|
43
|
+
sha256OfFile: async () => opts.actualSha ?? GOOD_SHA,
|
|
44
|
+
extractAsset: async () => '/extracted/asset',
|
|
45
|
+
mkTempDir: () => '/tmp/managed-test',
|
|
46
|
+
};
|
|
47
|
+
return { deps, downloads, imported, tagged, store };
|
|
48
|
+
}
|
|
49
|
+
test('cache HIT returns the cached selector WITHOUT downloading', async () => {
|
|
50
|
+
const r = makeDeps({ seedRef: WIRING.ref });
|
|
51
|
+
const out = await ensureFetchedImage(WIRING, { deps: r.deps });
|
|
52
|
+
assert.equal(out.cached, true);
|
|
53
|
+
assert.equal(out.selector, 'cached-bid');
|
|
54
|
+
assert.equal(r.downloads.length, 0, 'no download on a cache hit');
|
|
55
|
+
assert.equal(r.imported.length, 0);
|
|
56
|
+
});
|
|
57
|
+
test('cache MISS downloads → verifies → extracts → imports → tags exactly once', async () => {
|
|
58
|
+
const r = makeDeps(); // store empty → miss
|
|
59
|
+
const out = await ensureFetchedImage(WIRING, { deps: r.deps });
|
|
60
|
+
assert.equal(out.cached, false);
|
|
61
|
+
assert.equal(out.selector, 'fetched-bid'); // build id wins as the runtime selector
|
|
62
|
+
assert.deepEqual(r.imported, ['/extracted/asset']);
|
|
63
|
+
assert.deepEqual(r.tagged, [[WIRING.ref, 'fetched-bid', 'x86_64']]);
|
|
64
|
+
// Downloaded the asset + its sidecar (one candidate sufficed).
|
|
65
|
+
assert.equal(r.downloads.length, 2);
|
|
66
|
+
assert.ok(r.downloads[0].endsWith(WIRING.assetName));
|
|
67
|
+
assert.ok(r.downloads[1].endsWith(`${WIRING.assetName}.sha256`));
|
|
68
|
+
});
|
|
69
|
+
test('a sha256 MISMATCH throws loudly and never imports', async () => {
|
|
70
|
+
const r = makeDeps({ actualSha: 'b'.repeat(64) }); // sidecar says GOOD_SHA, file hashes differently
|
|
71
|
+
await assert.rejects(() => ensureFetchedImage(WIRING, { deps: r.deps }), /sha256 mismatch/);
|
|
72
|
+
assert.equal(r.imported.length, 0);
|
|
73
|
+
assert.equal(r.tagged.length, 0);
|
|
74
|
+
});
|
|
75
|
+
test('a digest-less / malformed sidecar throws before import', async () => {
|
|
76
|
+
const r = makeDeps({ sidecar: 'not-a-digest\n' });
|
|
77
|
+
await assert.rejects(() => ensureFetchedImage(WIRING, { deps: r.deps }), /missing or malformed/);
|
|
78
|
+
assert.equal(r.imported.length, 0);
|
|
79
|
+
});
|
|
80
|
+
test('all candidates failing to download surfaces an aggregate error', async () => {
|
|
81
|
+
const r = makeDeps();
|
|
82
|
+
r.deps.downloadToFile = async (url) => {
|
|
83
|
+
throw new Error(`404 ${url}`);
|
|
84
|
+
};
|
|
85
|
+
await assert.rejects(() => ensureFetchedImage(WIRING, { deps: r.deps }), /no published asset found/);
|
|
86
|
+
});
|
|
87
|
+
test('probeFetchedImage is read-only: cached → {digest,selector}; miss → nulls', () => {
|
|
88
|
+
const hit = makeDeps({ seedRef: WIRING.ref });
|
|
89
|
+
assert.deepEqual(probeFetchedImage(WIRING, { deps: hit.deps }), { digest: 'cached-bid', selector: 'cached-bid' });
|
|
90
|
+
const miss = makeDeps();
|
|
91
|
+
assert.deepEqual(probeFetchedImage(WIRING, { deps: miss.deps }), { digest: null, selector: null });
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=gondolin-image-fetch.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gondolin-image-fetch.test.js","sourceRoot":"","sources":["../../../tests/shell/gondolin-image-fetch.test.ts"],"names":[],"mappings":"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"}
|