@jr2/cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/deploy.ts CHANGED
@@ -7,7 +7,16 @@ import { readFile } from "node:fs/promises";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import {
9
9
  CA_CONFIGMAP,
10
+ custodianComposition,
11
+ CUSTODIAN_BOOTSTRAP_KEY,
12
+ CUSTODIAN_SCRIPT_KEY,
10
13
  GIT_SSH_SECRET,
14
+ HELD_CA_SECRET,
15
+ HELD_CONFIGMAP,
16
+ HELD_KEY,
17
+ HELD_MOUNT,
18
+ HELD_SECRETS_SECRET,
19
+ HELD_TLS_SECRET,
11
20
  HARNESS_ENV_SECRET,
12
21
  IMAGES_CONFIGMAP,
13
22
  IMAGES_KEY,
@@ -24,8 +33,10 @@ import {
24
33
  REPO_CACHE_HOSTPATH,
25
34
  STATE_PVC,
26
35
  type HarnessConfig,
36
+ type HeldManifest,
27
37
  type SandboxPlacement,
28
38
  } from "@jr2/orchestrator";
39
+ import type { Pem } from "./held-pki.ts";
29
40
 
30
41
  export {
31
42
  GIT_SSH_SECRET,
@@ -48,6 +59,17 @@ export const ANNOTATION_IMAGES = "jr2.dev/images";
48
59
 
49
60
  export const ORCHESTRATOR_SA = "jr2-orchestrator";
50
61
 
62
+ /** The HMAC over the held-secret inputs, on the Instance Harness's pod template (ADR-0059). */
63
+ export const ANNOTATION_HELD_DIGEST = "jr2.dev/held-digest";
64
+
65
+ /** The Instance Harness pod's `fsGroup` — a Sandbox pod's default work group (ADR-0005), for the
66
+ * same reason: a group every container is granted, which the Custodian's values are readable by. */
67
+ const INSTANCE_HARNESS_FS_GROUP = 2000;
68
+
69
+ /** The two ingress NetworkPolicies (ADR-0058) — see `harnessIngressPolicies`. */
70
+ export const SANDBOX_INGRESS_POLICY = "jr2-sandbox-ingress";
71
+ export const INSTANCE_HARNESS_INGRESS_POLICY = "jr2-instance-harness-ingress";
72
+
51
73
  /** The operator's install location — per-cluster, shared by every instance (ADR-0019). */
52
74
  export const OPERATOR_NAMESPACE = "jr2-system";
53
75
  export const OPERATOR_DEPLOYMENT = "jr2-controller-manager";
@@ -77,6 +99,24 @@ export function compareVersions(a: string, b: string): number {
77
99
 
78
100
  type KubeManifest = Record<string, unknown>;
79
101
 
102
+ /**
103
+ * What `jr2 up` resolved about held secrets (ADR-0059), ready to become objects. Values appear in
104
+ * two places only: `values` (the literals, for the `jr2-held-secrets` Secret) and the CA's and the
105
+ * leaves' keys — each lands in a Secret no Harness container mounts.
106
+ */
107
+ export type HeldObjects = {
108
+ manifest: HeldManifest;
109
+ /** The literal values, by name — never in the manifest, never in a ConfigMap. */
110
+ values: Record<string, string>;
111
+ /** The per-Instance CA — kept across converges; present whenever a secret is held. */
112
+ ca?: Pem;
113
+ /** One leaf per bound target, by file stem (`leafStem`). */
114
+ leaves: Record<string, Pem>;
115
+ /** The Custodian's rendered bootstrap (JSON) and script. */
116
+ bootstrap: string;
117
+ script: string;
118
+ };
119
+
80
120
  /** Everything `jr2 up` converges inside the instance's namespace, as one apply-able List. */
81
121
  export function instanceObjects(opts: {
82
122
  name: string;
@@ -85,14 +125,15 @@ export function instanceObjects(opts: {
85
125
  hash: string;
86
126
  /** Secret data (token, signing key, harness env literals) — written stringData, kube encodes. */
87
127
  secretData: Record<string, string>;
88
- /** The HARNESS containers' env values (creds, provider key) — a SEPARATE Secret from
89
- * `secretData` by doctrine (ADR-0013): Agent code executes where this lands, so the Instance
90
- * token and signing key must never share a Secret with it. */
128
+ /** The HARNESS containers' literal `harness.env` values — a SEPARATE Secret from `secretData` by
129
+ * doctrine (ADR-0013): Agent code executes where this lands, so the Instance token and signing
130
+ * key must never share a Secret with it, and no model key rides it either (ADR-0059). */
91
131
  harnessEnvData: Record<string, string>;
92
132
  harness?: HarnessConfig;
93
- /** The private-CA PEM bundle (`harness.caBundle` file contents, read by `up` — ADR-0020). */
94
- caBundle?: string;
95
- /** Every image ref THIS converge resolved (ADR-0037/0038/0049): `{ harness, adapter, operator?,
133
+ /** Held secrets (ADR-0059): the Custodian's objects. The `jr2-held` ConfigMap is applied always —
134
+ * every Harness pod runs a Custodian — and the rest only while a secret is held. */
135
+ held: HeldObjects;
136
+ /** Every image ref THIS converge resolved (ADR-0037/0038/0049): `{ harness, custodian, operator?,
96
137
  * sandbox: { <key>: ref }, sandboxUser: { <key>: user } }`, where a key is a build context's
97
138
  * content digest or the reserved `default`. It lands twice, deliberately as one JSON so the
98
139
  * record `up` diffs and the map pods read can never disagree: as the `jr2-images` ConfigMap the
@@ -161,9 +202,8 @@ export function instanceObjects(opts: {
161
202
  data: {
162
203
  [HARNESS_CONFIG_KEY]: JSON.stringify(
163
204
  {
164
- // apiKey is deliberately dropped: it materializes into the Secret as
165
- // JR2_PROVIDER_API_KEY (`up`), and the Harness reads it from env — a ConfigMap is not
166
- // a place for a credential.
205
+ // apiKey is deliberately dropped: it is a held secret (ADR-0059) — the Custodian holds
206
+ // it, the Harness's env holds its Stand-in, and a ConfigMap is no place for either.
167
207
  provider: opts.harness?.provider
168
208
  ? {
169
209
  id: opts.harness.provider.id,
@@ -176,6 +216,8 @@ export function instanceObjects(opts: {
176
216
  models: opts.harness.provider.models,
177
217
  }
178
218
  : undefined,
219
+ // Where pi's catalog providers send their calls (ADR-0059): endpoints, not keys.
220
+ catalog: opts.harness?.catalog,
179
221
  },
180
222
  null,
181
223
  2,
@@ -193,18 +235,20 @@ export function instanceObjects(opts: {
193
235
  metadata: meta(IMAGES_CONFIGMAP),
194
236
  data: { [IMAGES_KEY]: imagesJson },
195
237
  },
196
- // The private-CA bundle (ADR-0020) — a ConfigMap, not a Secret: CA certs are public data.
197
- // kubectlSandbox mounts it into the Harness container and points NODE_EXTRA_CA_CERTS at it.
198
- ...(opts.caBundle
199
- ? [
200
- {
201
- apiVersion: "v1",
202
- kind: "ConfigMap",
203
- metadata: meta(CA_CONFIGMAP),
204
- data: { "ca.crt": opts.caBundle },
205
- },
206
- ]
207
- : []),
238
+ // Held secrets (ADR-0059). `held.json` is what every composition reads — the Orchestrator per
239
+ // provision from the mount below, `jr2 up` for the Instance Harness — beside the Custodian's
240
+ // bootstrap and script, which Envoy reads at start. Names, hosts, headers: never a value.
241
+ {
242
+ apiVersion: "v1",
243
+ kind: "ConfigMap",
244
+ metadata: meta(HELD_CONFIGMAP),
245
+ data: {
246
+ [HELD_KEY]: JSON.stringify(opts.held.manifest, null, 2),
247
+ [CUSTODIAN_BOOTSTRAP_KEY]: opts.held.bootstrap,
248
+ [CUSTODIAN_SCRIPT_KEY]: opts.held.script,
249
+ },
250
+ },
251
+ ...heldSecretObjects(opts.held, meta),
208
252
  {
209
253
  apiVersion: "v1",
210
254
  kind: "Secret",
@@ -265,6 +309,9 @@ export function instanceObjects(opts: {
265
309
  // The image map, read per provision (ADR-0038). A mount, so `jr2 up` rewriting
266
310
  // it costs one kubelet propagation window instead of a rollout.
267
311
  { name: "images", mountPath: IMAGES_MOUNT, readOnly: true },
312
+ // What the Custodian holds, read per provision the same way (ADR-0059): names
313
+ // and hosts, which this process could not derive — its `.env` values are absent.
314
+ { name: "held", mountPath: HELD_MOUNT, readOnly: true },
268
315
  ],
269
316
  // The period, not the boot, is what `up`'s rollout wait measures. Measured: the
270
317
  // container answers `/healthz` 1.1s after it starts, and the default 10s period
@@ -289,6 +336,7 @@ export function instanceObjects(opts: {
289
336
  volumes: [
290
337
  { name: "state", persistentVolumeClaim: { claimName: STATE_PVC } },
291
338
  { name: "images", configMap: { name: IMAGES_CONFIGMAP } },
339
+ { name: "held", configMap: { name: HELD_CONFIGMAP, items: [{ key: HELD_KEY, path: HELD_KEY }] } },
292
340
  ],
293
341
  },
294
342
  },
@@ -303,12 +351,51 @@ export function instanceObjects(opts: {
303
351
  ports: [{ port: ORCHESTRATOR_PORT, targetPort: ORCHESTRATOR_PORT }],
304
352
  },
305
353
  },
354
+ ...harnessIngressPolicies(opts.name, meta),
306
355
  ...(opts.repoCache ? repoCacheObjects({ ...opts.repoCache, namespace: opts.namespace, labels, meta }) : []),
307
356
  ];
308
357
 
309
358
  return JSON.stringify({ apiVersion: "v1", kind: "List", items });
310
359
  }
311
360
 
361
+ /** The operator's label on every Sandbox pod (operator/internal/controller/helpers.go) — and on no
362
+ * other pod, which is what lets one selector mean "every Sandbox, now and later". */
363
+ const SANDBOX_POD_LABEL = "sandbox.jr2.dev/name";
364
+
365
+ /**
366
+ * Ingress to every Harness pod is the Orchestrator's alone (ADR-0058) — the NetworkPolicy half of
367
+ * the wire's defense; the bearer is the other, and the control (ADR-0013: policy bounds where a pod
368
+ * may talk, only a token bounds what it may do). One policy per placement: every Sandbox pod, by
369
+ * the label the operator stamps on each, and the Instance Harness.
370
+ *
371
+ * Converged UNCONDITIONALLY, not only when a Machine composes a Sandbox or declares a `"none"`
372
+ * Agent: a policy that selects nothing costs nothing, and a pod born before its policy would be a
373
+ * window. The peer is this instance's Orchestrator and no other — a `podSelector` never crosses the
374
+ * namespace, and the instance label pins it within one. What is NOT here, on purpose:
375
+ *
376
+ * - The Orchestrator is not selected. Every route there is token-gated, and a Custodian in any
377
+ * Harness pod must reach it; a policy could not tell the Custodian's packets from the Agent's.
378
+ * - No egress. What a Sandbox may reach is its own decision (ADR-0058 records it open).
379
+ * - No port. The Orchestrator speaks only the Harness wire to these pods, and a port here would
380
+ * have to track the CR's `port`; `kubectl port-forward` (the CLI, a human's shell into the User
381
+ * Container — ADR-0005) and kubelet probes never cross a policy at all.
382
+ */
383
+ function harnessIngressPolicies(instance: string, meta: (name: string) => KubeManifest): KubeManifest[] {
384
+ const fromOrchestrator = [
385
+ { from: [{ podSelector: { matchLabels: { app: ORCHESTRATOR_SERVICE, [LABEL_INSTANCE]: instance } } }] },
386
+ ];
387
+ const policy = (name: string, podSelector: Record<string, unknown>): KubeManifest => ({
388
+ apiVersion: "networking.k8s.io/v1",
389
+ kind: "NetworkPolicy",
390
+ metadata: meta(name),
391
+ spec: { podSelector, policyTypes: ["Ingress"], ingress: fromOrchestrator },
392
+ });
393
+ return [
394
+ policy(SANDBOX_INGRESS_POLICY, { matchExpressions: [{ key: SANDBOX_POD_LABEL, operator: "Exists" }] }),
395
+ policy(INSTANCE_HARNESS_INGRESS_POLICY, { matchLabels: { app: INSTANCE_HARNESS_SERVICE } }),
396
+ ];
397
+ }
398
+
312
399
  /** Where the cache agent's pod sees the node's directory: `--cache-dir`'s default. */
313
400
  const REPO_CACHE_MOUNT = "/cache";
314
401
  /** The agent's `$HOME` — an emptyDir, where it writes a deploy key for the life of the pod. */
@@ -426,23 +513,73 @@ function repoCacheObjects(opts: {
426
513
  ];
427
514
  }
428
515
 
429
- /** Where the Instance Harness's Harness container sees the CA bundle — the same path
430
- * `kubectlSandbox` mounts it at in a Sandbox pod (ADR-0020). */
431
- const CA_MOUNT = "/etc/jr2/ca";
516
+ /**
517
+ * The held-secret Secrets (ADR-0059), present only while a secret is held: the CA (which NO pod
518
+ * mounts — `jr2 up` alone reads it back), the literal values and the leaves (the Custodian's alone).
519
+ * `stringData` as written: no trailing newline is added to a value.
520
+ */
521
+ function heldSecretObjects(held: HeldObjects, meta: (name: string) => KubeManifest): KubeManifest[] {
522
+ if (held.manifest.secrets.length === 0 || !held.ca) return [];
523
+ const secret = (name: string, stringData: Record<string, string>): KubeManifest => ({
524
+ apiVersion: "v1",
525
+ kind: "Secret",
526
+ metadata: meta(name),
527
+ type: "Opaque",
528
+ stringData,
529
+ });
530
+ const leaves: Record<string, string> = {};
531
+ for (const [stem, pem] of Object.entries(held.leaves)) {
532
+ leaves[`${stem}.crt`] = pem.cert;
533
+ leaves[`${stem}.key`] = pem.key;
534
+ }
535
+ return [
536
+ secret(HELD_CA_SECRET, { "ca.crt": held.ca.cert, "ca.key": held.ca.key }),
537
+ ...(Object.keys(held.values).length ? [secret(HELD_SECRETS_SECRET, held.values)] : []),
538
+ secret(HELD_TLS_SECRET, leaves),
539
+ ];
540
+ }
432
541
 
433
- /** The Adapter's port on the pod's loopback — the same default the Sandbox pod uses. */
434
- const ADAPTER_PORT = 8081;
542
+ /**
543
+ * The trust ConfigMap (ADR-0020, ADR-0059): the user's `caBundle` as `ca.crt`, and while a secret
544
+ * is held, the three bundles its path needs. Its own manifest, apart from the List, because the
545
+ * bundles carry every root certificate — past what client-side apply's annotation can hold — so
546
+ * `jr2 up` applies it server-side. Undefined when there is nothing to trust.
547
+ */
548
+ export function trustObject(opts: {
549
+ name: string;
550
+ namespace: string;
551
+ caBundle?: string;
552
+ bundles?: { extra: string; bundle: string; upstream: string };
553
+ }): string | undefined {
554
+ if (opts.caBundle === undefined && opts.bundles === undefined) return undefined;
555
+ return JSON.stringify({
556
+ apiVersion: "v1",
557
+ kind: "ConfigMap",
558
+ metadata: {
559
+ name: CA_CONFIGMAP,
560
+ namespace: opts.namespace,
561
+ labels: { [LABEL_INSTANCE]: opts.name, "app.kubernetes.io/managed-by": "jr2" },
562
+ },
563
+ data: {
564
+ ...(opts.caBundle !== undefined ? { "ca.crt": opts.caBundle } : {}),
565
+ ...(opts.bundles
566
+ ? { "extra.crt": opts.bundles.extra, "bundle.crt": opts.bundles.bundle, "upstream.crt": opts.bundles.upstream }
567
+ : {}),
568
+ },
569
+ });
570
+ }
435
571
 
436
572
  /**
437
573
  * The Instance Harness (ADR-0031): the per-instance Harness Deployment + Service `jr2 up`
438
574
  * converges whenever an Agent a registered Machine CARRIES declares `workspace: "none"`
439
575
  * (ADR-0049's walk) — the placement for every Menu-only Agent's Turn, regardless of any enclosing
440
- * Workspace. The one Harness shape, minus the Workspace: the stock Harness image plus the Adapter
441
- * sidecar, the same harness-config ConfigMap and env/envFrom/CA wiring a Sandbox's Harness
442
- * container gets — and NO `/work` volume, no attach step. It runs the STOCK image permanently: `workspace: "none"` withholds the whole
443
- * Working toolset (ADR-0028), so there are no tools to carry and no Sandbox Image to resolve
444
- * (ADR-0037). No config key names, sizes, addresses, or enables it: the Machine walk is the
445
- * entire surface.
576
+ * Workspace. The one Harness shape, minus the Workspace: the stock Harness image plus the Custodian,
577
+ * composed by the same function a Sandbox's is (custodian.ts, ADR-0059), the same harness-config
578
+ * ConfigMap and env/envFrom/trust wiring a Sandbox's Harness container gets — and NO `/work`
579
+ * volume, no attach step. It runs the STOCK image permanently: `workspace: "none"` withholds the
580
+ * whole Working toolset (ADR-0028), so there are no tools to carry and no Sandbox Image to resolve
581
+ * (ADR-0037). No config key names, sizes, addresses, or enables it: the Machine walk is the entire
582
+ * surface.
446
583
  */
447
584
  export function instanceHarnessObjects(opts: {
448
585
  name: string;
@@ -455,16 +592,24 @@ export function instanceHarnessObjects(opts: {
455
592
  * a Sandbox's refs travel through the `jr2-images` ConfigMap. Both are right for what they are —
456
593
  * this Deployment is supposed to roll when its image moves; the Orchestrator is not. */
457
594
  harnessImage: string;
458
- /** The resolved Adapter ref: the Harness's one menu-delivery path, kept even though a `"none"`
595
+ /** The resolved Custodian ref: the Harness's one route to its Menu, kept even though a `"none"`
459
596
  * Agent cannot execute code — forking the path for one pod buys a divergence ADR-0031 declines. */
460
- adapterImage: string;
597
+ custodianImage: string;
598
+ /** What this converge resolved about held secrets (ADR-0059) — the same manifest the
599
+ * Orchestrator composes every Sandbox's Custodian from. */
600
+ held: HeldManifest;
601
+ /** An HMAC over every held-secret input (`held.json`, the leaves, the literal values, the
602
+ * Custodian's config), keyed with the signing key so it reveals nothing — on the pod template, so
603
+ * a change to any of them rolls this pod (ADR-0059). */
604
+ heldDigest: string;
461
605
  harness?: HarnessConfig;
462
- /** The instance ships a private-CA bundle (ADR-0020): mount `jr2-ca` into the Harness container. */
606
+ /** The instance ships a private-CA bundle (ADR-0020). */
463
607
  caBundle?: boolean;
464
- /** The Instance token's sha-256 — the Harness's echo gate (ADR-0023). The digest, never the
465
- * token: the same env every Sandbox Harness container gets, kept here so the one-Harness-shape
466
- * claim stays whole even though nothing narrates to the Instance Harness today. */
467
- echoTokenSha256?: string;
608
+ /** The digest of this placement's Harness bearer (ADR-0058; `harnessTokenDigest(key,
609
+ * "jr2-instance-harness")`) — the wire's gate. The digest, never the bearer: the same shape
610
+ * every Sandbox Harness container gets, so this pod can verify the Orchestrator and mint
611
+ * nothing. */
612
+ bearerSha256: string;
468
613
  }): string {
469
614
  const labels = { [LABEL_INSTANCE]: opts.name, "app.kubernetes.io/managed-by": "jr2" };
470
615
  const meta = (): KubeManifest => ({
@@ -482,31 +627,41 @@ export function instanceHarnessObjects(opts: {
482
627
  seccompProfile: { type: "RuntimeDefault" },
483
628
  });
484
629
 
630
+ // The Custodian (ADR-0013, ADR-0059): the placement's Sandbox token — signed for this
631
+ // placement's name (`up.ts` mints it into the instance Secret), so it speaks only for the Turns
632
+ // hosted HERE (tokens.ts) — plus every held secret. Mounted into the Custodian alone; the Instance
633
+ // token itself never enters this pod.
634
+ const custodian = custodianComposition(opts.held, {
635
+ image: opts.custodianImage,
636
+ token: { secret: INSTANCE_SECRET, key: "JR2_INSTANCE_HARNESS_TOKEN" },
637
+ caBundle: opts.caBundle === true,
638
+ });
639
+
485
640
  const harnessContainer = {
486
641
  name: "harness",
487
642
  image: opts.harnessImage,
488
643
  imagePullPolicy: "IfNotPresent",
489
644
  ports: [{ containerPort: INSTANCE_HARNESS_PORT }],
490
- // The same asymmetry the Sandbox pod builds (ADR-0013/0020): the mounted harness config and
491
- // the instance's valueFrom entries ride `env` (literal values live in the jr2-harness-env
492
- // Secret), the CA trust lands here and nowhere else, and no credential ever does.
645
+ // The same asymmetry the Sandbox pod builds (ADR-0013/0020/0059): the mounted harness config
646
+ // and the instance's valueFrom entries ride `env` (literal values live in the jr2-harness-env
647
+ // Secret), then the Custodian's address and every Stand-in, and no credential ever does.
493
648
  env: [
494
649
  {
495
650
  name: "JR2_HARNESS_JSON",
496
651
  valueFrom: { configMapKeyRef: { name: HARNESS_CONFIGMAP, key: HARNESS_CONFIG_KEY } },
497
652
  },
498
- // The placement gate (ADR-0031): every admission carries its own definition (ADR-0049) and
499
- // the wire is unauthenticated in-cluster, so the Harness itself refuses any admission whose
500
- // definition declares Workspace access — Menu-only Agents alone run here, which is what
501
- // makes "no code execution in this pod" true rather than asserted.
653
+ // The placement gate (ADR-0031): every admission carries its own definition (ADR-0049), so
654
+ // the Harness itself refuses any admission whose definition declares Workspace access —
655
+ // Menu-only Agents alone run here, which is what makes "no code execution in this pod" true
656
+ // rather than asserted. The bearer (below) narrows who may try; this decides what.
502
657
  { name: "JR2_MENU_ONLY", value: "1" },
503
- ...(opts.echoTokenSha256 ? [{ name: "JR2_ECHO_TOKEN_SHA256", value: opts.echoTokenSha256 }] : []),
504
658
  ...(opts.harness?.env ?? []).filter((v) => v.valueFrom !== undefined),
505
- { name: "JR2_ADAPTER_URL", value: `http://127.0.0.1:${ADAPTER_PORT}` },
506
- ...(opts.caBundle ? [{ name: "NODE_EXTRA_CA_CERTS", value: `${CA_MOUNT}/ca.crt` }] : []),
659
+ ...custodian.harnessEnv,
660
+ // The wire's gate (ADR-0058), last — as on a Sandbox — so no `harness.env` entry chooses it.
661
+ { name: "JR2_HARNESS_TOKEN_SHA256", value: opts.bearerSha256 },
507
662
  ],
508
663
  envFrom: [{ secretRef: { name: HARNESS_ENV_SECRET } }, ...(opts.harness?.envFrom ?? [])],
509
- ...(opts.caBundle ? { volumeMounts: [{ name: "ca", mountPath: CA_MOUNT, readOnly: true }] } : {}),
664
+ ...(custodian.harnessMounts.length ? { volumeMounts: custodian.harnessMounts } : {}),
510
665
  // The operator probes a Sandbox's Harness the same way: serving = the socket accepts.
511
666
  // Period and threshold as reasoned on the Orchestrator above: the default 10s period is the
512
667
  // rollout wait rather than the boot, and the threshold then has to carry the stall tolerance
@@ -520,32 +675,6 @@ export function instanceHarnessObjects(opts: {
520
675
  securityContext: hardenedContainerSecurityContext(),
521
676
  };
522
677
 
523
- const adapterContainer = {
524
- name: "adapter",
525
- image: opts.adapterImage,
526
- imagePullPolicy: "IfNotPresent",
527
- securityContext: hardenedContainerSecurityContext(),
528
- env: [
529
- {
530
- name: "JR2_ORCHESTRATOR_URL",
531
- value: `http://${ORCHESTRATOR_SERVICE}.${opts.namespace}.svc:${ORCHESTRATOR_PORT}`,
532
- },
533
- { name: "JR2_ADAPTER_PORT", value: String(ADAPTER_PORT) },
534
- // The Adapter's bearer env, carrying a sandbox-style token SIGNED FOR THIS PLACEMENT's
535
- // name (`up.ts` mints it into the instance Secret): ADR-0013's delivery doctrine, extended
536
- // to the second placement — the token speaks only for registrations that record the
537
- // Instance Harness as the pod hosting their Turn (tokens.ts), never a Workspace's, and the
538
- // Instance token itself never enters this pod. The credential lives in this container,
539
- // where no Agent can read it — and `JR2_MENU_ONLY` above is what keeps that true: only
540
- // Menu-only Agents run here, so nothing in this pod executes code (ADR-0031's
541
- // defense-in-depth bonus).
542
- {
543
- name: "JR2_SANDBOX_TOKEN",
544
- valueFrom: { secretKeyRef: { name: INSTANCE_SECRET, key: "JR2_INSTANCE_HARNESS_TOKEN" } },
545
- },
546
- ],
547
- };
548
-
549
678
  const items: KubeManifest[] = [
550
679
  {
551
680
  apiVersion: "apps/v1",
@@ -561,18 +690,26 @@ export function instanceHarnessObjects(opts: {
561
690
  strategy: { type: "Recreate" },
562
691
  selector: { matchLabels: { app: INSTANCE_HARNESS_SERVICE } },
563
692
  template: {
564
- metadata: { labels: { ...labels, app: INSTANCE_HARNESS_SERVICE } },
693
+ metadata: {
694
+ labels: { ...labels, app: INSTANCE_HARNESS_SERVICE },
695
+ // A held-secret edit rolls this pod; a live Sandbox keeps what its Custodian read at
696
+ // start (ADR-0059, the ADR-0037 stance).
697
+ annotations: { [ANNOTATION_HELD_DIGEST]: opts.heldDigest },
698
+ },
565
699
  spec: {
566
- containers: [harnessContainer, adapterContainer],
700
+ containers: [harnessContainer, custodian.custodianContainer],
567
701
  // The operator's isolation baseline (sandbox_controller.go), mirrored: same Harness
568
702
  // image, same "never reach the Kubernetes API" north star — JR2_MENU_ONLY makes code
569
- // execution here unlikely, not unimaginable.
703
+ // execution here unlikely, not unimaginable. No `shareProcessNamespace`: the Harness
704
+ // container must not read the Custodian's `/proc`. `fsGroup` makes the Custodian's
705
+ // group-readable values readable by Envoy's own uid, as a Sandbox pod's does.
570
706
  automountServiceAccountToken: false,
571
707
  securityContext: {
572
708
  runAsNonRoot: true,
573
709
  seccompProfile: { type: "RuntimeDefault" },
710
+ fsGroup: INSTANCE_HARNESS_FS_GROUP,
574
711
  },
575
- ...(opts.caBundle ? { volumes: [{ name: "ca", configMap: { name: CA_CONFIGMAP } }] } : {}),
712
+ volumes: custodian.volumes,
576
713
  },
577
714
  },
578
715
  },