Changelog

Every release, documented. Follow development on GitHub.

v0.12.1 June 23, 2026 Latest

Multi-Repo Project Fixes · Project Rename · Configurable Work-Queue Reviewers

  • sfs project repos no longer crashes. Listing a project's linked repos errored on every project because the CLI mishandled the endpoint's JSON-array response; it now lists them correctly.
  • Projects can now be renamed. The project name was previously fixed at creation, so a consolidated or umbrella project could be stuck showing a stale name. A new PATCH /api/v1/projects/{id} (project-admin only) and sfs project set --name let an admin rename it.
  • Promoting a new primary repo keeps the old one linked. Setting a new primary now demotes the previous primary to a regular linked repo — it stays resolvable — instead of dropping it. Enforced by a one-primary constraint at the model level.
  • Project merge surfaces repo linking. Merging projects already moved the source repos into the target; the dry-run plan now reports how many repos will be linked, and the path is covered end-to-end.
  • The autonomous review loop's reviewer authorization is now configurable. A work-queue reviewer's verdict now flows through the settle path with the server stamping the reviewer identity and trust from the authenticated caller, and org admins can register, list, and revoke trusted reviewers (/api/v1/orgs/{org_id}/trusted-reviewers + sfs admin trusted-reviewers). This lets a dedicated automated reviewer key be authorized to post the verdicts the work queue trusts. Tightly gated and fully audited.
  • Additive only — no schema change. Migrations 001–054 (unchanged). 2,525 backend + 397 dashboard UI tests passing. 68 MCP tools (unchanged) · 9 supported tools unchanged.
v0.12.0 June 22, 2026

Agent Work Queues — Autonomous, Supervised Ticket-Closing Loops

  • Point an agent at a queue of tickets and let it run. An agent (Claude, Codex, or Gemini) can be aimed at a work queue and woken repeatedly — via /loop, cron, or CI — to service, review, and close tickets using SessionFS as the source of truth, with no human dispatcher. The server holds all of the loop state, so the agent resumes correctly with zero chat memory. Each wake returns one bounded directive at a time; a crash between steps re-emits the same directive rather than losing or repeating work.
  • Three modes. review_until_clean auto-finishes an item only when the server itself re-derives a trusted, literal VERIFIED-CLEAN with no open findings — it never trusts an agent's self-reported outcome. implement_until_done and triage cover the build-it and sort-it cases. Available on all tiers.
  • Built-in safety envelope. A per-item attempt cap with exponential backoff (2m → 5m → 15m → 60m) parks a stuck item for human reset, plus a per-wake ticket cap, a cadence floor, and a dedicated rate-limit class on the step endpoint — load-bearing, since queues are available on every tier.
  • More trustworthy automated review. Review verdicts now require server-verified provenance: a VERIFIED-CLEAN is recognized only when the server itself stamps it from the authenticated identity, never from a value supplied in the request. Human-facing review display is unchanged; the strict gate backs the autonomous stop oracle.
  • Backend + MCP (MCP / API only — no dashboard UI or CLI yet). 6 new MCP tools (62 → 68): create_work_queue, get_work_queue, list_work_queues, set_work_queue_status, run_work_queue_step, complete_work_queue_step; 2 new scopes (work_queues:read / work_queues:write). Migrations 001–054. 2,487 backend + 397 dashboard UI tests passing · 9 supported tools unchanged. Reviewed end-to-end: Compass design + Atlas/Sentinel design review, four Codex-clean implementation phases, and a Shield-SR pre-release pass.
v0.11.2 June 20, 2026

Self-Service Licensing In The Dashboard — License Activation · Ownership Transfer

  • License activation, now in the dashboard. v0.11.0 shipped the licensing backend; this release adds the web UI for it. A user who isn't yet in an organization can activate a license key from the dashboard with a guided flow: enter the key, confirm the previewed org and tier, submit, then enter the single-use code emailed for verification — landing as the new organization's owner. Email verification is required, and invalid-key and bad/expired-code states are handled inline. Activation creates a new organization from the license.
  • Ownership transfer, now in the dashboard. An organization owner can transfer ownership to an existing admin directly from the members surface. The target sees an Accept / Decline banner, the initiator sees a pending banner with Cancel, and each shows the transfer's expiry. The owner role is shown in the members list and the owner row is protected from demotion or removal. (Distinct from project transfer.)
  • Hardened the profile and activation paths. A brief duplicate organization-membership race no longer fails the profile endpoint or activation: the caller's membership is resolved deterministically, and an already-member caller is rejected cleanly without creating a partial organization.
  • Dashboard UI for the v0.11.0 licensing backend, plus backend hardening. No schema change — migrations 001–052 (unchanged). 2,418 backend + 397 dashboard UI tests passing. 62 MCP tools (unchanged) · 9 supported tools unchanged.
v0.11.1 June 20, 2026

Patch — Entitlements Migration Hardening

  • Hardened the v0.11.0 entitlements backfill migration. Fixed a diagnostic statement that could fail the upgrade on databases where the backfill recorded notes (such as a plan-tier coercion or an unmatched license). No schema change, and no behavior change for databases already upgraded to v0.11.0.
  • Patch release. Migrations 001–052 (unchanged). 2,415 backend + 388 dashboard UI tests passing. 62 MCP tools (unchanged) · 9 supported tools unchanged.
v0.11.0 June 20, 2026

Licensing & Organization Management Redesign — Entitlements As The Single Source Of Truth · Self-Service License Activation · Ownership Transfer

  • Entitlements are now the single source of truth for an organization's plan. Plan tier, seats, and storage for an org are resolved from one authoritative record instead of being scattered across user rows and license tables. This fixes the case where enterprise admins could not fully see or manage their own organizations. Existing orgs are backfilled deterministically from their strongest existing signal, so no plan changes for anyone.
  • Self-service license activation with required email verification. An organization admin can activate a license key against their org directly. Activation completes only after the admin confirms a single-use, time-limited code sent to their email address — verification is required, not optional. The activation lookup never reveals whether an arbitrary key exists, and the flow is rate-limited.
  • Organization ownership transfer. A two-step, owner-initiated transfer (initiate → the new owner accepts, with cancel) moves ownership of an org to another member, backed by an explicit owner role. Both parties' standing is re-validated at accept time, and pending transfers expire if not accepted.
  • Last-owner safety guards. An organization can never be left without an owner: deactivating or transferring away the last owner is blocked or safely re-homed, with a full audit trail. The authenticated profile now returns the caller's effective tier, organization, and org role so the dashboard gates org-management surfaces correctly.
  • Backend + dashboard. Migrations 001–052 (050 entitlements foundation; 051 ownership-transfer table; 052 role/status integrity constraints). 2,415 backend + 388 dashboard UI tests passing. 62 MCP tools (unchanged) · 9 supported tools unchanged. Reviewed end-to-end: Codex + Sentinel design review, Codex code review, Shield-SR pre-release — all clean.
v0.10.32 June 18, 2026

Hotfix — A Transient Card Decline No Longer Locks Out Paying Customers

  • Transient billing-health statuses are now non-destructive. A temporary payment hiccup — for example a card decline that the payment processor automatically retries over the following days — previously downgraded a paying customer to the free tier and zeroed their seats and storage immediately. Now the customer keeps their tier, seats, and storage for the duration of the retry window, and a successful retry simply restores normal service.
  • Recovery path restored. Only a terminal subscription cancellation downgrades an account to free, which also keeps the recovery path intact so a customer who resolves a transient decline returns to their plan automatically. Three regression tests cover the keeps-access case for both users and organizations plus the full recovery flow.
  • Backend only; no schema change. Migrations 001–049 (unchanged). 2,279 backend + 388 dashboard UI tests passing. 62 MCP tools (unchanged).
v0.10.31 June 17, 2026

Multi-Repo Projects — One Project, Many Repos · Verified Repo Linking · Project Merge

  • A project can now own more than one git repo. Products that span several repositories — a backend, a frontend, an infra repo — no longer need a separate SessionFS project per repo. Personas, knowledge, tickets, and rules are shared across every repo in the project instead of duplicated and drifting across single-repo projects. Captured sessions from any linked repo roll up to the same project context. Free for all tiers.
  • Verified-ownership repo linking. Link and unlink repos from the CLI with sfs project link-repo / unlink-repo / repos, with ownership verified before a repo joins a project so you can't attach a repo you don't control. The web dashboard adds a Repos tab to view and manage every repo on a project.
  • sfs project merge to consolidate split projects. If you already had separate single-repo projects for the same product, merge them into one — their repos, knowledge, and tickets fold into a single project. Merge is dry-run by default so you preview exactly what moves before anything changes, with a matching Merge surface in the dashboard.
  • Security — dependency hardening. Upgraded to close freshly-published CVEs in starlette, python-multipart, and cryptography on the backend, plus vite and astro on the site. Shield-SR pre-release security review CLEAN.
  • Backend + CLI + dashboard. Migration 049 (first past 048). 2,278 backend + 388 dashboard UI tests passing (was 2,179 + 369 at v0.10.30; +99 backend / +19 dashboard). 62 MCP tools (unchanged) · 9 supported tools unchanged · pricing tiers unchanged. Shield-SR pre-release security review CLEAN.
v0.10.30 June 14, 2026

Dashboard Redesign — Dark-First UI · Real Transcript View · New Brand Tagline

  • Complete dashboard visual redesign. The web dashboard is rebuilt on a dark-first, design-token UI — consistent color, spacing, and typography tokens across every surface. A new grouped left sidebar makes navigation across sessions, projects, handoffs, and settings faster, and the session list adds list / grid layout toggles so you can scan many sessions at a glance or focus on detail. The dashboard remains a management interface, not a chat app — you interact with your AI tools natively and use the dashboard to review, resume, and share the resulting sessions.
  • Real captured-session transcript view. Session detail now renders the full captured conversation as a readable transcript with tool-aware formatting — file edits, command runs, and search results display as structured blocks instead of raw JSON. The transcript keeps pagination, the oldest/newest order toggle, and the sidechain / empty-message filters from before.
  • New brand tagline: “Memory Layer For AI Agents.” SessionFS is positioned as the memory and coordination layer for AI agents — capture every session, build organizational knowledge, and give your next AI agent everything the last one learned. Tagline and messaging updated across the site and product.
  • Frontend + site only — no backend changes, no new database migrations (still 48), no new MCP tools (still 62), no new CLI commands, no auth changes. 2,179 backend + 369 dashboard UI tests passing (dashboard suite grew from 198 to 369 with the redesign). 9 supported tools unchanged. Shield-SR pre-release security review CLEAN.
v0.10.29 June 11, 2026

Named Auth Profiles — Multi-Account On One Device · Cloud Dashboard CORS Fix

  • Named auth profiles. Run more than one SessionFS identity on a single machine — for example a personal account and a work org account — without re-authenticating each time. A single identity switch now governs coordination, sync, and the daemon consistently. Precedence is SESSIONFS_API_KEY > SESSIONFS_PROFILE > the active profile > default config, with zero migration for existing single-account users. Each named profile gets an isolated local store and trash list. New CLI: sfs auth login --profile / use / profiles / whoami. The daemon pins the profile it starts with so a mid-run switch can't flip identity underneath it.
  • Cloud dashboard 428 fix on Context / Knowledge Injection settings. Saving rules from the hosted dashboard on a separate domain returned a 428. Root cause: a CORS gap hid the ETag header from cross-origin JavaScript, so the dashboard replayed an empty If-Match. Fix exposes the ETag header on cross-origin reads, plus drift-guard tests.
  • Security hardening. Raw API keys are now written with atomic 0600 permissions; react-router bumped 7.13.2 → 7.17.0. Shield-SR pre-release security review CLEAN.
v0.10.28 June 3, 2026

Coordination + Recovery Surface — update_ticket Verb · Admin Key Recovery · Org Invite Resend · /compile Single-Pass

  • New update_ticket verb across MCP, REST, and CLI (tk_835a876529de4551). Until now every ticket correction had to land in the comment stream — descriptions drifted out of sync with reality and start_ticket loaded stale framing into the compiled persona context. PUT /api/v1/projects/{pid}/tickets/{tid} ships partial update for mutable fields (title, description, priority, acceptance_criteria, context_refs, file_refs, depends_on); status transitions remain FSM-only. Authz: ticket creator OR project admin. Optional lease_epoch for optimistic concurrency (409 + structured envelope on stale; last-write-wins when omitted; lease_epoch alone is rejected with 400 — it's a fence, not a mutation). Every successful update auto-posts ONE system diff comment (no-op writes don't pollute the audit) and per-field rows land in the new ticket_edits audit table. depends_on updates run same-project + cycle + dedup validation before wipe-and-reinsert. New sfs ticket update <id> CLI with flags --title / --description / --description-file / --priority / --acceptance-criteria-file / --lease-epoch. Codex R1 (2 MED + 1 LOW) → R2 CLEAN.
  • Admin POST /api/v1/admin/users/{user_id}/api-keys — mint-on-behalf for lost-key recovery (tk_4afbae8ed3a442e9). SessionFS has no magic-link / OAuth / password-reset flow, so a user who loses their key has no self-service recovery — the chicken-and-egg blocks POST /auth/me/api-keys. This admin-gated endpoint mints a user-kind ApiKey on behalf of any active user; raw key returned exactly once; audit row via _log_action(action="mint_api_key_on_behalf"). Guards: 404 unknown user, 403 inactive user (no backdoor for disabled accounts), 403 non-admin caller. Service keys remain out of scope — they live at /api/v1/orgs/{org_id}/service-keys per v0.10.10. Codex R1 (1 MED + 1 LOW) → R2 CLEAN → R3 docstring CLEAN.
  • Org invite UPSERT over stale rows (tk_d88678e6fe384de6). Migration 016 enforces uq_org_invites_org_email on (org_id, email). v0.10.22 added a route-level predicate filtering accepted/declined/expired rows so the admin surface "looked like" a resend was allowed, but the DB constraint blocked the INSERT with IntegrityError → generic 409 duplicate_resource. After expiry / decline, the CEO had no API path to re-invite (production incident 2026-05-31). Fix: both invite_member endpoints lookup ANY existing row with .with_for_update(), classify by state, and route accordingly — no row INSERTs fresh, active row 409s, live member 409s, stale rows UPDATE in place (regenerate id to invalidate stale email links, refresh expires_at, clear accepted_at / declined_at / decline_reason / last_emailed_at, preserve created_at as the audit signal). Codex R1 (1 MED + 1 LOW) → R2 CLEAN.
  • /compile single-pass auto_generate_concepts (tk_5c124c496ea14ecc). The route invoked auto_generate_concepts() twice on real-compile requests — once before the noop classification (intended for both branches) and again after the real-compile path (flagged "(Legacy) ... for compatibility" in the prior inline comment). Doubled LLM call latency and increased timeout / OOM risk on large projects. Removed the second call; concept_pages counted once and reused as concept_pages_updated in both response paths. Codex R1 CLEAN (non-blocking test tightening) → R2 CLEAN.
  • Security — PyJWT 2.12 → 2.13. Shield-SR pre-release scan flagged PYSEC-2026-175/177/178/179 against 2.12.x. Pinned PyJWT>=2.13.0,<3.0. Test suite re-run after upgrade: no regressions. cryptography>=46.0.7 already pinned since v0.10.12 (past all GH #16 reported CVE fix-versions) — GH #16 closed as resolved.
  • 2,153 backend + 198 dashboard UI tests passing (was 2,119 + 198 at v0.10.27; +34 new) · 48 migrations (047 + new 048 ticket_edits — strictly additive, JSON-encoded old/new as Text for SQLite Helm compatibility) · 62 MCP tools (was 61; +update_ticket). Shield-SR independent pre-release security review CLEAN 0 CRITICAL / 0 HIGH / 0 MEDIUM new findings; 4 PyJWT CVEs fixed by version bump. Issues closed: GH #50 (MCP update_persona / delete_persona parity — shipped v0.10.24, re-confirmed); GH #51 (sfs org create 500 + IntegrityError envelope — shipped v0.10.24); GH #16 (cryptography 43.0.3 CVEs — pinned >=46.0.7 since v0.10.12).
v0.10.27 May 30, 2026

Daemon Liveness Hotfix · Tier-Aware Per-File Cap Discovery via /sync/settings

  • Daemon: skip excluded sessions in _sync_sessions() loop (tk_4abfa69b38d54bc0). Without this guard, _sync_sessions() retried sessions already in the local exclusion list (deleted.json) every cycle. A 75 MB excluded session on a paying customer's daemon caused 862+ retries / 4 days, starving the async event loop on decompression + DLP scanning, timing out /health, and tripping 13 liveness probe kills. Fix: is_excluded(session_id) short-circuits with continue + _pending_sessions.discard(session_id) so the next cycle does not re-queue.
  • Server: max_member_bytes in GET / PUT /api/v1/sync/settings (tk_bb56f6a56da34b05). SyncSettingsResponse gains a tier-aware max_member_bytes: int field sourced from _member_size_limit_for_tier(await get_effective_tier(user, db)), tracking the same enforcement as _check_member_sizes and honoring SFS_MAX_SYNC_MEMBER_BYTES_{FREE,PAID} operator overrides. Purely additive response shape; no migration. Older CLIs ignore the new field harmlessly.
  • CLI: tier-aware per-file cap discovery via _resolve_max_member_size() (tk_d5945c4bce3245ce). Explicit precedence: SFS_MAX_SYNC_MEMBER_BYTES_PAID env-var override > server-supplied max_member_bytes from GET /sync/settings > 50 MB literal fallback (logged warning). Cached per CLI invocation — one /sync/settings call per sfs push / sfs pull / sfs sync / sfs handoff / sfs pull-handoff run. sfs sync upload path now also runs the resolver-aware oversized-member preflight (parity with sfs push + sfs handoff — no wasted upload of a guaranteed-413 archive).
  • 2,119 backend + 198 dashboard UI tests passing (was 2,108 + 198 at v0.10.26; +11 net new across the three tickets) · 47 migrations (no new) · 61 MCP tools (unchanged). Shield-SR independent pre-release security review CLEAN 0 CRITICAL / 0 HIGH / 0 MEDIUM. Codex review threads: tk_4abfa69b38d54bc0 R1 (1 LOW ruff) → R2 CLEAN; tk_bb56f6a56da34b05 R1 CLEAN; tk_d5945c4bce3245ce R1 (1 MED missed sync preflight) → R2 CLEAN.
v0.10.26 May 30, 2026

Hotfix — Catch typer 0.26 BadParameter Class Hierarchy Regression

  • handle_errors now catches both click.exceptions.ClickException AND typer._click.exceptions.ClickException. v0.10.25's click<8.4 pin installed click 8.3.3 in CI but tests still failed: typer 0.26.3 vendors its own click module (typer._click), so typer.BadParameter is typer._click.exceptions.BadParameter, NOT click.exceptions.ClickException. handle_errors builds a tuple of base classes at import time — best-effort tries from typer._click import exceptions and adds typer's ClickException / Exit / Abort classes to the tuples used in except clauses. Falls back to standard click on typer <0.26. Click pin removed.
  • 2,108 backend + 198 dashboard UI tests passing (unchanged from v0.10.24/25) · 47 migrations (no new) · 61 MCP tools (unchanged). Same root cause as the Codex round-2 fix on tk_e025375272b84a95 / v0.10.11, resurfaced by typer's 0.26 refactor.
v0.10.25 May 30, 2026

Hotfix — Pin click<8.4 So Deploy API Succeeds

  • Pin click<8.4 in pyproject.toml so CI installs the version handle_errors was tested against. v0.10.24 tagged and Release / MCP / Container-Images pipelines all succeeded, but CI + Deploy API failed on 2 CLI tests that assert click BadParameter-derived exit code and output format. Tests passed on local .venv (click 8.3.1) but CI installed click 8.4.1 which changed both the exit-code and the class hierarchy that handle_errors passes through. Affected sfs org create and other CLI flows that reached the generic "Unexpected error" branch.
  • 2,108 backend + 198 dashboard UI tests passing (unchanged from v0.10.24) · 47 migrations · 61 MCP tools (unchanged). Lifting the pin + making the 2 brittle CLI tests resilient to click BadParameter exit-code / output-format drift tracked as follow-up — superseded by v0.10.26's broader typer 0.26 catch.
v0.10.24 May 30, 2026

Issue / Task Rollup · GH #50 + #51 Enterprise Unblocks · Structured IntegrityError Envelope · Dashboard Surface

  • Issue / Task rollup (tk_dbccde26ed604b3c + tk_5d14f10e489d4361). Migration 047 strictly additive: tickets.kind (issue | task) + parent_ticket_id + composite index (project_id, parent_ticket_id). One user-reported problem can now roll up multiple executor workstreams. Forked FSM: Tasks keep the executor lifecycle (suggested → open → in_progress → blocked → review → done); Issues use a simpler manager FSM (open → in_progress → closed + cancelled escape). New POST /tickets/{tid}/close Issue terminator (rejects Tasks). Actor-based authorization via new user_is_project_admin(db, user_id, project) helper — drops the user-input-trusting assigned_to=='compass' branch. Compiled persona context emits Kind:, Parent Issue:, and child Task list. Dashboard TicketsTab gains kind filters, indigo edge bar, kind badge, in-place Children navigation, Back-to-parent breadcrumb on Tasks, and Close Issue button.
  • MCP update_persona + delete_persona tools (tk_32abb6d0d4744c5d / GH #50). MCP tool count 59 → 61. update_persona wraps PUT /personas/{name} (only fields the caller passed are sent — no silent overwrite); delete_persona wraps DELETE /personas/{name}?force=... with strict force-parsing (literal True OR case-insensitive {"true", "1", "yes"} only; closes bool("false") == True Python footgun). 409 surfaces the structured envelope so callers can route on error.code + error.details.open_ticket_count to decide whether to retry with force=true.
  • sfs org create 500 for manual-license enterprise customers (tk_17b39010f9a64cba / GH #51). One-line await db.flush() between db.add(org) and db.add(member). Latent since v0.10.0 because SQLAlchemy's unit-of-work doesn't reliably topologically sort two pending INSERTs by FK dependency without an intervening flush. For Stripe-paying users the implicit autoflush triggered by update(User) masked the bug; users with no Stripe fields (every manual-license enterprise customer) hit the FK violation → 500.
  • Generic IntegrityError envelope + structured 5xx hardening (tk_e7da4c4508d94bac / GH #51 ask #2). New FastAPI exception handler for sqlalchemy.exc.IntegrityError classifies by PostgreSQL SQLSTATE first, falls back to SQLite message string-matching. Unique violation → 409 duplicate_resource; FK violation → 500 foreign_key_violation; NOT NULL → 422 missing_required_field; CHECK → 422 check_constraint_violation. Handler logs raw DBAPI text at ERROR; client envelope strips column names and row values to avoid PII leak. New CLI helper format_api_error(body, status). Dashboard ApiError class extracts code + details + clean message.
  • Session rename (tk_cf9f1691091d4e8e). Click-to-edit title + alias inline on session detail view, side-by-side. New MCP tool rename_session(session_id, new_title?, new_alias?) — empty new_alias="" routes through DELETE /alias and captures canonical id from the DELETE response so a caller passing the about-to-be-cleared alias as the identifier doesn't 404 on the follow-up.
  • Rules page max-tokens UX + CORS If-Match preflight unblock (tk_d4a13a68b6724ba6 + febd732). Dashboard DebouncedTokenInput gains max={20000} + n > 20000 debounce gate. Server knowledge_max_tokens + context_max_tokens over-cap branches raise structured envelope code: 'max_tokens_exceeded'. CORSMiddleware.allow_headers extended with If-Match + If-None-Match so dashboard ETag-protected writes (PUT /rules) actually fire.
  • 2,108 backend + 198 dashboard UI tests passing (was 2,023 + 187 at v0.10.23; +85 backend / +11 dashboard) · 47 migrations (migration 047 strictly additive) · 61 MCP tools (was 58; +rename_session + update_persona + delete_persona). Shield-SR independent pre-release security review APPROVED 0 CRITICAL / 0 HIGH / 0 NEW MEDIUM. pip-audit 0 vulnerabilities; npm audit (dashboard + site) 0 vulnerabilities.
v0.10.23 May 26, 2026

Opt-In entity_ref Upsert on KB Writes · ChatGPT / Claude.ai MCP Tool-Discovery Hotfix · Persona Case-Insensitive Lookup · Release-Skill Hardening

  • Opt-in entity_ref upsert on /entries/add (tk_49db8d2b6c424d35). New optional upsert: bool = False field on AddEntryRequest. When True AND entity_ref is provided, the route looks up the prior active entry by (project_id, entity_ref, dismissed_at IS NULL, superseded_by_id IS NULL) and supersedes-in-place rather than running the similarity-based dedup that returns 409 on cache rotations. Closes Scout's state-cache silent loss path (3 duplicate tickets filed in 18 hours on a 6%-delta cache body). Existing callers unaffected — feature is fully opt-in. Cloud agent dispatchers (bedrock-action-group.yaml, vertex_tools.py) updated to expose upsert in their tool schemas. Scout n8n docs updated with the dismiss-then-create workaround for clients pinned to v0.10.22 and the upsert recipe for v0.10.23+. Codex R1 MEDIUM + LOW → R2 VERIFIED-CLEAN.
  • ChatGPT / Claude.ai MCP tool-discovery hotfix. New _ConsumedResponse(Response) sentinel in mcp/remote_server.py so handle_mcp returns a no-op ASGI callable after transport.handle_request() consumes the underlying response. Starlette 1.x's Route wrapper rejects None with TypeError: 'NoneType' object is not callable, which truncated the wire response and caused remote MCP clients to see "no tools" after OAuth connect. Auth ordering preserved — Bearer-token validate still runs before transport delegation. 4 new unit tests covering the sentinel shape, ASGI no-op semantics, and auth gate ordering.
  • Persona name case-insensitive lookup + normalize-at-write (tk_884b2321fdb74170). New _resolve_persona_name(db, project_id, name) helper in routes/tickets.py using func.lower(AgentPersona.name) == name.lower() with .order_by(id).first() as a deterministic tiebreaker. start_ticket, create_ticket, and update_ticket PUT all normalize assigned_to through it; unknown names pass through as free-text for back-compat. list_tickets filter switched to func.lower(Ticket.assigned_to) == assigned_to.lower() so discovery surfaces legacy Atlas-cased rows when an agent queries ?assigned_to=atlas. create_persona rejects case-insensitive duplicates with 409. Closes the v0.10.18 onboarding pain where start_ticket 400'd because .agents/atlas-backend.md capitalizes the persona name but agent_personas.name stores it lowercase. 6 regression tests. Codex R1 MED + 2 LOW → R2 VERIFIED-CLEAN.
  • Release-skill Scribe-Site gate (tk_2cc5bcca97284a91). Release script step 6f now reads git diff $(git describe --abbrev=0 --tags)..HEAD -- site/ and skips Scribe-Site invocation + site deploy when wc -l returns 0. Saves ~5-10 minutes on backend-only releases without weakening the "no stale site content" invariant — still mandatory when site/ is touched.
  • .release/README.md documenting expected develop→main merge conflicts (tk_7d3b6b1ac1714c60). Develop-only 71-line pure-docs addition explaining what .release/ contains, which paths conflict at merge time (.claude/commands/release.md, CLAUDE.md, .agents/**), and why we don't use a broad merge=ours .gitattributes rule (would silently drop legitimate develop-side changes).
  • 2,023 backend + 187 dashboard UI tests passing (was 2,007 + 187 at v0.10.22; +16 net new across the bundled tickets) · 46 migrations (no new migrations) · 58 MCP tools (unchanged). Shield-SR independent pre-release security review APPROVED 0 CRITICAL / 0 HIGH / 0 NEW MEDIUM. pip-audit 0 vulnerabilities; npm audit (dashboard + site) 0 vulnerabilities.
v0.10.22 May 24, 2026

Org-Member Project Access Fix · Invite Email + Decline / Resend / /invites Page · Scout Multi-Source Signal Contract

  • Org-member project access enforced everywhere (tk_7a457574c5624e12). The v0.10.0 Phase 5 multi-org design said org members could reach personas / KB / wiki / tickets / agent-runs on org-scoped projects without owning the project row — the model carried the intent, but no route ever enforced it. v0.10.22 ships a shared auth.project_access.user_can_access_project(db, user_id, project) helper and threads it through every project-scoped route group. New org members no longer have to clone the repo locally as a workaround just to read the project's persona doc or KB — their org-membership row is now the authoritative grant. Deny-by-default; helper is the single chokepoint, so a new project-scoped surface that forgets it fails closed.
  • Org invite email + decline + resend + dashboard /invites page (tk_6afbcfefe5804c1d). Both invite endpoints (legacy POST /api/v1/org/invite and multi-org POST /api/v1/orgs/{org_id}/members/invite) now dispatch a notification email through the configured provider via services/invite_helpers.dispatch_invite_email. Best-effort — the invite is still created if delivery fails, and the route never returns 500 on an email backend hiccup. New endpoints: POST /api/v1/orgs/{org_id}/invites/{invite_id}/resend (admin only), POST /api/v1/org/invite/{invite_id}/decline (recipient with optional decline_reason), and GET /api/v1/org/invites/me (caller's pending-invite list). Accept and decline both use atomic rowcount-1 guarded FSM transitions so two tabs can't both win. New dashboard /invites page with a top-nav pending-count badge. Migration 046 is strictly-additive: org_invites gains nullable declined_at, decline_reason, and last_emailed_at columns.
  • Scout multi-source signal-shape contract (tk_918073e8aa4c4478). Docs-only deliverable that finishes the Scout continuous-agent stack started in v0.10.21. New docs/integrations/scout-signal-shape.md (~600 lines) defines the normalized signal envelope every source adapter must emit before the Scout brain sees it, plus four reference n8n Code-node normalizers in docs/integrations/n8n-source-adapters/ (HN Algolia, GitHub Releases, Reddit, RSS). PII scrubbing is a hard rule — the Reddit adapter is the reference implementation (drops author handles, hashes anything left). Adding a new source is now 1 normalizer Code node + 1 Merge-node pin, not "extend the Scout brain prompt"; the brain stays source-agnostic.
  • 2,007 backend + 187 dashboard UI tests passing (was 1,985 + 187 at v0.10.21; +22 net new across the three tickets) · 46 migrations (migration 046 strictly-additive) · 58 MCP tools (unchanged). Shield-SR independent pre-release security review APPROVED 0/0/0/0. pip-audit 0 vulnerabilities; npm audit (dashboard + site) 0 vulnerabilities.
v0.10.21 May 22, 2026

Phase 4a KB Attribution + Scout v4 Contract + AgentRun Provenance + agent_runs:read Now Live

  • Phase 4a — persona_name + author_class on KnowledgeEntry (tk_f5ae3eea92934add). Migration 045 strictly additive: nullable persona_name VARCHAR(64) validated against agent_personas on write (unknown personas → 422, mirrors v0.10.7 wiki page-write policy); author_class VARCHAR(16) NOT NULL DEFAULT 'human' backfills every existing row to 'human'; new composite index idx_knowledge_persona_recent (project_id, persona_name, created_at DESC) for Scout v4's per-persona retrieval. Anti-spoof invariant: POST /entries/add forces author_class = 'agent' whenever auth.actor_type == 'service_key', regardless of request body. GET /entries gains three AND-composed filters: persona_name, author_class, and source_filter (literal substring via .contains(value, autoescape=True)% and _ in user input escaped so they match as data, not LIKE wildcards; Codex R1 LOW fix). Both new fields surface on every read response.
  • MCP add_knowledge persona attribution (tk_8028c79963fe4dc7). Input schema gains persona_name (max 64) and author_class (human|agent). When persona_name is omitted AND the active-ticket bundle's project_id matches the resolved project (mirroring update_wiki_page v0.10.7), the handler auto-threads the bundle's persona AND defaults author_class to 'agent' — without this default the bundle path would land rows as human and miss the agent retrieval channel (Codex R1 MEDIUM). Explicit args win in both directions. The tool response surfaces the attribution that actually landed so callers can detect server-side overrides (anti-spoof).
  • Scout v4 n8n workflow contract (tk_d8e02fb02d874b3f). New docs/integrations/scout-n8n.md (~620 lines) — the full contract for running Scout as a continuous analyst from n8n via the direct HTTP API. Covers the scope matrix, durable trigger_ref via $exec.id + String.hash('sha256'), persona preflight failure path (no AgentRun to complete on 404), POST /agent-runs flow with /start explicitly skipped (user-key only), KB writes with stable source_context format scout:n8n:<workflow_id>:<exec_id>:<signal_id>, dedupe + retry caps (MAX_KB_WRITES_PER_RUN=20, MAX_TICKET_CREATES_PER_RUN=5, MAX_RETRY_PER_SIGNAL=1), failure-branch severity matrix, and a run-N + run-N+1 smoke-test procedure that verifies the agent-memory loop end-to-end. Three rounds of polling-Codex review + one n8n-engineer human review.
  • AgentRunResponse exposes service-key audit triple (tk_a77b671fd86a42fb). actor_type, service_key_id, and service_key_name (v0.10.10 migration 042 columns) now serialize on every AgentRun read path (create, list, get, start, complete, cancel) via the shared _row_to_response helper. No DB migration. Scout n8n smoke-test now verifies provenance directly via GET /agent-runs instead of routing through the KB attribution fallback.
  • agent_runs:read service-key opt-in (tk_31b87575d5534d00). GET /agent-runs and GET /agent-runs/{run_id} converted from get_current_user to require_scope("agent_runs:read"), routed through _get_project_for_auth + assert_service_key_can_access_project. Write routes untouched — agent_runs:write remains required for create + complete; /start and /cancel stay user-key only by design. The scope catalog in docs/api-keys.md flips agent_runs:read from "reserved" to "✅ live".
  • Security — starlette>=1.0.1 pin to close GHSA-86qp-5c8j-p5mr (host-header URL reconstruction → potential auth bypass). Shield-SR caught this during the initial v0.10.21 review; FastAPI 0.135+ requires only starlette>=0.46.0 so the floor pin prevents the resolver from landing on a vulnerable transitive version on the next deploy.
  • Scout v4 unblock. After v0.10.21 deploys, Scout v4 can run as a continuous autonomous analyst from n8n with a least-privilege service key. Each execution loads its own prior findings via GET /entries?persona_name=scout&author_class=agent&limit=30, writes new findings with persona_name=scout + a stable source_context, wraps everything in an AgentRun that surfaces service-key provenance in read responses, and runs without needing a CEO personal key for the runtime persona load. The same loop unblocks every future autonomous agent on the SessionFS fleet (Sentinel-watch, Ledger-monitor, Relay-listener).
  • 1,985 backend + 187 dashboard UI tests passing (was 1,959 + 187 at v0.10.20; +26 net new across the five tickets) · 45 migrations (migration 045 strictly-additive) · 58 MCP tools (unchanged). Codex review across 5 threads — all VERIFIED-CLEAN after fixes. Shield-SR independent pre-release security review APPROVED 0/0/0/0 (caught + fixed the starlette CVE). pip-audit 0 vulnerabilities (after starlette pin); npm audit (dashboard + site) 0 vulnerabilities.
v0.10.20 May 22, 2026

Persona Routes Opt Into Service-Key Auth — personas:read + personas:write Now Live

  • Phase 3.6 service-key opt-in for persona CRUD routes (tk_4d932478298b4e27). Unblocks the n8n Scout agent's runtime persona load and every future autonomous agent that needs to fetch the actual persona doc as its system prompt at runtime. Mirrors the v0.10.18 + v0.10.19 conversion pattern exactly: require_scope(...) on opted-in routes, deny-by-default on the rest.
  • READ routes converted to require_scope('personas:read'): GET /projects/{pid}/personas (list) and GET /projects/{pid}/personas/{name} (detail — the route the n8n Scout's "Get Scout Persona" node calls).
  • WRITE routes converted to require_scope('personas:write'): POST /projects/{pid}/personas (create), PUT /projects/{pid}/personas/{name} (update), and DELETE /projects/{pid}/personas/{name} (soft-delete via is_active=False).
  • Tier C MCP tools intentionally NOT touched. assume_persona and forget_persona mutate the caller's local provenance bundle (not a project row) and remain user-key only.
  • Migration 044 — AgentPersona audit-row columns. Strictly-additive migration mirroring 042 + 043 exactly: agent_personas gains nullable actor_type / service_key_id / service_key_name columns (no defaults, no constraints, no indexes, no FKs). create / update / delete persona writes stamp the triple from AuthContext so audit rows record the service-key principal.
  • Cross-route helper reuse. Persona routes import _get_project_for_auth from knowledge.py (mirrors wiki._get_project_or_404 cross-route import). Service keys load the project by id only; user keys keep the legacy owner / session gate. assert_service_key_can_access_project enforces the org + allowlist boundary AFTER the helper returns at all 5 sites.
  • docs/api-keys.md updatedpersonas:read + personas:write moved from "reserved" to "✅ live" with full endpoint lists. Scope-vocabulary count corrected from 14 → 15 (Codex R1 LOW fix). Scout pattern example extended to include the runtime persona-load step. New explicit note that assume_persona / forget_persona remain user-key only.
  • 1,959 backend + 187 dashboard UI tests passing (was 1,952 + 187 at v0.10.19; +7 net new in tests/server/integration/test_scoped_service_keys.py) · 44 migrations (migration 044 strictly-additive) · 58 MCP tools (unchanged) · no new endpoints. Codex review on tk_4d932478298b4e27: R1 1 LOW (scope-count typo) → resolved in commit cbcd0c6, no behavioral findings. Shield-SR independent pre-release review APPROVED, 0 CRITICAL / 0 HIGH / 0 MEDIUM / 0 LOW.
v0.10.19 May 21, 2026

Ticket CREATE + Knowledge Routes Opt Into Service-Key Auth — knowledge:read + knowledge:write Now Live

  • Phase 3.5 service-key opt-in for ticket CREATE + the knowledge surface (tk_65a096acf57946eb). Unblocks the n8n Scout agent and every future autonomous discovery / research agent that gathers external signals and writes them back as KB entries + new tickets. Mirrors the v0.10.18 ticket-route conversion exactly: require_scope(...) on opted-in routes, deny-by-default on the rest.
  • WRITE route converted to require_scope('tickets:write'): POST /projects/{pid}/tickets — the entrypoint Scout uses to open follow-up tickets from a discovered signal.
  • READ routes converted to require_scope('knowledge:read'): GET /projects/{pid}/entries (list/search) and GET /projects/{pid}/entries/{eid} (detail).
  • WRITE routes converted to require_scope('knowledge:write'): POST /entries/add, PUT /entries/{eid} (dismiss/update), PUT /entries/{eid}/refresh, PUT /entries/{eid}/promote, PUT /entries/{eid}/supersede.
  • knowledge:read is truly read-only for service keys (Codex R1 HIGH 1). The GET /entries search side-effect path previously mutated used_in_answer_count / last_relevant_at / retrieved_count whenever the search matched — a service key with only knowledge:read could therefore alter freshness/decay state. v0.10.19 gates the telemetry UPDATE on key_kind == 'service' AND 'knowledge:write' not in scopes. User keys are unaffected.
  • New _get_project_for_auth helper (Codex R1 MEDIUM 1). Service keys minted by an org admin against a project owned by a different org member previously 403'd on the legacy user-owner / captured-session gate before the org/allowlist boundary could evaluate. The helper now branches on auth.key_kind: service keys load by id (404 only); user keys keep the legacy gate. assert_service_key_can_access_project still enforces the org+allowlist boundary AFTER the helper returns.
  • Migration 043 — Ticket audit-row columns. Strictly-additive migration mirroring 042 exactly: tickets gains nullable actor_type / service_key_id / service_key_name columns (no defaults, no constraints, no indexes). create_ticket and every KnowledgeEntry mutator (dismiss/update/refresh/promote/supersede) stamp the triple from AuthContext so audit rows record the service-key principal on every write path.
  • Tier C routes deliberately NOT opted in. /compile, /rebuild, /dismiss-stale, /health, and /compilations remain user-key only. A regression test (test_service_key_still_denied_on_compile_rebuild_dismiss_stale) pins the deny-by-default contract so any future Phase 4 must opt them in explicitly.
  • docs/api-keys.md updatedknowledge:read + knowledge:write moved from the "reserved for Phase 3" list to "✅ live" with full endpoint lists. New Scout-pattern example added (search_project_knowledge → add_knowledge → create_ticket curl flow). Audit-row narrative updated to include Ticket alongside the other 5 audit tables.
  • 1,952 backend + 187 dashboard UI tests passing (was 1,942 + 186 at v0.10.18; +10 + 1 net new) · 43 migrations (migration 043 strictly-additive) · 58 MCP tools (unchanged) · no new endpoints. Codex review on tk_65a096acf57946eb: R1 NEEDS-FIXES (HIGH knowledge:read telemetry mutation + MEDIUM legacy user-gate) → R2 VERIFIED-CLEAN on commit adf4955. Shield-SR independent pre-release review APPROVED, 0 CRITICAL / 0 HIGH / 0 MEDIUM / 0 LOW.
v0.10.18 May 21, 2026

Ticket Routes Opt Into Service-Key Auth — tickets:read + tickets:write Now Live

  • v0.10.10 Phase 3 route opt-in for the ticket surface (tk_1ea90b1d210d40a8). Unblocks the n8n triage agent build and every future CI / cloud-agent integration that touches tickets. Mirrors the v0.10.10 agent_runs + handoffs conversion pattern exactly.
  • READ routes converted to require_scope('tickets:read'): GET /projects/{pid}/tickets, GET /projects/{pid}/tickets/{tid}, GET /projects/{pid}/tickets/{tid}/comments, GET /projects/{pid}/tickets/{tid}/review-state.
  • WRITE routes converted to require_scope('tickets:write'): POST /projects/{pid}/tickets/{tid}/comments, POST /projects/{pid}/tickets/{tid}/start, POST /projects/{pid}/tickets/{tid}/complete.
  • Cross-org safety — every converted route runs assert_service_key_can_access_project(db, auth, project) before any DB work. TicketComment audit rows now populate actor_type, service_key_id, and service_key_name from AuthContext so service keys never silently impersonate humans. Lease-epoch fencing on complete / comment preserved.
  • Out of scope this releaseresolve / escalate / approve / dismiss / create stay on the legacy get_current_user dependency and continue to reject service keys with service_key_not_allowed. User keys continue to work on all converted routes.
  • docs/api-keys.md updatedtickets:read + tickets:write moved from the "reserved" rows to "✅ live" with the exact endpoint lists. New triage-bot persona example added to the "When to mint one" section.
  • 1,942 backend + 186 dashboard UI tests passing (was 1,935 + 186 at v0.10.17; +7 net new) · 42 migrations (no new) · 58 MCP tools (unchanged) · no new endpoints · no schema changes. Backend test additions live in tests/server/integration/test_scoped_service_keys.py covering ticket scope acceptance + cross-org denial + insufficient-scope rejection + audit-row provenance.
v0.10.17 May 21, 2026

Knowledge Health Fix — pending_entries Now Matches the Compile Pipeline + Two New Counters

  • pending_entries overcount + counterpart undercount fixed (tk_935a4eb62be94676). The count used to be a generalized "uncompiled non-dismissed claims" — it counted superseded / stale claims that compile skips, and missed auto-promotable evidence that compile processes. Operators got "Run compile to process N pending entries" advice that lied in both directions: stayed flat after a successful compile, and reported 0 for projects with only eligible evidence. pending_entries now mirrors the compile pipeline's Phase 2b select EXACTLY: claim_class='claim' AND compiled_at IS NULL AND not dismissed AND freshness_class IN ('current', 'aging') AND superseded_by IS NULL.
  • New auto_promotable_evidence field. Surfaces evidence rows the compiler's Phase 2a will auto-promote AND that survive Phase 2b post-promotion: claim_class='evidence' AND confidence >= 0.5 AND length(content) >= 30 AND not dismissed AND compiled_at IS NULL AND current/aging AND no superseder.
  • New uncompiled_notes field. Counts notes that need bulk_promote first — notes do not auto-promote on compile.
  • potentially_stale flag honest again. Now uses the same compile-eligible predicate as pending_entries — notes / superseded claims with novel terms can no longer drive a false-positive "Context may be stale" warning.
  • Smarter "run compile" recommendation. Drives off compile_work_total = pending_entries + auto_promotable_evidence. Structured breakdown below threshold ("3 pending claims + 1 auto-promotable evidence row — run compile"); single-line above threshold ("Run compile to process N entries (P pending claims, Q auto-promotable evidence)"). "No compilations yet" recommendation now gated on compile_work_total > 0; notes-only fresh projects get "No compile-eligible entries yet — add claims (or promote evidence) before running compile". New "N uncompiled notes — call bulk_promote..." recommendation fires whenever notes exist.
  • MCP get_knowledge_health description rewritten. Documents each field with its exact filter clause so MCP agents read the same contract the route enforces. Schema unchanged.
  • 5 new regression tests pin each surfacetest_health_pending_entries_matches_compile_filter, test_health_counts_auto_promotable_evidence, test_health_potentially_stale_ignores_notes_and_superseded, test_health_auto_promotable_excludes_stale_and_superseded_evidence, test_health_no_compile_advice_when_only_notes_exist. Toggle-tested: temporarily disabling the new filter clauses reproduces the original overcount shape and the regression test fails with the exact pre-fix count.
  • 1,935 backend + 186 dashboard UI tests passing (was 1,930 + 186 at v0.10.16) · 42 migrations (no new) · 58 MCP tools (description rewritten, schema unchanged). Backward-compatible additive API change — uncompiled_notes and auto_promotable_evidence are optional on the dashboard ProjectHealthResponse interface so older self-hosted servers still parse.
v0.10.16 May 20, 2026

Hotfix: Flush DELETEs Before INSERTs in auto_generate_concepts + Route Rollback

  • Second uq_kl_link violation site closed (tk_09d8bdf4f6374a13, R2 follow-up). After v0.10.15 deployed, Cloud Run logs surfaced a SECOND uq_kl_link violation at compiler.py:1379 (auto_generate_concepts.db.commit()). The existing-page branch was deleting prefetched (entry → page) links and immediately re-adding them for the same composite key. SQLAlchemy UnitOfWork orders INSERTs ahead of DELETEs for the same table by default, so the INSERT fired while the old row was still present.
  • Explicit await db.flush() after the delete loop at src/sessionfs/server/services/compiler.py:1346. Serialises DELETEs to the open transaction (still rollbackable) so the INSERTs that follow see a clean slate.
  • Belt-and-suspenders await db.rollback() in the /compile route's exception handler at routes/knowledge.py:976-987. Any future commit failure in auto_generate_concepts no longer poisons the session for downstream _count_pages calls.
  • New regression test test_auto_generate_concepts_flushes_delete_before_insert seeds a concept page + entry-link for the same pair, monkeypatches check_concept_candidates + generate_concept_article to force the regenerate branch, asserts exactly one (entry, page) link survives the delete+re-add cycle. Toggle-tested.
  • 1,930 backend + 186 dashboard UI tests passing (was 1,929 + 186 at v0.10.15) · 42 migrations (no new) · 58 MCP tools. Codex R3 review on tk_09d8bdf4f6374a13: R1 MEDIUM (test false-positive) → R2 VERIFIED-CLEAN. Cloud Run log diagnosis turned a "the worker is being killed somehow" theory into a deterministic fix at the right line.
v0.10.15 May 20, 2026

Hotfix: _auto_supersede Idempotency on uq_kl_link

  • Fixes the ACTUAL /compile 500 on the SessionFS dev project (tk_09d8bdf4f6374a13). The v0.10.14 fix closed an audited crash class but turned out not to be the bug crashing prod. _auto_supersede was creating KnowledgeLink rows with link_type='contradicts' on every compile pass without checking whether the same (source_id, target_id) pair already existed. The supersedes path was self-gating via older.superseded_by; contradicts had no such gate.
  • Prefetch + skip dedup pattern. Prefetch existing entry→entry link (source_id, target_id) pairs ONCE at the top of _auto_supersede; skip db.add(link) when present. In-run adds join the set so two contradictions in the same pass can't double-add. Pair-only dedup (not link_type-aware) matches the DB constraint exactly.
  • ORM model parity with migration 019. Added UniqueConstraint("project_id", "source_type", "source_id", "target_type", "target_id", name="uq_kl_link") to KnowledgeLink.__table_args__ so SQLite test schemas built via Base.metadata.create_all enforce the same uniqueness as production PostgreSQL. Codex R1 MEDIUM 1 fix — without this, regression tests are weaker than prod.
  • Misdiagnosis corrected. The 21-byte text/plain "Internal Server Error" is Starlette's default PlainTextResponse for uncaught exceptions when debug=False — NOT a Cloud Run worker kill. Worker stays alive between requests; only the failing request bubbles.
  • 2 new regression tests: test_auto_supersede_idempotent_on_existing_contradicts_link (monkeypatches word_overlap to 0.75 to force the contradicts branch, asserts flat link count across 2 passes) and test_auto_supersede_skips_when_other_link_type_already_exists (mixed-link-type edge — existing 'related' link prevents adding 'contradicts' for the same pair).
  • 1,929 backend + 186 dashboard UI tests passing (was 1,927 + 186 at v0.10.14) · 42 migrations (no new) · 58 MCP tools. Codex review on tk_09d8bdf4f6374a13: R1 HIGH (dedup key mismatch with constraint) + 2 MEDIUM (ORM parity, test branch coverage) → R2 VERIFIED-CLEAN.
v0.10.14 May 20, 2026

Hotfix: Defensive int(lk.source_id) Guard at 4 Sites + _safe_entry_link_ids Helper

  • Fixes /compile 500 on projects with malformed KnowledgeLink source IDs (tk_d92434fe63564c06). _prune_dead_concept_pages did int(lk.source_id) without a try/except when source_type=='entry'. KnowledgeLink.source_id is a String(64) column; convention is str(KnowledgeEntry.id) but legacy rows can have non-numeric values (slugs, UUIDs, malformed migration data). A single bad row crashed the request.
  • Extracted _safe_entry_link_ids(links, *, page_slug=None) -> list[int] helper at module scope. All 3 compiler sites now route through it: _prune_dead_concept_pages, dismissed-id prepass, per-existing-page deletion check. Malformed rows skip with a warning log instead of crashing.
  • Codex widened the audit (Codex review tk_e5185f5d432243f2 R1 HIGH) — caught a 4th unguarded site outside compiler.py at src/sessionfs/server/routes/wiki.py:602 (page regenerate route). Same crash class, different route. Fixed inline with the matching try/except (TypeError, ValueError) + logger.warning pattern. Final grep confirms only the helper's own (guarded) cast remains.
  • 2 new regression tests: test_prune_dead_concept_pages_skips_malformed_links (direct call path) and test_auto_generate_concepts_existing_page_skips_malformed_links (monkeypatches check_concept_candidates to force the existing-page branch where R1 HIGH lived).
  • 1,927 backend + 186 dashboard UI tests passing (was 1,925 + 186 at v0.10.13) · 42 migrations (no new) · 58 MCP tools. Codex R2 VERIFIED-CLEAN. v0.10.13's fail-closed contract isolated this to "availability only" — zero DB drift on each failed /compile, confirmed in production.
v0.10.13 May 20, 2026

Incident-Driven Safety Release — Fail-Closed /rebuild + Admin Restore Endpoint

  • Fail-closed /rebuild and /compile (tk_bc3c02a63e994717, CRITICAL). Forced by the 2026-05-20 incident on the SessionFS dev project where /rebuild wiped project.context_document and every active claim's compiled_at when the recompile crashed mid-flight. compile_project_context previously committed twice inside the function and the /rebuild route added two more commits BEFORE calling it for destructive resets — four transaction boundaries through one logical compile, with destructive resets in the first two. Any crash after them was data-destructive.
  • Refactored to single atomic commit — Phase 1 reads only (SELECT FOR UPDATE + pending claims + active claims + sessions for persona lookup), Phase 2 compute in memory during LLM call (no DB writes), Phase 3 single atomic transaction (freshness / decay / retention / auto-promote + destructive reset if force_rebuild=True + projection update + entries marked compiled + ContextCompilation INSERT + section pages + ONE commit). Any exception before the final commit rolls back everything.
  • Codex R1 caught a related correctness bugforce_rebuild=True was nulling compiled_at but the compile still read context_before = project.context_document and merged new claims INTO that stale text, so dismissed/superseded/aging entries' content could survive a "rebuild" forever. Fix: split previous_context (audit trail) from compile_base_context (becomes "" on force_rebuild so the merge starts from scratch).
  • Admin repair endpoint (tk_dd3ba7082ef0432e, HIGH) — POST /api/v1/admin/projects/{project_id}/restore-from-compilation recovers a project's context_document AND compiled_at metadata from a chosen ContextCompilation snapshot. Body {compilation_id: int, dry_run: bool = true}. Single atomic transaction. Admin-gated; audit-logged via AdminAction. Codex R1 caught Python's bool-is-int trap; fixed with explicit isinstance(x, bool) rejection.
  • Headline regression test test_rebuild_rollback_on_compile_crash_preserves_prior_state — the test that would have caught the 2026-05-20 incident if it had existed. Plus 9 integration tests for the admin restore endpoint covering dry-run/apply, 404 paths, non-admin 401/403, and strict bool validation.
  • 1,925 backend + 186 dashboard UI tests passing (was 1,912 + 186 at v0.10.12) · 42 migrations (no new) · 58 MCP tools. Codex review on tk_879dbd5a5a034d0e: R1 1 HIGH (force_rebuild empty-base) + 1 MEDIUM (strict bool rejection) → R2 VERIFIED-CLEAN. Shield-SR independent security review: 0 CRITICAL / 0 HIGH / 0 MEDIUM. Release approved.
v0.10.12 May 19, 2026

Bulk-Promote Eligible KB Notes — Practical Repair Surface + Release-Process Hardening

  • Bulk-promote eligible KB notes to claims (tk_c64915570f4d4042). The v0.10.10 confidence-clamp bug left many production KBs with hundreds of stuck note-class entries; per-entry repair (PUT /entries/{id}/confidence + PUT /entries/{id}/promote × N) doesn't scale past ~5 entries. New POST /api/v1/projects/{pid}/entries/bulk-promote + promote_eligible_entries MCP tool (57 → 58 tools) + sfs project promote-eligible --confirm CLI. Eligibility: class=note, not dismissed, not superseded, ≥ min length, ≥ min confidence (unless caller overrides via set_confidence), no near-duplicate against active claims. Dry-run is the default.
  • Per-ticket review-state endpoint row cap (tk_33a25a12a5cf4dc3, Shield-SR LOW follow-up from v0.10.11) — GET /tickets/{tid}/review-state now caps TicketComment fetch at 500 rows (parity with list_ticket_comments). ORDER BY (created_at, id) ASC ensures the earliest review rounds always survive the cap on long threads.
  • Release-process hardening — three improvements informed by the v0.10.11 post-mortem: (a) pinned mypy >= 1.20 in dev extras so local matches CI (v0.10.11 CI on main failed because local mypy 1.19.1 missed 11 union-attr errors); (b) new .release/sanitize_main.py deterministic helper for stripping private files during develop → main merge, with --dry-run default and 8 unit tests; (c) post-PyPI smoke step in the release skill that runs pip install sessionfs==X.Y.Z in a fresh venv with retry logic for PyPI index lag.
  • Codex R1 caught a dry-run/confirm asymmetry on bulk-promote (MEDIUM): the original implementation only appended eligible content to the compare set inside if not dry_run, so a dry-run with two near-duplicate notes reported both as promoted while the confirmed run correctly skipped one. Fix: append unconditionally; only the field writes stay gated. The "inspect-then-mutate" safety contract now holds. Also lowered boundary defaults from 0.85 → 0.80 to match the single-entry gate.
  • 1,912 backend + 186 dashboard UI tests passing (was 1,871 + 186 at v0.10.11) · 42 migrations (no new) · 57 → 58 MCP tools (promote_eligible_entries). Codex R2 VERIFIED-CLEAN on bulk-promote. Shield-SR auto-upgraded 9 HIGH/MEDIUM CVEs: cryptography 46.0.5→48.0.0, idna 3.11→3.15, mako 1.3.10→1.3.12, pygments 2.19.2→2.20.0, urllib3 2.6.3→2.7.0, pip 26.0→26.1.1.
v0.10.11 May 19, 2026

Service-Keys CLI + Per-Ticket Review-State Derivation + DNS-Error Catch

  • CLI for v0.10.10 scoped service keys (tk_e0d7db15ff814c0a) — closes the human-facing surface gap so issuing, listing, rotating, and revoking keys no longer requires curl. sfs admin service-keys list|create|revoke|rotate|scopes (org admin + Team+ tier for mutations) + sfs auth keys list|create|revoke (personal-key surface). --output-key flag on create + rotate emits ONLY the raw key for CI capture. Local scope validation rejects unknown scopes + * wildcard before any network call.
  • docs/api-keys.md (tk_522991717c6446c9) — public reference covering both kinds of keys, the 14-scope vocabulary with current opt-in status (only handoffs:write and agent_runs:write live today; 12 remaining scopes reserved for Phase 3 route opt-in), service-key lifecycle, cloud-agent integration recipes, rotation policy, structured error code reference.
  • Per-ticket review-state derivation (tk_e025375272b84a95) — compact summary of open findings, closed findings, last verdict, and severity counts for long review threads. No new schema. New GET /tickets/{tid}/review-state + get_ticket_review_state MCP tool (56 → 57) + sfs ticket review-state CLI. Closure rule: findings raised in round N close when any subsequent round has VERIFIED-CLEAN verdict.
  • DNS-error catch on sfs ticket watch (tk_aeb8580706d84e2e) — the shared _api_request helper now catches httpx.RequestError (parent of ConnectError, TimeoutException, NetworkError, SSL errors — but NOT HTTPStatusError) and exits 1 with an actionable Configure cloud auth first: sfs auth login message instead of a raw Python traceback. Every CLI command using _api_request benefits.
  • Shared _api_request bug fixes — DELETE method now forwards json_data via client.request("DELETE", ..., json=...) (httpx's client.delete() doesn't accept json=); 204 No Content responses no longer crash resp.json().
  • 1,871 backend + 186 dashboard UI tests passing (was 1,819 + 186 at v0.10.10) · 42 migrations (no new) · 54 → 57 MCP tools (update_entry_confidence, promote_entry, get_ticket_review_state). Five separate Codex review threads, all VERIFIED-CLEAN. Shield-SR: 0 critical / 0 high / 0 medium new findings.
v0.10.10 May 18, 2026

Scoped Service API Keys — Cloud Agents, CI Runners, and Integration Partners Get Capability-Restricted Tokens

  • Scoped service API keys for non-human callers. Cloud agents (Bedrock, Vertex), CI runners (GitHub Actions, GitLab MR), and integration partners now get expirable, scope-restricted keys instead of broad user bearer tokens. Replaces the "use your existing personal bearer token" pattern that was carried over from v0.10.2–v0.10.9.
  • Migration 042 — additive. ApiKey gains key_kind ('user' | 'service'), org_id (FK organizations CASCADE, required for service keys), scopes (JSON list), expires_at, revoked_at, revoke_reason, created_by_user_id, last_used_ip (IPv6-safe), service_key_name, project_ids (optional per-key project allowlist), key_prefix. Existing user keys back-fill to scopes='["*"]' so every legacy token continues to authorize unchanged.
  • Deny-by-default for service keys. get_current_user rejects service keys with 403 service_key_not_allowed. The ONLY way a service key reaches a route handler is via require_scope("handoffs:write") / require_any_scope(...) dependencies. Pre-route enforcement, not post-route middleware — side effects are impossible from unauthorized service keys.
  • 14 capability scopessessions:read, handoffs:read/write, tickets:read/write, personas:read/write, knowledge:read/write, rules:read/write, agent_runs:read/write, retrieval_audit:read, admin:*. * wildcard reserved for legacy user/admin keys; service keys must enumerate explicitly (422 at create otherwise).
  • Cross-org boundary. assert_service_key_can_access_project enforces that a service key's org_id matches the target project's org_id before any project-scoped state change. Optional per-key project_ids allowlist gives finer control. Handoff routes resolve the source session's project via sessions.project_id (authoritative since migration 036) — Codex Phase 2 R6 caught and fixed a git_remote_normalized fallback that originally "preferred the key's org" on ambiguity (bypass vector).
  • Org-scoped admin surface. POST/GET /api/v1/orgs/{org_id}/service-keys, DELETE /api/v1/orgs/{org_id}/service-keys/{id}, POST /api/v1/orgs/{org_id}/service-keys/{id}/rotate. Org admin role + Team+ tier required for mutations. Cross-org 404 (not 403) for existence hiding. Personal user keys under /api/v1/auth/me/api-keys. Raw key returned exactly once on create + rotate.
  • Structured error codesapi_key_revoked, api_key_expired, service_key_not_allowed, insufficient_scope (with required + current arrays), cross_org_denied (with key/project org IDs), service_key_project_required, service_key_project_not_registered, service_key_project_ambiguous. Agents can distinguish rotate-needed from permission-needed without log-scraping.
  • Audit-row provenance. Five audit-row tables (TicketComment, KnowledgeEntry, AgentRun, RetrievalAuditEvent, HandoffEvent) gain actor_type ('user' | 'service_key') + service_key_id + service_key_name. Service keys never silently impersonate humans in provenance. Phase 2 route opt-in covers POST /handoffs, claim, revoke, decline, comments POST + agent_runs create/complete.
  • KB confidence honored end-to-end. Manual/MCP add_knowledge calls supplying confidence now reach the database with the caller-supplied value — the silent min(confidence, 0.7) clamp on session_id in ("cli-ask", "manual") (and the matching MCP-side default that triggered it on every call) is removed. AddEntryRequest.confidence is now Optional[float]; when omitted, legacy 0.7-for-manual / 1.0-for-session-derived defaults still apply. New PUT /api/v1/projects/{pid}/entries/{id}/confidence as a repair path for entries clamped before the fix landed.
  • Compile no-op response includes word counts. POST /compile no-op response now derives context_words_before/after from project.context_document (same source as /health.word_count) instead of returning 0. New noop_reason field explains why nothing compiled (e.g., "N note(s) are uncompiled — notes do not auto-promote; update confidence then promote").
  • list_ticket_comments MCP tool (bundled from the v0.10.9 cycle) — wraps GET /api/v1/projects/{pid}/tickets/{id}/comments with since (ISO timestamp) + since_id (cursor tiebreaker for same-millisecond ties) + limit (1-500, default 200). Unblocks autonomous Codex/Claude review polling loops over MCP.
  • 1,819 backend + 186 dashboard UI tests passing (was 1,796 + 186 at v0.10.9) · 42 migrations (additive 042) · 54 MCP tools. Codex review across 7 rounds on parent ticket tk_2e030a85253143df: Phase 1 R1 scope → R2 schema → R3 implementation → R4 re-review VERIFIED-CLEAN; Phase 2 route opt-in R5 → R6 → R7 VERIFIED-CLEAN. KB confidence fix Codex R1 → R2 → R3 VERIFIED-CLEAN — HIGH finding (MCP write-path clamp) caught during review, original Atlas fix only addressed the symptom. Shield-SR independent pre-release security review: 0 CRITICAL / 0 HIGH / 0 MEDIUM. Release approved.
v0.10.9 May 17, 2026

Comprehensive Handoff Redesign — Provenance Carry-Through, Team Handoffs, Curated Attachments, Lifecycle Events

  • Handoff as a first-class coordination primitive. Single-feature release that elevates handoffs from "copy a session blob to an email recipient" to the same plane as tickets, personas, and agent runs. Migration 041 is additive (zero existing data loss): 12 new columns on handoffs and 5 new tables (teams, team_members, handoff_attachments, handoff_comments, handoff_events).
  • Provenance carry-through. Sender attaches ticket_id + persona_name; on claim, the response includes an active_ticket_payload the recipient's CLI writes to ~/.sessionfs/active_ticket.json. The next captured session is automatically tagged with the handed-off context. Persona-only handoffs derive project_id from the source session's git_remote.
  • Exactly-one-recipient invariant. Every handoff specifies one of recipient_email, recipient_user_id, or recipient_team_id. Enforced server-side via Pydantic @model_validator with strip_blank validators so whitespace-only IDs collapse to None before the count check.
  • Team handoffs (Team+ tier). Hand off to a team; any team member can claim with atomic race protection. UPDATE Handoff WHERE id=X AND status='pending' runs FIRST; new_session_id is pre-allocated and included in the atomic UPDATE. Race losers never write blobs or insert Session rows — no orphan storage. New team_handoff feature flag on Team + Enterprise tiers.
  • Curated attachments. attachments[] list of {kind: kb_entry|wiki_page|ticket, ref_id} validated against the session's project at create time. At claim, attachments are re-validated against recipient's accessible projects; inaccessible refs are silently dropped and surfaced in dropped_attachments with structured reason (not_accessible | deleted | invalid_id | unknown_kind).
  • Lifecycle endpoints. POST /handoffs/{id}/revoke (sender-only, required reason), POST /handoffs/{id}/decline (recipient-only, optional reason), POST/GET /handoffs/{id}/comments (both parties, paged 200), GET /handoffs/{id}/events (audit log).
  • Existence-hiding (404-not-403). Non-parties get 404 on GET /handoffs/{id}, /claim, /decline, /comments, /events, /summary. Eligibility is checked BEFORE any lazy-expire writes or status-specific responses so non-recipients can't distinguish pending vs claimed vs revoked via response codes.
  • Lazy expiry + viewed_at tracking. Pending handoffs past expires_at flip to expired on read with audit event. Per-tier expires_in_hours clamping: 720h (30d) on Free/Pro/Team, 2160h (90d) on Enterprise. First GET by a non-sender stamps viewed_at + emits a viewed event.
  • 4 lifecycle email notificationssend_handoff_claimed (to sender), send_handoff_revoked (to recipient), send_handoff_declined (to sender), send_handoff_comment (to the other party). All templates html.escape() user-supplied fields. All sends are best-effort try/except — never fail the route.
  • 8 new handoff MCP toolscreate_handoff, claim_handoff, get_handoff, list_inbox_handoffs, list_sent_handoffs, revoke_handoff, decline_handoff, add_handoff_comment. Plus list_ticket_comments follow-up. 46 → 54 tools total.
  • CLI extensions. sfs handoff accepts --to-user-id, --to-team-id, --ticket, --persona, --expires-hours, --attach kind:ref_id (repeatable). New subcommands: sfs handoffs get | revoke | decline | comment | comments | events. sfs pull-handoff now persists active_ticket_payload to ~/.sessionfs/active_ticket.json.
  • 1,796 backend + 186 dashboard UI tests passing (was 1,745 + 186 at v0.10.8) · 41 migrations (additive 041) · 54 MCP tools. Codex review across 5 rounds on parent ticket tk_89e90060e6314311: R1 (scope) clean with 2 corrections; R2 (schema) 3 MEDIUM + 1 LOW all fixed pre-implementation; R3 (implementation) 2 HIGH + 4 MEDIUM + 2 LOW all fixed; R4 re-review VERIFIED-CLEAN. Shield-SR independent pre-release security review: 0 CRITICAL / 0 HIGH findings.
v0.10.8 May 16, 2026

v0.10.7 Hotfix — CI Test Isolation Fix to Unblock Cloud Run Deploy

  • Test isolation fix. test_returns_kb_and_session_sources passed locally but failed on the public CI runner because _fetch_kb_entries_raw re-imports load_config from sessionfs.daemon.config inside the function. The monkeypatch on mcp_server.load_config didn't intercept the re-import; CI without ~/.sessionfs/config.toml on disk got empty defaults → empty sources_cited → assertion fail. Fix patches both namespaces.
  • No product code changes from v0.10.7. This release unblocks Deploy API + Deploy MCP Server pipelines that failed on the v0.10.7 push. Cloud Run picks up v0.10.7's customer-ask provenance fields + sfs ticket watch CLI + migration 040 SQLite-compat fix here. Version skipped 0.10.7.1 because Helm rejects 4-segment versions (strict SemVer). 1,745 backend + 186 dashboard UI tests passing.
v0.10.7 May 16, 2026

Customer-Ask Provenance — sources_cited, Wiki History, Personas Active, Lease Required Mode

  • sources_cited on ask_project — typed list[{type: "kb"|"session", id}] returned alongside the assembled research markdown. KB IDs come from a structured re-fetch (_fetch_kb_entries_raw) — no regex extraction from prose. Session IDs come from the local search index. Same evidence-trail pattern as v0.10.5 source_entries.
  • Wiki page revision history — new wiki_page_revisions table (migration 040) stores every page edit with revision_number, revised_at, user_id, persona_name, ticket_id, full content snapshot. New GET /api/v1/projects/{id}/pages/{slug}/history endpoint with cursor pagination. New get_wiki_page_history MCP tool (45 → 46 tools). Wiki PUT now accepts optional persona_name + ticket_id; MCP update_wiki_page auto-threads them from the active-ticket bundle when project matches.
  • personas_active on session summaries — new session_summaries.personas_active JSON list collected from manifest + per-message persona annotations. Refreshed on both deterministic and narrative regeneration paths. Documented as session-level overblocking (per-decision authorship requires summarizer prompt rework — separate ticket).
  • Lease required-mode org setting — org admins can flip Organization.settings.require_lease_epoch_on_ticket_writes = true; complete/comment/accept ticket writes then return 422 if lease_epoch is omitted. Existing supplied-lease behavior unchanged. Personal projects (no org) bypass.
  • New CLI sfs ticket watch <id> — polls the GET /comments endpoint and renders new comments live (Panel + Markdown). Flags: --interval N (clamped to [5, 300] seconds, default 30), --from-author NAME filter, --exit-on-new for CI scripting, --notify for macOS terminal-notifier. Pairs with sfs ticket comments (v0.10.5).
  • Migration 040 SQLite-incompat fix — first implementation called op.create_unique_constraint after op.create_table, which fails on SQLite (ALTER TABLE can't add constraints). Codex flagged across R2–R7. Fix: sa.UniqueConstraint(...) moved inside op.create_table() as a column-level argument; downgrade simplified to drop the table.
  • Wiki revision provenance ownership check_validate_revision_provenance allows ticket_id only when the user owns the ticket through one of three roles: creator, current resolver, or active executor (open RetrievalAuditContext for this ticket created by the user via start_ticket, with matching lease_epoch AND ticket still in_progress). Lease+status gate replaces the original closed_at IS NULL check.
  • 1,745 backend + 186 dashboard UI tests passing (was 1,727 + 186 at v0.10.6) · 40 migrations · 46 MCP tools. +18 backend: ask_project sources_cited, wiki history + revision provenance + executor-association + stale-lease + post-status, lease required-mode + personal-project bypass, sfs ticket watch clamping/filter/exit-on-new/404, migration smoke xfail. Shield-SR independent pre-release review CLEAN — 0 CRITICAL / 0 HIGH / 0 MEDIUM. Codex review across 8 rounds (R1 scope + R2–R8 implementation): final R8 verdict VERIFIED-CLEAN.
v0.10.6 May 15, 2026

Kilo Code Capture — 9-Tool Coverage

  • Kilo Code added as the 9th supported tool (capture-only) — VS Code extension (marketplace ID kilocode.Kilo-Code), fork of Roo Code which is a fork of Cline. New KiloCodeWatcher is a thin subclass of ClineWatcher: same per-task UUID storage format, different globalStorage path (kilocode.kilo-code).
  • Wired end-to-end across the toolchain — daemon registration, sfs init auto-detection, watcher CLI plumbing, MCP install (sfs mcp install --for kilo-code registers SessionFS as an MCP server in Kilo Code's settings), recapture path, and resume rejection (capture-only, like Cursor/Amp/Cline/Roo Code).
  • Bidirectional resume targets unchanged — resume targets remain claude-code, codex, copilot, gemini. Kilo Code sessions can be resumed in any of those four.
  • 1,727 backend + 186 dashboard UI tests passing (was 1,720 + 186 at v0.10.5) · 39 migrations · 44 MCP tools. +7 backend covering Kilo Code watcher detection, capture, recapture guard, and resume rejection.
v0.10.5 May 15, 2026

SoD Cohort Disqualification + Tier-Aware Archive Cap + Ticket Comments CLI

  • Compile source manifest extensionget_context_section.source_entries now carries created_by_persona (resolved from the source session's persona_name, set by the daemon's active-ticket annotation pipeline) and the parent compile_id. Agent Runner SoD callers can disqualify by persona/tool/whole-compile cohort, not just by created_by_user_id. Persona attribution is one batched SELECT per compile (bounded by distinct session_ids, not entry count); compile_id is denormalized at response time so the compile + manifest stay one atomic write.
  • Tier-aware archive unpack capsync/archive.py:validate_tar_archive and unpack_session now accept member_limit_bytes. CLI pull / sync / pull_handoff thread MAX_MEMBER_SIZE (reads SFS_MAX_SYNC_MEMBER_BYTES_PAID) into unpack. The old hardcoded 50 MB silently nullified paid-tier overrides above 50 MB — same class of bug DLP carried before v0.9.9.8. Default fallback is 100 MB, matching the server abuse cap in _validate_tar_gz.
  • New CLI sfs ticket comments <id> — read-only client of the existing GET /api/v1/projects/{id}/tickets/{id}/comments endpoint. Closes the gap where cross-agent review threads could only be read through the dashboard. Renders each comment in a titled Panel with author + created_at; Markdown body content so code fences render legibly.
  • Cross-project persona leak on source_entries closed — persona attribution SELECT now constrains Session.project_id == project_id AND Session.is_deleted == False. A project-scoped KB entry pointing at a session from another project (KnowledgeEntry.session_id is plain text, not a project-validated FK) no longer leaks that session's persona_name into this project's source_entries. Deleted-session attribution degrades to created_by_persona=null without error.
  • Deterministic compile_id in same-timestamp bucketget_context_section latest-compile lookup now orders by (compiled_at DESC, id DESC). Since compile_id is part of the SoD evidence contract, identical-timestamp tiebreaks need to be deterministic.
  • Security: site devalue HIGH (GHSA-77vg-94rm-hx3p, CWE-770 DoS, CVSS 7.5)site/node_modules/devalue 5.6.4 → 5.8.1 via npm audit fix. Astro stays on 6.3.1. No app code change.
  • 1,720 backend + 186 dashboard UI tests passing (was 1,711 + 186 at v0.10.4) · 39 migrations · 44 MCP tools. +9 backend: archive tier-aware regressions, cross-project persona leak regressions, same-timestamp compile tiebreak regression. Shield-SR independent pre-release review: 0 CRITICAL / 0 HIGH / 0 MEDIUM after devalue fix. Codex R2 on tk_12e6d8775eb045a2 (compile source manifest): no findings.
v0.10.4 May 15, 2026

Ticket Lease Fencing + Retrieval Audit + Source Manifest

  • Migration 039 — adds tickets.lease_epoch (NOT NULL DEFAULT 0), context_compilations.source_manifest (TEXT NOT NULL DEFAULT '{}'), sessions.retrieval_audit_id (nullable, indexed), and two new tables: retrieval_audit_contexts (one row per ticket start, links to project + ticket + persona + lease_epoch + created_by_user) and retrieval_audit_events (events per context, with serialized arguments/returned_refs, source flag, caller_user_id).
  • Ticket lease fencingstart_ticket atomically increments lease_epoch via UPDATE ... WHERE status IN (...) SET lease_epoch = lease_epoch + 1. complete_ticket, add_ticket_comment, and accept_ticket accept optional lease_epoch in the request body and use it as an inline WHERE-clause predicate. Stale daemons get 409 with a clear current-vs-supplied message. Coordinated audit, not strict mutex — opt-in semantics documented on the MCP tool descriptions.
  • Compile source manifestcompile_project_context snapshots the active KB claims feeding each section before LLM compilation and stores them on the compilation row. get_context_section returns source_entries so SoD/audit callers can trace rendered prose back to the claims that shaped it.
  • Retrieval audit log (server primary + local fallback) — 4 new routes: POST /api/v1/projects/{id}/retrieval-audit-contexts, POST /api/v1/projects/{id}/retrieval-audit-events, GET /api/v1/retrieval-audit-contexts/{ctx}/events, GET /api/v1/sessions/{id}/retrieval-log. Local fallback: ~/.sessionfs/retrieval_logs/<id>.jsonl for offline MCP clients. MCP records context-shaping retrievals (search_project_knowledge, get_wiki_page, get_persona, get_compiled_rules, get_context_section, find_related_sessions, get_session_context) when an active ticket bundle has retrieval_audit_id; sessions persist the id from active_ticket manifests so the audit chain survives capture.
  • MCP get_session_retrieval_log tool (44 total). Server-success and local-fallback paths return identical top-level keys {session_id, retrieval_audit_id, events, count}; local rows are lifted into the server's RetrievalAuditEventResponse shape with source="local" so consumers parse one schema.
  • New CLI sfs persona pull [<name>|--all] — pulls server-side personas to local .agents/*.md files. Preserves the existing kebab-naming convention on re-pull (<name>-<role-fragment>.md); HTML-comment preamble only (no double H1 against the persona's own identity header). Release-only personas untouched.
  • Site messaging rewrite for v1.0 positioning — landing / features / enterprise / pricing reframed around Memory / Identity / Coordination / Governance pillars. “Easy handoffs from humans to agents.” hybrid-operator section on enterprise. Cloud-agent-ready strip (Bedrock, Vertex, custom API) phrased precisely as “via integration docs” — not a hosted gateway.
  • Retrieval-audit defenses — 10 sentinel + 2 Codex follow-up findings closed. Path traversal: SAFE_AUDIT_ID_RE regex validates audit/session IDs at every entry point. Session upload validates claimed retrieval_audit_id exists in retrieval_audit_contexts for the same project AND was created by the uploading user — silently drops the field with a warning otherwise. Context create validates supplied ticket_id and persona_name belong to the same project; rejects forged provenance with 422. Event creator must own the context. Payloads capped at 16 KiB with _truncated marker. Cross-project SELECT leak closed via accessible-project subquery filter. Comment lease check is atomic. Local JSONL caps at 10 MiB. sanitize_arguments strips any key containing api_key/token/secret/password/auth/credential. collect_returned_refs walker has 50-level depth limit.
  • 1,711 backend + 186 dashboard UI tests passing (was 1,626 + 186 at v0.10.3) · 39 migrations · 44 MCP tools. +85 backend: retrieval-audit API regression suite, ticket lease tests, compile source-manifest assertions, sentinel hardening regressions. Shield-SR independent pre-release review: 0 CRITICAL / 0 HIGH / 0 MEDIUM. One LOW noted on context-create vs context-events visibility; tracked as Atlas follow-up, not release-blocking.
v0.10.3 May 14, 2026

Dashboard UI: Personas, Tickets, AgentRuns Tabs

  • Three new ProjectDetail tabs — Personas, Tickets, AgentRuns surface the v0.10.1 personas + tickets and v0.10.2 AgentRun backends. Read-focused MVP: the moderation transitions a human reviewer wants ship; the FSM transitions tied to the local active-ticket bundle stay in CLI/MCP. Reviewed across 2 rounds of Codex review on ticket tk_530dfba7f14446dd.
  • Personas tab — list with role + specializations + last-updated; create modal with name-immutable + ASCII regex pattern + markdown content; edit modal; delete with --force toggle for the server's non-terminal-ticket guard.
  • Tickets tab — list filtered by status (all 7 FSM states + All); expand-in-place detail panel (description, acceptance criteria as ☐ boxes, dependency list, completion notes, comments); approve (suggested→open) + dismiss (suggested/open→cancelled) buttons gated by current status; inline comment composer; new-ticket modal.
  • AgentRuns tab — audit-trail read-only view (CI-driven on the backend); status + persona + trigger filters; 30s refetchInterval; expand to view tool / ticket / trigger ref / fail-on / duration / structured findings JSON. Clickable CI run URL goes through a safeHttpUrl() allowlist (http/https only) so a crafted javascript: or data: URL falls back to plain text.
  • API client + react-query hooks for all three surfaces. 14 new methods on ApiClient; new typed interfaces mirror the server Pydantic responses field-for-field.
  • CLI sfs agent complete --findings-file now rejects non-object list elements locally with an element-index message before the HTTP call (avoids 422 stranding the run in running).
  • handle_errors decorator preserves typer.Exit(N) exit codes — latent bug: typer.Exit is a RuntimeError, not a SystemExit, so generic except Exception was downgrading every typer.Exit(N) to SystemExit(1) with "Unexpected error: N". Affected 7 sites across cmd_agent / cmd_persona / cmd_config / cmd_project. Now catches click.exceptions.Exit explicitly and re-raises as SystemExit(exit_code).
  • Dashboard addToast signature rewritten across 10 sites from addToast({kind, message}) to addToast(type, message). npm run build was failing TS2554 at every site.
  • Dashboard ci_run_url scheme guard via safeHttpUrl() allowlist with rel="noopener noreferrer"; DeleteConfirmModal Esc-close handler aligned with the other modals.
  • 1,688 backend + 186 dashboard UI tests passing (was 1,686 + 165 at v0.10.2) · 38 migrations · 43 MCP tools. +2 backend (findings-file rejection + Exit-preservation regression); +21 dashboard (three new tab test files covering happy + filter + expand + action-gating + toast + URL safety + Esc close). Shield-SR independent pre-release review CLEAN.
v0.10.2 May 14, 2026

AgentRun + CI Integration

  • AgentRun + CI Integration — an auditable execution record for one persona run, optionally against one ticket, with CI-friendly policy enforcement (severity × fail_onexit_code). Tracking + enforcement, not model auto-spawning — the CI script picks the LLM, SessionFS records the result. Cross-agent reviewed across 10 rounds of Codex review on ticket tk_f5381e113f144be5.
  • Migration 038 — new agent_runs table. Project-scoped FK; persona_name / ticket_id / session_id are plain strings (no FK) so audit rows survive persona-renames and session-deletes. Columns: status, severity, trigger_source / ref, ci_provider / run_url, findings JSON-as-text (NOT NULL DEFAULT '[]'), fail_on threshold, policy_result, exit_code, duration_seconds, started_at / completed_at. Five indexes including idx_agent_run_project_status, idx_agent_run_ticket, and three project-scoped composites for persona / trigger / created lookups.
  • AgentRun REST APIPOST/GET/POST start/POST complete/POST cancel at /api/v1/projects/{id}/agent-runs. Atomic UPDATE ... WHERE status IN (...) rowcount-1 guards on every transition. Same-project ticket validation (cross-project ticket-id rejected with 422). Errored/failed final statuses force exit_code=1 so --enforce always fails CI on crash. Tier: Team+ (agent_runs).
  • CLI sfs agent — 4 subcommands: run (create + start + emit compiled context to --context-file), complete (record findings + severity + policy result with --enforce for CI exit-code propagation), status (text / json / markdown formats; markdown is GitHub/GitLab step-summary compatible), list. Machine-safe --output-id on sfs agent run and sfs ticket create for CI scripting (stdout is just the id; human "Created ..." confirmation goes to stderr).
  • Cloud Agent Control Plane — Bedrock + Vertex AI integration. docs/integrations/bedrock-action-group.yaml + docs/integrations/bedrock_lambda.py (Bedrock action-group dispatcher with closed OPERATIONS table + parse.quote(safe="") on path params blocking traversal); docs/integrations/vertex_tools.py (function-calling schema + dispatcher). Public docs at Cloud Agents.
  • CI Integration docs pageCI Integration with policy matrix, crash-safety patterns, and PR-injection hardening playbook (token scoping, $[object Object] template-substitution avoidance, persist-credentials: false, separate comment-on-pr job) developed across 10 rounds of Codex review.
  • GitHub Actions + GitLab CI example workflowsdocs/integrations/github-actions-agent-run.yml + docs/integrations/gitlab-agent-run.yml. Defense layers: per-step SESSIONFS_API_KEY env scoping (off the PR-controlled review step), persist-credentials: false on actions/checkout, permissions: contents: read only, PR title/body via env: (never $[object Object] template), jq -e 'type == "array" and all(.[]; type == "object")' findings-shape guard routing crash / missing / malformed / non-object-element to the errored complete path so runs never stick in running, workspace-relative .sessionfs/ paths so hashFiles() works AND artifacts upload.
  • MCP tools (7 new)create_agent_run, complete_agent_run, list_agent_runs, approve_ticket (suggested → open dispatch), checkpoint_session (regex-validated snapshot name blocks path traversal), list_checkpoints, fork_session (records parent_session_id + optional forked_from_checkpoint lineage). Session forking + checkpointing previously CLI-only are now exposed via MCP through shared pure helpers in src/sessionfs/session_ops.py. Total MCP tool count: 36 → 43.
  • Env-var auth for CI_get_api_config now honors SESSIONFS_API_KEY / SESSIONFS_API_URL env vars before falling back to ~/.sessionfs sync config, so a fresh CI runner with only the documented secret authenticates without sfs auth login first.
  • Manifest schema additionspersona_name, ticket_id, instruction_provenance, _resume_parent_id added to src/sessionfs/spec/schemas/manifest.schema.json. Fixes a Phase-6 bug where active-ticket annotation collided with additionalProperties: false.
  • Tool-aware token budgets_compile_persona_context now recognises bedrock (16000 tokens) and vertex (8000 tokens) alongside the existing tool aliases.
  • 1,686 backend + 165 dashboard UI tests passing (was 1,626 + 165 at v0.10.1) · 38 migrations · 43 MCP tools. +60 backend over v0.10.1: 27 AgentRun coverage (lifecycle, policy matrix, atomic concurrent-start race, cross-project guards, tier gate, errored/failed exit-code forcing); 9 sfs agent CLI tests (markdown step-summary, json, output-id stream routing, env-var auth precedence, --enforce defense-in-depth); 14 MCP coverage for the new tools (approve dispatch + 409 path, checkpoint/list/fork happy paths, name regex rejection, duplicate-name rejection, missing-session, missing-checkpoint, prefix resolution); plus Cloud Agent integration smoke tests. Shield-SR independent review CLEAN — zero CRITICAL/HIGH findings.
v0.10.1 May 13, 2026

Agent Personas + Ticketing System

  • Agent Personas + Ticketing System — first SessionFS release with first-class persona + ticket management. Personas are portable AI roles per project; tickets are self-contained units of work with a server-enforced FSM, dependency graph, comments, and an active-ticket provenance bundle that tags every captured session with persona_name + ticket_id. Built across 6 phases under the cross-agent review pattern.
  • Migration 037 — 4 new tables: agent_personas (project-scoped, ASCII-name UNIQUE, soft-delete via is_active), tickets (FSM: suggested → open → in_progress → blocked → review → done | cancelled; reporter provenance split into created_by_user_id / session_id / persona), ticket_dependencies (composite-PK edge table with reverse-lookup index), ticket_comments (slack-like, non-idempotent). New columns on sessions: persona_name and ticket_id.
  • Persona CRUD APIGET/POST/PUT/DELETE /api/v1/projects/{id}/personas/{name}. ASCII regex ^[A-Za-z0-9_-]{1,50}$ (no Unicode leak). Pre-check duplicate before insert with narrow constraint-name catch. Persona-delete refuses 409 when non-terminal tickets reference the persona; ?force=true overrides. Tier: Pro+.
  • Ticket CRUD + lifecycle API — 13 routes under /api/v1/projects/{id}/tickets. Atomic UPDATE ... WHERE status='X' rowcount-1 guards on start_ticket and accept. Cross-project dependency validation + JOIN-filtered enrich (belt + suspenders). BFS cycle detection. Agent-created tickets require ≥1 acceptance criterion + ≥20-char description, max 3 per session_id. Tier: Team+.
  • Compiled persona + ticket contextstart_ticket returns {ticket, compiled_context}. Markdown assembled from persona content + ticket section (description, criteria as checkboxes, file refs, KB claims, completed-dep notes) + recent comments. Tool-aware token budget via ?tool=: claude-code 16k, codex/gemini/copilot/amp/generic 8k, cursor/windsurf/cline/roo-code 4k tokens.
  • Cross-project leak defense_compile_persona_context filters every data-exposure SELECT by project_id. KB claims filtered by KnowledgeEntry.project_id == ticket.project_id AND claim_class='claim' AND superseded_by IS NULL AND dismissed=False AND fresh; completion notes filtered by same project_id AND status='done'.
  • MCP tools (14 new across personas + tickets)list_personas, get_persona, create_persona, assume_persona, forget_persona (5 personas); list_tickets, get_ticket, create_ticket, start_ticket, complete_ticket, resolve_ticket, assign_persona, escalate_ticket, add_ticket_comment (9 tickets). Total MCP tool count: 22 → 36.
  • Shared active-ticket bundlesrc/sessionfs/active_ticket.py exposes bundle_path(), read_bundle(), write_bundle() (returns bool, surfaces provenance_warning on OSError), clear_bundle_if_owned() (reads-before-unlink; only removes when both ticket_id AND project_id match the completing ticket).
  • CLIsfs persona list|show|create|edit|delete|assume|forget (7 commands); sfs ticket list|show|create|start|complete|comment|status|block|unblock|reopen|approve|dismiss|assign|resolve|escalate (15 commands). --tool / --force / --no-print-context flags on start; --as PERSONA on comment; --raw on show.
  • Daemon integration — all 7 watchers (claude_code, codex, copilot, cursor, gemini, amp, cline) call annotate_manifest_with_active_ticket(session_dir) after capture, which reads the local bundle and adds ticket_id + persona_name to the session's manifest.json. Server-side _extract_manifest_metadata sanitizes at column widths.
  • Router precedencepersonas.router and tickets.router registered BEFORE projects.router in app.py so their /{project_id}/(personas|tickets)/... paths beat the catch-all /{git_remote_normalized:path} (same trick as rules + project_transfers).
  • 1,626 backend + 165 dashboard UI tests passing (was 1,502 + 165 at v0.10.0) · 37 migrations · 36 MCP tools. +124 backend over v0.10.0 across schema canaries, persona CRUD, ticket FSM + dependency + atomicity, comments + compiled context + cross-project leak regressions, bundle module, CLI smoke, watcher annotation, session-upload provenance roundtrip, and Phase 8 agent-workflow tools.
v0.10.0 May 13, 2026

Org Admin Console

  • Org Admin Console — first SessionFS release with full org-level administration. Org admins manage members, transfer projects between scopes, edit org defaults, and link captured sessions to org-scoped projects from a single Organization page and a parallel CLI surface. Shipped across 7 phases.
  • Migration 035projects.org_id (nullable FK, ON DELETE SET NULL) gives projects an explicit org scope. users.default_org_id stores each user's preferred org for multi-org routing. New project_transfers table provides a durable audit + state machine for cross-scope moves, with a partial-unique pending index as the DB-level backstop for concurrent-initiate races.
  • Migration 036sessions.project_id (nullable FK, ON DELETE SET NULL, indexed). Server resolves the linkage on every sync from the workspace git remote so the org-scope of any captured session is recoverable.
  • Project transfer APIPOST /projects/{id}/transfer initiates; POST /transfers/{xfer_id}/{accept,reject,cancel} mutates state. Atomic UPDATE ... WHERE state='pending' with rowcount check prevents double-accept. Auto-accept when initiator == target (personal → own org). Standing is re-validated on accept/reject.
  • Multi-org member management API — list memberships, invite, promote/demote, remove. Removal preserves all member-authored data ("data stays, access revoked") — sessions stay user-owned, org-scoped projects auto-transfer with an audit row, KB entries preserve authorship, default-org clears, pending transfers tied to the removed user's standing are cancelled. SELECT ... FOR UPDATE row-locks the admin rows before the last-admin guard.
  • Org settings APIGET/PUT /orgs/{id}/settings for the three KB creation defaults (kb_retention_days, kb_max_context_words, kb_section_page_limit). Admin-only PUT with range validation; DLP block survives a general-settings PUT via structural merge. New org-scoped projects inherit the defaults at create time.
  • Default-org APIGET /auth/me now includes default_org_id. PUT /auth/me/default-org sets it (membership validated); passing null clears.
  • Session project resolution — both upload surfaces (POST /sessions and PUT /sessions/{id}/sync) resolve session.project_id from the workspace git remote inside the write transaction with SELECT ... FOR UPDATE on Project and OrgMember rows. Re-sync re-evaluates; pre-v0.10.0 sessions retroactively pick up project_id when the project is created later.
  • Dashboard surfaces — new MembersTab (org member management), /transfers route inbox, per-project TransferPanel tab, OrgSettingsTab for KB creation defaults. New nav link with a pending-incoming badge.
  • CLIsfs project init --org <id> / --personal for explicit scope, sfs project transfer --to|--accept|--reject|--cancel, sfs project transfers --direction --state for transfer ops, sfs config default-org [<id>|--clear] for the server-canonical default-org preference.
  • 1,502 backend + 165 dashboard UI tests passing (was 1,384 + 117 at v0.9.9.12) · 36 migrations · 22 MCP tools. +118 backend (project transfers, org members, default-org routing, org general settings, creation-time inheritance). +48 dashboard (MembersTab, TransferInbox, TransferPanel, OrgSettingsTab).
v0.9.9.12 May 12, 2026

Daemon-Reindex Data Loss Hotfix

  • Daemon-reindex data loss when self-heal runs against any malformed manifest — user-reported "7-8 Codex sessions disappeared from sfs list after daemon restart following Index-was-corrupted recovery" traced to manifest.get("source", {}) returning None on "source": null, plus NOT NULL bindings (created_at, source.tool) hitting sqlite3.IntegrityError on the same input class. store/index.py rewritten with _as_dict / _as_list isinstance guards so any non-matching type (falsy or truthy) normalizes to an empty container instead of crashing.
  • Per-session exception broadened in the reindex loop_rebuild_index_from_disk in store/local.py previously caught only (json.JSONDecodeError, OSError), so any AttributeError / IntegrityError / TypeError aborted the entire loop and every session sorted alphabetically AFTER the bad one was silently dropped. Now except Exception with WARNING-level skip logging and an "N indexed, K skipped" summary.
  • sqlite3.IntegrityError misinterpreted as index corruptionupsert_session_metadata's except sqlite3.DatabaseError branch caught IntegrityError (parent class) and ran the destructive recreate-and-retry recovery path per bad session. IntegrityError-before-DatabaseError split now catches it first and re-raises so the per-session skip handles it cleanly. Same fix applied to upsert_tracked_session.
  • sfs daemon rebuild-index CLI command had the same two bugs duplicatedcli/cmd_daemon.py:rebuild_index carried its own copy of the null-unsafe defaults and the too-narrow except. Fixed in parallel with matching isinstance guards and broadened per-session except. Backfill block now repairs "source": null / "source": "codex" shapes in-place when a tracked_sessions row exists for the ID. Cross-reference comment added; DRY refactor queued for v0.10.x.
  • New regression suitetests/unit/test_resilience.py covers malformed-manifest survival across both the daemon and CLI reindex paths (+8 backend tests).
  • Helm chart 0.9.14 → 0.9.15, appVersion bumped to 0.9.9.12. Chart evolves independently of app per Helm SemVer.
  • 1,384 backend + 117 dashboard UI tests passing · 34 migrations · 22 MCP tools
v0.9.9.11 May 12, 2026

Dashboard Rules Tab Freeze Fix

  • Dashboard freeze when editing project rules → knowledge / context max tokens (RulesTab.tsx: debounce + flush-on-blur/unmount + boolean-gated Compile).
  • 1,376 backend + 117 dashboard UI tests passing · 34 migrations · 22 MCP tools
v0.9.9.10 May 12, 2026

KB Search Trigram Index, Sync Status Query Collapse, ANSI Test Helper

  • pg_trgm GIN index on knowledge_entries.content (migration 034) — accelerates ILIKE substring search used by GET /projects/{id}/entries?search= and MCP search_project_knowledge. PostgreSQL only; SQLite is a no-op. Route and MCP wrapper now enforce a 3-char minimum on search so every accepted query benefits from the index.
  • /sync/status collapsed 5 queries to 2 — watchlist counts (watched/queued/failed) now come from a single GROUP BY status SELECT against tracked_sessions. Aggregate session counters remain on a single multi-aggregate SELECT. Behavior unchanged.
  • Admin /orgs no longer N+1s — member counts for the paginated org list load via a single WHERE org_id IN (...) GROUP BY org_id SELECT, then merge into the response. Empty-page guard prevents IN ().
  • sfs project ask keyword extractor rebuilt — strips trailing punctuation, lowercases, drops stop words, enforces the 3-char server floor, dedupes, caps at 5 keywords. Previous tokenizer produced 422s on common 2-char tokens (db, ai, ui) once the search gate landed. Canary test fails if CLI floor and server gate drift.
  • Shared ANSI-strip test helper (tests/utils/ansi.py) — replaces four inline regex sites that previously protected tests from the v0.9.9.8 CI-color flake class. 10 dedicated unit tests; case-insensitive matching is opt-out.
  • confirm_or_exit() CLI helper — single source of truth for interactive confirmation prompts with a non-TTY guard so piped EOF returns a structured exit instead of leaking "Unexpected error" through handle_errors. Wired into DLP scan-on-push and push-after-delete prompts.
  • /release skill step 12 hardened — cache-busted probes, strict grep -F version match against the changelog endpoint, vercel inspect <live-alias> on the deployment that actually serves traffic, output captured to a variable so set -o pipefail doesn't mask failures.
  • urllib3 bumped past CVE-2026-44431 / CVE-2026-44432 (fix in 2.7.0). Transitive — no direct pin in pyproject.toml.
  • click.exceptions.Abort backstop in handle_errorsAbort does not inherit from ClickException; without an explicit catch it bubbled up as "Unexpected error: aborted." for any cancelled typer.confirm.
  • Helm chart 0.9.12 → 0.9.13, appVersion bumped to 0.9.9.10. Chart evolves independently of app per Helm SemVer.
  • 1,376 backend tests + 109 dashboard UI tests passing · 34 migrations · 22 MCP tools
v0.9.9.9 May 11, 2026

Deploy API Gate Fix, Stale-Window Tuning, Onboarding Polling Cleanup

  • CI / Deploy API gate fix — tests now ANSI-strip Rich-rendered CLI output before substring assertions. v0.9.9.8's Deploy API was blocked on this regression, leaving api.sessionfs.dev stuck at 0.9.9.7. Pipeline unblocked for this release and going forward.
  • Dashboard polling consolidated — removed dead useRunAudit hook; audit polling now lives only in BackgroundTasksProvider. One source of truth, no duplicate timers.
  • Onboarding stops polling on completion — getting-started page no longer refetches state once the user has at least one session AND one project. Cuts idle background traffic for fully onboarded users.
  • Stale windows extended 30s/60s → 300s — folder list and inbox-handoff queries reuse cached data for five minutes instead of seconds. Mutations still invalidate so no correctness risk; the dashboard just stops hammering the API while you read.
  • Helm chart 0.9.11 → 0.9.12, appVersion bumped to 0.9.9.9. Chart evolves independently of app per Helm SemVer.
  • 1,344 backend tests + 109 dashboard UI tests passing · 33 migrations · 22 MCP tools
v0.9.9.8 May 11, 2026

Tier-Aware DLP Caps, Handoff UX Fix, Doctor PATH Drift Detection

  • DLP per-member size cap is tier-aware — was hardcoded 50 MB and ignored SFS_MAX_SYNC_MEMBER_BYTES_PAID overrides. Now derives from effective tier (10 MB free/starter, 50 MB pro/team/enterprise) with a post-redaction guard so a redact-then-repack flow can't bypass the cap.
  • sfs handoff <handoff_id> redirects to pull-handoff — used to fail with "Missing option --to" when recipients ran the command they were emailed. Now prints clear guidance pointing at sfs pull-handoff <id>.
  • handle_errors preserves Typer validation — invalid CLI options no longer get swallowed as a generic "Unexpected error". UsageError / BadParameter pass through with the original Typer formatting and exit code.
  • Manual sync per-session retry of transient exclusions — hard-delete tombstones still respected via atomic check-and-clear under fcntl.flock. TOCTOU-safe: a delete that races against retry can't be silently undone. Malformed deleted.json entries handled defensively across all helpers.
  • sfs doctor detects PATH drift — when pip installs to user-site but the shell PATH points at an older binary, doctor reports the mismatch via shebang parsing and recommends a fix. python -m sessionfs fallback added for shimless environments.
  • Helm chart 0.9.10 to 0.9.11, appVersion bumped to 0.9.9.8.
  • 1,344 backend tests + 109 dashboard UI tests passing · 33 migrations · 22 MCP tools
v0.9.9.7 May 10, 2026

Tier-Aware Sync Caps, dismiss_knowledge_entry MCP Tool, KB Performance Indexes

  • Tier-aware per-member sync size cap — free / starter capped at 10 MB per archive member, pro / team / enterprise at 50 MB. New env vars SFS_MAX_SYNC_MEMBER_BYTES_FREE and SFS_MAX_SYNC_MEMBER_BYTES_PAID for self-hosted overrides. Oversized members surface SyncTooLargeError with actionable guidance.
  • dismiss_knowledge_entry MCP tool — 22nd MCP tool. Idempotent (calling twice is a safe no-op) with full audit triple: dismissed_at, dismissed_by, dismissed_reason. PUT /entries/{id} now SELECT FOR UPDATE locks the row so concurrent dismissals can't tear the audit record.
  • Concept compiler bulk prefetch — collapsed an N+1 (concept candidates × claims) into 1 + 1 + 1 queries. Compile times on knowledge-rich projects drop from seconds to milliseconds.
  • Handoff list batching + recipient_email_normalized index — migration 032. Inbox query plan no longer N+1 over recipients; large org inboxes load in a single round trip.
  • KnowledgeEntry composite indexes — migration 033 adds covering indexes for the list / pending / cursor pagination paths. Default-sort list_entries with high-volume projects is now bounded-time.
  • Dismissal audit columns on knowledge_entries — migration 031 makes dismissed_at / dismissed_by / dismissed_reason first-class so dashboard and audit reports can show "who dismissed this and why" without recomputing from history.
  • Helm chart 3-segment SemVer fix — chart version goes 0.9.9.6 to 0.9.10 (4-segment chart versions are invalid SemVer in Helm). appVersion bumped to 0.9.9.7.
  • Security — pip-audit advisories cleared and dashboard postcss bumped (transitive npm advisory fix).
  • 1,300 backend tests + 109 dashboard UI tests passing · 33 migrations · 22 MCP tools
v0.9.9.6 May 10, 2026

MCP Tier A Read Surface, Tier-Aware Knowledge Rate Limits, Compile Race Fix

  • MCP Tier A read surface — 7 new tools (get_knowledge_entry, list_knowledge_entries, get_wiki_page, get_knowledge_health, get_context_section, get_session_provenance, compile_knowledge_base). Total MCP tool count goes 14 to 21. All tools enforce existing project membership / session ownership checks.
  • list_knowledge_entries rich filters — claim_class, freshness_class, dismissed, session_id query params plus three sort modes (created_at_desc, last_relevant_at_desc, confidence_desc) with stable id tiebreak so identical sort-key values can't reorder.
  • Keyset cursor pagination — opt-in ?cursor=<id> on list_entries (default sort). Snapshot-stable across concurrent inserts/deletes — no skipped or duplicated rows. Server emits X-Next-Cursor on every default-sort page so callers can bootstrap iteration from page 1.
  • Per-user tier-aware knowledge rate limits — free=20, starter=50, pro=100, team=100, enterprise=200, admin=500 requests/hour on POST /entries/add. Bucket key is user_id (not session_id) so MCP manual callers don't share buckets. Admin tier promoted before effective-tier resolution.
  • Project-row lock on compile + concept generation — compile_project_context and auto_generate_concepts both SELECT FOR UPDATE the project row before reading pending claims / creating concept pages. Eliminates duplicate-context-document and duplicate-concept-page races.
  • 413 graceful failure — sfs push / handoff / sync pre-scan archives for oversized members (10MB limit) and surface SyncTooLargeError with actionable guidance instead of an opaque server 413.
  • Compression-safe capture guard — shared should_recapture() helper consulted by all 7 watchers before re-write. Checks deleted-sessions exclusion list FIRST, then compares source JSONL message count against existing .sfs to prevent re-capturing a compressed session as empty.
  • sfs recapture command — manual re-capture flow with CursorComposerPurgedError guard for purged-composer cases.
  • Cross-tool MCP-over-CLI nudge — three-layer fix so agents prefer MCP tools over sfs shell commands: tool descriptions instruct "use MCP instead of sfs", a guidance block is injected into every compiled rules file, and sfs hooks install writes a SessionStart hook for Claude Code that emits the cached compiled output before the first message.
  • Dashboard "Compile now" CTA — workflow hint banner now carries an inline button. Server-side health recommendations fire at any pending count > 0 (was > 20).
  • Pre-upgrade Helm migration Job — runs Alembic upgrade head before the API rolls out, so a self-hosted upgrade that drops a new migration cannot serve traffic against an unmigrated DB.
  • Dashboard manualChunks split — explicit vite.config.ts chunk naming. Main bundle drops from 212 KB to 30 KB (8x smaller); vendors cache separately across deploys.
  • /api/v1/health alias — added alongside /health for self-hosted ingress paths that scope readiness to /api/v1/*.
  • Settings.json fcntl locking — sfs hooks install/uninstall and rules emitter use fcntl.flock on a .sfs-lock file so concurrent invocations can't corrupt the JSON.
  • KB health pending count fix — only counts claims (excludes notes). Notes don't compile, so they shouldn't drive the pending banner.
  • Daemon transient errors no longer exclude sessions — only SyncTooLargeError (413) counts toward the exclusion threshold; transient SyncError failures are retried instead of permanently excluded.
  • 1,279 backend tests + 109 dashboard UI tests passing · 30 migrations · 21 MCP tools
v0.9.9.5 April 17, 2026

First-Run Onboarding, Unified Delete Propagation, Sort & Filter Improvements

  • First-run onboarding — signup auto-authenticates and navigates to /getting-started. Three-step onboarding page (install tool, capture session, create project) with live completion indicators. State-based redirect gate: 0 sessions + 0 projects sends new users to onboarding. API key shown in dismissible banner after signup.
  • Sort direction toggle — ascending/descending on all sort modes in the dashboard. CLI adds --sort messages-asc and --sort tokens-asc for finding small sessions.
  • Tool sort mode — real "Sort: Tool" in the sessions list groups by tool label.
  • Tool filter alias normalization — gemini/gemini-cli and copilot/copilot-cli treated as the same family across list, search, and admin endpoints.
  • Unified 410 delete propagation — structured SyncDeletedError replaces string-based detection. Shared cleanup_deleted_session() helper wired into all three sync paths. Dashboard-deleted sessions are auto-cleaned locally on next sync instead of showing red 410 errors.
  • Full local cleanup on server 410 — removes .sfs directory + SQLite index entry (sessions and tracked_sessions tables) + adds to exclusion list. No more orphaned local copies.
  • Cloud Run min-instances — API deployment now sets --min-instances 1 to eliminate cold-start latency.
  • Migration 030 cross-DB fix — replaced raw PostgreSQL-only INTERVAL syntax with SQLAlchemy Core queries (works on both PostgreSQL and SQLite).
  • sfs delete/restore prefix resolution — now resolves session ID prefixes via local store for all scopes (was only resolving for local/everywhere, not cloud).
  • sfs rules init TTY guard — fails fast with clear message when stdin is not a TTY and --yes is not passed.
  • 1,205 backend tests + 109 dashboard UI tests passing · 30 migrations
v0.9.9.4 April 16, 2026

Session Delete Lifecycle — Three-Scope Delete, Trash/Restore, Admin Purge

  • Three-scope session delete — sfs delete <id> with --cloud (server only, keep local), --local (device only, keep cloud), or --everywhere (both). No default scope — explicit choice required. Confirmation prompt with --force bypass for automation.
  • Sync-aware deletes — autosync respects intentional deletes via ~/.sessionfs/deleted.json exclusion list. Push and pull skip excluded sessions. Un-delete path gated behind X-SessionFS-Undelete header with ETag conflict check — autosync can never reverse a delete.
  • sfs trash — lists soft-deleted sessions in the 30-day retention window with scope badges and purge dates.
  • sfs restore <id> — reverses a soft-delete on the server, clears local tombstone, prints sfs pull guidance when local copy was removed.
  • Dashboard delete dialog — replaces the single confirm() with a two-choice dialog: "Remove from cloud" or "Delete everywhere". One-line explanation per option.
  • Dashboard Trash view — filter toggle on the session list showing soft-deleted sessions with scope badges, purge dates, restore buttons, and scope-aware restore guidance toast.
  • Admin purge endpoint — POST /admin/purge-deleted hard-deletes expired soft-deleted sessions and their blobs. Single-session or bulk. Returns purge count and bytes reclaimed.
  • Restore response guidance — POST /sessions/:id/restore returns restored_from_scope and local_copy_may_be_missing so clients show accurate recovery guidance.
  • DELETE endpoint breaking change — now requires ?scope=cloud|everywhere query parameter (was parameterless). Returns 200 with session record including purge_after (was 204). Old clients without ?scope= get 400.
  • Storage quota fix — soft-deleted sessions excluded from used-bytes calculation.
  • Share links for deleted sessions — return 410 Gone (was 404).
  • Autosync un-delete bug fix — original soft-delete was immediately reversed by autosync pushing the local copy. Now gated by explicit intent header + ETag check.
  • Security — CVE-2026-40347 (python-multipart DoS via crafted multipart preamble/epilogue) + purge endpoint hardening (session_id format validation + atomic audit logging).
  • 1,203 backend tests + 98 dashboard UI tests passing · 30 migrations
v0.9.9 April 14, 2026

Rules Portability — Canonical Project Rules, Five Tool Compilers, Resume-Time Sync

  • Canonical rules per project — new project_rules + rules_versions tables (migration 028) with 4 new instruction-provenance columns on sessions. Managed via GET/PUT /projects/:id/rules and POST /projects/:id/rules/compile with ETag-based optimistic concurrency (SELECT FOR UPDATE row lock).
  • Five tool compilers — deterministic, partial-compile-aware compilers for Claude Code (CLAUDE.md), Codex (codex.md), Cursor (.cursorrules), Copilot (.github/copilot-instructions.md), and Gemini (GEMINI.md). Each embeds a SessionFS managed marker at the top of the file.
  • Knowledge + context injection — compilers pull active claims (default convention + decision types) and project context sections (default overview + architecture) with per-tool token ceilings and progressive condensation.
  • sfs rules CLI — init (auto-detects existing rule files + recent session-history tool usage), edit, show (in-sync state), compile (--tool, --dry-run, --force), push, pull. Default is shared-in-repo; --local-only adds compiled files to .gitignore.
  • Managed-file safety — sfs rules compile refuses to overwrite a user-maintained rule file unless --force is set. Detection reads only the first 512 bytes so markers buried in hand-written content don't trigger false positives.
  • Session instruction provenance — manifests now carry rules_version, rules_hash, rules_source (sessionfs/manual/mixed/none), and instruction_artifacts[]. SFS_CAPTURE_GLOBAL_RULES=off suppresses global hashing for privacy-sensitive environments.
  • Resume-time rules sync — sfs resume preflights the target tool's rule file from current canonical rules before launching the tool (claude-code, codex, copilot, gemini supported). New flags: --no-rules-sync skips preflight; --force-rules overwrites an unmanaged target as a one-time permission (file becomes SessionFS-managed afterward).
  • Source-session provenance display — resume shows the rules version that shaped the original session vs current project rules (e.g. "Source session used rules v3 (sessionfs). Current project rules are v5. Synced codex.md from SessionFS rules v5.").
  • Non-fatal semantics — rules sync failure never fails the resume itself. Warning to stderr, resume continues, exit 0. Partial compiles never bump canonical version history.
  • MCP tools — read-only get_rules and get_compiled_rules for agents. No agent self-modification of rules.
  • Dashboard Rules tab — new RulesTab under ProjectDetail with version badge, static preferences editor, enabled-tools checklist, knowledge/context injection settings, per-tool compiled output viewer, version history, and compile action. ETag optimistic concurrency with 409 toast on stale saves.
  • Helm chart semver fix — 0.9.8.6 was an invalid four-segment chart version. v0.9.9 returns the chart to valid 3-segment semver matching the Python package.
  • Compile hash is marker-independent — content_hash is now the body hash only, so no-op detection doesn't break after a version bump (no more infinite-version-bump loops).
  • Path traversal defense — _safe_target_path() in cli/cmd_rules.py used by both sfs rules compile and resume preflight; validates against canonical TOOL_FILES[tool] and confirms the resolved path stays inside git_root.
  • Concurrent compile + first-create safe — version-number contention retries idempotently; same-body-hash collision short-circuits to no-op; get_or_create_rules catches IntegrityError on first-time creation.
  • Knowledge-injection determinism — claim ordering priority (decision > convention > pattern > dependency) moved into SQL ORDER BY so LIMIT respects it; deterministic id DESC tie-breaker.
  • Security — pytest bumped to >=9.0.3,<10.0 for CVE-2025-71176 (dev-only /tmp DoS).
  • 1,171 backend tests + 89 dashboard UI tests passing · 28 migrations
v0.9.8.6 April 13, 2026

Knowledge Base v2 — Three-Layer Claim Model, Per-Type Freshness, Writeback Gates

  • Three-layer claim model — every knowledge entry is now evidence (raw fact from sync), claim (promoted active truth), or note (rejected/dismissed). Migration 027 adds claim_class, entity_ref, freshness_class, supersession_reason, promoted_at, retrieved_count, used_in_answer_count, compiled_count.
  • Per-type freshness decay — bug 30d, dependency 60d, pattern/discovery 90d, convention 180d, decision 365d. Entries decay current → stale → archived from last_relevant_at.
  • Auto-promotion at compile — evidence with confidence ≥ 0.5 and content ≥ 30 chars is promoted to claim at the start of every compile pass.
  • Writeback gates for agent contributions — add_knowledge defaults to note. Auto-promotes to claim only if specificity gate, semantic dedup (Jaccard-min ≥ 0.85), and rate limit pass. Agents see classification feedback.
  • Supersession + refresh + rebuild endpoints — PUT /entries/:id/supersede retires a claim with a reason and links it to the replacement. PUT /entries/:id/refresh resets last_relevant_at (replaces the old "Still valid" no-op). POST /projects/:id/rebuild + sfs project rebuild force a full re-compile on settled projects.
  • Section pages as true projections — compile iterates ALL slug_map types, not just types in the pending batch. Pages with zero active claims are deleted inline.
  • used_in_answer tracking — MCP ask_project and API search expose a _used_in_answer flag that increments on retrieval, feeding the freshness signal.
  • Dashboard KnowledgeEntriesTab v2 — health banner with stale review queue, claim/freshness badges, filter controls, provenance blocks (entity ref, promoter, retrieved/used counts), promote/supersede/refresh/dismiss actions, rebuild button.
  • Compile default budget lowered to 2,000 words (from 8,000) — active-truth-per-token principle produces sharper context documents.
  • Concept page prune fix — page_type filter was looking for "auto" but concept pages store as "concept", so dead pages were never pruned.
  • Daemon fixes — startup order (settings fetch before full_scan), Codex watcher NoneType crash, sfs daemon stop PID resolution fallback to daemon.json.
  • 1,091 backend tests + 76 dashboard UI tests passing · 27 migrations
v0.9.8.5 April 12, 2026

Security Fixes, Lifecycle Hardening, Dashboard Help, 93 New Tests

  • Security fixes — GitLab webhook user binding (HIGH), GitHub installation claim IDOR (HIGH), effective-tier leak (MEDIUM), pr_comments unique index scoped (MEDIUM)
  • Dashboard Help page — MCP-first guidance, 8-tool installer, agent prompt examples, curated CLI reference, 12-tool MCP reference
  • Admin org endpoints — GET/POST /api/v1/admin/orgs + PUT /api/v1/admin/orgs/:id/tier for internal provisioning
  • Knowledge lifecycle hardening — LLM compile budget enforcement, semantic dedup on all extraction paths, concept page pruning, bulk dismiss-stale endpoint
  • Daemon fixes — Codex watcher null-content crash, startup delay (308 ms vs 96 s), sfs daemon stop fallback with PID resolution
  • Dashboard fixes — signup broken on app.sessionfs.dev, unguarded localStorage hardened via storage helper
  • Helm + Dockerfile hardening — postgres StatefulSet securityContext, helm test hook pod, non-root UID 10001 in both Dockerfiles
  • CVE patches — vite 7.3.2 (3 CVEs), defu 6.1.5 (prototype pollution). Security Scan workflow rewritten with raw trivy binary.
  • Self-hosted Security Posture docs + sfs dlp CLI documentation
  • 1,091 backend tests + 76 dashboard UI tests passing
v0.9.8.4 April 10, 2026

Dashboard Help Page, Signup Fix, Security Hardening

  • New dashboard Help page — 8-tool MCP installer, agent prompt examples, curated CLI reference, full 12-tool MCP reference
  • Signup fix — VITE_API_URL baked into production bundle; api.<domain> derived from app.<domain> in fallback chain
  • Helm chart + Dockerfile hardening — postgres StatefulSet securityContext, non-root UID 10001
  • Security Scan workflow rewritten; 4 CVE patches (vite, defu)
  • 1,052 backend tests + 22 dashboard UI tests passing
v0.9.8.3 April 9, 2026

Connection Pool Optimization, Sync Atomicity

  • Client-side upload concurrency limiter and per-user server semaphore with 429+Retry-After
  • sync_push split into phases — DB connection held ~70ms instead of ~5s
  • Commit-then-promote sync with temp blob preservation; PK constraint for creates, FOR UPDATE for updates
  • New /health/pool endpoint for pool utilization metrics
  • Sync summary shows error count alongside pushed/pulled/conflicts
v0.9.8.2 April 9, 2026

Database Pool Configuration

  • Configurable SQLAlchemy pool via SFS_DATABASE_POOL_SIZE (20), SFS_DATABASE_MAX_OVERFLOW (40), SFS_DATABASE_POOL_TIMEOUT (60s), SFS_DATABASE_POOL_RECYCLE (1800s)
  • pool_pre_ping=True — stale connections detected before checkout
v0.9.8.1 April 9, 2026

Knowledge Base Lifecycle, Billing Isolation

  • Entry decay (0.8x confidence after 90 days unreferenced), auto-dismiss past retention period
  • Quality gates — 20 char minimum, 20/hr rate limit per session, 85% similarity rejection
  • Context document 8,000 word budget with priority-aware trimming; section page caps (30 items)
  • Concept auto-refresh on 50% cluster growth; auto-delete when all entries dismissed
  • Actionable health endpoint; sfs resume --model for target tool selection
  • Billing webhook isolation — org state only mutates on positive customer_id AND subscription_id match
  • Migration 025: lifecycle fields on knowledge_entries + project settings
v0.9.8 April 9, 2026

DLP / Secret Scrubbing

  • Pre-sync content protection — 14 PHI patterns and 19 secret patterns
  • Three enforcement modes: BLOCK, REDACT, WARN — server-side scan of all archive files
  • Org policy via settings JSON; custom patterns and allowlist support
  • CLI: sfs dlp scan, sfs dlp policy; sfs push shows DLP preview
  • Dashboard: Settings > DLP tab for admins; session detail shows findings
  • DLP API feature-gated to Pro+; migration 024 (dlp_scan_results column)
  • 43 new DLP tests
v0.9.7.1 – v0.9.7.4 April 6–7, 2026

Dashboard UX Overhaul, Docs Refresh, MCP Roots, Billing Isolation

  • MCP workspace detection via roots/list protocol; explicit git_remote parameter on all 6 project-scoped tools
  • 0-byte index.db recovery — auto-rebuild on truncated files
  • New docs: knowledge base, dashboard guide, organizations, billing & tiers
  • CLI reference updated with 15 missing commands; all 12 MCP tools documented
  • Dashboard code splitting — main bundle 676KB → 262KB; 12 lazy route chunks
  • Command palette search (Cmd+K), mobile nav drawer, focus trapping, ARIA live regions, Zod form validation
  • Sessions hero redesign, project card health badges, typography polish, product identity sync
  • Billing isolation — Team checkout and subscription management stay scoped to the organization
  • Handoff security — recipient verification on claim, claimed summaries blocked, session ID persisted
  • Knowledge compiler content-level dedup across batches; backfill on project creation
  • Share-link passwords moved to POST body with PBKDF2-HMAC-SHA256 hashing
  • GitHub webhook signature enforcement; GitLab MR comment dedup; per-user webhook secrets
  • Migrations 020–023
v0.9.7 April 4, 2026

Living Project Context, Wiki Pages, 12 MCP Tools, Self-Healing Index

  • Living Project Context — auto-summarize on sync, knowledge entries (6 types), structured compilation
  • Wiki pages with multi-document structure, backlinks, and section pages
  • 12 MCP tools — 3 write tools + 2 search tools added
  • Auto-narrative toggle per project (LLM narrative on sync)
  • Self-healing SQLite index — auto-rebuild from .sfs files on corruption
  • sfs doctor — 8 health checks with auto-repair
  • handle_errors decorator on all CLI commands (no raw tracebacks)
  • Message pagination with newest-first default and order toggle
  • Dashboard: Knowledge Entries, Pages, History tabs on project detail
  • Project-level access control on all 13 knowledge + wiki routes
  • CLI: sfs project compile, entries, health, dismiss, ask, pages, page, regenerate, set
  • 1,052 tests passing
v0.9.5 March 30, 2026

FSL Licensing, Organizations, Hosted Billing, Security Pipeline

  • FSL licensing — MIT core + FSL enterprise with Helm license validation
  • Server-side tier gating (5 tiers, 30+ feature flags)
  • Organizations + RBAC (admin/member roles, org management)
  • Hosted billing integration with subscription lifecycle
  • LLM Judge revamp — confidence scores, CWE mapping, evidence linking, dismiss/confirm
  • Narrative LLM summaries for session overviews
  • Dashboard analytics + resume preview
  • Handoff UX — status stepper, session context display
  • CLI: sfs init wizard, sfs org, sfs security scan
  • 7 MCP tools (added get_session_summary, get_audit_report)
  • Security pipeline — Dependabot, Trivy, Bandit, pip-audit
  • 921 tests passing
v0.9.3 March 28, 2026

Judge V2, Session Summarization, GitLab Integration

  • Severity-classified findings (CRITICAL, HIGH, LOW) with auto-categorization
  • Contradictions-first audit dashboard with metric cards
  • Audit history stored in database with model comparison
  • GitLab MR integration (cloud + self-hosted)
  • Auto-audit trigger (manual, on_sync, on_pr)
  • Session summarization — files, tests, commands, packages extracted automatically
  • Claim extractor fixed for Claude Code sessions (0 claims to 50+)
  • 848 tests passing
v0.9.0 March 28, 2026

Autosync, Handoff Session Copy, Single-Ingress

  • Autosync with three modes: off, all, selective (per-session watchlist)
  • Handoff claim now copies session blob and creates recipient's session
  • Single-ingress via dashboard nginx (removed separate API/MCP ALB targets)
  • Nginx client_max_body_size 100m for session uploads
  • 835 tests passing
v0.8.2 March 27, 2026

Custom Judge Base URL, Project Context, Resume Auto-Launch

  • Custom base URL for LLM Judge (LiteLLM, vLLM, Ollama, Azure OpenAI)
  • Shared project context (sfs project init/edit/show + MCP tool)
  • Resume auto-launches native tool (Claude Code, Codex, Gemini)
  • Codex resume format fixed (source: "cli", missing fields added)
  • 819 tests passing
v0.8.0 March 26, 2026

Storage Management, EKS Fixes, Multi-Provider Email

  • Local storage management: sfs storage, sfs storage prune
  • Multi-provider email: Resend, SMTP, NullProvider
  • EKS deployment fixes: security contexts, nginx non-root, asyncpg SSL
  • Gemini model extraction from logs.json
  • 763 tests passing