All notable changes to app-platform are documented here.
The format follows Keep a Changelog, and this project uses Semantic Versioning.
[1.0.0] - 2026-09-30
Fixed
- The staging GitHub Actions policy signer now admits the one approved Factory Sync dispatch for app-knowledge-management through the GitHub App/Gateway path, while rejecting every other target, scope, workflow, reference, and authorization class.
Fixed
- A commit that never reached staging is now reported. A check exists for exactly that situation — the image build did not finish, no later deployment carried the commit, and staging quietly stayed on an older build — but two of its conditions could never be true at the same time, so it had never once run. It now runs, and only where it is needed: it waits to see whether a later deployment brings the commit along, stays quiet when one does, and speaks up only when staging really never received it. A deliberate hold on a fixed build still stays quiet.
Fixed
- A confirmation e-mail now actually confirms the address. The link pointed at the page that reports the result instead of the endpoint that checks the token, so anyone who registered themselves was told their link was "invalid or has expired" no matter how new it was. Only the resend path happened to build its link correctly, which is why re-sending appeared to be the cure.
Fixed
- A comment explaining a styling rule no longer fails the style gate as though it were the rule itself. The check that looks for a particular styling construct used at the start of a selector was matching any line that began with it, including prose inside a comment, so a component whose styling was correct could be blocked by its own explanation — and the failure named a defect that was not there. The check now reads selectors rather than lines, and points at the exact file and line when it does find one.
Fixed
- When the staging check that opens the account menu fails, the run now records what it found instead of only that a page change never arrived. The two previous failures could not be explained at all: nothing errored, so there was nothing to read. The run now notes whether the menu was still open and whether the page had finished becoming interactive, which is enough to tell a lost click apart from a menu that closed itself.
Fixed
- A staging deployment no longer reports success when the image build that triggered it failed. The deploy runs after the image build completes, and it correctly refused to deploy anything when that build had failed — but it then finished green, with the explanation buried in a step log, so a broken build and a healthy deployment looked the same from the outside. Another app on the platform spent four hours in exactly that state. A build that failed, timed out or never started now turns the deployment red and names the run to look at. A build cancelled because a newer commit superseded it still passes quietly, because the newer commit brings its own deployment.
Fixed
- A release note whose metadata cannot be processed is now reported while the change is still under review, instead of quietly withdrawing it from the merge line later with every status still showing green.
Fixed
- Dependency security advisories are now caught while a change is still under review. The audit previously ran only when a change edited one of the repository's JavaScript package files, so an advisory published against an untouched dependency surfaced far later, by withdrawing an unrelated change from the merge line.
Fixed
- The staging health check no longer splits a single page change into two verdicts. When a form is submitted the browser abandons everything the outgoing page was still downloading, and the check now judges each abandoned file by the page that asked for it rather than by the instant the browser got round to reporting it. Previously, if the new page finished arriving while those reports were still coming in, the earlier ones were accepted as normal and the later ones from the very same page change were reported as faults. The password-reset journey was failing this way. Files that genuinely fail to load are still reported, and so is a download cancelled by a page that stays put.
Choosing a password the policy refuses on the reset page now says so and keeps the form, instead of reporting that the reset link has expired.
The shared chat panel heading now wraps very long unbreakable words instead of pushing the panel wider than a phone screen.
Added
- The account-recovery screens are now protected against abuse. Asking the platform to email an unlock or account-recovery link is limited in two ways at once: how much recovery traffic one visitor may generate, and — separately — how many recovery emails a single address may receive. The second limit is what stops someone flooding another person's inbox by sending the same request from many different connections. Someone recovering their own account is unaffected: a lost first email can be re-requested, and mistyping an address does not use up anyone else's allowance.
- Revoking a session, signing out every other device, and regenerating recovery codes are now limited per person rather than per connection. This matters in two everyday situations that were previously wrong in opposite directions: colleagues sharing one office internet connection no longer share one allowance between them, and someone whose phone switches between mobile and wi-fi no longer gets a fresh allowance each time it switches.
- Opening an emailed recovery link has its own separate allowance, so a visitor who has used up their request allowance can still open a link they were already sent.
- The unlock, recovery-fallback and sign-in-conflict pages themselves now carry an allowance too, so the screens can no longer be loaded over and over from one connection. It is a generous allowance on purpose: a single recovery journey opens these pages several times by design, and a person working through one will not notice it.
Fixed
- Active sessions now name the device and the rough place each one was opened from — "Chrome on Windows" and the network the sign-in came from, instead of "Unknown device" and a dash on every row. The list existed to answer one question, whether anything on it is not you, and until now it gave every session the same anonymous description, so it could not answer that for anybody. Sessions opened before this change keep showing the old placeholders; sign out and back in to give a session a label.
Fixed
- The "Active sessions" screen in account settings now opens. Choosing it from the account menu previously showed an error page and then returned you to sign-in, so there was no way to see the devices signed in to your account or to sign any of them out. The screen had never worked since it was added.
The staging delivery edge now exposes the governed read-only metadata verification operation required by the Agent Delivery Pipeline.
Added
- Staging now enables cryptographically signed, read-only post-merge metadata observations for the Agent Delivery Pipeline.
Governed staging deployments now use the workflow package token for private NuGet restores without exposing credentials.
Added
- The theme editor's typography section is no longer a static preview. Administrators can now set the body, heading, and monospace font stacks directly, the same way every colour value is already set, and each field can be left blank to keep the platform default rather than requiring all three to be filled in.
Fixed
- Cross-app theme administration now uses a single governed admin credential, so a rotated key takes effect on the next deploy, and reports only a genuinely mismatched credential as credential drift.
Button labels now stay readable whatever colour you choose. Previously the text on a button was always white, so picking a pale or bright fill for your main or secondary action produced a label you could barely read, with nothing in the editor able to fix it. The label colour is now chosen for you from the fill behind it, and the theme editor offers both label colours if you want to set them yourself.
The packaged dark theme's secondary button is one of the surfaces this repairs: its white label sat at 3.0:1 against the teal fill, below the accessibility floor, and now reads at 5.6:1.
If you choose a see-through fill for a button, you are asked to pick the label colour yourself — a translucent fill has no single colour to read against, so the app declines to guess one.
The reusable chat panel now exposes localized names for reaction controls, uses singular wording for one unread message when supplied by its host, and omits empty reaction containers.
Chat timelines retain their reader-local timestamp, stable participant avatar, tombstone state, and reaction state when rendered through the shared chat component.
Fixed
- Automated browser checks can sign in again. The pages that render the sign-in form and the service that actually accepts it are two different processes, and the test environment was reaching them as two separate addresses — so submitting the form went nowhere, no session was ever created, and every page behind a sign-in appeared to reject the account. Nothing was wrong with the accounts or their permissions; the checks simply had no way to sign in, and reported success anyway. They now sign in through a single address, the way the real site does, and a check that fails to establish a session says so instead of continuing as an anonymous visitor.
- The two buttons on "My Apps" — *Try again* and *Discover more apps* — were smaller than the minimum comfortable tap size on a touch screen. They are now the same size as every other button in the product. The page had never been checked while signed in, which is why this went unnoticed.
Changed
- The existing test-environment setting that allows sign-in over an unencrypted local connection now covers the session cookie as well as the form-protection cookie. Previously only one of the two was covered, which left a session that appeared to be created and was then discarded by the browser. The setting stays off unless a deployment explicitly turns it on, and it changes nothing about how sessions are protected anywhere it is not set.
The shared sign-in library ships this as version 4.6.0 for the other applications built on the platform. They are unaffected until they choose to take it, and taking it changes nothing for them unless they turn the setting on themselves.
Fixed
- The CSS quality gate no longer fails at random on branches that cannot affect it. Its browser tests now always run against the same address, instead of occasionally being handed one where the browser cannot keep anyone signed in — which produced a sign-in timeout that looked like a defect in whatever was being reviewed.
- When sign-in does fail during that gate, the report now names the reason instead of showing only that an element never appeared.
Fixed
- Buttons that show only an icon — close, refresh, add, filter, the paging arrows — are now a comfortable size to tap in both directions. A recent change made every button tall enough but left these ones narrow, so they ended up taller than they were wide: a thin strip rather than the square target they are meant to be, and in that respect harder to hit than before.
Fixed
- Test environments that reach the app over a plain address rather than the local machine can now keep people signed in. The setting that was meant to allow this only ever applied to half of what it claimed, so sign-in appeared to succeed and the very next page was signed out again, with nothing in the logs to say why. Live environments are unaffected — the setting stays off unless it is switched on explicitly.
Added
- The design-system showroom now shows reference examples for alerts, buttons, form fields, data grids, loading placeholders and dialogs, including the states that are hard to reach in the running product — an empty grid, a loading grid, a disabled field, an icon-only button. They are marked as candidates, meaning they are ready to look at and compare against but are not yet a signed-off reference.
Added
- The design Showroom now carries a reference exhibit for each family of third-party UI controls the platform builds on — alerts, buttons, form fields, data grids, dialogs and loading placeholders. Each one can be viewed in light and dark, in every state it supports, including the deliberately awkward ones: the longest label a translation is likely to produce, an empty table, a field that failed validation. Reviewers get one place to say what these controls are supposed to look like, rather than inferring it from whichever screen happens to use them.
Fixed
- Styling rules for alerts, tables, buttons and password fields were keyed on element names the underlying control library no longer produces, so they had quietly stopped applying — without failing, and without appearing anywhere as an error. They now match what the controls actually render, restoring the intended look for alert severities, grid rows, icon-only buttons and password inputs.
Changed
- Four background colours in the shared control styling were pinned to fixed values that no theme could move. They now follow the theme first and fall back to the fixed value only if a theme does not define one, so a custom theme reaches surfaces it previously could not.
Fixed
- The styling corrections for alerts, tables, buttons, password fields and dialogs now actually reach the other apps that share this platform's look. The corrections themselves shipped earlier, but each app pins the shared styling to a fixed release, so they stayed on the old one until that pin moved. One app had been left far enough behind that no styling correction had reached it since June.
Changed
- The changelog page now renders release notes as structured, styled sections instead of plain text, and shows a clear message when notes aren't available.
Added
- The Showroom now includes a live exhibit of the shared changelog page, viewable in both interface languages and both themes.
Fixed
- Error pages are themed again. A page that did not exist, and any page the platform declined to show, arrived with none of the theme definitions the rest of the site is built from, so it painted in the stock component palette instead of the theme in use — and in an app where the visitor had chosen a theme of their own, announced that theme while defining none of it. They are themed in the packaged light or dark palette rather than in a chosen one, though: neither an administrator-curated theme nor a personally customised one is reproduced on an error page.
- The platform's own "page not found" address no longer answers with a blank error. Reaching /not-found by following a link or typing it returned an empty 500 instead of the page; arriving there by mistyping some other address always worked, which is why the broken half went unnoticed. Both routes now render the page and both report "not found" to the browser.
Added
- Applications can now offer people a personal theme editor. It shows every colour a theme is allowed to set, each one named in plain language rather than by its technical identifier, with a live sample of the application beside it that updates as colours change — no saving or reloading to see the effect.
- The editor also reports readability while someone works: it checks the combinations people actually read, such as body text on the page and text on a highlighted row, and says which ones may be hard to read. It reports and never blocks — a theme can always be saved, because the person choosing the colours is the one who knows what they need.
- Someone opening the editor for the first time starts from the colours they are already using, not from an empty form, and the editor says so. Typefaces for body text, headings and code can be set alongside the colours.
Changed
- The shared theming library now publishes the token contract itself — which colours a theme must define, and which optional groups are available — so an application's theme editor and the service that validates a saved theme read the same list instead of two copies that can disagree.
- The design token catalogue can now carry a short human-readable name for a token. Existing catalogue entries are unchanged; the name is present only for the tokens a person can edit.
Added
- Signed-in people can now open a personal theme editor from the platform's own user menu and adjust the colours they see across the platform. Their choices apply only to them, and the page states which application the theme belongs to before anything is saved.
- The editor opens from the colours already in use rather than an empty form, and says so. Where a saved theme does not set a colour, the editor shows the value currently in effect instead of a blank swatch.
- If the theming service is briefly unavailable, the editor still opens against the colours in effect rather than failing the page.
The personal theme editor's controls now sit in a single row at the foot of the editor, in the order the page design calls for: Import, Export, Delete, Reset all, Save. They used to be split across two rows — Save and Reset all in one, Export and Import in a quieter row below — which read as two unrelated groups and pushed Import out of sight.
Colour settings now sit two to a line when the editor is wide enough, instead of one per line, so a long list of colours takes half the scrolling it used to. Below that width they stack as before.
Deleting your theme now asks in a dialog rather than by swapping the Delete button for a confirm-and-cancel pair. The question is the same one and deleting still takes a deliberate second step; what changes is that the row of buttons no longer rearranges itself under your cursor, and the question can no longer be left half-asked while you carry on editing behind it. Dismissing the dialog leaves your theme alone, as cancelling does.
Fixed
- Trait marks and damage boxes now keep a minimum size that stays comfortable to click and to tap. They previously kept shrinking as the space around them got tighter, with no lower limit — in a narrow panel they ended up well under the size a pointer can reliably hit, and they were smallest on touch screens, where they need to be largest. They now shrink only as far as that minimum. Below it the row scrolls sideways inside its own panel instead, so every mark stays a full-size target and none of them are hidden, cut off, or pushed out onto the page around them.
- The keyboard focus outline on those marks stays fully visible in a panel that scrolls, including on the first mark in a row.
Added
- The shared trait-rating control now exposes its mark size and the two gaps between marks — the small gap inside a group of five and the wider gap at the 5th/6th boundary — as their own design tokens, so a host can retarget a single control's grammar without touching the control's internals. The control's default grammar now matches the 10-cap trait row (Disciplines, Willpower, Humanity): an 11px mark with a 3px within-group gap and a 6px group boundary, in one row that never wraps up to twelve marks.
- Trait rows that carry a specialisation (for example "Firearms" with a "Rifles +1" bonus) can now render that specialisation directly under the trait's label, with an optional "add a specialisation" control shown only while the row is editable.
- The unfilled trait mark's border now clears WCAG 1.4.11's non-text contrast floor (>=3:1) against the control's own card background out of the box; the design token it renders from (--ap-wodvtt-mark-empty-border) was raised at its source, and a new consumer-overridable --ap-trait-mark-empty-border token is available as a fallback for a host that does not theme the WoDVTT-specific one.
Changed
- The trait mark's accessible tap target is now independent of its visible size: overriding the mark size alone moves the ink, not the 27px (fine-pointer) / 44px (coarse-pointer) tap target, which no longer needs a matching layout footprint to stay met. This is what lets the 10/12-mark row above fit the intended card width without scrolling.
- Existing callers that do not set the new mark/gap tokens get a visibly smaller default mark and different grouping (see the package CHANGELOG for the exact before/after numbers).
- The trait mark's accessible tap target moved from an exclusive per-mark overlay to a single target owned by the whole mark row -- ten separate 27px/44px overlays could not fit the row's own width.
- The interactive mark row is now one real control, not ten: .tr-marks itself is role="slider", is the row's sole focus stop, and owns click/pointer/keyboard handling (nearest-mark from pointer position; arrows/Home/End/PageUp/PageDown/digits from the keyboard). The per-mark role="slider" buttons are gone in interactive mode -- marks are presentational, unclickable spans. Readonly rendering is unchanged.
New AppPlatform.Authorization package for applications built on the platform. It lets an application say what a person may *do* — archive a group, erase a record — separately from who they *are*, so those rights can be granted per environment in configuration instead of being wired into the sign-in system.
For people using the applications, the practical effect is that a right can be given or taken away without a release, and a right that depends on a relationship — being the game master of a particular group — simply applies while that relationship lasts, with nothing to hand out and nothing to clean up afterwards.
Signing in and being refused now stay clearly different: someone who is not signed in is sent to sign in, and someone who is signed in but not permitted is told so rather than being asked to sign in again.
Internal build fix — no change to how any application behaves.
Package publication now aligns referenced project versions with published package records when recovering a release, preventing stale dependency versions from blocking publication.
Admin users signed in through the normal browser session can now lock accounts and force credential rotation from the Identity Admin Console instead of getting an unexplained 401.
The "Resend confirmation email" and "Email me an unlock link" buttons on the sign-in page now line up with the other actions instead of sitting flush and left-aligned.
An application can now send people who have no account yet straight to the registration form instead of the sign-in form.
If you follow an invitation link and create an account instead of signing in, you now land back on the invitation after confirming your address, rather than on the start page. This also works when you sign in with Google, Microsoft or GitHub.
Sign-in and account-recovery redirects now reject a wider set of crafted return addresses that could previously send you to another site after a successful sign-in.
Authorization can now withhold a permission from specific signed-in accounts, layered on top of any broader grant. Previously a grant could only say "everyone" or "everyone with a given role" — there was no way to carve out a named exception. Staging now withholds the personal theme editor from one seeded account, so its forbidden-access journey can be exercised alongside every other account's normal access.
The permission withhold can now wrap any host's outermost resolver, so every app on the platform can withhold a permission from named accounts.
The reusable chat panel now preserves mobile Enter-as-newline interaction, opens an existing transcript at its latest turn, and provides a stable message-content integration contract.
Data tables are now announced correctly by screen readers.
Every data table in the platform is built on a third-party grid component. That component wraps its rows in an extra, unlabelled container, which leaves assistive technology unable to see that the table has any rows at all — it is announced as a table, and then as empty. Automated accessibility checks classify this as critical, and it affected every data table in the product, not one page.
The extra container is now made transparent so the rows are reported as belonging to the table. Nothing changes visually, and keyboard navigation is unaffected. The underlying issue has been reported to the component's authors; this workaround will be removed once they ship a fix.
Added
- A new shared component is available for services that need to report their own health. A service adopting it answers the three platform health addresses identically to every other service: one for the overall condition, one for whether the process should be restarted, and one for whether the instance should receive traffic. All three answer without credentials, and the restart-facing one never reaches out to a shared dependency, so one dependency wobble can no longer restart services that are themselves fine.
- The answer it produces names the overall condition, every dependency it checked with that dependency's own condition and how long it took, and which service and which release replied. A reader can act on it without knowing anything about the service that sent it, and when a dependency fails the reply still names it while carrying nothing about the underlying failure that would be unsafe to publish.
- A service that has no dependency checks of its own still reports something on the traffic-readiness address rather than reporting success unconditionally.
Services that verify their own health contract now get a stricter answer about their liveness checks. A liveness check must report healthy once every outbound capability it could reach has been taken away — not merely report the same thing it reported while those capabilities were still present. A service that was quietly degraded with and without them used to be accepted; it is now reported, because a liveness check with nothing outside the process to consult has nothing to be degraded about.
Added
- A companion shared component is available to services that report their health through the platform health addresses. It lets a service establish, against its own running configuration rather than a description of it, that all three addresses answer without credentials, that each answer carries the agreed fields, that the traffic-readiness address reports something instead of succeeding unconditionally, and that a single failing dependency is still named individually rather than collapsed into one word.
- The same component establishes that the restart-facing address reaches no shared dependency: it runs those checks with every outbound connection replaced by one that refuses to connect. An outbound connection it is unable to replace is reported rather than passed over, so a service cannot appear to meet the requirement by reaching a dependency through a route that was never checked.
- It also confirms that anonymous access to the health addresses is a deliberate exemption rather than a side effect of a service that asks nobody to sign in.
- Adopting services obtain all of this without writing the checks themselves, so two services cannot drift into proving different things about the same behaviour.
Every service in this product now answers the platform health contract at /health, /health/live and /health/ready, anonymously and with the same structured body. /health reports every check, /health/live reports only whether the process itself is running, and /health/ready reports whether the service can take traffic — and it can no longer report ready on the strength of having nothing to check.
Restart decisions now follow the liveness answer rather than the readiness one. A dependency being unreachable makes a service report that it is not ready to take traffic; it no longer makes the service look dead and get restarted. An outage that used to roll every instance of an otherwise healthy service now takes it out of rotation until the dependency returns.
The previous /alive address keeps answering exactly as before, so anything already pointing at it continues to work.
The two components a service installs to prove it answers the platform health contract — the contract itself, and the checks that verify a service answers it — are now published alongside the other shared components on every release, so a service can take them as a normal dependency instead of copying them.
They are always published together, and a service that installs the checks always gets exactly the version of the contract those checks were written against. It is no longer possible to end up with newer checks judging an older contract, which would have reported a service as broken when the real problem was the mismatch.
The web frontend now answers its readiness questions at its own address, so each service can be checked on its own rather than through the address that belongs to another one. Only the readiness questions are answered there; every other request to that address is refused.
Fixed
- The agent-user-cmslite-pro agent persona and the prouser@staging.local roster account now get cmslite-author, so cmslite's authoring workspace is reachable on staging by the E2E lane and by hand.
Fixed
- A colour chosen in the personal theme editor is now kept. Picking a colour showed it correctly in the picker and then discarded it as soon as the picker closed, so no personal theme could be built at all. Colours now take effect as you choose them, and the preview updates with them.
Fixed
- A personal theme you save is now the theme you see. Colours were stored and shown correctly the next time you opened the editor, but the rest of the app kept painting the previous theme, so saving appeared to do nothing.
Added
- The personal theme editor is now available to other applications as a reusable package, so an app can offer its users the same editor instead of building one of its own.
The personal theme editor now has a way back: if you have saved your own theme, you can delete it and return to the theme the app would otherwise apply. Deleting takes two clicks and cannot happen by accident.
Typing a colour code by hand now works. Previously the colour picker closed while you typed, so a code entered by keyboard was lost without any message; dragging the colour surface was the only reliable way to set a value.
Fixed
- The light/dark toggle in the header now shows the correct switch target as soon as the page appears. Previously it always offered to switch to dark mode on the first render — including when dark mode was already active, which is what a first-time visitor sees — and corrected itself only once the page became interactive. Where the host cannot yet tell the control which scheme is active, it now shows a neutral toggle rather than a confidently wrong one.
Changed
- The staging agent email proxy site is protected the same way the staging mail viewer already was, rather than by a second, parallel arrangement built only for it. Two different ways of doing one job had grown up side by side; the newer one is gone.
- What a visitor sees is unchanged: the site still asks for credentials before showing anything, and its availability addresses still answer without them.
- A routine staging update now stops with a message naming what is missing if the credential for a protected site is not in place, instead of proceeding. It cannot publish the site unprotected: the proxy refuses to load a configuration whose protected site has no credential, so the previous, protected configuration keeps serving.
Changed
- The staging agent email proxy site no longer asks for a shared password before it will show anything. It now signs people in with the platform's own sign-in, the same account used everywhere else, instead of a separate password held outside it.
- The shared password was only ever a temporary lid, put on while the site's mailbox and agent-credential screens had no sign-in of their own. Those screens gained a real one, so the lid came off.
- Nothing on the site became public: an unauthenticated visitor is sent to sign in rather than shown content. Availability addresses still answer without signing in.
- The staging health check no longer expects this site to answer with a password prompt, so a change that quietly reopened it would be noticed rather than accepted.
Added
- AIMS can now validate a short-lived, device-bound public-key proof before it issues a GitHub read-session binding to an authorized harness. Proof material is single-use, repository-bound, and never returned by the API, so the Gateway and its consumers can later establish read-only GitHub access without receiving a reusable device secret.
- The binding now includes the sole AIMS-derived role eligible for GitHub read access; inactive, missing, malformed, or ambiguous owner bindings are rejected before a session can be opened.
Fixed
- GitHub read-session clients can now rely on published AIMS proof and admission endpoint contracts with correctly scoped persistent session access.
Added
- Sign-in throttling can now be counted once for the whole platform instead of separately by each running copy of it. With the shared mode switched on, the configured attempt budget is the budget an actor actually gets no matter which copy answers, and it is no longer wiped clean by a routine platform update. An installation running a single copy is unaffected and behaves exactly as before.
- Throttling now also covers the second-factor challenge and the sign-on endpoints other applications use to obtain and renew access on a user's behalf. Those had no throttle at all until now. Each one has its own separate budget, so busy legitimate traffic against one cannot exhaust another's.
Changed
- If the shared counter becomes unreachable while the platform is running, sign-in stays available and stays throttled: each copy falls back to enforcing the budget on its own. That is more permissive than a single shared count, but it is never unlimited — no sign-in endpoint becomes unthrottled. The switch to and from the reduced mode is recorded in the logs so an operator can see it happen, and full shared counting resumes on its own once the counter is reachable again, with no restart.
- An operator who prefers the opposite trade-off for a given environment can declare the shared counter mandatory there; sign-in attempts are then refused while it is unreachable rather than throttled per copy.
- The shared mode is off by default. An installation that does not turn it on keeps exactly the previous behaviour and the previous attempt budgets.
Changed
- When the "Active sessions" screen cannot work out which of the listed devices is the one you are reading it on, that now gets recorded. Nothing about what you see changes, and nothing unsafe happened before: the screen simply showed no "This device" marker, and "sign out all other devices" refused rather than guessing. But it looked exactly like an ordinary page, so on the one occasion it happened there was nothing afterwards to say why. It is now distinguishable from a normal render, and the two different causes are distinguishable from each other.
Added
- Platform administrators can now administer an individual application's themes from the platform administration screen, not only through the administration API. The themes screen gained an application selector: choosing an application lists that application's themes, and the screen states which application it is showing, so a theme is never edited against the wrong one by mistake.
- A theme can now be created for a chosen application through the same theme editor used for platform-wide themes, with every required colour value. The theme is stored by the application that owns it and appears in that application's list.
- Applications that do not support themes are now listed as unavailable, with a reason, rather than left out. An application that is missing from a list and one that simply has no themes yet look identical, and only one of those is worth investigating.
- When an application cannot be reached, the themes screen now says so and offers to try again, and withholds the list rather than showing part of it as though it were complete. When the failure is instead a credential mismatch, the screen says that plainly and names the action an operator has to take, because trying again cannot succeed until they do.
- When saving a theme fails because the owning application could not be reached, the editor now keeps everything that was typed and distinguishes three situations: the application was unreachable, its credentials did not match, or it did not answer in time and the change may or may not have been applied. In that last case the screen says the outcome is unknown and asks for the list to be checked before saving again, rather than stating that nothing was saved — a second attempt could otherwise create a duplicate theme.
- The administration API reports a change that was sent to an application which then did not answer as its own outcome, separate from an application that could not be reached at all. The two need different handling: nothing was stored in the second case, and in the first that cannot be known.
Keep the Reject action readable on dark approval cards when shared theme styles are loaded.
Ensure shared UI package releases can verify historical package versions using the registry’s documented authentication format.
Ensure shared UI package releases recover correctly when a historical package version is already available.
Fixed
- Shared packages that rely on older theming components can now be restored reliably when a historical package archive needs to be completed.
Ensure shared UI package releases correctly detect historical package versions already available in the package registry.
Fixed
- A blank or spaces-only key-ring encryption certificate is now recognised as no certificate at all, rather than being taken as certificate material. Supplying one — which is what a half-finished provisioning leaves behind — used to stop the service during start-up with a cryptography error about corrupted data, which named neither the setting at fault nor the fact that it was simply empty. The start-up check that does name it, and refuses to run a staging or production service whose key ring would otherwise be stored unprotected, now gets to report first.
The credential broker can send receipt-bound live dispatches through a dedicated Gateway route.
Added
- The credential broker now has a persistently backed, device-bound GitHub workflow-read admission service. Session state contains no GitHub credential or proof payload, expires within fifteen minutes, and remains subject to a fresh AIMS authorization decision on every read.
The private Credential Broker now has a fail-closed binding for independently governed Staging admission evidence while remaining disabled by default.
Shared-library delivery now preserves privately bundled components without requiring a separate publication. Public dependencies retain their existing version and availability checks.
Removed a duplicate staging reverse-proxy configuration file that was never used to serve traffic, and corrected a comment in the active file that pointed readers at the unused copy. The remaining file is now covered by a check that fails if a second copy is reintroduced, so routing changes cannot be applied to one copy and silently missed in the other. No change to staging behaviour or routing.
Fixed
- New shared packages now receive their first ledger-bound SemVer allocation instead of blocking publication before consumers can adopt them.
Fixed
- The reusable chat panel now keeps reader reactions available on other participants' messages, renders the consumer's localized unread-count text, and distinguishes Enter-to-send from Shift+Enter-to-add-a-line consistently.
Fixed
- Shared chat transcripts now preserve a reader's scroll position when a remote deletion replaces a message with its tombstone.
Separates reaction-chip names from reaction-action labels, allowing localized consumers to expose grammatical accessible names.
Production delivery preparation now keeps deployment routing separate from general CI runner selection.
Fixed
- The staging CI policy signer now uses its own least-privilege identity and publishes its public bundle only through its dedicated boundary.
Fixed
- The post-deploy staging smoke test runs on the runner that hosts staging, so it can actually inspect the deployed control plane instead of looking for it on a runner that never had it.
Fixed
- Chat readers stay in history when new messages arrive and can return with a new-message affordance.
- Loading older messages keeps the reader's visible position stable.
- The chat composer now shows its character counter from 90% of its configured limit.
Added
- Local AppHost runs the Credential Broker with its dedicated database, valid resource identifier, and a distinct managed HTTP endpoint.
Fixed
- The default-off Credential Broker image is now built and tracked by the normal staging delivery workflow, and teardown uses a separately bounded maintenance database role without weakening the runtime role's audit protections.
- A missing Broker image anchor now self-heals on the next publish, while exact staging pins from before Broker publication remain usable.
- Staging observability contracts now include the Broker in the fail-closed full service refresh inventory.
Added
- Applications that sign their users in with a platform account can now keep a much smaller record of those users. Until now such an application inherited the platform's own account storage wholesale, including places to keep a password, a lock-out counter and a two-factor setting — none of which it could ever fill in, because it does not sign anyone in itself. It now has the option of storing only what it actually needs: who the person is, enough of their profile to show a name and a picture, and the link its own data hangs off.
- The practical effect for anyone reviewing one of these applications is that its stored account record no longer describes abilities the application does not have. An empty password field on something that cannot check a password reads as a place a password might one day sit; removing it removes the question.
- This is an addition, not a replacement. An application that changes nothing keeps exactly the behaviour it has today, and each one can move across on its own schedule.
The design-system and styling quality check that runs before changes are merged now also runs when the repository's JavaScript dependency manifests change. That check installs and executes those dependencies on every run, but was not started by a change to them, so a dependency update could alter what it validates without the check ever running. No runtime component is affected and there is no change to application behaviour.
The design-system and styling quality check now starts on every pull request rather than only on ones that touch styling files, reporting itself as skipped when nothing relevant changed. This is what allows it to become a check that must pass before merging: a check that does not start at all reports no result, and a merge gate reads a missing result as unsatisfied rather than as passed. Which changes the check actually inspects is unchanged, and no runtime component or application behaviour is affected.
Improve contrast for filled error badges, chips and progress labels. Keep existing validation-text colors; their dark-theme contrast issue remains open.
Improve dark-theme validation message contrast on raised surfaces while preserving readable filled destructive actions. Invalidate cached theme CSS when unchanged theme rows use the corrected packaged error colour.
Security
- The platform service now treats an endpoint as protected unless it is explicitly declared public. Previously the opposite was true: anything that did not name an access rule was open to anyone, so a new feature could be published without protection simply by forgetting to add one. Every existing endpoint has been reviewed and now states which of the two it is.
- Nothing that was reachable without signing in has become unreachable. Signing in, registering, confirming an email address, choosing new sign-in credentials after forgetting them, unlocking an account, recovering a lost second factor, signing in through an external provider, and the pages and availability checks that must answer before anyone is signed in all keep working exactly as before.
- The service is now checked on every build against a reviewed list of the endpoints that answer without sign-in. Adding a new public endpoint is a deliberate, visible decision rather than something that can happen by omission, and an endpoint that names neither option now stops the build instead of shipping.
Fixed
- Design-context exports now retain the token metadata that automated quality checks need to identify and validate theme-aware design assets.
Changed
- The page-not-found screen's heading now uses the largest size in the design system's type scale instead of a one-off size that belonged to no scale position. It grows very slightly; nothing else about the screen moves.
Fixed
- Across the administration and operations screens, a number of colours and borders were written against value names the design system does not define, each with a working alternative supplied alongside. Nothing looked wrong, because the alternative is what the browser used — but anyone who later defined one of those names would have silently changed how those screens render, with no warning at review time. Every one of them now names a value the design system actually defines, so what is written is what is drawn, and the trap is gone.
Changed
- Removed 46 leftover hard-coded values that sat behind design-system colours, sizes and spacings as a "if the design system is missing, use this instead" instruction. In every case the design system was present, so the browser used it and the leftover value was never reached — nothing looked different before this change and nothing looks different after it. They mattered because each one disagreed with the value it stood in for, so if one of those design-system entries were ever renamed or retired, the affected screens would quietly have started drawing someone's private colour instead of visibly breaking and being noticed. The worst example would have put a border on the secondary button in exactly the same colour as the button itself, making it invisible. The instruction to fall back is simply gone now, so a future rename fails loudly where it can be seen and fixed.
Fixed
- Confirmation dialogs and bottom action sheets now line up with the screen instead of the page column beside the navigation rail. On wide screens a dialog sits in the middle of the window again, and on a phone the action sheet spans the full width instead of running off the right edge.
Fixed
- A verified direct-squash approval can now be durably consumed only once. The Broker records the exact binding before provider dispatch, rejects expiry, replay, binding substitution, and storage uncertainty, and preserves the consumed marker after a crash or ambiguous downstream outcome.
Fixed
- A dispatcher deploy without an explicit target now fails in hosted CI before it can start a staging-host operation.
Fixed
- Dispatcher rollbacks now require the deployment identity they must restore. Automatic operation identities include the workflow attempt, so rerunning a deployment cannot collide with retained rollback evidence.
Fixed
- Staging dispatcher preflight and verification now select the revision running in the Gateway themselves. Operators no longer need to locate and enter a matching app-shared commit; an explicit deployment target remains required for a dispatcher rollout.
Fixed
- Read-only runner-dispatcher preflights and dry-runs no longer disappear while a staging deployment is collecting browser evidence. They run outside the exclusive mutation lane and still reject a changed Gateway revision.
Fixed
- The existing dispatcher staging operation validates its replacement configuration before stopping the current dispatcher and preserves the staging reservation journal mount when enabled. Diagnostic containers are retained instead of being automatically removed.
Fixed
- Buttons across the application were 38 pixels tall, below the 44-pixel minimum that accessibility guidance sets for anything meant to be tapped. On a touch screen a target that small is easy to miss and easy to hit by accident. Of the 67 buttons in the platform, 16 already met the minimum; the remaining 51 now do too. Among them: the plan activation buttons, the retry buttons on every page that can fail to load, and the full set of administration buttons for themes, MCP endpoints, policies, agent identities, the audit log, the ops dashboard and the CI runner console.
- The dismiss button on the "your recovery codes are running low" warning was about 20 pixels tall, and the "back to user search" link in identity administration about 19. Both are now 44.
- These were invisible until now because the automated accessibility sweep could not sign in: the test environment had no shared web address, so the sign-in step failed and every page behind a login quietly redirected to the sign-in screen, which passes. The sweep now refuses to report a pass on a page it was bounced away from, so a page it cannot reach is reported as unchecked rather than as fine. The plan page and the password-reset request page were added to the pages it checks.
- AppShared.Identity.UI is republished as 4.8.2 so the two identity fixes reach the other applications. Nothing in a consuming application needs to change.
Known gaps
- The administration pages are still not covered by the automated sweep. The account it signs in with is refused by all three administration permission levels, which is an account-setup problem outside this change; the buttons on those pages are fixed, but nothing yet stops them regressing.
- One control is still under the minimum and is deliberately not fixed here: the "drill" link in the shared KPI tile, which appears on the ops dashboard. It belongs to the shared component library rather than to this application, and correcting it there — where every application that uses the tile gets the fix — is a separate change.
The recurring check of the deployed estate now verifies that every launched app presents the shared platform shell — the common top bar and at least one visible navigation entry — on desktop and mobile. Until now only the platform's own pages were checked for the shell, so an app shipped without the common look and feel was never flagged. Non-conformant apps are reported together after all apps have been visited, so one failing app no longer hides the state of the others.
Fixed
- A failed staging sign-in journey now keeps the screenshot and video that explain it. The archiving step added in the previous release never ran: it was written so that any earlier failure in the same health check silently skipped it, and even when it did run it was pointed at a folder the next journey group had already emptied. Both are corrected, so the next failure can be diagnosed from the run instead of only from a timed-out wait with no further detail.
The daily check that walks the deployed applications in a browser now also opens each one the way a new visitor does: not signed in, arriving cold.
Every check before this one ran after signing in, so all of them described the view of someone who already had an account — while the first screen, where the impression that these applications belong together is made or lost, was never looked at. One application served a close copy of the shared frame, built in its own code, for months without anything noticing. All four pass the new check.
Preserve complete PostgreSQL-backed GitHub Event Ingestion maintenance evidence during hydration.
Fix PostgreSQL durable-evidence identifier handling so staging maintenance evidence persists after bootstrap.
- Added a controlled recovery procedure that checks policy compatibility before activation, preserves a recoverable backup, and supports interrupted operations. Recovery succeeds only after service health and an external agent read are verified. Existing service settings are preserved across container recreation.
Allow approved Factory agents to access the harness repository through the existing GitHub gateway without changing operation permissions. Apply the repository policy in isolation with the existing runtime image, verified readback, and rollback on failure.
Added
- The credential broker can now admit authenticated Gateway workflow-read continuations against the original persisted session while rechecking a fresh AIMS device proof for every request.
Fixed
- A targeted GitHub Gateway recovery now refreshes its approved automation policy before it restarts. Recovery therefore applies a newly signed policy immediately instead of retaining the one from the previous service instance.
GitHub Event Ingestion staging maintenance now verifies live authorization and the selected release before preparing its secured recovery directory. An unauthorized or mismatched request cannot alter that directory.
Fixed
- GitHub Event Ingestion staging deployments now verify the attested migration proof for the exact immutable image candidate, rather than requiring the migration and deployment to reuse the same one-shot operation ID. Rollback snapshots remain separately scoped to each deployment attempt.
Improved safe operational diagnostics for failed event-ingestion activation.
Adds a default-off, one-time receipt-bound broker seam for GitHub Actions live dispatch. It verifies the exact dispatch binding and permanently consumes the receipt before any future executor; default execution remains denied.
Fixed
- Bind Staging GitHub Event Ingestion host bootstrap to the immutable Gateway image that accepts a preserved empty predecessor schema with ACL metadata.
Fixed
- GitHub Event Ingestion staging activation now reports bounded, secret-free deployment-state failure diagnostics before safely rolling back.
Fixed
- GitHub event ingestion now checks its runtime database access before enabling the feature. A stale credential is rejected without restarting the service.
Fixed
- Staged GitHub Event Ingestion releases now reliably retain the security evidence that protects authorized rollouts.
Fixed
- The approved GitHub Event Ingestion deployment operation now supplies its read-only Actions token to the fail-closed cutover-attestation verifier. Default-off deployment no longer stops after admission because the nested maintenance process cannot authenticate to GitHub's attestation service.
Fixed
- GitHub Event Ingestion staging migration now verifies its retained cutover receipt attestation after it is recorded.
GitHub Event Ingestion now automatically retains the newest verified recovery point when repeated safe deployments use the same release candidate.
Improve the reliability of GitHub event ingestion maintenance operations.
Pin the GitHub Event Ingestion staging bootstrap to the attested Gateway image that safely binds the runtime database password.
Fixed
- GitHub event-ingestion activation now resolves its approved default-off deployment receipt from durable PostgreSQL evidence across separate staging workflow operations.
Fixed
- GitHub Event Ingestion staging migration now materializes its host-bound cutover receipt for immutable workflow attestation without widening host filesystem access.
Fixed
- Added a read-only staging inventory for ambiguous GitHub event-ingestion default-off deployment lineage.
GitHub Event Ingestion staging cutover evidence now persists across ephemeral runner replacement.
Staging event-ingestion operations now prepare and verify a missing rollback baseline before database transport setup. The required npm vulnerability gate now retries only bounded registry transport failures while keeping real findings fail-closed.
- Staging activation now deploys the attested control-plane image by its immutable digest, keeps the feature off until an explicit activation step, and retains a validated predecessor image for automatic or operator-driven rollback.
Fixed
- Updated a transitive validation dependency to remediate high-severity security advisories and keep protected merges eligible.
Route GitHub Event Ingestion in staging to the platform-private PostgreSQL network.
Fixed
- Prevent GitHub Event Ingestion updates from being blocked by its separately attested host-bootstrap helper.
Fixed
- GitHub event ingestion maintenance now preserves verified release identity information in durable storage before it begins. This prevents false missing-evidence failures and keeps recovery information available.
Fixed
- The staging event-ingestion preflight can now create its evidence directory through the Docker host even when /opt/staging has not previously existed.
Fixed
- The GitHub event-ingestion migration gate now provisions the same single PostgreSQL target database used by Staging and proves runtime permissions for application, Wolverine persistence, and queue schemas. This catches an invalid migration contract before an operator can use it.
Fixed
- GitHub Event Ingestion staging maintenance now keeps every retained backup, receipt, attestation, snapshot, and recovery record distinct. Earlier recovery evidence remains available while later cutovers are performed.
Wire the staging GitHub control plane for closed repository-webhook management and signed, default-off Actions owner policy injection.
GitHub Event Ingestion staging maintenance now uses the project-private PostgreSQL network, preventing shared-network service-name collisions during preflight and migration.
Fixed
- GitHub event ingestion staging maintenance now safely accepts PostgreSQL credentials containing valid punctuation.
Emit safe, actionable authority evidence when GitHub Event Ingestion refuses an invalid predecessor transport baseline.
GitHub Event Ingestion bootstrap now proves the preserved predecessor transport can be reached over password-authenticated TCP by the read-only migrator after ownership hardening.
Fixed
- Allow GitHub Event Ingestion updates to use the preserved predecessor transport safely when earlier maintenance evidence was produced by a prior approved release.
Fixed
- GitHub event ingestion staging maintenance now preserves the MassTransit predecessor transport while granting its one-shot migrator only read access needed for cutover evidence.
Staging event-ingestion bootstrap now transfers historical transport ownership to the neutral administrator, restricts the migrator to read-only access, and removes runtime access before migration work proceeds.
Prevent GitHub Event Ingestion from resolving PostgreSQL through a shared-network service name in staging.
Preserve a tested local rollback preimage when staging registry retention has removed the running Gateway image tag.
Classify GitHub Event Ingestion activation health failures through its bounded readiness contract without exposing container output or credentials.
Fixed
- Provision and verify the isolated PostgreSQL restore target before GitHub Event Ingestion restore validation, while keeping runtime access denied.
GitHub Event Ingestion can now safely retain a rollback image when a later staging deployment has pruned its local image record.
Staging now passes the GitHub Event Ingestion PostgreSQL runtime password as a separate secret value, preventing punctuation in rotated credentials from breaking the Wolverine startup connection string.
Improved protection for sensitive operational information.
Fixed
- The GitHub Event Ingestion staging deployment now supplies the gateway's database credential only inside the Vault-injected command. This lets the gateway restart safely while Compose validates unrelated observability services, without writing credentials to a staging environment file.
Added
- Prepared controlled event-driven GitHub workflow observation with safe readiness checks and a polling fallback, while keeping activation disabled until its separate operational approvals are complete.
- Kept the protected live-admission switch compatible with GitHub Actions configuration-variable naming rules.
Fixed
- GitHub Event Ingestion staging maintenance now preserves the gateway's internal authentication configuration while updating that component.
GitHub Event Ingestion staging operations now consume the canonical staging database credential and report stable fail-closed errors when required transport credentials are missing.
Fixed
- GitHub Event Ingestion staging activation now reports a bounded, secret-safe startup failure category when the service cannot become ready in time, allowing operators to diagnose a failed activation while preserving the automatic default-off rollback.
GitHub Event Ingestion staging maintenance now passes predecessor PostgreSQL credentials as typed, secret-safe configuration fields.
Fixed
- GitHub Event Ingestion Staging maintenance now requires a verified cutover receipt before it can deploy or activate the feature.
Strengthened GitHub Event Ingestion safeguards for the preserved predecessor transport.
Fixed
- GitHub event ingestion's Staging contract now uses the existing PostgreSQL database with dedicated application, Wolverine persistence, and Wolverine queue schemas. The feature remains off by default, and deployment and rollback operations retain the predecessor SQLite and MassTransit data only as protected migration sources.
Security
- Gateway request credentials are no longer retained in access-log records.
Prepare opt-in work-eligibility checks with strict request validation and explicit unavailable responses. The feature remains disabled until its required identity and data services are configured.
Store work-eligibility decisions and their audit records together. Failed or unconfirmed storage never authorizes a response. The database upgrade is additive and preserves existing data.
Read-session access now reflects current identity, team, role, device and release status. Revocation and policy changes take effect on the next check.
Adds a default-off approval-receipt verifier for shared-write credential-reference operations. Receipts require two distinct approvals, exact request and policy bindings, a short signed lifetime, and immutable replay protection.
The foundation uses synthetic in-memory evidence only. It does not issue approvals, contact providers, expose credentials or personal identities, persist records, open endpoints, or enable deployment or runtime admission.
Added
- Added synthetic-only credential broker authorization and dispatch-permit contracts.
- Added canonical replay IDs, exact authority bindings, and atomic in-memory transitions.
- No credentials, network access, persistence, runtime admission, or deployment are enabled.
Added
- Added opaque credential references with server-assigned identities, strict input validation, deterministic retries, and fail-closed in-memory pending-enrollment and revocation behavior.
- Accepted results reject inconsistent lifecycle pairs: pending_enrollment requires reference version 1, and revoked requires reference version 2.
- Revocation rejects timestamps earlier than the reference's recorded state, while an equal timestamp remains valid.
- Reference activation and access to providers, credentials, personal data, networks, persistent storage, endpoints, runtime admission, deployment, and production systems remain disabled.
Added
- Added the synthetic-only closed provider-proof contract and deterministic public-test-root verifier.
- Added global proof identity, immutable replay binding, and atomic in-memory provider transitions.
- No provider, network, credential, persistence, runtime admission, Staging, deployment, or Production behavior is enabled.
Add a default-off, in-memory credential-reference enrollment foundation that validates signed provider callbacks, three current provider proofs, and a matching approval receipt before an opaque reference becomes internally eligible. The public reference remains pending until it is revoked, duplicate provider-scoped locators cannot be recycled, and rejected or repeated callbacks do not expose provider locator values or partially mutate reference state.
This foundation performs no provider or network call, stores no credential or personal data, adds no endpoint or durable storage, and does not admit a runtime, host, deployment, staging, or production path.
Add a default-off, in-memory credential-reference provisioning foundation that validates an already accepted and current signed approval before allocating opaque server-owned reference and provider-operation identifiers. Equivalent retries return the original safe result, while changed approvals or request bindings fail without partial allocation. The public result contains only opaque identifiers, version, status, logical URI, and recorded time; it never exposes a provider locator, handle, credential, secret, or raw approval material.
This foundation issues no approval, performs no provider or network call, stores no credential or personal data, adds no endpoint or durable storage, and does not admit a runtime, host, deployment, staging, or production path.
Add the default-off, private registered-Staging reference persistence and synthetic provider checkpoint. The committed flag remains false and the slice delivers zero credentials; runtime admission and Production remain excluded.
Fixed
- The shared health-conformance component now compares a service's reported checks against the registrations of the very host that reported them. Two of its criteria add a check to a second copy of the service and then read the check list from the first, so a check the service registers only when its configuration supplies a dependency could be listed by one copy and not the other. The component reported that difference as the service having dropped a check from its health report — a failure that appeared only when a suite ran in full, and never when the affected criterion ran on its own.
Changed
- The three tests that boot the API in a production-like mode now supply their startup configuration to their own service instance instead of setting it for the whole test process. The process-wide route was previously believed to be the only one early enough to be read; it is not, and the narrower route removes the possibility of one test configuring a service another test is starting at the same moment.
Fixed
- The address monitoring uses to ask "is this still running?" now answers from a real check instead of from nothing at all. It previously reported a clean bill of health without consulting anything, so it would have said "fine" even about a copy that had stopped working — and the one question it exists to answer was the one question it could never get wrong or right. It now reports on whether the running copy itself is up, and still deliberately ignores the database and other dependencies, so a passing dependency wobble cannot get a perfectly healthy copy restarted.
- The separate address that asks "is this ready to take visitors?" now reports only on the dependencies it is meant to cover, rather than mixing in the running-copy check as well. The verdict it gives is unchanged; the answer is just no longer padded with something that was never able to fail.
Both addresses keep working exactly as before for anything already calling them, and nothing changes for visitors.
Added
- Identity email can now be sent through an authenticated relay, so confirmation and password-reset messages reach the recipient's own mailbox instead of a staging sandbox that only a reviewer ever opens. When the relay is not configured or cannot be reached the message still goes to the sandbox and the service keeps running, but every such send is recorded at Critical naming the missing settings — a degradation that is visible and countable rather than one that looks healthy from the outside.
Fixed
Identity mail addressed to a reserved domain such as .local or .test no longer goes to the real SMTP relay. Those addresses cannot hold a mailbox, so every such message was a guaranteed hard bounce against the live sending account — and enough hard bounces get that account throttled, which would take real confirmation and password-reset mail down with it. The staging deploy's own end-to-end journeys send to exactly such addresses on every run. They now go to the smtp4dev sandbox, logged at Warning rather than Critical, because nothing was degraded: no mailbox was ever going to receive them.
Fixed
- A registration, password-reset or account-unlock request no longer waits up to 100 seconds when the mail sandbox accepts the connection and then goes silent. The send now gives up after five seconds and reports the failure the way any other delivery failure is reported, so the person waiting at the form gets an answer instead of a page that appears to hang.
Fixed
- Package publication now reports a concurrent immutable-package conflict rather than silently treating it as a successful delivery.
Fixed
- Staging keeps the existing live GitHub Actions policy while admitting the separately signed, preauthorized CI dry-run policy in its own slot.
Changed
- Merge candidates run the selected Showroom checks in two isolated jobs with balanced exhibit groups. Admission combines their results only after verifying every expected exhibit, source revision, run attempt and successful test case. Failed or cancelled groups cannot produce a passing partial result.
Fixed
- The key ring maintenance routine now proves what it did. Its check afterwards read container logs, which the platform's containers do not serve — so the check reported "nothing yet, look by hand" on every run it ever made, and had in fact never once run. Each service now records what it found in the key ring where the routine can read it directly, and a run that cannot get that answer fails rather than passing with a note.
- A cleared key ring is no longer mistaken for a repaired one. The routine used to count only how many sign-in keys were left unprotected, and an empty ring has none — so losing the ring entirely and repairing it looked identical. It now requires a replacement key to be present and protected before it calls the run good.
- Each service writes its replacement sign-in key at start-up instead of waiting for the first person to sign in, so the state the routine reports on is the finished state rather than an empty ring that happens to be quiet.
- Sign-in keys that an earlier addressing mistake left in another application's store can now be removed. They are no longer read by anything, so clearing them signs nobody out; the routine refuses to touch a store any service still uses.
Fixed
- The component holding the sign-in key contract is now included when the running images are assembled. It had been added to the solution but not to the lists each image uses to gather its parts, and leaving something off those lists does not report a missing part — the assembly step skips it silently and the failure surfaces later, naming neither the missing piece nor the list it is missing from. A check now holds every image to the same rule, so the next component added below them is caught here instead of during an image build.
Changed
- Sign-in key maintenance now reaches every application, not only the platform. Each application keeps its own set of sign-in keys — those keys are the ability to issue that application's sign-in cookies, so no two applications share a set — while looking after them is now a single operator hand here, because the platform is where identity lives and because one maintenance hand per application is how one of them quietly stops being used.
Which applications it can act on is read from the shared application register, so an application is included from the moment it is listed there and nothing else has to be remembered. Sign-in keys that nobody looks after behave exactly like sign-in keys somebody does, right up until the moment someone needs them to have been looked after.
The register also records which key stores exist today and which are still being set up, so an application midway through moving is shown rather than omitted. Removing keys refuses the ones not yet set up, and refuses to act on more than one application at a time: seeing the whole estate in one run is useful, signing it all out in one run is not.
Removing keys now also asks for a confirmation phrase that names the application. The old phrase was the same every time, and a phrase that never changes stops being read after the second use.
Fixed
- The key ring maintenance job now establishes which key store the running services actually use, instead of assuming it. Its first two runs assumed, and the removal they performed emptied a store the services were not reading: it signed everyone out and left the exposure exactly where it was, while the services went on reporting the untouched original.
The connection is configured by a bare service name, and those names belong to the network rather than to the project that defined them — so on a shared network the name can resolve to another application's store entirely. The job now reports, per service, which networks it is on and what that name resolves to, and reports every candidate store on the host rather than only the one it was configured for. Reporting a single candidate is what kept the disagreement invisible.
Removal now refuses outright when the services do not all resolve to the store it is about to modify, and refuses when a service's resolution cannot be established at all — an unreadable answer is the case the check exists for.
Added
- An app can now tell the platform which page to open when someone launches it, instead of always being opened at its front page. Apps that only recognise a signed-in visitor on certain pages can point at one of those, so launching them from the catalog, from My Apps, or from the app switcher arrives as the person who is already signed in rather than as a stranger.
Fixed
- The three places that launch an app — the catalog's start button, the launch link, and the app switcher — each built the destination address separately, so the same app could be opened differently depending on which one was used. They now share one, and it refuses any destination that would lead away from the app being launched.
Fixed
- Warning and informational message text was too pale to read reliably on a light page. Measured against the light theme's page background, the warning colour reached 3.5:1 and the informational colour 4.4:1, both under the 4.5:1 minimum accessibility guidelines set for normal text — so a caution or an explanatory note was the hardest thing on the screen to read for anyone with reduced contrast sensitivity, on a dim screen, or in daylight. Both are now darker: warnings sit at 5.1:1 and informational text at 6.8:1. They are still recognisably a warning orange and an information blue, just deeper ones. The dark theme was already above the minimum and is unchanged.
- The same two colours are also used to fill warning and information badges and buttons, where they carry white text. That combination was below the minimum too — the warning fill sat at 3.8:1 — and the darker values lift it to 5.6:1, so the labels on those controls are now legible as well.
- The two shared packages that carry these colours to the other applications are republished so the repair actually reaches them: AppPlatform.DesignTokens 1.7.1 (the generated token stylesheet) and AppPlatform.Theming 2.1.1 (the offline fallback palette). Neither adds, removes or renames a token, so nothing in a consuming application needs to change.
Configured the staging Credential Broker with its required public root for validating signed live GitHub Event Ingestion dispatch approvals.
Live dispatch approvals now distinguish host access, environment migration, and deployment operations.
Added a protected, main-only staging workflow that signs and submits exact one-shot GitHub Event Ingestion live-dispatch admissions through the Credential Broker without exposing receipt or secret material.
Added
- Both platform services now report at start-up how much of the shared key ring is still stored without protection, rather than only that a protecting certificate is configured. The two are not the same thing: the certificate protects entries as they are created, and a service that already had a valid key carries on using it, so an installation can be fully configured and still hold every key unprotected for months.
This ring is the widest one in the estate — one ring shared by every application on the domain — so what it protects is every sign-in, every form submission and every passkey challenge anywhere on it. It had never been measured. The report says what is actually there so the size of the problem is known before anything is decided about it.
Nothing is changed or removed. The remediation the other two services got does not transfer: one needed a code migration because it stores secrets under its ring, the other could simply discard and restart, and clearing this one would sign everyone out of everything at once.
Fixed
- Changes no longer get pulled out of the merge line by test failures that have nothing to do with them. Three separate causes were doing this: a database contention error arrived wrapped in another error and so escaped the retry that was meant to absorb it; several build steps fetched shared configuration without pinning it, and hit rate limits when many changes were checked at once; and two tests deliberately filled a shared in-memory index to its ceiling, which starved any unrelated test that happened to run alongside them. Between them these accounted for the bulk of the retry cycles spent re-running work that had already passed.
Fixed
- Security updates have been applied to two development-tool dependencies. The application runtime is unchanged.
The dependency-vulnerability check that runs before changes are merged now covers every JavaScript package in the repository. One package was outside its scope and had gone unchecked, so advisories affecting it accumulated without anything failing. Those advisories are resolved in the same change by updating the affected build-time development tools. No runtime component is affected and there is no change to application behaviour.
Changed
- The shared-package publishing pipeline's feed-existence check now logs detailed diagnostics for every dependency it examines, instead of only on failure. This is a diagnostics-only change with no effect on which dependencies pass or fail the check. It exists to capture real evidence the next time the check reports a dependency as missing from the feed when it is in fact already published, a false-negative failure class that has recurred twice in production and remains unresolved.
Ensure shared package releases correctly recognize a dependency version that is already published on the package registry.
The publish check now uses the registry's documented address format and the correct registry identity, so it no longer blocks a release by reporting an already-shipped package as missing.
Added
- Agent Email Proxy can now sign people in with their platform account, the same way WoDVTT and CMS Lite already do. It is registered as a first-party application that proves itself without holding a stored credential of its own, and a signed-in person can only ever be returned to one of that application's own web addresses.
- Signing in there now also yields a credential that Agent Email Proxy's own back end accepts, so a signed-in person's actions are recognised rather than refused.
Changed
- Signing in to WoDVTT and CMS Lite is unchanged by the addition.
Fixed
- Administrative changes saved in the content-management application are accepted again. Sign-in now issues that application an access pass that names the service the change is sent to, so the service can recognise it instead of turning the change away as unauthenticated.
Removed
- The interactive tour is no longer registered as a sign-in application. It is a public site with no login of any kind, so the registration granted a return path to an application that has nothing to return to.
Changed
- The rules that decide how an application keeps its sign-in keys — where they are stored, how they are encrypted where they sit, and what each running copy reports about them at start-up — are now published once and used by every application, instead of living inside the platform where only the platform could reach them.
Every application in the estate has to answer the same question the same way, because the operator hand that looks after those keys asks all of them and believes what it is told. Four separate copies of that answer would not disagree loudly; they would disagree quietly, and the run that checked whether a key cleanup had worked would report success for an application it had not actually established anything about.
Nothing changes for anyone signed in to the platform: it now reads the shared rules rather than its own copy of them, and its keys, its store and the boundary around them are exactly what they were.
Fixed
- The sign-in service now clears out its own spent sign-in records instead of keeping every one ever issued. Each sign-in to a connected application wrote records that nothing ever removed, so the store grew for as long as the service ran and would eventually have slowed sign-in down and consumed disk without limit. Only records that are already finished with — expired, signed out, or replaced — are removed, and only after a grace period, so no one is signed out and a recent problem can still be investigated. How long that grace period is, and how often the clear-out runs, are both settings.
- Removing the throwaway accounts left behind by the automated staging checks now also removes their sign-in records. Those records were tied to the account only by an identifier, so deleting the account left them behind permanently — the routine clear-out above cannot reach them, because a record belonging to a deleted account never expires on its own.
Added
- The one-time cleanup of shared key ring entries written before the protecting certificate existed can now be carried out. Reporting that those entries were still unprotected arrived last release, but there was no way to act on the report: the work has to happen on the staging host, and nobody — operator or agent — has a shell there. It is now a dispatched maintenance job that inspects the ring and, once told the consequence out loud, removes the unprotected entries and restarts all three tiers that read it.
The removal is a single atomic operation that can reach exactly one entry store and removes only entries that fail the encrypted test, so a mixed ring keeps everything already protected. It refuses to run at all when no protecting certificate is configured — in that case the replacement key would be written unprotected as well, leaving things exactly as exposed as they started, in exchange for signing every user out.
The restart is included rather than left to whoever runs it. A running service keeps its key ring in memory and carries on signing with a key that has just been removed, so a removal on its own looks like it did nothing and then signs everyone out at whatever later moment the service happens to recycle.
- The Showroom now reports its key ring at start-up like the other two tiers. It reads the same shared ring, so it was a third witness to the same question and the only one that could not answer it.
Fixed
- Package release allocations can pass the change-fragment check when their packed changelog is the exact generated output of the already declared source changes. Altered history, provenance, versions, and new package payload still require valid evidence.
- Updated the affected Stylelint dependencies to clear the dependency audit that was blocking release allocations and other pull requests.
Fixed
- Serialized package publication now honors its immutable allocated version even when a package keeps its declared version local to its project file.
Added
- Added an hourly, one-minute package-publication dispatcher that delegates pending releases to the serialized publisher.
Fixed
- Retried package-release finalization now continues after an already-published no-op state.
Package changes now declare release intent through validated change fragments, preventing concurrent pull requests from colliding on package versions.
Fixed
- Private package-publication automation now declares its required squash merge method explicitly.
Fixed
- Retried serialized package publications now continue to finalization after an already-recorded exact package push.
Fixed
- Private test-feed publication now receives its repository-scoped workflow token reliably during automated runs.
Fixed
- The private package test feed now uses the workflow's repository-scoped token, so it needs no broad personal access token and cannot use the production synchronization credential.
Fixed
- Package publication now uses an isolated private test feed with explicit repository authorization, keeping test releases separate from production packages.
Fixed
- Shared package releases now have a deterministic, ledger-backed planning foundation. The planner allocates one exact next version after merge, keeps coupled Health and Identity packages aligned, and refuses stale or already consumed change fragments instead of silently reusing a version.
Fixed
- Shared package release allocations now materialize a committed, digest-bound provenance record and exact package changelog sections before publication can begin. The publisher-facing verifier rejects missing, stale, or forged allocation evidence.
Fixed
- An administrator can now choose one of the two built-in themes as the platform default. Previously only a self-authored theme could hold that role, which left a trap: retire the one custom theme an environment had, and there was no way to nominate a replacement. The administration screen would then report that no default was set and advise creating a custom theme — the very step that had introduced the problem in the first place. Both built-in themes are now offered alongside the rest, and everything else about them stays protected: they still cannot be renamed, edited or switched off.
- The message shown when no default is set was also wrong about the consequences. Nothing breaks without one — the platform simply uses the first available theme — and the message now says so instead of implying an outage.
Changed
- The dark theme is now the one an environment falls back to when no default has been chosen. It previously fell back to the light theme. This only affects visitors who have expressed no preference of their own in an environment whose administrator has not nominated a default; a saved personal preference and an administrator's explicit choice both still win. Anyone wanting the previous behaviour can nominate the light theme as the default, which is exactly what the fix above makes possible.
Fixed
- The built-in light and dark themes now follow the palette that ships with each release. They were written into the database the first time an environment started up and then left alone forever, so every later colour correction stopped at the database's door: the stored copy overrode the shipped one, and what people actually saw was whatever palette happened to be current on the day that environment was first installed. Staging was still serving a light theme built from the dark colours on a white page — its warning text measured 1.9:1 against the page, far under the 4.5:1 minimum accessibility guideline for normal text, and its informational, error and success text failed too. The previous release's contrast repair was correct and simply never reached the screen. Each startup now compares the stored built-in themes against the shipped palette and updates them when they differ, so a colour fix takes effect on the next deployment of every environment.
- The same repair now also runs during the pre-deployment step used by production. Production took a different route through startup than the other environments and was never handed the built-in themes at all — so it would have missed this correction entirely, and had in fact never received the built-in palette in the first place.
- Themes an administrator created are untouched by this. Only the two built-in themes are managed this way, and those are the two nobody can edit — the administration screens have always refused to change them. Which theme is the default, and whether a theme is switched on, also stay exactly as the environment has them; a startup never makes that decision.
Added
- A contrast check that measures the themes an environment is really serving, rather than the palette in the release. The existing check reads the shipped stylesheet, which is why it stayed green for months while staging served failing colours to every visitor — it was answering a question about the release when the question that mattered was about the deployment. The new one reads the live theme stylesheet from a running environment and measures every theme it finds, including ones an administrator created that no release-time check can see. It is run on demand against a chosen environment, and reports each theme and colour pair that falls below the accessibility minimum.
Fixed
- Signing in with a passkey now refuses a repeated sign-in attempt that re-sends an already-used challenge, and keeps refusing it. Previously the refusal alternated, so a repeated attempt could still go through until the challenge aged out. This applies only to deployments that have opted in to server-signed passkey challenges; the default configuration was never affected.
Fixed
- GitHub Event Ingestion staging-operation evidence now survives ephemeral GitHub runners by being retained in PostgreSQL, while the cutover receipt's provenance remains independently verified through GitHub attestation.
Fixed
- The staging email preview remains accessible with the existing operator credentials after service restarts.
The personal theme editor can now export your theme to a file and import one back.
Export gives you your own colours as a small JSON file. Import takes one and, before it replaces anything, shows you what it would do: which file you picked, how many of your colours would change, and a preview you can apply or discard. Nothing is written until you agree — choosing a file is not the same as accepting it.
A file that cannot be used is refused with the actual reason, shown as the service worded it rather than as a generic failure. A theme file written by a newer version of the app is told apart from one that is simply damaged, because only one of those is worth trying again.
Sharing a theme has always been a file rather than a sharing feature inside the product; this is the pair of buttons that finally makes that work from the editor.
Added
- Signed-in users of a connected app (for example WoDVTT) can now reach their platform-stored personal theme using the sign-in they already have with that app, instead of needing a separate platform credential. Reading, writing, deleting, exporting and importing a personal theme all work the same way as before; only who counts as "signed in" widened.
- Deployment note: a connected app's OpenID Connect client registration must list the platform's personal-theme resource (platform.personal-themes-api.v1) in its configured resources before its users can reach these routes. Without it, the app's users can still sign in everywhere else, but their platform-stored theme stays unreachable exactly as it was before this change.
Added
- Added a default-off Broker/AIMS admission boundary for bounded pilot work-item writes, binding each request to an enrolled device, an execution key and an explicitly admitted work item. Gateway integration and installed-agent rollout are required before the pilot can use this boundary.
Fixed
- Keep the staging deployment lane reserved through account journey verification, using the selected deployment source and eliminating the separate automatic E2E trigger.
Pop-up confirmation messages are now readable in the light theme. The message box kept a dark background whatever theme you were using, while its text followed the theme — so in the light theme a dark message sat on a dark box and was very hard to read, with a close button that was close to invisible. The box now takes its colour from your theme, and the kind of message (success, warning, error) is shown by a coloured bar down its edge.
Added
- The production environment can now be brought up. Production previously had no way to supply the security material the platform requires there, so it refused to start by design. Every one of those values now has a defined place to come from, and an operator checklist states what must exist before the first start, how each item is obtained, and what the start-up failure looks like if it is absent — so a failed first start names the missing item instead of leaving it to be guessed.
- Production now keeps the shared state that sign-in depends on across a restart. Until now that state was held only in memory, so a routine restart would have signed everyone out and invalidated form submissions in flight, without reporting anything.
Changed
- The production setup script no longer makes up its own security material. Material of that kind has to be the same on every machine and survive restarts, so it is now prepared once by an operator and read from where it is held. The script explains that instead of producing a throwaway value that the platform would have refused anyway.
Every shared package this repository publishes now has a changelog, and the publish itself refuses to run without one. Six of the eight packages had no changelog at all, so anyone installing them had no way to tell a bug fix from a breaking change; those six are now written, covering every version each package has ever released. For the seven packages that do not ship their changelog inside the package, correcting or extending it no longer counts as changing the package, so documentation can be improved without issuing a release nobody asked for.
Fixed
- Scheduled shared-package publication now parses release allocation metadata correctly instead of failing before any package is packed or published.
Fixed
- Shared-package publication now retains the reviewed version-stamping tool while recovering an immutable package-source allocation, so a historical allocation cannot reintroduce a publisher defect that has already been fixed on main.
Fixed
- The shared-package publisher's tooling snapshot, which keeps a pinned release building against current build scripts, previously covered only one script by name. It now covers every script the publisher runs while building a pinned release, so a fix to any of them takes effect on the next retry instead of only the one script that was protected before.
Changed
- The shared UI, theming and identity UI packages are republished at a new patch version. Consuming applications get no behaviour change from this release: the package contents and their declared dependencies are unchanged, and the version moves only so consumers are not left pinned to a version that no longer matches what the platform builds against.
Added
- Platform builds now fail when a Radzen component is given a setting name that component does not actually accept. Previously such a setting was silently dropped and rendered as a plain markup attribute, so the intended styling or behaviour never took effect while the build still reported success. Misspelled or removed settings are now caught before release rather than surfacing as a control that quietly looks or behaves wrong.
The readiness answer is now capable of reporting a problem on every part of the platform, rather than only on the part that talks to a database.
Readiness is reported over a set of checks. Where a part of the platform registers no checks of its own, that set was empty — and an empty set is always well, so the answer came back the same whether the part was healthy or not. Automated monitoring read a steady all-clear it could never have read any other way. The part's own state is now always included, so the answer reflects something real; where checks are already registered, they continue to be reported alongside it and nothing about that changes.
This affects what a monitoring reader sees, not what it asks for: the addresses, their responses and their meaning are unchanged.
Fixed
- A future change that reintroduced the sign-out and session defects fixed in the previous release is now caught while it is still under review, instead of reaching people and looking like working software.
Fixed
- Staging health checks now validate anonymous registered-app redirects through a bounded, trusted path instead of accepting any redirect response.
Fixed
- A device you asked the platform to remember is no longer signed out early. The clean-up job that ends long-untouched sessions was set to a period shorter than the one a remembered sign-in is promised, so a device left alone for a couple of weeks could be signed out well before it should have been. The period is now set past the full length of a remembered sign-in, so leaving a device unused no longer ends the session ahead of time. Sessions that have genuinely finished are still cleared away as before, and the limit on how many devices one account keeps signed in is unchanged.
Fixed
- Four quality gates that drive a browser no longer stall for a quarter of an hour and then give up while trying to download operating-system packages the machine already has. The stall blocked unrelated work twice in one afternoon, and nothing in the resulting failure named the cause.
Theme switching, selection and packaged theme styles now have mandatory checks before merge. These checks run against a local Showroom and do not wait for shared Staging. Missing or skipped checks prevent a successful result.
Changed
- Retiring a sign-in client now actually removes it. Until now an application could be taken off the list of apps allowed to use platform sign-in, and its registration would quietly stay behind on the sign-in service — including the return addresses it was allowed to be sent back to. A retired client is now named as retired and its registration is deleted, so an app that no longer uses platform sign-in no longer holds a usable one.
- The Interactive Tour is the first client retired this way. It serves only public content and stopped using platform sign-in some time ago; its leftover registration, and any consent and sign-in sessions still recorded against it, are removed.
- Deleting a client registration is now recorded in the identity audit trail, naming the client, so the removal can be reconstructed later from the audit history rather than only from service logs.
Fixed
- Removing a client can no longer affect an app that is still using sign-in. A client that is both in use and named as retired is refused twice over: the service will not start on that contradiction, and the removal step declines to act on it. Retiring nothing is likewise inert — it never removes registrations that were simply not on the list.
- Signing in to WoDVTT and CMS Lite is unaffected.
Checks that install the project's JavaScript dependencies before merging are now required to run whenever those dependencies change, and a new automated check enforces that rule for all of them.
Several such checks read the dependency manifests on every run but were not started by a change to them, so a dependency update could alter what they validate without the checks ever running. Each occurrence so far was found by hand after the fact. The rule is now verified automatically, so a new occurrence fails a check instead of going unnoticed, and the one check still affected has been corrected. No runtime component is affected and there is no change to application behaviour.
Added
- Apps that sign their users in at the platform can now keep a session working for as long as it is meant to last, even while the person leaves a page open for hours. Until now such an app had no way to record the fresh credential the platform hands back each time it renews a session, so a second browser tab could present a credential that had already been used up. The platform treats a reused credential as a sign the session may have been stolen and can end every one of that person's sessions in response — which is the right instinct, but it was being triggered by ordinary use.
- The shared building block for this keeps a signed-in session on the server rather than entirely in the browser cookie, and encrypts it where it is stored. Apps opt in one at a time; nothing changes for an app that does not.
Changed
- The shared database groundwork an app inherits now comes in two shapes: one for the platform itself, which issues sign-in credentials, and one for an app that only accepts them. An app of the second kind no longer has to carry a table it can never put anything in. Existing apps are unaffected until they choose to move.
Enabled automated CI capacity without distributing personal access credentials to the scheduling service.
Runner readiness checks now stop reliably when a request fails or returns an unexpected response status, preventing false successful preflight results.
Strengthened automated CI capacity rollout checks so runtime identity drift is detected before scheduling services are changed.
Changed
- Staging runner maintenance now resolves the selected release automatically and uses one GitHub staging approval for live actions.
Fixed
- Automated CI capacity now authenticates without distributing personal access credentials to the scheduling service.
Secondary badges are now readable whatever colour an application uses for its secondary accent. The shared stylesheet pinned every filled secondary badge to that accent with white lettering, and did so in a way the application could not override. White reads well on the platform's own teal; on a brighter accent it did not, leaving badge text that could not comfortably be read and no way to correct it.
The cause was that the bridge between the design system and the underlying component library never connected the secondary colour, which is why the badge had to be pinned by hand in the first place. That connection now exists, and it carries the label colour as well as the fill — so each theme's badge lettering is chosen against that theme's own accent, exactly as it already is for buttons.
The platform's own appearance is unchanged: secondary badges keep the colour and the white lettering they had.
This ships in AppPlatform.DesignTokens 1.8.1. An application on an earlier version keeps the old behaviour until it raises its pin.
Added
- An operator can now declare the sign-in request limiter authoritative for a given environment, meaning it must count centrally and is never allowed to fall back to each instance counting on its own. When that declaration is made, the service checks at startup that the shared counter is actually configured and answering, and refuses to start if it is not — naming what was declared and what is missing. Refusing to start is the safer outcome here: an authoritative limiter with no reachable counter would turn every guarded request away, and doing that after a successful start looks like an application fault rather than a configuration one.
- The same check refuses a configuration that asks for an authoritative limiter while leaving central counting switched off. That combination cannot do what it claims — a limiter counting separately in each instance is not authoritative — and previously it would have started and quietly counted per instance under a setting promising the opposite.
Nothing changes for existing deployments. No environment declares the limiter authoritative today, and without that declaration the service starts exactly as before, including when the shared counter is absent entirely. The limiter also keeps its existing behaviour during an outage in that default mode: it carries on enforcing a bounded limit in each instance rather than letting requests through unchecked, and returns to central counting on its own once the shared counter is reachable again.
Changed
- The seed endpoint and the startup seed routine now read the two-factor seed account's shared secret from the service's configuration rather than straight from the process. How it is supplied in a deployment does not change — the same environment variable, under the same name, still reaches both readers — and one secret is now read one way instead of two.
Fixed
- Shared package publication now uses one serialized, ledger-bound publisher instead of duplicate-tolerant legacy push paths.
Improved
- The reusable session-chat component now preserves the complete interaction contract needed by product consumers, including reliable delivery feedback, message actions, readable grouping, and responsive long-message layout.
Fixed
- The reusable WoDVTT chat panel now uses the platform's declared action and text tokens. Its own-message accent, selected reactions, new-message control and composer action therefore render with the active light or dark theme rather than silently losing those styles.
Fixed
- Session chat now supports touch actions and reliable keyboard navigation.
Fixed
- The reusable WoDVTT chat panel's avatars now use the platform full-radius token, keeping their circular shape while complying with the shared design token contract.
Adds the publishable AppPlatform.WoDVTT.Chat.UI package with the reusable session-chat panel and typed host contracts.
Fixed
- Loading older conversation history now keeps the reader at the same visible message instead of moving the transcript unexpectedly.
Fixed
- The shared chat panel's "Message failed to send." notice is now easy to read on your own highlighted messages in both light and dark themes, and the chat error message is a little easier to read too.
Fixed
- The shared chat panel keeps rendering when a new message, an edit or a deletion arrives, instead of replacing the page with an error notice.
Fixed
- The shared chat panel no longer freezes the page when it is closed or replaced while it is still loading.
Fixed
- The shared chat panel's pending "sending" message text is now easy to read on your own highlighted messages in light theme.
Changed
- The shared chat panel's buttons, delete confirmation and mobile action sheet now use the platform's standard Radzen controls and dialogs, so they look and behave like every other platform dialog. The platform moves to Radzen.Blazor 11.4.3.
Changed
- The rule deciding where a theme choice is stored so it can follow you between the different addresses the platform is served from is, of necessity, written out in four places: it has to run before a page paints, which rules out sharing it the usual way, and one copy lives in a separate application. All four are now checked against each other automatically. If any one of them is edited on its own, the build fails and names the file — previously they were only asked to match in a comment, and a copy that disagreed would have quietly stopped theme choices carrying across, with nothing reporting a problem.
Fixed
- Publishing a new shared library package could be incorrectly blocked because an already-published, currently-shipping version of one of its dependencies was reported as missing. A correctly published dependency no longer blocks the release.
Fixed
- Fixed a configuration gap in one of the platform's shared packages that was blocking an entire scheduled package release from completing, including a previously shipped package correction that had been unusable since it first went out. Both are now able to publish successfully.
Readonly trait ratings and large blood-pool tracks remain compact and legible on touch screens, while editable controls retain full-size touch targets.
Fixed
- Release notes now keep a bullet that wraps onto several lines together as one list item, instead of cutting it off mid-sentence and showing the remainder as a separate paragraph.
Added
- The shared hosted-app shell now offers optional page header, footer, and error-message slots, plus a way to gate a page's content behind a permission check while keeping the navigation rail and top bar visible. Two new shared building blocks are available for apps that build on the shell: a sign-out menu with an avatar or initials, and a navigation entry that can show a count badge (for example, an open-jobs count). These additions are purely additive and do not change how any existing shell-based page behaves.
Showroom dialogs now stay inside the screen on mobile devices. Icon buttons announce their action, and code examples support keyboard scrolling with a visible focus indicator.
Fixed
- Alerts in the Showroom now show their own severity again. A Showroom stylesheet was painting the same accent stripe on every alert, so an error, a warning, a note and a confirmation were indistinguishable from one another in both the light and dark themes. Since the Showroom is where components are reviewed and signed off, this made the alert's approved appearance wrong for everyone comparing against it. Alerts in the products themselves were never affected.
Fixed
- The Showroom's catalogues can be reached by clicking again. Its component and pattern catalogues, the token, theme, icon and asset galleries, and the reference pages had all dropped out of the side navigation, so the only way to open any of them was to know and type its address. Anyone opening the Showroom to look at a component found an apparently empty one instead.
Changed
- The Showroom ChatPanel exhibit now shows chat package 0.4.2 and checks its longest-text review state with accented pseudo-localized text.
Changed
- The Showroom ChatPanel exhibit now shows chat package 0.4.4, whose heading wraps very long words instead of overflowing on a phone-width screen.
Added
- The Showroom Component Catalog now includes the shared ChatPanel with populated, delivery, read-only, loading, error, and message-action review states.
Design system component lifecycle is now recorded in one place. The lifecycle of a reusable component (draft, candidate, approved, retired) lives with the component contract that describes it, rather than being repeated on the showroom exhibit that displays it. Nothing changes about which components are available or production eligible.
The agent-facing design context published alongside the design system now reports candidate and approved counts for components rather than for exhibits, under key names that say so. A tool that read the previous exhibit-based counts should read the component counts instead.
Fixed
- Preserve accessible grid roles and keyboard focus in current Radzen tables while retaining compatibility with earlier table markup.
The Design System Showroom now opens with a catalog overview. Reviewers can reach every catalog and the design-preview overview from one clear starting point.
Fixed
- The Showroom icon gallery now displays the platform's existing icons, searchable names and usage guidance, replacing its unfinished placeholder.
Prevent affected interface changes from being accepted when shared components have known visual defects.
Fixed
- The WODVTT Trait Controls preview now shows a 25-box blood-pool example as two rows of ten and a final row of five, so reviewers can verify the partial-row layout directly.
Fixed
- The WODVTT Trait Controls preview now gives its specialisation action a usable target and visible keyboard focus. The action adds and resets preview entries, with English and German labels.
Added
Where the component gallery previews a building block another application can actually use, it now shows the few lines of code that produce exactly what is on screen, with a button to copy them.
Until now the gallery could show what a block looked like in each of its states and nothing else. Anyone wanting to use one had to go and find how it was written somewhere else, which is the part of the job the gallery exists to save. That is answered for the entries where it can be, which today is a minority of them — most of what the gallery displays turns out to be a stand belonging to the gallery rather than a block any application could reference, and those now say so plainly instead of offering code that names something unreachable.
The code is not written by hand and kept current by someone remembering to. It is worked out from the same record the preview itself is drawn from, so the two cannot disagree, and switching state changes both together.
Deriving it turned out not to be enough on its own to make it correct, and the gallery now says so where it applies. Where the code shown would not actually work if pasted — because the setting involved has no form that can be written as plain text, or because the block being previewed is a display stand belonging to the gallery rather than something any application can use — the panel explains that instead of handing over something misleading. Where the state being previewed is the light or dark setting of the page rather than a property of the block, it says that too, so two identical snippets under two different-looking previews read as the truth rather than as a mistake. And the deliberately abusive test states, which exist to make text overflow visible, are previewed but not offered for copying.
Fixed
- The keys that protect a signed-in session are now kept in the platform's own supporting service rather than, intermittently, in one belonging to a connected application. The platform addressed that service by a name that more than one application on the shared internal network answered to, so which store received the keys could change whenever services were restarted. The name is now unambiguous, which also means the durability work in the previous release actually takes effect: it had been protecting a store the platform was not always using.
Security
- Session-protection keys were reachable in a connected application's store, and were held there unprotected. Anyone able to read that store could have derived signed-in sessions for the platform. The keys now live only in the platform's own store; the stray copy is removed as part of the cutover, and every session in existence beforehand is ended once so that nothing issued under the exposed key remains valid.
Fixed
- A platform host that signs its single-sign-on tokens with throwaway keys now says so the moment it starts, instead of only in staging logs no one reads. Such a host mints a new key every time it is redeployed, which makes the first sign-in to each connected app after every deploy fail and the next one succeed. The warning previously covered production only, so on staging the condition was invisible.
- The daily staging journey check now names the reason a connected app failed to open. When the app answered the browser with a server error and an empty page, the check reported only that the page was blank, and the server error it had already observed was discarded. It now reports the page's own response status alongside every failed request it saw.
Fixed
- "Sign out all other devices" no longer refuses every time it is used. The page could not tell which session you were reading it on, so it declined the whole operation rather than risk signing you out along with everyone else.
- The sessions page no longer lets you sign yourself out from it. The guard that reserves the current session for the ordinary sign-out could not fire while that session was unidentifiable.
- Signing out now ends the session everywhere, not just in the browser that asked. The sign-out cleared the browser cookie but left the underlying session credential usable until it expired on its own.
Fixed
- Signing out now ends the session everywhere it counts. Choosing "Sign out" cleared the browser's sign-in cookie but left the underlying session token valid until it expired on its own, so the sign-out was less complete than it looked. Signing out of all devices was unaffected.
Fixed
- Approved live workflow requests can now be admitted through a dedicated, authenticated path while preserving the signed approval’s exact request.
Fixed
- Signing in with a Microsoft work or school account can no longer claim an email address the account does not own. Microsoft lets whoever runs an organisation's directory type any address they like against a user, and does not check that the organisation owns it — so treating that address as proven let someone set up their own organisation, put a stranger's address on it, and be handed a new account already carrying that address as confirmed. Microsoft sign-in still works for anyone who has already connected their account, and anyone else can connect one from their account settings after signing in normally; what no longer happens is a brand-new account being created out of an address nobody verified.
- Resolving the "this address already has an account" screen now asks for the second step when the account has two-step sign-in switched on. It only asked for the password, so someone who knew the password alone could get in past a protection the account owner had deliberately turned on — and could permanently attach their own sign-in method while doing it, which kept working even after the password was changed.
- Wrong passwords on that screen now count toward locking the account, a locked account is refused there as it is everywhere else, and the screen sits behind the same attempt limit as the ordinary sign-in form. Until now it counted nothing and limited nothing, so it was a quieter place to guess a password than the sign-in page itself.
Fixed
- Replacing the key the platform signs sign-in tokens with no longer logs everybody out. Until now the platform trusted exactly one such key at a time, so the moment a replacement went live every token already in someone's browser stopped being accepted and every signed-in person was bounced back to the sign-in screen. The scheduled replacement the operations runbook asks for twice a year could therefore only be done in an announced outage window.
- An operator now names the outgoing key alongside the incoming one for as long as the changeover needs, and both are accepted while people's existing sessions run out naturally. Nobody is signed out, and nobody has to notice the changeover happened.
Added
- The public list of keys the platform publishes for others to check its tokens against now carries the outgoing key as well as the current one, and tells whoever fetches it not to hold onto that list for more than five minutes — long enough to be cheap, short enough that a changeover is picked up promptly.
Refuses to start on
- A changeover described incorrectly — the outgoing key named as if it were the current one, the same key named twice, or a key that cannot be read. Each of these would look like a clean start and then sign everyone out at the worst possible moment, so the platform stops and says which entry is wrong instead. No key material appears in the message.
Fixed
- Signing a device out of your account is now recorded once in the security history instead of twice, so the list of what happened to your account matches what you actually did.
- When an administrator ends one of your sessions, the security history no longer also records it as something you did yourself. "Who signed me out" now has one answer instead of two contradictory ones.
Fixed
- Signing out of the website now ends only your own session. It previously ended whichever session the request happened to arrive carrying, without checking it belonged to the person asking — which was only ever safe because of how requests happened to be routed, not because anything verified it.
- A sign-out that cannot be attributed to anyone no longer ends a session on the strength of the credential it arrived with. It records why in the logs instead, and still signs the browser out.
Apps that sign out by handing back a sign-in token they hold themselves are unaffected and still work: there is no account to check the token against, and such an app can only end the session it already had.
Fixed
- "Sign out all other devices" now refuses, and changes nothing, when the request cannot be shown to come from one of your own signed-in devices. It previously went ahead on the strength of whichever sign-in token the request happened to arrive carrying — and if that token was not yours, "all other devices" included the one you were sitting at, so you were signed out of everything by a button that says the opposite.
- The "This device" marker is decided the same way, and was unreliable for the same reason. It now works from a session confirmed to be yours, or is left off.
The related rule that stops you ending your current session from the list still cannot apply to a request like this — there is no confirmed current session for it to protect — so on such a request that session remains endable from its own row. The worst outcome is signing yourself out of the device you are on, which is why this records the situation rather than refusing the whole page.
No one could ever reach another person's sessions this way — the list you see and the sessions you can end have always been limited to your own account — so the harm was only ever to the person asking.
- A request arriving with a sign-in token that is not the caller's is now recorded as exactly that. It used to look like an ordinary healthy request, which is what left an earlier occurrence of this on staging with nothing to explain it.
Fixed
- Skipped follow-up checks no longer displace a waiting staging update, while real acceptance checks retain the existing reservation within the platform deployment lane.
Staging IAM email validation now verifies its mailbox access before browser journeys, reporting environment drift directly instead of timing out as a product defect.
Added
- The staging email sandbox now reserves one address whose delivery always fails, so end-to-end checks can exercise the failed-delivery state and the resend journey. Every other recipient continues to be accepted.
Security
- The staging agent email proxy site now requires sign-in credentials before it shows anything. Its mailbox and agent-credential screens were previously open to anyone who knew the address, which is not acceptable for screens that display a freshly issued credential on screen.
- Automated availability checking is unaffected: the site's health addresses still answer without credentials, so an operator still sees a real outage as an outage.
- Protecting a staging site takes no manual work on the server. The credential is applied by an operator-run update to the staging entry point, and a credential that cannot be retrieved stops that update instead of publishing the site unprotected.
Changed
- Sign-in throttling on the staging environment is now counted in shared storage rather than in the running copy's own memory. Staging is updated several times a day, and each update replaced the running copy and with it the record of how many attempts had already been made, so the attempt budget started over every time. It now carries across an update. The behaviour operators see is otherwise unchanged, and if the shared store becomes unreachable the throttle stays in force per running copy rather than being lifted. Production is unaffected by this change.
Security
- Recorded, next to the code they govern, why two machine-facing endpoints are reachable without a signed-in user and exactly what does control them. Neither endpoint's behaviour changed; the reasoning and the reviewed inventory of such endpoints were previously easy to misread, which is the condition under which a genuinely open surface gets waved through.
Fixed
- Staging browser checks now identify both the API and web build before running. Missing web revisions and builds that change during the checks invalidate the result instead of attaching successful browser evidence to the wrong build. Test-only changes remain eligible when the existing build classifier confirms that each service's build inputs are unchanged.
Changed
- A routine staging update no longer prints a run of "variable is not set" notices it could do nothing about. Seven settings were named in the deployment description but never supplied by anything; each one produced a notice on every single step of every deploy. The settings that were doing no work have been removed, and the ones belonging to a service that is reserved but not yet switched on now state plainly that they are meant to be empty for now.
- The observability site keeps being protected the way it already was — by its own sign-in — rather than by a second, unrelated password at the front door. The front-door setting had been described for years but was never actually in place; the description now matches what is really running. Putting one in would also have locked out the routine checks that read from that site to decide whether a release may go to production.
- The note explaining how staging containers get updated said an operator console performed the update. It does not, and has not for some time — the update comes from the release pipeline. The note now says so, so the next person reading it looks in the right place.
Fixed
- A placeholder value written during every deploy was described as harmless because it belonged to a service nobody runs. It actually belongs to a service that is running. It still does not reach that service today, but the note beside it no longer says something untrue about it, and it now names itself as a placeholder rather than looking like a real credential.
Added
- Every staging update now records which version of the shared toolbox the GitHub access helper was built from, shows it in the run summary, and refuses to continue if that record failed to attach. The helper is built fresh during each update from whatever the shared toolbox looked like at that moment, so until now two updates of the very same application version could quietly produce two different helpers with no way to tell them apart afterwards.
Fixed
- A staging update started by hand no longer reports a clean bill of health without having checked anything. In that mode the four application images are not being freshly published, so every version check excused itself and the run finished green having verified nothing. The four images travel together, so they are now required to at least agree with one another; a mismatched set — the sign of a half-finished publish — fails the run and names which image disagrees.
- The step that stores the reviewer links produced by a staging update was running on a retired platform version and warned about it on every run. It has been moved to the current one.
Changed
- A staging deploy that ends up with nothing to deploy now says so and fails, instead of concluding green and leaving staging quietly on the previous build. Only fires when the image build for that commit did not succeed and staging still does not have the commit, so intentional holds and nothing-to-ship changes stay green.
Fixed
- A delayed re-run of an older Docker Publish no longer refreshes Staging from mutable image tags after a newer successful Main publish has already taken over the deployment lane. The stale event is recorded as superseded; an unrelated, failed, or unverifiable publish still follows the existing fail-closed deploy path.
Fixed
- A staging deploy no longer fails when the build gate deliberately rebuilds only some services. The revision check treated the images it correctly retained on their previous build as evidence of a partial publish, so every deploy that actually refreshed containers went red while the runs that deployed nothing reported green. The check still fails a deploy whose images disagree with no revision asserted against them at all.
Fixed
- Staging deployment diagnostics now identify conflicting internal consumer-key names without exposing credential material.
Staging email validation now continues to report its account journeys even when an unrelated health check has failed.
Fixed
- Staging verification evidence now includes only the intended reports and visual diagnostics, excluding the temporary administrator access-token cache.
Added
- Staging can now be held on one exact build while that build is being validated. An operator can deploy a specific commit to staging and hold staging on it, so work merged by someone else in the meantime no longer replaces the build under review. The hold carries an expiry so it cannot be forgotten, and it can be released early either by promoting the build or by cancelling the hold. Once the hold ends, staging resumes following the main line on its own.
- A held build that does not deploy successfully no longer leaves staging stuck. The hold is withdrawn automatically, staging is left exactly as it was, and the run says so.
Fixed
- The staging health check no longer reports an ordinary page change as a browser error. When a form is submitted, the browser abandons whatever the outgoing page was still downloading -- normal behaviour on every site -- and the health check was recording part of that as a fault. It only ever flagged some of the abandoned files, because the rule it used went by where a file lived rather than by what had happened to it, so the same single page change was judged two different ways at once. The password-reset journey was failing this way on most runs, which hid whether anything else in that step was actually healthy. Files that genuinely fail to load are still reported, and so is a download the page itself cancels while staying put.
Fixed
- Staging health failures now reuse one tracker per stable failure signature and close trackers for checks proven healthy during a partially failing run, preventing recurring SHA-based issue backlogs.
- GitHub issue-list authentication failures now fail visibly instead of being treated as an empty backlog.
- Cross-repository health issue writes now use the governed application gateway instead of a stale long-lived token.
Fixed
- Staging mail access credential rotations now take effect when the operator reloads the staging gateway configuration.
Fixed
- Staging-operation preflight requests now use a signed, immutable authorization contract.
Added
- Staging now has a safe, self-service way to clear out leftover containers for services that no longer exist. Until now, deleting a service from the configuration left its container running on the host forever: every routine update noticed it and complained, but nothing could remove it, and there was no way in for anyone without direct access to the machine. One such leftover had been complaining on every update since it was retired.
- The new routine lists what it would remove and stops there by default. Removing anything takes a second, deliberate run, and afterwards it checks again and reports a failure if anything it meant to remove is still there.
- It decides what counts as leftover by asking the configuration itself which services exist, rather than working from a hand-kept list that would drift. It only ever considers containers belonging to this stack, and it refuses to act at all if the configuration cannot be read — so a configuration problem can never be mistaken for "everything is leftover".
The staging health run now asks every registered application whether it is ready to serve traffic, not just whether it is running. Previously it only ever asked the simpler question, so an application that could not answer the readiness question at all went unnoticed — and would first have shown up as an apparent outage of a perfectly healthy application.
An application that answers the question in the older plain-text form is reported as a warning rather than a failure, so the remaining conversions stay visible without holding up the run.
Fixed
- Restored the staging test-account provisioning workflow so operator-triggered account creation can complete successfully.
Every page that previously carried inline layout styling now uses the same scoped styling mechanism as the rest of the application. The component-level layout is consistent across all user-facing pages.
Fixed
- The account menu at the top right of every app is readable again in the light theme. It had been painting itself a fixed near-black rectangle whatever theme was chosen, while the little arrow inside it followed the theme — so in light mode the arrow was almost exactly the same shade as the block behind it and the control looked like a broken image rather than a menu you can open. The menu now takes its colours from the theme, so the arrow and the surface behind it always move together and neither can end up invisible against the other. The same applies to the menu once it is open, including the highlight that follows the pointer.
- The sidebar no longer lists "App Platform" twice. The product name at the very top is the brand mark; the entry a few rows below it that takes you back to the platform is now labelled "Platform home", so it is clear which of the two is a place you can go to and which is simply the name of the product.
Fixed
- The staging journey that opens the account menu and reaches the profile page no longer fails at random. It clicked the menu entry exactly once, so a click that arrived before the header finished becoming interactive was simply lost, and the check then waited twenty seconds for a page change that was never going to happen. The journey now retries the entry, and still fails if the profile page is genuinely unreachable.
- When that same set of sign-in journeys does fail, the run now keeps the screenshot, video and trace that explain why. They were previously discarded with the machine that produced them, because the step is advisory and never reports a failure of its own for the archiving step to react to — leaving the report saying only that a page change never arrived.
Fixed
- The account menu in the platform top bar is back to the width it had before: a shared stylesheet rule had been widening it slightly on every signed-in page.
Changed
- The list of devices signed in to your account is now shown a page at a time, with the device you are using kept at the top of the first page and a plain statement of how many there are in total. Before, the page tried to draw a row and a sign-out button for every session at once. For an account that had signed in a great many times that meant well over a thousand rows in a single page — slow to load, enormous to download, and impossible to actually read through and act on.
- "Sign out all other devices" now says how many devices that is, once the list is long enough to be shown in pages. The button always signed out every other device; that was unremarkable when the page showed all of them and is a genuine surprise when it shows twenty of a thousand.
Added
- An account now keeps a bounded number of sessions. Signing in on a new device when the limit is already reached signs out the device you have gone longest without using, and records that it did so — the sessions list stays a list of devices you actually use rather than a log of every time you have ever signed in. The limit is set well above any realistic number of devices, and the device you are signing in on is never the one that goes.
- Sessions that have been dead for a long time are now cleared away on a schedule, and a session left untouched past a configured idle period is ended rather than left open until it happens to expire. Nothing had ever removed a finished session before: signing in created one, and no automated activity ever signed out.
Changed
- The admin console's user-detail screen now shows active sessions a page at a time, with a count of how many there are in total and links to the rest. Opening an account that had signed in a few hundred times previously rendered every one of those sessions on a single screen, each with its own sign-out button. The account-security panel also stopped re-reading every session of every account it checks, which is what made it slow on a large roster.
The automation gateway on staging only ever answered for one of the seven application repositories. A request about any of the other six came back refused, and the refusal was indistinguishable from a credential or permission fault — so the fix looked like re-authenticating, re-installing, or re-issuing a key, none of which could have helped. It now answers for all seven, and the list carries a note saying why each one has to be named individually and what would replace that.
The Showroom chat panel exhibit now wraps its long package version line instead of pushing the page wider than a phone screen.
The component showroom now simply shows the platform's building blocks. The internal approval bookkeeping — the golden-reference index, the status-mapping page, the recertification and approval ledgers, and the promotion badges on exhibits and themes — was removed. What a component looks like and how it behaves is unchanged; the pages that catalogued sign-off state are gone, because a reference gallery is for looking things up, not for tracking ceremony.
Fixed
- The link in an "unlock your account" email now unlocks the account. It did nothing: clicking it opened the page that asks for an unlock link rather than the one that acts on it, so anyone locked out could request link after link and stay locked out, with no error to explain why. Requesting a link, the email itself, and the wording were all correct — only the address the link pointed at was wrong, so it never reached the part of the platform that clears the lockout. Anyone still holding an older unlock email should request a fresh one; the old links point at the previous address and will not work.
- Requesting a link for an account that is not locked, and links that are expired or already used, are unchanged: they still say nothing about whether the account exists.
Fixed
- The first planned AppPlatform.Shared.UI release now allocates a version that is genuinely new. Its migration baseline had been recorded one patch behind the version the project already declares, so the planner would have re-minted an already-published version — a push that reports success while shipping nothing, leaving every consuming app on the older package. The repository now also carries a check that compares every recorded baseline against the version each project actually packs.
Fixed
- The screen that asks an administrator to set up two-factor authentication now appears with its normal styling. Administrators who were required to enrol saw that screen completely unstyled, because the enrolment requirement was being applied to the screen's own styling and supporting files as well as to the pages it protects, so those files never arrived. They now load normally, while every protected page still requires the second factor exactly as before.
Fixed
- The shared sign-in key component now brings a patched version of the cryptography library it depends on, instead of leaving each application to notice and pin one itself. Inside the platform the outdated version was invisible twice over — the web framework supplies that library directly, and this repository already pinned a fixed version — so the first sign of it was another application failing its own security audit over a library nobody there had ever named. Carrying the fix in the shared component is the point of having one: a floor that has to be re-established in every application is one that will eventually be missed in one of them.
Changed
- The merge-queue quality gate no longer spends roughly four minutes rebuilding the same test web host over and over. Eleven test classes each stood up a fresh ASP.NET host for every single test method, because the only thing stopping them from sharing one was that the shared-host base class could not carry their configuration. It can now, and the platform test assembly runs about a third faster on the same machine — measured back to back on a four-core run, 230 s down to 145 s, with the CPU it burns falling by the same share. No test lost its isolation: each one still starts against a brand-new empty database and cleared caches.
Publishing a shared package once again requires that the package says what changed in it.
When package publishing moved to a new serialized publisher, the check that refuses to publish a version nobody has documented was left behind in the older publishing jobs that were switched off at the same time. The publisher does verify the release notes it generated itself, so this was not the open door it first appeared to be, but the estate's own published-package check had stopped running — and it is the one that also refuses a release still marked as unreleased. It now runs in the new publisher, covering exactly the packages that release is about to push, and early enough to stop one before anything is published.
The list that recorded this as a known gap is now empty, and is deliberately kept rather than deleted: switching the check off again cannot be done quietly, because the only way to make the build pass afterwards is to write the affected publishing job back onto that list where a reviewer will see it.
Separately, the checks that read the publisher itself now cover more of it. Two malformed lines that would each have stopped the publisher's first real release were found and fixed elsewhere. Those two were written in a form the existing check reads; the checks here now also read the forms it does not, across every workflow that publishes, so a third of the same kind fails on the pull request that introduces it rather than partway through a release.
Changed
- The published copy of the personal theme editor's design intent now matches the approved page design again, after that design was updated to describe how the colour list actually behaves at the width the editor really has.
Fixed
- The register form states every password rule it enforces, marks the ones a refused password broke, and no longer answers a mistyped address with a complaint about the password. The hint named the length and the breach check and stopped there, while a digit, both letter cases and a symbol were also required, so a password could satisfy every word on screen and still be rejected with nothing left to try.
- Password fields take their own minimum length from the configured policy, so the field and the rule beside it can no longer disagree.
- A refused registration keeps the address you typed instead of emptying the whole form.
Fixed
- The active-sessions page now marks the device you are actually reading it on. It could previously put "This device" against somebody else's row, and then — right after you used "sign out all other devices" — put it against no row at all, leaving a single surviving session that the page could not recognise as yours. The page was reading a sign-in belonging to a different session, which the platform had kept hold of and reused; it now reads the one the request in front of it actually arrived with.
- With no row marked as the current one, the page also offered to sign you out of the very session you were using it from. That control is correctly withheld again.
Fixed
- The active-sessions list now shows a full page when it says it is showing a full page. On accounts with more sessions than fit on one screen, the summary line could read "showing 1-20 of 22" above a list of eighteen — the page was counting one thing and listing another. Nothing was ever hidden or shown twice; the numbers simply described a page that had not been fetched. The same correction applies to the admin view of a user's sessions, which reads the same list.
Fixed
- Staying signed in no longer depends on when the platform was last updated. The keys that protect a signed-in session were held only in memory by a supporting service, so a routine platform update discarded them: everyone signed in to the platform and to the design showroom was signed out, and the connected applications presented a login form instead of signing people through automatically. Those keys are now recorded as they change and are read back after a restart, so an update leaves existing sessions alone.
- The same supporting service now has an explicit memory ceiling and a shedding rule that can only discard rebuildable cached data. It previously had no ceiling at all, so a busy period could end with the service stopped and restarted empty — the same silent sign-out, with nothing at the time to indicate a cause. The shedding rule cannot reach the session keys, so cache pressure can no longer sign anyone out.
The lists that decide which shared packages get published are now checked against each other automatically, so one of them can no longer fall behind the others unnoticed.
Publishing a shared package depends on several separate lists agreeing: the projects marked as publishable, the publishing jobs' own lists of what to build and pack, and the list used to decide whether a change has to be written up. Keeping them aligned was left to whoever added a package, and every way they could disagree failed silently — a package could be added in one place and go unpublished, or change without anyone being asked to describe what changed. An automated check now derives the expected list from the projects themselves and compares it against the others on every pull request, so a mismatch fails a check instead of going unnoticed.
The related question of which packages must carry a written changelog before they can be published is now answered from whether the package actually has one, rather than from a list kept by hand. Every shared package carries one today, and the check keeps the two in step in both directions, so a publishing job cannot quietly stop asking for a changelog it used to ask for.
That protection is not currently running anywhere, and the check says so rather than implying otherwise. Publishing recently moved to a new serialized publisher, and the changelog requirement lived only inside the older publishing jobs that were switched off with it. The new publisher does not yet ask whether a package documents what changed in it. That gap is now written down and asserted as a known gap, so it cannot be forgotten, and closing it will show up as a change to this check.
The check also covers the new publisher's own way of working. It works out which projects to build from the release plan rather than listing them, and it relies on each package living in a directory named after it — something it previously only discovered while publishing, once a version number had already been committed and could not be reused. That expectation is now checked on every pull request instead.
A large block of superseded checking code has been removed. It had stopped running during an earlier change but still read as active, and had already caused a note in the publishing job to claim a protection that no longer existed. No runtime component is affected and there is no change to application behaviour.
Fixed
- The sign-in button in the top bar is now a real button rather than a link dressed up to look like one. Screen readers and keyboards were being told it behaved like a button when it did not: pressing Space on it did nothing. It also had to be nudged into place by hand to stop its label sitting high in the button, and that is no longer necessary.
Fixed
- The sign-in button in the top bar now has its label and icon centred inside it. They had been drawn slightly high, sitting closer to the top edge than the bottom. On a narrow phone screen, where the button shrinks down to just the icon, the icon was also pushed hard against the left edge instead of sitting in the middle of the button.
Access to the personal theme editor is now something an operator can grant and withdraw, rather than something every signed-in account simply has. Nothing changes for anyone today: the editor is granted to every signed-in account out of the box, which is exactly who could open it before.
What changed is that there is now a gate at all. Until now the editor page and the six personal-theme API routes accepted any signed-in caller, so an installation that wanted to offer theme authoring to some people and not others had no way to say so. Withdrawing the right, or narrowing it to one role, is now a settings change for that environment, and takes effect the next time the app starts.
The account menu follows the same grant as the page: an account without the right sees no entry for the editor, rather than an entry that leads to a refusal.
Fixed
- The buttons at the foot of the personal theme editor now match each other. Import and Export were drawn by hand rather than by the app's own button styling, so they appeared in a different case and a different size from Delete, Reset all and Save sitting beside them in the same row. All five now take their appearance from the theme, and all five are the same height the page design calls for.
Added
- The site now reports which build of itself it is serving, on its own address rather than the one that only ever answered for the back end. A change to the pages people actually look at can be tied to the build it shipped in, instead of being checked against whatever the back end happened to be running.
Fixed
- Catch theme control interaction and appearance regressions earlier, before they reach the shared acceptance environment.
Fixed
- A custom theme can no longer affect anything outside its own appearance. Previously a theme could carry a colour, size or name containing punctuation that ends a style rule, and the stylesheet built from it would carry extra styling the theme never defined — capable of restyling or hiding parts of any page shown with that theme. Themes are now rejected when saved with such content, and any that reached storage by another route have the offending entry left out of the stylesheet rather than written into it.
- The protection previously covered only the typeface settings, so the same content was refused in a font selection but accepted in a colour. It now applies to every value a theme supplies, and for the first time to the entry names as well, which were never checked at all.
Fixed
- A theme saved before the colour set changed no longer falls back to dark colours on a light theme. Any colour such a theme does not specify is now taken from the built-in theme of the same kind — light values for a light theme, dark values for a dark one — instead of from the dark defaults. The visible case was badges and links rendering pale text on a pale tint, below the contrast needed to read them. A colour the theme does specify is always kept.
- Colour changes now reach the browser as soon as they are published. Previously a change to how colours are worked out could leave browsers and caches showing the previous palette for up to an hour, and longer for some caches, with nothing to indicate the page was out of date.
Fixed
- The theme picker in the account/profile menu now shows the theme that is actually in use, and switching to that theme works. Previously it could keep showing an earlier theme after the page finished loading the real one, and choosing that shown theme did nothing.
Fixed
- The theme toggle switches to the opposite visible scheme on the first interactive click, even while its initial state is still arriving. Quick repeated clicks update the icon and label immediately and keep the last selected theme as the saved preference.
Fixed
- Custom themes now actually change what the application looks like. A theme could previously be created, saved and applied without any error while leaving most of the screen on the built-in colours: the colour values a theme was asked for were not the ones the pages read. On a light custom theme this produced a white page carrying the dark theme's text, at a contrast far below the accessibility minimum for readable text.
- Colours that only a theme can reasonably decide were missing from the set a theme is asked for, so they stayed on the built-in dark values whatever the theme said. Badges and links drawn on a pale tint were the visible case — pale text on a pale background, unreadable in a light theme. Those colours are now part of what a theme supplies, including the strong and hover border colours, the light and subtle accent shades, the text drawn on a subtle accent or success tint, and the danger button's fill and border.
- The two built-in themes are now defined identically in the design system and in the stored copy the application falls back to when it cannot reach the theme service, so a service outage no longer changes the palette.
Changed
- The set of colour values a theme must supply has changed, in both the names used and the number of them. Themes saved before this release are carried over automatically: every colour such a theme already specified now reaches the screen, and the colours newly added to the set start from the matching light or dark built-in value, without anyone reopening or re-saving the theme. The theme editor shows the new colours the next time it is opened, so they can be adjusted. Anything calling the theme administration API directly must send the current set.
The shared package publisher no longer stamps a package's own version onto an unrelated package it merely depends on.
Publishing the personal theme editor package at version 3.0.0 accidentally also stamped that same version number onto its listed dependency on the underlying theming package — a version of that dependency that was never published and, because published package versions are immutable, never will be. Installing or updating the personal theme editor package at 3.0.0 has been broken ever since, failing to resolve its own declared dependency.
The publisher now determines each package's version independently, so publishing one package can no longer overwrite the version another, unrelated package reports for itself. A second, independent check now opens every package just built and refuses to publish anything if a declared dependency does not match what that dependency is actually shipping, or does not exist yet — catching a future recurrence of this class of defect before anything reaches the package feed, not after.
The personal theme editor package's 3.0.0 release stays published and unusable; a corrected release carrying the identical feature set follows from the accompanying package release notes.
The design-system Showroom's theme-provider exhibit now shows every one of the 23 design tokens a theme is required to supply, in both the packaged light and dark schemes, instead of a five-token sample. The automated acceptance check that runs against staging was widened to match, so a token going missing from the served stylesheet is now caught rather than passing unnoticed.
Fixed
- The device you are reading the page on is listed exactly once in your active sessions. On an account with more sessions than fit on one page it could be shown at the top of the first page AND again further down the list, and the first page carried one row more than the "showing 1-20 of 22" count above it claimed. The device you are on is still always on the first page.
Fixed
- Rows of trait marks no longer push a page sideways when they are given a narrow space to sit in. A group of five marks used to insist on its full width however little room it had, and the surplus spilled out of the panel and made the whole page slide left and right — even though every panel on the page looked as if it fitted. The marks now shrink to fit the space available. None of them are hidden or cut off, and where there is room they stay at their normal size.
Fixed
- Visual updates now fail earlier with a clear readiness diagnosis when the application cannot start.
Fixed
- Several panels and labels that were meant to be styled were rendering with parts of their appearance missing. Each referred to a colour, spacing or corner-radius value by a name the design system does not define and offered no alternative, so the browser discarded the whole instruction and fell back to whatever it inherited. The visible effects were borders that did not draw, backgrounds that came out transparent, padding and rounded corners collapsing to nothing, and a heading that took the wrong size.
- The surfaces repaired are the operations dashboard status badges, the MCP audit log's detail panel, the release-notes callout and its bullet indentation on both the platform and the component gallery, and the application shell's brand label. Each now points at a value the design system does actually define, so it follows the active theme in both light and dark rather than silently going unstyled.
Added
- The forced-MFA-enrollment gate (mfa-enforcement-and-step-up, VoR-01) now has an automated credentialed proof instead of relying on an operator to check it by hand. A dedicated seeded admin account that is genuinely subject to the gate (not one of the accounts an operator has deliberately exempted) signs in, requests a real admin surface, is redirected to the two-factor setup interstitial with that surface denied in the meantime, completes a real authenticator enrollment, and only then reaches the surface it originally asked for. No production behavior changes — this closes an evidence gap in the staging health check.
- The account this proof uses converges back to its starting state every run: the seed step always resets it to "no second factor" before the proof enrolls one, so completing the proof once does not make the next run's proof impossible.
- The TOTP-code generator used by the existing 2FA-challenge proof is now a shared helper instead of being duplicated, so both proofs derive codes from one implementation.
Added
- The step-up re-authentication requirement for sensitive account actions (mfa-enforcement-and-step-up, VoR-02) now has an automated credentialed proof instead of relying on an operator to check it by hand. A seeded administrator signs in and completes a real second-factor challenge, waits out the real recency window a sensitive action like changing your password requires, and then attempts that change: the attempt is denied and the password is confirmed unchanged. The same session then re-proves its identity through the existing "confirm it's you" screen, and only after that does the same change succeed. No production behavior changes — this closes an evidence gap in the staging health check, the same shape as the earlier forced-MFA-enrollment and forced-credential-rotation proofs.
- The account this proof uses converges back to its starting state every run: the seed step always resets its password to the starting value before the proof changes it, so completing the proof once does not make the next run's proof impossible.
Added
- The forced-credential-rotation gate (mfa-enforcement-and-step-up, VoR-03) now has an automated credentialed proof instead of relying on an operator to check it by hand. A dedicated seeded administrator account that genuinely carries a pending forced-rotation obligation signs in, is redirected platform-wide to the credential-rotation screen, requests a real protected administrative page and is denied it in the meantime, and completes a real password rotation through the existing screen. No production behavior changes — this closes an evidence gap in the staging health check, the same shape as the earlier forced-MFA-enrollment proof.
- Because the account is a genuine administrator with no second factor enrolled, the proof now also shows the separate forced-MFA-enrollment requirement taking over immediately after rotation: the account is denied the requested page a second time, for a different reason, until it completes a real second-factor enrollment through the existing screen — demonstrating the two hardening gates compose in their intended order rather than proving the rotation gate alone.
- The proof also confirms that once the credential has been rotated, the original seeded password no longer works for signing in — a sign-in attempt with it is rejected the same way a mistyped password would be — and that the pending rotation obligation is cleared.
- The account this proof uses converges back to its starting state every run: the seed step always re-applies the pending-rotation obligation and resets the password to its starting value before the proof rotates it away, so completing the proof once does not make the next run's proof impossible.
- The internal seeding endpoint used by the staging health check can now also set a pending forced-rotation obligation on a seeded account at creation time, alongside the two-factor flag it already supported.
Internal GitHub workflow-read admission failures now return a safe denial response instead of an unexpected server error.
Staging can now activate device-bound, AIMS-authorized GitHub Actions reads for agents without exposing GitHub credentials.
Move reusable chat follow-to-latest and unread-message handling into the chat UI package.
Fixed
- The overall health answer for the staging WoDVTT site is now given by the same part of the application that already answered its readiness question. Until now the two answers came from different parts, so anything asking the site how it was doing received a blended picture — and could be told all was well by one part while the other was the one in difficulty.
Changed
- On the health track, clicking a lethal-damage box now turns it and the boxes before it aggravated, clicking an aggravated box clears it and the boxes after it, and the damage-state wording is translated.
Fixed
- A trait rating row (Strength, and any other dots/boxes row) could silently change value just by moving the mouse over it, with no button held, after a click that started on the row and released somewhere else. The row is no longer left armed by a release outside it, so hovering it afterwards leaves the value alone; clicking and dragging to set a value still work as before.
Fixed
- Choosing a theme now actually sticks. The choice was saved in the browser, but every full page load re-applied the site-wide default over the top of it, so anyone who picked a theme was quietly put back on the standard one the next time they opened a page. The theme a visitor picks is now remembered by the server and used to render the page from the very first byte, so it survives page loads, sign-in redirects and new browser sessions.
- A theme picked on the platform now also carries over to the component gallery on its own subdomain, and back again. The two were meant to share the setting, but only the gallery was still passing it along, so the hand-off worked in neither direction.
- The theme no longer drops back to the default part-way through flows that swap page content in place — signing up and being handed on to the sign-in page, for example — which briefly showed the wrong colours until the next reload.
- Read-only preview links that force a theme through the address bar still leave the visitor's own saved choice untouched, on the platform and on every sibling subdomain.
- The other applications built on the shared theming package pick up the same repairs once they take the new version: the theme no longer drops out during in-place page changes, and a theme chosen in one of them is now passed to the platform. The platform's choice does not yet flow back the other way — each application has to read the shared setting when it renders a page before that leg works, which is a separate change per application.
[0.9.1] - 2026-08-04
Changed
- When an operator sets a CI/runner recommendation to a status the platform does not recognise, the refusal now lists the statuses that are accepted instead of only saying the value is invalid.
- When an operator filters the CI/runner findings list by a value the platform does not recognise, the refusal now lists the values that are accepted. The same message serves several different filters, so naming the accepted values also makes it clear which filter was rejected.
[0.9.0] - 2026-08-04
This release adds no database migration and needs no configuration change.
Added
- Platform administrators can now administer an individual application's themes from the platform administration API: listing an application's themes, creating and updating one, activating or deactivating it, and choosing which theme that application uses by default. Themes stay in the application that owns them; the platform administers them in place rather than keeping its own copy.
- Choosing a new default now reports which theme stopped being the default in the same response, and confirms afterwards that the application really does have exactly one default theme.
- A person can now keep their own theme for the platform and for each application they use, see all of them together in one place, and move one between places by exporting it to a file and importing it again. Export is how a theme is shared with someone else. The editing surface for personal themes arrives in a later release; this release provides the interface it will use.
- An imported theme file is checked to the same standard as a theme published by an administrator. A file that is unreadable, or written in a format this release does not recognise, is refused with a reason that says which of the two it is.
Changed
- Theme selection now looks for a person's own theme for where they are, and prefers it to the administrator's default. Nothing on screen changes in this release, because no page reads that selection yet; the change removes the step that would otherwise have made a saved personal theme appear to do nothing once the editing surface arrives.
Fixed
- If an application cannot be reached while an administrator is saving a theme, the save is reported as failed and is not attempted a second time, so a momentary outage cannot leave a duplicate or half-applied theme behind.
- If an application accepts a change of default theme but then becomes unreachable before the result can be confirmed, the administrator is told the change was most likely applied and to re-check, rather than being told nothing happened. The earlier wording could have led an administrator to reverse a change that had in fact taken effect.
Security
- A personal theme is visible only to the person who created it. It is not returned to another person, or to an administrator, anywhere in the product.
[0.8.0] - 2026-08-04
Added
- Themes can now carry a typeface selection for body text, headings and monospaced text. The selection is optional in every part: a theme may set one, some or none of them, and a theme that sets none keeps rendering exactly as it does today. Existing themes need no edit and keep working unchanged.
- The shared design system defines body, heading, and monospace font-family custom properties, each defaulting to the standard font stack, and the Radzen font variables resolve through them — so a typeface chosen for a theme reaches the whole interface instead of stopping at a fixed font list. Picking up the new package version is opt-in.
- Personal themes now have a central home on the platform, recorded against the user who owns them and the scope they were created in. A theme made for one application belongs to that application only, and a person can hold one personal theme in each place they use — with the platform itself counted as one such place. Nothing is visible to anyone but the owner. The editing surface for personal themes arrives in a later release; this release prepares the storage for it. Deploying this release runs a database migration that adds one new table for personal themes. No existing table is changed and no existing data is read, rewritten or removed. Rolling the migration back removes only the new table.
Changed
- Themes now record the scope they belong to, with the platform treated as one scope alongside applications rather than a separate kind of theme. Existing themes keep their names, display names, ordering, default selection and active state, and users continue to see the same themes as before. Deploying this release runs a database migration that renames the theme table and records the platform scope on every existing theme. All theme data is preserved — the table is renamed rather than rebuilt — and rolling the migration back restores the previous table name.
- A typeface saved on a theme is now checked for being a well-formed font list before it is accepted, and a malformed one is refused with a clear reason instead of being stored. Colour and other theme settings are unaffected.
- A font name containing a period, such as Foo.Bar, must now be written in quotes. Unquoted, it is not something a browser can read, so the typeface was silently ignored on the page even though saving appeared to succeed. It is now refused at save time with the remedy in the message.
- Theme names are now validated against the lowercase slug format the platform has always documented, and a name outside that format is rejected with a clear message when the theme is saved. The check applies to the whole name, including any trailing blank line. The generated theme stylesheet renders names inertly as well, so it stays well-formed whatever a stored name contains. Themes that ship with the platform, and any theme already following the documented format, are unaffected.
Fixed
- Form fields, checkboxes, radio buttons, and outlined buttons now have a clearly visible outline in both light and dark themes, meeting the accessibility contrast requirement for control boundaries. Previously these outlines were too faint to separate a control from the surface behind it, and because the dark appearance is what most people see by default, the weaker of the two was the common case. Dropdowns and selects that had been left with a fixed grey outline now follow the theme as well, including their lower edge, which had been drawn in a different shade from the other three sides. Pointing at a form field, dropdown, checkbox, or radio button in the dark appearance now brightens its outline instead of making it vanish, so it stays clear which one is under the pointer. Dividers, table rules, and panel outlines are deliberately unchanged and stay understated.
- Pointing at a checkbox or radio button that is already ticked no longer makes it look untouched. Previously the coloured fill of a ticked checkbox faded to a barely-there tint as soon as the pointer reached it, leaving its white tick almost invisible; a selected radio button faded the same way in both the light and dark appearance, and because the filled dot is the only thing marking a radio button as chosen, it briefly looked as though the selection had been lost. Both now keep a solid, clearly visible fill while the pointer is over them, and the fill still lightens enough to show which control the pointer is on. Selected rows in tables, highlighted text, and slider and progress tracks keep the soft tint they have always had.
- In the dark appearance, the accent colour used for selected and focused controls is now a touch brighter. It had been very slightly too dark to stand out against the raised panels that cards, tiles, and dialogs are drawn on, so a tick box, a selected radio button, a switch, or a slider sitting inside one of those panels was harder to pick out than it should have been. The keyboard focus outline benefits most: it is the only thing showing which control you have moved to with the keyboard, and inside a panel it had been right at the edge of being too faint to see. Button labels remain just as legible as before, and the light appearance is unchanged.
- Sign-in and account pages now draw their borders in the same colour as the rest of the platform when a dark theme is active. Previously the borders on those pages were slightly darker than everywhere else, and the outline of secondary buttons was indistinguishable from the button's own fill.
- Monospace text — code samples and similar fixed-width content — previously fell back to whichever monospace face the browser happened to choose, because the monospace property was referenced in several places but never actually defined. It now resolves to the standard monospace stack, so that text renders consistently across the platform and the design showroom. Body and heading text, and every packaged theme, render exactly as before.
- Style updates now reach people who have visited before. Previously the browser could keep using its saved copy of the shared styling after an update shipped, so a returning visitor might continue seeing the old appearance for a while. Each update is now published under its own address, so browsers fetch it immediately while still caching aggressively between updates.
- The generated theme stylesheet now ignores a typeface value it cannot read as a well-formed font list, rather than writing it out as it was stored. Themes in use are unaffected: a typeface saved through the platform is already checked at save time, so those values keep rendering exactly as they do today. A value that reaches storage some other way now falls back to the platform default typeface, and the rest of that theme's settings are still applied.
- Applications that embed the shared application frame no longer have their page content hidden behind the top bar. The bar previously floated above the page instead of sitting above it, covering roughly the first 85 pixels of every page — headings were obscured and any button in that strip could not be clicked at all. The correction had already been made in the platform itself but had never been released to the embedding applications. The shared user-interface package is released as 1.6.0; applications that embed the shared frame pick the fix up by moving to that version, and until they do they keep showing the old behaviour. That release also carries the shared-frame and operations-component work merged since the previous release, so expect visual differences beyond the top bar and review the embedding applications after the upgrade.
[0.7.8] - 2026-08-01
Fixed
- Pages now arrive in the correct theme on first paint. Previously the initial response was unthemed and the theme only appeared once the browser had finished starting the app, producing a visible flash on every page load.
[0.7.7] - 2026-07-30
Fixed
- Showroom Workbench tables now keep component identities, release status, migration details, and actions readable without horizontal scrolling at supported desktop widths.
- Contract preview selectors are visibly labelled, and technical and evidence values wrap instead of being clipped.
[0.7.6] - 2026-07-30
Fixed
- Staging deployments now keep the SigNoz MCP observability sidecar stable and report container OOM/restart evidence when its liveness gate fails.
[0.7.5] - 2026-07-30
Fixed
- Showroom headings and current-status badges now remain readable in both themes.
- Workbench filters are visibly labelled and catalog rows expose their complete values and actions.
- Anonymous Workbench visits now reach the centralized sign-in page instead of a blank response.
[0.7.4] - 2026-07-30
Added
- The internal platform API now provides the dormant, append-only activation ledger for the GitHub control plane. It separates deployment and runtime identities, enforces approved candidate and rollback sequencing, and permits recovery only after completed rollback proof. No organization-wide GitHub routing or production activation is enabled.
[0.7.3] - 2026-07-30
Fixed
- Golden Sample replay now validates the rendered component while the Validate tab is open.
[0.7.2] - 2026-07-29
Fixed
- The Showroom now focuses its navigation on Discover, Contract, and Migrations.
- Reviewers can open each preview from the navigation and use its Radzen-based review actions directly.
- Signed Golden Sample links continue to open the exact approved preview.
[0.7.1] - 2026-07-29
Fixed
- Workbench review-link manifests now use stable camel-case field names.
[0.7.0] - 2026-07-29
Added
- Added three staging/development-only compiled Design Workbench draft previews for Discover, Contract, and Migrations. Every visible draft element is composed from Radzen Blazor components.
- Added private, time-boxed reviewer links tied to a specific draft and deployment. Draft previews remain disabled by default, unavailable in production, and cannot create review access from the Workbench UI.
[0.6.31] - 2026-07-24
Fixed
- Staging deployments now validate GitHub workflow access through the supported read-only integration.
[0.6.30] - 2026-07-24
Fixed
- The theme toggle in the shell header now announces its action in German ("Zum hellen Modus wechseln" / "Zum dunklen Modus wechseln") when the platform is used in German, instead of always announcing in English.
- The DE/EN language-switch buttons in the shell header now expose a clear, localized accessible name ("Deutsch" / "English") to screen readers and other assistive technology.
[0.6.29] - 2026-07-24
Changed
- The pattern catalog's trait-control reference card now links to the live interactive dot and box control exhibit instead of describing it as a planned placeholder.
[0.6.28] - 2026-07-24
Fixed
- The staging GitHub integration now declares its approved read-only repository-content and issue-metadata permissions, allowing the control plane to validate the least-privilege deployment boundary.
[0.6.27] - 2026-07-24
Fixed
- The staging GitHub integration now starts with its approved repository and permission boundaries and loads its control-plane credential from the dedicated security scope, restoring authorized agent reads while preserving fail-closed behavior.
[0.6.26] - 2026-07-23
Fixed
- Loading placeholders now show the full set of shimmering lines they were meant to, instead of a single line, so a screen that is still fetching content gives an honest impression of how much is on the way. This affects the app catalog and app details pages, My Apps, plan management, the interactive tour page, the app switcher, the admin operations and theme screens, the theme editor and deactivation dialogs, and the design showroom.
[0.6.25] - 2026-07-23
Added
- The agent audit log now shows tool discovery as well as tool calls. When the platform refreshes the set of observability tools it offers to agents, the log records the upstream address, the environment, and how many tools were found — and records it again when that upstream stops answering or comes back. An entry is written only when the outcome or the tool set actually changes, so the routine one-minute refresh cycle does not bury the calls and denials an operator is looking for.
[0.6.24] - 2026-07-23
Fixed
- Filtering the MCP audit log by an agent identity now lists that agent's activity. Governed tool calls and refusals are recorded against the identity that made them, so a filtered view shows the agent's events instead of coming back empty as though nothing had happened, and the agent's display name is still shown alongside each event.
[0.6.23] - 2026-07-23
Fixed
- Alerts across the admin and showroom pages now display their severity colour and icon — error alerts appear red with an error icon, warnings amber, information blue, and success green — instead of a neutral grey box with a generic icon.
[0.6.22] - 2026-07-21
Security
- Updated a bundled .NET cryptography component to a patched release, addressing a high-severity denial-of-service advisory (CVE-2026-50648). No configuration or migration steps are required.
[0.6.21] - 2026-07-17
Fixed
- Restored the packaged light and dark theme colours on the offline theme path, so apps show the same palette and keep button labels readable when the theme service cannot be reached.
[0.6.20] - 2026-07-14
Fixed
- Kept Showroom state and viewport controls readable across dark-mode validation views.
[0.6.19] - 2026-07-14
Fixed
- Kept the Showroom ThemeCssProvider exhibit readable in dark mode across its explanatory text and packaged theme samples.
[0.6.18] - 2026-07-14
Fixed
- Switched the Showroom ThemeToggle exhibit and shell action to the canonical AppPlatform.Theming implementation so dark-mode accessibility evidence covers the same component used by the platform shell.
[0.6.17] - 2026-07-13
Fixed
- Kept the Showroom appearance switch readable inside review previews.
[0.6.16] - 2026-07-13
Fixed
- Kept Showroom design sample rows readable in dark mode while preserving the packaged theme examples.
[0.6.15] - 2026-07-13
Fixed
- Improved contrast in Showroom theme examples when dark samples appear inside a light page.
[0.6.14] - 2026-07-13
Fixed
- Made Identity Administration user search and detail navigation work through durable browser requests so operators are not left with an empty grid when the live circuit reconnects during staging validation.
[0.6.13] - 2026-07-13
Fixed
- Restored the identity administration user-detail action workspace so operators can see the guarded account, role, two-factor, credential, and session actions.
- Made the staging validation open the searched account row instead of a stale visible result after filtering.
[0.6.12] - 2026-07-13
Fixed
- Made the authenticated account menu use native browser disclosure behavior so the Identity Administration entry remains reachable from staging home even if Blazor Server interactivity is delayed after sign-in.
[0.6.11] - 2026-07-13
Security
- Hardened MCP gateway authentication so invalid agent credentials are rejected before any tool names or policy states are returned.
- Preserved denied-credential response status semantics while keeping MCP tool discovery fail-closed.
[0.6.10] - 2026-07-13
Fixed
- Improved the Identity Administration account-menu flow for authorized operators so the console launch remains stable after sign-in.
- Kept identity administration hidden from ordinary authenticated users while preserving the authorized operator entry.
[0.6.9] - 2026-07-13
Fixed
- Improved Operations dashboard route stability so quick navigation no longer exposes a runtime error state during operator validation.
[0.6.8] - 2026-07-13
Fixed
- Made staging admin seeding fail loudly if the well-known admin cannot be backfilled with IdentityAdmin, so a green seed run now proves the account can reach the canonical Identity Administration console.
- Added post-deploy browser proof for the identity-admin console from staging home navigation, covering the account-menu entry, user search/detail, audit, posture, and non-admin nav absence.
- Improved Showroom theme previews so dark-mode examples remain readable.
[0.6.7] - 2026-07-13
Changed
- Rehomed operator user management under the Identity Administration console so user search and account-management review live in the dedicated identity operations workspace.
- Retired the old broad-admin user-management API surface with deprecated responses so identity user management follows the narrower identity-admin access model.
Fixed
- Added a real access-denied state for unauthorized identity administration navigation instead of presenting a missing-page experience.
[0.6.6] - 2026-07-13
Added
- Added a staging-only internal endpoint that clears out the disposable test accounts left behind by the hourly staging health check, so the identity store, user counts, and admin console stay free of automation clutter.
Fixed
- Fixed the unbounded build-up of throwaway sign-up test accounts on staging by removing them automatically after each health-check run and clearing the existing backlog. Production is never affected.
[0.6.5] - 2026-07-13
Security
- Hardened staging sign-in by retiring the older shared symmetric signing method, so staging now relies solely on stronger asymmetric signing. No user-facing change; production is unaffected and keeps its current signing.
Fixed
- Improved Showroom ThemeToggle preview contrast in dark mode so the design system review surface remains readable.
[0.6.4] - 2026-07-12
Fixed
- Improved Operations dashboard stability so live evidence rows no longer disrupt the operator page during staging validation.
[0.6.3] - 2026-07-12
Added
- Added operations workspaces for reviewing MCP administration activity, operational audit history, approval queues, policies, endpoint inventory, identity oversight, and dashboard health.
- Added scoped agent access management improvements for controlled service access.
- Added staging routing for MCP operations flows so approved reviews can exercise the same deployed candidate that may later be promoted.
Changed
- Improved Operations navigation so authorized users see coherent operations surfaces and unauthorized users receive a clearer forbidden-state experience.
- Improved public changelog rendering so release notes display as structured release text instead of raw markdown.
- Improved staging signing and operations hardening at an operator-visible outcome level.
Fixed
- Improved Admin Users localization, mobile layout, action visibility, and expired-session handling.
[0.6.2] - 2026-07-06
Added
- Added identity administration screens for authorized operators to find users, review account status, and manage common account actions.
- Added account recovery and session-management foundations for safer unlock, lost-authenticator recovery, and device/session review flows.
- Added stronger visual quality checks that help catch missing icons, clipped text, overlapping content, and contrast problems before release.
Changed
- Improved release visibility so the displayed app version comes from SemVer release metadata with commit information.
- Improved staging review and design-system workflows for operators.
Fixed
- Fixed an invisible theme toggle icon so users can see and use the light/dark mode control reliably.
- Fixed small-screen branding layout so long product names wrap instead of overflowing.
- Improved theme-toggle contrast in light mode.
Security
- Improved protection for external sign-in changes so stale sessions cannot silently change how an account signs in.
- Strengthened administrator account-management safeguards and audit visibility.
- Improved account recovery, session revocation, audit retention, and personal-data minimization safeguards.
- Strengthened multi-factor enforcement and recent-authentication checks for sensitive account actions.
- Improved protection for long-lived sign-in sessions.
[0.6.1] - 2026-07-02
Added
- Added operator-facing automation health information for scheduled platform and runner monitoring.
- Added staging design-review previews for pipeline health monitoring screens.
Fixed
- Improved sign-in reliability when the same account signs in from more than one place at nearly the same time.
- Improved second-factor and recovery-code sign-in reliability during concurrent account activity.
[0.6.0] - 2026-07-01
Added
- Added runner and automation health monitoring, including trends, source freshness, findings, alerts, and recommendations for operators.
- Added product version ownership, public changelog pages, and footer links to release notes.
- Added shared design-system controls for character traits, box tracks, and health tracks so connected apps can use consistent interactive controls.
- Added gothic styling variables for character-sheet controls used by WoDVTT.
- Added staging review previews for Agent Email Proxy product screens.
- Added health and capacity visibility that helps operators spot pressure on deployed services.
Changed
- Improved deployed version labels so users see a product version with commit context instead of placeholder build values.
- Improved reliability of operations metrics collection and storage reporting.
- Updated Agent Email Proxy design previews based on product review feedback.
- Updated the design-system showroom to display the real shared character-sheet controls instead of local demonstrations.
Fixed
- Fixed WoDVTT sign-in sessions expiring too soon after session renewal.
- Replaced placeholder support and notification email addresses with reachable product addresses.
- Fixed passkey setup and sign-in in deployed environments.
Security
- Improved protection for service-to-service access at the public edge.
- Replaced shared service credentials with independently managed consumer access so one integration can be changed without affecting others.
[0.5.0] - 2026-06-25
Added
- Established 0.5.0 as the first app-platform SemVer baseline for deployed services.