Multi-Workspace deployments fail the public plugin icon route closed (404), so plain <img src> icons broke everywhere locally and for the runner picker in production. A shared cached hook fetches the authenticated icon for installed plugins and leaves marketplace surfaces on the cloud URL.
Passkeys and passwords belong to the Account, but those routes required a Workspace -- which the WebUI never sends for them -- and then resolved the caller by looking the login name up as an email address. Every Account whose login name is not its email answered 404, so the passkey panel could neither add nor list a key.
The routes now authenticate with the account token and address the Account the caller authenticated as, matching the self-service second-factor routes in the same file. /space/bind-authorize-url carried the same Workspace requirement.
The login page also surfaced a TypeError instead of the passkey login failure message when an assertion was rejected.
The second-factor listing was instance-wide and the three mutating endpoints never checked that the target Account belongs to the caller's Workspace, so an owner or admin could see and reset another tenant's account. The listing now resolves the Workspace members and the mutations answer 404 for a foreign account, which does not disclose that it exists.
Also fixes the TOTP write path on Postgres: six write points passed timezone-aware datetimes into timestamp-without-time-zone columns, which asyncpg rejects, so every enroll, confirm, revoke and recovery-code consumption raised a 500 there. SQLite tolerated the same values.
The panel derives its oversight view from the API authorization instead of the length of the account list, which would have hidden it in a single-member Workspace.
Rename the traceability surface to "operation log", move its entry from the sidebar into the settings dialog, and collect the capture level and the retention policy in one dialog. The retention fields are shown inline instead of behind a disclosure; edits stay a draft until Save, and Reset restores the persisted policy. Copy updated across the eight locales.
- render the banner through the Alert API so its grid keeps one full-width text column instead of wrapping inside the empty icon column
- point the cloud link at space.langbot.app/cloud?environment=dedicated and the self-hosted link at the GitHub repository
- keep the last known state and retry the probe (1/2/4/8s plus a re-check on window focus) so a restarting backend cannot hide the banner for the session
- publish --beta-banner-height so the fixed sidebar container, the sidebar wrapper and the wizard shell start below the banner
Systematically cross-checked all 263 route/method pairs against the classifier
and fixed every case where a real change could be mislabelled:
- POST /skills and PUT /skills/<name> were classified as skill_view (read
bucket), so creating or editing a skill was silently unlogged at the mutation
level. They now record skill_update.
- The public webhook ingress (/bots/<uuid>) and the embedded chat widget
(/embed/*) are unauthenticated visitor traffic, not Workspace changes; they
are dropped instead of logged as create/resource.
- Uploading files and indexing documents (data flowing into a resource) are
offered as observations so they never fill the log; deleting a knowledge-base
file is destructive and now records file_delete.
- A plugin's own config-file edit/delete is a plugin change, not an opaque
resource delete; ordered before the /config rule that also matched it.
- The bucket now comes from the action table (plus 'a read verb is always a
read'), so a write action can no longer be forced into the read bucket.
- Added agents and files resource families; localized every new action/resource
type in all nine locales.
- Split the route rules on the HTTP method so a DELETE is classified as a
destructive action instead of falling into the read/write bucket. Uninstalling
a plugin or skill and deleting a knowledge base or MCP server were recorded as
a view/update and left no readable trace.
- Add plugin_uninstall, skill_uninstall, knowledge_base_delete and mcp_delete.
- Skip the assistant's own conversation traffic: a chat session produced opaque
create/resource rows that answered nothing.
- Localize the new actions and the assistant_conversation resource type in all locales.
- Sync the assistant tool action list and apply ruff formatting.