# Collaboration, Review, and Templates

Two Oshun Studio disciplines sit at the human edge of authoring: the
**collaboration** layer (presence, comments, suggestions, review signoff, and
activity feeds) and the **templates** library (a role-aware gallery of governed,
pre-bound content scaffolds). They serve editors, reviewers, SMEs, translators,
and tenant operators working inside the §16 Studio surface (a route tree under
`apps/oshun/web/src/app/studio/`, not a separate app). Both are **implemented as
real, domain-specific logic** in the source library `@oshun/studio-authoring`
(v0.1.0, pure ESM, `main`/`types` → `./src/index.ts`), which re-exports nine
subdomain modules including `collaboration` and `templates`. This page is the
feature-side companion to the architecture catalog; the hub for the set is
[../features.md](../features.md).

## What ships, honestly

The **logic and contracts are real and tested**: notification fan-out
de-duplicates recipients, signoff gating refuses approval while required items
are open, a token-level three-way merge produces explicit conflict markers, the
reconnect-resync delta replays exactly the activity a client missed, and the
template validator and rehearsal harness run real field checks. The web
workspace at `apps/oshun/web/src/components/studio/StudioAuthoringWorkspace.tsx`
genuinely consumes `@oshun/studio-authoring`.

What is **honestly aspirational**: the _running CRDT substrate_ for live
co-editing. The library ships the presence/cursor model, a minimal merge, and a
reconnect-resync algorithm — i.e. the contracts and the logic — but a verified
end-to-end CRDT transport with offline-first replication is not confirmed in
this library. The route
`apps/oshun/web/src/app/studio/real-time-collaboration-substrate/` exists as the
surface for it. The 2026-06-22 completeness audit also notes that the
`editorial-review-approval` walkthrough currently drives an **incident decision
panel (INC-2041)** rather than the editorial-artifact lifecycle, so the e2e
coverage of the editorial review flow is weaker than the library implies. Where
something is spec-only or gated, this page says so. Honest "planned/gated" beats
fake "shipped."

---

## Collaboration, Comments, and Review Threads

Implemented in `libs/oshun/studio-authoring/src/collaboration/` (§16.8) across
`collaboration.ts` (presence, comments, suggestions, signoff, activity feed) and
`routing.ts` (notification fan-out, reconnect, suggestion resolution, text
merge).

### Presence and live co-editing

A `PresenceCursor` carries `creatorId`, `artifactId`, an optional `blockId`,
`selectionStart`/`selectionEnd`, and `lastSeenUnixSeconds`.
`presenceForArtifact` returns only the cursors seen within a `staleAfterSeconds`
window (default 60s), so the "presence, cursors, selection sharing" promise of
the source is a real, time-bounded query rather than a static roster.

Conflict-free merging is realized by `mergeBlockText`, a **token-level three-way
merge**: it splits base/left/right on whitespace, takes the side that changed
when only one did, keeps the agreed value when both agree, and emits an explicit
`<<<left|||right>>>` marker with a `conflictMarkers` count when both sides
changed the same span. The source code documents this as the "presence-aware
live co-edit path," with richer block-level merges deferred to the §16.2
checkpoint module. This is honest about scope: it is a working merge primitive,
not a full CRDT engine — see the scope note below.

### Comments and threads

`CollabComment` models an inline comment with a `threadId`, `blockId`,
`authorId`, `mentions`, `body`, a `reviewerOnly` flag (the source's
"reviewer-only comment channels"), and a state from
`COLLAB_COMMENT_STATES = open | resolved | rejected`. Resolution timestamps are
recorded explicitly, giving the source's "resolved/unresolved threads."

### Suggestion mode (track changes)

A `SuggestionEdit` is a proposed `before → after` block change in state
`pending | accepted | rejected`. `resolveSuggestion` applies the decision and
records attribution: on `accept`, it replaces `before` with `after` in the
current block text (falling back to the suggested text if the span has moved)
and returns the applied text; on `reject`, it leaves the block untouched. Either
way, it stamps `resolvedById` and `resolvedAtUnixSeconds` — the source's
"accept/reject with attribution."

### Review checklists and signoff gating

`ReviewChecklistItem` records each gating question with a `required` flag and
its `signedOffById` / `signedOffAtUnixSeconds`. `evaluateReviewSignoff` returns
`{ ok: true }` only when **every required item** has both a non-empty signer and
a timestamp; otherwise it returns `{ ok: false, missingItemIds }`. This is the
mechanism that the §16.7 change-request machine consults: in
`transitionChangeRequest`, moving a change request to `approved` while
`openRequiredChecklistItems > 0` is rejected with reason `open-signoff-items`.
That cross-module wiring is why "review checklists embedded in the artifact,
with reviewer signoff blocks, required evaluations, and gating questions" is an
enforced gate, not a UI suggestion. The version-control side is detailed in
[Taxonomy, Localization, and Versioning](./taxonomy-localization-versioning.md).

### Activity feed

`ACTIVITY_FEED_KINDS` enumerates the per-artifact event vocabulary — `edit`,
`comment`, `citation-changed`, `asset-swapped`, `reviewer-assigned`, `approval`,
`publish-state-transition`, `suggestion-accepted`, `suggestion-rejected` — and
`appendActivityFeed` keeps the feed append-only. This is exactly the source's
"activity feed per artifact: edits, comments, citations changed, assets swapped,
reviewers assigned, approvals granted, and publish-state transitions," with the
two suggestion outcomes added.

### Notification routing and reconnect behaviour

`routeCommentNotifications` (in `routing.ts`) fans a new comment out to three
recipient classes — `mention`, `thread-participant`, and `reviewer-assigned` —
**de-duplicating** so that a person who is both mentioned and a reviewer is
notified once. It never notifies the comment's own author. This is the source's
"mentions, assignment, and per-thread notification routing" made deterministic.

Reconnect is handled by `buildReconnectResyncDelta`: given a
`ReconnectResyncCursor` (the client's `lastFeedEntryId` and
`lastPresenceUnixSeconds`), it returns the `missedFeedEntries` after the
client's last-seen entry, the `missedComments` authored since, and the
`currentPresences` still live within 60 seconds. This directly answers the
source's test promise of "presence/awareness behaviour under reconnect" — the
client gets exactly the diff it needs to catch up, not a full reload.

### What the audit flags

The 2026-06-22 completeness audit calls out that the `editorial-review-approval`
spec currently exercises an **incident decision panel (INC-2041)** rather than
the editorial-artifact lifecycle. The collaboration _logic_ above is real and
unit-tested; the _end-to-end_ editorial-review walkthrough is the partial piece.
Recording this honestly: the library is stronger than its e2e coverage here.

---

## Templates Library

Implemented in `libs/oshun/studio-authoring/src/templates/` (§16.9) across
`templates.ts` (the template model, validation, tenant overrides, binding
checks) and `rehearsal.ts` (fixture-bound rehearsal, replacement recommendation,
the canonical gallery).

### The role-aware gallery

`STUDIO_TEMPLATE_KINDS` enumerates the seventeen template kinds the source
promises, one-for-one:

| Group                    | Kinds                                                                                                        |
| ------------------------ | ------------------------------------------------------------------------------------------------------------ |
| Contemplative / personal | `ritual-script`, `meditation`, `breathwork-session`, `journaling-prompt`, `weekly-review`, `reflection-card` |
| Editorial / scholarly    | `story-brief`, `claim-card`, `source-note`, `passage`, `edition-comparison`, `sky-event-briefing`            |
| Education                | `lesson-plan`, `course-outline`, `assessment`, `rubric`                                                      |
| Operations               | `admin-review-template`                                                                                      |

`buildCanonicalGallery` turns templates into browsable `CanonicalGalleryEntry`
rows (title, description, `availableToRoles`, deprecation status, replacement
pointer) — the "canonical, browsable, role-aware template gallery."
Role-awareness is structural: every `StudioTemplate` carries a `roleAllowlist`
of `StudioCreatorRole`s (the twelve studio roles — `author`, `editor`,
`curator`, `reviewer`, `sme`, `teacher`, `scholar`, `translator`, `illustrator`,
`narrator`, `producer`, `publisher`; see
[Creator Roles and the Authoring Workspace](./authoring-workspace-and-roles.md)).

### The template contract and validation

A `StudioTemplate` declares everything the source promises a template should
pre-bind:

```ts
interface StudioTemplate {
  templateId: string;
  kind: StudioTemplateKind;
  version: number; // template versioning
  roleAllowlist: StudioCreatorRole[]; // role-aware
  fields: StudioTemplateField[]; // required fields + validation
  suggestedGroundingSources: string[]; // suggested grounding sources
  recommendedEvaluations: string[]; // recommended evaluations
  boundPersonaId: string; // pre-bound persona
  boundTonePolicyId: string; // pre-bound tone policy
  boundAgenticPipelineId: string | null; // pre-bound agentic pipeline
  tenantOverrides: ReadonlyMap<string, StudioTemplate | null>;
  deprecatedAtUnixSeconds: number | null;
  replacementTemplateId: string | null;
}
```

Each `StudioTemplateField` carries a typed `validation` rule — `string` (with
`minLength`/`maxLength`/`pattern`), `number` (with `min`/`max`), `enum`, or
`block-list` (with `minBlocks`). `validateTemplatePayload` checks a submitted
payload against the field rules and returns a `TemplateValidationResult` with
the `missingFieldIds`, `invalidFieldIds`, and an `ok` verdict — the source's
"required fields, validation."

`validateTemplateBindings` audits the template _itself_, emitting a typed
`TemplateBindingIssue` for any unbound governance dimension:
`no-role-allowlist`, `no-required-fields`, `missing-grounding-source`,
`missing-recommended-evaluation`, `missing-persona`, `missing-tone-policy`, or
`missing-agentic-pipeline`. In other words, a template that fails to pre-bind a
persona, tone policy, grounding source, evaluation set, or agentic pipeline is
flagged before it can mislead an author — the binding discipline that connects
templates to [Lilith Persona Policy](./lilith-persona-policy.md),
[Sophia Grounding](./sophia-grounding.md), and the §32 generation pipeline in
[Isis Generation Control](./isis-generation-control.md).

### Per-tenant overrides for institutional Metis

Templates support the source's "per-tenant template extensions and overrides for
institutional Metis delivery." `resolveTemplateForTenant` returns a tenant's
override when one exists. `resolveTemplateAvailability` is more expressive: it
returns `{ ok: true, source: 'base' }` when no override applies,
`{ ok: true, source: 'override' }` when a tenant-specific template is provided,
and `{ ok: false, reason: 'disabled-for-tenant' }` when a tenant's entry in the
override map is `null` — i.e. an institution can _disable_ a template, not just
replace it. See [Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md)
and [Metis — Education and Tutoring](./domain-metis.md).

### Versioning, fixture-bound rehearsal, and deprecation

Templates are versioned (`version`) and can be deprecated
(`deprecatedAtUnixSeconds`); `isTemplateDeprecated` evaluates that against a
supplied clock.

The standout is **fixture-bound rehearsal** (`rehearsal.ts`), which fulfills the
source's "fixture-bound rehearsal" test promise. A `TemplateRehearsalFixture`
pins a concrete payload to its `expectedOk`, `expectedMissingFieldIds`, and
`expectedInvalidFieldIds`. `runTemplateRehearsal` re-validates the payload and
reports every `mismatch` between expected and actual, and `runAllRehearsals`
aggregates a `RehearsalSummary` (`templatesCovered`, `fixturesRun`, `passed`,
`failed`). This is the mechanism that lets CI detect when a template change
silently invalidates a downstream artifact — a template edit that breaks an
existing fixture fails the suite.

On deprecation, `recommendReplacementTemplates` answers "replacement
recommendation" with a real similarity computation: it builds a **field-shape
fingerprint** (each field as `fieldId:required:kind`, sorted and joined) and
ranks non-deprecated templates _of the same kind_ by Jaccard overlap of those
shapes. It labels each candidate
`identical | near-identical | broadly compatible | partial` and returns the
top-`k` above `minSimilarity`. A curator deprecating a template is handed the
structurally closest survivors, not an arbitrary list.

---

## How these connect to the rest of V1

- **Signoff gates versioning.** `evaluateReviewSignoff` (here) is what the §16.7
  change-request state machine calls before allowing `approved` — the two
  modules are wired together, and the version-control side lives in
  [Taxonomy, Localization, and Versioning](./taxonomy-localization-versioning.md).
- **Templates pre-bind governance.** A template's `boundPersonaId`,
  `boundTonePolicyId`, and `boundAgenticPipelineId` connect a scaffold to the
  persona/tone/grounding stack and the §32 content pipeline, so an author who
  starts from a template inherits its guardrails by construction.
- **Collaboration surfaces are routed.** The Studio route tree includes
  `commenting-annotation-system/`, `presence-cursor-systems/`,
  `real-time-collaboration-substrate/`, `review-approval-workflows/`, and
  `activity-change-feeds/` under `apps/oshun/web/src/app/studio/`; those routes
  are the surfaces these contracts back.

## Honest scope notes

- `mergeBlockText` is a **working token-level three-way merge with explicit
  conflict markers** — not a full CRDT. A verified offline-first CRDT transport
  for live co-editing is the aspirational piece; the library ships the
  presence/cursor model, the merge primitive, and the reconnect-resync
  algorithm, and the `real-time-collaboration-substrate/` route is the surface
  for that transport.
- These modules are **pure functions over typed records**; timestamps are inputs
  and persistence is an application/infrastructure concern, not part of
  `@oshun/studio-authoring`.
- The `editorial-review-approval` end-to-end walkthrough currently drives an
  incident decision panel (INC-2041) rather than the editorial artifact
  lifecycle, per the 2026-06-22 audit. The collaboration logic is unit-tested;
  the editorial-flow e2e is the partial seam.

## Related

- [Taxonomy, Localization, and Versioning](./taxonomy-localization-versioning.md)
  — the §16.5–§16.7 sibling whose change-request machine consumes review signoff
- [Creator Roles and the Authoring Workspace](./authoring-workspace-and-roles.md)
  — the twelve studio roles and the workspace that drive collaboration and
  templates
- [Editorial Calendar and Asset & Media Library](./editorial-and-asset-library.md)
  — the lifecycle and assets that activity feeds and templates reference
- [Lilith Persona Policy](./lilith-persona-policy.md),
  [Sophia Grounding](./sophia-grounding.md), and
  [Isis Generation Control](./isis-generation-control.md) — the
  persona/grounding/pipeline that templates pre-bind
- [Tenant, Institution, and Operator Toolkit](./tenant-toolkit.md) and
  [Metis — Education and Tutoring](./domain-metis.md) — per-tenant template
  overrides for institutional delivery
- [Product Surfaces](./product-surfaces.md) and the hub
  [../features.md](../features.md)
