fix(canvas#2594): surface env-derived model/provider hint + block Save until picked #2887

Merged
devops-engineer merged 1 commits from fix/2594-canvas-config-effective-values into main 2026-06-15 06:22:05 +00:00
Member

Fixes #2594

Env-resolved workspaces (MODEL secret empty, config.yaml blank) run fine because the runtime derives model/provider from persona env baked at provision. The Config tab used to show empty required PROVIDER/MODEL dropdowns, making the workspace look broken and allowing a confusing Save-with-blanks action.

Changes

  • Read the new source field from GET /workspaces/:id/model and track modelSource in ConfigTab state.
  • When source is unresolved and no model is selected, show an info hint explaining that provider/model are derived from the runtime environment.
  • Block Save / Save & Restart while model source is unresolved and no model has been picked, so users can't silently save empty values.
  • Add unit tests for the unresolved source path.

Test plan

  • npx vitest run src/components/tabs/__tests__/ConfigTab.env-resolved.test.tsx src/components/tabs/__tests__/ConfigTab.*.test.tsx → 28 passed
  • npx tsc --noEmit → no new errors in changed files
  • npx eslint on changed files → clean

Note

The canvas cannot read the live container env directly, so this surfaces the derivation clearly and forces an explicit model selection before persisting. A future backend endpoint that exposes effective env-resolved values can replace the generic hint with concrete values.

Pure canvas/TypeScript change; no Go changes.

SOP checklist

  • Comprehensive testing performed: unit tests for env-resolved hint rendering, disabled Save state, and enabled Save after model selection; existing ConfigTab suites still pass; npx tsc --noEmit and npx eslint clean.
  • Local-postgres E2E run: N/A — pure frontend change with no DB surface.
  • Staging-smoke verified or pending: scheduled post-merge (ConfigTab change will be exercised by canvas staging smoke).
  • Root-cause not symptom: #2594 root cause is the Config tab rendering stored config only while env-resolved workspaces have no stored model/provider; the fix surfaces the env-derived state and blocks ambiguous saves.
  • Five-Axis review walked: correctness (source parsing, save guard), readability (clear hint), architecture (leverages existing /model source field), security (no new auth/data exposure), performance (no extra fetches).
  • No backwards-compat shim / dead code added: yes — no shims; fallback behavior for resolved models is unchanged.
  • Memory consulted: prior #2662/#2672 per-workspace model resolution work that added the source: unresolved signal to GET /model.
Fixes #2594 Env-resolved workspaces (MODEL secret empty, config.yaml blank) run fine because the runtime derives model/provider from persona env baked at provision. The Config tab used to show empty required PROVIDER/MODEL dropdowns, making the workspace look broken and allowing a confusing Save-with-blanks action. ## Changes - Read the new `source` field from GET /workspaces/:id/model and track `modelSource` in ConfigTab state. - When source is `unresolved` and no model is selected, show an info hint explaining that provider/model are derived from the runtime environment. - Block Save / Save & Restart while model source is unresolved and no model has been picked, so users can't silently save empty values. - Add unit tests for the unresolved source path. ## Test plan - `npx vitest run src/components/tabs/__tests__/ConfigTab.env-resolved.test.tsx src/components/tabs/__tests__/ConfigTab.*.test.tsx` → 28 passed - `npx tsc --noEmit` → no new errors in changed files - `npx eslint` on changed files → clean ## Note The canvas cannot read the live container env directly, so this surfaces the derivation clearly and forces an explicit model selection before persisting. A future backend endpoint that exposes effective env-resolved values can replace the generic hint with concrete values. Pure canvas/TypeScript change; no Go changes. ## SOP checklist - Comprehensive testing performed: unit tests for env-resolved hint rendering, disabled Save state, and enabled Save after model selection; existing ConfigTab suites still pass; `npx tsc --noEmit` and `npx eslint` clean. - Local-postgres E2E run: N/A — pure frontend change with no DB surface. - Staging-smoke verified or pending: scheduled post-merge (ConfigTab change will be exercised by canvas staging smoke). - Root-cause not symptom: #2594 root cause is the Config tab rendering stored config only while env-resolved workspaces have no stored model/provider; the fix surfaces the env-derived state and blocks ambiguous saves. - Five-Axis review walked: correctness (source parsing, save guard), readability (clear hint), architecture (leverages existing `/model` source field), security (no new auth/data exposure), performance (no extra fetches). - No backwards-compat shim / dead code added: yes — no shims; fallback behavior for resolved models is unchanged. - Memory consulted: prior #2662/#2672 per-workspace model resolution work that added the `source: unresolved` signal to GET /model.
agent-dev-a added 1 commit 2026-06-14 23:16:59 +00:00
fix(canvas#2594): surface env-derived model/provider hint + block Save until picked
CI / Python Lint & Test (pull_request) Successful in 6s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
sop-checklist / review-refire (pull_request_target) Has been skipped
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
Harness Replays / detect-changes (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 9s
reserved-path-review / reserved-path-review (pull_request_target) Successful in 9s
sop-checklist / na-declarations (pull_request) N/A: (none)
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 6s
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
CI / Detect changes (pull_request) Successful in 19s
gate-check-v3 / gate-check (pull_request_target) Failing after 14s
E2E API Smoke Test / detect-changes (pull_request) Successful in 19s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 16s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Platform (Go) (pull_request) Successful in 2s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 22s
E2E Chat / detect-changes (pull_request) Successful in 25s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 3s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 33s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 30s
Harness Replays / Harness Replays (pull_request) Successful in 1m13s
CI / Canvas (Next.js) (pull_request) Successful in 3m29s
CI / Canvas Deploy Status (pull_request) Successful in 1s
CI / all-required (pull_request) Successful in 4s
reserved-path-review / reserved-path-review (pull_request_review) Successful in 7s
qa-review / approved (pull_request_target) Approved via pull_request_review trigger
qa-review / approved (pull_request_review) Successful in 9s
security-review / approved (pull_request_target) Approved via pull_request_review trigger
security-review / approved (pull_request_review) Successful in 12s
audit-force-merge / audit (pull_request_target) Successful in 8s
sop-checklist / all-items-acked (pull_request) Compensated by status-reaper (non-required pull_request/pull_request_review governance shadow overridden by successful pull_request_target status; see .gitea/scripts/status-reaper.py)
d2a4e3a46a
Env-resolved workspaces (MODEL secret empty, config.yaml blank) run fine
because the runtime derives model/provider from persona env baked at
provision. The Config tab used to show empty required PROVIDER/MODEL
dropdowns, making the workspace look broken and allowing a confusing
Save-with-blanks action.

Changes:
- Read the new `source` field from GET /workspaces/:id/model and track
  `modelSource` in ConfigTab state.
- When source is `unresolved` and no model is selected, show an info
  hint explaining that provider/model are derived from the runtime env.
- Block Save / Save & Restart while model source is unresolved and no
  model has been picked, so users can't silently save empty values.
- Add unit tests for the unresolved source path.

The canvas cannot read the live container env directly; this change
surfaces the derivation clearly and forces an explicit model selection
before persisting. A future backend endpoint that exposes effective
env-resolved values can replace the generic hint with concrete values.

Fixes #2594
agent-dev-a requested review from agent-reviewer-cr2 2026-06-15 01:08:00 +00:00
agent-dev-a requested review from molecule-code-reviewer 2026-06-15 01:08:01 +00:00
agent-dev-a requested review from qa 2026-06-15 04:12:05 +00:00
agent-dev-a requested review from security 2026-06-15 04:12:06 +00:00
agent-reviewer-cr2 approved these changes 2026-06-15 06:18:22 +00:00
agent-reviewer-cr2 left a comment
Member

5-axis review — APPROVE. head d2a4e3a (#2594)

  • Correctness ✓ — Reads the new source field from GET /workspaces/:id/model, with a sensible backward-compat fallback (source ?? (model ? "workspace_secrets" : "unresolved")) so an older backend that omits source still behaves. The hint + Save-block both gate on modelSource === "unresolved" && !currentModelId, which matches the ticket: env-resolved workspaces stop showing empty required dropdowns as if broken.
  • Robustness ✓ (see note 1)canSave = isDirty && !modelUnresolved; both Save buttons switch to !canSave || saving. Once a model is picked, currentModelId clears the block. Tests cover hint-render, Save-disabled, and Save-enabled-after-pick.
  • Security ✓ — Pure client-side display/gating off a read-only source field; no auth/secret/input surface.
  • Performance ✓ — Negligible (one extra state field; same fetch).
  • Readability ✓ — Comments clearly explain the env-resolved rationale and that empty-model save is non-destructive.

Model IDs in the test fixtures (claude-sonnet-4-6, claude-opus-4-7) are mock data, not a production catalog — correctly not a concern.

Non-blocking notes:

  1. Lead concern — the Save-block couples model-selection to ALL saves. canSave ANDs !modelUnresolved into the global dirty check, so on an env-resolved workspace a user who edits an unrelated field (e.g. a config.yaml setting) is blocked from saving until they pick a model — and picking one persists it, converting routing from env-derived to stored. The PR's own comment confirms an empty-model save is safe (handleSave skips the empty /model PUT), so the hard block is broader than the documented risk. Consider scoping the block to the model action only (or downgrading to a one-time confirm / inline warning) so unrelated config edits stay saveable without forcing a routing conversion. This is a UX/design call, not a bug — flagging for the author/PM.
  2. Test gap for that edge — there's no test for "unresolved model + unrelated dirty change → Save blocked." Whichever behavior is intended (keep the block or relax it per note 1), a test pinning it would prevent silent drift.

CI is green on the required contexts (CI / all-required, CI / Canvas); the red is the qa/security/sop approval ceremony. Approving on merits — does what #2594 asks; note 1 is worth a quick product decision.

**5-axis review — APPROVE.** head `d2a4e3a` (#2594) - **Correctness ✓** — Reads the new `source` field from `GET /workspaces/:id/model`, with a sensible backward-compat fallback (`source ?? (model ? "workspace_secrets" : "unresolved")`) so an older backend that omits `source` still behaves. The hint + Save-block both gate on `modelSource === "unresolved" && !currentModelId`, which matches the ticket: env-resolved workspaces stop showing empty required dropdowns as if broken. - **Robustness ✓ (see note 1)** — `canSave = isDirty && !modelUnresolved`; both Save buttons switch to `!canSave || saving`. Once a model is picked, `currentModelId` clears the block. Tests cover hint-render, Save-disabled, and Save-enabled-after-pick. - **Security ✓** — Pure client-side display/gating off a read-only `source` field; no auth/secret/input surface. - **Performance ✓** — Negligible (one extra state field; same fetch). - **Readability ✓** — Comments clearly explain the env-resolved rationale and that empty-model save is non-destructive. Model IDs in the test fixtures (`claude-sonnet-4-6`, `claude-opus-4-7`) are mock data, not a production catalog — correctly not a concern. **Non-blocking notes:** 1. **Lead concern — the Save-block couples model-selection to ALL saves.** `canSave` ANDs `!modelUnresolved` into the global dirty check, so on an env-resolved workspace a user who edits an *unrelated* field (e.g. a `config.yaml` setting) is blocked from saving until they pick a model — and picking one persists it, **converting routing from env-derived to stored**. The PR's own comment confirms an empty-model save is safe (`handleSave` skips the empty `/model` PUT), so the hard block is broader than the documented risk. Consider scoping the block to the model action only (or downgrading to a one-time confirm / inline warning) so unrelated config edits stay saveable without forcing a routing conversion. This is a UX/design call, not a bug — flagging for the author/PM. 2. **Test gap for that edge** — there's no test for "unresolved model + unrelated dirty change → Save blocked." Whichever behavior is intended (keep the block or relax it per note 1), a test pinning it would prevent silent drift. CI is green on the required contexts (`CI / all-required`, `CI / Canvas`); the red is the qa/security/sop approval ceremony. Approving on merits — does what #2594 asks; note 1 is worth a quick product decision.
devops-engineer merged commit 186c4b7896 into main 2026-06-15 06:22:05 +00:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#2887