# Oshun Studio — Authoring, Editorial, Curation

Oshun Studio is the operator- and creator-facing surface where every authored
artifact — Veritas stories, Nisaba editions, Metis lessons, Tara passages,
ritual scripts, persona/voice packs, and Living Scene scores — is drafted,
reviewed, curated, localized, versioned, and published. It is **not** a separate
application: it ships as a subroute tree under `apps/oshun/web/src/app/studio/`
(see the `V1/TODOS.md` audit snapshot, [§16](../TODOS.md)). It is backed by the
real domain library `@oshun/studio-authoring`. Studio builds on the platform
substrates — [Sophia](./substrate-sophia.md) (grounding/evidence),
[Isis](./substrate-isis.md) (generation control),
[Lilith](./substrate-lilith.md) (policy), and [Iris](./substrate-iris.md)
(memory) — and is consumed by customer-side creators and operator-side editors
alike. Product scope lives in
[`V1/features.md` § Content Authoring, Curation, and Editorial Operations](../features.md#content-authoring-curation-and-editorial-operations);
backlog is [§16](../TODOS.md).

## Where the logic actually lives

The bulk of Studio's domain behavior is a pure-functions TypeScript library:
`@oshun/studio-authoring` (package version `0.1.0`, `private`, pure ESM, with
`main`/`types` pointing directly at `./src/index.ts`). This is a **source**
library, not a tsup-built artifact. Its
`libs/oshun/studio-authoring/src/index.ts` barrel re-exports nine subdomain
modules:

| Module (`src/<dir>/`)    | Backlog | Responsibility                                                                                                                               |
| ------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `creator-roles/`         | §16.1   | Role taxonomy, per-role permission matrix, creator profiles, onboarding/certification state machine, scorecards                              |
| `authoring-blocks/`      | §16.2   | Block model, AI-assist guardrails, evaluation/tone policy, citation density, preview parity, autosave checkpoints, drag-to-cite source panel |
| `editorial-lifecycle/`   | §16.3   | 12-state lifecycle machine, gates, calendar, embargo/hotfix flows, recurrence engine, findings→publish gate                                  |
| `asset-metadata/`        | §16.4   | Asset metadata/rights/provenance/accessibility logic, approval queue, bulk-upload, EXIF/presets, analytics                                   |
| `taxonomy-curation/`     | §16.5   | Tag/theme/lineage/concept-graph curation operations, contest status, ontology rollout, stewardship                                           |
| `localization-workflow/` | §16.6   | Translation segments, translation memory, glossary, do-not-translate, stale detection, locale QA, launch scorecards                          |
| `versioning/`            | §16.7   | Per-artifact version chain, validation, diff, rollback cascade                                                                               |
| `collaboration/`         | §16.2   | Presence/cursors, inline comments, suggestions, review sign-off, activity feeds, reconnect resync                                            |
| `templates/`             | §16.2   | Studio template kinds, payload/field validation, tenant resolution, rehearsal fixtures                                                       |

> **Honesty note on the layer split.** This library is **real, non-stub,
> domain-specific code** — but it is a _contract and logic_ layer. Every
> function is pure: timestamps, hashes, and "current" states are **inputs**, not
> read from a clock or a database. There is no persistence engine, no running
> CRDT substrate, and no object-storage client inside the library; those are
> infra concerns wired up elsewhere (and several are still aspirational — see
> [Aspirational vs. shipped](#aspirational-vs-shipped) below). The web workspace
> at `apps/oshun/web/src/components/studio/StudioAuthoringWorkspace.tsx` (824
> lines) genuinely imports and exercises the library.

## Creator and contributor roles (§16.1)

Studio defines a creator/contributor taxonomy that is **separate** from customer
and operator scopes. `STUDIO_CREATOR_ROLES` (in `creator-roles/roles.ts`)
enumerates twelve roles:

```
author · editor · curator · reviewer · sme · teacher · scholar ·
translator · illustrator · narrator · producer · publisher
```

Capabilities are governed by `STUDIO_ROLE_PERMISSIONS`, ten distinct
permissions, applied through a real `ROLE_PERMISSION_MATRIX` and the
`roleHasPermission(role, permission)` predicate. The matrix is least-privilege
by design — for example, only `publisher` can approve and unpublish, only
`translator` can translate, and only `curator` can curate taxonomy:

| Role          | Permissions                                  |
| ------------- | -------------------------------------------- |
| `author`      | edit-draft, request-review                   |
| `editor`      | edit-draft, review, request-review           |
| `curator`     | curate-taxonomy, manage-assets               |
| `reviewer`    | review                                       |
| `sme`         | review                                       |
| `teacher`     | edit-draft, review                           |
| `scholar`     | edit-draft, review                           |
| `translator`  | translate                                    |
| `illustrator` | manage-assets                                |
| `narrator`    | manage-assets                                |
| `producer`    | manage-assets, schedule-publish              |
| `publisher`   | approve-publish, schedule-publish, unpublish |

The full permission vocabulary is: `edit-draft`, `request-review`, `review`,
`approve-publish`, `schedule-publish`, `unpublish`, `translate`,
`curate-taxonomy`, `manage-assets`, `mint-certification`. (`mint-certification`
is the certification-granting permission used by the onboarding/certification
flow; it is reserved rather than handed out broadly in the base matrix.)

Profiles carry provenance and consent as first-class fields. `CreatorProfile`
records `lineage`, `credentials`, an attribution preference
(`displayName`/`handle`/`bylineVisible`), `rightsConsentRecordIds`,
`provenanceAttestationIds`, and disclosure preferences including an
`aiAssistDisclosure` choice of `always` | `on-request` | `opt-out` and a
`piiSafetyConsent` flag. `validateCreatorProfileCompleteness({ profile, role })`
enforces role-sensitive completeness: a bio of at least 24 characters and
present attribution for every role. The credentialed roles (`editor`, `curator`,
`reviewer`, `sme`, `teacher`, `scholar`, `translator`, `publisher`) must also
have non-empty `credentials`, and the lineage-attested roles (`author`,
`curator`, `sme`, `teacher`, `scholar`, `translator`, `illustrator`, `narrator`)
must have non-empty `lineage`. It returns a typed list of
`CreatorProfileMissingField` reasons rather than a bare boolean, so the UI can
show exactly what is missing.

The creator lifecycle itself is its own state set,
`CREATOR_LIFECYCLE_STATES = invited → onboarding → training → certified → sandbox → graduated → suspended → revoked`,
which the onboarding module drives with visible queues and SLA tracking.

## Authoring workspace and AI assist (§16.2)

The authoring workspace is a multi-pane editor for stories, claims, passages,
lessons, ritual scripts, Living Scene scores, and persona/voice packs.
`StudioAuthoringWorkspace.tsx` imports concrete library functions and types
straight from `@oshun/studio-authoring`:

- functions: `acceptAutosave`, `applyDragToCite`, `citationDensityByBlock`,
  `evaluateAiAssistGuardrails`, `evaluateAllPreviewSurfaces`,
  `evaluateAuthoringWithPolicy`, `evaluatePublishReadiness`
- types: `AiAssistRequest`, `AuthoringDocument`, `PreviewParityVerdict`,
  `PublishingBindings`, `SourceSnippet`, `TonePolicy`

### AI assist is governed, not free-form

Ten `AI_ASSIST_PANELS` are defined (`authoring-blocks/ai-assist.ts`):

```
research · drafting · rewriting · summarizing · citation-lookup ·
fact-checking · translation-suggest · illustration-generation ·
narration-generation · accessibility-pass
```

Crucially, every assist request carries an explicit governance binding rather
than relying on prose like "governed by Sophia/Isis/Lilith." The
`AiAssistGovernance` shape makes the governance contract concrete:

```ts
interface AiAssistGovernance {
  readonly tonePolicyId: string; // which TonePolicy applies
  readonly evidencePackId: string | null; // Sophia evidence binding (required for fact-check/citation panels)
  readonly releaseGateIds: readonly string[];
  readonly piiRedactionRequired: boolean;
  readonly attributionRequired: boolean;
}
```

`evaluateAiAssistGuardrails(request)` then _enforces_ that binding: it rejects
an unknown panel, requires an `evidencePackId` for evidence-bearing panels
(fact-checking/citation lookup), requires a `tonePolicyId`, and refuses panels
that would generate PII-bearing output when `piiRedactionRequired` is not set.
This is the seam where Studio composes with Sophia (the evidence pack), Lilith
(release gates), and Isis (generation). But the _enforcement_ is real local
logic, so a missing binding fails loud rather than silently passing.

### Evaluation, tone policy, and the publish gate

`evaluateAuthoringWithPolicy(document, config)` walks the block tree against a
`TonePolicy` (`policyId`, `bannedPhrases`, `cautionaryPhrases`,
`minReadingEase`) and emits a list of `AuthoringEvaluationFinding`s with `block`
/ `warn` / `info` severity. It flags overconfident claim language (a
`CLAIM_MARKERS` set including phrases such as "studies show", "research proves",
"guaranteed", "cure", "eliminates") that lacks an adjacent `citation` or
`evidence-pin` block, checks Flesch reading ease via `fleschReadingEase(text)`
against `minReadingEase`, and treats a banned phrase as a `block`-severity
finding. `citationDensityByBlock` returns a per-block citation count so the UI
can surface thin-evidence passages.

These findings are not merely advisory — they are _folded into the publish
gate_. `editorialGateFromAuthoringFindings(findings)` produces a required
`EditorialGate` whose `gateId` is the canonical
`AUTHORING_EVALUATION_GATE_ID = 'authoring-evaluation:no-blocking-findings'`,
marking it **unsatisfied** whenever any finding is `block`-severity. Because the
lifecycle machine consumes that gate (next section), a blocking tone-policy
violation literally refuses the transition into a published state until the
finding is resolved. `warn`/`info` findings surface in review but never gate.

## Editorial lifecycle and calendar (§16.3)

Every authored artifact moves through the **same** lifecycle regardless of type.
`EDITORIAL_LIFECYCLE_STATES` (in `editorial-lifecycle/lifecycle.ts`) is a
**twelve-state** machine:

```
idea → draft → in-review → changes-requested → approved →
scheduled → published → updated → deprecated → sunset →
archived → takedown
```

> **Reconciliation — canonical names.** Earlier prose in
> [`../ARCHITECTURE.md`](../ARCHITECTURE.md) and an older mermaid diagram showed
> a 7-state "core subset" with the terminal names `rejected` and `retracted`.
> **Those state names do not exist in the code.** The canonical enum uses
> `changes-requested` (not `rejected`) and `takedown` (not `retracted`), and
> adds `idea`, `updated`, `deprecated`, and `sunset`. `V1/features.md` already
> documents the full twelve-state set and notes the older subset was an
> intentional simplification; this page treats the code enum as authoritative.

Transitions are constrained by an explicit `TRANSITIONS` adjacency map:

| From                | Allowed next states                     |
| ------------------- | --------------------------------------- |
| `idea`              | draft                                   |
| `draft`             | in-review, archived                     |
| `in-review`         | changes-requested, approved             |
| `changes-requested` | draft, archived                         |
| `approved`          | scheduled, published, changes-requested |
| `scheduled`         | published, changes-requested            |
| `published`         | updated, deprecated, takedown           |
| `updated`           | published, deprecated                   |
| `deprecated`        | sunset, published                       |
| `sunset`            | archived                                |
| `archived`          | _(terminal)_                            |
| `takedown`          | archived                                |

```mermaid
stateDiagram-v2
    [*] --> idea
    idea --> draft
    draft --> in_review: submit
    draft --> archived: discard
    in_review --> changes_requested: needs changes
    in_review --> approved: approve
    changes_requested --> draft
    changes_requested --> archived
    approved --> scheduled: schedule
    approved --> published: publish now
    approved --> changes_requested: send back
    scheduled --> published: scheduled time
    scheduled --> changes_requested: hold
    published --> updated: hotfix
    published --> deprecated
    published --> takedown: takedown /<br/>correction
    updated --> published
    updated --> deprecated
    deprecated --> sunset
    deprecated --> published
    sunset --> archived
    takedown --> archived
    archived --> [*]

    note right of takedown
        Takedown fans out via the cascade —
        kept and shared artifacts,
        Sophia evidence packs,
        downstream notebooks and feeds.
    end note
```

Two predicates back the machine: `isEditorialTransitionAllowed(from, to)` (pure
adjacency check) and `tryEditorialAdvance({ from, to, gates })`, which returns a
typed `EditorialAdvanceResult`. The failure reasons are explicit:
`transition-not-allowed` (the edge is not in `TRANSITIONS`) or
`unsatisfied-gates` (one or more _required_ gates are not `satisfied`, with the
offending `missingGateIds` returned). This is exactly how the
authoring-evaluation gate above blocks a publish: a `block`-severity finding
makes that required gate unsatisfied, so `tryEditorialAdvance` refuses the move
into a published state.

### Calendar, embargo, and the recurrence engine

`EditorialCalendarEntry` carries scheduling and dependency context —
`deadlineUnixSeconds`, `embargoUntilUnixSeconds`, `scheduledPublishUnixSeconds`,
`scheduledUnpublishUnixSeconds`, an `assignedReviewer`, a `recurrence`, and
upstream `dependencies`.
`evaluateEditorialPublishWindow({ entry, nowUnixSeconds, dependencies })`
returns one of `embargoed`, `not-yet-scheduled`, `publish-window-not-open`,
`publish-window-closed`, or `blocked-dependencies` — so a scheduled artifact
will not auto-publish while embargoed, outside its window, or while an upstream
dependency is not yet `published`.

The **recurrence engine** (`editorial-lifecycle/recurrence.ts`) is a real
primitive that the docs previously only hinted at by example. Its module header
literally encodes the platform's recurring cadences — _daily Veritas briefings,
daily Tara passages, weekly Arete reflections, nightly Nyx highlights_ — as
`EditorialRecurrence` (`cadence: daily | weekly | monthly`, an
`anchorUnixSeconds`, and tags). `nextOccurrences(...)` computes the next N
timestamps from an anchor; `nextPublishableOccurrences(...)` filters out
embargoed occurrences. `exportEditorialCalendarAsIcal(...)` emits a real
RFC-5545-style `BEGIN:VCALENDAR … END:VCALENDAR` string (with
`PRODID:-//Oshun Studio//Editorial Calendar//EN`, per-occurrence `VEVENT`s,
`ORGANIZER`/`ATTENDEE` lines) that operator dashboards and partner calendars can
import. `buildCrossTeamCalendarView(...)` rolls entries up per domain with owner
counts and an upcoming-deadline count.

## Asset and media library (§16.4)

`asset-metadata/` implements asset **logic** — metadata, rights, provenance,
accessibility, approval, bulk-upload, EXIF/presets, and analytics — across
`metadata.ts`, `approval-queue.ts`, `bulk-upload.ts`, `exif-and-presets.ts`, and
`analytics.ts`.

> **Accuracy fix — storage backing.** Earlier docs said the asset library is
> "backed by MinIO/S3, with provenance bundles travelling with every asset." The
> _binding to object storage is not in this library_: `asset-metadata/` contains
> **no** MinIO or S3 client (`grep` for `minio`/`@aws-sdk`/`putObject` returns
> nothing). MinIO does exist as a docker-compose dev dependency at the infra
> level (`docker/docker-compose.dev.yml`, ports `:9000`/`:9001`), so storage is
> real _at infra level_. But the authoring library handles metadata and rights,
> not binary blobs. Treat blob-store ↔ metadata wiring as an integration that
> the library is designed _for_ but does not itself perform.

What the library _does_ model is rich. `STUDIO_ASSET_KINDS` spans `image`,
`audio`, `voiceover`, `video`, `avatar-rendering`, `diagram`, `illustration`,
`manuscript-scan`, `motion-clip`, `ambient-loop`, and `ritual-sound-pack`.
Rights are a first-class field via `STUDIO_ASSET_LICENSES` (`cc0`, `cc-by`,
`cc-by-sa`, `cc-by-nc`, `cc-by-nd`, `platform-commercial`, `platform-internal`,
`creator-reserved`, `public-domain`, `all-rights-reserved`).
`StudioAssetMetadata` carries a `contentHash` and optional `perceptualHash`, a
`license` with optional `rightsExpiryUnixSeconds` and `usageScopes`, and a
`StudioAssetAccessibility` block (`altText`, `transcript`, `captionTrackId`,
`audioDescriptionId`, `altTextCoveragePct`). It also carries a
`StudioAssetProvenance` bundle (`provenanceBundleId`, `generatorId`, `modelId`,
`promptHash`, `lineageIds`), an optional `StudioAssetWatermark`, `nsfwLabels`,
and an `approvalStatus` of `pending | approved | rejected | taken-down`.
`validateAssetMetadata(...)`, `findDuplicateAssets(...)` (perceptual-hash
de-duplication), and `buildAssetReplacementCascade(...)` (which artifacts must
be revalidated when an asset is swapped) round out the
metadata/rights/provenance logic.

## Taxonomy, ontology, and concept-graph curation (§16.5)

This is an operator-tier surface that promotes Nisaba concept-graph nodes/edges
and Metis knowledge-graph promotions through Sophia evaluation gates.
`taxonomy-curation/curation.ts` defines ten `TAXONOMY_OBJECT_KINDS` — `tag`,
`theme`, `lineage`, `mood`, `modality`, `topic`, `claim-cluster`,
`prerequisite-chain`, `concept-node`, `concept-edge` — and five
`TAXONOMY_CURATION_OPERATIONS`: `add`, `merge`, `split`, `deprecate`,
`reparent`.

A `TaxonomyChangeRequest` carries the `operation`, `targetKind`, `targetIds`,
optional `newParentId`/`newNodeIds`, `evidenceReferences`, a `proposedBy`, and a
`contestStatus` of `uncontested | contested | resolved` — so a curation move can
be challenged and must be resolved before it lands. `buildTaxonomyChangePreview`
shows the effect before commit; `checkTaxonomyIntegrity(...)` returns a verdict
including a `contestedCount`; and rollout is staged through
`ONTOLOGY_ROLLOUT_STAGES = preview → staged → rolled-out → rolled-back` via
`tryAdvanceOntologyRollout`/`advanceOntologyRollout`. Concept edges have their
own typed policy validation (`validateConceptEdgePolicies`), which flags
`unresolved-contest` when a contested edge has no `resolvedByCreatorId`. The
localization workspace cross-references the wider localization story in
[Content, Localization, Documentation, and Launch Readiness](./content-localization-and-launch.md).

## Localization and translation workspace (§16.6)

`localization-workflow/translation.ts` models translation as governed,
memory-assisted work rather than raw string replacement. A `TranslationSegment`
carries a `sourceHash` used for **stale detection**:
`detectStaleTranslations(...)` recomputes the current source hash and flags any
segment whose translation was authored against an out-of-date source, returning
a `TranslationStalenessVerdict`. A `TranslationMemoryEntry` carries a
fuzzy-match `score`, surfaced through `fuzzyMatchTranslationMemory(...)`
(Jaccard similarity above a `minScore` threshold). `GlossaryEntry` supports
`doNotTranslate` (protected brand/lineage terms) and a `lineageScope` so
glossary rules can be lineage-specific; `lookupGlossary(...)` resolves the
applicable entry. `evaluateLocaleQA(...)` checks segments against
`LOCALE_QA_CHECKLIST_AREAS`, and `buildLocaleLaunchScorecard(...)` produces a
per-locale readiness scorecard.

The launch locale set is defined centrally in `libs/oshun/i18n/src/index.ts`:
`OSHUN_LAUNCH_LOCALES = en-US, es-US, fr-FR, de-DE, ar, he, ja-JP, pt-BR`
(eight), with `OSHUN_DEFAULT_LAUNCH_LOCALE = en-US`. Two of those — `ar` and
`he` — are RTL, which is why the localization workspace and design system carry
RTL parity concerns. The library's stale-detection plus glossary
do-not-translate rules are precisely the machinery that keeps an eight-locale
launch in sync as source artifacts change.

## Versioning, diff, and rollback (§16.7)

Every authored artifact is version-tracked. `versioning/versioning.ts` defines
an `ArtifactVersionRecord`:

```ts
interface ArtifactVersionRecord {
  readonly artifactId: string;
  readonly version: number;
  readonly authorId: string;
  readonly authoredAtUnixSeconds: number;
  readonly changeSummary: string;
  readonly approvalState: 'pending' | 'approved' | 'rejected' | 'merged';
  readonly reviewPackageId: string | null;
  readonly priorVersion: number | null;
  readonly contentFingerprint: string;
}
```

`appendArtifactVersion(history, next)` links each record to its predecessor, and
`validateVersionChain(history)` audits the whole chain, returning typed
`VersionChainIssue`s of five kinds:

| Issue kind               | Meaning                                                 |
| ------------------------ | ------------------------------------------------------- |
| `artifact-mismatch`      | a record's `artifactId` differs from the chain's        |
| `non-monotonic-version`  | version numbers do not strictly increase                |
| `prior-version-mismatch` | `priorVersion` does not point at the actual predecessor |
| `missing-review-package` | an `approved`/`merged` record has no `reviewPackageId`  |
| `empty-change-summary`   | the `changeSummary` is blank                            |

`buildVersionDiff(...)` produces a typed `VersionDiff` whose entries are tagged
by kind (`prose`, `structured-block`, `citation`, `source-binding`, `asset`,
`metadata`, `translation`), so rollback can reason about _what_ changed and
which downstream bindings need re-evaluation. Rollback therefore respects
downstream invalidation rather than blindly reverting bytes.

## Collaboration, comments, and review threads (§16.2)

`collaboration/collaboration.ts` and `collaboration/routing.ts` model presence,
comments, suggestions, and review sign-off. `PresenceCursor` tracks who is on an
artifact. `presenceForArtifact(...)` treats a presence as stale after a window,
and `buildReconnectResyncDelta(...)` computes the catch-up diff a reconnecting
client needs, including comments newer than its last presence Lamport timestamp.
`CollabComment` states are `open | resolved | rejected`; suggestions resolve via
`resolveSuggestion(...)`. Review sign-off runs through `ReviewChecklistItem`s
and `evaluateReviewSignoff(...)`. The activity feed enumerates nine
`ACTIVITY_FEED_KINDS` — `edit`, `comment`, `citation-changed`, `asset-swapped`,
`reviewer-assigned`, `approval`, `publish-state-transition`,
`suggestion-accepted`, `suggestion-rejected`. The comment substrate is shared
with admin review queues, so editorial threads can hand off to trust-and-safety
where required (see
[Trust, Safety, and Privacy](./trust-safety-and-privacy.md)).

> **Honesty note on real-time co-editing.** The "conflict-free merge" path here
> is deliberately modest. `mergeBlockText(...)` is a **token-level three-way
> merge** that emits explicit `<<<left|||right>>>` conflict markers when both
> sides change the same span. Its own comment calls it a "minimal" merge for the
> "presence-aware live co-edit path," with richer block-level merges left to the
> §16.2 checkpoint module. There is **no confirmed running CRDT substrate** in
> this library. The collaboration module is presence/comment/merge _contract and
> logic_, and a production CRDT/presence transport is still aspirational. (The
> route surface `real-time-collaboration-substrate/` exists, but the substrate
> behind it is not verified here.)

## Templates (§16.2)

`templates/templates.ts` defines seventeen `STUDIO_TEMPLATE_KINDS`
(`ritual-script`, `meditation`, `breathwork-session`, `journaling-prompt`,
`weekly-review`, `reflection-card`, `story-brief`, `claim-card`, `source-note`,
`passage`, `edition-comparison`, `sky-event-briefing`, `lesson-plan`,
`course-outline`, `assessment`, `rubric`, `admin-review-template`).
`validateTemplatePayload(...)` and `validateTemplateBindings(...)` enforce
field-level and binding-level correctness; `resolveTemplateForTenant(...)` and
`resolveTemplateAvailability(...)` handle per-tenant resolution. The
`rehearsal.ts` fixtures (`runTemplateRehearsal`, `runAllRehearsals`,
`recommendReplacementTemplates`) let operators dry-run a template against
canonical inputs before a deprecated template is retired
(`isTemplateDeprecated(...)`).

## The Studio route tree

The `apps/oshun/web/src/app/studio/` directory contains roughly 54 subroutes —
far more than the authoring core alone. The directly relevant ones include
`authoring/`, `review-approval-workflows/`, `commenting-annotation-system/`,
`real-time-collaboration-substrate/`, `presence-cursor-systems/`,
`asset-preview-pipeline/`, `file-media-ingestion/`,
`internationalization-localization/`, `activity-change-feeds/`,
`audit-compliance-surfaces/`, `rbac-permission-policy/`, and
`accessibility-governance/`, alongside domain-specific authoring entry points
(`bellona/`, `hathor/`, `neith/`, `tara/`, `aja/`, `isis/`, `yemaya/`,
`project-obsidian/`, `concordia-workbench/`). The earlier "subroute, no separate
app" claim is therefore **verified-correct**.

## Generate → gate → author: the §3 content service handoff

Studio is the _authoring_ surface; the **agentic content pipeline** that
machine-generates candidate artifacts is a separate, deployable service:
`apps/oshun/content-service` (project `@oshun/content-service-app`). Its
`main.ts` boots `createContentHttpServer` over an Iris-routed creative _writer_
plus a calibrated three-member `JudgePanel` — `judge-strict` (temperature 0.2),
`judge-balanced` (0.4), and `judge-exploratory` (0.6). It **fails loud**
(`NotConfiguredError`) when `ANTHROPIC_API_KEY` is absent: no provider means no
generation, never fabricated output. (Ops can disable specific model ids via
`CONTENT_SERVICE_DISABLED_MODELS`, and the resolver downgrades through a
fallback chain or fails loud with `NoAvailableModelError` if the whole chain is
disabled.)

The `@oshun/content-service` router (`http-router.ts`) exposes:

| Method + path                      | Purpose                                  |
| ---------------------------------- | ---------------------------------------- |
| `POST /v1/content/briefs`          | submit a brief → gated artifact + run id |
| `GET /v1/content/runs`             | list runs                                |
| `GET /v1/content/runs/:id`         | retrieve a persisted (replayable) run    |
| `POST /v1/content/runs/:id/replay` | replay a run, reproducing the artifact   |
| `GET /v1/operator/runs`            | operator dashboard run list (filters)    |
| `GET /v1/operator/runs/:id`        | operator dashboard run detail            |

A `ContentBrief` is `{ briefId, contentType, prompt, submitter }`, where
`contentType` is typed as `ContentType` from `@oshun/content-quality-judge`, and
a run carries a `ContentRunStatus` of `completed | blocked | failed` (a
`promptSha256` of the winning prompt makes runs reproducible). The natural
handoff is: the content service _generates and gates_ a candidate, then a human
_authors and publishes_ it through `@oshun/studio-authoring`'s lifecycle. The
generated artifact still has to clear the same editorial gates, tone policy, and
Sophia evidence bindings before it can advance to `published`. The agentic
generation topology itself is described in
[Agentic AI Studio](./agentic-ai-studio.md) and
[Generation Audience Tiers](./generation-audience-tiers.md); its deployment is
honestly tracked as gated (`[~]`) on real provider credentials.

## Aspirational vs. shipped

To match the docs' existing candor, the line between implemented and
aspirational here is explicit:

- **Shipped (real, non-stub, domain-specific logic):** the 12-state editorial
  lifecycle with its `TRANSITIONS` table and gate-checking; the
  authoring-evaluation → publish gate binding; versioning with
  `validateVersionChain` and typed diffs; taxonomy add/merge/split/deprecate/
  reparent with contest status and staged rollout; localization translation
  memory, glossary, do-not-translate, and `sourceHash` stale detection; the
  recurrence engine and iCal export; role/permission matrix and profile
  completeness. The web workspace genuinely consumes the library.
- **Aspirational / not verified here:** a running CRDT/presence substrate for
  real-time co-editing (the library has presence/merge _contracts_ and a minimal
  token merge, not a confirmed live substrate); MinIO/S3-backed binary asset
  storage wired to `asset-metadata/` (the library is metadata/rights logic, with
  no storage client; MinIO is infra-level dev tooling); and database persistence
  (the library is pure functions: timestamps and states are inputs, not read
  from a clock or store).
- **Coverage caveat (completeness audit, 2026-06-22):** the
  `editorial-review-approval` walkthrough is a _partial_ spec whose end-to-end
  steps currently drive the **incident** decision panel (INC-2041) rather than
  the editorial artifact lifecycle — so the e2e coverage of the editorial flow
  is weaker than the library's depth would suggest. This is recorded honestly as
  a test-coverage gap, not a logic gap.

Backlog detail lives at [§16](../TODOS.md) (with subsections §16.1–§16.7); the
content-service pipeline is tracked under [§3](../TODOS.md). Cross-domain and
provider dependencies are in [`../DEPENDENCIES.md`](../DEPENDENCIES.md).

## Related

- [Agentic AI Studio](./agentic-ai-studio.md) — the generate→gate side of the
  handoff
- [Generation Audience Tiers](./generation-audience-tiers.md) — tiered
  generation policy
- [Sophia — Grounding Substrate](./substrate-sophia.md) — evidence packs behind
  AI-assist governance
- [Isis — Generation Control Substrate](./substrate-isis.md) — generation
  control
- [Lilith — Contemplative Policy Substrate](./substrate-lilith.md) — release
  gates and policy
- [Content, Localization, Documentation, and Launch Readiness](./content-localization-and-launch.md)
  — localization at launch scale
- [Trust, Safety, and Privacy](./trust-safety-and-privacy.md) — editorial →
  trust-and-safety handoff
- [Customer Curation](./customer-curation.md) — the customer-facing curation
  surface
- [`V1/features.md` § Content Authoring, Curation, and Editorial Operations](../features.md#content-authoring-curation-and-editorial-operations)
- Hub: [../ARCHITECTURE.md](../ARCHITECTURE.md)
