Repository navigation
feat: a session's sandbox tier — control plane, exchange, mocactl (P6.3 PR 2, #425) - #444
Conversation
…discovery (rossoctl#425) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
… returned by the exchange (rossoctl#425) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
… (Task 9 review) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ded for its session (rossoctl#425) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…cement (rossoctl#425) projectResources gains a top-level placement (sandboxId, tier, workspaceReset) from the data plane's turn-end report, and session.sandboxTier by the session view's rule (sessionTier: the stored tier, or today's default for a record written before P6.3). The runtime hash allow-list now keeps sandboxId, sandboxTier, workspaceResetAt and workspaceResetFrom: putRuntime dropped them on write, so placement could never have appeared. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
… reports where it ran (rossoctl#425) The turn-end runtime report read the placement from result?.sandbox only, so a turn that fell back to a fresh sandbox and then threw recorded no sandboxId, tier or workspace reset, and the resources view never showed that the session lost its workspace. executeTurn gains onPlacement, called once inside the lease's try as soon as a leased turn has a placement (before the workspace_reset frame), with the shape withPlacement attaches. Both authenticated /turn paths capture it and report result?.sandbox ?? placement. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…time, pre-P6.3 view test (rossoctl#425) - resources: workspaceResetAt goes through num(), so a garbage self-reported time is workspaceReset: null rather than at: NaN (and '' stays absent). - harness: the onPlacement test asserts its order against workspace_reset in one log. - handlers-sessions: the pre-P6.3 view test deletes the field outright and creates the session in a non-default tier, so it can pass only through sessionTier's fallback. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…tier API (rossoctl#425) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ontrol plane; order-free refusal mapping; placement schema (rossoctl#425) Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…t runs in (rossoctl#425 final review) The session and resources views showed null for a record stored with '' (created while no tiers were declared), though the data plane gives such a session the deployment default -- so /resources showed session.sandboxTier null beside placement.tier 'container'. Both views now use viewTier: the stored tier, else today's default, null only on an untiered deployment. sessionTier and the exchange are unchanged. Docs (OpenAPI Session.sandboxTier, spec §0.3 item 4, §3.3, §3.4, §6) say the same; the stale "'' or undefined means no tier filter" bullet now says the data plane applies the default on a tiered deployment. Also drops a comment copied from the harness that is wrong in the control plane's copy of the tier parser. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…nal review
- mocactl: a malformed discovery sandboxTiers (anything but
{ names: string[], default: string }) counts as no tiers instead of
breaking session creation with a TypeError.
- mocactl run: a --option value not among the choices is named as rejected
("unknown sandbox tier 'gpu'; declared: container, microvm"), sanitized,
for any session option field.
- knative-server: runtimeFieldsForTurn writes sandboxTier even as '', so an
untiered turn clears the previous turn's tier in the merged runtime hash
(putRuntime keeps empty strings; pinned by an ownership test).
Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
pdettori
left a comment
There was a problem hiding this comment.
Summary
This connects PR 1's data-plane tier filtering to sessions cleanly. The tier is validated and recorded once at creation, carried through the exchange, and shown in the session, resources and mocactl views. Things I checked that hold up:
- The session hash (
sh:cp:session:<id>) and the runtime hash (…:runtime) are separate keys. The new runtimesandboxTierallow-list entry therefore can't overwrite the tier recorded at creation. - The
onPlacementcapture covers turns that throw, on both/turnpaths. - The mocactl resolution edge cases behave correctly: untiered with
--option, a single tier with a wrong value, headless with multiple tiers, and a 404 from discovery.
One nit, outside the diff hunks: docs/api/openapi.yaml:421 still types the resources session as a bare { type: object }. So the new session.sandboxTier isn't documented there, although the PR body says OpenAPI covers everything.
Author: pdettori (MEMBER — maintainer)
Areas reviewed: TypeScript (control-plane, knative-server, harness, mocactl), OpenAPI/docs, tests
Agent/IDE config (.claude/.vscode): none
Commits: 11 commits, all signed off: yes
CI status: passing (14/14)
No blocking issues. Posted as COMMENT only because GitHub doesn't allow an author to approve their own PR.
Assisted-By: Claude Code
| * field at all and gets TODAY's default: what it would have been given had it been created now. | ||
| */ | ||
| export function sessionTier(rec: SessionRecord, tiers: CpConfig['sandboxTiers']): string { | ||
| return rec.sandboxTier ?? tiers?.default ?? ''; |
There was a problem hiding this comment.
suggestion: A record stored as '' makes this function (??) omit the tier, so the data plane falls back to its own SH_SANDBOX_DEFAULT_TIER. Meanwhile viewTier (||) shows the control plane's default. The view only matches the actual placement if both processes read the same default.
Using || here as well would keep the untiered response byte-identical (when tiers is null this still returns ''). It would also make the exchange the one source of truth for '' sessions, and sessionTier and viewTier would differ only in returning '' vs null. Turn behaviour wouldn't change: '' sessions would still follow the current default on every turn.
Leaf paths still read the data-plane env, so PR 3 must wire both sides from one value regardless. This just removes one place where they can disagree.
There was a problem hiding this comment.
Fixed in 12a65d9. sessionTier is now rec.sandboxTier || tiers?.default || '', so for a session stored as '' on a tiered deployment the exchange names today's default instead of leaving it to the data plane's SH_SANDBOX_DEFAULT_TIER.
viewTieris nowsessionTier(rec, tiers) || null, so the two differ only in''vsnull.- Untiered, it still returns
'', so the exchange response is unchanged (the existing stricttoEqualstill pins it). - Added an exchange test: a session created untiered, then exchanged on a tiered deployment, gets
sandboxTier: 'microvm'. A mutation back to??fails it and two unit tests. - Updated the
sessionTier,ExchangeResponseandSessionRecordcomments, the OpenAPI/internal/credentialsdescription, and spec §0.3 item 4, §3.3 and §3.4. The §3.4 fallback now covers only the unauthenticated and leaf paths, which, as you note, PR 3 must wire from the same value.
… recorded no tier; document the resources session (rossoctl#444 review) sessionTier now treats a stored '' like a pre-P6.3 record: on a tiered deployment the exchange names today's default instead of leaving the data plane to apply its own SH_SANDBOX_DEFAULT_TIER, so the views (viewTier = sessionTier || null) and the placement come from one value. Untiered, it still returns '' and the exchange response stays byte-identical. docs/api/openapi.yaml types the resources response's session object, including sandboxTier, and the /internal/credentials sandboxTier description follows the new rule. Spec §0.3 item 4, §3.3 and §3.4 updated. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
|
Review nit ( Checks on 12a65d9: control-plane 492 passed, knative-server 434 passed, mocactl contract tests passed, |
…nd 11 claims marked historical (rossoctl#454 review) The P6.3 spec's Status is Implemented (PRs rossoctl#442, rossoctl#444 and rossoctl#454; accepted on the KVM rig and OpenShift 4.20.8), its two mocactl --tier mentions are --option sandboxTier=..., and the README's P6.3 row is implemented. The slice-2 spec's 'both tiers together are refused' carries the P6.3 superseded pointer, and its '11 claims' is the count of slice 2's time (P6.3 adds claim 12). Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Summary
PR 2 of 3 for #425 (P6.3, slice 3 of epic #426). The control plane now records a session's sandbox tier, the exchange returns it, and the turn runs in that tier. PR 1 (#442) built the data-plane side: tier filtering and session-to-sandbox affinity in
selectPoolSandbox. This PR connects it to sessions. PR 3 adds deployment wiring and acceptance.Spec:
docs/specs/2026-10-04-p6-on-kubernetes-slice3-design.md, now at v1.3. §0.3 records the corrections this PR made.What changed
SH_SANDBOX_TIERSandSH_SANDBOX_DEFAULT_TIERare parsed at boot. An invalid list or default refuses to start.packages/control-plane/src/sandbox-tiers.tsis a copy of the harness parser. The same fixture (harness/test/fixtures/sandbox-tiers-cases.json) tests both copies.GET /v1/discoveryreturnssandboxTiers: { names, default } | null.POST /v1/sessionsacceptssandbox: { tier }. If the tier is omitted, the deployment default is recorded. Changing the default later never moves an existing session to another tier.invalid_requestthat names the declared tiers:sandboxis not an object; the tier is not a string; the tier is undeclared; or the deployment declares no tiers but the request names one.sandboxTier, the tier the session actually runs in: the recorded tier, or the deployment default. The default applies to a session recorded with''(created while the deployment was untiered) and to a record written before P6.3. Such sessions follow the deployment default on every turn, so a later change to the default does move them. A session created with tiers declared never moves./internal/credentialsreturnssandboxTieronly when it is non-empty.TurnAuth.sandboxTierholds the tier, andbuildConfigpasses it on asTurnConfig.sandboxTier. Only a non-empty string from the exchange is trusted.GET /v1/sessions/{id}/resourcesgainssession.sandboxTierand a top-levelplacement: { sandboxId, tier, workspaceReset: { at, from } | null } | null.sandboxTier: '', so a stale tier never sits beside a newsandboxId.ExecuteTurnInput.onPlacementcallback reports it, and both/turnpaths use it.sandboxTiersession option. It is available in the New Session overlay, in presets, and asrun --new --option sandboxTier=<name>. The server default is preselected.run --new, an omitted tier is left to the server, not refused.refusalCode), not by the order of the options.Compatibility
SH_SANDBOX_TIERSunset:sandboxTiers: null;sandboxTier: null;sandboxTiers, or returns a 404, there is no tier option.Test results
packages/knative-server/test/run-leaf-route.test.ts› "400s on a malformed envelope" failed once locally (it got a 404) and passed on every rerun. This branch does not touch that file, so it looks like an existing flaky test.🤖 Generated with Claude Code