Oshun Platform · Features

Collaboration, Review, and Templates

A focused page within the Oshun Platform Features documentation. The full map and every sibling page live in the Features hub.

6sections9 minread1table

On this page

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.

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.

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-assignedde-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).

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 StudioCreatorRoles (the twelve studio roles — author, editor, curator, reviewer, sme, teacher, scholar, translator, illustrator, narrator, producer, publisher; see Creator Roles and the Authoring Workspace).

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, Sophia Grounding, and the §32 generation pipeline in Isis Generation Control.

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 and Metis — Education and Tutoring.

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.
  • 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.