- Expose the workspace's purchased plan revision from the entitlement
snapshot.
- Stack the purchased revision card in front of the latest revision in
the plan comparison dialog; both cards share the same layout, and the
purchased card's copy and prices come from the Space revision endpoint
while benefits stay on the entitlement snapshot.
- Add a cancel-first tooltip for subscribing to the newer revision.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Coerce/default/drop invalid or unknown legacy fields with warnings instead of blocking
- Never block on LocalAgent model; prune dangling KB/tool/MCP references
- Mark custom (non-builtin) 4.10 runners as unsupported_runner instead of failing
- Pin runner plugins to current marketplace versions (0.2.x); fall back to latest when a pin is withdrawn
- Raise plugin install/upgrade timeout to 600s for slow dependency installs
- Assistant configure_pipeline writes the 4.11 runner shape
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.
The list was noisy and hard to read: a constant "verified" badge and the
"L1/L2" level badge on every row carried no information, the same "view X"
line repeated until it buried real changes, and each row rendered its full
payload diff inline.
- Drop the per-row level badge and the "verified" badge; keep only an
exception badge, so a healthy row shows nothing extra.
- Collapse consecutive identical rows into one line with an "xN" count.
- Make the change digest opt-in per row (one expanded at a time) and show a
"<n> changes" toggle instead of dumping the diff.
- Show the registered route under each row so "Resource / System" is no
longer ambiguous about what actually happened.
- Replace the arrows-only pager with a page selector ("Page x / y") plus the
arrows, for logs with hundreds of records.
Verified: tsc, eslint, prettier and check-i18n all pass.
- Count hash mismatches / broken links over the whole filtered history instead
of only the returned page, so the badges no longer reset to zero on page 2.
- Classify failing record ids during the same scan and expose them through an
integrity=all|issues|hash_mismatch|chain_broken listing filter.
- Report scanned_count / scan_truncated so an approximate (row-capped) summary
is never mistaken for a clean one.
- Resolve the acted-on resource identity from route params or the request body,
giving every resource family a "which plugin/skill/knowledge base" trace.
- Render resource types and the two counters through i18n, keeping all eight
locale files key- and line-aligned.
- Export download failed because the URL builder appended the path to a
"/" base, producing a protocol-relative "//api/..." URL that the browser
read as host "api"; the request never reached the backend. Resolve the
base to the current origin, mirroring the other URL builders.
- Remove the orphan ``clear_prune`` action: the branch has no prune/clear
route and no route rule maps to it, so it could never be produced. Its
i18n key is removed too.
- Remove ``tamperedCount``, now unreferenced after the verification badge
was split into integrity/chain counters. The operationTrace catalogue is
now free of unproduced keys.
- Correct the settings controller docstring: only governance, operation
logs, filters and export routes exist; there is no on-demand delete.
Verified: no producer for clear_prune, classify() can no longer emit it,
operationTrace orphan scan returns none, tsc/check-i18n/prettier/py_compile
all pass.
- Capture scope: treat the ``audit`` bucket (viewing the log / tracing
settings) as a read-level observation instead of recording it from the
mutation level. At "mutations only" the log no longer fills with GET page
views; views are recorded only at the read level.
- Coverage: classify plugin lifecycle (install / upgrade / config / page),
skills, knowledge bases and MCP servers, with dedicated actions and
resource types, so those operations are traced instead of falling into the
generic resource bucket. Route rules now support multi-fragment AND
matching and per-verb read/write actions.
- Panel: move "Download logs" next to "Refresh" in the toolbar.
- Verification: count hash mismatches and broken links separately instead of
collapsing both into one "tampered" signal.
- i18n: add the new action labels plus the split verification labels in all
eight locales.
Performance of the operation traceability surface:
- Cache the retention / row-budget / dedupe policy per Workspace and
invalidate it on governance change, so the hot write path no longer
issues three metadata round trips per recorded operation.
- Resolve the governance payload with one metadata query instead of four.
- Turn the dedupe probe into an existence check (LIMIT 1) instead of a
full COUNT over the dedupe window.
- Defer the row-budget COUNT to every N inserts; prune still enforces the
exact budget, and the maintenance loop keeps it bounded.
- Panel: fetch only the records page when paging or filtering instead of
re-requesting governance and the filter catalogue every time.
- Export: include the effective filters (and limit) in the artifact so a
downloaded CSV is self-describing.
Fixes and cleanup:
- Drop the unused OperationLogPruneReport import that broke `tsc`.
- i18n: remove the 9 redundant blank lines unique to en-US and format the
six non-compliant locale files with the project Prettier config; all
eight locales keep identical key sets, key order and placeholders.
Resolve the revision-graph conflict introduced by the release line.
Conflicts
- tests/integration/persistence/test_rag_document_identity.py: master had
independently introduced the same dynamic-head helper (`current_head`) plus
a `DOCUMENT_IDENTITY_REVISION` constant, and it explicitly upgrades to that
revision. Keep master's semantics (the explicit revision matters because
upgrading to the head now also traverses the TOTP branch) while keeping the
module-level alembic imports on this branch.
New migration
- 0030_merge_totp_into_release joins the TOTP branch's own merge revision
(0026_merge_totp_and_rag_identity) with the release head
(0029_merge_rag_identity). Both reached 0025_rag_document_identity without
including each other, which left Alembic with two heads and made
`upgrade head` fail with "Multiple head revisions are present".
Verification
- Single Alembic head confirmed (0030_merge_totp_into_release).
- tests/integration/persistence/test_rag_document_identity.py: 34 passed.
- Full fast integration suite: 356 passed, 84 skipped.
resetPassword() unconditionally added `method` and an empty `totp_code` to
every request, which broke the Playwright smoke test that asserts the
recovery-key flow posts exactly {user, recovery_key, new_password}.
Send only the second-factor fields that apply to the selected method, so the
default recovery-key flow stays byte-compatible with existing callers while
the totp / recovery_code methods still carry their inputs.
Address the migration and formatting failures reported on the TOTP branch.
Migrations
- Register totp_credentials and totp_recovery_codes in
_ALEMBIC_TENANT_TABLES. On a legacy PostgreSQL install these two tables
reference users.uuid, which only exists after 0009, so create_all() must
not run ahead of Alembic the way it did for the other tenant tables.
Without this the PostgreSQL migration test failed with
"column uuid referenced in foreign key constraint does not exist".
Tests
- Resolve the Alembic head dynamically in the RAG document identity
regression instead of pinning 0025_rag_document_identity. The TOTP and
RAG branches now meet at a merge revision, so the pinned value was no
longer the head. This matches the convention already used by
test_migrations_postgres.
Formatting
- Apply ruff format to the new backend modules and prettier to the locale
files and TOTP components, so the lint jobs pass.
Add a per-Account time-based one-time password (TOTP) second sign-in
factor, plus the owner/admin tooling needed to operate it.
Backend
- TotpService: enrolment, constant-time verification with a +/- one step
drift window, single-use recovery codes and disablement.
* shared secret is only ever persisted as a Fernet token whose key is
derived (HKDF-SHA256) from the instance JWT secret and gated by a
key_version epoch;
* recovery codes are only ever persisted as salted
PBKDF2-HMAC-SHA256 digests (600k iterations) and are single-use;
* the consumed counter advances monotonically so a captured code cannot
be replayed inside the same time window.
- New persistence entities and alembic migrations for credentials and
recovery codes.
- Login second-factor challenge, bound to the Account that passed the
password step.
- Owner/admin oversight endpoints to inspect and re-bind the second
factor of any Account.
Frontend
- Account settings panel for enrolling, managing, re-binding and
disabling the second factor.
- Login second-factor step and the matching client methods.
- Strings for all eight locales.
The shared secret never crosses the API boundary: enrolment returns a
server-rendered QR code (as a data: URL) and the plaintext secret is
discarded as soon as the image is produced.
Marketplace cards now reflect whether an extension is already installed in
the current workspace, and the installed-extension list gains a search box.
Backend (stream install progress):
- _read_httpx_response_limited gains an optional task_context: it publishes
download_total from Content-Length before the first chunk and updates
download_current / download_speed per chunk. The marketplace download path
previously had no progress reporting; it now matches the GitHub path.
- _marketplace_get forwards task_context to that helper.
- install_plugin resets the per-install counters so re-installing the same
plugin cannot inherit stale metadata, and reports human-readable stages:
preparing -> downloading -> inspecting -> storing -> installing
dependencies -> launching -> waiting for plugin to become ready.
Frontend (installed state):
- New marketplace-installed helper normalises the sidebar identities
(plugin: author/name, mcp: author__name, skill: bare name) into one
type:author/name index and resolves a card's installed state from it.
useMarketplaceInstalledIndex memoises on the sidebar lists, so a finished
install (which refreshes the sidebar) re-evaluates the cards automatically.
- PluginMarketCardVO carries installed / hasUpdate. An installed extension
turns its download affordance into a hollow green ring with a green check
in place, instead of adding a separate badge; the count slot switches to
the installed label. Cards with an available update use amber.
- PluginMarketComponent derives the annotated list and shares the index with
RecommendationLists.
Frontend (install task UI):
- mapActionToStage matches the new connector stage strings. The pre-download
stages are checked before the generic "install" match, because
"preparing plugin install" also contains "install".
- Stage progress ranges are non-overlapping; overall progress interpolates on
real byte counts while downloading and drifts monotonically elsewhere,
capped at 99%.
- The progress dialog and task queue expose the launching stage.
Frontend (installed list search):
- The installed list had no search at all. A query box in the page header
filters by label / name / author / description, case-insensitively, applied
before grouping so grouped and flat views both honour it.
- Search misses and an empty list now show distinct empty states, with a
clear action on a search miss.
- AsyncTask entity gains the optional created_at field.
i18n: new marketplace / install / search strings across all 8 locales.
Verified: ruff format + check, tsc --noEmit, prettier --check, eslint
(0 errors), and 89/89 frontend unit tests.