@jr2/cli 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jr2/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "The jr2 CLI: init, up, run — the interface to a jr2 Instance.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -39,13 +39,13 @@
39
39
  "ts-blank-space": "^0.9.0"
40
40
  },
41
41
  "peerDependencies": {
42
- "@jr2/orchestrator": "0.1.4"
42
+ "@jr2/orchestrator": "0.2.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^26.0.1",
46
46
  "xstate": "^5.18.0",
47
47
  "zod": "^4.4.3",
48
- "@jr2/orchestrator": "0.1.4"
48
+ "@jr2/orchestrator": "0.2.1"
49
49
  },
50
50
  "files": [
51
51
  "bin",
package/src/build.ts CHANGED
@@ -1004,8 +1004,8 @@ const execCommand: RunCommand = async (command, args, cwd) => {
1004
1004
  * dependency graph, wherever the folder sits — so it is staged as a copy
1005
1005
  * ({@link BUNDLE_STAGE_EXCLUDE}) and installed frozen and production-only by the lockfile's own
1006
1006
  * package manager ({@link lockfileInstall}). This is how a standalone instance committed INSIDE
1007
- * a pnpm monorepo bundles: the kit's own `docs/intro` sits under the checkout's
1008
- * `pnpm-workspace.yaml` and is deliberately not one of its packages.
1007
+ * a pnpm monorepo bundles: it sits under a `pnpm-workspace.yaml` that does not list it, so the
1008
+ * workspace above it says nothing about how it installs.
1009
1009
  * 2. A `pnpm-workspace.yaml` above it. A workspace member carries no lockfile of its own — the
1010
1010
  * workspace root holds it — so it is the ABSENCE of one that sends the instance to `pnpm deploy
1011
1011
  * --legacy`, unchanged. pnpm is a contributor prerequisite, like go for the operator, never a
@@ -59,6 +59,7 @@ import {
59
59
  loadWorkflows,
60
60
  matchCredential,
61
61
  partsOf,
62
+ harnessTokenDigest,
62
63
  sandboxToken,
63
64
  type CarriedAgent,
64
65
  type CarriedImage,
@@ -750,9 +751,9 @@ export async function up(args: string[], io: Io): Promise<number> {
750
751
  adapterImage: refs.adapter,
751
752
  harness: config.harness,
752
753
  caBundle: caPem !== undefined,
753
- // The echo gate (ADR-0023): the digest of the token materialized above — the same env
754
- // the orchestrator stamps onto every Sandbox Harness at provision.
755
- echoTokenSha256: createHash("sha256").update(instanceToken).digest("base64url"),
754
+ // The wire's gate (ADR-0058): the digest of the bearer the Orchestrator derives for this
755
+ // placement from the kept key — the same shape it stamps onto every Sandbox at provision.
756
+ bearerSha256: harnessTokenDigest(Buffer.from(signingKey, "base64"), INSTANCE_HARNESS_SERVICE),
756
757
  }),
757
758
  ...ctx,
758
759
  });
package/src/deploy.ts CHANGED
@@ -48,6 +48,10 @@ export const ANNOTATION_IMAGES = "jr2.dev/images";
48
48
 
49
49
  export const ORCHESTRATOR_SA = "jr2-orchestrator";
50
50
 
51
+ /** The two ingress NetworkPolicies (ADR-0058) — see `harnessIngressPolicies`. */
52
+ export const SANDBOX_INGRESS_POLICY = "jr2-sandbox-ingress";
53
+ export const INSTANCE_HARNESS_INGRESS_POLICY = "jr2-instance-harness-ingress";
54
+
51
55
  /** The operator's install location — per-cluster, shared by every instance (ADR-0019). */
52
56
  export const OPERATOR_NAMESPACE = "jr2-system";
53
57
  export const OPERATOR_DEPLOYMENT = "jr2-controller-manager";
@@ -303,12 +307,51 @@ export function instanceObjects(opts: {
303
307
  ports: [{ port: ORCHESTRATOR_PORT, targetPort: ORCHESTRATOR_PORT }],
304
308
  },
305
309
  },
310
+ ...harnessIngressPolicies(opts.name, meta),
306
311
  ...(opts.repoCache ? repoCacheObjects({ ...opts.repoCache, namespace: opts.namespace, labels, meta }) : []),
307
312
  ];
308
313
 
309
314
  return JSON.stringify({ apiVersion: "v1", kind: "List", items });
310
315
  }
311
316
 
317
+ /** The operator's label on every Sandbox pod (operator/internal/controller/helpers.go) — and on no
318
+ * other pod, which is what lets one selector mean "every Sandbox, now and later". */
319
+ const SANDBOX_POD_LABEL = "sandbox.jr2.dev/name";
320
+
321
+ /**
322
+ * Ingress to every Harness pod is the Orchestrator's alone (ADR-0058) — the NetworkPolicy half of
323
+ * the wire's defense; the bearer is the other, and the control (ADR-0013: policy bounds where a pod
324
+ * may talk, only a token bounds what it may do). One policy per placement: every Sandbox pod, by
325
+ * the label the operator stamps on each, and the Instance Harness.
326
+ *
327
+ * Converged UNCONDITIONALLY, not only when a Machine composes a Sandbox or declares a `"none"`
328
+ * Agent: a policy that selects nothing costs nothing, and a pod born before its policy would be a
329
+ * window. The peer is this instance's Orchestrator and no other — a `podSelector` never crosses the
330
+ * namespace, and the instance label pins it within one. What is NOT here, on purpose:
331
+ *
332
+ * - The Orchestrator is not selected. Every route there is token-gated, and an Adapter in any
333
+ * Harness pod must reach it; a policy could not tell the Adapter's packets from the Agent's.
334
+ * - No egress. What a Sandbox may reach is its own decision (ADR-0058 records it open).
335
+ * - No port. The Orchestrator speaks only the Harness wire to these pods, and a port here would
336
+ * have to track the CR's `port`; `kubectl port-forward` (the CLI, a human's shell into the User
337
+ * Container — ADR-0005) and kubelet probes never cross a policy at all.
338
+ */
339
+ function harnessIngressPolicies(instance: string, meta: (name: string) => KubeManifest): KubeManifest[] {
340
+ const fromOrchestrator = [
341
+ { from: [{ podSelector: { matchLabels: { app: ORCHESTRATOR_SERVICE, [LABEL_INSTANCE]: instance } } }] },
342
+ ];
343
+ const policy = (name: string, podSelector: Record<string, unknown>): KubeManifest => ({
344
+ apiVersion: "networking.k8s.io/v1",
345
+ kind: "NetworkPolicy",
346
+ metadata: meta(name),
347
+ spec: { podSelector, policyTypes: ["Ingress"], ingress: fromOrchestrator },
348
+ });
349
+ return [
350
+ policy(SANDBOX_INGRESS_POLICY, { matchExpressions: [{ key: SANDBOX_POD_LABEL, operator: "Exists" }] }),
351
+ policy(INSTANCE_HARNESS_INGRESS_POLICY, { matchLabels: { app: INSTANCE_HARNESS_SERVICE } }),
352
+ ];
353
+ }
354
+
312
355
  /** Where the cache agent's pod sees the node's directory: `--cache-dir`'s default. */
313
356
  const REPO_CACHE_MOUNT = "/cache";
314
357
  /** The agent's `$HOME` — an emptyDir, where it writes a deploy key for the life of the pod. */
@@ -461,10 +504,11 @@ export function instanceHarnessObjects(opts: {
461
504
  harness?: HarnessConfig;
462
505
  /** The instance ships a private-CA bundle (ADR-0020): mount `jr2-ca` into the Harness container. */
463
506
  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;
507
+ /** The digest of this placement's Harness bearer (ADR-0058; `harnessTokenDigest(key,
508
+ * "jr2-instance-harness")`) — the wire's gate. The digest, never the bearer: the same shape
509
+ * every Sandbox Harness container gets, so this pod can verify the Orchestrator and mint
510
+ * nothing. */
511
+ bearerSha256: string;
468
512
  }): string {
469
513
  const labels = { [LABEL_INSTANCE]: opts.name, "app.kubernetes.io/managed-by": "jr2" };
470
514
  const meta = (): KubeManifest => ({
@@ -495,15 +539,16 @@ export function instanceHarnessObjects(opts: {
495
539
  name: "JR2_HARNESS_JSON",
496
540
  valueFrom: { configMapKeyRef: { name: HARNESS_CONFIGMAP, key: HARNESS_CONFIG_KEY } },
497
541
  },
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.
542
+ // The placement gate (ADR-0031): every admission carries its own definition (ADR-0049), so
543
+ // the Harness itself refuses any admission whose definition declares Workspace access —
544
+ // Menu-only Agents alone run here, which is what makes "no code execution in this pod" true
545
+ // rather than asserted. The bearer (below) narrows who may try; this decides what.
502
546
  { name: "JR2_MENU_ONLY", value: "1" },
503
- ...(opts.echoTokenSha256 ? [{ name: "JR2_ECHO_TOKEN_SHA256", value: opts.echoTokenSha256 }] : []),
504
547
  ...(opts.harness?.env ?? []).filter((v) => v.valueFrom !== undefined),
505
548
  { name: "JR2_ADAPTER_URL", value: `http://127.0.0.1:${ADAPTER_PORT}` },
506
549
  ...(opts.caBundle ? [{ name: "NODE_EXTRA_CA_CERTS", value: `${CA_MOUNT}/ca.crt` }] : []),
550
+ // The wire's gate (ADR-0058), last — as on a Sandbox — so no `harness.env` entry chooses it.
551
+ { name: "JR2_HARNESS_TOKEN_SHA256", value: opts.bearerSha256 },
507
552
  ],
508
553
  envFrom: [{ secretRef: { name: HARNESS_ENV_SECRET } }, ...(opts.harness?.envFrom ?? [])],
509
554
  ...(opts.caBundle ? { volumeMounts: [{ name: "ca", mountPath: CA_MOUNT, readOnly: true }] } : {}),