Contract-freeze · UX approval · FF-EPIC-19 · FFRNT-280

Configuration Management Console · Approval Frames

FF-EPIC-17 and FF-EPIC-18 deliver a complete configuration backend reachable only by curl. A config service with no editor delivers none of its value — its entire purpose is to let non-engineers change settings. These frames freeze that console: a settings editor that says where every value came from, a key catalog that says what every key is, and the secret and audit surfaces that make changes safe and attributable.

The hard part here is not the inputs. It is communicating why a setting has its value and why some settings cannot be changed — invisible in a plain form. Walk each frame; approve per flow.

Flow 1 · Settings editor · route /config · ConfigSettingsEditorFlow
Frame (a) · /config

Settings editor

Scope switcher, namespace, typed inputs per valueType, and a provenance badge on every row: set here / inherited from / locked by. Atomic save with expectedVersion.

Frame (b) · /config

Provenance, lock & unset-vs-pin

Six entry shapes: set-here, inherited, locked-by-ancestor naming the ancestor, readonly, scope-not-allowed, default — plus unset vs pin-the-parent's-value as two distinct operations.

Frame (c) · /config

Editor states

Loading, empty namespace, load error, read-only scope (fail-closed, no save bar), and system-hidden absent entirely — omitted by the API, never filtered client-side.

Frame (d) · /config

Save failures

VERSION_CONFLICT → re-read and merge (never blind retry, no force-save), per-field VALIDATION_ERROR, LOCKED_BY_ANCESTOR mid-edit, and stale value vs updated definition.

Flow 2 · Key catalog · route /admin/config/catalog · ConfigKeyCatalogFlow
Frame (e) · /admin/config/catalog

Key catalog

Every KeyDefinition in a namespace: type, allowed scopes, system / hidden / secret / readonly / deprecated flags, defaults. The only surface where hidden keys are visible.

Frame (f) · /admin/config/catalog/:key

Definition & diff-against-parent

One definition plus the full resolution chain — which scope wins and why, the no-divergence case, and a chain terminated by a lock with the scopes beneath marked unreachable.

Frame (g) · /admin/config/catalog

Catalog states

Loading, no namespaces vs namespace with no keys vs no search results (three different meanings), error, and non-platform-admin refused before any data loads.

Flow 3 · Secrets & audit · route /admin/config/keys/:key/history · ConfigSecretAuditFlow
Frame (h) · /config (secret keys)

Secret values

Write-only: isSet not value, no show-toggle, replace-only, reveal-once (separately authorized and audited), SECRET_UNAVAILABLE ≠ unset, and may-set-but-not-reveal.

Frame (i) · /admin/config/keys/:key/history

Audit history & revert

Append-only trail with actor, scope and reason. Revert appends, never rewinds. Secret history redacted both sides. Empty / loading / error, and error ≠ forbidden.

Build inventory

Approving a flow approves its component/package plan and its contract binding — implementation cannot quietly invent a different architecture. Mirrored in manifest.json → build. Approval is per flow: the editor (Flow 1) can be approved and built while the catalog (Flow 2) and the secret/audit surfaces (Flow 3) still iterate.

ConfigSettingsEditorFlow

route /config · flag fuzefront.config.management-console · approved: false
contract: @fuzefront/config-client (REAL) — GET /v1/config, PUT /v1/config, GET /v1/namespaces → @fuzefront/config-ui
ConfigConsoleShellScopeSwitcherScopeChainStepNamespacePicker SettingsEditorFormSettingCategoryGroupSettingRow TypedValueInputEnumValueSelectBooleanValueToggle NumberValueInputDurationValueInputJsonValueEditor UrlValueInputColorValueInput ProvenanceBadgeLockedByAncestorNoticeInheritedFromNotice ResetValueMenuUnsetOperationOptionPinParentValueOption RequiresRestartNoticeStaleValueWarningScopeNotAllowedNotice ReadOnlyScopeNoticeSaveBarDirtyChangeCounterChangeReasonField FieldValidationErrorVersionConflictResolverConflictRow ConfigSkeletonEmptyNamespaceStateConfigLoadError

ConfigKeyCatalogFlow

route /admin/config/catalog · flag fuzefront.config.key-catalog · approved: false
contract: @fuzefront/config-client (REAL) — GET /v1/namespaces, GET /v1/namespaces/{ns}/keys, GET /v1/namespaces/{ns}/keys/{key} → @fuzefront/config-ui
KeyCatalogTableKeyCatalogFiltersKeyCatalogSearchKeyDefinitionRow ValueTypeBadgeAllowedScopesListKeyFlagBadges KeyDefinitionDetailDefinitionFactsTable ResolutionChainViewResolutionChainStepNoDivergenceNotice LockTerminatedChainNoticeCatalogPagination EmptyNamespacesStateEmptyKeysStateNoCatalogResultsState PlatformAdminOnlyNotice

ConfigSecretAuditFlow

route /admin/config/keys/:key/history · flag fuzefront.config.secrets-audit · approved: false
contract: @fuzefront/config-client (PARTLY ANTICIPATED — reveal + history/revert commissioned by these frames, FF-EPIC-18-S1/S2/S3) → @fuzefront/config-ui
SecretValueFieldSecretSetIndicatorSecretReplaceInput RevealSecretDialogRevealOnceValueSecretUnavailableNotice AuditHistoryTableAuditEntryRowAuditOperationBadge RedactedValueCellAuditActorCell RevertToValueDialogRevertSemanticsNotice EmptyHistoryStateHistoryLoadError

npm packages

  • @fuzefront/config-ui (new)
  • @fuzefront/config-client (existing — consumed, frozen by FF-EPIC-17-S1)
  • @fuzefront/design-system (existing — composed from)

DS primitives needed

  • DataTable — catalog and history tables
  • StatusPill — provenance, flags, operations
  • SearchInput — debounced search field
  • Switch — boolean settings
  • Skeleton — loading states
  • Banner — error / warn / info / ok blocks
  • Dialog — reveal, revert, conflict resolution
  • Breadcrumb — the scope chain
  • CodeField — JSON / duration monospace input
● Awaiting approval — per flow (Flow 1 / 2 / 3)

Design system: @fuzefront/design-system (fuse-seam, tokens only) · Epic: FF-EPIC-19 (Configuration Management UI) · Story: FF-EPIC-19-S1 / FFRNT-280.
Contracts. Flows 1 and 2 bind to the real, frozen services/config-service/openapi.yaml via @fuzefront/config-client — GET /v1/namespaces, GET /v1/namespaces/{ns}/keys, GET /v1/namespaces/{ns}/keys/{key}, GET /v1/config, PUT /v1/config. Flow 3 is partly anticipated: secret reveal and the history / revert surfaces are FF-EPIC-18-S1/S2/S3 and are not in the frozen contract today — these frames commission them, and bind revert to the existing set/unset operations rather than inventing a delete.
Provenance is the UI. Every row renders source, locked, lockedBy and editable — a build that reads only value produces a form that looks right and misrepresents its own contents.
Hidden means absent. The editor performs no client-side filtering and must have no code path that could render an isHidden key. QA asserts on the network payload, not on the absence of a rendered row.
The UI is not the authorization boundary. A disabled input reflects editable: false; the server refuses the write regardless. The four reasons a control is disabled — locked, readonly, scope-not-allowed, no Permit grant — are visually distinct, because the user's next action differs.
DS primitives listed above are not all in @fuzefront/design-system today. frontend-engineer (sole editor of design-system/) adds them as foundation PRs; these frames compose them from tokens only in the meantime.
Approve per-flow by setting approved: true on the flow in manifest.json, or reply @claude approve <flow> / @claude reject: <reason>.

Review · config-management approve or reject each flow ↓
settings-editorapproved · izzywdev
key-catalogapproved · izzywdev
secret-auditapproved · izzywdev