@jr2/orchestrator 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/http.ts CHANGED
@@ -2,15 +2,16 @@
2
2
  // carries NO domain logic of its own: every handler delegates to a single `RunHost` method, so the
3
3
  // wire shape and the in-process API stay one behavior.
4
4
  //
5
- // TWO dialects, one primitive (ADR-0013). The Orchestrator does not speak MCP — that moved into the
6
- // Sandbox, where the Adapter serves it to the Agent over localhost. What is left here are two thin
7
- // adapters over the same registration table:
5
+ // TWO dialects, one primitive (ADR-0013). The Orchestrator does not speak MCP — nothing does: the
6
+ // Harness presents the Menu to its model itself, and reaches these routes through the Custodian in
7
+ // its pod, which holds the Sandbox token. What is left here are two thin adapters over the same
8
+ // registration table:
8
9
  //
9
10
  // # human / webhook / CI — the Gate resource of ADR-0011
10
11
  // GET /runs/:id open gates: accepts + schemas + meta [Instance token]
11
12
  // POST /runs/:id/gates/:gate/events validate + deliver [Instance token]
12
13
  //
13
- // # the Agent's Adapter, and nothing else
14
+ // # the Agent's Harness, through its Custodian, and nothing else
14
15
  // GET /agents/:iid/surface accepts + schemas + semantics [Sandbox token]
15
16
  // POST /agents/:iid/events validate + deliver → the turn receipt [Sandbox token]
16
17
  // POST /sandboxes/:name/fetch ask the node cache to fetch one Repo [Sandbox token]
@@ -180,7 +181,7 @@ async function consoleTsAsset(file: string): Promise<Response> {
180
181
  // Content negotiation for the two page addresses (ADR-0032). JSON is the DEFAULT dialect: only a
181
182
  // request whose `Accept` prefers `text/html` — a browser's navigation — gets the Console shell.
182
183
  // No header, a bare wildcard, `application/json`, `text/event-stream` all fall through to JSON,
183
- // so the CLI, the Adapter and EventSource never see HTML they did not ask for.
184
+ // so the CLI, the Harness and EventSource never see HTML they did not ask for.
184
185
  function prefersHtml(c: Context): boolean {
185
186
  return (
186
187
  accepts(c, { header: "Accept", supports: ["application/json", "text/html"], default: "application/json" }) ===
@@ -301,8 +302,8 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
301
302
 
302
303
  /**
303
304
  * Authenticate, AND require the INSTANCE token (ADR-0014). A Sandbox token is a token we minted,
304
- * so `authenticated` alone lets it through — which on the run surface is too much: it would let an
305
- * Adapter's credential read every run's context (other features' branches, tickets, verdicts) and
305
+ * so `authenticated` alone lets it through — which on the run surface is too much: it would let a
306
+ * Custodian's credential read every run's context (other features' branches, tickets, verdicts) and
306
307
  * CANCEL any run. Neither is on the Agent's surface. Its scope is delivering to agent
307
308
  * registrations recorded against its OWN Sandbox (ADR-0013), and the gate route already says so in
308
309
  * the other direction.
@@ -358,8 +359,8 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
358
359
  // A body failing the machine's declared input schema (ADR-0033) → 400 naming the accepted
359
360
  // shape — the same error class, and the same wire mapping, as a gate delivery failing its
360
361
  // schema (`POST /runs/:id/gates/:gate/events` below). Instance-band (ADR-0058): starting work is
361
- // run control, and a Sandbox token's scope is its own agent surface — no Adapter forwarding this
362
- // route is what keeps an Agent from starting runs today, and that is not a control.
362
+ // run control, and a Sandbox token's scope is its own agent surface — the Custodian's route
363
+ // allowlist not forwarding this route is a second layer, not the control (ADR-0059).
363
364
  app.post("/workflows/:name/runs", instanceOnly, async (c) => {
364
365
  const name = c.req.param("name");
365
366
  const input = await readJson(c.req.text());
@@ -671,10 +672,12 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
671
672
  });
672
673
 
673
674
  // ---- The Agent's surface (`/agents/:iid/*` — ADR-0013) --------------------------------------
674
- // Served to ONE caller: the Adapter in the Agent's Sandbox. It renders `surface` as `tools/list`
675
- // and turns a `tools/call` into an `events` POST. The Orchestrator therefore keeps no MCP
676
- // dependency, no transport, no session handling — and the Agent keeps no route to this API
677
- // except through a process whose credential it cannot read.
675
+ // Served to ONE caller: the Agent's Harness, through the Custodian in its pod. The Harness renders
676
+ // `surface` as the Menu and turns a pick into an `events` POST. The Orchestrator therefore keeps
677
+ // no MCP dependency, no transport, no session handling — and the Agent keeps no route to this
678
+ // API except through a container whose credential it cannot read. Everything a pick is judged by
679
+ // is judged HERE: the token's scope, the live registration, the event's name and payload — the
680
+ // Custodian forwards and never decides (ADR-0059).
678
681
  //
679
682
  // Both routes are adapters over the SAME registration table the gates ride: lookup, validation
680
683
  // and delivery are implemented once, in `registration.ts`.
@@ -696,7 +699,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
696
699
  };
697
700
 
698
701
  // This turn's menu: the events the invoking state accepts, their input schemas, their semantics.
699
- // A transition swaps the registration, which swaps this — so the Adapter gets a state-scoped
702
+ // A transition swaps the registration, which swaps this — so the Harness gets a state-scoped
700
703
  // toolset for free, and needs no `list_changed` to know it (flue re-lists on every submission).
701
704
  app.get("/agents/:instanceId/surface", authenticated, (c) => {
702
705
  const { surface, error } = agentRegistration(c);
@@ -704,7 +707,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
704
707
  });
705
708
 
706
709
  // The Agent's pick, delivered into the state that invoked it. The receipt describes itself
707
- // (ADR-0024): what was delivered, and whether that ended the turn — which the Adapter renders as
710
+ // (ADR-0024): what was delivered, and whether that ended the turn — which the Harness renders as
708
711
  // prose. Its `deliveryId` still makes an outcome addressable after the fact, the room a deferred
709
712
  // result will need when it lands.
710
713
  app.post("/agents/:instanceId/events", authenticated, async (c) => {
@@ -721,7 +724,7 @@ export function createApp(host: RunHost, auth?: Authenticator, opts: CreateAppOp
721
724
  });
722
725
 
723
726
  // The pod's one route out (ADR-0053). A `git fetch` inside a Sandbox runs a program on the
724
- // runtime volume, which asks the Adapter on localhost, which forwards to this. What lands here
727
+ // runtime volume, which asks the Custodian on localhost, which forwards to this. What lands here
725
728
  // is one identity; what goes back is the landing, or the cache as it stands with git's own words
726
729
  // — freshness degrades, absence does not, so this answers 200 either way and the program prints
727
730
  // the warning. The caller waits on the ask the way an attach waits on Ready: same status, same
package/src/images.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // not folded into sandbox-kubectl.ts, because the CLI needs the type without dragging in the
5
5
  // kubectl port.
6
6
  //
7
- // The map is NESTED, never flat. The kit's own `harness`/`adapter` refs sit beside a `sandbox`
7
+ // The map is NESTED, never flat. The kit's own `harness` and the `custodian` sit beside a `sandbox`
8
8
  // sub-map, so a user image can never shadow them — and `default` is the one reserved key in that
9
9
  // sub-map, because ADR-0037's fallback chain is built on it.
10
10
  //
@@ -26,8 +26,9 @@ import { join, relative } from "node:path";
26
26
  import { fileURLToPath } from "node:url";
27
27
 
28
28
  /**
29
- * Every image ref one converge resolved. `harness`/`adapter` are the kit's own (built from a kit
30
- * checkout, or the published `<kitversion>` tags); `sandbox` is the instance's own docker-context
29
+ * Every image ref one converge resolved. `harness` is the kit's own (built from a kit checkout, or
30
+ * the published `<kitversion>` tag); `custodian` is the pinned Envoy the kit deploys and never
31
+ * builds (ADR-0038, ADR-0059); `sandbox` is the instance's own docker-context
31
32
  * builds — one `docker build` straight to a content tag, carrying no jr2 layers at all, because the
32
33
  * Harness arrives at POD time on the `/opt/jr2` volume (ADR-0037).
33
34
  *
@@ -38,9 +39,10 @@ import { fileURLToPath } from "node:url";
38
39
  export type ImageRefs = {
39
40
  /** The stock Harness image — also the last leg of the Sandbox Image fallback chain. */
40
41
  harness: string;
41
- /** The Adapter image (ADR-0013). An Agent with no Adapter cannot drive its Machine at all, so
42
+ /** The Custodian's image (ADR-0059) — upstream Envoy, pinned by digest, re-homed onto a
43
+ * `kitRegistry` when one is set. Every Harness pod runs one (it carries the Menu, ADR-0013), so
42
44
  * this is required: a map without it fails the provision rather than shipping a mute pod. */
43
- adapter: string;
45
+ custodian: string;
44
46
  /** Built Sandbox Images by their build context's CONTENT DIGEST (ADR-0049), plus the reserved
45
47
  * key `default` — the Instance's `images/default`, ADR-0037's middle leg. Empty when the
46
48
  * instance ships no context and scaffolded no default. */
@@ -99,10 +101,9 @@ export async function readImageRefs(path: string): Promise<ImageRefs> {
99
101
  return value as string;
100
102
  };
101
103
  const harness = ref("harness");
102
- // Required, not optional: with the ref living in the map there is no "no adapter configured"
103
- // branch left to gate on, and an Agent whose pod has no Adapter parks forever on a tool call it
104
- // cannot make (ADR-0013). Failing the provision is the only honest outcome.
105
- const adapter = ref("adapter");
104
+ // Required, not optional: an Agent whose pod has no Custodian parks forever on a Menu it cannot
105
+ // read (ADR-0013). Failing the provision is the only honest outcome.
106
+ const custodian = ref("custodian");
106
107
  const sandboxRaw = map["sandbox"];
107
108
  if (sandboxRaw !== undefined && (typeof sandboxRaw !== "object" || sandboxRaw === null || Array.isArray(sandboxRaw)))
108
109
  bad("`sandbox` must be an object of name → ref");
@@ -121,7 +122,7 @@ export async function readImageRefs(path: string): Promise<ImageRefs> {
121
122
  if (typeof value !== "string") bad(`\`sandboxUser.${name}\` is not a USER (got ${JSON.stringify(value)})`);
122
123
  sandboxUser[name] = value as string;
123
124
  }
124
- return { harness, adapter, sandbox, sandboxUser };
125
+ return { harness, custodian, sandbox, sandboxUser };
125
126
  }
126
127
 
127
128
  /**
package/src/index.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  // delivery surfaces, the duplex Actor over the Harness wire, and durable snapshot persistence
3
3
  // (ADR-0002/0006/0007). Builds on the `@jr2/agent-protocol` wire contract.
4
4
  //
5
- // It does NOT speak MCP (ADR-0013): the Agent's MCP surface is hosted by the Adapter, in the
6
- // Sandbox. What lives here is the registration table and two thin HTTP adapters over it.
5
+ // It does NOT speak MCP (ADR-0013): the Harness presents the Menu to its model itself, and reaches
6
+ // the surface here through the Custodian in its pod. What lives here is the registration table and
7
+ // two thin HTTP adapters over it — plus what composes a Custodian (ADR-0059).
7
8
 
8
9
  // The wire contract, re-exported: a workflow authors against ONE package (`defineEvent`,
9
10
  // `jr2Setup`, `workspace`, … all import from "@jr2/orchestrator" — ADR-0015).
@@ -24,6 +25,9 @@ export * from "./actor.ts";
24
25
  export * from "./gate.ts";
25
26
  export * from "./workspace.ts";
26
27
  export * from "./images.ts";
28
+ export * from "./model-keys.ts";
29
+ export * from "./held-secrets.ts";
30
+ export * from "./custodian.ts";
27
31
  export * from "./sandbox-kubectl.ts";
28
32
  export * from "./repos.ts";
29
33
  export * from "./repo-fetch.ts";
package/src/instance.ts CHANGED
@@ -116,7 +116,7 @@ export async function startInstance(opts: InstanceOptions): Promise<RunningInsta
116
116
  // Resolved BEFORE the host, and the signing key with it: the host derives every Harness bearer
117
117
  // from the key (ADR-0058) — admission, stream, abort, echo. The key is loaded from (or minted
118
118
  // into) the instance folder, NOT generated per process: live Sandboxes outlive a restart, their
119
- // Adapters still bear tokens this key signed, and their Harnesses still check bearers it derived.
119
+ // Custodians still hold tokens this key signed, and their Harnesses still check bearers it derived.
120
120
  // The Instance token is per-boot; the key is not. Both are served under in step 4 below.
121
121
  const instanceToken = opts.instanceToken ?? mintInstanceToken();
122
122
  const signingKey = opts.signingKey ?? (await loadSigningKey(opts.dir));
@@ -0,0 +1,92 @@
1
+ // The known model keys (ADR-0059): every env var pi's catalog reads a provider's credential from,
2
+ // the catalog provider(s) that read it, and the one host those providers' calls go to by default.
3
+ //
4
+ // A COPY of pi's data at the exact pin `@jr2/harness` holds, because this package has no pi
5
+ // dependency and must not grow one: `jr2 up` reads this table host-side (R1, and the default
6
+ // `hosts` of a held secret), and the in-cluster Orchestrator never touches pi. The copy cannot
7
+ // drift silently — `@jr2/harness`'s `model-keys.test.ts` checks it against pi itself, env names per
8
+ // provider and every provider's default host, in the default gate. A pi bump that adds or moves a
9
+ // provider fails there first, the same canary role Harness conformance plays (ADR-0027).
10
+ //
11
+ // `host` is present only when every model of every provider that reads the name goes to ONE static
12
+ // host. It is absent when there is none: the base URL is a template (`{location}`, an account id),
13
+ // the providers disagree (MOONSHOT_API_KEY serves two regions), or the host is regional (Bedrock).
14
+ // A held secret with such a name must then say its `hosts` itself (§3.4 of the spec, R5).
15
+
16
+ /** One known model-key env name. */
17
+ export type ModelKey = {
18
+ /** The catalog provider ids that read this name. */
19
+ providers: readonly string[];
20
+ /** The host (port 443) those providers' calls go to by default, when there is exactly one. */
21
+ host?: string;
22
+ };
23
+
24
+ export const MODEL_KEYS: Readonly<Record<string, ModelKey>> = {
25
+ // anthropic reads three names. AUTH_TOKEN goes as `Authorization: Bearer`, the other two as
26
+ // `x-api-key` (an `sk-ant-oat` value is an OAuth token, which a Stand-in never is).
27
+ ANTHROPIC_API_KEY: { providers: ["anthropic"], host: "api.anthropic.com" },
28
+ ANTHROPIC_AUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
29
+ ANTHROPIC_OAUTH_TOKEN: { providers: ["anthropic"], host: "api.anthropic.com" },
30
+ OPENAI_API_KEY: { providers: ["openai"], host: "api.openai.com" },
31
+ GEMINI_API_KEY: { providers: ["google"], host: "generativelanguage.googleapis.com" },
32
+ // `https://{location}-aiplatform.googleapis.com` — regional, so no default.
33
+ GOOGLE_CLOUD_API_KEY: { providers: ["google-vertex"] },
34
+ COPILOT_GITHUB_TOKEN: { providers: ["github-copilot"], host: "api.individual.githubcopilot.com" },
35
+ // The bearer form of Bedrock auth. Regional, so no default. (SigV4 credentials cannot be held at
36
+ // all — a signature is not a header a Stand-in can be swapped into — and `jr2 up` warns on them.)
37
+ AWS_BEARER_TOKEN_BEDROCK: { providers: ["amazon-bedrock"] },
38
+ ANT_LING_API_KEY: { providers: ["ant-ling"], host: "api.ant-ling.com" },
39
+ QWEN_TOKEN_PLAN_API_KEY: {
40
+ providers: ["qwen-token-plan"],
41
+ host: "token-plan.ap-southeast-1.maas.aliyuncs.com",
42
+ },
43
+ QWEN_TOKEN_PLAN_CN_API_KEY: { providers: ["qwen-token-plan-cn"], host: "token-plan.cn-beijing.maas.aliyuncs.com" },
44
+ // The catalog names no base URL: an Azure deployment is the user's own resource.
45
+ AZURE_OPENAI_API_KEY: { providers: ["azure-openai-responses"] },
46
+ NVIDIA_API_KEY: { providers: ["nvidia"], host: "integrate.api.nvidia.com" },
47
+ DEEPSEEK_API_KEY: { providers: ["deepseek"], host: "api.deepseek.com" },
48
+ GROQ_API_KEY: { providers: ["groq"], host: "api.groq.com" },
49
+ CEREBRAS_API_KEY: { providers: ["cerebras"], host: "api.cerebras.ai" },
50
+ XAI_API_KEY: { providers: ["xai"], host: "api.x.ai" },
51
+ // pi reads the name, and its catalog lists no model for the provider at this pin.
52
+ RADIUS_API_KEY: { providers: ["radius"] },
53
+ OPENROUTER_API_KEY: { providers: ["openrouter"], host: "openrouter.ai" },
54
+ AI_GATEWAY_API_KEY: { providers: ["vercel-ai-gateway"], host: "ai-gateway.vercel.sh" },
55
+ ZAI_API_KEY: { providers: ["zai"], host: "api.z.ai" },
56
+ ZAI_CODING_CN_API_KEY: { providers: ["zai-coding-cn"], host: "open.bigmodel.cn" },
57
+ MISTRAL_API_KEY: { providers: ["mistral"], host: "api.mistral.ai" },
58
+ MINIMAX_API_KEY: { providers: ["minimax"], host: "api.minimax.io" },
59
+ MINIMAX_CN_API_KEY: { providers: ["minimax-cn"], host: "api.minimaxi.com" },
60
+ // Two providers, two hosts (`.ai` and `.cn`): no single default.
61
+ MOONSHOT_API_KEY: { providers: ["moonshotai", "moonshotai-cn"] },
62
+ HF_TOKEN: { providers: ["huggingface"], host: "router.huggingface.co" },
63
+ FIREWORKS_API_KEY: { providers: ["fireworks"], host: "api.fireworks.ai" },
64
+ TOGETHER_API_KEY: { providers: ["together"], host: "api.together.ai" },
65
+ // Two providers, one host.
66
+ OPENCODE_API_KEY: { providers: ["opencode", "opencode-go"], host: "opencode.ai" },
67
+ KIMI_API_KEY: { providers: ["kimi-coding"], host: "api.kimi.com" },
68
+ // Account-templated base URLs.
69
+ CLOUDFLARE_API_KEY: { providers: ["cloudflare-workers-ai", "cloudflare-ai-gateway"] },
70
+ XIAOMI_API_KEY: { providers: ["xiaomi"], host: "api.xiaomimimo.com" },
71
+ XIAOMI_TOKEN_PLAN_CN_API_KEY: { providers: ["xiaomi-token-plan-cn"], host: "token-plan-cn.xiaomimimo.com" },
72
+ XIAOMI_TOKEN_PLAN_AMS_API_KEY: { providers: ["xiaomi-token-plan-ams"], host: "token-plan-ams.xiaomimimo.com" },
73
+ XIAOMI_TOKEN_PLAN_SGP_API_KEY: { providers: ["xiaomi-token-plan-sgp"], host: "token-plan-sgp.xiaomimimo.com" },
74
+ };
75
+
76
+ /** Every catalog provider id some known model key belongs to — the set `harness.catalog` may name. */
77
+ export const MODEL_KEY_PROVIDERS: ReadonlySet<string> = new Set(
78
+ Object.values(MODEL_KEYS).flatMap((key) => key.providers),
79
+ );
80
+
81
+ /**
82
+ * Credentials that sign a request instead of riding it in a header (ADR-0059). A Stand-in cannot be
83
+ * swapped into a signature, so these cannot be held — `jr2 up` warns when `harness.env` sets one,
84
+ * because the Agent can read it, and names a gateway as the way out.
85
+ */
86
+ export const SIGNING_CREDENTIALS: readonly string[] = [
87
+ "AWS_ACCESS_KEY_ID",
88
+ "AWS_SECRET_ACCESS_KEY",
89
+ "AWS_SESSION_TOKEN",
90
+ "AWS_PROFILE",
91
+ "GOOGLE_APPLICATION_CREDENTIALS",
92
+ ];
package/src/names.ts CHANGED
@@ -12,13 +12,13 @@ export const INSTANCE_SECRET = "jr2-instance";
12
12
  /** The Instance Harness's Deployment + Service name (ADR-0031): converged by `jr2 up` whenever any
13
13
  * Agent definition declares `workspace: "none"`, and the deterministic Service DNS the Agent actor
14
14
  * resolves such a Turn to. Doubles as the delivery scope a Menu-only registration records — the
15
- * name the placement's Adapter token is signed for (tokens.ts, ADR-0013). The port is the
15
+ * name the placement's Sandbox token is signed for (tokens.ts, ADR-0013), which its Custodian holds. The port is the
16
16
  * Harness's own listen port (`PORT` default). */
17
17
  export const INSTANCE_HARNESS_SERVICE = "jr2-instance-harness";
18
18
  export const INSTANCE_HARNESS_PORT = 8080;
19
19
 
20
20
  /** The harness config ConfigMap the stock Harness boots from (ADR-0018): what this instance can
21
- * REACH — the custom model provider, minus its key. No Agents ride it: a Machine carries its own
21
+ * REACH — the custom model provider minus its key, and the catalog providers' gateways (ADR-0059). No Agents ride it: a Machine carries its own
22
22
  * (ADR-0049), and the definition rides each admission, so this holds deployment facts alone
23
23
  * (ADR-0050). */
24
24
  export const HARNESS_CONFIGMAP = "jr2-harness";
@@ -26,7 +26,7 @@ export const HARNESS_CONFIGMAP = "jr2-harness";
26
26
  export const HARNESS_CONFIG_KEY = "harness.json";
27
27
 
28
28
  /** The resolved image map `jr2 up` writes and every provision reads (ADR-0037/0038): key → ref for
29
- * the Harness, the Adapter, and every Sandbox Image context the registered Machines carry — keyed
29
+ * the Harness, the Custodian, and every Sandbox Image context the registered Machines carry — keyed
30
30
  * by content digest, plus the reserved `default` (ADR-0049).
31
31
  *
32
32
  * MOUNTED, never projected into env — the load-bearing part. A mount updates in place through
@@ -38,13 +38,37 @@ export const IMAGES_MOUNT = "/etc/jr2/images";
38
38
  /** The ConfigMap key, hence the filename under the mount — the two-sided contract's other half. */
39
39
  export const IMAGES_KEY = "images.json";
40
40
 
41
- /** The HARNESS containers' env Secret — Agent creds only, never the Instance token (ADR-0013). */
41
+ /** The HARNESS containers' env Secret — the literal `harness.env` values, never the Instance token
42
+ * (ADR-0013) and never a model key (ADR-0059: those are held). */
42
43
  export const HARNESS_ENV_SECRET = "jr2-harness-env";
43
44
 
44
- /** The instance's private-CA bundle ConfigMap (`harness.caBundle`, ADR-0020) — mounted into the
45
- * Harness container (and only it) so Agent egress trusts an internal CA. Public data by nature. */
45
+ /** The instance's trust ConfigMap (ADR-0020, ADR-0059): the user's `harness.caBundle` as `ca.crt`
46
+ * and, while a held secret exists, the three bundles the held-secret path needs — `extra.crt` and
47
+ * `bundle.crt` for the Harness, `upstream.crt` for the Custodian. Public data by nature. */
46
48
  export const CA_CONFIGMAP = "jr2-ca";
47
49
 
50
+ /** Held secrets (ADR-0059). The per-Instance CA — its key included — that `jr2 up` alone reads over
51
+ * the kube API, and that NO pod mounts: every leaf is issued before a pod starts. */
52
+ export const HELD_CA_SECRET = "jr2-held-ca";
53
+ /** The literal held values, one key per name — mounted into the Custodian and nothing else. */
54
+ export const HELD_SECRETS_SECRET = "jr2-held-secrets";
55
+ /** One leaf per bound `host:port` (`<stem>.crt`, `<stem>.key`) — the Custodian's alone. */
56
+ export const HELD_TLS_SECRET = "jr2-held-tls";
57
+ /** `held.json` (what `jr2 up` resolved) plus the Custodian's bootstrap and script. Always applied,
58
+ * possibly holding no secret: every Harness pod runs a Custodian. */
59
+ export const HELD_CONFIGMAP = "jr2-held";
60
+ /** Where the Orchestrator reads `held.json`, per provision — a mount, so a held-secret edit reaches
61
+ * future Sandboxes without rolling the Orchestrator (the `jr2-images` pattern, ADR-0038). */
62
+ export const HELD_MOUNT = "/etc/jr2/held";
63
+ export const HELD_KEY = "held.json";
64
+
65
+ /** The Custodian's listeners (ADR-0059), all taken on every Harness pod (ADR-0005): the Harness's
66
+ * HTTPS proxy on loopback, the kubelet's probe, and the control listener the Menu and the ask ride
67
+ * to the Orchestrator, at the address `jr2-upload-pack` defaults to (ADR-0053). */
68
+ export const CUSTODIAN_PORT = 15001;
69
+ export const CUSTODIAN_HEALTH_PORT = 15021;
70
+ export const CUSTODIAN_CONTROL_PORT = 8081;
71
+
48
72
  /** The scaffold's default deploy-key Secret name (ADR-0047/0051): what `jr2 init`'s wildcard
49
73
  * `git.credentials` entry names as its `sshKey`, and what `jr2 up`'s ssh offer generates into when
50
74
  * an entry names it. Only a default — an entry may name any Secret. */
@@ -1,6 +1,6 @@
1
1
  // The internal registration table (ADR-0011): one structure behind both delivery dialects.
2
2
  // An Agent slot and `gate` register `address → { accepted event defs, deliver closure, meta }`;
3
- // the two HTTP surfaces (`/agents/:iid/*` for the Adapter, `/runs/:id/gates/*` for humans and
3
+ // the two HTTP surfaces (`/agents/:iid/*` for the Agent's Harness, `/runs/:id/gates/*` for humans and
4
4
  // webhooks) are adapters over it — lookup, schema validation, delivery, discovery, and lifecycle
5
5
  // are implemented ONCE. The table is implementation structure, not vocabulary: workflows speak
6
6
  // only `defineEvent` / `agent` / `gate`.
package/src/repo-fetch.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Nothing in the pod holds a git credential, and nothing in it may talk to a remote — the node's
4
4
  // cache agent is the only thing that fetches (ADR-0051). So a `git fetch` in a pod is a program on
5
- // the runtime volume (`jr2-upload-pack`, operator/cmd) asking the Adapter on localhost, the Adapter
5
+ // the runtime volume (`jr2-upload-pack`, operator/cmd) asking the Custodian on localhost, the Custodian
6
6
  // asking the Orchestrator with the Sandbox token it already holds, and this port marking the
7
7
  // Sandbox CR: one annotation per Repo key, timestamp value. The operator copies the mark onto the
8
8
  // pod, the agent reads its demand off pods alone, and the landing comes back on the Sandbox's
package/src/run-host.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  // closures over their own `sendBack` — so delivery lands on the invoking state at any
8
8
  // nesting depth and the host routes nothing.
9
9
  // - Two thin surfaces sit on that table, and NEITHER is MCP (ADR-0013 — the Orchestrator does
10
- // not speak it): `agentSurface` / `sendToAgent` serve the Agent's Adapter (`/agents/:iid/*`),
10
+ // not speak it): `agentSurface` / `sendToAgent` serve the Agent's Harness (`/agents/:iid/*`),
11
11
  // `gates` / `sendToGate` serve humans, webhooks and CI (`/runs/:id/gates/*`). Lookup,
12
12
  // validation, delivery and lifecycle stay implemented once, in the table.
13
13
  // - The run's Agent children report their durable admissions through the run binding into
@@ -169,13 +169,14 @@ export type GateView = {
169
169
  };
170
170
 
171
171
  /**
172
- * One live agent surface, as its Adapter reads it (`GET /agents/:iid/surface` — ADR-0013). The
173
- * Adapter renders this as `tools/list`: each accepted event becomes a tool, its `input` schema the
174
- * tool's input schema. `semantics` rides along so the Adapter can tell an awaiting tool from a
175
- * fire-and-forget one — the room ADR-0006's deferred results will land in, unbuilt today.
172
+ * One live agent surface, as the Harness reads it through its Custodian (`GET /agents/:iid/surface`
173
+ * — ADR-0013). The Harness presents it to its model as the Menu: each accepted event becomes a
174
+ * tool, its `input` schema the tool's parameters. `semantics` rides along so the Harness can tell
175
+ * an awaiting tool from a fire-and-forget one — the room ADR-0006's deferred results will land in,
176
+ * unbuilt today.
176
177
  *
177
- * `sandbox` is the Sandbox that may deliver here. It is not a secret from the Adapter (that pod IS
178
- * the sandbox), and serving it lets the Adapter fail loudly on a surface that is not its own.
178
+ * `sandbox` is the Sandbox that may deliver here. It is not a secret from that pod (the pod IS the
179
+ * sandbox), and serving it lets a caller fail loudly on a surface that is not its own.
179
180
  */
180
181
  export type AgentSurfaceView = {
181
182
  instanceId: string;
@@ -186,7 +187,7 @@ export type AgentSurfaceView = {
186
187
 
187
188
  /**
188
189
  * The answer to one Agent delivery (`POST /agents/:iid/events`) — a receipt that DESCRIBES ITSELF
189
- * (ADR-0024). The Adapter renders it as prose, because a bare `deliveryId` told the Agent nothing
190
+ * (ADR-0024). The Harness renders it as prose, because a bare `deliveryId` told the Agent nothing
190
191
  * about whether it was finished, and the model answered that silence by calling again.
191
192
  *
192
193
  * `turnComplete` is the HINT, never the guarantee (the abort on invocation end is): it is read off
@@ -579,15 +580,14 @@ export class RunHost {
579
580
  /**
580
581
  * An agent instance's LIVE surface (`GET /agents/:iid/surface` — ADR-0013): what the state that
581
582
  * invoked this Agent accepts, right now. Undefined once nothing is registered (the state exited,
582
- * the run settled, the iid is unknown) — the one catch point, and the reason the Adapter never
583
+ * the run settled, the iid is unknown) — the one catch point, and the reason the Harness never
583
584
  * has to learn which turn is live: it asks, per turn, and the answer IS the turn.
584
585
  *
585
586
  * "Right now" is load-bearing (ADR-0029): the registered defs are the state's VOCABULARY, derived
586
587
  * statically from its transitions, and the guards on those transitions are asked here — so an
587
588
  * event the Machine cannot currently accept is not offered. The pick has not happened yet, so the
588
589
  * question is `mayMove`, not `wouldMove`: a guard that would have judged the Agent's arguments is
589
- * left on the menu and settled at delivery. The Adapter rebuilds this per MCP connection and the
590
- * Harness re-lists per Submission, so the filter lands at turn boundaries and never moves under
590
+ * left on the menu and settled at delivery. The Harness reads this afresh per Submission, so the filter lands at turn boundaries and never moves under
591
591
  * an Agent mid-turn.
592
592
  */
593
593
  agentSurface(instanceId: string): AgentSurfaceView | undefined {
@@ -609,8 +609,10 @@ export class RunHost {
609
609
  }
610
610
 
611
611
  /**
612
- * Deliver one event from an Agent's Adapter (`POST /agents/:iid/events` — ADR-0013). Validation
613
- * and delivery are the table's; this only agent-scopes the address and mints the receipt.
612
+ * Deliver one event from an Agent's Harness (`POST /agents/:iid/events` — ADR-0013). Validation
613
+ * and delivery are the table's; this only agent-scopes the address and mints the receipt. (A
614
+ * `deferred` event never reaches here: `jr2Setup` refuses to build a Machine that declares one —
615
+ * ADR-0013.)
614
616
  */
615
617
  sendToAgent(instanceId: string, event: { type?: unknown } & Record<string, unknown>): AgentDeliveryReceipt {
616
618
  const { type, ...payload } = event;