Skip to content

Release prep v0.14.0: drop full-sync prune, review fixes, consent rule, one draft bar - #242

Merged
windischb merged 4 commits into
developfrom
fix/release-0-14-prep
Sep 24, 2026
Merged

windischb merged 4 commits into
developfrom
fix/release-0-14-prep

Conversation

@windischb

Copy link
Copy Markdown
Contributor

Why

Pre-release review of everything between v0.13.0 (in production) and 0.14.0-beta.9 (#233–#241), followed by a click-through of the admin UI against a dev realm. This PR carries what both turned up, plus the decision to drop full-sync prune before it ships another release.

What changes

Full-sync prune is removed (feat(provisioning)!)

  • ?prune=true is gone from every manifest route: control plane …/realms/{slug}/apply, data plane …/realm-config/plan|apply, and the draft plan/apply endpoints.
  • The two-step confirmation the 0.14 betas carried goes with it: 409 Manifest.ConfirmationRequired, confirmation tokens, parked review drafts, PruneOnApply. None of it was ever in a release.
  • An apply is an additive merge by id. Deleting is a staged deletion in a draft, and the plan shows it before the apply. That works the same from the admin UI and from a Management API client.
  • The plan result drops its Prune field and the protected action.
  • A service-account credential can't be deleted through the clients section. It leaves through its account's Credentials list, which is the only place the plan shows it.

Fixes found in the review

  • Draft endpoints with a bearer token. They read the caller from NameIdentifier, which a Management API bearer doesn't carry, so every call returned 500. They now read sub first.
  • Renaming in a draft. Staging matched the draft entry by natural key only, so a rename appended a second entry with the same id. Staging now matches by Id first.
  • Pinned credential id. A service-account credential that had an Id in the manifest was issued under a random id. Applying the same file a second time then failed with ClientIdAlreadyExists.
  • Dormant groups. A group with an empty BoundTo came out of export → apply bound to the system app, because the exporter wrote the empty list as absent and create treats absent as ['modgud']. Empty BoundTo, Capabilities and job Parameters now export as empty. This also removes the phantom (empty) → [] plan changes.
  • One-time secret after apply. Applying from the staging bar on any page other than the workspace dropped the secret behind a toast. The bar now opens the workspace, which shows it.
  • Service-account grid after apply. It stayed empty until a reload. The post-apply refresh didn't cover service accounts, jobs or the inbox policy, and the applier sent no ServiceAccount event. Both are fixed.

Consent for dynamic clients (fix(oauth))

  • Since Consent that means it, and a way to end one OAuth authorization #236 every DCR and CIMD client re-prompted on every authorize.
  • A remembered authorization now skips the consent screen when the client's identity is assured (RFC 8252 §8.6): the redirect is https on a real host (claude.ai), or the client is confidential (ChatGPT's private_key_jwt, confidential DCR).
  • Public clients with a loopback or private-use redirect (Claude Code, VS Code, Zed) are still asked every time. Any local process can listen on a loopback port.

Draft / export UI (feat(ui))

  • The staging bar is the only footer bar. It holds Discard (confirmed, available from any admin page), Park and Apply, and Apply is the only primary action. The draft name and its counters link to the review. On narrow windows Discard and Park move into a ⋯ menu.
  • The export basket moves into a header chip.
  • Confirmations name their action instead of "OK". The library's popconfirm arrow is never positioned and covers the title, so it is hidden (tracked upstream).
  • Lists show the draft state as New / Changed / To be deleted.
  • A refused staged deletion shows why, and offers "Undo the deletion".
  • Drafts show local time.
  • The selective export no longer shows [object Object] for group roles.
  • About 20 missing German strings are added.

Contract changes (pre-1.0)

  • ?prune=true is ignored. A script that still sends it gets an additive merge and no deletions.
  • Exports write empty BoundTo, Capabilities and job Parameters as [] / {}.
  • Public DCR/CIMD clients that redirect to loopback see the consent screen on every authorize.

The draft release notes for v0.14.0 have been updated with all of the above.

Verification

  • Backend: 852/852 integration tests (the count matches --list-tests) and 1749/1749 unit tests.
  • Each new test was run red without its fix and green with it: bearer drafts, rename by id, pinned credential id, consent https vs loopback, private_key_jwt, dormant-group round trip.
  • Frontend: pnpm type-check, the production build and the docs build all pass.
  • UI: a Playwright tour of the draft and export surface passes 13 checks, with no console errors. It covers discard from any page, the export chip, the disabled-apply reason, undoing a deletion, the narrow-window menu and the sidebar badge.
  • UI: manual click-through against a dev realm. Groups (create, rename, protected and real deletion), service accounts with credentials, users, app settings, jobs and realm settings all behaved as expected, and the consent flow was checked in the browser.

🤖 Generated with Claude Code

windischb and others added 4 commits September 24, 2026 17:52
…ssured

Since #236 AllowRememberConsent is enforced, and DCR and CIMD always stored
it false, so every dynamic client saw the consent screen on every authorize
- claude.ai and ChatGPT included.

For a dynamic client the stored flag is no longer consulted; RFC 8252 §8.6
decides (DynamicClientConsent): a remembered authorization skips the screen
when the request's redirect is https on a real host, or the client is
confidential (private_key_jwt, a DCR secret). A public client redirecting
to loopback or a private-use scheme (Claude Code, VS Code, Zed, Cursor) is
still asked every time: its client_id is public and any local process can
listen on a loopback port - on any port since #238. Admin-created clients
keep their own flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Full-sync prune is removed from every manifest route (control plane,
data plane, drafts), together with the two-step confirmation the 0.14
betas carried (409 Manifest.ConfirmationRequired, confirmation tokens,
parked review drafts, PruneOnApply). An apply is an additive merge by id;
deleting is a staged deletion in a draft, which the plan shows before the
apply - for the admin UI and a Management API client alike. The plan
loses its Prune field and the "protected" action. A service-account
credential is never deleted through the clients section: it leaves
through its account's Credentials list, the only place the plan shows it.

Found in the pre-release review and fixed on the way:
- Draft endpoints took the caller from NameIdentifier, which a bearer
  principal does not carry: every Management API call answered 500.
  They read sub first now.
- Staging matched a draft entry by natural key only, so renaming an
  entity inside a draft appended a second entry with the same id.
  Staging matches by Id first.
- A credential with an Id in the manifest was issued under a random id;
  the next apply of the same file re-issued its client_id and failed on
  ClientIdAlreadyExists. The issue op now takes the pinned id.
- About fifteen strings of the staging UI had no German translation, and
  a few English fallbacks were German.

BREAKING CHANGE: ?prune=true is no longer read; a script sending it gets
the additive merge and no deletions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A dormant group (empty BoundTo) came out of export -> apply into
  another realm bound to the system app: the export wrote the empty list
  as absent, and a create reads absent as ['modgud']. Empty BoundTo,
  Capabilities and job Parameters now export as empty, which also removes
  the phantom "(empty) -> []" plan changes for untouched groups and jobs.
- A credential secret created by a draft apply vanished when the apply
  ran from the staging bar on another admin page (toast only). The bar
  now opens the drafts workspace, which shows the one-time secrets.
- The service-account grid was empty after an apply until a reload: the
  post-apply refresh skipped service accounts, jobs and the inbox policy,
  and the applier dispatched no ServiceAccount event. Both fixed; the
  refresh no longer requests positions with the feature off (404).
- A staged deletion can be taken back from its plan entry (the plan note
  said "unstage", the modal had no way to), and the previous apply's
  outcome no longer stands above the next draft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e header

A UI review of the draft surface (screenshots of every state) found two
look-alike footer bars stacked on each other doing unrelated things: the
export basket's "Exportieren…" sat right above "Draft anwenden", both
primary, and a draft could only be discarded from inside the workspace or
after parking it.

- The export selection is a header chip (icon + count) with a panel:
  per-entry remove, Clear, and a secondary "Download as manifest…".
- The staging bar is the only footer bar: accent stripe, the draft name
  plus "n staged" / "m errors" as one link to the review, Discard (with a
  danger confirm, from any admin page), Park, and Apply as the single
  primary. A disabled Apply says why. On narrow windows Discard and Park
  move into a "⋯" menu. The workspace no longer repeats Apply / Discard.
- Confirmations say what they do ("Anwenden", "Verwerfen", "Abbrechen")
  instead of the library's "OK" / "Cancel"; the library's popconfirm arrow
  (never positioned, it covered the title's first letter) is hidden.
- Lists show the staged state as "New" / "Changed" / "To be deleted", and
  their delete action reads "Stage deletion" while staging.
- A refused staged deletion shows its intent and reason on the plan card;
  its modal shows the refusal as an error and "Undo the deletion" as the
  primary action, without the contradicting "applying deletes it" note.
- Auto-named drafts read "Draft by <user> · <local time>" (the stored name
  carries UTC); parked/shared drafts are counted in the sidebar.
- Selective export: group roles no longer render as [object Object], the
  hint wraps, singular/plural wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@windischb
windischb merged commit ecfe1c9 into develop Sep 24, 2026
8 checks passed
@windischb
windischb deleted the fix/release-0-14-prep branch September 24, 2026 20:34
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