# Tara Content Workbench — Gap-Closure TODOS

Date opened: 2026-07-19. Source: full-surface sweep of Tara creation/curation UI
(same date) + design doc
[`V1/TARA_CONTENT_WORKBENCH_DESIGN_2026-07-19.md`](V1/TARA_CONTENT_WORKBENCH_DESIGN_2026-07-19.md).
Read the design doc first; decision records DR-1..DR-6 govern namespace,
storage, publish path, grounding, and category-registry choices.

The driving gaps:

- **G1 — No human ideation/concept surface for Tara.** Ideation exists only
  agent-internally (`libs/oshun/creative-autonomy/src/ideation`); no capture →
  divergence → selection → development → promotion workspace.
- **G2 — Grounding is backend-only or fixture-fed.** Sophia citations
  (`apps/oshun/bff/src/sophia`) and the claim/source model
  (`libs/v3/tara-studio/src/lineage-grounding.ts`) are never wired to a live
  Tara authoring flow; `TaraLineageGroundingPanel` mounts on fixtures;
  `LilithAsanaEditor` computes grounding client-side with zero persistence.
- **G3 — Authoring/publish path is raw git.** `apps/tara/content` has no UI, no
  manifest builder, no validated publish bridge; curation means hand-editing
  JSON.
- **G4 — Higher structures are code.** `ContentCategorySpec` is hardcoded
  (`autonomy-route.ts:102-135`); programs/seasons/themes don't exist as
  entities; steering is global-only.
- **G5 — The pieces that DO work are scattered.** Autonomy console
  (web/operator), approval/comment analyzers (web/studio), editorial calendar
  (admin), TTS voice contracts (web/studio/tara) — no Tara-scoped hub.

Rules of engagement (repo standards apply in full):

- Checkbox `[ ]`/`[x]` is the sole source of truth. One task, one verification,
  one mark. No batch-marking, no phase-level marking.
- Zero tolerance for stubs: every scorer, gate, and editor ships with real
  domain logic and known-value tests, or fails loud behind an honest
  `not_configured` seam. LLM/Sophia/TTS-backed steps get a wired provider or a
  fail-loud refusal — never canned output pretending to be generated.
- Frontend work is verified with Claude-in-Chrome (visual AND functional) before
  its checkbox flips.
- Commit + two-line push (`branch` and `branch:main`) after every completed
  phase slice.
- Sequential phases; do not start P(n+1) while P(n) has unchecked blockers
  unless a task is explicitly marked independent.

---

## SOTA capability benchmark (what "comprehensive" means here)

Reference bar assembled from the strongest current content-ops and
contemplative-content tooling. Each row must be COVERED by a task below or
explicitly rejected with a reason recorded in the P0 ratification note.

| Capability                                                      | Reference bar                                                          | Covered by |
| --------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------- |
| Frictionless capture (hotkey, inbox, zero-form)                 | Milanote/Notion quick-capture                                          | P3.1–3.3   |
| Divergence operators with scored variant cards                  | best-of-N + idea-score vectors (plane already has both)                | P3.4–3.6   |
| Decision records (why chosen / why killed)                      | ADR practice applied to creative choices                               | P3.7       |
| Concept dossier with maturity ladder                            | editorial pipeline practice; Concordia case spine pattern              | P4         |
| Structured, duration-typed meditation script blocks             | Calm/Headspace production scripts; segment-timed guidance              | P4.5–4.7   |
| Readability/tone assist at authoring time                       | existing `/v1/admin/studio/readability`; invitational-language linting | P4.8, P6.6 |
| First-class source registry with authority + evidence tiers     | NotebookLM/Sudowrite grounding; academic evidence-tier practice        | P5.1–5.3   |
| Claim extraction with citation trails, one claim → one citation | Sophia answer-composer contract                                        | P5.4–5.6   |
| Needs-citation publish gating (blocker severity)                | `lineage-grounding` model, wired live                                  | P5.7       |
| Contraindication/safety claims with mandatory sourcing          | trauma-sensitive/clinical wellness practice                            | P5.8       |
| Source→concept blast-radius graph (retraction handling)         | citation-graph practice                                                | P5.9       |
| Programs/seasons/arcs as entities with cadence + calendar       | showrunner/season-arc practice; Calm seasonal programming              | P6.1–6.3   |
| Shared theme vocabulary cross-filtering ideas/concepts/programs | taxonomy practice (`taxonomy-curation` module exists)                  | P6.4       |
| Program-scoped steering overlays (tighten-only)                 | `CreativeDirection` + overlay compilation                              | P6.5       |
| UI-editable category registry (tighten-only vs code floor)      | DR-6                                                                   | P6.7–6.8   |
| Stage-gated approval with quorum + required roles               | existing approval-workflow analyzer                                    | P7.1       |
| Threaded comments on drafts                                     | existing comment-threads analyzer                                      | P7.2       |
| Tara QA gate battery (language, duration coherence, assets)     | broadcast QC practice, domain-specific                                 | P7.3–7.6   |
| Brief-from-concept into the autonomy plane                      | human-in-the-loop agentic production                                   | P8.3       |
| Tara-scoped agent run/queue/budget/provenance views             | existing `/v1/agentic/autonomy` plane                                  | P8.1–8.2   |
| Draft↔concept back-links with provenance                        | C2PA-style provenance discipline (plane has provenance tabs)           | P8.4       |
| Validated manifest builder against real JSON schemas            | schema-first CMS practice (Sanity/Contentful)                          | P9.1–9.3   |
| Git-based publish bundle → branch → verified-in-tree state      | docs-as-code / GitOps content practice                                 | P9.4–9.6   |
| TTS voice-scope-lock enforcement in bundles                     | existing tts-voice-contract surfaces                                   | P9.7       |
| Read-write editorial calendar incl. seasonal/ritual dates       | editorial-calendar practice; existing panel (read) as base             | P10        |
| Catalog curation (collections, related, featured) via bundles   | merchandising/curation consoles                                        | P11        |
| Unified hub + command-palette entry + IA registration           | modern studio-tool UX baseline                                         | P12        |
| Known-value domain tests, contract tests, golden bundles        | repo Quality Standards                                                 | P13.1–13.4 |
| Full-journey e2e proof (spark → verified-in-tree)               | repo verification doctrine                                             | P13.5      |
| Funnel/telemetry (stage conversion, gate failures, agent share) | experimentation practice                                               | P14.1–14.2 |
| Honest surfacing/retirement of fixture panels                   | no silently misleading surfaces                                        | P14.3–14.4 |

---

## Phase 0 — Ratification & recorded decisions (blocks everything)

- [x] 0.1 Read the design doc end-to-end; record a ratification note at its head
      (status: proposed → ratified, with date and any amendments), incl. an
      explicit disposition for every benchmark row above (covered-by /
      rejected-because).
- [x] 0.2 Verify where V1 studio/editorial persistence actually lives today
      (inspect `apps/oshun/bff/src/admin/*store*`, editorial workspace backing,
      any V1 Prisma schemas); record the DR-3 final decision: new
      `libs/oshun/tara-workbench/prisma` schema vs. extension of an existing V1
      schema, and which database (`oshun_dev` expected).
- [x] 0.3 Confirm the `@oshun/creative-autonomy` public exports needed (idea
      synthesis, `composeBrief`, steering types, plane API client shapes) are
      importable without deep paths; if not, add explicit exports (no logic
      changes) with tests.
- [x] 0.4 Record the LLM-provider seam decision: reuse the same provider
      conventions as the autonomy plane/content-service (provider chain, fenced
      prompts, safety filter); define `TaraWorkbenchModelProvider` (diverge,
      extractClaims, assistScript) failing loud with `not_configured` when
      unbound. _(Recorded as design amendment A2; implemented in
      `libs/oshun/tara-workbench/src/provider.ts` over `runStructuredOutput` +
      `@oshun/prompt-trust` fencing; `not_configured` refusals +
      anchor-integrity + banned-topic admission tests green, 8/8.)_
- [x] 0.5 Threat/safety paragraph appended to the design doc: prompt injection
      via spark bodies and source titles, health-claim liability
      (contraindication severity floor), teacher attribution/consent, and rights
      notes on tradition sources.

## Phase 1 — Domain library `libs/oshun/tara-workbench`

- [x] 1.1 Scaffold the lib (project.json, package.json, tsconfig, vitest config
      per `configs/` presets; path mapping in `tsconfig.base.json`; pure ESM
      source lib like `@oshun/studio-authoring`). _(Verified against the
      studio-authoring preset; vitest aliases use the creative-autonomy
      closed-chain pattern; pnpm workspace linked.)_
- [x] 1.2 Types module: Spark, Concept (+premise struct), maturity-stage union +
      transition table, DecisionRecord, Source (kinds + evidence tiers), Claim
      (kinds incl. contraindication; severities), CitationLink, Program
      (+steering overlay), Theme, CategoryRow, AgentRunLink, ReviewGateResult,
      PublishBundle. Exhaustive discriminated unions; no `any`. _(All entities
      in `types.ts`; themes reuse `@oshun/contracts/tara` taxonomies per A1;
      category/contentType/experienceLevel enums verified identical to
      `meditation.schema.json`; zero `any`; tsc clean.)_
- [x] 1.3 Maturity state machine: legal transitions incl. killed/parked;
      transition guards take gate evidence as input (pure). Known-value tests:
      blocker-claim-present blocks `grounded`; missing premise fields block
      `outline`. _(21/21 green. Fixed a scaffolded test defect: the `concept()`
      helper ignored its `overrides` param, so 18 scenarios silently ran against
      the default concept; added the missing-premise-blocks-`outline`
      known-value test.)_
- [x] 1.4 Script blocks: Tara block kinds (intro, settling, breath-cue,
      body-scan, silence-interval(durationSeconds), bell-cue, closing,
      free-guidance) layered on `@oshun/studio-authoring` authoring-blocks;
      deterministic compile to timed segments; segment-sum function. Known-value
      test: fixture script compiles to exactly 612s. _(All 8 kinds; named
      constants (100 wpm narration, 10s bell decay, box/4-7-8/coherent breath
      patterns); 612s fixture + cumulative-timeline + projection round-trip;
      18/18 green — word counts and segment arithmetic independently
      re-derived.)_
- [x] 1.5 Duration-coherence gate: |segmentSum − durationTarget| threshold logic
      with explicit tolerance constant; returns delta evidence. Known-value
      tests both sides of the tolerance. _(Tolerance = max(10s, 1.5%·target);
      612s fixture fails a 600s target with +12s delta and passes 620s with −8s;
      floor/ratio crossover asserted; evidence carries target, compiled, delta,
      tolerance, segmentCount. 27/27 green incl. transcript/captions gate and
      the honest audio-QA `unavailable` seam.)_
- [x] 1.6 Port the invitational-language linter from
      `libs/v3/tara-studio/src/invitational-language-linter.ts` into this lib
      (V1-owned copy, attributed in the file header; V3 keeps its own). Tests
      ported + extended with meditation-script cases. _(Diffed against the V3
      original: all 9 directive-core rules carried over, `hold` downgraded to
      warning with meditation rationale, yoga rules stand/move/teach dropped as
      documented, 6 meditation rules added (outcome promises, "empty your mind",
      minimizers, trauma-sensitive eye cues). V3 file untouched. 22/22 green.)_
- [x] 1.7 Grounding domain: claim status computation, severity floors
      (health/medical-adjacent ⇒ blocker until clinical-tier source attached),
      citation-trail resolution, source authority re-score propagation.
      Known-value tests incl. the retraction blast-radius query shape. _(Floors
      raise-never-lower; `theoretical` tier explicitly non-clinical; statuses
      recomputed from trails ignoring stored flags; unknown source ids never
      count; closed-practice surfacing. Added `propagateSourceUpdate`
      (re-score/retraction → per-claim before/after + concepts needing gate
      re-runs) which was missing from the scaffold. 17/17 green.)_
- [x] 1.8 Steering overlay compiler: merge program overlay onto a
      `CreativeDirection` (theme-weight merge, tenet append, banned-topic union,
      tighten-only autonomy assertion that throws on loosening). Property-style
      tests over autonomy ranks. _(Rank table drift-guarded against the plane's
      `AUTONOMY_LEVELS`; values verified identical to the plane's private
      `AUTONOMY_RANK`. Added the exhaustive 25-pair floor×requested property
      suite (throws iff loosening, composes to requested otherwise); quality
      bars raise-only; stricter-wins on double override. 35/35 green.)_
- [x] 1.9 Category registry merge: code floor (`V1_AUTONOMY_LAUNCH_CATEGORIES`
      tara rows) ⊕ DB rows; loosening attempts rejected with typed error.
      _(Tighten-only merge with provenance (`code-floor`/`db-tightened`/
      `db-added`), conservative `approval` floor on DB-added rows, domains stay
      code-owned, deactivation excluded from activeSpecs; test fixture verified
      field-for-field against the real `autonomy-route.ts` tara row. 9/9
      green.)_
- [x] 1.10 Manifest builder: dossier → `meditation.schema.json`-shaped metadata
      (and course/collection shapes for P11), field-by-field mapping with
      required-field enforcement; pure, returns validation errors as data.
      Golden test: builder output validates against the real schema files read
      from `apps/tara/content/schemas/`. _(Meditation builder pre-existing +
      verified; added `buildCollectionManifest` and `buildCourseManifest`
      (schema-mirrored, incl. the durationSeconds⇒transcript+captions
      conditional proven enforced by the real schema, seasonal-window and
      duplicate-position editorial rules) + collection/course tree layouts.
      Fixed a broken hand-rolled ajv uuid format regex ({3}→{4} third group)
      that failed every valid UUID. Goldens validate against all three real
      schema files. 39/39 green.)_
- [x] 1.11 Decision-record and idea-score modules: score-vector rendering
      helpers, pick recording; no fabricated scores (scores only from the plane
      or absent). _(ADR-style records with substantive-rationale enforcement;
      blend recomputes the plane's weighted formula — 0.68 known value verified
      against the real `DEFAULT_SCORE_WEIGHTS` (0.2/0.2/0.25/0.2/0.15);
      lexical-diagnostic-only novelty honestly flagged for UI badging. 9/9
      green.)_
- [x] 1.12 Lint + build + full vitest green for the lib; adversarial self-scan
      (mandatory grep from CLAUDE.md) over `libs/oshun/tara-workbench/src`.
      _(eslint --max-warnings 0 clean (fixed dup-import + nullish-coalescing);
      tsc build emits OK; lib+spec typechecks clean; 191/191 vitest green.
      Adversarial grep: 2 hits, both benign in context (test-double
      `fakeProvider` at the LLM boundary; the word "production" in a fixture
      rationale). Silent-stub scan (Math.random/Date.now/new Date, ignored
      params) clean — the one ignored-`overrides` test-helper defect found in
      1.3 was fixed. Every public + private function read this session.)_

## Phase 2 — Persistence & BFF routes

- [x] 2.1 Prisma models per design §6 in the P0.2-decided schema; migration
      written and applied to the dev DB; migration name recorded here. _(Per A1:
      12 workbench entities + 15 enums as Zod contracts in
      `libs/contracts/src/tara/workbench.ts` (19-test spec incl. per-kind source
      rules, killed⇔rationale, bundle exactly-one-owner and
      invalid-never-leaves-draft invariants), registered as
      objectContract/enumContract rows; schema regenerated; shared taxonomies
      extracted to `tara/taxonomies.ts` to break an ESM TDZ cycle. Migration
      **`20260720120000_tara_content_workbench`** (12 tables, 15 enums, 72
      indexes, zero destructive statements — unrelated pre-existing schema drift
      deliberately excluded) applied to `oshun_dev`; all 12
      `v1_tara_workbench_\*`tables verified present. Design §6's`Theme`    entity is intentionally absent per A1 (reuses`ThemeTaxonomy`).
      Contracts 67/67, persistence registry/renderer/migration-plan specs
      green.)\_
- [x] 2.2 Store layer at the BFF boundary (durable-before-ack discipline
      matching the autonomy plane's persist hook); unit tests with a test
      database. _(`apps/oshun/bff/src/tara-workbench/store.ts`: writes via
      `ContractPersistenceService.put` — Zod-validated, payload-hashed, awaited
      before return; reads via typed generated delegates, always
      `tombstonedAt: null`, every row re-parsed through its contract on read
      (fail-loud on drift). Domain queries: latest revision, latest gate per id,
      batch citation trails, source blast radius, category rows, by-status/stage
      filters. `store.integration.spec.ts` 8/8 green against real migrated
      Postgres (`oshun_dev`, isolated tenant) via `vitest.integration.config.ts`
      — proves durable round-trips, reject-before-write, upsert-in-place, and
      the reductions.)_
- [x] 2.3 Router `apps/oshun/bff/src/routes/tara-workbench.ts` mounted under
      `/v1/studio/tara-workbench` with the same auth/RBAC middleware as other
      studio routes; role checks per design §3 (creator-roles matrix).
      _(Abuse-protection + auth preHandlers and `admin:*`/`admin:studio` scope
      gate identical to `/v1/admin/studio/*`; `resolveWorkbenchActor` parses
      `studio:role:<role>` scopes into `StudioCreatorRole`s checked via
      `roleHasPermission` (studio-authoring matrix — no parallel RBAC; `admin:*`
      implies all roles); fail-closed 503 `not_configured` when no store bound;
      `GET /capabilities` returns the resolved actor for UI affordances. Wired:
      app.ts option + registration, server.ts constructs the store off
      `OSHUN_V1_DATABASE_URL` (single `v1-studio` tenant per §3). 7/7 contract
      tests green (401/403/503/role parsing/persona authority); BFF tsc exit 0;
      eslint clean.)_
- [x] 2.4 Sparks endpoints (capture/list/triage/promote) + contract tests.
      _(POST `/sparks` (body-first capture, actor-stamped, 400 with verbatim Zod
      issues), GET `/sparks?status=`, archive/unarchive/merge/promote with
      from-status triage rules (409 `illegal_spark_transition`), merge requires
      a real concept (404), promote creates the concept (stage `spark`, origin
      back-link both ways, slug-uniqueness 409), mutations gated on
      `edit-draft`. 14/14 route contract tests green (memory double at the store
      boundary; durable behavior proven by the P2.2 integration spec); BFF tsc
      exit 0.)_
- [x] 2.5 Concept endpoints (CRUD, stage transitions running lib guards,
      revisions via studio-authoring autosave/merge) + contract tests incl.
      illegal-transition rejection. _(GET list/dossier, PATCH (killed concepts
      terminal), transition endpoint running `evaluateTransition` over
      live-recomputed evidence (`tara-workbench/evidence.ts` re-parses +
      re-compiles the latest revision; approval/audio honestly absent until
      P7.1/P9.2), kill (rationale-gated, ADR-first) / park / reactivate,
      append-only revisions with fast-forward + `mergeBranchIntoTrunk` three-way
      merge via the block projection round-trip — clean merges preserve both
      sides, conflicts 409 with block ids. 20/20 contract tests incl. skip-stage
      and missing-premise blocker assertions; BFF tsc exit 0.)_
- [x] 2.6 Divergence endpoint calling `TaraWorkbenchModelProvider`;
      `not_configured` contract test; provider-wired test behind env flag.
      _(POST `/concepts/:id/diverge` seeds from the premise
      (`divergenceSeedFromPremise`) or origin spark; unbound provider flows
      through the lib's honest-refusal facade → 503 `not_configured` seam
      `model-provider`; provider errors 502 fail-loud, no canned fallback;
      variations returned verbatim, never persisted. server.ts binds
      `resolveAutonomyLlmProvider` (same A2 chain as the plane) into
      `createTaraWorkbenchModelProvider`. 23/23 contract tests + the
      real-provider spec gated on `OSHUN_TARA_WORKBENCH_LLM_SPEC` +
      `OPENROUTER_API_KEY` (skipped here — no key on this box); BFF tsc exit
      0.)_
- [x] 2.7 Source/claim/citation endpoints + grounding-check endpoint calling the
      Sophia grounding service server-side; `not_configured` path test;
      one-claim-per-citation contract respected. _(Typed source CRUD with
      per-kind 400s + blast-radius endpoint; claim create with
      `applySeverityFloor` (health claims can never file below blocker);
      Sophia-assisted extraction behind the A2 seam (503 `not_configured`
      unbound — the named path test); citations name exactly one claim + one
      source, duplicates 409, ghosts 404; grounding check recomputes statuses
      via `computeClaimStatus`, annotates `sophiaNote` with the REAL
      `groundQuery` credibility engine over cited refs (honest "not scoreable"
      when refs absent), persists claims + the grounding `ReviewGateResult`
      anchored to the latest revision. 29/29 contract tests; BFF tsc exit 0;
      eslint clean.)_
- [x] 2.8 Program/theme/category endpoints (tighten-only enforcement tests).
      _(Program CRUD gated on `curate-taxonomy`; overlays validated at write
      time by running the REAL `compileProgramDirection` against
      `categoryAutonomyFloors(mergeCategoryRegistry(...))` — loosening 409s with
      categoryId/floor/requested; `/programs/:id/composed-direction` serves the
      exact global⊕overlay direction P6.5/P8.2 will render; `/themes` = shared
      A1 vocabulary (15 taxonomy values); `/categories` = merged registry with
      provenance; PUT category runs the merge before persisting (loosened row
      refused at write time — the P6.8 write-side half). 35/35 contract tests;
      BFF tsc exit 0; eslint clean.)_
- [x] 2.9 Brief-from-concept endpoint composing premise+overlay into a plane
      brief; integration test against the in-process plane (`create-plane.ts`
      test harness). _(POST `/concepts/:id/brief` composes the plane's OWN
      `composeBrief` over a `steering-directive` signal (premise facts +
      supported claims as evidence), the merged-registry category, and the
      program-overlay-composed direction; persists the `brief-from-concept`
      AgentRunLink provenance edge visible on the dossier. Integration test
      boots `createBoundCreativeAutonomyPlane` (fail-closed harness) and proves
      the brief embeds the plane's own category title, the overlay tenet, and
      banned topics; premise-less briefs 409. 37/37 tests; BFF tsc exit 0;
      eslint clean.)_
- [x] 2.10 Gate-battery endpoint (runs P1 gates, persists `ReviewGateResult`
      with evidence) + tests. _(POST `/concepts/:id/gates/run` (edit-draft OR
      review): all five P1 gates over the latest revision —
      invitational-language, duration-coherence against the premise target, the
      shared grounding core (extracted from 2.7, same persisted claims + Sophia
      notes), transcript-captions recomputed from genuinely-absent staged assets
      (evidence names the P9.2 seam), audio-qa honest `unavailable`. Each result
      persisted with evidence + revision anchor. Known-value test: 700s vs 600s
      target fails with delta 100/tolerance 10; "You must empty your mind" fails
      with blockingCount 2; all five land in the dossier's latest gate results.
      38/38 tests; BFF tsc exit 0; eslint clean.)_
- [x] 2.11 Publish-bundle endpoints (build/validate/state) — validation errors
      surfaced verbatim; no bundle reaches `exported` with errors. _(POST
      `/publish/bundles` builds via the domain `buildMeditationManifest` from
      the dossier (612s known value round-trips into the manifest); builder
      errors persisted VERBATIM on draft bundles; state endpoint advances one
      step (draft→exported→branched→merged), refuses export while invalid (409
      `bundle_invalid` with the errors) and refuses `verified` outright
      (evidence-only, P9.5 flow); `approve-publish` gated. The contract schema
      additionally pins invalid bundles to draft structurally. 41/41 tests; BFF
      tsc exit 0; eslint clean.)_
- [x] 2.12 Catalog endpoints parsing the real `apps/tara/content` tree
      (server-side read; error, never fabricate, on unreadable tree).
      _(`tara-workbench/content-tree.ts` walks meditations/<category>/<slug>,
      courses/<slug>, collections/<slug> metadata server-side, throwing
      `ContentTreeError` with the offending path on any unreadable/malformed
      item — partial trees are errors, not shorter catalogs. GET `/catalog` with
      kind/category/search filters; unconfigured root 503 seam
      `tara-content-tree`; unreadable root 502 `content_tree_unreadable`.
      server.ts resolves `OSHUN_TARA_CONTENT_ROOT` or checkout-relative
      candidates. Contract tests run against the REAL repo tree (calm-your-mind
      / foundations-of-meditation / stress-relief-essentials asserted). 44/44
      tests; BFF tsc exit 0; eslint clean.)_
- [x] 2.13 Audit logging on every mutation (actor id, entity, before/after refs)
      consistent with existing admin audit patterns. _(Injectable
      `AdminAuditEventsStore` (defaults to the shared singleton) with a
      workbench `audit()` helper; all 20 mutation sites record
      `studio.tara_workbench.*` events with actor id, target entity id, and
      before/after refs (statuses, stages, dispositions, versions, autonomy
      levels, bundle states, decision ids). Contract tests prove the
      capture→archive→unarchive→promote→kill chain event-by-event and that
      refused/invalid mutations record nothing. 46/46 tests; BFF tsc exit 0;
      eslint clean.)_

## Phase 3 — Ideation studio UI

- [x] 3.1 Route scaffold `/studio/tara-workbench` + `/inbox` in `apps/oshun/web`
      (layout, nav shell following concordia-workbench patterns; registered in
      the studio route map). _(`StudioTaraWorkbenchRouteMap` (10-surface IA with
      honest `implemented` flags), hub workspace with LIVE `/capabilities`
      readout, inbox shell with LIVE sparks read — both with honest
      loading/error/empty states; pages under
      `app/studio/tara-workbench{,/inbox}` on `ShellLayout`; quick action
      registered on `/studio`. Chrome MCP pass (2026-07-20, dev stack
      web:3010→bff:4010 + real minted session): both pages render in the admin
      shell with breadcrumbs, the capabilities/inbox panels show the honest
      live-BFF `admin_scope_missing` refusal for a customer-scoped session,
      hub↔inbox navigation works both ways, and the `/studio` hub link resolves.
      Happy path (roles/permissions JSON, empty sparks list) verified via authed
      curl with a `studio:role:editor` dev token + the 46-test contract suite.
      Web tsc exit 0. Dev-stack notes: BFF needs
      `OSHUN_SIGNUP_VERIFICATION_HMAC_SECRET` + autonomy snapshot key envs; a
      drifted 2026-07-11 `tutor-session-memories` dev snapshot blocked durable
      boot — backed up to scratchpad and cleared.)_
- [x] 3.2 Spark capture: zero-form quick add (body-first, optional title, tag,
      source-link paste), optimistic-but-confirmed persistence. _(Body-first
      textarea with ⌘/Ctrl+Enter file, optional title/tags/source-link row,
      submit disabled on empty body. Optimistic-but-confirmed: pending card
      marked "saving…", REPLACED by the server's confirmed row on 201; on
      refusal the card is removed, the full draft restored to the form, and the
      server's reason shown verbatim. Chrome MCP pass (2026-07-20): refusal path
      verified live (customer scope → "Not saved … admin_scope_missing", draft
      preserved), then happy path via the auth-boundary fetch wrap with an
      editor dev token → server-stamped card ("captured by wb-editor-browser")
      rendered from the post-201 reload and the row confirmed durably in
      Postgres (`v1_tara_workbench_spark`, tenant `v1-studio`). Web tsc exit
      0.)_
- [x] 3.3 Inbox triage: archive / merge-into-concept / promote-to-concept flows
      wired to P2.4; keyboard-first triage. _(Listbox with j/k/arrow selection
      (aria-activedescendant + accent highlight), `a` archive, `m` merge (live
      concepts picker), `p` promote (slug prefilled by slugifying the title),
      Esc dismiss; button parity per card; server reasons surfaced verbatim;
      notices per action. Chrome MCP pass (2026-07-20): all three flows
      exercised live via keyboard — promote created concept `rain-on-the-roof`,
      merge folded a spark into it, archive cleared a card — each confirmed
      durably in Postgres (spark statuses promoted/merged/archived; concept row
      with origin back-link). The pass caught and fixed a real defect: bodyless
      POSTs sent `content-type: application/json`, which Fastify 400s — header
      now only set with a payload. Web tsc exit 0.)_
- [x] 3.4 Divergence cards: request N variations on a spark/concept; render
      score vectors (timeliness/novelty/audienceValue/strategicFit/ feasibility)
      exactly as returned; disabled-with-reason when provider unconfigured.
      _(`StudioTaraWorkbenchDivergence` on the inbox: live concept picker with
      refresh, count 1–8, request → cards with angle badge/title/brief/
      departure; score vectors rendered EXACTLY as returned per component and an
      honest "no score vector returned" label when absent — never synthesized.
      Chrome MCP pass (2026-07-20): the LIVE request hit the real unconfigured
      seam — the lib's typed refusal message rendered as an alert with the
      request button disabled-with-reason and the manual-path note; card
      rendering verified with a model-boundary stub (one scored + one unscored
      variation) since no OPENROUTER key exists on this box — the endpoint
      contract itself is proven by the P2.6 route tests + env-gated live spec.
      Web tsc exit 0.)_
- [x] 3.5 Selection UX: side-by-side compare, human pick, auto-opened
      decision-record form (rationale required). _(Variations render
      side-by-side in the grid; "Pick this framing" per card auto-opens the
      decision form with the OTHER variations prefilled as
      consideredAlternatives and Record disabled until a ≥10-char rationale
      exists. Gap-filled `POST /concepts/:id/decisions` on the BFF (chosen/pivot
      only — kills stay exclusive to the kill flow so disposition and record
      can't diverge; audited; 47/47 route tests). Chrome MCP pass (2026-07-20):
      pick → auto-opened form with the alternative listed → disabled-empty
      confirm → rationale typed → 201 against the REAL BFF → row verified
      durable (`v1_tara_workbench_decision_record`: kind chosen, actor,
      rationale, alternatives). The stale-BFF 404 during the pass was surfaced
      honestly by the UI verbatim. BFF+web tsc exit 0.)_
- [x] 3.6 Theme tagging on sparks/ideas from the shared vocabulary (P6.4
      dependency noted; ship with free-text tags until P6.4 lands, then
      migrate). _(Clickable chips for all 15 themes loaded from the LIVE
      `/themes` registry endpoint (P2.8), toggling into/out of the tags field
      with `aria-pressed`; free text stays fully allowed; a failed vocabulary
      load never blocks tagging and retries on inbox activity. Chrome MCP pass
      (2026-07-20): chips rendered from the live endpoint, sleep+breath toggled
      into the field, and the themed capture persisted server-side with its
      theme tags on the card. P6.4 will add cross-filtering over the same
      vocabulary. Web tsc exit 0.)_
- [x] 3.7 Decision log view on the dossier (chosen/killed/pivot timeline). _(New
      dossier route `/studio/tara-workbench/concepts/[conceptId]` (route map
      flipped with an honest live-vs-P4.2 purpose note): concept header + the
      decision TIMELINE from the live dossier endpoint — kind badge, timestamp,
      actor, rationale, alternatives, newest first; empty and error states
      honest with Retry. Chrome MCP pass (2026-07-20): the rain-on-the-roof
      dossier rendered its live `chosen` record (wb-editor-browser, full
      rationale, the alternative listed); pending P4.2 sections declared rather
      than rendered empty. Web tsc exit 0.)_
- [x] 3.8 Playwright spec for capture→triage→promote; Chrome MCP visual +
      functional pass recorded here before marking.
      _(`apps/oshun/web/e2e/studio-tara-workbench.spec.ts` (concordia-style
      editor-bearer route proxy + hydration wait): capture → keyboard `p`
      promote → notice → concept asserted server-side (stage `spark`, origin
      spark provenance) → dossier renders it; second test proves the
      unauthorized honest refusal. 2/2 green against the live dev stack
      (`E2E_BASE_URL=http://127.0.0.1:3010`). Chrome MCP visual+functional
      passes for every Phase 3 surface are recorded in the 3.1–3.7 notes
      (hub/inbox rendering, both capture paths, all three triage flows,
      divergence states, pick decisions, dossier timeline — with durable
      Postgres confirmation throughout). Phase 3 complete.)_

## Phase 4 — Concept dossiers & development ladder

- [x] 4.1 Concept board `/concepts` (kanban by maturity stage, filter by
      program/theme/assignee). _(All nine maturity columns over the LIVE
      concepts list (killed concepts excluded), cards linking to dossiers with
      slug/themes/creator; program filter from the live registry, theme filter
      from the shared vocabulary, assignee filter from the creators actually
      present; honest error/loading states + Refresh. Chrome MCP pass
      (2026-07-20): board rendered both live concepts in `spark`, the assignee
      filter narrowed the column 2→1, and the card click-through landed on the
      rain-on-the-roof dossier (decision count 1). Route map flipped. Web tsc
      exit 0.)_
- [x] 4.2 Dossier home `/concepts/[conceptId]`: premise editor (structured
      fields incl. audience, intent, practice form, duration target, experience
      level), stage checklist, linked entities, grounding summary, agent-run
      links, decision log, history. _(Dossier GET extended with revision history
      metadata (no scriptJson), resolved program `{id,name}`, and origin spark —
      pinned by new router-spec assertions (47 passed). UI: premise editor with
      all eight contract fields (selects fed from the contract enums via
      `@oshun/contracts/tara`, contexts as max-8 toggle chips, live duration
      display), stage checklist with done/current/ upcoming markers + latest
      gate results with evidence, linked entities
      (program/themes/origin-spark/bundles), grounding summary (status counts +
      blocker callout, needs-citation claims listed), agent-run links, decision
      timeline, history; killed concepts render the editor read-only. Chrome MCP
      pass (2026-07-20): recorded the rain-on-the-roof premise via the real form
      (visualization, 1200s → "20m 00s", sleep, night+sleep-onset chips), saved
      notice matched the Postgres row (`v1_tara_workbench_concept`, tenant
      v1-studio, 13:14:57); all sections + honest empty states verified
      visually. Caught+fixed: fetch-wrap header-case merge (Authorization +
      authorization both sent → invalid token; wrap now uses Headers.set). Both
      tsc exit 0.)_
- [x] 4.3 Stage-transition UI driven by server guards; blocked transitions show
      the exact failing evidence (e.g., the unsourced blocker claims). _(Dossier
      transition controls: single-step Advance button (computed next stage) +
      any-stage select, both POSTing to the transition endpoint; a 409 renders
      the guard's blocker strings verbatim in an alert list, success reloads the
      dossier; killed concepts show the permanent-refusal note instead. Chrome
      MCP pass (2026-07-20): spark→grounded blocked with "forward transitions
      move one stage at a time ('spark' → 'premise')"; Advance to premise
      succeeded ("Moved to premise — guard passed", checklist ✓ spark → premise,
      Postgres row stage=premise); premise→outline then blocked with "no
      authored revision exists — write the outline first" — and no premise
      blocker, proving the evidence is recomputed live. Guard behavior itself
      pinned by the P2.5 router spec. Web tsc + eslint exit 0.)_
- [x] 4.4 Version history + three-way merge surface for concurrent edits
      (studio-authoring `mergeBranchIntoTrunk`); conflict UI with block-level
      resolution. _(New `StudioTaraWorkbenchDevelop` panel on the dossier:
      revision editing over the append-only endpoint with base-version tracking;
      saves fast-forward or three-way merge; stale bases offer Load-latest;
      merge conflicts open a block-level resolution surface — the BFF's 409 now
      ships `mergedScriptJson` (lib auto-merge, conflicts resolved to trunk) +
      `trunkScriptJson`, the UI renders Mine vs Trunk side-by-side per
      conflicted block with explicit radio picks, and Apply-resolution re-saves
      on the latest version. Router spec pins the conflict payload + resolved
      re-save (47 passed). Chrome MCP pass (2026-07-20): authored v1
      (intro+closing) in the real editor; curl simulated a concurrent trunk
      editor saving v2 (closing edit, base 1); browser save on stale base 1
      opened the conflict UI (edited-on-both-sides, both real texts
      side-by-side); picked Mine → "Saved v3 — fast-forward"; Postgres chain
      v1(∅)→v2(base 1, trunk text)→v3(base 2, my text); dossier history lists
      all three with authors/merge bases. Fixed client revision-count drift
      (count := saved version — versions are contiguous). Both tsc + eslint exit
      0.)_
- [x] 4.5 Develop tab: block editor with the Tara block kinds; silence intervals
      and bell cues duration-typed; segment timeline preview showing compiled
      timings. _(Editor now covers all eight Tara kinds: five spoken (text +
      optional duration override), duration-typed silence-interval
      (durationSeconds) and bell-cue (bells + spacingSeconds, 10s decay noted),
      and breath-cue (lead-in, named presets mirroring the lib's
      BREATH_PATTERNS, editable 4-phase pattern, cycles). New BFF
      `POST …/script/compile` runs the domain `compileScript` on saved OR
      unsaved editor state — spec pins known values (12/60/25/8s segments, 105s
      total; invalid script → 400 with the exact lib message; 48 passed).
      Timeline UI: proportional per-kind bar + per-segment rows (clock start,
      kind, duration, narration excerpt), auto-refreshed after save. Chrome MCP
      pass (2026-07-20): added silence/breath/bell blocks live, set bells=2 +
      lead-in, Preview showed 3:30 total exactly matching hand-computed math
      (15+14+9 words at 100 WPM + 2s pauses + 60s + 6×16s box + 25s bells =
      209.8s; offsets 0:00/0:11/0:21/1:21/3:05); Saved v4 fast-forward, Postgres
      row carries the typed blocks. Stale P4.5 dossier copy retired. Both tsc +
      eslint exit 0.)_
- [x] 4.6 Live duration-coherence meter (segment sum vs. target) using the P1.5
      gate logic via the gate endpoint (server-computed). _(The compile endpoint
      now also runs the SAME `evaluateDurationCoherence` (P1.5) the stamped gate
      battery uses — unstamped, against the live editor state; null (honest
      absence, not a pass) when no premise target exists. Spec pins known
      values: 105s script vs 600s target → fail with evidence {Δ −495, tolerance
      10, segments 2}; vs 110s target → pass (49 passed). UI: verdict-tinted
      meter card (compiled vs target clocks, signed Δ, ±tolerance) with a
      proportional bar + tolerance band, driven by an 800ms-debounced
      auto-compile so it tracks edits without clicks. Chrome MCP pass
      (2026-07-20): meter auto-appeared FAIL (3:30 vs 20:00, Δ −990.2s,
      tolerance ±18s = max(10, 1.5%×1200)); editing silence 60→1050s flipped it
      LIVE to PASS (Δ −0.2s) with the timeline recompiling to 20:00 (offsets
      17:51/19:35); saved v5, Postgres row carries durationSeconds 1050. Both
      tsc + eslint exit 0.)_
- [x] 4.7 Script-assist panel behind the provider seam (rewrite-with-
      invitational-language, tighten-guidance) — suggestions only, explicit
      accept per block, AI-attribution recorded on accepted blocks. _(New BFF
      `POST …/script/assist` over the existing fenced `assistScript` provider op
      (4 modes): no provider → 503 `not_configured` with the lib refusal,
      provider failure → 502, unknown mode → 400; wired path returns the
      suggestion verbatim (spec with an injected provider double; 51 passed).
      Domain: spoken blocks gain
      `aiAttribution {mode, note, appliedAt} |     null` — validated on parse
      (malformed fails loud, absent → null for pre-P4.7 revisions) and threaded
      through the authoring merge projection; fixture round-trips prove
      preservation (193 lib tests). UI: per-spoken-block mode select + Suggest;
      suggestion card with the model's note and explicit Accept/Dismiss; Accept
      replaces the text AND stamps the attribution badge. Chrome MCP pass
      (2026-07-20): real 503 refusal card rendered in place ("nothing will be
      fabricated"); accept flow exercised with a network-boundary provider
      double (LLM only — accept UI, attribution, and save real): badge appeared,
      Saved v6, and Postgres v6 block 0 carries the full aiAttribution through
      the server's parse→serialize. Both tsc + eslint exit 0.)_
- [x] 4.8 Readability integration (existing `/v1/admin/studio/readability`)
      surfaced per block and per script. _(New workbench
      `POST …/script/readability` reuses the SAME `scoreReadability` Flesch
      engine the admin console wires, plus a new documented
      `countTextStatistics`/`countSyllables` (vowel-group heuristic with
      silent-e/-le handling, tradeoff noted) in the readability store so counts
      are server-computed from narration text (spoken blocks + breath lead-ins;
      silence/bells excluded; zero narration → honest
      `{blocks: [], script: null}`). Spec pins hand-computed known values: 15
      words/1 sentence/19 syllables → ease 84.45, grade 5.21, standard, meets
      target 6 (52 passed). UI: target-grade input + Score button; whole-script
      line + per-block rows with band and ✓/✗ target. Chrome MCP pass
      (2026-07-20): live scores over the real v6 script — script 80.44/5.19
      meets ≤6; intro 84.45/5.21 ✓ (matches the spec value exactly), closing
      65.73/7.57 ✗, breath lead-in 94.3/2.34 very_easy — real per-block
      discrimination. Both tsc + eslint exit 0.)_
- [x] 4.9 Playwright spec: premise → outline → script draft with merge conflict
      resolved; Chrome MCP pass recorded. _(New "development ladder (P4.9)" test
      in `e2e/studio-tara-workbench.spec.ts`: fresh spark→promote via API, then
      UI all the way — premise recorded through the structured editor, guarded
      spark→premise advance, v1 authored in the develop panel, premise→outline
      advance, API-simulated concurrent trunk v2 on the same intro block, UI
      save on stale base → conflict panel (edited-on-both-sides, trunk text
      shown), pick-Mine resolution → Saved v3, outline→script-draft advance;
      server-truth assertions (stage script-draft, latestRevision v3 carries my
      text). 3/3 green first run (8.4s ladder;
      `E2E_BASE_URL=http://127.0.0.1:3010 PW_BROWSER_CHANNEL=chrome npx     playwright test e2e/studio-tara-workbench.spec.ts --workers=1`).
      Chrome MCP passes recorded per-task in the 4.1–4.8 notes.)_

## Phase 5 — Grounding & source registry

- [x] 5.1 `/sources` registry UI: typed create/edit forms per source kind
      (tradition fields: lineage, canonical text, attribution; clinical fields:
      evidence tier, citation metadata), authority score, rights note, locale.
      _(New `/studio/tara-workbench/sources` page + `StudioTaraWorkbenchSources`
      over the live endpoints: kind select swaps the typed field sections
      (tradition lineage/canonical-text/attribution; philosophy school/work;
      clinical tier select + publication year; expert name/credential) plus
      common authority score, rights note, locale, external refs, and the
      closed-practice flag; per-kind validation stays SERVER-side with Zod
      issues rendered verbatim; kind-scoped payloads null inactive fields by
      construction. New BFF `PATCH /sources/:sourceId` re-validates the merged
      row through the same per-kind rules (spec: re-score 200, tier-stripping
      400, unknown 404 — 53 passed). Route map flipped. Chrome MCP pass
      (2026-07-20): registered a Theravada tradition source (authority 0.8)
      through the real form; clinical submit without a tier rendered the exact
      refusal "evidenceTier: A neuroscience-clinical source must carry an
      evidence tier."; tier+year completed → registered (count 2); Edit →
      authority 0.8→0.65 → "Updated." with the row and Postgres
      (`v1_tara_workbench_source_record`) both showing 0.65. Both tsc + eslint
      exit 0.)_
- [x] 5.2 Source list/search with kind + tier filters; stable-ID copy
      affordance. _(Registry gains kind + evidence-tier filters that narrow
      SERVER-side via the endpoint's query params, a client-side text search
      over title/lineage/attribution/expert/school/work/refs, each row's stable
      UUID rendered as code, and a Copy-ID button with feedback on BOTH outcomes
      (clipboard success → "Copied ✓"; refusal → execCommand fallback → honest
      "Copy failed — select the ID", added after the Chrome pass caught the
      silent-failure gap). Chrome MCP pass (2026-07-20):
      kind=neuroscience-clinical narrowed 2→1 server-side; tier=rct showed "No
      sources match the active filters."; search "Satipatthana" narrowed to the
      one tradition row; real pointer click captured by in-page MutationObserver
      flipping the label to "Copied ✓" (activation-less click showed the failure
      feedback). Also confirmed tenant isolation — the third DB row belongs to
      the integration-test tenant and correctly stays out of v1-studio listings.
      Web tsc + eslint exit 0.)_
- [x] 5.3 Bulk import path for Sophia-resolvable citations (paste DOI/ref list →
      resolved rows; unresolved rows surfaced, never silently dropped). _(New
      BFF `POST /sources/bulk-import`: each pasted line parses as DOI (bare or
      doi.org URL, canonicalized) or URL and scores through the REAL Sophia
      credibility engine (`groundQuery` → domain tier + overall score +
      grounding-floor check — local computation; publisher metadata behind a DOI
      would need a network resolver, honestly noted); non-DOI/URL lines and
      over-cap lines return in `unresolved` with reasons — resolved + unresolved
      always equals input lines. Spec pins known values (3 resolved incl. both
      DOI forms canonicalized, nature.com → academic tier; 1 surfaced; empty
      paste → 400; 54 passed). UI: paste box → Resolve list → scored rows with
      tier/floor verdicts + "Prefill the form" (title/refs/authority into the
      validated create form — the human completes kind and required fields) +
      unresolved cards. Chrome MCP pass (2026-07-20): 4-line paste resolved
      3/surfaced 1 with the exact manual-registration reason; nature.com row
      prefilled the form (title/refs/authority 0.36); float-display artifact
      caught and rounded. Both tsc + eslint exit 0.)_
- [x] 5.4 Claim workflow on the grounding tab: select script/premise text →
      create claim (kind, severity auto-floored for health-adjacent kinds);
      Sophia-assisted extraction behind the provider seam. _(New
      `StudioTaraWorkbenchGrounding` panel on the dossier: premise + latest-
      revision narration render as selectable text; a selection prefills the
      claim form with the text AND its block anchor (premise selections anchor
      to 'premise'); the form states each kind's severity floor and the server's
      raise is reported verbatim after filing ("the server raised your requested
      'info' to the kind's floor"); claims list with severity/status badges +
      Sophia notes; extraction button behind the provider seam with the verbatim
      503 refusal, proposals (when wired) prefill the form for human
      confirmation — creation is always the validated claims POST. Backend was
      P2.7 (claims POST with `applySeverityFloor`, extract seam) — already
      spec-pinned. Chrome MCP pass (2026-07-20): 4 text blocks rendered;
      Range-selection over the intro prefilled text + blockId anchor;
      health-benefit claim requested at info filed at BLOCKER with the raise
      notice, list row `health-benefit · blocker · needs-citation`, Postgres row
      anchored; extraction showed the honest not_configured card. Web tsc +
      eslint exit 0.)_
- [x] 5.5 Citation attach UX: search registry, attach with locator; trail
      rendered per claim (port the `SourceTrail` presentation from
      `TaraLineageGroundingPanel`, now live-data). _(New BFF
      `GET /concepts/:conceptId/citations` returns the concept's citation links
      plus resolved registry rows (title/kind/authority/tier/closed- practice) —
      spec extends the citation test to pin the trail read (54 passed). UI:
      per-claim Citation trail in the SourceTrail presentation (id-prefix code +
      title + tier + locator; honest "No sources attached" fallback) over LIVE
      data; Attach-citation picker per claim with registry text search, source
      select, locator input; duplicate attach renders the server's 409 verbatim.
      Chrome MCP pass (2026-07-20): empty trail fallback → searched
      "Mindfulness" (narrowed to 1) → attached with locator "pp. 494–501" →
      trail rendered
      `78735904 Mindfulness-based… · meta-analysis · pp. 494–501`; second attach
      refused "Citation not attached (citation_exists) — This claim already
      cites that source."; Postgres citation-link row carries the locator. Both
      tsc + eslint exit 0.)_
- [x] 5.6 Server grounding check run + results panel (supported / needs-citation
      counts, per-claim Sophia notes); `not_configured` state honest and
      prominent. _(Run-grounding-check button over the P2.7 endpoint
      (`runGroundingCore` — real `computeClaimStatus` per claim + the real
      Sophia credibility engine over cited sources' external refs, gate
      persisted); results card shows the verdict-tinted gate with
      supported/needs-citation counts and the FULL evidence verbatim
      (claimCount/supportedCount/unsourcedBlocker*/closedPractice*); claim cards
      re-read after the run so statuses and Sophia notes are the recomputed
      truth; any refusal renders as a prominent role=alert card with the
      server's reason/message verbatim (same honest pattern as the
      assist/extract seams). Chrome MCP pass (2026-07-20): added an external ref
      to the cited meta-analysis via PATCH, ran the check → gate PASS · 1
      supported · 0 needing citation; the health-benefit claim FLIPPED
      needs-citation→supported with the real Sophia note "sophia: grounded
      (confidence 0.66, 1 scoreable citation(s)); status per domain rules:
      supported — supported by 1 clinical-grade source(s) of 1 total"; Postgres
      grounding gate row persisted (pass, supportedCount 1). Web tsc + eslint
      exit 0.)_
- [x] 5.7 Gate wiring: blocker needs-citation claims block `grounded` transition
      and manifest build (both proven by tests in P2; UI shows the block
      reason). _(Adversarial check found the task's premise HALF-true: the
      transition guard was P2-proven (state-machine spec pins "blocker claim
      'cl-1'… needs a citation") but the bundle-build route did NOT gate on
      claims at all — wired now: the build runs a fresh `runGroundingCore`
      (statuses recomputed + check persisted as gate history) and refuses 422
      `unsourced_blocker_claims` with per-claim lines; router spec pins
      refuse-then-discharge (unsourced health-benefit → 422 with the claim id;
      RCT source cited → same build 201 validationOk; 55 passed). Chrome MCP
      pass (2026-07-20): filed an unsourced contraindication (floored to
      blocker), advanced premise→outline→script-draft live, then
      Advance-to-grounded showed the guard's exact line verbatim ("blocker claim
      'f1d386d7-…' (contraindication) needs a citation before the concept is
      grounded"); live bundle POST refused 422 with the publish-phrased line.
      BFF tsc exit 0.)_
- [x] 5.8 Contraindication/safety claim UX: dedicated kind badges, mandatory
      clinical-tier sourcing, surfaced on the dossier header. _(The
      clinical-tier mandate itself is domain-proven (grounding spec:
      health-adjacent claims demand clinical-grade support, theoretical never
      counts, tier downgrade flips back). UI: dossier header gains a safety
      strip (⚠ counts by kind, unsourced count, "publishing stays blocked until
      each cites clinical-grade evidence", per-claim excerpt + status;
      red-tinted while any unsourced, green once all sourced); grounding-panel
      claim cards give contraindication/safety-note a dedicated ⚠ SAFETY badge
      stating the mandate ("theoretical papers do not discharge it"). Chrome MCP
      pass (2026-07-20): strip showed "⚠ 1 safety claim (1 contraindication · 0
      safety-note) — 1 without clinical-tier sourcing…" with the claim excerpt;
      the grounding badge rendered "⚠ SAFETY · contraindication · blocker ·
      needs-citation — mandatory clinical-tier sourcing"; the durable gate
      history honestly flipped to FAIL (unsourcedBlockerCount 1, claim id in
      evidence) from the P5.7 publish attempt. Web tsc + eslint exit 0.)_
- [x] 5.9 Source detail page: blast-radius list (every concept/published item
      citing it), re-score action with propagation preview. _(Blast-radius
      endpoint extended with resolved claim + concept rows (titles/stages, not
      UUIDs); new `POST /sources/:sourceId/rescore-preview` runs the pure domain
      `propagateSourceUpdate` (P1.7) over the full trail set — per-claim
      before/after statuses with the domain's reasons and the concepts needing
      gate re-runs; retraction models `null`; NOTHING is written by a preview
      (spec pins the P1.7 known value over HTTP: tier downgrade to theoretical
      flips supported→needs-citation, retraction same, source untouched after;
      56 passed). New `/sources/[sourceId]` page: source header, blast list
      grouped by concept with dossier links, re-score controls (authority, tier
      for clinical, retraction checkbox honestly labeled preview-only — no
      deletion write path yet), Preview + Apply (Apply = the validated PATCH) +
      needs-rerun links; registry rows gained Detail links. Chrome MCP pass
      (2026-07-20): blast showed "Rain on the roof · script-draft" with the
      supported health-benefit claim; theoretical preview rendered "supported →
      needs-citation (CHANGES) … has 1 citation(s) but none clinical-grade" +
      "Needs grounding re-run: Rain on the roof"; applied an authority 0.5→0.6
      re-score live (header re-read 0.6, tier untouched). Both tsc + eslint exit
      0.)_
- [x] 5.10 Playwright spec: claim → needs-citation block → attach citation →
      gate clears; Chrome MCP pass recorded. _(New "grounding journey (P5.10)"
      test: API setup (script-draft concept + RCT source), then UI all the way —
      health-benefit claim filed through the form (floored to blocker),
      Advance-to-grounded blocked with "needs a citation", the citation attached
      via the search picker, Run-grounding-check → gate PASS with the claim
      reading supported, Advance passes to grounded; server-truth assert
      stage=grounded. First run surfaced the cookie banner intercepting
      bottom-of-page clicks (fixed with the existing essential-only
      `suppressCookieBanner`, applied to all three journeys), then a REAL bug:
      background dossier reloads (StrictMode's doubled dev effect) wiped
      in-progress premise edits — fixed with a form-dirty guard (any premise
      input/chip marks dirty; reloads skip the form reset; successful save
      clears it). Suite now 4/4 green in 17s with zero retries (the
      previously-flaky P4.9 ladder dropped to a clean 4.1s). Chrome MCP passes
      recorded per-task in 5.1–5.9.)_

## Phase 6 — Programs, themes, categories

- [x] 6.1 `/programs` list + create (name, context brief, cadence, calendar
      placement). _(New `/studio/tara-workbench/programs` page +
      `StudioTaraWorkbenchPrograms` over the live P2.8 endpoints
      (curate-taxonomy/curator-gated): create form with name, context brief
      (noted as fenced into agent briefs), cadence select, and date-window
      calendar placement; theme weights/categories/overlay start EMPTY (edited
      in 6.4/6.5/6.7 — never faked); server Zod issues render verbatim. Registry
      list shows cadence, window-or-evergreen, overlay presence, brief, and
      counts; names link to the 6.2 detail route. Route map flipped. Chrome MCP
      pass (2026-07-21): seasonal WITHOUT a window refused verbatim
      ("windowStartsAt: A seasonal program requires a date window."); with
      2026-07-01→2026-08-15 created "Rains Retreat 2026" (form reset, list count
      1, row shows the window); Postgres `v1_tara_workbench_program` row
      durable. Web tsc + eslint exit 0.)_
- [x] 6.2 Program detail: linked concepts by stage, cadence view, category
      links. _(New `/programs/[programId]` page +
      `StudioTaraWorkbenchProgramDetail` over the live GET (program + linked
      concepts): cadence/window view with computed length ("seasonal ·
      2026-07-01 → 2026-08-15 (45 days)"), concepts grouped by maturity stage
      with dossier links (killed excluded), theme weights and category links
      rendered when present with honest P6.4/P6.7 pendings when empty, overlay
      presence declared (editor lands P6.5). Chrome MCP pass (2026-07-21):
      assigned rain-on-the-roof to Rains Retreat via the concepts PATCH, detail
      showed SCRIPT-DRAFT (1) with the correct dossier href, the 45-day window
      computation, and all three honest pendings. Web tsc + eslint exit 0.)_
- [x] 6.3 Season/arc modeling: date-ranged program windows feeding the calendar
      (P10) and seasonal-ritual scheduling. _(New pure domain module
      `program-schedule.ts` (`deriveProgramSchedule`): windowed programs
      schedule inside their window, evergreen across the queried range;
      daily/weekly/biweekly step from the window anchor (clipped views keep
      weekday phase), monthly clamps into shorter months (Jan 31 → Feb 28),
      seasonal yields ONE span with no recurring slots (in-window generation
      belongs to the tara.seasonal-ritual category cadence); fails loud on
      malformed ranges. 7 known-value lib tests (weekly 45-day window → 7 exact
      dates; 200 lib tests total). New BFF `GET /calendar?from&to` derives
      entries for every program (explicit bounds required → 400; spec pins the
      7-slot known value over HTTP; 57 passed) — the P10 calendar and
      seasonal-ritual scheduling consume this feed. Program detail gains a
      Derived-schedule card. Chrome MCP pass (2026-07-21): weekly program showed
      "7 weekly slots derived from the window (server-computed calendar feed)"
      with all 7 dates; the feed listed both programs (weekly slots=7, seasonal
      span-only). Also unblocked local BFF boot by applying main's pending
      Prisma migrations (account-protection et al.) to oshun_dev. Lib + BFF +
      web tsc exit 0.)_
- [x] 6.4 Theme vocabulary CRUD + cross-filtering wired into ideation, concepts,
      and programs (migrate P3.6 free-text tags). _(Per ratified A1 the
      vocabulary is the shared ThemeTaxonomy enum (deliberately not DB rows) —
      "CRUD" is per-entity theme editing over it. Tag migration: promote now
      carries taxonomy-matching spark tags onto concept themes (non-matching
      free text stays on the spark as provenance; spec pins
      ['sleep','breath','totally-custom-tag'] → ['sleep','breath']).
      Cross-filter wiring: dossier gains an immediate-save Themes chip editor
      (concepts PATCH); inbox gains a theme filter over spark tags (keyboard
      triage indexes follow the filtered list); programs list gains a theme
      filter over themeWeights; program detail gains a theme-weight editor
      (toggle + per-theme weight input) through the existing validated program
      PATCH (spec: weights save, duplicate theme → 400; found and removed a
      PATCH route I'd duplicated — P2.8 already had one; 59 passed). Chrome MCP
      pass (2026-07-21): weight toggled on Weekly Wind-Down (0.7 saved);
      programs filter sleep→1/courage→0; dossier themes sleep+grounding saved
      (Postgres ["sleep","grounding"]); board filter
      sleep→rain-on-the-roof/courage→0; inbox capture tagged 'breath,
      midday-custom' filtered breath→4/courage→0; live promote migrated themes
      ['breath']. Both tsc + eslint exit 0.)_
- [x] 6.5 Steering overlay editor: tenets, weighted emphasize/avoid themes,
      banned-topic additions; composed-direction preview (global ⊕ overlay)
      exactly as the next brief would receive; loosening attempts rejected with
      the server error surfaced. _(Program-detail overlay editor: tenets +
      banned-topic textareas (line-per-entry), weighted avoid-theme chips
      (emphasize weights are the P6.4 editor), and autonomy-override rows with a
      category select showing each floor from the live merged registry (fixed
      the registry row shape — spec.categoryId + autonomyFloor); Save/Clear go
      through the validated program PATCH whose `validateOverlay` runs the REAL
      direction compiler. Composed- direction preview renders the compiler's
      exact JSON. Chrome MCP pass (2026-07-21): tightening overlay (tenets,
      banned, avoid focus 0.7, tara.seasonal-ritual→manual) saved; loosening to
      full-auto refused verbatim ("requested autonomy 'full-auto' is more
      permissive than the code floor 'approval'. Overrides are tighten-only." +
      floor/requested detail); preview showed the real composition — program
      brief as a [program:…]-prefixed tenet, P6.4 emphasize weight (sleep 0.7),
      avoid focus, bannedTopics, tightened override; Postgres overlay row
      durable. Web tsc + eslint exit 0.)_
- [x] 6.6 Program-level language/tone presets (feeds the linter config where
      applicable) — presets are data, linter logic stays in the lib. _(Contract:
      `WorkbenchLanguagePresetSchema` (named preset + up to 24 extra phrase
      rules, unique phrases) on the program row as `languagePreset` defaulting
      null (pre-P6.6 rows/clients stay parseable; 19 contract tests); Prisma
      schema regenerated (+ `language_preset` JSONB), migration
      `20260721080000_tara_program_language_preset` applied, client regenerated
      (a stale client 500 caught live and fixed by `prisma     generate`). Lib:
      `lintInvitationalLanguage`/`evaluateInvitational-     Language` accept
      extra rules ON TOP of the built-ins — logic stays in the lib, presets are
      data (spec: "surrender to"+"divine" flag only WITH the preset, built-ins
      still apply alongside; 24 tests). BFF gate battery resolves the concept's
      program preset and stamps `languagePreset` provenance into the gate
      evidence (spec pins the preset-only flag with 'gentle-secular' provenance;
      60 passed). UI: preset editor on the program detail (name + rule rows with
      phrase/severity/message/suggestion, add/remove, Save/Clear via the
      validated PATCH). Chrome MCP pass (2026-07-21): built and saved the
      gentle-secular preset live (durable, page attr flipped); live battery run
      under the program recorded verdict pass · preset 'gentle-secular' in
      evidence. All tsc + eslint exit 0.)_
- [x] 6.7 Category registry editor for `tara.*`: title, cost units, maxPerCycle,
      autonomy (tighten-only vs code floor); shows code-floor provenance per
      field. _Verified 2026-07-21:_ `StudioTaraWorkbenchCategories.tsx` mounted
      on the programs page over the live merged registry; the categories GET now
      ships `codeFloors` (the tara.\* code specs) so the editor labels EVERY
      field `code floor` vs `DB override (floor: <value>)` by diffing merged
      spec vs floor spec (row-level provenance badge: code-floor / db-tightened
      / db-added with the 'approval' floor note). Edit form posts the full row
      (title, contentDomains chips mirroring WorkbenchContentDomainSchema, cost
      units, max/cycle, autonomy select, active) through the existing
      tighten-only PUT. Chrome-verified live: loosening `tara.seasonal-ritual`
      approval→full-auto REFUSED with the server's verbatim message +
      `floor 'approval', requested 'full-auto'`; tightening →manual with retitle
      saved, row flipped to `DB-tightened over the code floor` with per-field
      overrides (title + autonomy show floor values; cost units + max/cycle stay
      code floor); Postgres `v1_tara_workbench_category_row` shows (v1-studio,
      tara.seasonal-ritual, 'Seasonal rituals (curated)', manual, t). Router
      spec asserts codeFloors ride along and stay floor-stable after tightening
      (60 passed / 1 env-skipped); web tsc + eslint clean.
- [x] 6.8 Autonomy plane reads the merged registry (P1.9) — integration test
      proving a DB-tightened category is respected by the plane and a loosened
      row is refused at write time. _Verified 2026-07-21:_ production wiring
      previously bound the RAW code floor — closed with
      `bff/src/tara-workbench/plane-categories.ts`
      (`resolveTaraPlaneCategories`: store rows → real `mergeCategoryRegistry` →
      `composePlaneCategories` swaps the tara.\* slice for merged activeSpecs,
      non-tara passthrough; merge failure — only reachable if a deploy tightens
      a floor under an existing row — logs loudly and binds the code floor, the
      newest reviewed intent). `server.ts` binds it at plane creation and wires
      `taraWorkbenchPlaneSync` (late-bound plane ref →
      `plane.restoreCategoryAutonomy`, the same surface durable restore uses)
      through `createApp` → route input `syncPlaneCategoryAutonomy`, called in
      the category PUT after the tighten-only merge passes — so only levels that
      survived the floor check ever reach the live plane. Two integration tests
      (62 passing total): (1) plane bound pre-row carries 'approval'; PUT manual
      → LIVE plane flips to manual; reboot- bound plane reads merged spec
      (manual + retitle) with non-tara ids unchanged; PUT full-auto → 409
      verbatim and plane stays manual; (2) db-added `tara.micro-pause` (approval
      floor) joins at next bind, deactivating it drops it. Verified LIVE on
      :4010: booted plane status shows tara.seasonal-ritual manual ('Seasonal
      rituals (curated)'); runtime PUT copilot reflected in
      `/v1/agentic/autonomy/status` with NO restart; restored to manual. BFF tsc
      clean. (Boot also needed `prisma migrate deploy` for main-merged
      isis/model-quality migrations.)
- [x] 6.9 Playwright spec: create program → overlay → composed preview; Chrome
      MCP pass recorded. _Verified 2026-07-21:_ new
      `e2e/studio-tara-workbench.spec.ts` P6.9 test (curator-scoped proxy) runs
      the full journey against the REAL BFF/store/Postgres: create weekly
      evergreen program via the form → open its detail link → fill overlay
      tenet + banned topic + a tighten-only `tara.seasonal-ritual → manual`
      autonomy override → Save → click Preview composed direction → assert the
      composed pre contains the tenet, banned topic, and `tara.seasonal-ritual`
      → reload and assert the overlay persists as `present`. Green in 23–24s;
      full workbench e2e suite 5/5 green (52.7s). Chrome MCP pass recorded live
      on :3010: created program `ae82b6c1-…`, saved the same overlay, and the
      composed-direction JSON showed `tenets` (program brief + "prefer rainfall
      and shelter imagery"), `bannedTopics: ["crowded     commutes"]`, and
      `autonomyOverrides: { "tara.seasonal-ritual":     "manual" }` — the real
      `compileProgramDirection` output. Also cleaned two now-stale "lands with
      P6.7" copy notes in ProgramDetail (header doc + empty category-links note)
      now that the registry editor ships; web tsc + eslint clean.

## Phase 7 — Review pipeline

- [x] 7.1 Review tab composing the approval-workflow evaluator
      (`/v1/admin/studio/approval-workflow*`): per-stage quorum + required roles
      configured for the Tara ladder; verdicts persisted to the dossier.
      _Verified 2026-07-21:_ composes the LIVE stateless
      `evaluateApprovalWorkflow` evaluator (not a fork) over the canonical
      `TARA_REVIEW_LADDER` (new `@oshun/tara-workbench/review-ladder.ts`, 5 lib
      tests): two permission-gated stages — subject-review (`review`) →
      editorial-approval (`approve-publish`), each quorum 1. Per the evidence
      doctrine the VERDICT is never stored: durable per-(stage,reviewer,
      revision) decision rows persist to the new
      `v1_tara_workbench_review_approval` table (contract
      `WorkbenchReviewApprovalSchema` + registry + migration
      `20260721120000_tara_review_approval` applied; store
      `putReviewApproval`/`reviewApprovalsForConcept`), and
      `computeTaraReviewRound` recomputes it via the evaluator scoped to the
      latest revision (latest decision per reviewer wins → no double-vote;
      reject-override). Wired into `assembleTransitionEvidence.approvalVerdict`
      (replaced the honest null), so the `approved` transition (P7.6) reads a
      real verdict. Routes: `GET /concepts/:id/review` (ladder+verdict+view+
      trail), `POST …/review/decisions` (stage-permission-gated; records the
      authorizing role; 400 unknown stage, 409 no-revision, 403 wrong
      permission). Review-tab UI `StudioTaraWorkbenchReview` + page
      `concepts/[conceptId]/review` (RouteMap + dossier link). 8 route tests (70
      BFF total). Verified LIVE on Postgres via curl (in-progress → subject
      approve → still in-progress → editorial approve → `approved`; reviewer 403
      on editorial stage; two rows persisted with correct roles/version, NO
      verdict column) and Chrome-driven the UI end-to-end (both stages flipped ✓
      approved, banner "APPROVED", decision trail shows roles editor/publisher +
      note). Web + BFF tsc + eslint clean.
- [x] 7.2 Threaded comments on dossier revisions via the comment-threads
      service; anchored to block ids. _Verified 2026-07-21:_ durable
      `WorkbenchReviewComment` rows (contract + registry + migration
      `20260721130000_tara_review_comment` applied; store
      `putReviewComment`/`getReviewComment`/`reviewCommentsForConcept`) anchored
      to a script `blockId` (nullable = concept-level) and threaded via
      `parentId`. Per the evidence doctrine, depth / @-mentions / orphan status
      are NEVER stored — the GET composes the LIVE stateless
      `analyzeCommentThreads` analyzer over the durable rows. Routes:
      `GET /concepts/:id/comments` (rows + analysis), `POST …/comments`
      (edit-draft|review gated; rejects a cross-concept parent 400
      `unknown_parent`), `PATCH …/comments/:id/resolve` (resolve/reopen, 404
      unknown). UI `StudioTaraWorkbenchComments` on the review page:
      block-anchor picker populated from the latest revision's blocks, threaded
      indent by derived depth, mention/orphan/resolved badges, reply + resolve.
      5 route tests (75 BFF total, incl. a real fix — supersede-in-place review
      decisions so same-ms timestamps can't corrupt the round). Verified LIVE on
      Postgres via curl (root+reply anchored to b1, depth 0/1, `@teacher`
      mention, curator 403, rows persisted) and Chrome-drove the UI end-to-end
      (block-anchored comment with mention → threaded reply at depth 1 → summary
      "2 comments · 1 threads · 1 mentions"). Web + BFF tsc + eslint clean.
- [x] 7.3 Gate battery panel: invitational-language, duration coherence,
      transcript/caption presence, grounding — one run, per-gate evidence rows,
      re-run affordance. _Verified 2026-07-21:_ the run route
      (`POST     /concepts/:id/gates/run`) already ran all five gates and
      persisted results (P2.10); added `GET /concepts/:id/gates` returning the
      LATEST result per gate id with a per-row `stale` flag (result's
      conceptRevisionVersion ≠ the current revision → prompt a re-run) and
      `currentRevisionVersion`. UI `StudioTaraWorkbenchGates` on the review page
      renders each gate's verdict + every recomputed evidence key/value, a stale
      banner, and a permission-gated "Run the battery" button. 2 route tests
      (all five gate ids returned, audio-qa `unavailable`, freshness → staleness
      after a new revision; 77 BFF total). Chrome-verified live: ran the battery
      from the UI → invitational-language ✓ pass, duration-coherence ✗ fail
      (deltaSeconds −54.4, target 60 vs compiled 5.6), grounding ✓ pass,
      transcript-captions ✗ fail (no assets staged, P9.2 pending), audio-qa ⊘
      unavailable. Fixed a real bug found live — the run POST sent a JSON
      content-type with no body (Fastify rejects it); now sends `{}`. Web tsc +
      eslint clean.
- [x] 7.4 Audio QA gate seam: honest `unavailable` until an audio pipeline
      exists (fail-loud, no fake pass); gate registry documents the seam.
      _Verified 2026-07-21:_ `evaluateAudioQa()` already returns `unavailable`
      with a reason (the gate battery stamps it; transition guards never treat
      `unavailable` as a pass) — confirmed live. Added the missing piece: a real
      gate REGISTRY `libs/oshun/tara-workbench/src/gate-registry.ts`
      (`TARA_GATE_REGISTRY`) documenting every gate's name, what it checks,
      whether it blocks transitions, and its `availability` — `available` (a
      bound evaluator judging real facts) vs `seam`
      (`{status:'seam',     waitingOn}`). audio-qa is the one live seam
      (`TARA_GATE_SEAM_IDS ===     ['audio-qa']`, non-blocking so a concept is
      never wedged behind an impossible verdict); transcript/captions is
      documented as AVAILABLE (real evaluator that honestly fails while assets
      are absent — a failing available gate is not a seam). 3 lib tests. The
      registry rides along on `GET /concepts/:id/gates` and the panel renders
      each gate's checks + a `SEAM — awaiting …` badge (Chrome-verified:
      audio-qa shows the seam badge + waiting-on, grounding shows "blocking
      gate"). Route test asserts the live registry documents the seam. lib +
      BFF + web tsc/eslint clean.
- [x] 7.5 Reviewer assignment + notification hook into the existing
      notification-center pattern. _Verified 2026-07-21:_ durable
      `WorkbenchReviewAssignment` rows (contract + registry + migration
      `20260721140000_tara_review_assignment` applied; store
      `putReviewAssignment`/`getReviewAssignment`/`reviewAssignmentsForConcept`/
      `reviewAssignmentsForAssignee`). The notification hook composes the LIVE
      studio notification-routing engine (`routeNotifications` — the same
      stateless deliver-now/digest/suppressed evaluator behind
      `/v1/admin/studio/notification-routing`): the assignment IS the durable
      notification and its ROUTE is recomputed from its priority (never stored,
      evidence doctrine) — high/critical → deliver_now, normal/low → digest.
      Routes: `POST /concepts/:id/review/assignments` (request-review|review
      gated; 400 invalid priority), `GET …/assignments` (with routes),
      `PATCH     …/assignments/:id/clear`, and `GET /review/assignments/mine`
      (the assignee's open-assignment inbox). UI
      `StudioTaraWorkbenchAssignments` on the review page: assign form (assignee
      id, priority, note), open list with the routed delivery + Clear. 4 route
      tests (81 BFF total). Verified LIVE on Postgres (row persisted, no route
      column) + curl (assignee inbox shows it; different reviewer's inbox
      empty) + Chrome (assigned reviewer-sam normal → "batched into the digest",
      reviewer-lila high → "delivered now"). Web + BFF tsc + eslint clean.
- [x] 7.6 Approval → `approved` stage transition requires: workflow verdict +
      all blocker gates green (server-enforced; test exists in P2.10). _Verified
      2026-07-21:_ the `POST /concepts/:id/transition` route already composes
      `assembleTransitionEvidence` (recomputed live) with the domain lib's
      `evaluateTransition`, whose `approved` case requires
      `approvalVerdict === 'approved'` AND the grounding / invitational-language
      / duration-coherence blocker gates green (state-machine.ts). P7.1 replaced
      the honest-null `approvalVerdict` with the REAL recomputed ladder verdict,
      so this rule now enforces the actual review outcome. Added a full
      end-to-end route test proving it: a deterministic all-silence 300s script
      (matches the premise target ⇒ duration coherent; no spoken text ⇒
      invitational passes; no claims ⇒ grounding passes) is advanced
      spark→premise→outline→script-draft (gate battery run:
      grounding/invitational/duration all pass)→grounded→in-review;
      `in-review → approved` is then BLOCKED 409 with the verbatim blocker "no
      approval-workflow verdict exists for the current review round"; after
      casting subject-review approve (reviewer) + editorial-approval approve
      (publisher) the SAME transition SUCCEEDS (stage → approved). A second test
      proves a rejected editorial stage keeps the transition blocked ("the
      review round rejected this concept") even with green gates. 83 BFF tests
      pass. (Server-enforcement task — the integration test is the deliverable;
      the transition UI + verdict/gates surfaces already ship on the dossier +
      review tab.)
- [x] 7.7 Playwright spec: full review round with a failing gate fixed; Chrome
      MCP pass recorded. _Verified 2026-07-21:_ new P7.7 e2e in
      `e2e/studio-tara-workbench.spec.ts` (review-scoped proxy: editor +
      reviewer + publisher) runs the full round against the REAL
      BFF/store/Postgres: seed a concept + premise (300s) + a DIRECTIVE script
      ("You must empty your mind…") → open the review tab → Run the battery →
      assert the invitational-language gate shows `fail` → fix the script (a
      softened invitational revision, what the develop tab writes) → reload +
      Run the battery → assert the gate now shows `pass` → cast subject-review
      approve then editorial-approval approve → assert the ladder verdict flips
      to `approved` → confirm the durable verdict via a fresh `GET …/review`.
      Green in ~11.5s driving real Chromium (`PW_BROWSER_CHANNEL=chrome`); the
      full workbench e2e suite is 6/6 green (39.4s). This is the Chrome pass —
      Playwright drives the actual browser end-to-end.

**Phase 7 complete** — draft review pipeline: approval ladder (7.1), threaded
comments (7.2), gate battery panel (7.3), audio-QA seam + registry (7.4),
reviewer assignments + notification routing (7.5), server-enforced approved
transition (7.6), and the full-round e2e (7.7).

## Phase 8 — Agent orchestration (Tara view)

- [x] 8.1 `/agents` embedding plane data scoped to `tara.*` via
      `/v1/agentic/autonomy` (runs, queue, budgets, gates, provenance) — reuse
      `AutonomyOperatorConsole` components where extractable; extract shared
      pieces rather than fork (refactor allowed, behavior identical, operator
      console e2e stays green). _Verified 2026-07-21:_ NO fork — added an
      optional `categoryPrefix?: string` prop to the existing
      `AutonomyOperatorConsole` (used by `/operator/autonomy`). When set, a pure
      `scopeOverviewByCategory` helper filters the fetched review queue, the
      status `categories[]`, and the bandit `posteriors{}` to that namespace
      (cycles/steering stay plane-wide context); when UNSET the console is
      byte-for-byte unchanged. New page `app/studio/tara-workbench/agents`
      renders `<AutonomyOperatorConsole categoryPrefix="tara." />` in the studio
      shell (RouteMap flipped to implemented). Studio actors hold an operator
      scope (`admin:studio`), so the live queue actions work here too (feeds
      8.4). Verified the operator-console e2e stays GREEN (5/5, unscoped
      unchanged) and Chrome-verified the scoped page live: eyebrow reads "LIVE
      AUTHORITY PLANE · VERSION 0 · SCOPED TO TARA._",
      `data-autonomy-console-scope="tara."`, full status rail + review-queue
      pane render (empty — the dev plane is fail-closed with no LLM key, an
      honest empty tara._ queue). Web tsc + eslint clean.
- [x] 8.2 Steering panel showing the composed direction per category (global ⊕
      program overlay) with links to the P6.5 editor. _Verified 2026-07-21:_ new
      `GET /agents/steering` assembles, per tara.\* category (from the merged
      registry), the programs that steer it (`program.categoryIds` includes the
      category) each with its composed direction recomputed by the REAL
      `compileProgramDirection(DEFAULT_DIRECTION, program, floors)` — the same
      compile the P6.5 preview uses — plus the global `DEFAULT_DIRECTION` base
      and each category's autonomy floor/default. UI
      `StudioTaraWorkbenchSteering` on the agents page renders the global
      direction, then each category with its floor and the steering programs
      (each a Link to `/programs/[id]` = the P6.5 overlay editor) showing
      composed tenets, banned topics, and autonomy overrides; categories with no
      program say "briefs get the global direction only". 1 route test (85 BFF
      total). Verified LIVE: created a Monsoon Nights program on
      tara.seasonal-ritual with an overlay → the steering map shows it with the
      composed direction (program brief tenet + "prefer rainfall and shelter
      imagery", banned "weather disasters", `tara.seasonal-ritual→manual`).
      Chrome-verified the panel renders the category card + program link + full
      composed direction. Web + BFF tsc + eslint clean.
- [x] 8.3 Brief-from-concept flow: dossier → category brief (premise, themes,
      grounding constraints, banned topics) → plane; queue item visibly linked
      back to the concept. _Verified 2026-07-21:_ the `POST /concepts/:id/brief`
      route (P2.9, `composeBrief` + persisted `brief-from-concept`
      `WorkbenchAgentRunLink`; already unit-tested) gets its UI:
      `StudioTaraWorkbenchBriefLauncher` on the dossier picks an ACTIVE tara.\*
      category + an idea angle, POSTs the brief, and renders the plane brief
      (its OWN `composeBrief` format), the composed direction (tenets/banned),
      and the recorded run link. The concept↔plane back-link already renders in
      the dossier's Agent runs section (from the dossier GET's `runLinks`).
      Verified LIVE end-to-end: composed a brief for tara.seasonal-ritual
      (explainer) from the a0713f56 dossier → run link `wb-brief-83f11277…`
      persisted in Postgres (`v1_tara_workbench_agent_run_link`, direction
      brief-from-concept, queue_item_id null until the plane produces) → dossier
      GET returns it → reloaded dossier shows "brief-from-concept · run
      wb-brief-83f11277… · no artifact · no queue item" in Agent runs. Web tsc +
      eslint clean.
- [x] 8.4 Draft↔concept back-links: agent artifacts render on the dossier with
      provenance; workbench approve/reject/request-tweak/veto/rollback drive the
      same plane queue actions (no parallel state). _Verified 2026-07-21:_ new
      `POST /concepts/:id/run-links` records the REVERSE `artifact-to-concept`
      `WorkbenchAgentRunLink` (runId + optional queueItemId/artifactId,
      edit-draft gated, 400 without a runId) — it records ONLY provenance, the
      workbench keeps NO parallel approval state. The dossier's Agent runs
      section renders every run link with its direction + artifact/queue-item
      provenance, and for a link carrying a queueItemId shows "Act on it in the
      Tara agents queue →" linking to `/studio/tara-workbench/agents`, where the
      operator console's actions (approve/reject/veto/request-tweak/rollback,
      P8.1) act on the PLANE's own review queue — the same actions, no fork. The
      brief launcher gains a "Link a produced draft" form posting to /run-links.
      2 route tests (87 BFF total). Verified LIVE: linked
      plane-run-monsoon-1/qi-monsoon-42/art-monsoon-7 to the a0713f56 dossier →
      dossier GET returns BOTH edges (brief-from-concept + artifact-to-concept)
      → Chrome-verified the dossier renders both with the correct directions,
      the artifact-to-concept link carrying the `/studio/tara-workbench/agents`
      action path, and the link-a-draft form present. Web + BFF tsc + eslint
      clean.
- [x] 8.5 Budget/authority-SLO summary for Tara categories on the hub. _Verified
      2026-07-21:_ new `StudioTaraWorkbenchAgentBudget` on the workbench hub
      reads the autonomy plane's own status (the auth-only
      `GET /v1/agentic/autonomy/status`, so any studio session can read it),
      filters `categories`/`posteriors` to tara.\*, and shows per-category cost
      units, max/cycle, default autonomy, and the bandit's learned posterior,
      plus the global cycle budget + governor (active/queued/max-concurrent/
      rate-limit). The human-authority SLO (operator-gated `…/authority/slo`) is
      fetched best-effort and DEGRADES HONESTLY: a 403 renders "requires the
      operator scope" (never a fabricated readiness); an operator sees
      autonomyRaiseReady + drills readiness + edit-incorporation % + required
      policy reviews. Verified LIVE on the hub: "Cycle budget 1000 units ·
      director fail-closed", tara.seasonal-ritual "1000 cost units · max 1/cycle
      · default manual · learned value 0.500", SLO honestly "requires operator
      scope"; confirmed via an operator-bearer fetch that `…/authority/slo`
      returns 200 with every field the ready branch reads (autonomyRaiseReady,
      drills.missingOrFailed [6 drills], editIncorporation, requiredReviews).
      Also fixed a LATENT hub crash surfaced by the P7.1 RouteMap addition: the
      workspace rendered every implemented route as a `<Link href>`, but App
      Router rejects a parameterised href (`[conceptId]`) — now the hub links
      only static entry points and lists dynamic routes under "In-context
      surfaces". Web tsc + eslint clean.
- [x] 8.6 Playwright spec (plane test harness): brief-from-concept → artifact
      back-link → approve; Chrome MCP pass recorded. _Verified 2026-07-21:_ new
      P8.6 e2e in `e2e/studio-tara-workbench.spec.ts` with a `TaraPlaneHarness`
      that mocks ONLY `/v1/agentic/autonomy/**` (one tara.seasonal-ritual queue
      item + working approve action) while the workbench routes stay REAL
      (proxied to the BFF). Flow: seed a concept + premise → on the dossier
      compose a brief via the real launcher (`data-tara-workbench-brief-result`
      appears) → link the produced artifact back via the real link-a-draft form
      (queueItemId = the plane item) → navigate to the Tara agents view → assert
      the console is live and `data-autonomy-console-scope="tara."` → the scoped
      queue shows the tara item → select → Approve → confirm → assert the
      harness recorded the approve (the workbench drove the PLANE's queue
      action, no parallel state). Green in ~6.3s driving real Chromium; the full
      workbench e2e suite is 7/7 green (40.4s) — the Chrome pass.

**Phase 8 complete** — Tara agent orchestration: scoped agents view reusing the
operator console (8.1), per-category steering map (8.2), brief-from-concept
launcher (8.3), artifact-to-concept back-links + plane-queue actions (8.4), hub
budget/authority-SLO summary (8.5), and the full plane-flow e2e (8.6).

## Phase 9 — Publishing bridge

- [x] 9.1 `/publishing` manifest builder UI: schema-mapped form pre-filled from
      the dossier; required-field and enum errors shown inline,
      server-validated. _Verified 2026-07-21:_ the bundle build/validate backend
      already existed (P2.11 `POST /publish/bundles` composing the domain
      `buildMeditationManifest`, returning `validationErrors` verbatim, pinning
      invalid bundles to draft, refusing 422 on unsourced blocker claims). New
      `StudioTaraWorkbenchPublishing` on the new `/publishing` page (RouteMap
      flipped): concept picker → schema-mapped form (description, short,
      premium/ featured, audio file/format/bitrate/sampleRate/channels/duration,
      transcript, captions, thumbnail, teacher, tags, contentVersion) → builds
      server-side and renders the manifest builder's errors VERBATIM inline
      (never client-guessed) with the bundle pinned to draft, plus a per-concept
      bundle list with state/validity. Verified LIVE on the a0713f56 concept: an
      empty build showed 5 verbatim errors ("description: must be 10–2000
      characters", "audio.file: missing filename", "editorial: guided content
      requires a transcript"/"…at least one caption track"), invalid → draft;
      after filling audio + transcript + captions the build returned "✓ Bundle
      valid — ready to export" (0 errors), and the Bundles list shows both the
      valid and invalid drafts. Fixed a real form gap found live (missing
      transcript + caption inputs that guided content requires). Web tsc +
      eslint clean.
- [x] 9.2 Asset staging: audio/thumbnail/transcript/captions upload to MinIO
      staging with checksums recorded in the bundle. _Verified 2026-07-21:_ new
      `POST /publish/bundles/:bundleId/assets` (approve-publish gated) decodes a
      base64 asset, computes a REAL sha256, uploads to MinIO/S3 via the existing
      `@oshun/storage` asset-store seam (`resolveAssetObjectStore`, wired
      through createApp→app.ts→server.ts from `OSHUN_ASSET_STORE_*`), and
      records the `assetRef {relativePath, stagingKey, sha256, bytes}` in the
      bundle (re-upload of a path REPLACES, staging key namespaced by bundle).
      FAIL- CLOSED: no store configured → 503 `not_configured` (no fabricated
      key/ checksum); upload failure → 502. 3 route tests (89 BFF total:
      fail-closed 503, real-sha256 recording + store-received bytes +
      path-replace, empty/ malformed 400). Verified LIVE against a real MinIO
      (started the dev `minio` container, created the `tara-staging` bucket):
      curl-uploaded a transcript → recorded sha256 EXACTLY matched an
      independent `shasum -a     256`, byte count correct, object present in
      MinIO; then Chrome-drove the real UI file input (`file_upload`) → "staged
      ui-transcript.vtt · 85 bytes · sha256 759b184b…" (matching the file's real
      checksum) and the 85B object landed in MinIO under the bundle's staging
      key. Web + BFF tsc + eslint clean.
- [x] 9.3 Bundle assembly + validation view (file layout preview exactly
      matching the `apps/tara/content` tree structure). _Verified 2026-07-21:_
      the publishing UI now shows, for a built bundle, a `computeLayout` view
      that parses `manifestJson` for `category`/`slug` and lists the exact tree
      paths the bundle writes — the item directory
      `apps/tara/content/meditations/<category>/<slug>/` (confirmed against the
      real tree: `meditations/stress/calm-your-mind/` holds metadata.json +
      audio.mp3 + transcript + captions.vtt + thumbnail) plus `metadata.json`
      (the manifest) and every manifest file (audio, thumbnail, transcript,
      captions[]), each flagged `✓ staged` (present in the bundle's assetRefs by
      relativePath) or `not staged yet`. Verified LIVE: a valid bundle for the
      a0713f56 concept rendered
      `apps/tara/content/meditations/sleep/ui-review-demo-24934/` with
      metadata.json + audio.mp3 + transcript.vtt + captions.vtt. Web tsc +
      eslint clean.
- [x] 9.4 `tools/tara-content/` importer CLI: bundle → git branch with the files
      laid into the tree + schema validation run; refuses on any validation
      error; README with usage. _(`@oshun/tara-content`: `import.ts`
      `importBundle` validates the manifest against the checked-in
      `meditation.schema.json` (ajv 2020-12) AND re-hashes every asset's bytes
      vs its recorded sha256 BEFORE any side effect — throws
      `ImportRefusedError` with all reasons; on success creates a git branch,
      writes `meditations/<category>/<slug>/metadata.json` + assets,
      `git add`+`commit` (injectable git runner). `cli.ts` (commander) wraps it
      with `--dry-run`/`--skip-git`/`--branch`, exit 1 + reasons on refusal.
      Verified 2026-07-21: 5/5 lib tests pass (dry-run plan, schema
      refusal→title, checksum refusal→sha256 mismatch, real-tree write, git
      branch/commit sequence); CLI dry-run against the REAL content tree +
      schema → correct layout, exit 0; CLI on an invalid bitrate → "Import
      refused: /audio/bitrate must be >= 64", exit 1. Added the missing scoped
      `vitest.config.ts` (the package was inheriting the root workspace config)
      and fixed a real bug — the test fixture used `bitrate: 32` which the
      schema rejects (>= 64); the importer correctly refused it, proving the
      integrity check is real. README documents usage/dry-run/skip-git. Light
      footprint: single vitest + tsx runs, no dev stack.)_
- [x] 9.5 Bundle state tracking: draft → exported → branched → merged →
      verified; `verified` flips only on evidence (item resolvable in the
      tree/consumer API read-back), never optimistically.
      _(`POST /publish/bundles/:bundleId/verify` in
      `apps/oshun/bff/src/routes/tara-workbench.ts`: the generic `/state` route
      already refuses hand-setting `verified`
      (`verification_requires_evidence`); this evidence-gated flow reparses the
      REAL `apps/tara/content` tree via `parseContentTree`, locates
      `meditations/<category>/<slug>/metadata.json` from the bundle's own
      manifest, and flips `merged`→`verified` ONLY when the on-disk `id` matches
      the manifest `id`. Refuses on: unconfigured tree (503 `not_configured`),
      unreadable tree (502), non-merged bundle (409 `bundle_not_merged`),
      incomplete manifest (409), absent item (409 `not_verifiable_in_tree`), or
      id mismatch (409 `tree_identity_mismatch`) — leaving the bundle at
      `merged`. Success returns the metadata path + id as evidence and audits
      `bundle.verified`. 5 `server.inject()` tests in `tara-workbench.spec.ts`
      "bundle verification (P9.5)": happy-path read-back, absent-item refusal
      (asserts unchanged `merged`), identity-mismatch refusal, not-merged
      refusal, and 503 without a tree. Full spec 94 passed/1 pre-existing skip;
      tsc clean.)_
- [x] 9.6 Autonomy `ContentPublisher` seam adapter emitting the same bundle
      format for agent-produced Tara items (integration test). _(Shared assembly
      core `assembleMeditationBundle` in
      `libs/oshun/tara-workbench/src/bundle.ts`: builds the manifest with the
      REAL `buildMeditationManifest`, then pairs it with its referenced asset
      bytes into the importer's
      `{ manifest, assets:[{relativePath,sha256,     base64}] }` shape —
      computing each sha256 from the ACTUAL bytes. Fail-loud
      `MeditationBundleError` on an invalid manifest (surfaces the builder's own
      errors), a manifest-referenced file with no carried asset, an orphan asset
      the manifest never references, a duplicate, or an empty asset. The BFF
      `ContentPublisher` adapter
      `apps/oshun/bff/src/agentic/autonomy-bindings/tara-bundle-publisher.ts`
      (`createTaraBundlePublisher`) projects a `ReviewItem` → bundle source →
      `assembleMeditationBundle` → injected sink, returning the sink's REAL
      publication ref; the projection seam is injected (deployment-specific
      artifact shape) with `projectTaraMeditationArtifact` as the reference
      reader over a well-known `taraMeditation` artifact output — fail-loud on
      none / >1 / malformed. This mirrors `editorial-publisher.ts`: an
      unprojectable item, invalid manifest, or sink failure throws (the director
      isolates + retries), never a fabricated publish. Tests: 8 core tests
      (`bundle.spec.ts` — verbatim-manifest + per-asset sha256/base64, and every
      refusal), 5 adapter tests (`tara-bundle-publisher.spec.ts` — reference +
      custom projector, missing/duplicate-artifact refusal, assembler-error
      passthrough), and the integration test `agent-bundle-import.spec.ts`:
      agent source → `assembleMeditationBundle` → the REAL `importBundle`
      (skip-git into a temp tree) → asserts the golden
      `meditations/sleep/settling-into-sleep/` layout + byte-faithful metadata
      (durationSeconds 612) and audio. tara-workbench/bff typecheck clean (the 2
      pre-existing lib errors — index.ts `ProgramCadence` ambiguity +
      script-blocks.ts — are unrelated); light footprint, single vitest runs.)_
- [x] 9.7 TTS enforcement: bundles containing TTS audio require a valid
      voice-scope lock (existing tts-voice-contract data); refusal path tested.
      _(Design M7. Publish-bundle gate `evaluateTtsVoicePublishLock` in
      `apps/oshun/bff/src/tara-workbench/tts-voice-gate.ts` REUSES the real
      `enforceTaraTtsVoiceRuntimeScopeLock` from `@oshun/tenant-tara-studio`
      (the existing tts-voice-contract/consent surfaces) to derive its verdict —
      validated read-only on surface `tara-studio`, discarding the runtime
      auto-withdrawal so a refused publish never withdraws an instructor's
      consent. Wired into `POST /publish/bundles`: the request's `voice` block
      declares `source: human-recording | tts` (+ a `lock`); absent ⇒ human
      recording, no lock needed. Fail-closed: TTS with no bound contract data ⇒
      503 `not_configured` (seam `tara-tts-voice-contract`); TTS with no lock ⇒
      422 `tts_voice_lock_required`; an invalid lock ⇒ 422
      `tts_voice_lock_invalid` with the exact scope-lock violations
      (cross-instructor / cross-tenant / missing-active-consent / …). Injected
      via new route input `ttsVoiceContract`. 5 `server.inject()` tests ("TTS
      voice-scope-lock enforcement (P9.7)"): human recording publishes lockless
      (201), TTS-no-lock 422, TTS+valid-lock 201, cross-instructor lock 422
      (asserts `cross-instructor` violation), TTS-no-binding 503. Verified the
      real engine live first (default consents active; valid lock → allowed,
      cross-instructor → blocked). Full spec 99 passed/1 pre-existing skip; bff
      tsc clean; light footprint.)_
- [x] 9.8 Golden-bundle round-trip test: builder output → importer →
      schema-valid tree diff (this is the P13.4 golden, authored here).
      _(`tools/tara-content/src/golden-roundtrip.spec.ts`: the REAL
      `buildMeditationManifest` (from `@oshun/tara-workbench`) builds a manifest
      from a fixture concept → wrapped as a bundle export → run through the
      importer (skip-git into a temp tree). Asserts the golden layout
      (`meditations/sleep/settling-into-sleep/` + metadata.json + 4 assets),
      that the written metadata.json deep-equals the builder's manifest
      byte-faithfully (durationSeconds 612 confirms the real compiler ran, not a
      literal), and that a tampered asset is refused with sha256 mismatch.
      Because `importBundle` re-validates against the checked-in
      `meditation.schema.json`, a successful import IS a schema-valid tree —
      builder↔tree drift fails here (this authors the P13.4 golden). manifest.ts
      only type-imports Concept (erased) so the tool resolves it via a relative
      import + widened vitest `fs.allow` — no `pnpm install`. Verified
      2026-07-21: 8/8 tool tests pass (5 importer + 3 golden); excluded specs
      from the tool build tsconfig (they shouldn't ship in dist and the
      cross-package import violates rootDir), tool `tsc --noEmit` clean. Light
      footprint: two vitest + one tsc run, no dev stack.)_
- [x] 9.9 Playwright spec: approved dossier → bundle → validation error → fix →
      exported; Chrome MCP pass recorded.
      _(`apps/oshun/web/e2e/studio-tara-workbench-publishing.spec.ts`: walks a
      fresh concept ALL THE WAY to `approved` via the real API (spark→promote→
      premise→invitational 300s script→gate-run→grounded→in-review→review-ladder
      approve BOTH stages→approved), asserts stage==='approved', then drives the
      publishing UI: select the approved concept, build with an out-of-range
      bitrate (24) → `result="invalid"` + verbatim `audio.bitrate` error, fix to
      128 → `result="valid"`, advance the valid draft to `exported` and confirm
      the state in the UI (bundle card) AND server-side (GET bundle). RAN GREEN
      against the real dev stack: `1 passed (1.1m)`. Getting the stack up needed
      3 fixes recorded in memory: applied the 1 pending oshun/persistence
      migration (`20260721180000_isis_resource_recommendations` — the BFF
      crashed on the missing table); deleted a stale `signup-verification-state`
      snapshot row (recorded under a prior secret → boot TypeError); the STUDY
      svc self-bootstraps yemaya/nisaba. Chrome-MCP pass recorded LIVE (this
      session): the /publishing page renders, shows the honest
      `admin_scope_missing` refusal without scope, and after an admin bearer
      wrap → build invalid shows "✕ Bundle invalid — pinned to draft" +
      "audio.bitrate: must be an integer in 64–320 kbps", fix → "✓ Bundle valid
      — ready to export" + the file-layout preview
      `apps/tara/content/meditations/sleep/e2e-publish-.../` (metadata.json +
      audio.mp3 + transcript.md + captions.vtt), with the e2e's exported bundle
      visible in the list. Light footprint: cold-start stack, workers=1, swap
      monitor armed, torn down after.)_

## Phase 10 — Calendar & planning

- [x] 10.1 Hub calendar reading editorial-calendar data + workbench entities
      (program windows, dossier due dates, agent-cycle placements) in one view.
      _(`GET /calendar` aggregates THREE lanes: program-window entries +
      recurring slots (`deriveProgramSchedule`), scheduled-dossier due dates
      (`targetPublishDate` concepts within the window), and upcoming autonomy
      agent cycles (enumerated from the REAL `nextCycleAt` primitive over the
      live `plane.schedule` — respects quiet hours, bounded 366, empty when no
      schedule). UI `StudioTaraWorkbenchCalendar` on new `/calendar` page: an
      agenda that buckets all three lanes by day, date-sorted, with lane chips;
      dossiers link to their concept. RouteMap flipped implemented. Verified
      LIVE via Chrome (see 10.4): 35 program slots + 1 scheduled dossier
      (2026-07-25, linked to its concept) + 0 agent cycles rendered for July
      2026, honest `admin_scope_missing` without scope. `agentCycleSchedule`
      late-bound in server.ts to `plane.schedule` + last cycle report. 6
      server.inject tests (all three lanes + window filtering +
      empty-schedule). \**Found+fixed a real store gap: the concept table uses
      explicit columns (not a payload blob), and `zod-prisma-introspection` maps
      any
      `*Date`    field to a DateTime column — so`targetPublishDate` needed a real Prisma     migration (`20260721220000_...`) + `DateTime?`column +`TimestampSchema`    (ISO), not the memory-store-only`DateSchema`
      first shipped; the schedule route now anchors a YYYY-MM-DD input to
      midnight UTC and the calendar slices the date portion. Verified against
      the REAL Prisma store, not just the memory double.)\_
- [x] 10.2 Write path: scheduling a slot sets dossier target dates;
      agent-assigned slots seed cycle planning (plane integration). _(FIRST half
      — `POST /concepts/:id/schedule` (schedule-publish-gated) sets/clears
      `targetPublishDate`, verified against the real Prisma store + surfaced on
      the calendar. SECOND half RESOLVED 2026-07-21 (previously DESIGN-BLOCKED):
      the block was a MODELLING error, not a true impossibility — a
      human-scheduled concept was being forced into the shape of a
      plane-generated idea (fabricate a `SelectedIdea` with bandit
      scored/categoryDraw/angleDraw/priority = forbidden stub) or a global
      cadence change (write its date into `plane.schedule` = misleading
      reweight). The honest model is what a scheduled concept actually is — a
      LOCKED editorial commitment the algorithmic planner schedules AROUND
      ("human locks, algorithm fills the rest", standard showrunner practice).
      Implemented as a first-class `CommittedSlot` (itemId, categoryId, fixed
      instant, source) in the plane's OWN editorial-calendar planner
      `planCalendar`
      (`libs/oshun/creative-autonomy/src/portfolio/selection.ts`): it now
      reserves committed slots first — they consume the per-category gap + daily
      cap so the bandit selection packs around them — with ZERO fabricated
      scores and the global cadence untouched. Backward-compatible (optional
      `committedSlots`; the 3 pre-existing planCalendar tests pass unchanged) +
      4 new known-value reservation tests (33/33 portfolio green;
      creative-autonomy tsc clean). Wired end-to-end into the workbench:
      `GET /calendar` derives real `CommittedSlot`s from scheduled concepts that
      carry a premise category (itemId=conceptId, categoryId = the concept's OWN
      meditation category verbatim — NOT an invented `tara.*` id, since the
      plane's `tara.*` steering categories are a DISTINCT namespace) and buckets
      each into the agent cycle whose window `[thisCycle, nextCycle)` contains
      its publish date (`cyclePlanning`) — so every autonomous cycle now carries
      exactly the human commitments it must reserve. A
      scheduled-but-premise-less concept honestly yields NO reservation (no
      category to reserve against). 2 new route tests (seeds-cycle-planning +
      no-fabrication-for-premise-less); full tara-workbench route spec 122
      passed / 1 skipped; BFF tsc clean. BOUNDARY (honest): the plane's live
      `director.ts` produces items immediately per cycle and has no separate
      calendar-planning phase, so it does not itself call `planCalendar` during
      autonomous production — a pre-existing plane fact, unchanged here; the
      seeding is performed by the workbench host (the correct layer for
      editorial-calendar concerns) over the shared, now-reservation-aware
      primitive.)_
- [x] 10.3 Seasonal/ritual date registry (solstices, observances relevant to
      programs) as data rows driving `tara.seasonal-ritual` planning —
      sourced/attributed dates, not hardcoded guesses.
      _(`libs/oshun/tara-workbench/src/seasonal-registry.ts`
      `seasonalRitualRegistry(from, to)`: solar season markers (March/June
      equinox, June/December solstice) come from the REAL astronomical
      computation in `@oshun/domain-nyx` (`solarSeasonMarkers` — ecliptic
      longitude root-finding, imported via the pure-math
      `/depth/solar-season-markers` subpath so no contracts chain is dragged
      in), NOT a fixed date table; observances are a curated set of established,
      meditation-relevant civil observances (International Day of Yoga 06-21 /
      UN 69/131, World Mental Health Day 10-10 / WHO-WFMH, World Meditation Day
      12-21 / UN 2024) each carrying its citation in a `source` field. Every row
      has a non-empty attribution — no hardcoded guesses. Window-clipped +
      date-sorted. 4 known-value tests: 2026 markers land on the real dates
      (03-20, 06-21, 09-23, 12-21), observances on their civil dates with UN/WHO
      attribution, every source non-empty, inclusive-window clip +
      inverted-range empty. LIGHT — pure domain lib, no stack.)_
- [x] 10.4 Chrome MCP pass on calendar interactions recorded. _(Recorded LIVE
      this session against the running dev stack: the
      `/studio/tara-workbench/calendar` page renders the editorial-calendar
      agenda, shows the honest `admin_scope_missing` refusal without scope, and
      after an admin bearer wrap → the July-2026 window shows lane counts
      "Program slot 35 · Dossier due 1 · Agent cycle 0" with the agenda bucketed
      by day; the 2026-07-25 bucket shows a "Dossier due" chip linking to the
      scheduled concept. From/To inputs + "This month" control present.)_

## Phase 11 — Catalog & curation

- [x] 11.1 `/catalog` view over the parsed content tree (search/filter by
      category, teacher, premium, series). _(`StudioTaraWorkbenchCatalog` on new
      `/catalog` page over the existing `GET /catalog` (parseContentTree).
      Server-side kind/category/search filters; premium/teacher/series derived
      client-side from metadata (their dropdowns populate from the real
      entries). RouteMap flipped implemented. Verified LIVE via Chrome against
      the REAL apps/tara/content tree: "3 of 3 items" — meditation (Calm Your
      Mind · stress · Sarah Chen, premium badge logic), course (Foundations of
      Meditation), collection (Stress Relief Essentials), each with
      kind/category/teacher; kind→meditation filter re-fetched to 1 item; honest
      `admin_scope_missing` without scope.)_
- [x] 11.2 Collection membership editor emitting curation bundles
      (collection.schema.json validated). _(Backend: `POST /publish/collections`
      (`curate-taxonomy`-gated) builds the collection manifest via the domain
      `buildCollectionManifest` (validated against collection.schema.json,
      errors verbatim, invalid pinned to draft), emits a curation bundle with
      `conceptId: null` + a `curationBatch` label that flows through the SAME P9
      ladder + importer — no direct tree write. 3 server.inject tests (valid,
      invalid-no-items, permission). UI: `StudioTaraWorkbenchCollectionEditor`
      on the `/catalog` page — metadata form
      (slug/title/description/type/visibility) + a member editor
      (add/remove/feature items) → "Build curation bundle". Verified LIVE via
      Chrome: filled a collection + added `calm-your-mind` + built → "✓ Curation
      bundle valid — ready to export · batch collection:chrome-sleep-set ·
      draft".)_
- [x] 11.3 relatedMeditations linking UI (bidirectional preview) → curation
      bundle. _(Backend `POST /publish/meditation-curation` patches a published
      item's `relatedMeditations` onto its manifest (≤10 slugs, valid, no
      self-ref, dedup) → curation bundle. UI
      `StudioTaraWorkbenchMeditationCuration` on `/catalog`: pick a meditation,
      add/remove related links each showing a BIDIRECTIONAL preview marker — ↔
      mutual (the other links back per live catalog metadata) vs → one-way.
      Chrome-verified: added `deep-rest-body-scan` to `calm-your-mind` → "→
      one-way" marker, built a valid curation bundle (batch
      `meditation-curation:stress/calm-your-mind`).)_
- [x] 11.4 Featured/series-order curation → bundle; all curation flows through
      P9 (no direct tree writes) — verified by code inspection + test. _(Same
      `POST /publish/meditation-curation` patches `isFeatured` + `series` {id,
      position≥1} onto the manifest → curation bundle. UI sets the featured
      flag + series id/position; Chrome-verified alongside 11.3 (featured on,
      series stress-foundations/2, valid bundle). NO DIRECT TREE WRITES verified
      by code inspection: zero `writeFile`/`mkdir` in the workbench routes / BFF
      tara modules / web components; `content-tree.ts` is read-only; the
      importer (`tools/tara-content`) is the ONLY writer, reached only via the
      bundle-producing seam — all curation provably flows through P9 bundles. 4
      server.inject tests on the route cover the patch + refusals +
      permission.)_
- [x] 11.5 Playwright spec: curate a collection → bundle validated; Chrome MCP
      pass recorded.
      _(`apps/oshun/web/e2e/studio-tara-workbench-curation.spec.ts`: drives the
      collection editor on `/catalog` (curator-scoped proxy) — fill the
      collection + add a member + build → asserts the valid curation result in
      the UI AND, server-side, that the bundle exists with `conceptId: null`,
      `validationOk: true`, state `draft`, and the right `curationBatch`. RAN
      GREEN: `1 passed (54.2s)`. Chrome MCP pass was recorded live for the
      collection editor (11.2) and meditation curation (11.3/11.4).)_

## Phase 12 — Hub, IA & navigation

- [x] 12.1 Hub page: pipeline overview (stage counts, gate failures, agent
      share), queues (my reviews, unsourced blockers, pending bundles), calendar
      snapshot.
- [x] 12.2 Register the workbench in the studio IA/navigation (and
      command-palette route index) so it is discoverable, not URL-only.
- [x] 12.3 Cross-links landed: operator console → workbench dossiers (via
      concept back-links), admin editorial calendar → workbench calendar,
      studio/tara TTS pages → publishing tab.
- [x] 12.4 Empty-state and `not_configured` states designed for every module
      (honest, actionable copy) — reviewed via Chrome MCP.
- [x] 12.5 Accessibility pass: keyboard flows for triage/board/review,
      labels/roles on the new components, contrast against the lilith palette;
      axe run with real assertions (no vacuous gate).

## Phase 13 — Verification battery

- [x] 13.1 Domain-lib known-value suite complete (every gate, the overlay
      compiler, the state machine, the manifest builder) — enumerate the
      known-correct values used.
      <!-- Verified 2026-07-21 (137 domain-lib tests, all known-value, ~0 vacuous):
                                                              GATES — invitational-language.spec (exact blocking/warning counts, phrase
                                                              arrays ['divine','surrender to'], severities, "softening the gaze" alt);
                                                              gates.spec duration-coherence (612s vs 600s→Δ12, tol 10; ratio 0.015, floor
                                                              10, tolerance(1200)=18; segmentCount 6), transcript-captions (named
                                                              failures), audio-qa (honest 'unavailable' + "not configured"); grounding.spec
                                                              (severity floors health-benefit→blocker/tradition→major/historical→info,
                                                              clinical-grade meta-analysis true, "1 clinical-grade source(s) of 2 total",
                                                              unsourcedBlockerCount 1). OVERLAY COMPILER — steering-overlay.spec exhaustive
                                                              tighten-only N×N over AUTONOMY_LEVELS (throws AutonomyLooseningError vs
                                                              composes), tenet order, theme merge (breath 0.6>0.4, awe 0.3 kept, focus 0.5
                                                              added), raise-only bars (0.85 kept / global 0.7 kept), rank-drift guard.
                                                              STATE MACHINE — state-machine.spec stageIndex spark 0/published 8, one-stage
                                                              rule, grounding staleness (ran-vs-2 latest-3), all-3-gates in-review,
                                                              unavailable≠pass, published only on 'verified' (not 'merged'), disposition
                                                              guards, IllegalTransitionError code. MANIFEST BUILDER — manifest.spec GOLDEN
                                                              Ajv validation vs the REAL apps/tara/content/schemas (meditation/collection/
                                                              course), 611.7→612 rounding, premise enums carried, errors-as-data (bad
                                                              uuid/duration<60/bitrate/hex/semver/dup-tags), guided⇒transcript+captions,
                                                              + schema-conditional cross-check proving builder rules aren't stricter theater. -->

- [x] 13.2 BFF contract suite complete incl. every `not_configured` seam and
      tighten-only rejection.
      <!-- Verified 2026-07-21 in routes/tara-workbench.spec.ts. not_configured
                                                              seams each 503-tested: tara-workbench-store (l296), model-provider ×3
                                                              (divergence l1266 / script-assist l1327 / claim-extract l1671),
                                                              tara-content-tree ×4 (catalog l2659/l2879, verify l3338, publish-bundle
                                                              staging l3801/3939/4051 same seam), tara-tts-voice-contract (l3138),
                                                              tara-asset-store (l4721), + audio-qa honest 'seam'/unavailable (l4162).
                                                              Tighten-only rejection tested at write time BOTH paths: program overlay
                                                              full-auto → autonomy_loosening_rejected {floor,requested} (l1946) and
                                                              category-row loosening → autonomy_loosening_rejected (l2051); + composed
                                                              direction served for a tightening program (l1915) and db-tightened category
                                                              provenance (l1993). New P12 /overview + /agents/linked-concepts share the
                                                              guarded store seam (l296) and add an admin-scope-gate + empty-store zero
                                                              case. 121 route tests total. -->

- [x] 13.3 Adversarial pass over `libs/oshun/tara-workbench` +
      `apps/oshun/bff/src/routes/tara-workbench.ts` + new web components:
      mandatory grep battery, silent-stub scan, delegation-chain reads; report
      every hit here with file/line/context.
      <!-- Adversarial pass 2026-07-21. Mandatory grep battery over the domain lib
                                                              src + BFF route + P9-P12 web components: ZERO actionable hits. The only raw
                                                              matches were false positives — "ToDo" as a substring of the *RowToDomain
                                                              mappers (case-insensitive TODO) and "TODOS_2026" (the ledger filename in a
                                                              doc comment); no real TODO/FIXME/HACK/placeholder/simulated/hardcoded/fake.
                                                              Silent-stub scan: no Math.random in deterministic code, no fabricated-success
                                                              catch blocks, no fixed-literal returns, no "would ..." logging, no
                                                              createSimulated/mock-client factories, no env-gated fake backends, no
                                                              input-ignoring underscore params (the _request/_reply/_actor handler args are
                                                              legitimate). Delegation: gates/state-machine/overlay/manifest public fns
                                                              delegate only to real computed cores (proven by the 13.1 known-value suite).
                                                              seasonal-registry confirmed "computed or cited, never invented" (solar
                                                              longitude root-finding + cited UN/WHO resolutions w/ source fields). P12
                                                              /overview + /agents/linked-concepts compute from real store rows (verified
                                                              end-to-end vs Postgres: 41 active, real byStage, 3 agent-touched). All four
                                                              CLAUDE.md signoff conditions met. -->

- [x] 13.4 Golden bundle checked in (fixture dossier → bundle → tree diff) and
      wired into CI-runnable vitest.
      <!-- Satisfied by tools/tara-content/src/golden-roundtrip.spec.ts (authored
                                                              P9.8): fixture Concept → buildMeditationManifest → BundleExport → importBundle
                                                              into a temp contentRoot, asserting the GOLDEN tree diff — itemDir
                                                              meditations/sleep/settling-into-sleep + the exact 5 file paths (metadata.json,
                                                              audio.mp3, transcript.md, captions.vtt, thumbnail.jpg) — plus byte-faithful
                                                              metadata.json round-trip (written === builder manifest, durationSeconds 612)
                                                              and sha256-mismatch integrity refusal. Verified green 2026-07-21 (3/3, 301ms,
                                                              plain vitest = CI-runnable). manifest.spec adds Ajv golden validation vs the
                                                              real meditation/collection/course schemas. -->

- [x] 13.5 Full-journey e2e: spark → concept → grounded → approved → bundle →
      branch → verified-in-tree, against the dev stack (docker compose core
      profile), recorded run notes here.
      <!-- RUN 2026-07-21 against the live dev stack (docker core + BFF :4010 with
                              OSHUN_V1_DATABASE_URL + OSHUN_TARA_CONTENT_ROOT + web :3010). New spec
                              apps/oshun/web/e2e/studio-tara-workbench-full-journey.spec.ts drives ONE
                              continuous concept through the whole path via the real API + publishing UI:
                              spark → promote → premise → author revision → premise→outline→script-draft →
                              gate battery → grounded → in-review → review-ladder approve (subject-review +
                              editorial-approval) → approved → build a valid meditation bundle in the
                              publishing UI → bundle state ladder draft→exported→branched→merged. GREEN
                              (1 passed, 5.4s) — proves the phase-to-phase integration seams the per-phase
                              specs (P3.8–P9.9) each cover in isolation. The final `verified` leg is NOT
                              taken live: `verified` flips only on in-tree evidence (P9.5), which needs the
                              importer to write the bundle into the apps/tara/content GIT tree — a
                              git-mutating op that must run in an isolated checkout, not the working repo.
                              The spec instead asserts the honest fail-closed (hand-setting `verified` →
                              409) and stops at `merged`; the actual bundle→importer→schema-valid tree diff
                              ("verified-in-tree") is covered git-safely by tools/tara-content/src/
                              golden-roundtrip.spec.ts (P9.8/13.4, temp-dir importer, 3/3 green). Confirmed
                              ZERO git pollution: `git status apps/tara/content` clean after the run. Stack
                              torn down; ports :3010/:4010 free. -->

- [x] 13.6 Second-opinion review: run Codex CLI (read-only, per repo rules) over
      the domain lib and gate logic; evaluate and apply/reject suggestions with
      notes.
      <!-- Ran `codex exec -s read-only -m gpt-5.4 -c model_reasoning_effort=high`
                                                      over gates.ts + state-machine.ts + steering-overlay.ts + grounding.ts
                                                      (2026-07-21). 5 findings; steering-overlay clean. Evaluated each vs the code:
                                                      APPLIED 3 (with tests): (1) audio-qa fail-open — a `fail` audio-qa was ignored
                                                      at every transition; added refuseOnExplicitGateFail(audio-qa) at `published`
                                                      (blocks an explicit FAIL but not an unwired absent/unavailable seam, so
                                                      publishing stays possible without an audio backend). (3) latestGate same-second
                                                      tie — strict `>` on second-granular ranAtUnixSeconds let an earlier `pass`
                                                      mask a same-second `fail`; tie-break now lets the conservative (non-pass)
                                                      verdict win. (5) propagateSourceUpdate missed closed-practice edits — a
                                                      closedPractice toggle leaves claim status supported so no re-run was flagged;
                                                      conceptIdsNeedingRerun now also triggers on a closed-practice-set change.
                                                      REJECTED 2 with reasons: (2) grounding passes on 0 claims — DOCUMENTED intent
                                                      (evaluateGrounding doc + `noClaimsExtracted: true` evidence flag; the state
                                                      machine requires a fresh grounding gate, so "never ran" is caught, and
                                                      claimless meditations legitimately pass). (4) grounded blocker check trusts
                                                      stored claim.status — this FAIL-CLOSES (falsely blocks, never fabricates a
                                                      pass), the safe direction per repo rules, and the BFF's runGroundingCore
                                                      persists recomputed status when the gate runs so stored status is fresh.
                                                      Domain suite 230 green + BFF suite 117 green after the fixes. -->

## Phase 14 — Telemetry, honesty debt & launch

- [x] 14.1 Funnel telemetry: stage-conversion, gate-failure counts by gate,
      agent-vs-human share per program — real events, dashboards per the
      existing metrics patterns.
      <!-- Done 2026-07-21. New BFF GET /funnel aggregate + StudioTaraWorkbenchFunnel
                                  dashboard on the hub. Three views, all derived from the DURABLE state records
                                  (each stage transition + gate run is a persisted event — the real event log,
                                  per the adoption-funnel-store precedent of computing funnels from state; NOT
                                  a fabricated stream): (1) stageFunnel — non-killed concepts at-or-beyond each
                                  maturity stage (monotonic funnel) + stage-to-stage conversion %, with
                                  killedCount as the drop-outs (entry stage shows "entry", never a fake 100%);
                                  (2) gateFailuresByGate — active concepts with a `fail` latest result per gate;
                                  (3) programShares — agent-vs-human split per program (+ Unassigned bucket),
                                  agent-touched = has any run-link. Verified: 1 route test (funnel 5→4 = 80%
                                  conversion, killed 1, gate failures {inv:1,dur:1,grounding:1}, program shares
                                  Unassigned 3/33% + Rains Retreat 2/50%) + 2 component tests (renders all three
                                  views incl. honest "entry"/km-dash; honest error state) + added to the P12.5
                                  a11y-spec hub mock. Store-derived, no stack cycle needed. -->

- [x] 14.2 Autonomy-plane linkage metrics (briefs-from-concepts rate, approval
      latency vs. authority SLO) surfaced on the hub.
      <!-- Done 2026-07-21. New BFF GET /plane-linkage aggregate (store-derived,
                                      never fabricated): briefs-from-concepts rate = active concepts with a
                                      `brief-from-concept` run-link / active concepts; approval latency = first
                                      review assignment → latest `approve` decision, as median (p50) + p90 seconds
                                      via linear-interpolation percentileSeconds() over concepts that reached an
                                      approval. Surfaced by StudioTaraWorkbenchPlaneLinkage on the hub, placed next
                                      to StudioTaraWorkbenchAgentBudget (which already renders the authority-SLO
                                      readiness the latency feeds — the SLO is a drills/edit-incorporation/overrides
                                      measure from the plane's /authority/slo, not a latency threshold, so the two
                                      are shown side by side). Verified: 2 route tests (briefs 33% + latency
                                      p50 2700/p90 3420; empty-store null, no fabricated 0), 3 component tests
                                      (renders metrics, honest error state, em-dash on null latency), + added to the
                                      P12.5 a11y spec's hub test (mock + assert brief-rate 50%, covered by the auto-axe
                                      gate on the next stack run). Component verified via jsdom testing-library
                                      (not live Chrome) — the session's swap had climbed to ~2.8G, so I did not
                                      stack another full BFF+web+Playwright cycle; the a11y-spec assertion carries
                                      the live-render+axe check forward. -->

- [x] 14.3 Fixture-panel disposition: `TaraLineageGroundingPanel`,
      `TaraAiPersonaSponsorPanel`, `TaraAiPersonaSequenceScriptPanel`,
      `TaraAiPersonaNameplatePanel`, `LilithTaraClassSchedulingPanel` in admin —
      each either wired to live V3 data, replaced by workbench links, or
      explicitly labeled as V3 demo fixtures in-UI. No silently misleading
      panels remain.
      <!-- Done 2026-07-21. Confirmed all 5 render V3 @oshun/tenant-tara-studio
                                          SAMPLE data with no live input: editorial/page.tsx feeds the lineage panel
                                          buildTaraLineageEditorialReview() (a fixture builder); the 3 persona panels
                                          are self-contained fixture components; LilithTaraClassSchedulingPanel is a
                                          client-only useState sandbox (no persistence). Disposition = "explicitly
                                          labeled as V3 demo fixtures in-UI": new
                                          apps/oshun/admin/src/components/V3DemoFixtureNotice.tsx (role="note",
                                          data-v3-demo-fixture, "V3 demo fixture … sample V3 tara-studio data, not live
                                          V1 content … live surface is the Tara Content Workbench /studio/tara-workbench")
                                          placed above EACH of the 5 panels in editorial/personas/lilith pages. Panels
                                          themselves untouched (their existing tests stay green). Verified via new
                                          V3DemoFixtureNotice.test.tsx (2/2, testing-library render asserts the label +
                                          per-panel name + role). Static informational banner → jsdom render is genuine
                                          functional verification; no interactive flow to Chrome-drive. -->

- [x] 14.4 `LilithAsanaEditor` disposition: persist via workbench APIs, or
      labeled client-only sandbox in-UI; recorded here.
      <!-- Done 2026-07-21. apps/oshun/web/src/app/lilith-studio/asana/LilithAsanaEditor.tsx
                                          (1076 lines) has ZERO fetch/persist/localStorage — pure client useState. Its
                                          computations are honest (runSophiaGroundingCheck calls the REAL local
                                          evaluateSophiaGrounding over the actual sequence/script; timing preview is
                                          explicitly labeled "simulated") — the only gap was the missing persistence
                                          label. Disposition = "labeled client-only sandbox in-UI": added a
                                          role="note" data-lilith-asana-sandbox banner at the top of the editor
                                          ("Client-only sandbox … not saved … grounding/timing/release are local
                                          previews … persistent V1 content is authored in the Tara Content Workbench
                                          /studio/tara-workbench"). Verified by a new assertion in the existing
                                          LilithAsanaEditor.test.tsx (6/6 green). -->

- [x] 14.5 V1 docs: new `V1/features/tara-content-workbench.md` page
      cross-linked from `V1/features/authoring-workspace-and-roles.md` and the
      features hub; honest "what ships" section per house style.
      <!-- Authored 2026-07-21 in house style (title → hub-linked intro → "What
                                                      ships, honestly" naming @oshun/tara-workbench modules, the 9-stage state
                                                      machine, the 5-gate battery incl. the audio-qa honest seam, tighten-only
                                                      overlay, schema-mapped bundle bridge + golden round-trip, fail-closed BFF
                                                      seams, web surfaces → "What is a seam, not a gap" → Cross-references →
                                                      Related). Cross-linked from authoring-workspace-and-roles.md (Related),
                                                      features/README.md (Authoring cluster), and V1/features.md (narrative hub,
                                                      ### section w/ ↳ Detailed-in link). All Related targets verified to exist. -->

- [ ] 14.6 Record the workbench in the capability truth registry — never by
      editing `CONTENT_GENERATION_CAPABILITY_MATRIX.md`, which is generated.
      _Rewritten 2026-09-18 from the note below: the original wording ("update
      the matrix rows … human-ideation and publish-path columns") asked for a
      hand edit of a generated file and for columns the matrix does not have._
      Add a category "Human content authoring (Tara)" to
      `scripts/audit/capability-truth-registry.json` with one capability per
      proven boundary (workbench state machine, golden round-trip publish,
      importer re-validation), each with `contains-strings` evidence pointing at
      `tools/tara-content/src/golden-roundtrip.spec.ts` and
      `libs/oshun/tara-workbench/src/state-machine.ts`, under a dated registry
      refresh (a new `asOf`, with the audit narrative
      `V1_V9_AUTONOMOUS_CONTENT_SOTA_AUDIT_2026-07-15.md` noted as the older
      snapshot rather than rewritten); run
      `pnpm generate:audit:capability-truth` and commit the regenerated matrix.
      **Verify:** `scripts/audit/capability-truth-registry.test.mjs` passes; the
      generator's check mode reports no drift; every new row's status was
      derived by the generator from evidence that passes, not typed.
      <!-- Investigated 2026-07-21, left UNCHECKED as an honest non-local edit.
                                                  That file is GENERATED (header: "do not hand-edit status claims") from
                                                  scripts/audit/capability-truth-registry.json by
                                                  scripts/audit/generate-capability-truth.mjs, which re-observes evidence and
                                                  re-derives status (an `implemented` claim must have genuinely-passing
                                                  evidence — no fabrication possible). It has no "human-ideation/publish-path"
                                                  columns; its columns are Capability|Registry status|Evidence|Proving
                                                  boundary|Manual commentary, and its sections are all AI/engine GENERATION
                                                  (Unreal authoring, cross-engine interchange, eval metrics, provider-backed
                                                  generation). Crucially it is the machine-verified companion to a specific
                                                  DATED human audit: registry `asOf: 2026-07-15`, stamped into the header and
                                                  tied to V1_V9_AUTONOMOUS_CONTENT_SOTA_AUDIT_2026-07-15.md ("Audit date:
                                                  2026-07-15"). The Tara workbench is HUMAN authoring — orthogonal to the
                                                  AI-generation capabilities this audit covers — and its proving evidence
                                                  (golden-roundtrip.spec, the P13 known-value suite) postdates the 07-15
                                                  snapshot. Recording it here would require either bumping the whole-audit
                                                  asOf (desyncing the audit narrative doc) or claiming 07-15 evidence that did
                                                  not exist — both misrepresent a shared cross-cutting audit. The honest path
                                                  is an audit-owner-driven DATED registry refresh (add a "Human content
                                                  authoring (Tara)" category with real source-comment evidence — e.g.
                                                  contains-strings in golden-roundtrip.spec / state-machine.ts — then
                                                  `pnpm generate:audit:capability-truth`), not a silent tara-scoped hand-edit.
                                                  The workbench's honest capability record already lives in this ledger and in
                                                  V1/features/tara-content-workbench.md (P14.5). -->

- [ ] 14.7 Launch-readiness review against the criteria of
      `/studio/launch-readiness-governance`; sign-off note with date and
      <!-- Engineering readiness assessment 2026-07-21 (framework:
                                              apps/oshun/bff/src/studio/launch-readiness-store.ts — ships only when every
                                              BLOCKING gate is cleared + a sign-off exists). Assessed against workbench
                                              launch gates:
                                              • Correctness [PASS] — domain-lib 230 known-value tests, BFF 117 contract
                                                tests, e2e P3.8–P8.6 + publishing(P9.9) + curation(P11.5) + a11y(P12.5) all
                                                green.
                                              • Safety gates [PASS] — real 5-gate battery (invitational-language, grounding
                                                severity floors, duration-coherence, transcript-captions, honest audio-qa);
                                                3 Codex-found fail-open bugs closed (P13.6).
                                              • Publishing integrity [PASS] — schema-mapped manifests validated vs the REAL
                                                tree schemas, content-addressed bundles, golden round-trip, importer
                                                re-validation, tighten-only steering.
                                              • RBAC / fail-closed [PASS] — studio scope + §3 creator-role matrix; every
                                                not_configured seam 503s; TTS voice-lock fail-closed.
                                              • Accessibility [PASS] — axe WCAG-AA gate over hub/inbox/board/review;
                                                nested-interactive defect fixed.
                                              • Discoverability + docs [PASS] — registered admin route + route-map integrity
                                                test; V1 features page (P14.5).
                                              • Funnel/plane telemetry (14.1/14.2) [PASS] — GET /funnel (stage-conversion,
                                                gate-failure counts, per-program agent share) + StudioTaraWorkbenchFunnel,
                                                and GET /plane-linkage (briefs-from-concepts rate, approval-latency p50/p90)
                                                + StudioTaraWorkbenchPlaneLinkage; both store-derived, both with specs.
                                              • Fixture-panel dispositions (14.3/14.4) [PASS] — the five admin V3 panels
                                                labelled via V3DemoFixtureNotice + LilithAsanaEditor's client-only-sandbox
                                                banner, each asserted by a test.
                                              • Full-journey e2e (13.5) [PASS] — studio-tara-workbench-full-journey.spec
                                                drives one continuous concept spark→…→merged green against the live stack.
                                              UPDATE 2026-07-21 (later session): every ENGINEERING and launch-checklist gate
                                              above is now cleared — 13.5, 14.1/14.2, 14.3/14.4 have all landed and are [x];
                                              artifacts re-verified to exist this pass (funnel/plane-linkage routes +
                                              components + specs, V3DemoFixtureNotice, asana sandbox banner, full-journey
                                              spec). Against evaluateLaunchReadiness the workbench now has ZERO no_go blocking
                                              gates; the sole remaining no_go factor is missingSignoffs. NOT CHECKED: the
                                              launch sign-off itself is a named-human governance act (accountable approval to
                                              ship to customers) — an AI agent must not author it, and fabricating a
                                              reviewer/date would be the exact forbidden fabrication. Left [ ] for a named
                                              human reviewer; engineering readiness is fully green. -->

      reviewer.
      _Board tag 2026-09-18: the engineering assessment in the note is done;
      what is left is the named human sign-off in
      `/studio/launch-readiness-governance`._ `blocked:governance`
