@jr2/orchestrator 0.5.1 → 0.6.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/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +56 -20
- package/docs/concepts/architecture.md +30 -17
- package/docs/concepts/charts.md +6 -3
- package/docs/concepts/converge.md +3 -0
- package/docs/concepts/effects.md +86 -31
- package/docs/concepts/machines-and-workflows.md +9 -0
- package/docs/concepts/pools-and-sources.md +6 -6
- package/docs/concepts/secrets-and-trust.md +126 -42
- package/docs/concepts/the-menu.md +3 -0
- package/docs/concepts/workspaces.md +129 -51
- package/docs/get-started/an-agent-in-a-sandbox.md +21 -19
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +17 -4
- package/docs/guides/bind-open-parts.md +5 -4
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +1 -1
- package/docs/guides/let-an-agent-write-a-chart.md +5 -1
- package/docs/guides/look-inside-a-sandbox.md +18 -15
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +19 -18
- package/docs/guides/set-an-agents-access-and-dials.md +67 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/use-a-hosted-provider.md +6 -0
- package/docs/guides/watch-a-run.md +47 -24
- package/docs/guides/write-a-workflow.md +1 -1
- package/docs/llms.txt +33 -12
- package/docs/reference/authoring-surface/agent.md +34 -11
- package/docs/reference/authoring-surface/answer.md +19 -3
- package/docs/reference/authoring-surface/arg.md +27 -5
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/command.md +36 -21
- package/docs/reference/authoring-surface/fail.md +17 -4
- package/docs/reference/authoring-surface/file.md +36 -16
- package/docs/reference/authoring-surface/http.md +108 -8
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +1 -1
- package/docs/reference/authoring-surface/open.md +36 -5
- package/docs/reference/authoring-surface/paramsof.md +22 -4
- package/docs/reference/authoring-surface/pool.md +4 -4
- package/docs/reference/authoring-surface/push.md +30 -12
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/secret.md +25 -4
- package/docs/reference/authoring-surface/source.md +3 -3
- package/docs/reference/authoring-surface/type-agentdefinition.md +2 -2
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +5 -6
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +22 -4
- package/docs/reference/authoring-surface/type-customize.md +4 -2
- package/docs/reference/authoring-surface/type-dispatchoutput.md +8 -1
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +2 -4
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +7 -1
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +2 -2
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +32 -7
- package/docs/reference/authoring-surface/type-reposlot.md +5 -5
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcespec.md +10 -10
- package/docs/reference/authoring-surface/type-workspaced.md +29 -8
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +65 -37
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +30 -10
- package/docs/reference/cli.md +1 -1
- package/docs/reference/config.md +13 -4
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +17 -10
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +29 -19
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +30 -30
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +35 -17
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +8 -2
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +6 -4
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +21 -9
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +2 -1
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +8 -9
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +62 -65
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +9 -1
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +13 -12
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +4 -1
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +18 -19
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +8 -8
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +4 -4
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +24 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +5 -3
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +110 -67
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +101 -74
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +5 -6
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +2 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +181 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +74 -63
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +31 -20
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions.md +3 -0
- package/docs/reference/glossary.md +51 -12
- package/docs/reference/http-api.md +45 -7
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +5 -4
- package/package.json +3 -3
- package/src/actor.ts +161 -35
- package/src/agent.ts +6 -6
- package/src/ambient.ts +3 -5
- package/src/chart.ts +186 -19
- package/src/charts.ts +9 -2
- package/src/config.ts +16 -6
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +251 -77
- package/src/customize.ts +11 -3
- package/src/effect-checks.ts +50 -3
- package/src/effect.ts +343 -127
- package/src/effector-wire.ts +6 -3
- package/src/fail.ts +17 -4
- package/src/harness-client.ts +74 -31
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +125 -16
- package/src/holds.ts +3 -0
- package/src/http-effect.ts +88 -32
- package/src/http.ts +56 -17
- package/src/index.ts +15 -1
- package/src/internal.ts +1 -0
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +3 -4
- package/src/model-keys.ts +24 -0
- package/src/names.ts +19 -6
- package/src/notices.ts +443 -54
- package/src/open-body.ts +85 -19
- package/src/open.ts +36 -5
- package/src/parts.ts +40 -28
- package/src/party.ts +33 -0
- package/src/pick.ts +41 -7
- package/src/pool.ts +44 -23
- package/src/registration.ts +53 -11
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +106 -20
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +63 -16
- package/src/secret-placement.ts +9 -2
- package/src/setup.ts +86 -6
- package/src/tokens.ts +107 -32
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +10 -10
- package/src/workspace.ts +264 -253
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
package/src/http-effect.ts
CHANGED
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
type WireBody,
|
|
78
78
|
} from "./effector-wire.ts";
|
|
79
79
|
import { boundRun, type AnyActorSystem } from "./registration.ts";
|
|
80
|
+
import type { AnyWorktreePath } from "./worktree.ts";
|
|
80
81
|
import { open } from "./open.ts";
|
|
81
82
|
import { effectBindingShape, openEffectLogic, openPartsOf, type OpenOf } from "./open-effect.ts";
|
|
82
83
|
import { httpFileWith, sandboxEffectorPort } from "./sandbox-effect.ts";
|
|
@@ -169,13 +170,25 @@ const openSecretOf = new WeakMap<object, object>();
|
|
|
169
170
|
*
|
|
170
171
|
* @example
|
|
171
172
|
* ```ts
|
|
172
|
-
* import {
|
|
173
|
+
* import {
|
|
174
|
+
* command,
|
|
175
|
+
* fail,
|
|
176
|
+
* isReady,
|
|
177
|
+
* jr2Setup,
|
|
178
|
+
* secret,
|
|
179
|
+
* workspace,
|
|
180
|
+
* worktree,
|
|
181
|
+
* type Workspaced,
|
|
182
|
+
* } from "@jr2/orchestrator";
|
|
173
183
|
*
|
|
174
184
|
* type Input = Workspaced<{}, "app">;
|
|
175
185
|
*
|
|
186
|
+
* const deployTree = (context: Input) => `${context.workspace.repos.app}/deploy` as const;
|
|
187
|
+
*
|
|
176
188
|
* const body = jr2Setup({
|
|
177
189
|
* types: {} as { context: Input; input: Input },
|
|
178
190
|
* actors: {
|
|
191
|
+
* cut: worktree(),
|
|
179
192
|
* // The command reads the file at $KUBECONFIG. The Agent never reads the secret.
|
|
180
193
|
* deploy: command(["kubectl", "apply", "-f", "k8s/"], {
|
|
181
194
|
* env: { KUBECONFIG: secret("kube").path },
|
|
@@ -186,17 +199,26 @@ const openSecretOf = new WeakMap<object, object>();
|
|
|
186
199
|
* id: "deploy",
|
|
187
200
|
* context: ({ input }) => input,
|
|
188
201
|
* on: { "workspace.lost": ".lost" },
|
|
189
|
-
* initial: "
|
|
202
|
+
* initial: "cutting",
|
|
190
203
|
* states: {
|
|
191
|
-
*
|
|
204
|
+
* cutting: {
|
|
205
|
+
* invoke: {
|
|
206
|
+
* src: "cut",
|
|
207
|
+
* input: ({ context }) => ({ path: deployTree(context), branch: "deploy" }),
|
|
208
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "deploying" }, { target: "notCut" }],
|
|
209
|
+
* },
|
|
210
|
+
* },
|
|
211
|
+
* deploying: {
|
|
212
|
+
* invoke: { src: "deploy", input: ({ context }) => ({ worktree: deployTree(context) }), onDone: "done" },
|
|
213
|
+
* },
|
|
192
214
|
* done: { type: "final" },
|
|
215
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
193
216
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
194
217
|
* },
|
|
195
218
|
* });
|
|
196
219
|
*
|
|
197
220
|
* export const machine = workspace(body, {
|
|
198
221
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
199
|
-
* spec: () => ({ branch: "deploy" }),
|
|
200
222
|
* });
|
|
201
223
|
* ```
|
|
202
224
|
*
|
|
@@ -501,15 +523,12 @@ function asText(v: string | Secret | Text): Text {
|
|
|
501
523
|
/** A value of a file body's part: as written, or from the Effect's input. */
|
|
502
524
|
export type FromInput<S, T> = T | ((input: S extends z.ZodType ? z.infer<S> : void) => T);
|
|
503
525
|
|
|
504
|
-
/** `file(path, {
|
|
526
|
+
/** `file(path, { segments?, contentType? })`: a Worktree file as an `http` body. */
|
|
505
527
|
export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
506
528
|
readonly file: {
|
|
507
|
-
/** The file, relative to the
|
|
508
|
-
* Slot. */
|
|
529
|
+
/** The file, relative to the Worktree that `worktree` of the Effect's input names, or to `/work`
|
|
530
|
+
* in a Workspace with no Repo Slot. */
|
|
509
531
|
readonly path: FromInput<S, string>;
|
|
510
|
-
/** The Repo Slot whose Worktree holds it. Write it only when the Workspace has more than one.
|
|
511
|
-
*/
|
|
512
|
-
readonly slot?: string;
|
|
513
532
|
/** Path segments that jr2 adds to the end of the url, as `path` of `request` does for a json
|
|
514
533
|
* or text body. */
|
|
515
534
|
readonly segments?: FromInput<S, readonly string[]>;
|
|
@@ -521,14 +540,18 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
521
540
|
|
|
522
541
|
/**
|
|
523
542
|
* Make a Worktree file the body of an `http` Effect: `body: file("dist/report.pdf")`. The Effect
|
|
524
|
-
* then runs in the Sandbox Effector of the Workspace whose body invokes it.
|
|
525
|
-
*
|
|
543
|
+
* then runs in the Sandbox Effector of the Workspace whose body invokes it. Under a Workspace with
|
|
544
|
+
* Repo Slots, the invoke's input names the Worktree: `input: { worktree: "/work/app/fix" }`. It is a
|
|
545
|
+
* Worktree that a cut of this run made. That Effector opens the file in that Worktree and sends it.
|
|
546
|
+
* In a Workspace with no Repo Slot, the input names no `worktree`, and the path is relative to
|
|
526
547
|
* `/work`, the folder that its steps share. Use it with `PUT`, `POST` or `PATCH`. `path` and
|
|
527
548
|
* `segments` can be functions of the input of the Effect.
|
|
528
549
|
*
|
|
529
|
-
* Some paths and files give the output `refused`, and then nothing is sent. The
|
|
530
|
-
*
|
|
531
|
-
*
|
|
550
|
+
* Some inputs, paths and files give the output `refused`, and then nothing is sent. The `worktree`
|
|
551
|
+
* has no cut in this Sandbox: `no cut made <path> in this Sandbox: cut it with worktree(); nothing
|
|
552
|
+
* was sent`. Or the input names a `worktree` in a Workspace with no Repo Slot. Or the path has a
|
|
553
|
+
* symlink in it, has `..`, or is absolute. Or the file is a FIFO, a directory, or a file with more
|
|
554
|
+
* than one link.
|
|
532
555
|
*
|
|
533
556
|
* It refuses, when you call it:
|
|
534
557
|
* - a path that is not a string or a function: `file(path): the path is a string, or a function of
|
|
@@ -548,8 +571,13 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
548
571
|
* there is no Worktree to read; invoke it from a workspace()'s body, or send a json or text body
|
|
549
572
|
* (ADR-0068)`.
|
|
550
573
|
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
574
|
+
* And when it starts, under a Workspace with Repo Slots, an input with no `worktree`: ``http
|
|
575
|
+
* "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its input names no
|
|
576
|
+
* `worktree` ``.
|
|
577
|
+
*
|
|
578
|
+
* @param path - The file, relative to the Worktree that `worktree` of the input names, or to
|
|
579
|
+
* `/work` in a Workspace with no Repo Slot: a string, or a function of the input of the Effect
|
|
580
|
+
* that gives one.
|
|
553
581
|
* @param options - Optional. Each field says what it does.
|
|
554
582
|
* @returns The `body` of an `http` Effect.
|
|
555
583
|
*
|
|
@@ -561,17 +589,22 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
561
589
|
* fail,
|
|
562
590
|
* file,
|
|
563
591
|
* http,
|
|
592
|
+
* isReady,
|
|
564
593
|
* jr2Setup,
|
|
565
594
|
* secret,
|
|
566
595
|
* workspace,
|
|
596
|
+
* worktree,
|
|
567
597
|
* type Workspaced,
|
|
568
598
|
* } from "@jr2/orchestrator";
|
|
569
599
|
*
|
|
570
600
|
* type Input = Workspaced<{}, "app">;
|
|
571
601
|
*
|
|
602
|
+
* const reportTree = (context: Input) => `${context.workspace.repos.app}/report` as const;
|
|
603
|
+
*
|
|
572
604
|
* const body = jr2Setup({
|
|
573
605
|
* types: {} as { context: Input; input: Input },
|
|
574
606
|
* actors: {
|
|
607
|
+
* cut: worktree(),
|
|
575
608
|
* build: command(["npm", "run", "report"]),
|
|
576
609
|
* upload: http({
|
|
577
610
|
* method: "PUT",
|
|
@@ -588,18 +621,29 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
588
621
|
* id: "report",
|
|
589
622
|
* context: ({ input }) => input,
|
|
590
623
|
* on: { "workspace.lost": ".lost" },
|
|
591
|
-
* initial: "
|
|
624
|
+
* initial: "cutting",
|
|
592
625
|
* states: {
|
|
593
|
-
*
|
|
594
|
-
*
|
|
626
|
+
* cutting: {
|
|
627
|
+
* invoke: {
|
|
628
|
+
* src: "cut",
|
|
629
|
+
* input: ({ context }) => ({ path: reportTree(context), branch: "report" }),
|
|
630
|
+
* onDone: [{ guard: ({ event }) => isReady(event.output), target: "building" }, { target: "notCut" }],
|
|
631
|
+
* },
|
|
632
|
+
* },
|
|
633
|
+
* building: {
|
|
634
|
+
* invoke: { src: "build", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "uploading" },
|
|
635
|
+
* },
|
|
636
|
+
* uploading: {
|
|
637
|
+
* invoke: { src: "upload", input: ({ context }) => ({ worktree: reportTree(context) }), onDone: "done" },
|
|
638
|
+
* },
|
|
595
639
|
* done: { type: "final" },
|
|
640
|
+
* notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
|
|
596
641
|
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
597
642
|
* },
|
|
598
643
|
* });
|
|
599
644
|
*
|
|
600
645
|
* export const machine = workspace(body, {
|
|
601
646
|
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
602
|
-
* spec: () => ({ branch: "report" }),
|
|
603
647
|
* });
|
|
604
648
|
* ```
|
|
605
649
|
*
|
|
@@ -610,9 +654,6 @@ export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
|
610
654
|
export function file<S extends z.ZodType | undefined = undefined>(
|
|
611
655
|
path: FromInput<S, string>,
|
|
612
656
|
options: {
|
|
613
|
-
/** The Repo Slot whose Worktree holds the file. Write it only when the Workspace has more than
|
|
614
|
-
* one. */
|
|
615
|
-
slot?: string;
|
|
616
657
|
/**
|
|
617
658
|
* Path segments that jr2 adds to the end of the url, as `path` does in `request` for a json or
|
|
618
659
|
* text body. A list of strings, or a function of the input of the Effect that gives one.
|
|
@@ -633,7 +674,6 @@ export function file<S extends z.ZodType | undefined = undefined>(
|
|
|
633
674
|
return {
|
|
634
675
|
file: {
|
|
635
676
|
path,
|
|
636
|
-
...(options.slot !== undefined ? { slot: options.slot } : {}),
|
|
637
677
|
...(options.segments !== undefined ? { segments: options.segments } : {}),
|
|
638
678
|
...(options.contentType !== undefined ? { contentType: options.contentType } : {}),
|
|
639
679
|
},
|
|
@@ -723,10 +763,13 @@ export type HttpDefinition<S extends z.ZodType | undefined = undefined> = {
|
|
|
723
763
|
request?: (input: S extends z.ZodType ? z.infer<S> : void) => HttpRequestParts;
|
|
724
764
|
// A file body is an ADR-0067 Effect: the Sandbox Effector of the Workspace sends it.
|
|
725
765
|
/**
|
|
726
|
-
* A Worktree file as the body: `file(path, {
|
|
727
|
-
* runs in the Sandbox Effector of its Workspace.
|
|
766
|
+
* A Worktree file as the body: `file(path, { segments?, contentType? })`. The Effect then
|
|
767
|
+
* runs in the Sandbox Effector of its Workspace. Under a Workspace with Repo Slots, the invoke's
|
|
768
|
+
* input names a Worktree that a cut of this run made, in `worktree`; with none, it refuses when
|
|
769
|
+
* it starts: ``http "<slot>" reads a Worktree file, under a Workspace with Repo Slots, and its
|
|
770
|
+
* input names no `worktree` ``. Do not write `request` beside it: the `segments`
|
|
728
771
|
* of `file` are the path. It refuses another body: ``http({ body }): a body is file(path, {
|
|
729
|
-
*
|
|
772
|
+
* segments?, contentType? }) — json and text come from `request` ``.
|
|
730
773
|
*/
|
|
731
774
|
body?: FileBody<S>;
|
|
732
775
|
/**
|
|
@@ -755,6 +798,11 @@ export type HttpDefinition<S extends z.ZodType | undefined = undefined> = {
|
|
|
755
798
|
/** @internal */
|
|
756
799
|
export type HttpInput<S> = S extends z.ZodType ? z.infer<S> : void;
|
|
757
800
|
|
|
801
|
+
/** The input of an `http` Effect with a Worktree file as its body. Under a Workspace with Repo
|
|
802
|
+
* Slots, `worktree` names the Worktree. In a Workspace with no Repo Slot, the input has no
|
|
803
|
+
* `worktree`, and the path is relative to `/work`. */
|
|
804
|
+
export type HttpFileInput<S> = (S extends z.ZodType ? z.infer<S> : unknown) & { worktree?: AnyWorktreePath };
|
|
805
|
+
|
|
758
806
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
759
807
|
const DEFAULT_MAX_BODY = 65_536;
|
|
760
808
|
/** How long a re-asked request waits, at most, after a transport failure. */
|
|
@@ -830,7 +878,7 @@ export function httpParts<S extends z.ZodType | undefined>(def: HttpDefinition<S
|
|
|
830
878
|
const body = def.body?.file;
|
|
831
879
|
if (def.body !== undefined && (typeof body !== "object" || body === null)) {
|
|
832
880
|
throw new Error(
|
|
833
|
-
"http({ body }): a body is file(path, {
|
|
881
|
+
"http({ body }): a body is file(path, { segments?, contentType? }) — json and text come from `request`",
|
|
834
882
|
);
|
|
835
883
|
}
|
|
836
884
|
if (body && !FILE_METHODS.includes(def.method)) {
|
|
@@ -866,7 +914,6 @@ export function httpParts<S extends z.ZodType | undefined>(def: HttpDefinition<S
|
|
|
866
914
|
...(body
|
|
867
915
|
? {
|
|
868
916
|
file: {
|
|
869
|
-
...(body.slot !== undefined ? { slot: body.slot } : {}),
|
|
870
917
|
...(body.contentType !== undefined ? { contentType: body.contentType } : {}),
|
|
871
918
|
},
|
|
872
919
|
}
|
|
@@ -1347,8 +1394,9 @@ function parseDuration(d: Duration): number {
|
|
|
1347
1394
|
* src: "comment" }`.
|
|
1348
1395
|
*
|
|
1349
1396
|
* It runs in the Instance Effector, which `jr2 up` deploys when a Machine has an `http` Effect.
|
|
1350
|
-
* With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it
|
|
1351
|
-
* The Effector makes the call. The
|
|
1397
|
+
* With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it,
|
|
1398
|
+
* and under Repo Slots its input names the Worktree in `worktree`. The Effector makes the call. The
|
|
1399
|
+
* Orchestrator holds no secret.
|
|
1352
1400
|
*
|
|
1353
1401
|
* Every outcome is output (`HttpOutput`): `response`, `unreachable`, `refused` or `unknown`. A 4xx
|
|
1354
1402
|
* or 5xx status is output too. jr2 sends the call at most one time, unless you mark it
|
|
@@ -1402,6 +1450,14 @@ function parseDuration(d: Duration): number {
|
|
|
1402
1450
|
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
1403
1451
|
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
1404
1452
|
*/
|
|
1453
|
+
export function http<
|
|
1454
|
+
S extends z.ZodType | undefined = undefined,
|
|
1455
|
+
const U extends HttpDefinition<S>["url"] = string,
|
|
1456
|
+
const A extends HttpDefinition<S>["auth"] = undefined,
|
|
1457
|
+
const H extends HttpDefinition<S>["headers"] = undefined,
|
|
1458
|
+
>(
|
|
1459
|
+
def: HttpDefinition<S> & { url: U; auth?: A; headers?: H; body: FileBody<S> },
|
|
1460
|
+
): EffectLogic<HttpFileInput<S>, HttpOutput> & OpenOf<HttpOpenParts<U, A, H>>;
|
|
1405
1461
|
export function http<
|
|
1406
1462
|
S extends z.ZodType | undefined = undefined,
|
|
1407
1463
|
const U extends HttpDefinition<S>["url"] = string,
|
package/src/http.ts
CHANGED
|
@@ -61,7 +61,14 @@ import {
|
|
|
61
61
|
} from "./registration.ts";
|
|
62
62
|
import type { RepoStatus } from "./repos.ts";
|
|
63
63
|
import { UnmountedRepoError, type FetchAnswer } from "./repo-fetch.ts";
|
|
64
|
-
import {
|
|
64
|
+
import {
|
|
65
|
+
isAgentPrincipal,
|
|
66
|
+
mayAskForSandbox,
|
|
67
|
+
mayDeliverToAgent,
|
|
68
|
+
SLOT_HEADER,
|
|
69
|
+
type Authenticator,
|
|
70
|
+
type Principal,
|
|
71
|
+
} from "./tokens.ts";
|
|
65
72
|
import {
|
|
66
73
|
DispatchKeyConflictError,
|
|
67
74
|
keyAnswer,
|
|
@@ -387,8 +394,9 @@ export const HTTP_ROUTES: readonly HttpRoute[] = [
|
|
|
387
394
|
method: "GET",
|
|
388
395
|
path: "/agents/:instanceId/surface",
|
|
389
396
|
band: "authenticated",
|
|
390
|
-
summary:
|
|
391
|
-
|
|
397
|
+
summary:
|
|
398
|
+
"An Agent's Menu, this Turn's Allowed picks and the conversation's pending notices. A control token reads only its own pod's, for the slot it states.",
|
|
399
|
+
response: "AgentSurfaceView & { notices: Array<{ id, notice }> }",
|
|
392
400
|
stream: false,
|
|
393
401
|
adrs: ["0013", "0029"],
|
|
394
402
|
},
|
|
@@ -411,11 +419,21 @@ export const HTTP_ROUTES: readonly HttpRoute[] = [
|
|
|
411
419
|
stream: false,
|
|
412
420
|
adrs: ["0013", "0024"],
|
|
413
421
|
},
|
|
422
|
+
{
|
|
423
|
+
method: "POST",
|
|
424
|
+
path: "/agents/:instanceId/notices",
|
|
425
|
+
band: "authenticated",
|
|
426
|
+
summary: "Acknowledge the notices a step boundary appended to the Turn: they are heard in its conversation.",
|
|
427
|
+
body: "{ heard: string[] }",
|
|
428
|
+
response: "{ heard: number }",
|
|
429
|
+
stream: false,
|
|
430
|
+
adrs: ["0062", "0076"],
|
|
431
|
+
},
|
|
414
432
|
{
|
|
415
433
|
method: "POST",
|
|
416
434
|
path: "/sandboxes/:name/fetch",
|
|
417
435
|
band: "authenticated",
|
|
418
|
-
summary: "Ask the node cache to fetch one Repo that this Sandbox mounts. A
|
|
436
|
+
summary: "Ask the node cache to fetch one Repo that this Sandbox mounts. A fetch token asks only for its own.",
|
|
419
437
|
body: "{ identity }",
|
|
420
438
|
response: "FetchAnswer: { fetched } | { stale, asOf }",
|
|
421
439
|
stream: false,
|
|
@@ -828,7 +846,8 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
828
846
|
* hand every Agent in the cluster a delivery API, which is the hole this ADR exists to close. */
|
|
829
847
|
const authenticated: MiddlewareHandler<JR2Env> = async (c, next) => {
|
|
830
848
|
if (!auth) return next(); // no authenticator configured: tests only (see the doc comment)
|
|
831
|
-
|
|
849
|
+
// The slot a control request states (ADR-0076): a control token without one resolves to nothing.
|
|
850
|
+
const principal = auth(bearerOf(c), c.req.header(SLOT_HEADER));
|
|
832
851
|
if (!principal) return c.json({ error: "unauthorized" }, 401);
|
|
833
852
|
if (principal.kind === "publisher") return c.json({ error: PUBLISHER_ONLY }, 403);
|
|
834
853
|
c.set("principal", principal);
|
|
@@ -847,7 +866,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
847
866
|
*/
|
|
848
867
|
const instanceOnly: MiddlewareHandler<JR2Env> = async (c, next) => {
|
|
849
868
|
if (!auth) return next(); // no authenticator configured: tests only (see the doc comment)
|
|
850
|
-
const principal = auth(bearerOf(c));
|
|
869
|
+
const principal = auth(bearerOf(c), c.req.header(SLOT_HEADER));
|
|
851
870
|
if (!principal) return c.json({ error: "unauthorized" }, 401);
|
|
852
871
|
if (principal.kind === "publisher") return c.json({ error: PUBLISHER_ONLY }, 403);
|
|
853
872
|
if (principal.kind !== "instance") {
|
|
@@ -1288,11 +1307,11 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
1288
1307
|
// the gated state. Unknown gate (never opened, state exited, run settled) → 404; a name the gate
|
|
1289
1308
|
// doesn't accept, or a payload failing its schema → 400 naming what IS accepted.
|
|
1290
1309
|
//
|
|
1291
|
-
// A Gate is a HUMAN's decision (or a webhook's, or CI's).
|
|
1292
|
-
// refused here unconditionally
|
|
1293
|
-
// and "the Agent approves its own PR" (ADR-0013).
|
|
1310
|
+
// A Gate is a HUMAN's decision (or a webhook's, or CI's). Every Agent principal — a control
|
|
1311
|
+
// token or a fetch token — is refused here unconditionally: this is the exact line between "the
|
|
1312
|
+
// Agent reports an outcome" and "the Agent approves its own PR" (ADR-0013, ADR-0076).
|
|
1294
1313
|
app.post("/runs/:runId/gates/:gate/events", authenticated, async (c) => {
|
|
1295
|
-
if (principalOf(c)
|
|
1314
|
+
if (isAgentPrincipal(principalOf(c))) {
|
|
1296
1315
|
return c.json({ error: "a Sandbox token cannot deliver to a gate — gates are not on the Agent's surface" }, 403);
|
|
1297
1316
|
}
|
|
1298
1317
|
const body = await readJson(c.req.text());
|
|
@@ -1419,7 +1438,9 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
1419
1438
|
// and delivery are implemented once, in `registration.ts`.
|
|
1420
1439
|
|
|
1421
1440
|
/** Guard: the surface must exist, and this principal must be allowed to speak for it. */
|
|
1422
|
-
const agentRegistration = (
|
|
1441
|
+
const agentRegistration = (
|
|
1442
|
+
c: Context<JR2Env, "/agents/:instanceId/surface" | "/agents/:instanceId/events" | "/agents/:instanceId/notices">,
|
|
1443
|
+
) => {
|
|
1423
1444
|
const instanceId = c.req.param("instanceId");
|
|
1424
1445
|
// Only a Turn that is over answers 404 (ADR-0026). While the host is stopping its runs are
|
|
1425
1446
|
// parked for restore, not over, so every `/agents/*` call answers 503: the Harness re-asks a
|
|
@@ -1442,9 +1463,10 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
1442
1463
|
// iid) → there is no surface to serve. 404 BEFORE the scope check — a caller with a valid
|
|
1443
1464
|
// token learns nothing from it that it did not already know.
|
|
1444
1465
|
if (!surface) return { error: c.json({ error: `no live agent surface for instance "${instanceId}"` }, 404) };
|
|
1445
|
-
if (!mayDeliverToAgent(principalOf(c), surface
|
|
1446
|
-
// A
|
|
1447
|
-
//
|
|
1466
|
+
if (!mayDeliverToAgent(principalOf(c), surface)) {
|
|
1467
|
+
// A control token for a DIFFERENT pod (or for a workspace-less run, which no pod owns), a
|
|
1468
|
+
// stated slot that is not this registration's (ADR-0076), or a fetch token. The first is the
|
|
1469
|
+
// check that keeps one feature's coder out of another's reviewer.
|
|
1448
1470
|
return { error: c.json({ error: `this token cannot speak for instance "${instanceId}"` }, 403) };
|
|
1449
1471
|
}
|
|
1450
1472
|
return { surface };
|
|
@@ -1454,11 +1476,28 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
1454
1476
|
// semantics, fixed for the conversation — and this Turn's Allowed picks (ADR-0029). A transition
|
|
1455
1477
|
// swaps the registration, which swaps the Allowed picks, never the Menu; the Harness reads both
|
|
1456
1478
|
// per Submission and needs no `list_changed`.
|
|
1457
|
-
|
|
1479
|
+
// The same read at a step boundary carries the conversation's pending notices in its audience
|
|
1480
|
+
// (ADR-0062), reserved for this read: the Harness appends them and acknowledges them below.
|
|
1481
|
+
app.get("/agents/:instanceId/surface", authenticated, async (c) => {
|
|
1458
1482
|
const { surface, error } = agentRegistration(c);
|
|
1459
1483
|
if (error) return error;
|
|
1460
1484
|
host.agentSurfaceServed(c.req.param("instanceId"));
|
|
1461
|
-
|
|
1485
|
+
const notices = await host.boundaryNotices(c.req.param("instanceId"));
|
|
1486
|
+
return c.json({ ...surface, notices });
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
// The Harness appended a step boundary's notices to the Turn (ADR-0062, ADR-0076): they are heard
|
|
1490
|
+
// in that conversation, ledgered in one save. An id this conversation did not reserve is ignored,
|
|
1491
|
+
// so the call says nothing a caller did not already know.
|
|
1492
|
+
app.post("/agents/:instanceId/notices", authenticated, async (c) => {
|
|
1493
|
+
const { error } = agentRegistration(c);
|
|
1494
|
+
if (error) return error;
|
|
1495
|
+
const body = await readJson(c.req.text());
|
|
1496
|
+
const heard = (body as { heard?: unknown }).heard;
|
|
1497
|
+
if (!Array.isArray(heard) || heard.some((id) => typeof id !== "string")) {
|
|
1498
|
+
return c.json({ error: "an acknowledgment names the notices heard: { heard: string[] }" }, 400);
|
|
1499
|
+
}
|
|
1500
|
+
return c.json({ heard: host.noticesHeard(c.req.param("instanceId"), heard as string[]) });
|
|
1462
1501
|
});
|
|
1463
1502
|
|
|
1464
1503
|
// The Agent's pick, delivered into the state that invoked it. The receipt describes itself
|
|
@@ -1514,7 +1553,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
|
|
|
1514
1553
|
return c.json({ error: restorePendingMessage(`delivery "${deliveryId}"`) }, 503);
|
|
1515
1554
|
}
|
|
1516
1555
|
if (!scope) return c.json({ error: `no delivery "${deliveryId}" for instance "${instanceId}"` }, 404);
|
|
1517
|
-
if (!mayDeliverToAgent(principalOf(c), scope
|
|
1556
|
+
if (!mayDeliverToAgent(principalOf(c), scope)) {
|
|
1518
1557
|
return c.json({ error: `this token cannot speak for instance "${instanceId}"` }, 403);
|
|
1519
1558
|
}
|
|
1520
1559
|
const receipt = await host.awaitDelivery(instanceId, deliveryId, opts.deliveryWaitMs, c.req.raw.signal);
|
package/src/index.ts
CHANGED
|
@@ -33,12 +33,25 @@ export type { Binding, RepoSlot } from "./parts.ts";
|
|
|
33
33
|
// Effects: work a Machine does outside itself, as an actor slot (ADR-0067, ADR-0068, ADR-0071),
|
|
34
34
|
// and what an author writes between a pick and that work.
|
|
35
35
|
export { arg } from "./effect.ts";
|
|
36
|
-
export type { CommandOutput } from "./effect.ts";
|
|
36
|
+
export type { CommandOutput, LaterOutcome } from "./effect.ts";
|
|
37
37
|
export { command } from "./harness-client.ts";
|
|
38
38
|
export { basic, bearer, file, http, secret, text } from "./http-effect.ts";
|
|
39
39
|
export type { HttpOutput } from "./effector-wire.ts";
|
|
40
40
|
export { push } from "./sandbox-effect.ts";
|
|
41
41
|
export type { PushOutput } from "./sandbox-effect.ts";
|
|
42
|
+
export { branchName, isReady, readyOf, worktree } from "./worktree.ts";
|
|
43
|
+
export type {
|
|
44
|
+
AnyFramePath,
|
|
45
|
+
AnyWorktreePath,
|
|
46
|
+
FramePath,
|
|
47
|
+
WorktreeFound,
|
|
48
|
+
WorktreeInput,
|
|
49
|
+
WorktreeOptions,
|
|
50
|
+
WorktreeOutput,
|
|
51
|
+
WorktreePath,
|
|
52
|
+
WorktreeReady,
|
|
53
|
+
WorktreeRefused,
|
|
54
|
+
} from "./worktree.ts";
|
|
42
55
|
export { dispatch } from "./dispatch-effect.ts";
|
|
43
56
|
export type { DispatchOutput } from "./effect.ts";
|
|
44
57
|
export { answer, fromPick, paramsOf } from "./pick.ts";
|
|
@@ -51,6 +64,7 @@ export type { Customize } from "./customize.ts";
|
|
|
51
64
|
export type { EffectBinding } from "./open-effect.ts";
|
|
52
65
|
export type { Size, UserContainer } from "./size.ts";
|
|
53
66
|
export { pool, source } from "./pool.ts";
|
|
67
|
+
export type { PoolNextInput } from "./pool.ts";
|
|
54
68
|
|
|
55
69
|
// Charts: the Open body a Chart wrapper places in `actors`, and the grammar a planner's pick
|
|
56
70
|
// declares, whole or narrowed to one wrapper (ADR-0070).
|
package/src/internal.ts
CHANGED
package/src/kube-client.ts
CHANGED
|
@@ -108,7 +108,7 @@ export interface KubeClient {
|
|
|
108
108
|
/** Create; an existing object is a {@link KubeError} with status 409. */
|
|
109
109
|
create<T extends KubeObject>(kind: KubeKind, namespace: string, body: T): Promise<T & KubeObject>;
|
|
110
110
|
/** Delete; true when there was one to delete, false when it was already gone. */
|
|
111
|
-
delete(kind: KubeKind, namespace: string, name: string): Promise<boolean>;
|
|
111
|
+
delete(kind: KubeKind, namespace: string, name: string, opts?: { foreground?: boolean }): Promise<boolean>;
|
|
112
112
|
/** A JSON merge patch — `null` removes a key. `status` patches the status subresource. Answers
|
|
113
113
|
* the object as it stands after the patch. */
|
|
114
114
|
patch<T extends KubeObject>(
|
|
@@ -216,9 +216,12 @@ export function kubeClient(opts: KubeClientOptions = {}): KubeClient {
|
|
|
216
216
|
});
|
|
217
217
|
},
|
|
218
218
|
|
|
219
|
-
delete(kind: KubeKind, ns: string, name: string) {
|
|
219
|
+
delete(kind: KubeKind, ns: string, name: string, opts: { foreground?: boolean } = {}) {
|
|
220
220
|
return writes(async () => {
|
|
221
|
-
|
|
221
|
+
// Foreground: the object stays until its dependents, a Sandbox's pod, are gone.
|
|
222
|
+
const res = await send("DELETE", pathOf(kind, ns, name), {
|
|
223
|
+
...(opts.foreground ? { query: { propagationPolicy: "Foreground" } } : {}),
|
|
224
|
+
});
|
|
222
225
|
if (res.status === 404) {
|
|
223
226
|
await res.body?.cancel();
|
|
224
227
|
return false;
|
package/src/machine-doc.ts
CHANGED
|
@@ -42,9 +42,9 @@ export type MachineTransitionDoc = {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
/** An Effect slot's parts, as the doc carries them (ADR-0067): a command's argv (an argv slot reads
|
|
45
|
-
* `<name>`)
|
|
45
|
+
* `<name>`) and its timeout. Its input names its Worktree, so the doc does not (ADR-0075). */
|
|
46
46
|
export type EffectDoc =
|
|
47
|
-
| { kind: "command"; argv: string[]; timeoutMs: number;
|
|
47
|
+
| { kind: "command"; argv: string[]; timeoutMs: number; secrets?: string[]; pin?: true }
|
|
48
48
|
/** An http Effect (ADR-0068): its method, its fixed target, the secrets it names — never a value
|
|
49
49
|
* — and `file` when its body is a Worktree file (ADR-0067). */
|
|
50
50
|
| {
|
|
@@ -422,12 +422,11 @@ function effectDoc(parts: EffectParts): EffectDoc {
|
|
|
422
422
|
timeoutMs,
|
|
423
423
|
};
|
|
424
424
|
}
|
|
425
|
-
const { argv, timeoutMs,
|
|
425
|
+
const { argv, timeoutMs, secrets, pin } = parts;
|
|
426
426
|
return {
|
|
427
427
|
kind: "command",
|
|
428
428
|
argv: argvText(argv),
|
|
429
429
|
timeoutMs,
|
|
430
|
-
...(slot ? { slot } : {}),
|
|
431
430
|
...(secrets?.length ? { secrets: [...secrets] } : {}),
|
|
432
431
|
...(pin ? { pin } : {}),
|
|
433
432
|
};
|
package/src/model-keys.ts
CHANGED
|
@@ -50,3 +50,27 @@ export const SIGNING_CREDENTIALS: readonly string[] = [
|
|
|
50
50
|
"AWS_PROFILE",
|
|
51
51
|
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
52
52
|
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Names whose value is the path of a credential or trust file that the Harness PROCESS reads (the
|
|
56
|
+
* AWS SDK, Google's library), when `harness.env` or `harness.envFrom` sets one (ADR-0076). The
|
|
57
|
+
* Harness runs with the control Stand-in in its memory, so such a file must be one that the Agent's
|
|
58
|
+
* code cannot write: `~/.aws/config` can name a `credential_process`, which the SDK runs. `jr2 up`
|
|
59
|
+
* refuses a path that the domain writes for certain (`DOMAIN_WRITABLE_ROOTS`), and warns on any
|
|
60
|
+
* other, because whether the Sandbox's uid can write a path of the image is the image's, which jr2
|
|
61
|
+
* cannot read. An image's own `ENV` never reaches the Harness process.
|
|
62
|
+
*/
|
|
63
|
+
export const CREDENTIAL_FILES: readonly string[] = [
|
|
64
|
+
"AWS_CONFIG_FILE",
|
|
65
|
+
"AWS_SHARED_CREDENTIALS_FILE",
|
|
66
|
+
"AWS_WEB_IDENTITY_TOKEN_FILE",
|
|
67
|
+
"AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
|
|
68
|
+
"AWS_CA_BUNDLE",
|
|
69
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
70
|
+
"GOOGLE_API_CERTIFICATE_CONFIG",
|
|
71
|
+
"CLOUDSDK_CONFIG",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
/** Folders of a Sandbox's Harness container that the domain writes in every pod: the Worktree root,
|
|
75
|
+
* the temporary folders, `/dev/shm`, and the HOME that jr2 gives an image with no `USER`. */
|
|
76
|
+
export const DOMAIN_WRITABLE_ROOTS: readonly string[] = ["/work", "/tmp", "/var/tmp", "/dev/shm", "/home/jr2"];
|
package/src/names.ts
CHANGED
|
@@ -25,8 +25,10 @@ export const EFFECTOR_PORT = 8082;
|
|
|
25
25
|
|
|
26
26
|
/** Where a Harness persists its conversations, and the env var that tells it so (ADR-0031): one
|
|
27
27
|
* fixed path in both placements — the Instance Harness's PersistentVolumeClaim (deploy.ts) and a
|
|
28
|
-
* Sandbox's emptyDir (sandbox-kube.ts), each mounted into the Harness container alone.
|
|
29
|
-
|
|
28
|
+
* Sandbox's emptyDir (sandbox-kube.ts), each mounted into the Harness container alone. It is under
|
|
29
|
+
* `/run/jr2`, as each path that the Harness holds alone is, so no process that the Harness confines
|
|
30
|
+
* can read an Agent's history or write into it (ADR-0076). */
|
|
31
|
+
export const CONVERSATIONS_DIR = "/run/jr2/conversations";
|
|
30
32
|
export const CONVERSATIONS_DIR_ENV = "JR2_CONVERSATIONS_DIR";
|
|
31
33
|
export const CONVERSATIONS_VOLUME = "conversations";
|
|
32
34
|
|
|
@@ -98,12 +100,23 @@ export const sandboxCustodianConfigMap = (sandbox: string): string => `${sandbox
|
|
|
98
100
|
* (`workspaceHeldPod`), so the two cannot collide. */
|
|
99
101
|
export const INSTANCE_HARNESS_HELD_POD = "instance";
|
|
100
102
|
|
|
101
|
-
/** The Custodian's
|
|
102
|
-
* HTTPS proxy on loopback, the kubelet's probe, and the
|
|
103
|
-
*
|
|
103
|
+
/** The Custodian's ports (ADR-0059), all taken on every Harness pod (ADR-0005): the Harness's
|
|
104
|
+
* HTTPS proxy on loopback, the kubelet's probe, and the fetch listener that the ask rides to the
|
|
105
|
+
* Orchestrator, at the address `jr2-upload-pack` defaults to (ADR-0053). The agent routes are not
|
|
106
|
+
* on a port: they are on the control socket ({@link CUSTODIAN_CONTROL_SOCKET}, ADR-0076). */
|
|
104
107
|
export const CUSTODIAN_PORT = 15001;
|
|
105
108
|
export const CUSTODIAN_HEALTH_PORT = 15021;
|
|
106
|
-
export const
|
|
109
|
+
export const CUSTODIAN_FETCH_PORT = 8081;
|
|
110
|
+
|
|
111
|
+
/** The folder of the Custodian's control socket (ADR-0076): an emptyDir that the Harness container
|
|
112
|
+
* and the Custodian container alone mount, at this path in both. It is under `/run/jr2`, so no
|
|
113
|
+
* process that the Harness confines can make a socket here, and none can take the control
|
|
114
|
+
* listener's place while the Custodian restarts. The Custodian writes it; the Harness mounts it
|
|
115
|
+
* read-only, which still lets it connect to the socket. */
|
|
116
|
+
export const CUSTODIAN_SOCKET_DIR = "/run/jr2/custodian";
|
|
117
|
+
/** The control socket: the Custodian's listener for the four agent routes, which carry the control
|
|
118
|
+
* Stand-in (ADR-0059, ADR-0076). */
|
|
119
|
+
export const CUSTODIAN_CONTROL_SOCKET = `${CUSTODIAN_SOCKET_DIR}/control.sock`;
|
|
107
120
|
|
|
108
121
|
/** The scaffold's default deploy-key Secret name (ADR-0047/0051): what `jr2 init`'s wildcard
|
|
109
122
|
* `git.credentials` entry names as its `sshKey`, and what `jr2 up`'s ssh offer generates into when
|