@jr2/orchestrator 0.3.0 → 0.5.1
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/README.md +20 -2
- package/bin/server.ts +7 -4
- package/console/canvas.ts +27 -14
- package/console/components/app.ts +12 -1
- package/console/components/fleet.ts +52 -6
- package/console/components/machine-pane.ts +128 -2
- package/console/components/nav.ts +1 -1
- package/console/components/schema-form.ts +12 -4
- package/console/json-field.ts +23 -0
- package/console/main.ts +122 -39
- package/console/store.ts +321 -21
- package/console/style.css +111 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +442 -0
- package/docs/concepts/architecture.md +230 -0
- package/docs/concepts/charts.md +143 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +254 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +603 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +289 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +222 -0
- package/docs/concepts/the-menu.md +378 -0
- package/docs/concepts/workspaces.md +678 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
- package/docs/get-started/install.md +126 -0
- package/docs/guides/add-tools-to-the-sandbox.md +295 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +223 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +226 -0
- package/docs/guides/look-inside-a-sandbox.md +174 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +486 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +544 -0
- package/docs/guides/run-one-worker-per-item.md +321 -0
- package/docs/guides/set-an-agents-access-and-dials.md +375 -0
- package/docs/guides/size-a-workspace.md +204 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +208 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +326 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +216 -0
- package/docs/reference/authoring-surface/agent.md +128 -0
- package/docs/reference/authoring-surface/answer.md +140 -0
- package/docs/reference/authoring-surface/arg.md +60 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +191 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +83 -0
- package/docs/reference/authoring-surface/file.md +122 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +159 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +74 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +125 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +112 -0
- package/docs/reference/authoring-surface/secret.md +118 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +12 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-binding.md +62 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +19 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
- package/docs/reference/authoring-surface/type-customize.md +109 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +41 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +15 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +32 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +94 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
- package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
- package/docs/reference/authoring-surface/workspace.md +259 -0
- package/docs/reference/authoring-surface.md +197 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +756 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions.md +83 -0
- package/docs/reference/glossary.md +648 -0
- package/docs/reference/http-api.md +1348 -0
- package/docs/reference/machines.md +190 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +206 -0
- package/package.json +6 -4
- package/src/actor.ts +976 -61
- package/src/agent.ts +114 -38
- package/src/ambient.ts +4 -1
- package/src/assembly-checks.ts +347 -0
- package/src/assembly-tar.ts +494 -0
- package/src/assembly.ts +721 -0
- package/src/cause.ts +234 -0
- package/src/chart.ts +2182 -0
- package/src/charts.ts +748 -0
- package/src/config.ts +520 -162
- package/src/content-hash.ts +105 -0
- package/src/custodian.lua +6 -5
- package/src/custodian.ts +143 -39
- package/src/customize.ts +388 -55
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +557 -0
- package/src/effect.ts +1202 -0
- package/src/effector-wire.ts +674 -0
- package/src/fail.ts +209 -0
- package/src/fingerprint.ts +4 -3
- package/src/gate.ts +8 -0
- package/src/git-clone.ts +65 -0
- package/src/git-push.ts +336 -0
- package/src/harness-client.ts +764 -48
- package/src/held-secrets.ts +82 -26
- package/src/holds.ts +156 -0
- package/src/http-effect.ts +1423 -0
- package/src/http.ts +916 -113
- package/src/images.ts +47 -21
- package/src/index.ts +62 -43
- package/src/instance.ts +505 -59
- package/src/internal.ts +84 -0
- package/src/kube-client.ts +333 -0
- package/src/machine-doc.ts +106 -6
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/mount-digest.ts +53 -0
- package/src/names.ts +66 -8
- package/src/notices.ts +141 -0
- package/src/open-body.ts +1580 -0
- package/src/open-effect.ts +217 -0
- package/src/open.ts +59 -6
- package/src/parts.ts +709 -41
- package/src/party.ts +534 -0
- package/src/pick.ts +761 -0
- package/src/pool.ts +248 -37
- package/src/registration.ts +382 -19
- package/src/repo-fetch.ts +60 -85
- package/src/repos.ts +65 -75
- package/src/rest.ts +133 -0
- package/src/run-host.ts +2383 -115
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +1126 -0
- package/src/sandbox-effector.ts +111 -0
- package/src/sandbox-kube.ts +1721 -0
- package/src/sandbox-watch.ts +260 -0
- package/src/secret-placement.ts +529 -0
- package/src/server.ts +284 -84
- package/src/setup.ts +819 -117
- package/src/size.ts +454 -0
- package/src/snapshot-store.ts +115 -20
- package/src/stub-harness.ts +49 -16
- package/src/sync-client.ts +54 -0
- package/src/timers.ts +191 -0
- package/src/tokens.ts +35 -4
- package/src/vocabulary.ts +135 -15
- package/src/wire.ts +238 -10
- package/src/workspace.ts +1195 -338
- package/src/sandbox-kubectl.ts +0 -1108
package/src/config.ts
CHANGED
|
@@ -21,11 +21,12 @@ import { join } from "node:path";
|
|
|
21
21
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
22
22
|
import { z } from "zod";
|
|
23
23
|
import { repoIdentity } from "./repo-identity.ts";
|
|
24
|
+
import { assertSize, type Size } from "./size.ts";
|
|
24
25
|
|
|
25
26
|
// ------------------------------------------------------------------------------------------------
|
|
26
27
|
// `git.credentials` (ADR-0051): how the cluster authenticates to a Repo, and the fence.
|
|
27
28
|
//
|
|
28
|
-
// A Repo is identified by its url and nothing in this file names one (
|
|
29
|
+
// A Repo is identified by its url and nothing in this file names one (GLOSSARY.md "Repo"). What
|
|
29
30
|
// the config declares is CREDENTIALS, matched by prefix on the identity — the Argo and Flux shape.
|
|
30
31
|
// The Orchestrator resolves the entry when it creates a Repo CR and writes a `secretRef`; the
|
|
31
32
|
// cache agent reads only that. The list is also the fence: a per-run url (run input, a ticket
|
|
@@ -33,19 +34,96 @@ import { repoIdentity } from "./repo-identity.ts";
|
|
|
33
34
|
// credential against an arbitrary host. A bound url is code the Instance typechecked and deployed
|
|
34
35
|
// — admitted without a match, cloned anonymously.
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
// One credentials entry. An entry may carry neither `token` nor `sshKey`: it then admits its
|
|
38
|
+
// prefix through the fence and the clone is anonymous.
|
|
39
|
+
/** One entry of `git.credentials`: the credential that the cluster uses for the Repos whose
|
|
40
|
+
* identity starts with `match`. An entry with neither `token` nor `sshKey` admits its Repos
|
|
41
|
+
* through the fence, and they clone with no credential. */
|
|
38
42
|
export type GitCredential = {
|
|
39
|
-
/**
|
|
43
|
+
/** The Repos this entry is for: a prefix of a Repo's identity, such as `github.com/ourorg/`, or
|
|
44
|
+
* `*` for every Repo. The identity of a Repo is its host and path with no scheme, user or `.git`.
|
|
45
|
+
* When more than one entry matches, the longest `match` wins; on a tie, the first entry in the
|
|
46
|
+
* list wins. */
|
|
40
47
|
match: string;
|
|
41
|
-
|
|
48
|
+
// The cache agent spends the clone Secret; the Orchestrator never holds it (ADR-0068).
|
|
49
|
+
/** For an `https://` url: the name of the env var that holds a token, read from the shell or
|
|
50
|
+
* `.env` when `jr2 up` runs. `jr2 up` writes the value into a Secret (`jr2-git-<hash>`) that only
|
|
51
|
+
* the Repo cache agent reads. The Agent never sees it. */
|
|
42
52
|
token?: string;
|
|
43
|
-
/**
|
|
53
|
+
/** For an ssh url: the name of a Secret that you create. It holds a deploy key under the Flux
|
|
54
|
+
* key names `identity`, `identity.pub` and `known_hosts`. */
|
|
44
55
|
sshKey?: string;
|
|
56
|
+
// The credential a `push()` spends (ADR-0051, ADR-0067). A token goes Basic through a credential
|
|
57
|
+
// helper only.
|
|
58
|
+
/** The credential that a `push()` Effect uses, when it is not the clone credential. Write
|
|
59
|
+
* `{ sshKey }` or `{ token }`. Default: the clone credential pushes. An entry with no credential
|
|
60
|
+
* admits a clone and never a push. When the push finds no usable credential, it is refused, for
|
|
61
|
+
* example: `no git.credentials entry matches "<identity>" — a push spends the entry its url
|
|
62
|
+
* matches`, or `git.credentials entry "<match>" holds no credential a push over <scheme> can
|
|
63
|
+
* spend`. */
|
|
64
|
+
push?:
|
|
65
|
+
| {
|
|
66
|
+
/** For an ssh url: the name of a Secret that you create, with the same keys as the
|
|
67
|
+
* entry's `sshKey`. */
|
|
68
|
+
sshKey: string;
|
|
69
|
+
}
|
|
70
|
+
| {
|
|
71
|
+
/** For an `https://` url: the name of the env var that holds the push token, read as for
|
|
72
|
+
* the entry's `token`. */
|
|
73
|
+
token: string;
|
|
74
|
+
};
|
|
75
|
+
// Explicit (ADR-0067): an auth key is never reused as a signing key.
|
|
76
|
+
/** The key that a `push({ sign: true })` signs with, and the committer it signs as:
|
|
77
|
+
* `{ sshKey, name, email }` or `{ gpgKey, name, email }`. `sshKey` names a Secret with
|
|
78
|
+
* `identity`. `gpgKey` names a Secret with `private.asc` and no passphrase. jr2 never signs with
|
|
79
|
+
* the clone or push key, so name the signing key here. The email must belong to the forge account
|
|
80
|
+
* that holds the key, or the forge shows the signature as unverified. A signed push without it is
|
|
81
|
+
* refused: `signed, and git.credentials entry "<match>" holds no sign field`. */
|
|
82
|
+
sign?:
|
|
83
|
+
| {
|
|
84
|
+
/** The name of a Secret that you create, which holds the ssh signing key under `identity`. */
|
|
85
|
+
sshKey: string;
|
|
86
|
+
/** The committer's name on the signed commit. */
|
|
87
|
+
name: string;
|
|
88
|
+
/** The committer's email. It must belong to the forge account that holds the key. */
|
|
89
|
+
email: string;
|
|
90
|
+
}
|
|
91
|
+
| {
|
|
92
|
+
/** The name of a Secret that you create, which holds the GPG signing key under
|
|
93
|
+
* `private.asc`, with no passphrase. */
|
|
94
|
+
gpgKey: string;
|
|
95
|
+
/** The committer's name on the signed commit. */
|
|
96
|
+
name: string;
|
|
97
|
+
/** The committer's email. It must belong to the forge account that holds the key. */
|
|
98
|
+
email: string;
|
|
99
|
+
};
|
|
100
|
+
// The squashed commit is new, and without `sign` no signer names its committer (ADR-0067).
|
|
101
|
+
/** Who commits an unsigned `push({ squash: true })`: `{ name, email }`. The squashed commit is
|
|
102
|
+
* new, so it needs a committer. Its date comes from the Effector's clock, and its author stays
|
|
103
|
+
* the Agent. A signed push is committed by the signer, so it does not read this field. A squashed
|
|
104
|
+
* push with neither field is refused: `squashed without sign, and git.credentials entry "<match>"
|
|
105
|
+
* holds neither sign nor committer`. */
|
|
106
|
+
committer?: {
|
|
107
|
+
/** The committer's name on the squashed commit. */
|
|
108
|
+
name: string;
|
|
109
|
+
/** The committer's email on the squashed commit. */
|
|
110
|
+
email: string;
|
|
111
|
+
};
|
|
45
112
|
};
|
|
46
113
|
|
|
114
|
+
/** How the cluster authenticates to Repos. */
|
|
47
115
|
export type GitConfig = {
|
|
48
|
-
/**
|
|
116
|
+
/** The credential entries, matched by prefix on the Repo identity. The longest `match` wins; on a
|
|
117
|
+
* tie, the first entry in the list wins.
|
|
118
|
+
*
|
|
119
|
+
* The list is also a fence. A per-run url, which comes from the run input, must match an entry,
|
|
120
|
+
* or the Sandbox refuses it when it attaches the Repo:
|
|
121
|
+
* `Sandbox "<name>" refuses the per-run repo <url> for slot "<slot>": no git.credentials entry matches "<identity>"`.
|
|
122
|
+
* Thus a run cannot spend this cluster's credentials against any host. A url that a Machine
|
|
123
|
+
* binds needs no entry. When no entry matches it, it clones with no credential.
|
|
124
|
+
*
|
|
125
|
+
* Each entry is checked when the config loads:
|
|
126
|
+
* `<file> at git.credentials[<i>]<path>: <problem>`, followed by the shape of an entry. */
|
|
49
127
|
credentials?: readonly GitCredential[];
|
|
50
128
|
};
|
|
51
129
|
|
|
@@ -63,7 +141,7 @@ export function matchCredential(identity: string, list: readonly GitCredential[]
|
|
|
63
141
|
}
|
|
64
142
|
|
|
65
143
|
/** The Secret a Repo CR's `secretRef` names for `url` under `entry`, picked by the url's scheme:
|
|
66
|
-
* https/http spend a token (
|
|
144
|
+
* https/http spend a token (the clone Secret `jr2 up` writes from the env var), ssh spends the
|
|
67
145
|
* named deploy-key Secret. `undefined` when no entry, no applicable field, or a scheme that carries
|
|
68
146
|
* no credential (`git://`, a local path) — the clone is anonymous. */
|
|
69
147
|
export function credentialSecretFor(
|
|
@@ -94,21 +172,41 @@ export function isSshUrl(url: string): boolean {
|
|
|
94
172
|
}
|
|
95
173
|
}
|
|
96
174
|
|
|
97
|
-
|
|
98
|
-
|
|
175
|
+
// An env var on the Harness container, in the CR's (corev1.EnvVar) shape — `value` or a
|
|
176
|
+
// `valueFrom` secret/configmap reference, passed through to the operator verbatim.
|
|
177
|
+
/** One env var of the Harness container, in the Kubernetes `EnvVar` shape. The Agent can read it,
|
|
178
|
+
* so it must not hold a model key: put a model key in `secrets`. */
|
|
99
179
|
export type HarnessEnvVar = {
|
|
180
|
+
/** The env var's name. A known model key name, such as `ANTHROPIC_API_KEY`, is refused:
|
|
181
|
+
* `harness.env[<i>] sets <NAME>, a model key the Agent could read and take out of the pod — hold it instead`. */
|
|
100
182
|
name: string;
|
|
183
|
+
/** The value, usually read from `process.env`. `jr2 up` writes it into a Secret of the Instance.
|
|
184
|
+
* Write `value` or `valueFrom`. */
|
|
101
185
|
value?: string;
|
|
186
|
+
/** Or a Kubernetes `valueFrom` reference, such as `{ secretKeyRef: { name, key } }`, given to the
|
|
187
|
+
* pod as it is. A reference to a Secret of the kit is refused:
|
|
188
|
+
* `harness.env[<i>] (<NAME>) reads Secret "<name>", which is the kit's and never the Harness's`. */
|
|
102
189
|
valueFrom?: Record<string, unknown>;
|
|
103
190
|
};
|
|
104
191
|
|
|
105
|
-
|
|
106
|
-
|
|
192
|
+
// A whole-Secret/ConfigMap env injection (corev1.EnvFromSource) for the Harness container.
|
|
193
|
+
/** A Secret or a ConfigMap whose keys all become env vars of the Harness container, in the
|
|
194
|
+
* Kubernetes `EnvFromSource` shape. Write `secretRef` or `configMapRef`. The Agent can read every
|
|
195
|
+
* key, so `jr2 up` refuses one that holds a known model key:
|
|
196
|
+
* `harness.envFrom[<i>] loads <where>, which carries <KEY> — a model key the Agent could read`. */
|
|
107
197
|
export type HarnessEnvFromSource = {
|
|
108
|
-
/**
|
|
198
|
+
/** Text put before each key's name, as Kubernetes does. Default: none. */
|
|
109
199
|
prefix?: string;
|
|
110
|
-
|
|
111
|
-
|
|
200
|
+
/** A Secret that you manage, by name: `{ name }`. `jr2 up` checks that it exists. */
|
|
201
|
+
secretRef?: {
|
|
202
|
+
/** The name of the Secret. */
|
|
203
|
+
name: string;
|
|
204
|
+
};
|
|
205
|
+
/** A ConfigMap that you manage, by name: `{ name }`. */
|
|
206
|
+
configMapRef?: {
|
|
207
|
+
/** The name of the ConfigMap. */
|
|
208
|
+
name: string;
|
|
209
|
+
};
|
|
112
210
|
};
|
|
113
211
|
|
|
114
212
|
/** This kit's version — npm version == published image tag, one release train (ADR-0019). */
|
|
@@ -118,188 +216,405 @@ export const KIT_VERSION = (
|
|
|
118
216
|
}
|
|
119
217
|
).version;
|
|
120
218
|
|
|
121
|
-
|
|
122
|
-
|
|
219
|
+
// Token limits for one model — the Harness's provider-registration options, keyed per model
|
|
220
|
+
// because limits are properties of the MODEL, not the endpoint (agents pick models per definition,
|
|
221
|
+
// ADR-0018).
|
|
222
|
+
/** The token limits of one model of a custom provider. */
|
|
123
223
|
export type HarnessProviderModel = {
|
|
124
|
-
/** The model's context window, in tokens (vLLM: `max_model_len`).
|
|
224
|
+
/** The model's context window, in tokens (vLLM: `max_model_len`). Default: the provider's
|
|
225
|
+
* `contextWindow`. */
|
|
125
226
|
contextWindow?: number;
|
|
126
|
-
/** The
|
|
227
|
+
/** The most output tokens in one completion. Default: the provider's `maxTokens`. */
|
|
127
228
|
maxTokens?: number;
|
|
128
229
|
};
|
|
129
230
|
|
|
130
|
-
/**
|
|
131
|
-
* `
|
|
132
|
-
*
|
|
231
|
+
/** The wire protocols the Harness speaks for a custom provider (`harness.provider.api`, ADR-0018) —
|
|
232
|
+
* `CUSTOM_APIS` in `@jr2/harness`, whose test holds the two lists equal. `jr2 up` refuses any other
|
|
233
|
+
* value before it starts anything, as the Harness would refuse it at boot. */
|
|
234
|
+
export const CUSTOM_PROVIDER_APIS: readonly string[] = ["openai-completions"];
|
|
235
|
+
|
|
236
|
+
// A custom model provider (ADR-0018) — what the stock Harness registers with pi's
|
|
237
|
+
// `registerProvider(id, { api, baseUrl, … })` (ADR-0027). The vLLM/Ollama path: an
|
|
238
|
+
// OpenAI-compatible endpoint under an instance-chosen provider id.
|
|
239
|
+
/** A custom model provider: an endpoint that is not in the model catalog, such as vLLM or Ollama,
|
|
240
|
+
* under a provider id that you choose. An Agent names one of its models as `<id>/<model>`. */
|
|
133
241
|
export type HarnessProvider = {
|
|
134
|
-
/** The provider id model
|
|
242
|
+
/** The provider id that model names start with: `vllm` in `vllm/Qwen/Qwen3-32B`. It must not
|
|
243
|
+
* also be a key of `harness.catalog`:
|
|
244
|
+
* `harness.catalog.<id>: "<id>" is also harness.provider.id — a custom provider owns its own baseUrl`. */
|
|
135
245
|
id: string;
|
|
136
|
-
/** The wire protocol
|
|
246
|
+
/** The wire protocol. The only value is `"openai-completions"`, which most OpenAI-compatible
|
|
247
|
+
* endpoints speak, such as vLLM and Ollama. `jr2 up` refuses any other value before it builds or
|
|
248
|
+
* applies anything:
|
|
249
|
+
* `refusing: harness.provider.api is "<api>" — the Harness speaks: openai-completions (ADR-0018)`. */
|
|
137
250
|
api: string;
|
|
138
|
-
|
|
139
|
-
|
|
251
|
+
// Deployment-varying → resolve from env (`.env`), never hardcode (ADR-0019).
|
|
252
|
+
/** The endpoint's URL. The pods must reach it: `localhost` never works, and a LAN address works
|
|
253
|
+
* on kind. It changes between deployments, so read it from `process.env` (`.env`). `jr2 up`
|
|
254
|
+
* checks from inside the cluster that the endpoint answers. */
|
|
140
255
|
baseUrl: string;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*
|
|
145
|
-
*
|
|
256
|
+
// A held secret (ADR-0059). Genuinely optional: an unauthenticated endpoint needs none (the
|
|
257
|
+
// Harness sends the wire library's placeholder, which vLLM/Ollama ignore).
|
|
258
|
+
/** The API key, when the endpoint needs one. Read it from `process.env`. It is a Held secret,
|
|
259
|
+
* `JR2_PROVIDER_API_KEY`: `jr2 up` gives the value to the Custodian only, bound to the host of
|
|
260
|
+
* `baseUrl`, and the Harness container holds only its Stand-in. Thus `baseUrl` must be
|
|
261
|
+
* `https://` when you set it:
|
|
262
|
+
* `harness.provider.apiKey is set, and harness.provider.baseUrl is not https://`. Default: none,
|
|
263
|
+
* for an endpoint that needs no key. */
|
|
146
264
|
apiKey?: string;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
265
|
+
// Limits resolve per-model → provider-level → 0, and a CUSTOM provider id has no catalog entry —
|
|
266
|
+
// unset limits resolve to 0, which leaves auto-compaction no context budget to reason about. The
|
|
267
|
+
// wire request sends no limit for a 0 `maxTokens`: pi-ai omits it when the context window is
|
|
268
|
+
// known, and the Harness removes the 1 that pi-ai would send when neither is (`unlimitedPayload`).
|
|
269
|
+
/** The context window, in tokens, of each model of this provider that `models` does not set.
|
|
270
|
+
* Set it here or in `models`: a limit that nothing sets is 0, and then Compaction has no context
|
|
271
|
+
* budget. */
|
|
151
272
|
contextWindow?: number;
|
|
273
|
+
/** The most output tokens in one completion, for each model of this provider that `models` does
|
|
274
|
+
* not set. Default: no limit is sent. */
|
|
152
275
|
maxTokens?: number;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
276
|
+
// Committable model properties, not deployment-varying: whichever model an Agent definition names
|
|
277
|
+
// resolves its own entry.
|
|
278
|
+
/** Token limits for each model, keyed by the model id after the provider prefix: the key for
|
|
279
|
+
* `vllm/Qwen/Qwen3-32B` is `"Qwen/Qwen3-32B"`. A limit set here wins over the provider's. */
|
|
156
280
|
models?: Record<string, HarnessProviderModel>;
|
|
157
281
|
};
|
|
158
282
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
283
|
+
// Where one of pi's catalog providers sends its calls (ADR-0059) — reach, not a model choice
|
|
284
|
+
// (ADR-0018). The catalog keeps every model fact (limits, cost, thinking levels).
|
|
285
|
+
/** Where the calls of one catalog provider go: a gateway that serves that provider's own API, such
|
|
286
|
+
* as LiteLLM. Only the endpoint changes. The catalog keeps every fact about the models. */
|
|
162
287
|
export type CatalogReach = {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* `https://litellm.corp.example/v1` for `openai
|
|
288
|
+
// Replaces `model.baseUrl` for every model of this provider.
|
|
289
|
+
/** The whole base URL that the provider's API adds its paths to: `https://litellm.corp.example`
|
|
290
|
+
* for `anthropic`, which adds `/v1/messages`; `https://litellm.corp.example/v1` for `openai`. It
|
|
291
|
+
* must be `https://`: `harness.catalog.<id>.baseUrl must be an https:// URL`. It changes between
|
|
292
|
+
* deployments, so read it from `process.env` (`.env`). */
|
|
166
293
|
baseUrl: string;
|
|
167
294
|
};
|
|
168
295
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
296
|
+
// One secret of the Instance (ADR-0059's entry shape, ADR-0068's list). Its placement is READ,
|
|
297
|
+
// never declared, and naming is placement: a pod gets the secrets something in it names. Both is
|
|
298
|
+
// both, and `jr2 up` reports it per pod. `hosts`, `headers` and `paths` are the binding, never the
|
|
299
|
+
// placement.
|
|
300
|
+
/**
|
|
301
|
+
* One secret of the Instance. You do not say where it goes: it goes where something names it.
|
|
302
|
+
*
|
|
303
|
+
* - It is a Held secret in each Harness pod that names it. A pod names it when its `workspace()`
|
|
304
|
+
* lists it under `secrets`, or when it is the model key of a provider that an Agent there uses. The Harness
|
|
305
|
+
* container's env var then holds only its Stand-in, `jr2-held-<name>`, and the pod's Custodian
|
|
306
|
+
* puts the value into requests toward `hosts` only.
|
|
307
|
+
* - It is an Effect secret where an Effect names it with `secret("name")`: a file in that
|
|
308
|
+
* Effect's Effector.
|
|
309
|
+
*
|
|
310
|
+
* One secret can be both. Its `hosts`, `headers` and `paths` are its binding, never its placement:
|
|
311
|
+
* they say where the value may go, not which pod gets it. `jr2 up` warns about an entry that nothing names:
|
|
312
|
+
* `secrets[<i>] (<NAME>) is named by nothing; goes nowhere`.
|
|
313
|
+
*/
|
|
314
|
+
export type SecretEntry = {
|
|
315
|
+
/** As a Held secret, the name of the env var that the Harness container sees, which holds the
|
|
316
|
+
* Stand-in. As an Effect secret, the name that `secret("<name>")` names, and the name of the
|
|
317
|
+
* Effector's file. For a Held secret, it must match `[A-Z_][A-Z0-9_]*`:
|
|
318
|
+
* `secrets[<i>].name "<name>" is not an env var name ([A-Z_][A-Z0-9_]*)`. It must not start with
|
|
319
|
+
* `JR2_` and must not be a proxy or trust variable of the kit:
|
|
320
|
+
* `secrets[<i>].name <NAME> is the kit's — JR2_ names and the proxy and trust variables belong to the mechanism`.
|
|
321
|
+
* One name, one entry: `secrets[<i>].name <NAME> is held twice`. */
|
|
173
322
|
name: string;
|
|
174
|
-
|
|
175
|
-
|
|
323
|
+
// `jr2 up` writes it into the Secret of each placement that names it —
|
|
324
|
+
// `jr2-held-secrets-<pod>` (that pod's Custodian's), `jr2-instance-effector` — and nowhere else.
|
|
325
|
+
/** The value, usually read from `process.env` (`.env`). `jr2 up` writes it only into the Secrets
|
|
326
|
+
* of the containers that hold it. Write exactly one of `value` and `valueFrom`:
|
|
327
|
+
* `secrets[<i>] needs exactly one of value or valueFrom`. A `.env` variable that is not set reads
|
|
328
|
+
* as no value, and an empty one is refused: `secrets[<i>].value is empty — set <NAME> in .env`. */
|
|
176
329
|
value?: string;
|
|
177
|
-
/** Or a key of a Secret you manage (Sealed Secrets, External Secrets)
|
|
178
|
-
*
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
330
|
+
/** Or a key of a Secret that you manage (Sealed Secrets, External Secrets):
|
|
331
|
+
* `{ secretKeyRef: { name, key } }`. `jr2 up` checks that the Secret and the key exist. Each
|
|
332
|
+
* container that holds the secret mounts that one key as a file, and no other container does. */
|
|
333
|
+
valueFrom?: {
|
|
334
|
+
/** The Secret and the key in it that hold the value. */
|
|
335
|
+
secretKeyRef: {
|
|
336
|
+
/** The name of the Secret. */
|
|
337
|
+
name: string;
|
|
338
|
+
/** The key in the Secret that holds the value. */
|
|
339
|
+
key: string;
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
// A known model-key name is a key of `MODEL_KEYS` (model-keys.ts), which holds its default host.
|
|
343
|
+
/** Where the value may go as a Held secret. Each entry is `host`, `host:port`, or an `https://`
|
|
344
|
+
* URL, of which only the host and port count. Write exact DNS names or IP addresses, with no
|
|
345
|
+
* wildcards. The port default is 443. A known model key, such as `ANTHROPIC_API_KEY`, may leave
|
|
346
|
+
* it out: it then goes to the one host that its provider calls. Any other Held secret must name
|
|
347
|
+
* its hosts: `secrets[<i>].hosts is absent, and <NAME> has no single default host`. */
|
|
184
348
|
hosts?: readonly string[];
|
|
185
|
-
/**
|
|
186
|
-
* `["authorization", "x-api-key", "x-goog-api-key", "api-key"]`.
|
|
349
|
+
/** The request headers in which the Custodian puts the value in place of the Stand-in,
|
|
350
|
+
* lowercase. Default: `["authorization", "x-api-key", "x-goog-api-key", "api-key"]`. A header
|
|
351
|
+
* that frames or routes the request is refused:
|
|
352
|
+
* `secrets[<i>].headers[<j>] "<header>" cannot carry a held secret`. */
|
|
187
353
|
headers?: readonly string[];
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
354
|
+
// A key minted through the Custodian is a key the Agent can take away.
|
|
355
|
+
/** The path prefixes to which the value may be sent, such as `/v1/messages`. Each starts with
|
|
356
|
+
* `/`. Default: every path. Narrow it for a gateway key that can reach admin routes, such as a
|
|
357
|
+
* LiteLLM key that can make new keys. The Agent could take away a key made that way. */
|
|
191
358
|
paths?: readonly string[];
|
|
192
359
|
};
|
|
193
360
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
361
|
+
// The agent-runtime section (ADR-0018): what the stock Harness image consumes alongside the Agent
|
|
362
|
+
// definition each Turn hands it (ADR-0049). Moved out of `sandbox` deliberately — `sandbox` is pod
|
|
363
|
+
// transport (it still CARRIES this env to the Harness container), but model concerns are Harness
|
|
364
|
+
// semantics.
|
|
365
|
+
//
|
|
366
|
+
// It declares what this instance can REACH — endpoints, credentials, trust — and never WHICH
|
|
367
|
+
// model to use (ADR-0018). This config holds no instance-wide `model` default and no Agent
|
|
368
|
+
// roster: an Agent is an actor slot on ONE Machine, whose definition rides the Turn (ADR-0049).
|
|
369
|
+
// One definition value may still be carried by several Machines, so the variation that matters is
|
|
370
|
+
// per-definition and per-invocation, which one global default serves not at all.
|
|
371
|
+
/** What the Harness can reach: the model endpoints, their credentials, and the CAs it trusts. It
|
|
372
|
+
* never says which model an Agent uses: each Agent's definition names its model. */
|
|
204
373
|
export type HarnessConfig = {
|
|
205
|
-
|
|
206
|
-
|
|
374
|
+
// Preflighted from inside the cluster by `jr2 up` (ADR-0019); its `apiKey` is held (ADR-0059).
|
|
375
|
+
/** A custom model provider, such as a vLLM or Ollama endpoint. See {@link HarnessProvider}.
|
|
376
|
+
* Default: none; the Agents use catalog providers only. */
|
|
207
377
|
provider?: HarnessProvider;
|
|
208
|
-
|
|
209
|
-
|
|
378
|
+
// A gateway that serves the provider's own API (ADR-0059).
|
|
379
|
+
/** Catalog providers whose calls go to another endpoint, such as a gateway, keyed by the catalog
|
|
380
|
+
* provider id: `anthropic`, `openai`, and so on. See {@link CatalogReach}. A key that is not a
|
|
381
|
+
* catalog provider with a known model key is refused:
|
|
382
|
+
* `harness.catalog.<id>: "<id>" is not a pi catalog provider jr2 knows a key for`. Default:
|
|
383
|
+
* each provider's own endpoint. */
|
|
210
384
|
catalog?: Readonly<Record<string, CatalogReach>>;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* into the instance-owned Secret by `jr2 up`. The Agent can read every one of them, so a known
|
|
217
|
-
* model key here is refused — it goes in `heldSecrets` (ADR-0059). */
|
|
385
|
+
// Values read from `process.env`/`.env` are materialized into the instance-owned Secret by
|
|
386
|
+
// `jr2 up`. A known model key here is refused (ADR-0059).
|
|
387
|
+
/** Env vars for the Harness container. The Agent can read every one of them, so do not put a
|
|
388
|
+
* credential here. A known model key is refused here. Put it in the top-level `secrets` list.
|
|
389
|
+
* See {@link HarnessEnvVar}. */
|
|
218
390
|
env?: readonly HarnessEnvVar[];
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
391
|
+
// `jr2 up` preflights that each referenced Secret exists (ADR-0019), and refuses one that
|
|
392
|
+
// carries a known model key (ADR-0059).
|
|
393
|
+
/** Secrets and ConfigMaps that you manage, whose keys all become env vars of the Harness
|
|
394
|
+
* container. See {@link HarnessEnvFromSource}. */
|
|
222
395
|
envFrom?: readonly HarnessEnvFromSource[];
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
*
|
|
227
|
-
*
|
|
396
|
+
// Only `jr2 up` reads it (host-side): it materializes the `jr2-ca` ConfigMap and runs the
|
|
397
|
+
// provider preflight with the same trust (ADR-0020, ADR-0059).
|
|
398
|
+
/** A path to a PEM CA bundle, relative to the Instance folder. An example is the CA of an
|
|
399
|
+
* internal endpoint in front of a LAN vLLM. Commit the file: CA certificates are public. `jr2 up`
|
|
400
|
+
* puts it in the `jr2-ca` ConfigMap and uses it when it checks the provider. The Harness
|
|
401
|
+
* container trusts it, and so does the Custodian when it verifies a host that a Held secret is
|
|
402
|
+
* bound to. */
|
|
228
403
|
caBundle?: string;
|
|
229
404
|
};
|
|
230
405
|
|
|
231
|
-
|
|
232
|
-
|
|
406
|
+
// One pod-spec toleration, verbatim (ADR-0052): `nodeSelector` and `tolerations` are the raw
|
|
407
|
+
// Kubernetes shapes, the stance `HarnessEnvVar` takes for `EnvVar`.
|
|
408
|
+
/** One toleration of a Sandbox pod, in the Kubernetes `Toleration` shape: a node taint that the
|
|
409
|
+
* pod tolerates. */
|
|
233
410
|
export type Toleration = {
|
|
411
|
+
/** The taint key. Leave it out, with `operator: "Exists"`, to tolerate every taint. */
|
|
234
412
|
key?: string;
|
|
413
|
+
/** `"Equal"` matches a taint whose value is `value`; `"Exists"` matches any value. Default:
|
|
414
|
+
* `"Equal"`. */
|
|
235
415
|
operator?: "Exists" | "Equal";
|
|
416
|
+
/** The taint value, for `operator: "Equal"`. */
|
|
236
417
|
value?: string;
|
|
418
|
+
/** The taint effect to tolerate: `"NoSchedule"`, `"PreferNoSchedule"` or `"NoExecute"`. Default:
|
|
419
|
+
* every effect. */
|
|
237
420
|
effect?: "NoSchedule" | "PreferNoSchedule" | "NoExecute";
|
|
421
|
+
/** For `"NoExecute"`: how many seconds the pod stays on the node after the taint is added.
|
|
422
|
+
* Default: the pod stays. */
|
|
238
423
|
tolerationSeconds?: number;
|
|
239
424
|
};
|
|
240
425
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
*
|
|
248
|
-
|
|
426
|
+
// Where an Instance's Sandboxes may land (ADR-0052). Both keys ride the Sandbox pod verbatim, the
|
|
427
|
+
// Repo cache agent's DaemonSet takes the same two so a cache is only ever where a Sandbox can reach
|
|
428
|
+
// it, and `jr2 up` reads the same predicate off the nodes to report the set. A Machine says nothing
|
|
429
|
+
// about placement: a node label is a deployment fact (ADR-0050). These two are the default class;
|
|
430
|
+
// a per-Machine `classes` map is the deferred extension.
|
|
431
|
+
/** Which nodes are Sandbox nodes: the nodes on which this Instance's Sandboxes may run. Absent, a
|
|
432
|
+
* Sandbox runs wherever an ordinary pod runs: on a node that is not cordoned and has no taint. No
|
|
433
|
+
* jr2 label is ever needed. The Repo cache agent runs on the same nodes, and `jr2 up` reports the
|
|
434
|
+
* set and warns when it is empty. */
|
|
249
435
|
export type SandboxPlacement = {
|
|
250
|
-
/**
|
|
436
|
+
/** The pod's `nodeSelector`: a Sandbox node must have every one of these labels. Default: none. */
|
|
251
437
|
nodeSelector?: Readonly<Record<string, string>>;
|
|
252
|
-
|
|
253
|
-
|
|
438
|
+
// No `tolerationSeconds` is added.
|
|
439
|
+
/** The pod's `tolerations`, as you write them. Default: none. jr2 adds no `tolerationSeconds`.
|
|
440
|
+
* Thus a tolerated `NoExecute` taint keeps a Sandbox on its node while its Lease is renewed. */
|
|
254
441
|
tolerations?: readonly Toleration[];
|
|
255
442
|
};
|
|
256
443
|
|
|
444
|
+
// The Instance's Sandbox defaults (ADR-0052, ADR-0060).
|
|
445
|
+
/** The Instance's Sandbox defaults: the Sandbox nodes (see {@link SandboxPlacement}), and the Size
|
|
446
|
+
* of a Workspace that states none. The config load refuses a `sandbox` that is not an object:
|
|
447
|
+
* `<file> at sandbox: expected an object — { nodeSelector?, tolerations?, resources? }`. A key that
|
|
448
|
+
* this type does not have fails the typecheck that `jr2 up` runs. */
|
|
449
|
+
export type SandboxConfig = SandboxPlacement & {
|
|
450
|
+
// NEVER an override of a Size a Machine states: a Size is a fact of the Machine, and its one
|
|
451
|
+
// override path is `customize(machine, { resources })` (ADR-0060).
|
|
452
|
+
/**
|
|
453
|
+
* The Size of a Workspace that states no Size at all: `{ limits: { memory, cpu } }` and nothing
|
|
454
|
+
* else. The kit default fills a field that it leaves out. It never changes a Size that a Machine
|
|
455
|
+
* states: to change that, use `customize(machine, { resources })`. Default: the kit default,
|
|
456
|
+
* memory `2Gi` and cpu `1`. jr2 reserves the whole Size and enforces the cpu limit. See
|
|
457
|
+
* {@link Size} for the refusals, such as
|
|
458
|
+
* `<file> at sandbox.resources.requests is not accepted`.
|
|
459
|
+
*/
|
|
460
|
+
resources?: Size;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// The PriorityClasses jr2's pods run under (ADR-0060). Absent, `jr2 up` creates two,
|
|
464
|
+
// cluster-scoped, beside the CRDs.
|
|
465
|
+
/**
|
|
466
|
+
* The PriorityClasses that jr2's pods run under. Absent, `jr2 up` creates two: `jr2-control`
|
|
467
|
+
* (value 100000, `PreemptLowerPriority`) and `jr2-sandbox` (value 1000, `preemptionPolicy: Never`).
|
|
468
|
+
* Thus an ordinary pod cannot preempt a live Workspace, and a Sandbox that waits for a node evicts
|
|
469
|
+
* no pod. A cluster with its own scheme names its existing classes here, and `jr2 up` then creates
|
|
470
|
+
* none for that key. The config load refuses any other key:
|
|
471
|
+
* `<file> at priorityClasses<path>: <problem> — { control?, sandbox? }, each the name of an existing PriorityClass`.
|
|
472
|
+
*/
|
|
473
|
+
export type PriorityClasses = {
|
|
474
|
+
/** An existing PriorityClass for the Orchestrator, the operator and the Repo cache agent.
|
|
475
|
+
* Default: `jr2-control`, which `jr2 up` creates. */
|
|
476
|
+
control?: string;
|
|
477
|
+
/** An existing PriorityClass for the Sandboxes and the Instance Harness. Default: `jr2-sandbox`,
|
|
478
|
+
* which `jr2 up` creates. */
|
|
479
|
+
sandbox?: string;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* The shape of `jr2.config.ts`: the deployment facts of an Instance, which a Machine cannot carry.
|
|
484
|
+
* It holds the Instance's name, its secrets, how the cluster authenticates to Repos, what the
|
|
485
|
+
* Harness can reach, where Sandboxes run, and where images go. It names no Repo, no model and no
|
|
486
|
+
* Sandbox Image: a Machine names those itself.
|
|
487
|
+
*
|
|
488
|
+
* Every field is optional, and an Instance can run with no `jr2.config.ts`. Write the file as
|
|
489
|
+
* `export default defineConfig({ … })`. When the config loads, a file with no default export is
|
|
490
|
+
* refused: `` <file> has no default export (use `export default defineConfig({…})`) ``. The load also
|
|
491
|
+
* checks the shape of `git.credentials`, `secrets`, `harness.catalog` and `priorityClasses`. It
|
|
492
|
+
* checks that `sandbox` is an object, and checks its `resources`. Each refusal names the field.
|
|
493
|
+
* `jr2 up` checks the rest.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* ```ts
|
|
497
|
+
* import { defineConfig, type JR2Config } from "@jr2/orchestrator";
|
|
498
|
+
*
|
|
499
|
+
* const harness: JR2Config["harness"] = {
|
|
500
|
+
* provider: {
|
|
501
|
+
* id: "vllm",
|
|
502
|
+
* api: "openai-completions",
|
|
503
|
+
* baseUrl: process.env.VLLM_URL ?? "",
|
|
504
|
+
* models: { "Qwen/Qwen3.6-35B-A3B": { contextWindow: 131072, maxTokens: 16384 } },
|
|
505
|
+
* },
|
|
506
|
+
* };
|
|
507
|
+
*
|
|
508
|
+
* export default defineConfig({ name: "my-instance", harness });
|
|
509
|
+
* ```
|
|
510
|
+
*
|
|
511
|
+
* @see docs/reference/config.md
|
|
512
|
+
* @see docs/get-started/connect-a-model.md
|
|
513
|
+
* @see docs/concepts/secrets-and-trust.md
|
|
514
|
+
* @see docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md
|
|
515
|
+
*/
|
|
257
516
|
export type JR2Config = {
|
|
258
|
-
|
|
259
|
-
|
|
517
|
+
// The instance's identity (ADR-0019).
|
|
518
|
+
/** The Instance's name. Its Kubernetes namespace is this name (`-n` overrides it), and
|
|
519
|
+
* `jr2 up` labels every object that it owns with it. Default: the name of the Instance folder. */
|
|
260
520
|
name?: string;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
521
|
+
// One list for both sides (ADR-0068). Naming is the placement; `hosts` is a Held secret's
|
|
522
|
+
// binding, never its placement: an entry nothing names goes nowhere.
|
|
523
|
+
/** Every secret of the Instance, in one list. A secret goes where something names it. A Held
|
|
524
|
+
* secret goes where a `workspace()` lists it under `secrets` or an Agent's provider reads it. An
|
|
525
|
+
* Effect secret goes where an Effect names it with `secret("name")`. Its `hosts` is a Held
|
|
526
|
+
* secret's binding, never its placement: an entry that nothing names goes nowhere. A model API
|
|
527
|
+
* key goes here, never in `harness.env`. See {@link SecretEntry}. Each entry is checked when the
|
|
528
|
+
* config loads: `<file> at secrets[<i>]<path>: <problem>`, followed by the shape of an entry. */
|
|
529
|
+
secrets?: readonly SecretEntry[];
|
|
530
|
+
// NOT a list of Repos (ADR-0051): a Machine names those itself, through its `workspace()`'s Repo
|
|
531
|
+
// Slots, and whether the instance has a data plane at all is read off the registered Machines.
|
|
532
|
+
/** How the cluster authenticates to Repos, and the fence that a per-run url must pass. It is not
|
|
533
|
+
* a list of Repos: a Machine names its Repos in the Repo Slots of its `workspace()`. See
|
|
534
|
+
* {@link GitConfig}. */
|
|
264
535
|
git?: GitConfig;
|
|
265
|
-
/**
|
|
536
|
+
/** What the Harness can reach: a custom model provider, gateways for catalog providers, env
|
|
537
|
+
* vars, and CAs. See {@link HarnessConfig}. */
|
|
266
538
|
harness?: HarnessConfig;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
539
|
+
// See `SandboxPlacement` (ADR-0052) and `SandboxConfig` (ADR-0060).
|
|
540
|
+
/** Which nodes are Sandbox nodes, and the Size of a Workspace that states none. Default: a
|
|
541
|
+
* Sandbox runs wherever an ordinary pod runs, at the kit's Size. See {@link SandboxConfig}. */
|
|
542
|
+
sandbox?: SandboxConfig;
|
|
543
|
+
// See `PriorityClasses` (ADR-0060).
|
|
544
|
+
/** Existing PriorityClasses to use in place of the two that `jr2 up` creates. See
|
|
545
|
+
* {@link PriorityClasses}. */
|
|
546
|
+
priorityClasses?: PriorityClasses;
|
|
547
|
+
// Deployment-varying — resolve from env (ADR-0019).
|
|
548
|
+
/** The image registry prefix to which `jr2 up` pushes the images that it builds, such as
|
|
549
|
+
* `registry.example.com/team`. It changes between deployments, so read it from `process.env`
|
|
550
|
+
* (`.env`). Default: none; `jr2 up` loads the images into the kind cluster. On a cluster that is
|
|
551
|
+
* not kind, `jr2 up` then refuses:
|
|
552
|
+
* `` context <context> is not a kind cluster and no `registry` is configured ``. */
|
|
272
553
|
registry?: string;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
*
|
|
281
|
-
*
|
|
554
|
+
// Deployment-varying — resolve from env. Seeding that mirror is a deliberate, instance-less act
|
|
555
|
+
// (`jr2 kit push`), never a side effect of `jr2 up` (ADR-0044).
|
|
556
|
+
//
|
|
557
|
+
// Separate from `registry` on purpose: `registry` addresses images THIS converge builds,
|
|
558
|
+
// `kitRegistry` addresses artifacts the kit already published. One key for both would make every
|
|
559
|
+
// private-registry user mirror the Kit images they could have pulled from the home.
|
|
560
|
+
/** The registry from which this cluster pulls the published Kit images: a mirror for a cluster
|
|
561
|
+
* that is self-hosted, air-gapped, or mirror-only. Default: none; the cluster pulls from the
|
|
562
|
+
* canonical home, `ghcr.io/snapwich/jr2-harness:<version>` and the others. Set, the cluster pulls
|
|
563
|
+
* the same tags from `<kitRegistry>/jr2-harness:<version>`. Fill the mirror with
|
|
564
|
+
* `jr2 kit push <registry>`; `jr2 up` never pushes to it. Read it from `process.env` (`.env`). */
|
|
282
565
|
kitRegistry?: string;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
566
|
+
// Deployment-varying — resolve from env (ADR-0045). Present → ABSOLUTE: derivation is skipped
|
|
567
|
+
// (still intersected, so an unpublished platform is a named error, never a silent build).
|
|
568
|
+
//
|
|
569
|
+
// The escape hatch for the two cases derivation cannot see: a pool that autoscales from zero (no
|
|
570
|
+
// nodes to read yet), and a polluted set (an amd64 GPU pool beside arm64 workers, where the
|
|
571
|
+
// derived pair would cost a needless qemu cross-build). Not additive or subtractive.
|
|
572
|
+
/** The platforms for which `jr2 up` builds its images, as docker platform strings, such as
|
|
573
|
+
* `["linux/arm64"]`. Default: the platforms of the cluster's schedulable nodes that the kit
|
|
574
|
+
* releases images for. `jr2 up` reports a node platform that it skips.
|
|
288
575
|
*
|
|
289
|
-
*
|
|
290
|
-
* nodes
|
|
291
|
-
*
|
|
576
|
+
* Set it when the nodes cannot tell. One case is a node group that scales up from zero. Another
|
|
577
|
+
* is a set of nodes that need no image, such as an amd64 GPU group beside arm64 nodes. When you
|
|
578
|
+
* set it, it replaces the default. It adds to nothing and removes from nothing. The kit releases
|
|
579
|
+
* images for `linux/amd64` and `linux/arm64` only, so `jr2 up` refuses any other:
|
|
580
|
+
* `` `platforms` names <platforms> — the kit releases images for linux/amd64, linux/arm64 and nothing else ``. */
|
|
292
581
|
platforms?: readonly string[];
|
|
293
|
-
|
|
582
|
+
// Kit development territory (ADR-0019).
|
|
583
|
+
/** Settings of the operator layer, for kit development. */
|
|
294
584
|
operator?: {
|
|
295
|
-
/** `false
|
|
585
|
+
/** `false`: `jr2 up` does not converge the operator, and you run its controller yourself.
|
|
586
|
+
* Default: `true`. */
|
|
296
587
|
manage?: boolean;
|
|
297
588
|
};
|
|
298
589
|
};
|
|
299
590
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
591
|
+
// Identity passthrough that checks a config object against `JR2Config` where it is written —
|
|
592
|
+
// excess keys included, at every depth, which is why the parameter is the type and not a generic
|
|
593
|
+
// bound by it.
|
|
594
|
+
/**
|
|
595
|
+
* Declare the config of an Instance: the default export of `jr2.config.ts`. It gives back the
|
|
596
|
+
* object that you pass. Its only work is to check that object against {@link JR2Config} where you
|
|
597
|
+
* write it, at every depth, a misspelled key included. `jr2 up` typechecks the file before it
|
|
598
|
+
* converges.
|
|
599
|
+
*
|
|
600
|
+
* @param c - The Instance's config. See {@link JR2Config} for every field.
|
|
601
|
+
* @returns The same object.
|
|
602
|
+
*
|
|
603
|
+
* @example
|
|
604
|
+
* ```ts
|
|
605
|
+
* import { defineConfig } from "@jr2/orchestrator";
|
|
606
|
+
*
|
|
607
|
+
* export default defineConfig({
|
|
608
|
+
* name: "my-instance",
|
|
609
|
+
* secrets: [{ name: "ANTHROPIC_API_KEY", value: process.env.ANTHROPIC_API_KEY }],
|
|
610
|
+
* git: { credentials: [{ match: "github.com/you/", token: "GITHUB_TOKEN" }] },
|
|
611
|
+
* });
|
|
612
|
+
* ```
|
|
613
|
+
*
|
|
614
|
+
* @see docs/reference/config.md
|
|
615
|
+
* @see docs/get-started/create-an-instance.md
|
|
616
|
+
* @see docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md
|
|
617
|
+
*/
|
|
303
618
|
export function defineConfig(c: JR2Config): JR2Config {
|
|
304
619
|
return c;
|
|
305
620
|
}
|
|
@@ -308,9 +623,9 @@ export function defineConfig(c: JR2Config): JR2Config {
|
|
|
308
623
|
* Load an instance's `jr2.config.ts` (default export). Absent file → undefined (an instance
|
|
309
624
|
* can boot configless); a file that fails to IMPORT throws — a broken config must be loud,
|
|
310
625
|
* never silently treated as "no config". The shape checks live here, on the path the host CLI
|
|
311
|
-
* and the in-cluster Orchestrator share: `git.credentials`, `harness.
|
|
312
|
-
*
|
|
313
|
-
*
|
|
626
|
+
* and the in-cluster Orchestrator share: `git.credentials`, `secrets` and `harness.catalog` are
|
|
627
|
+
* checked at runtime, because an instance is zero-build and nothing typechecks this file before
|
|
628
|
+
* Node strips its types and imports it. SHAPE only: a secret's
|
|
314
629
|
* `value` reads `.env`, which the in-cluster evaluation does not have, so an absent value is not
|
|
315
630
|
* this function's to refuse — `jr2 up` checks presence and every held-secret rule host-side
|
|
316
631
|
* (`heldSecretsOf`, ADR-0059).
|
|
@@ -321,12 +636,29 @@ export async function loadConfig(dir: string): Promise<JR2Config | undefined> {
|
|
|
321
636
|
const mod = (await import(pathToFileURL(file).href)) as { default?: JR2Config };
|
|
322
637
|
if (!mod.default) throw new Error(`${file} has no default export (use \`export default defineConfig({…})\`)`);
|
|
323
638
|
if (mod.default.git !== undefined) checkGit(mod.default.git, file);
|
|
324
|
-
if (mod.default.
|
|
639
|
+
if (mod.default.secrets !== undefined) checkSecrets(mod.default.secrets, file);
|
|
640
|
+
if (mod.default.harness !== undefined) checkHarness(mod.default.harness, file);
|
|
641
|
+
if (mod.default.sandbox !== undefined) checkSandbox(mod.default.sandbox, file);
|
|
642
|
+
if (mod.default.priorityClasses !== undefined) checkPriorityClasses(mod.default.priorityClasses, file);
|
|
325
643
|
return mod.default;
|
|
326
644
|
}
|
|
327
645
|
|
|
328
646
|
const NonEmpty = z.string().min(1);
|
|
329
|
-
const Credential = z
|
|
647
|
+
const Credential = z
|
|
648
|
+
.object({
|
|
649
|
+
match: NonEmpty,
|
|
650
|
+
token: NonEmpty.optional(),
|
|
651
|
+
sshKey: NonEmpty.optional(),
|
|
652
|
+
push: z.union([z.object({ sshKey: NonEmpty }).strict(), z.object({ token: NonEmpty }).strict()]).optional(),
|
|
653
|
+
sign: z
|
|
654
|
+
.union([
|
|
655
|
+
z.object({ sshKey: NonEmpty, name: NonEmpty, email: NonEmpty }).strict(),
|
|
656
|
+
z.object({ gpgKey: NonEmpty, name: NonEmpty, email: NonEmpty }).strict(),
|
|
657
|
+
])
|
|
658
|
+
.optional(),
|
|
659
|
+
committer: z.object({ name: NonEmpty, email: NonEmpty }).strict().optional(),
|
|
660
|
+
})
|
|
661
|
+
.strict();
|
|
330
662
|
|
|
331
663
|
/** `git.credentials`' SHAPE, checked at load — by index and field, so a bad entry names itself
|
|
332
664
|
* (`git.credentials[1].token`) rather than surfacing as a Secret that never matches. */
|
|
@@ -345,10 +677,12 @@ function checkGit(git: unknown, where: string): void {
|
|
|
345
677
|
}
|
|
346
678
|
}
|
|
347
679
|
|
|
348
|
-
const GIT_HINT =
|
|
680
|
+
const GIT_HINT =
|
|
681
|
+
"an entry is { match, token?, sshKey?, push?: { sshKey } | { token }, sign?: { sshKey | gpgKey, name, email }, " +
|
|
682
|
+
"committer?: { name, email } } (ADR-0051)";
|
|
349
683
|
|
|
350
684
|
const OptionalText = z.string().optional();
|
|
351
|
-
const
|
|
685
|
+
const SecretShape = z
|
|
352
686
|
.object({
|
|
353
687
|
name: NonEmpty,
|
|
354
688
|
// `.env` values are absent in-cluster, so `undefined` must pass here — presence is `jr2 up`'s.
|
|
@@ -364,25 +698,25 @@ const HeldSecretShape = z
|
|
|
364
698
|
.strict();
|
|
365
699
|
const CatalogReachShape = z.object({ baseUrl: z.string() }).strict();
|
|
366
700
|
|
|
367
|
-
/** `
|
|
368
|
-
*
|
|
369
|
-
function
|
|
701
|
+
/** `secrets`' SHAPE (ADR-0059's entry, ADR-0068's list) — by index and field, the `checkGit` style,
|
|
702
|
+
* so a bad entry names itself (`secrets[1].hosts`). */
|
|
703
|
+
function checkSecrets(secrets: unknown, where: string): void {
|
|
704
|
+
if (!Array.isArray(secrets)) throw new Error(`${where} at secrets: expected an array — ${SECRET_HINT}`);
|
|
705
|
+
for (const [i, entry] of secrets.entries()) {
|
|
706
|
+
const parsed = SecretShape.safeParse(entry);
|
|
707
|
+
if (parsed.success) continue;
|
|
708
|
+
const issue = parsed.error.issues[0];
|
|
709
|
+
const at = ["", ...(issue?.path ?? [])].map(String).join(".");
|
|
710
|
+
throw new Error(`${where} at secrets[${i}]${at}: ${issue?.message ?? "invalid"} — ${SECRET_HINT}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** `harness.catalog`'s SHAPE (ADR-0059) — by provider id and field, the `checkGit` style. */
|
|
715
|
+
function checkHarness(harness: unknown, where: string): void {
|
|
370
716
|
if (typeof harness !== "object" || harness === null || Array.isArray(harness)) {
|
|
371
717
|
throw new Error(`${where} at harness: expected an object`);
|
|
372
718
|
}
|
|
373
|
-
const {
|
|
374
|
-
if (heldSecrets !== undefined) {
|
|
375
|
-
if (!Array.isArray(heldSecrets)) {
|
|
376
|
-
throw new Error(`${where} at harness.heldSecrets: expected an array — ${HELD_HINT}`);
|
|
377
|
-
}
|
|
378
|
-
for (const [i, entry] of heldSecrets.entries()) {
|
|
379
|
-
const parsed = HeldSecretShape.safeParse(entry);
|
|
380
|
-
if (parsed.success) continue;
|
|
381
|
-
const issue = parsed.error.issues[0];
|
|
382
|
-
const at = ["", ...(issue?.path ?? [])].map(String).join(".");
|
|
383
|
-
throw new Error(`${where} at harness.heldSecrets[${i}]${at}: ${issue?.message ?? "invalid"} — ${HELD_HINT}`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
719
|
+
const { catalog } = harness as { catalog?: unknown };
|
|
386
720
|
if (catalog !== undefined) {
|
|
387
721
|
if (typeof catalog !== "object" || catalog === null || Array.isArray(catalog)) {
|
|
388
722
|
throw new Error(`${where} at harness.catalog: expected an object keyed by catalog provider id — ${CATALOG_HINT}`);
|
|
@@ -397,6 +731,30 @@ function checkHeld(harness: unknown, where: string): void {
|
|
|
397
731
|
}
|
|
398
732
|
}
|
|
399
733
|
|
|
400
|
-
const
|
|
401
|
-
"an entry is { name, value? | valueFrom?: { secretKeyRef: { name, key } }, hosts?, headers?, paths? } (ADR-0059)";
|
|
734
|
+
const SECRET_HINT =
|
|
735
|
+
"an entry is { name, value? | valueFrom?: { secretKeyRef: { name, key } }, hosts?, headers?, paths? } (ADR-0059, ADR-0068)";
|
|
402
736
|
const CATALOG_HINT = "an entry is <provider id>: { baseUrl } (ADR-0059)";
|
|
737
|
+
|
|
738
|
+
/** `sandbox.resources`' SHAPE (ADR-0060), checked at load by field — `requests`, or any key beside
|
|
739
|
+
* the two limits, is refused naming itself (`sandbox.resources.requests`). */
|
|
740
|
+
function checkSandbox(sandbox: unknown, where: string): void {
|
|
741
|
+
if (typeof sandbox !== "object" || sandbox === null || Array.isArray(sandbox)) {
|
|
742
|
+
throw new Error(`${where} at sandbox: expected an object — { nodeSelector?, tolerations?, resources? }`);
|
|
743
|
+
}
|
|
744
|
+
const { resources } = sandbox as { resources?: unknown };
|
|
745
|
+
if (resources !== undefined) assertSize(`${where} at sandbox.resources`, resources);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const PriorityClassesShape = z.object({ control: NonEmpty.optional(), sandbox: NonEmpty.optional() }).strict();
|
|
749
|
+
|
|
750
|
+
/** `priorityClasses`' SHAPE (ADR-0060): two optional class names, nothing else. */
|
|
751
|
+
function checkPriorityClasses(classes: unknown, where: string): void {
|
|
752
|
+
const parsed = PriorityClassesShape.safeParse(classes);
|
|
753
|
+
if (parsed.success) return;
|
|
754
|
+
const issue = parsed.error.issues[0];
|
|
755
|
+
const at = ["", ...(issue?.path ?? [])].map(String).join(".");
|
|
756
|
+
throw new Error(
|
|
757
|
+
`${where} at priorityClasses${at}: ${issue?.message ?? "invalid"} — { control?, sandbox? }, each the name of ` +
|
|
758
|
+
"an existing PriorityClass (ADR-0060)",
|
|
759
|
+
);
|
|
760
|
+
}
|