feat(console): the settings save renders the crypto-unavailable refusal as a first-class state (#4570) - #4579
Merged
Conversation
…al as a first-class state (#4570) A deployment that cannot encrypt a declared-secret setting refuses the write, and since objectstack#8396 it says so in its own envelope: SETTINGS_CRYPTO_UNAVAILABLE, with error.details locating the refused { namespace, key } and error.message carrying the operator prescription. SettingsView read none of it. The code fell through to the generic error path, where extractFieldErrors finds no details.fields array and returns null, so nothing was marked and the refusal collapsed into one transient "save failed" toast — the admin was told the save failed, while which key was refused and that the DEPLOYMENT cannot encrypt were on the wire and discarded. It now branches on the code the way it already does for SETTINGS_LOCKED: the refused key is named as namespace.key from the declared error.details slot, and the server's prescription renders verbatim in a persistent panel. The console frames the refusal but never restates how to fix it — the server owns that copy. The value is never rendered: the envelope deliberately does not carry the secret, and the console does not re-introduce it from the draft it holds. The draft survives so the value is not lost while the deployment is reconfigured, and the refusal clears only when its claim can have become false — a new save attempt, a successful save, a discard, or a reload. Notably it does NOT clear on editing the key: that is a field-error semantic, and typing does not make a deployment able to encrypt. SETTINGS_LOCKED and SETTINGS_VALIDATION are byte-untouched, and an unrecognized code still takes the generic path — all three pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4570
A deployment with nothing able to encrypt a declared-secret setting refuses the write. Since objectstack#8396 the framework says so in its own wire envelope — status 500,
error.codeSETTINGS_CRYPTO_UNAVAILABLE,error.detailslocating the refused{ namespace, key }, anderror.messagecarrying the operator prescription.The console read none of it.
SETTINGS_CRYPTO_UNAVAILABLEfell through to the generic error path, whereextractFieldErrorsfinds nodetails.fieldsarray and returns null — so nothing was marked, nothing was rendered, and the whole refusal collapsed into one transient "save failed" toast. The admin was told the save failed; that the deployment cannot encrypt, and which key it refused, was on the wire and thrown away.The shape, mirrored from the SETTINGS_LOCKED precedent
Measured first, then mirrored (SettingsView.tsx ~:152): detect on
error.code, locate the subject from the declarederror.detailsslot, frame it in the console's own copy.namespace.key, read fromerror.details.Locked by environment: KEYwith a code-specificCannot encrypt secrets: ai.api_key.Red-first, with the wire envelope as the fixture
Predictions were written before the first source edit. Pre-fix baseline, measured on the unmodified view:
Tests 5 failed | 3 passed (8), every failure the same cause —Unable to find an element with the text: /cannot encrypt secrets/i. Post-fix: 8 passed.Reverse verification removed only the source fix via
git checkout origin/main -- SettingsView.tsx(nevergit stash), keeping the tests:5 failed | 36 passed (41)— the 5 red are exactly the new cases, and the other four settings test files stayed green. Restored and verified byte-identical by sha256 (141e4108...).One honest correction to the written prediction: the never-the-value case was predicted green-and-vacuous, on the assumption it would be a document-wide absence check that trivially holds when no panel exists. It was instead written to await the panel first and then read only that panel's
textContent, which makes it impossible to pass vacuously — so it joins the red set pre-fix. Stronger construction, same underlying reasoning about the pre-fix DOM.Pins
lucide-reactimport, re-added withShieldAlert. Everything else is pure insertion. Both sub-blocks were hash-compared againstorigin/mainwith identical anchors and are identical: LOCKEDd15e7d41..., VALIDATION662cca45....textContent— the secret is legitimately inside the input the admin typed it into, so a document-wide query would assert the wrong thing.Verification
pnpm exec vitest run --maxWorkers=2 apps/console/— 46 files, 520 tests, all green.tsc --noEmit,tsc -b tsconfig.node.json --force) after a dependency-closure build — both exit 0.origin/mainand 6 warnings / 0 errors here; the new test file contributes 0.check:control-bytes,check:phantom-deps,check:i18n-keys,check:i18n-drift, changeset presence and the no-major guard all green. The i18n drift gate confirms 0 locale values changed.Declared deviations from the dispatch's presumptions
@object-ui/console, not@object-ui/app-shell. The card expected an app-shell view and an app-shell patch changeset. There is exactly oneSettingsView.tsxin the repo and it isapps/console/src/pages/settings/SettingsView.tsx; app-shell has no SettingsView and must not be bumped. Changeset names@object-ui/console: patch, and the PR title is scopedconsolefor the same reason.t('console.settingsHub.*'), whose keys live across ten locale packs inpackages/i18n— outside this card's surface. Routing one string through i18n here would leave a single translated string among a dozen hard-coded ones in the same file, drag ten packs into the surface, and walk into the objectui#4514 provider-less trap: these tests mount no I18nProvider, andt()outside a provider renders the raw key.api.tsedit.lockedKeyOflives there because it is a dual-position compat shim (declareddetails.keyplus the pre-objectstack#4224 sibling). This code is new in #8396 with exactly one declared position, so there is nothing to reconcile and the read stays inline in the view.5d24f4b94(#7166), which predates #8396 — it still carries the base64NoopCryptoAdapterwith no refusal at all, so the envelope could not be re-confirmed against framework source and was taken from the issue body, which the dispatch names authoritative.Generated by Claude Code