Skip to content

ST5: automate the live gate — prove leaf execs land on the relay worker - #175

Merged
pdettori merged 6 commits into
rossoctl:mainfrom
pdettori:feat/st5-relay-leaf-smoke
Aug 27, 2026
Merged

ST5: automate the live gate — prove leaf execs land on the relay worker#175
pdettori merged 6 commits into
rossoctl:mainfrom
pdettori:feat/st5-relay-leaf-smoke

Conversation

@pdettori

Copy link
Copy Markdown
Member

Automates ST5's live gate

Adds deploy/knative/relay-leaf-smoke.sh: a gated Kind smoke that proves a leaf's tool
calls executed on the remote gRPC worker rather than an in-cluster sandbox pod, plus the
two gaps that blocked anyone from reproducing that by following the docs.

Until now the remote path had no automation at all — nothing under deploy/ referenced
SH_REMOTE_SANDBOX. It had only ever been validated by hand.

Why the assertion is about which backend served

A green leaf run does not prove the remote worker handled it. select-sandbox builds
candidates = [...pods, ...grpcRecs] and leases least-loaded-first, so with idle
in-cluster sandbox pods present a pod wins even when SH_REMOTE_SANDBOX=1. Validating
this by hand, my first remote run returned a correct-looking verdict that had been produced
by an Alpine pod, and it looked like success.

So the gate asserts the backend, using a filesystem discriminator — the sandbox-N pods run
Alpine, the worker image runs RHEL, and the model's stated reason names the OS it read:

Backend pattern: "Alpine" pattern: "Red Hat"
kubectl sandbox pod FLAGGED CLEAR
remote worker CLEAR FLAGGED

Both fingerprints are asserted on the remote path, so a silent landing on a pod fails either
way, with a message saying so rather than a bare verdict mismatch.

The discriminator is verified, not assumed. The script reads /etc/os-release from both
backends and aborts if they no longer differ as expected — otherwise the day someone changes
a base image, this quietly degrades into a tautology that passes while proving nothing.

Negative test. With the pool selector left at its default so a pod wins, the
"Alpine must be CLEAR" assertion fails as designed. A smoke that cannot detect the wrong
backend was the one thing this must not ship as, so that path was exercised deliberately.

What it asserts (6)

  • worker's live Attach appears in Redis presence with transport=grpc
  • the Alpine/RHEL discriminator holds
  • remote: Alpine → CLEAR
  • remote: Red Hat → FLAGGED
  • after restore, control run on the pod path: Alpine → FLAGGED again
  • presence record is gone after teardown

Restore is trap-driven

An EXIT trap restores the harness env and deletes the relay/worker even on assertion
failure or interrupt. Leaving a cluster flipped to the remote path with a pool selector
matching no pods would break every subsequent leaf run — a worse outcome than the smoke
failing. Two mechanical traps are handled: kubectl set env does not work on a Knative
Service (no kind "Service" is registered), and a merge patch that drops the container's
volumeMounts is rejected by Knative's webhook (volume with name "tmp" not mounted).

The two gaps it depends on

  • relay-deployment.yaml omitted SH_RELAY_TOKEN. Relay auth is fail-closed, so
    applying the manifest as shipped produced a relay that rejected every worker Attach. Now
    set to dev-token, matching what worker-example.yaml already hardcodes, with a note that
    production wants a secretKeyRef (ST4 worker: follow-ups deferred during review #173).
  • README-worker.md did not warn about the pool-peer trap. It now states plainly that
    SH_REMOTE_SANDBOX=1 alone does not route execs to a worker, and that a leaf can succeed
    against a pod while looking like a successful remote run.

Verification

Passing run: 6/6 assertions, exit 0. Negative test: fails as required. Cluster verified
restored afterwards — original harness env, no relay/worker, empty presence, and a leaf on
the kubectl path working again.

shellcheck -x -S warning (CI's gate, security-scans.yml:66) is clean. Note plain
pre-commit shellcheck -x reports style findings on this script (6) as it does on the
committed leaf-smoke.sh (10) — the new script is consistent with the existing baseline
rather than stricter or looser.

Scope

Kind only. #88's title claims "Kind → OCP" and the OCP half is untested — running this
against OCP is the remaining work there, along with a recorded decision on req_id
uniqueness (see #89), which is the one contract-level gap I would want a position on before
the epic closes.

Refs #88, #89

…worker

select-sandbox.ts leases least-loaded-first across [...pods, ...grpcRecs], so
an idle in-cluster sandbox pod can still win a lease even with
SH_REMOTE_SANDBOX=1 set and a worker connected — a green leaf run does not by
itself prove which backend served it.

Add relay-leaf-smoke.sh, a RELAY_LIVE_SMOKE=1-gated Kind smoke that forces the
harness's pool selector to match no local pods (so the worker is the only
lease candidate) and fingerprints the OS a leaf's tool call actually ran on:
the in-cluster sandbox pool runs Alpine, the reference worker image runs RHEL
(registry.access.redhat.com/ubi9/ubi-minimal). Both an Alpine-CLEAR and a
Red Hat-FLAGGED assertion are required against the remote path, and the
discriminator itself is validated before being relied on. Restore of the
harness env is trap-driven (EXIT) so an interrupted run can't leave the
cluster flipped with a selector matching nothing.

Also:
- relay-deployment.yaml: set SH_RELAY_TOKEN=dev-token so the relay's
  fail-closed Attach auth actually accepts the worker's matching
  SANDBOX_TOKEN; without it every worker connection is rejected before
  presence is written. Production should use a secretKeyRef (issue rossoctl#173).
- README-worker.md: call out the pool-selection trap this smoke exists to
  catch — SH_REMOTE_SANDBOX=1 alone does not route execs to a worker.

Verified live against kind cluster sh-knative: the smoke passes end to end
(6/6 assertions), and a deliberate variant that leaves the pool selector at
its default value (matching real pods) while flipped reproduces the exact
failure the smoke is designed to catch (Alpine FLAGGED instead of CLEAR),
confirming the gate fails closed on the wrong backend rather than always
passing.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>

@pdettori pdettori left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review (author = reviewer, so this is posted as a COMMENT — GitHub rejects APPROVE/REQUEST_CHANGES on one's own PR). No blocking issues found.

Every technical claim in the description was verified against upstream source, including the subtle one that motivates the whole gate:

Claim Verified
select-sandbox pools pods + workers, least-loaded-first select-sandbox.ts:103 candidates = [...pods, ...grpcRecs.map(r => r.sandboxId)], orderByLoad ascending — a pod really can win the lease under SH_REMOTE_SANDBOX=1
Relay auth is fail-closed main.ts:95 expected !== undefined && token === expected — a missing SH_RELAY_TOKEN rejected every Attach. Real bug, correct fix
Alpine/RHEL discriminator sandbox-pool.yaml = alpine:3.20 (×3); default selector sh.kagenti.io/sandbox-pool=default matches the script's fallback
Token matches the worker worker-example.yaml:48 SANDBOX_TOKEN: dev-token
"transport":"grpc" grep pool-records.ts:32 JSON.stringify(rec) + relay.ts:76 transport: "grpc" — substring matches exactly
shellcheck -x -S warning is CI's gate security-scans.yml: find deploy/ -name '*.sh' -exec shellcheck -x -S warning — covers the new script; job passed
Assertions accumulate under set -e lib.sh:36-37ok/ko both end in plain assignments (exit 0), so a failed assertion does not abort the run

The negative test and the verify-the-discriminator-before-relying-on-it step are the right instincts — a smoke that can't detect the wrong backend would have been worse than no smoke.

The four findings are all about the durability of the two fixes, not the smoke itself: the token fix has no CI-visible guard, its blast radius includes OCP, and the restore trap can be disarmed by exactly the patch failures the PR documents.

On the open scope question: agree Kind-only is the right boundary here. Finding #2 is the one that interacts with the OCP half of #88, so it's worth settling alongside req_id uniqueness (#89) before the epic closes.

Areas reviewed: Shell, YAML/K8s manifests, Docs, CI config, Security, Tests
Agent/IDE config (.claude/.vscode): none
Commits: 1, signed-off: yes
CI: passing (10/10, incl. trivy-scan)

Assisted-By: Claude Code

# hardcoded dev value is fine for kind/local dev; production wants a
# secretKeyRef instead of a literal value here (tracked in issue #173).
- name: SH_RELAY_TOKEN
value: dev-token

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion — this fix has no CI-visible guard.

packages/knative-server/test/relay-deployment.test.ts already exists for exactly this manifest, and already carries a regression test for a past bug in the same spirit (the workingDir / #102 follow-up). Nothing in it asserts SH_RELAY_TOKEN.

The only thing currently asserting the token is the new smoke, which is gated behind RELAY_LIVE_SMOKE=1 — and no workflow sets that variable (checked every file under .github/workflows/). So if someone drops this env var again, the relay silently returns to rejecting every worker Attach and CI stays green.

A few lines in the existing test file would close it permanently. Worth noting the real invariant is cross-file — relay SH_RELAY_TOKEN must equal worker-example.yaml's SANDBOX_TOKEN — and right now that only lives in prose comments in both files, so a test is the only place it can actually be enforced:

it("sets SH_RELAY_TOKEN matching the worker example (relay auth is fail-closed)", () => {
  const c = docs().find((o) => o.kind === "Deployment").spec.template.spec.containers[0];
  const token = c.env.find((e) => e.name === "SH_RELAY_TOKEN");
  expect(token?.value).toBeTruthy();
  const worker = parse(readFileSync(resolve(DEPLOY, "worker-example.yaml"), "utf8"));
  const wEnv = worker.spec.template.spec.containers[0].env;
  expect(token.value).toBe(wEnv.find((e) => e.name === "SANDBOX_TOKEN").value);
});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9ac340d. relay-deployment.test.ts is now 4/4, and the new test asserts the
cross-file invariant rather than just presence — relay SH_RELAY_TOKEN must equal
worker-example.yaml's SANDBOX_TOKEN.

Verified it can fail, since a guard that can't is worse than none: removing the env from the
manifest gives

AssertionError: relay auth is fail-closed ...: expected undefined to be truthy

then 4/4 green again after restoring.

Your framing was the useful part. I had treated "the smoke asserts it" as coverage, but the
smoke is gated behind RELAY_LIVE_SMOKE=1 and no workflow sets it — I checked, and you were
right — so the token had no CI-visible guard at all. And the invariant being cross-file means
prose comments in two manifests could never have enforced it.

# MUST equal the worker's SANDBOX_TOKEN (see worker-example.yaml). This
# hardcoded dev value is fine for kind/local dev; production wants a
# secretKeyRef instead of a literal value here (tracked in issue #173).
- name: SH_RELAY_TOKEN

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion — "fine for kind/local dev" understates the blast radius: this manifest is not Kind-only.

overlays/ocp/kustomization.yaml lists ../../relay-deployment.yaml in resources, and its only patches: entry targets kind: Sandbox — there is no relay patch. So dev-token ships to OpenShift through setup-ocp.sh as well.

This is not a regression in posture — before this change the relay rejected every Attach on OCP too — but it does flip a committed, world-readable string into a valid credential on OCP-deployed relays. The relay Service is ClusterIP h2c, so exploitation needs in-cluster network access, which keeps it out of must-fix territory.

Two options, either is fine: narrow this comment to say the OCP overlay inherits the literal and that #173 is a prerequisite for any non-dev OCP worker use, or add an overlays/ocp patch swapping in a secretKeyRef now. Given #88's OCP half is still open, calling it out here means it can't be missed when that work lands.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected in 31fb80c. You were right and my comment was misleading — I verified it:
deploy/knative/kustomization.yaml:17 includes the manifest, and
overlays/ocp/kustomization.yaml:31 includes ../../relay-deployment.yaml while its
patches: block targets only kind: Sandbox. So dev-token does ship to OpenShift.

The comment now names the overlay path, says plainly that the literal is inherited there and
is therefore a valid credential on any OCP-deployed relay, and marks #173 (secretKeyRef)
as a prerequisite for non-dev OCP worker use. "Fine for kind/local dev" was simply wrong.

I took the narrow option rather than adding an overlays/ocp patch now: a secretKeyRef
needs a Secret created and managed in setup-ocp.sh, which is #88's OCP half and could break
that flow as a drive-by. Your point about #88's OCP work still being open is exactly why the
comment needed to be accurate — it is the thing that will be read when that work lands.

Comment thread deploy/knative/relay-leaf-smoke.sh Outdated
-p "[{\"op\":\"replace\",\"path\":\"/spec/template/spec/containers/0/env\",\"value\":$HARNESS_ENV_SNAPSHOT}]" \
>/dev/null 2>&1 || echo "WARN: restore patch failed (see kubectl get ksvc/$KSVC -o json manually)" >&2
wait_ksvc_ready
HARNESS_FLIPPED=0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestionHARNESS_FLIPPED=0 runs even when the restore patch failed, which disarms the EXIT trap precisely when it's needed.

Line 64 only warns on failure, then execution falls through to line 66 and clears the flag. The trap's later restore_harness_env call then returns immediately at the line 59 guard, so there is no second attempt — the cluster stays flipped to the remote path with a pool selector matching no pods.

That's the outcome the file header singles out as "the worst outcome this script could produce", and the PR description documents two mechanical cases where Knative rejects patches on this very resource — so a failing patch here is a demonstrated possibility, not a hypothetical.

Clearing the flag only on success keeps the trap armed for a retry:

if kubectl patch ksvc "$KSVC" -n "$NS" --type=json \
     -p "[{\"op\":\"replace\",\"path\":\"/spec/template/spec/containers/0/env\",\"value\":$HARNESS_ENV_SNAPSHOT}]" \
     >/dev/null 2>&1; then
  wait_ksvc_ready
  HARNESS_FLIPPED=0
else
  echo "WARN: restore patch failed; leaving HARNESS_FLIPPED set so the EXIT trap retries" >&2
fi

Since the function is documented as idempotent and is called from both step 9 and the trap, a retry costs nothing when the first attempt already succeeded.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b2aef52. The flag now clears only on a successful patch, and the failure branch
says explicitly that it is leaving HARNESS_FLIPPED set so the trap retries.

You picked the one bug in here that actually matters. Everything else in this PR fails
loudly; this one fails silently in the direction of leaving the cluster broken — and it
disarms the very mechanism the file header calls the worst outcome to avoid. That the PR
itself documents two Knative patch rejections on this exact resource makes it a demonstrated
path, not a hypothetical one.

body=$(jq -nc --arg s "$sid" --arg m "$MODEL" --arg p "$pat" \
'{sessionId:$s, model:$m, item:{item_id:"i1", file:"/etc/os-release", pattern:$p}}')
# shellcheck disable=SC2086 # CURL_OPTS is intentionally word-split
curl -s $CURL_OPTS --max-time 120 ${CURL_HDR[@]+"${CURL_HDR[@]}"} \

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — with set -e, a connection-level curl failure (timeout, connection refused) exits the script during the resp_alpine="$(dispatch_pattern ...)" assignment, before assert_verdict can print its "likely the model endpoint is unreachable; check the llm-credentials secret / SH_MODEL" hint.

That branch isn't dead — it still fires when the endpoint answers with an empty or non-JSON body (curl exits 0) — it just gets bypassed in the one scenario its message names, leaving a bare set -e exit instead.

This mirrors leaf-smoke.sh's dispatch_item, which is unguarded the same way, so it's consistent with the existing baseline rather than a new problem. If you want the hint reachable, || true on the curl is enough.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c080c11|| true on the curl, so a connection-level failure yields an empty body
and lands in the existing hint path instead of a bare set -e exit.

Worth taking despite being a nit: the hint names "the model endpoint is unreachable" as the
likely cause, and an unreachable endpoint was precisely the case that bypassed it. A
diagnostic that goes missing in the scenario it was written for is worse than not having one.

Noted that leaf-smoke.sh's dispatch_item is unguarded the same way. Left alone here to
keep this PR scoped, but it is the same one-token fix if someone wants it.

…fails

restore_harness_env() warned on a failed kubectl patch but then
unconditionally cleared HARNESS_FLIPPED, so the EXIT trap's later call
returned immediately at the guard -- there was no second attempt. A failed
restore left the cluster flipped to the remote path with a pool selector
matching no pods, breaking every subsequent leaf run: the worst outcome this
script's own header calls out. Only clear HARNESS_FLIPPED on a successful
patch so the trap can retry; the function is documented idempotent, so a
retry after an already-successful restore is a no-op.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Under set -e, a connection-level curl failure (timeout, connection refused)
inside dispatch_pattern exited the script during the response-capturing
assignment, before assert_verdict's "likely the model endpoint is
unreachable; check the llm-credentials secret / SH_MODEL" hint could print.
Add `|| true` so a connection failure yields an empty body and falls through
to that existing hint path instead of a bare set -e exit.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
relay-deployment.yaml's SH_RELAY_TOKEN and worker-example.yaml's
SANDBOX_TOKEN must be equal, or the relay's fail-closed auth rejects every
worker Attach. That invariant only lived in prose comments in both files, so
nothing caught a regression (e.g. the env var being dropped again) and CI
would stay green. Add a cross-file assertion to the existing
relay-deployment.yaml test suite, reusing its docs()/DEPLOY/parse
conventions, with a failure message that explains the fail-closed
consequence rather than just the value mismatch.

Verified the new test fails (AssertionError: relay auth is fail-closed ...:
expected undefined to be truthy) when SH_RELAY_TOKEN is removed from
relay-deployment.yaml, then restored the manifest and reconfirmed green.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
The comment implied the hardcoded dev-token value only mattered for
Kind/local dev. In fact relay-deployment.yaml is included, unpatched, by the
OCP overlay (overlays/ocp/kustomization.yaml) too -- its patches: block only
targets kind: Sandbox, with no relay patch -- so the literal is also a valid
credential on any OCP-deployed relay. This is not a new regression (the
relay rejected every Attach on OCP before this PR too), but the comment's
wording was misleading about scope. State plainly that secretKeyRef
(issue rossoctl#173) is a prerequisite for any non-dev OCP worker use.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…OKEN test

vitest doesn't typecheck, so "tests pass" wasn't evidence the file compiled
under `tsc --noEmit` (a separate CI step). docs()'s .toJS() returns `any`,
and the existing tests get away with `all.find((o) => ...)` because `all` is
typed as an array, which contextually types the callback param. The new
test's chain (docs().find(...).spec...containers[0].env.find((e) => ...))
runs through `any` end to end, so `.find((e) => ...)` had a genuinely
implicit `any` parameter, which noImplicitAny rejects (TS7006).

Add a local `EnvVar` type and annotate both env arrays so the `.find`
callbacks are contextually typed instead of implicit-any. That documents the
manifest's expected shape and, in turn, makes both `.find()` results
possibly-undefined per strictNullChecks; non-null-assert them at the two
access sites already implied to be defined by the truthy check two lines
above (same runtime behavior as before, now explicit).

Verified:
  cd packages/knative-server && pnpm exec tsc --noEmit 2>&1 | grep relay-deployment
produces no output (the full tsc run still has unrelated pre-existing
errors from this worktree's unbuilt pi-fork submodule).
pnpm --filter @sh/knative-server test -- relay-deployment: still 4/4 pass.
Re-confirmed the test still fails when SH_RELAY_TOKEN is removed from
relay-deployment.yaml (same AssertionError as before), then restored the
manifest.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
@pdettori
pdettori merged commit 6297dcd into rossoctl:main Aug 27, 2026
10 checks passed
@pdettori
pdettori deleted the feat/st5-relay-leaf-smoke branch August 27, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant