DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/phase-40-operator-vocabulary-schemas/phase-40-cr-operator-vocabulary-schemas-v0_2.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-053 — Phase 40: Operator-vocabulary Response Schemas (v0.2)

Version. 0.2 Date. 2026-05-05 Author. Marvin Percival (DUNIN7), prepared via Claude. Target. /Users/dunin7/loomworks (substrate) on DUNIN7-M4 (MacMini). Priority. Standard. Confidential. Internal. Supersedes. CR-2026-053 v0.1 (Phase 40). Companion to. Phase 40 scoping note v0.1, engine implementation strategy v0.2 §10, Operator Layer discovery v0.4 §§9–10, Phase 39 CR v0.1 (dashboard endpoints), Phase 37 CR v0.2 (composition endpoints), Phase 31 CR v0.1 (conversation engine). Status. Second draft. Resolves five blockers and absorbs seven recommendations from the CC pre-approval audit of v0.1. Consumes settled scoping decisions from scoping note v0.1 without relitigating them. Incorporates the critical update from the handoff: Phase 37 has landed, so the composition endpoint ships live (not as a stub).

v0.1 → v0.2 changes

Blockers resolved:

Recommendations absorbed:

Observations addressed:

Vocabulary budget tightening: InboxItem.verbs and InboxRespondRequest.verb now use Literal types (not free-form str) so the vocabulary-wall test covers verb values. New OperatorVerb type alias.


Contents


1. Executive summary

Phase 40 is the final engine phase. It builds the Orchestration API — a new /operator/... endpoint surface that translates engine objects into Operator-vocabulary objects. The vocabulary wall is enforced architecturally: every response schema uses Operator terms exclusively. The existing engine API under /engagements/... and /me/... stays unchanged.

Seven endpoint groups, five live and two stub:

Live:

  1. GET /operator/dashboard — wraps Phase 39's three cross-engagement aggregation endpoints into a single dashboard response. Translates all field names to Operator vocabulary.
  2. GET /operator/inbox + POST /operator/inbox/{item_id}/respond — wraps Phase 39's needs_you with actionable verbs. Respond executes the engine operation.
  3. GET /operator/library + GET /operator/library/{artifact_id}/download — wraps render queries and the download endpoint with Operator vocabulary.
  4. POST /operator/converse — wraps Phase 31's conversation engine with server-side project-creation bootstrap. Ships with engagement-creation intent only; other intents added as Arc 2 lands them.
  5. POST /operator/projects/{project_id}/render_with_review — wraps Phase 37's composition creation. Translates RenderComposition lifecycle into Operator vocabulary.

Stub (HTTP 501 with full contract schema):

  1. GET /operator/projects/{project_id}/story — project narrative. Requires Arc 2 Companion work.
  2. GET /operator/projects/{project_id}/implied_specifications — downstream specifications. Requires Phase 38 grammar data to be meaningfully queryable.

New module: src/loomworks/orchestration/ with schemas, translators, helpers, and per-endpoint routers. No migrations. No frontend changes. Substrate-only phase.

After Phase 40 tags, all seven Operator Layer engine prerequisites (strategy doc §§4–10) are complete. The Operator Layer arc work (Arcs 1–6) can proceed.


2. Grounding

Phase 40 is grounded in three layers, consulted in order.

Layer 1 — Methodology document v0.20. The Operator Layer is the experience surface for people who build artifacts, not engines. Plain-English communication is a methodology commitment. The vocabulary wall is the architectural enforcement of that commitment. Recognition is load-bearing — the companion's vocabulary treats the Operator as the source of intent, not as a user of a specification engine.

Layer 2 — Operator Layer discovery v0.4. Section 9 (the vocabulary wall) defines the mapping and the architectural enforcement commitment. Section 10 (the Orchestration API) defines the seven endpoint groups. The discovery document is the design authority; this CR implements the substrate layer of what v0.4 specifies.

Layer 3 — Engine implementation strategy v0.2 §10. The specification of Phase 40 as the seventh engine prerequisite. Module structure, schema examples, endpoint mapping, vocabulary-wall test pattern, stub-then-wire pattern for composition. The strategy doc bridges the discovery architecture to engine-level implementation decisions.


3. Prerequisites

3.1 Baseline

3.2 Pre-flight checks

CC verifies at Step 0:

  1. Test count matches baseline (1,447 passed, 2 skipped). If divergent, reconcile and report the actual count before proceeding.
  2. Alembic single head at 0056.
  3. No existing /operator/... routes in the application. [CC verifies: grep router registrations in the app factory.]
  4. Phase 39 dashboard endpoints exist: GET /me/dashboard/active, GET /me/dashboard/needs_you, GET /me/dashboard/recent. [CC verifies: router file location and function names.]
  5. Phase 39 dashboard response schemas exist: DashboardActiveItem, DashboardNeedsYouItem, DashboardRecentItem. [CC verifies: schema file location.]
  6. Phase 37 composition endpoints exist: POST /engagements/{eid}/compositions, POST /engagements/{eid}/compositions/{cid}/steps/{step_index}/decide, GET /engagements/{eid}/compositions/{cid}, GET /engagements/{eid}/compositions. [CC verifies: router file location and function names.]
  7. Phase 37 composition model exists: RenderComposition with six event kinds. [CC verifies: model file location.]
  8. Phase 31 conversation engine exists at POST /engagements/{eid}/seed/converse (engagement-scoped, with assert_engagement_creator dependency). Engagement creation exists at POST /engagements. Engagement instantiation exists at POST /engagements/{eid}/instantiate or equivalent finalization endpoint. [CC verifies: all three endpoint paths, function signatures, and the ConverseTurnResponse schema fields (seed_document, completion_percentage, status, turn_number, seed_fields).]
  9. Authentication dependency get_current_person exists and is used by Phase 39 dashboard endpoints. [CC verifies: import path and usage pattern.] Note: get_current_person uses Depends(get_secret_key) and is override-friendly. The Phase 37 cookie-auth carry-forward (_resolve_cookie_contributor reads _default_settings.loomworks_secret_key directly) does NOT affect Phase 40 because Phase 40 routes use get_current_person, not get_contributing_contributor.
  10. Render download endpoint exists: GET /engagements/{eid}/renders/{rid}/download (param is object_id). [CC verifies: router location and the function-level helper that can be called without HTTP dispatch.]
  11. No existing src/loomworks/orchestration/ directory.
  12. _retire_render function exists and is callable (underscore-prefixed private function). [CC verifies: module location.]
  13. ActorRef construction pattern exists — _actor_from_contributor in Phase 37. [CC verifies: import path and signature.]

Pre-flight divergence stops execution at Step 0 and drives a CR amendment.

3.3 CR archival

Archive this CR to docs/phase-crs/phase-40-cr-operator-vocabulary-schemas-v0_2.md at Step 0 before Step 1 begins.


4. Construction decisions this CR closes

All six scoping decisions from the Phase 40 scoping note v0.1 are adopted. This CR additionally closes the following construction-level decisions.

D1 — Composition endpoint is live (critical update)

The scoping note was written before Phase 37 landed and specifies the composition endpoint (POST /operator/projects/{project_id}/render_with_review) as a stub returning HTTP 501. Phase 37 has now landed. The composition endpoint ships live — wrapping the engine's POST /engagements/{eid}/compositions with Operator vocabulary. This reduces the stub count from three to two.

Prior position: scoping note v0.1 §N3 — three stubs (narrative, implied_specifications, composition). Current position: two stubs (narrative, implied_specifications). Composition is live.

D2 — Authentication is person-scoped

All /operator/... endpoints authenticate via get_current_person (the Phase 39 dashboard pattern), not get_contributing_contributor (the engagement-scoped pattern). The Operator Layer is person-scoped: the dashboard, inbox, and library aggregate across engagements. Project-scoped endpoints (composition, narrative, implied_specifications) still authenticate the person first, then verify the person's membership on the referenced engagement via the verify_project_membership helper (§8.3).

D3 — Inbox verbs are typed Literals, not free-form strings

Inbox items carry a verbs list — the actions the Operator can take. Verbs use Operator language: "review", "approve", "save", "discard". The verb type is a Literal (not list[str]) so the vocabulary-wall test covers verb values. The respond endpoint maps each verb to the engine operation. The Operator never sees engine operation names.

Prior position (v0.1): verbs: list[str] — free-form, not covered by vocabulary-wall test. Current position (v0.2): verbs: list[OperatorVerb] — typed Literal, wall-test-covered.

D4 — Library wraps existing download pathway; download is not an inbox verb

The library endpoint queries render events across the person's engagements and translates them to Operator vocabulary. The download endpoint delegates to the engine download function directly (same-process call, not HTTP dispatch).

Prior position (v0.1): download was an inbox verb that "redirected" to the library download — undefined wire shape. Current position (v0.2): download is removed from the verb set. Inbox items for ready_artifact carry a download_url field instead. The Operator downloads via the library, not via the inbox respond endpoint.

D5 — Converse endpoint bootstraps project creation (B1 resolution)

Phase 31's seed conversation endpoint is engagement-scoped: POST /engagements/{eid}/seed/converse. It operates on an existing candidate engagement and cannot create one. The actual project-creation flow is three steps:

  1. POST /engagements (with draft body) → creates a candidate engagement.
  2. POST /engagements/{eid}/seed/converse → iterate on the seed.
  3. POST /engagements/{eid}/instantiate → finalize.

The converse endpoint handles this transparently. On the first create_project turn (no project_id), the endpoint:

  1. Creates a candidate engagement server-side (calling the engine's POST /engagements function with a minimal draft derived from the Operator's first message).
  2. Passes the message to the seed conversation endpoint on the new candidate.
  3. Returns the response with project_id populated (the new candidate's UUID).

Subsequent turns arrive with project_id set and route directly to the seed conversation. The Operator never sees candidate-engagement mechanics.

Instantiation (step 3) is wrapped as a separate intent: when the Phase 31 conversation signals status="ready_for_review" (Operator vocabulary: readiness="ready_for_review"), the response includes a suggested action "finalize_project". A subsequent converse call with intent_hint="finalize_project" calls the instantiation endpoint. This keeps all project-creation lifecycle within the converse surface.

[CC verifies: the POST /engagements request body (what fields are required for a minimal candidate), and the instantiation endpoint path and signature.]

D6 — Error responses use Operator vocabulary

All error responses from /operator/... endpoints use Operator vocabulary. A 404 for a missing engagement says "Project not found," not "Engagement not found." A 422 for an invalid state says "This note is already saved," not "Assertion already committed." The error response schema is:


class OperatorErrorResponse(BaseModel):
    error: str           # Human-readable message in Operator vocabulary
    detail: str | None   # Optional additional context

[CC verifies: consistency with the existing engine error response pattern and how FastAPI exception handlers work for the new router.]

D7 — Default rationale on verbs that require it (B3 resolution)

Engine operations retract_assertion, _retire_render, and close_shape_confirmation require a non-null rationale parameter. The Operator may omit rationale in InboxRespondRequest. The respond handler substitutes a default:

| Verb | Default rationale | |---|---| | discard | "Discarded by Operator" | | archive | "Archived by Operator" | | approve | "Approved by Operator" |

If the Operator provides a rationale, it takes precedence.


5. Module structure

New module at src/loomworks/orchestration/:


src/loomworks/orchestration/
    __init__.py
    schemas.py              # All Operator-vocabulary Pydantic models
    translators.py          # Engine-to-Operator translation functions
    helpers.py              # actor_from_person, verify_project_membership
    routers/
        __init__.py
        dashboard.py        # GET /operator/dashboard
        inbox.py            # GET /operator/inbox + POST respond
        library.py          # GET /operator/library + download
        converse.py         # POST /operator/converse
        composition.py      # POST /operator/projects/{id}/render_with_review
        narrative.py        # GET /operator/projects/{id}/story (stub)
        downstream.py       # GET /operator/projects/{id}/implied_specifications (stub)

The schemas.py module is the single home for all Operator-vocabulary models. The translators.py module is the vocabulary wall — each function takes an engine object and returns an Operator-vocabulary object. The helpers.py module holds cross-cutting utilities (actor construction, membership verification). The routers call engine functions, pass results through translators, and return Operator-vocabulary responses.

This structure ensures the vocabulary wall is enforceable: any import from orchestration/schemas.py that uses engine vocabulary is a test failure (§9).


6. The vocabulary mapping

From scoping note v0.1 §N1, adopted with completeness additions (R3). This mapping governs every field name and literal value in orchestration/schemas.py.

| Engine term | Operator term | Context | |---|---|---| | engagement / engagement_id | project / project_id | Object identity | | assertion / assertion_id | note / note_id | Knowledge contributions | | shape_event | specification | Shaped outputs | | render_event | artifact | Rendered outputs | | held | waiting | Assertion state | | committed | saved | Assertion state | | retracted | discarded | Assertion state | | redirected | moved_to_other_project | Assertion state | | pending_confirmation | needs_review | Shape state | | confirmed | (not exposed) | Shape terminal state — not surfaced in dashboard/inbox/library | | amended_superseded | (not exposed) | Shape terminal state — not surfaced | | produced | ready | Render state | | retired | archived | Render state | | invalidated | (not exposed) | Render terminal state — not surfaced | | contributor | contributor | Unchanged | | membership | membership | Unchanged | | manifestation | (not exposed) | Internal pipeline concept |

Engine-internal terms from Phases 36–38 (display_number, specification_grammar, prior_render_ref, revision_strategy) are not exposed through the Orchestration API. The mapping extends when new Operator-facing concepts emerge.


7. Operator-vocabulary schemas

All schemas live in src/loomworks/orchestration/schemas.py. Every field name, every Literal value, every docstring uses Operator vocabulary exclusively. The vocabulary-wall test (§9) enforces this structurally.

7.1 Common schemas


# Verb type — Literal so the vocabulary-wall test covers verb values
OperatorVerb = Literal[
    "review", "approve", "request_revision",
    "save", "discard",
    "archive",
    "accept", "revise",
    "retry", "dismiss",
    "start_project",
]

class OperatorErrorResponse(BaseModel):
    error: str
    detail: str | None = None

class OperatorStubResponse(BaseModel):
    status: Literal["not_implemented"]
    message: str
    expected_capability: str

7.2 Dashboard schemas

The dashboard response aggregates Phase 39's three endpoints into a single response shape. Field names translate from engine vocabulary.


class DashboardActiveProject(BaseModel):
    project_id: UUID
    project_name: str
    activity_kind: Literal[
        "drafting", "producing", "reviewing", "processing_externally"
    ]
    activity_id: UUID
    activity_label: str
    started_at: datetime
    progress_hint: str | None = None

class DashboardInboxPreview(BaseModel):
    project_id: UUID
    project_name: str
    item_kind: Literal[
        "needs_review", "waiting_note", "ready_artifact",
        "review_report", "external_issue", "downstream_spec"
    ]
    item_id: UUID
    item_label: str
    detail: str
    arrived_at: datetime

class DashboardRecentArtifact(BaseModel):
    project_id: UUID
    project_name: str
    artifact_kind: Literal["artifact", "review_report", "transformed_artifact"]
    artifact_id: UUID
    artifact_label: str
    completed_at: datetime
    download_url: str | None = None

class DashboardResponse(BaseModel):
    active: list[DashboardActiveProject]
    active_count: int
    inbox_preview: list[DashboardInboxPreview]
    inbox_count: int
    recent: list[DashboardRecentArtifact]
    recent_count: int

Translation notes. Phase 39's item_kind Literals use engine vocabulary (shape_production, render_production, pending_shape, held_assertion, etc.). The translator maps each to an Operator-vocabulary equivalent. The mapping:

| Phase 39 item_kind | Operator activity_kind / item_kind | |---|---| | shape_production | drafting | | render_production | producing | | composition | reviewing | | external_polling | processing_externally | | pending_shape | needs_review | | held_assertion | waiting_note | | produced_render_for_review | ready_artifact | | review_report | review_report | | external_failure | external_issue | | downstream_specification | downstream_spec |

Note (O1/O2). Phase 39 reserves several item kinds that it never currently produces (composition, review_report, downstream_specification, external_failure, produced_render_for_review). The translator maps them for forward compatibility. Tests exercise only item kinds with backing data; the forward-compatible kinds are covered by the vocabulary-wall test scanning the Literal values.

7.3 Inbox schemas


class InboxItem(BaseModel):
    item_id: UUID
    project_id: UUID
    project_name: str
    item_kind: Literal[
        "needs_review", "waiting_note", "ready_artifact",
        "review_report", "external_issue", "downstream_spec"
    ]
    title: str
    detail: str
    arrived_at: datetime
    verbs: list[OperatorVerb]
    download_url: str | None = None  # Populated for ready_artifact items

class InboxResponse(BaseModel):
    items: list[InboxItem]
    total_count: int
    next_cursor: str | None = None

class InboxRespondRequest(BaseModel):
    item_kind: Literal[
        "needs_review", "waiting_note", "ready_artifact",
        "review_report", "external_issue", "downstream_spec"
    ]
    verb: OperatorVerb
    rationale: str | None = None  # Optional — defaults substituted per D7

class InboxRespondResponse(BaseModel):
    success: bool
    message: str        # Companion-style confirmation in Operator vocabulary

Verb mapping. The translator assigns verbs based on item_kind:

| Item kind | Available verbs | Notes | |---|---|---| | needs_review (pending shape) | ["review", "approve", "request_revision"] | request_revision returns 501 (future) | | waiting_note (held assertion) | ["save", "discard"] | | | ready_artifact (produced render) | ["archive"] | Download via download_url, not a verb | | review_report | ["review", "accept", "revise"] | | | external_issue | ["retry", "dismiss"] | Both return 501 (future) | | downstream_spec | ["start_project", "dismiss"] | Both return 501 (future) |

The respond endpoint maps verbs to engine operations:

| Verb | Engine operation | Default rationale (if Operator omits) | |---|---|---| | approve | Confirm shape event (close_shape_confirmation) | "Approved by Operator" | | request_revision | (future — not wired in Phase 40, returns 501) | — | | save | Commit assertion (commit_assertion) | — (commit does not require rationale) [CC verifies] | | discard | Retract assertion (retract_assertion) | "Discarded by Operator" | | archive | Retire render (_retire_render — private, cross-module import accepted) | "Archived by Operator" | | accept | Composition decision — accept (decide_composition, decision="accept") | — | | revise | Composition decision — revise (decide_composition, decision="revise") | — | | retry | (future — external retry, returns 501) | — | | dismiss | (future — mark dismissed, returns 501) | — | | start_project | (future — create engagement from implied spec, returns 501) | — |

[CC verifies: the engine functions for close_shape_confirmation, commit_assertion, retract_assertion, _retire_render, decide_composition. These are existing engine operations; the respond endpoint calls them by function reference, not by assumed path. Verify each function's required parameters and which require rationale.]

Verbs marked (future) return a 501 with an OperatorStubResponse when the respond endpoint receives them. The verb is in the contract; the backing engine operation is not yet wired. This follows the stub-then-wire pattern.

7.4 Library schemas


class LibraryArtifact(BaseModel):
    artifact_id: UUID
    project_id: UUID
    project_name: str
    title: str
    format: str                   # e.g. "PDF", "HTML", "Markdown"
    status: Literal["ready", "archived"]
    created_at: datetime
    download_url: str | None = None

class LibraryResponse(BaseModel):
    artifacts: list[LibraryArtifact]
    total_count: int
    next_cursor: str | None = None

Download endpoint. GET /operator/library/{artifact_id}/download resolves the artifact's engine identity (render event ID and engagement ID), verifies membership, then calls the engine download function directly (same-process, not HTTP dispatch). Returns the binary file with Content-Disposition: attachment.

Security note (O3). The engine download route uses get_contributing_contributor for its own auth. The Operator route bypasses this by calling the function-level helper directly. The verify_project_membership check in the Operator route (§8.3) is therefore the sole auth gate and is security-load-bearing. This is the correct design — the Operator Layer authenticates as Person, not Contributor — but the membership check must not be omitted.

7.5 Converse schemas


class ConverseRequest(BaseModel):
    message: str
    project_id: UUID | None = None   # Null on first create_project turn
    intent_hint: Literal[
        "create_project", "finalize_project"
    ] | None = None

class ConverseSideEffect(BaseModel):
    description: str                  # "Created a new project"
    resource_kind: str                # "project", "note", "artifact"
    resource_id: UUID | None = None

class ConverseResponse(BaseModel):
    companion_message: str
    side_effects: list[ConverseSideEffect]
    suggested_actions: list[str]      # ["Add some notes", "Describe your first deliverable"]
    project_id: UUID | None = None    # Populated after project creation
    draft_specification: str | None = None   # Phase 31 seed_document, in Operator vocab
    readiness: Literal[
        "drafting", "ready_for_review"
    ] | None = None                   # Phase 31 status, translated

Translation from Phase 31 ConverseTurnResponse (R5). The Phase 31 response includes seed_document (the evolving draft spec), completion_percentage, status: Literal["in_progress", "ready_for_review"], turn_number, and seed_fields. The translator maps:

| Phase 31 field | Operator field | Translation | |---|---|---| | companion_response | companion_message | Passthrough | | seed_document | draft_specification | Passthrough (content is already Operator-facing) | | status | readiness | "in_progress""drafting", "ready_for_review""ready_for_review" | | completion_percentage | (not surfaced) | Dropped — the frontend has the readiness signal | | turn_number | (not surfaced) | Dropped — conversation ordering is implicit | | seed_fields | (not surfaced) | Dropped — the draft_specification is the Operator view |

7.6 Composition schemas (render with review)


class ReviewStep(BaseModel):
    step_type: Literal["produce", "review", "translate", "transform", "validate"]
    specialist_label: str         # Human-readable name, not the ActorRef
    config: dict | None = None

class RenderWithReviewRequest(BaseModel):
    specification_id: UUID        # Confirmed shape event ref
    steps: list[ReviewStep]
    reason: str | None = None     # Why the Operator is requesting this

class RenderWithReviewResponse(BaseModel):
    review_id: UUID               # Composition ID in Operator vocabulary
    status: Literal["started"]
    message: str                  # "Your artifact is being produced and will be reviewed."

Translation. The router translates specification_idconfirmed_shape_event_ref, steps[].specialist_labelsteps[].specialist_ref (by looking up the specialist by human-readable name or by accepting an opaque reference that the frontend knows). [CC determines: whether the specialist resolution is by label lookup or by passing through an opaque ID. The key constraint is that specialist_ref (an engine concept) never appears in the request schema.]

The engine's 202 response (composition ID + initial state) translates to the RenderWithReviewResponse with review_id as the Operator-vocabulary term for composition ID.

7.7 Stub schemas


class ProjectStoryResponse(BaseModel):
    project_id: UUID
    narrative: str                # The companion's account of the project journey
    milestones: list[str]         # Key moments
    last_updated: datetime

class ImpliedSpecificationItem(BaseModel):
    title: str
    rationale: str                # "Your application spec implies a deployment spec because…"
    action: str                   # "start_project"
    source_artifact_id: UUID

class ImpliedSpecificationsResponse(BaseModel):
    project_id: UUID
    specifications: list[ImpliedSpecificationItem]

Both endpoints return HTTP 501 with:


OperatorStubResponse(
    status="not_implemented",
    message="This capability is coming soon.",
    expected_capability="Project narrative"  # or "Implied specifications"
)

The response schemas above define the contract that Arc 2 and Arc 3 build against. When the backing capability lands, the stub is replaced with the live wiring. Callers don't change.


8. Translators and helpers — the vocabulary wall

8.1 Translation functions

src/loomworks/orchestration/translators.py contains one translation function per engine object type. Each function takes an engine object and returns an Operator-vocabulary object. These functions are the vocabulary wall.


def translate_active_item(engine_item) -> DashboardActiveProject:
    """Phase 39 DashboardActiveItem → DashboardActiveProject."""

def translate_needs_you_item(engine_item) -> DashboardInboxPreview:
    """Phase 39 DashboardNeedsYouItem → DashboardInboxPreview."""

def translate_recent_item(engine_item) -> DashboardRecentArtifact:
    """Phase 39 DashboardRecentItem → DashboardRecentArtifact."""

def translate_needs_you_to_inbox(engine_item) -> InboxItem:
    """Phase 39 DashboardNeedsYouItem → InboxItem with verbs and optional download_url."""

def translate_render_to_library(engine_row, engagement_name: str) -> LibraryArtifact:
    """SQLAlchemy Row from render_events_view → LibraryArtifact.
    Operates on the row directly (dict-like mapping), matching the
    dashboard's SQL approach. Not instantiated as a typed RenderEvent."""

def translate_composition_response(engine_response) -> RenderWithReviewResponse:
    """Engine composition creation response → RenderWithReviewResponse."""

def translate_converse_response(engine_response, project_id: UUID) -> ConverseResponse:
    """Phase 31 ConverseTurnResponse → ConverseResponse.
    Maps seed_document → draft_specification, status → readiness."""

8.2 Translation discipline

  1. One-way. Engine objects go in, Operator objects come out. The reverse direction (Operator request → engine call) is handled in the router, not in translators. The router reads the Operator-vocabulary request, constructs the engine call parameters, calls the engine function, passes the engine response through the translator, and returns the Operator-vocabulary response.
  1. No engine imports in schemas. orchestration/schemas.py imports nothing from api/schemas/ or from engine model modules. The translator imports both — it is the bridge. This import discipline is verifiable.
  1. Null handling. Translators handle null engine fields gracefully. A render with no download URL translates to download_url=None. A composition with no failure detail translates cleanly. [CC verifies: null-field behavior in the engine response schemas being wrapped.]
  1. Kind mapping. Each translator carries the item_kind mapping table (§7.2) as a constant dict, not as inline if/elif chains. This makes the mapping auditable and extensible.

[CC determines: the exact engine object types that each translator receives — these are the Pydantic response models or SQLAlchemy Row objects from the engine endpoints. The CR specifies function signatures by intent; CC wires them to the actual types at pre-flight.]

8.3 Helpers

src/loomworks/orchestration/helpers.py holds cross-cutting utilities.

actor_from_person (R1). Engine operations (commit_assertion, retract_assertion, close_shape_confirmation, _retire_render, decide_composition) take actor: ActorRef. Phase 40 authenticates as Person, not Contributor. Phase 37's _actor_from_contributor only takes a Contributor.


def actor_from_person(person) -> ActorRef:
    """Construct an ActorRef from a Person object.
    kind="person", id=person.id, display_name=person.display_name."""

[CC verifies: the ActorRef constructor fields (kind, id, display_name or equivalent). Follow the pattern in _actor_from_contributor but with kind="person".]

verify_project_membership (R6). Project-scoped endpoints (composition, narrative, implied_specifications, library download) authenticate the person first, then verify membership on the referenced engagement.


async def verify_project_membership(*, person, project_id: UUID, db) -> None:
    """Verify person is a member of the engagement identified by project_id.
    Raises OperatorProjectNotFoundError (→ 404) if not a member.
    Uses the same membership query as Phase 39 dashboard endpoints."""

[CC verifies: the membership query function — likely get_membership_for_person_on_engagement or equivalent from Phase 14.]


9. Vocabulary-wall enforcement test

A dedicated test class scans all Pydantic models defined in src/loomworks/orchestration/schemas.py and verifies that no engine vocabulary appears in field names or Literal values.

9.1 Forbidden terms


FORBIDDEN_FIELD_NAMES = {
    "engagement_id", "engagement", "engagement_name",
    "assertion", "assertion_id",
    "shape_event", "shape_event_id", "shape_event_ref",
    "render_event", "render_event_id", "render_event_ref",
    "shaping", "manifestation",
    "normative_force",
    "seed",
}

FORBIDDEN_LITERAL_VALUES = {
    # Engine states
    "held", "committed", "retracted", "redirected",
    "pending_confirmation", "confirmed", "produced",
    # Engine item kinds (Phase 39 reserved values)
    "shape_production", "render_production",
    "composition", "external_polling",
    "pending_shape", "held_assertion",
    "produced_render_for_review",
    "external_failure", "downstream_specification",
    # Engine object names
    "engagement", "assertion", "shape_event", "render_event",
}

9.2 Test mechanics


def test_vocabulary_wall_no_forbidden_field_names():
    """Scan all schemas in orchestration/schemas.py for engine-vocabulary field names."""
    # Import all BaseModel subclasses from orchestration.schemas
    # For each model, iterate model_fields
    # Assert no field name is in FORBIDDEN_FIELD_NAMES
    # Report ALL violations, not just the first

def test_vocabulary_wall_no_forbidden_literal_values():
    """Scan all Literal type annotations for engine-vocabulary values.
    Covers: item_kind, activity_kind, artifact_kind, status, verbs, verb,
    readiness, intent_hint, and any future Literal fields."""
    # For each model field with a Literal type annotation
    # Extract the literal values (including from OperatorVerb alias)
    # Assert no value is in FORBIDDEN_LITERAL_VALUES
    # Report ALL violations, not just the first

The test is structural and automated. It cannot catch creative synonyms or semantic leaks, but it catches the obvious engine-vocabulary terms. It runs as part of the normal test suite and will fail if any future schema addition accidentally uses engine vocabulary.

The OperatorVerb Literal type alias (§7.1) ensures that verb additions are also scanned. This closes the gap identified in the vocabulary-budget cross-check.

[CC determines: the best way to enumerate all BaseModel subclasses in a module — inspect.getmembers or __dict__ iteration. The test should discover models dynamically so new schemas are automatically covered. The OperatorVerb type alias needs special handling — it's not a BaseModel but its Literal values must be scanned.]


10. Dashboard endpoint

10.1 GET /operator/dashboard

Wraps Phase 39's three dashboard endpoints into a single aggregated response.

Implementation:

  1. Authenticate via get_current_person.
  2. Call the three Phase 39 endpoint functions internally (not via HTTP — direct function call within the same process).
  3. Translate each response through the appropriate translator (§8.1).
  4. Return DashboardResponse with all three sections populated.

[CC verifies: how to call the Phase 39 endpoint functions directly. If the endpoint functions accept the person as a parameter (dependency-injected), CC can call them with the authenticated person. If they read from the request directly, CC may need to extract the underlying query function that the endpoint calls.]

Pagination. The dashboard endpoint returns a fixed number of items per section (e.g., 10 active, 10 inbox preview, 10 recent). Full pagination is available on the Inbox and Library endpoints individually. The dashboard is a preview — the Operator sees "12 items need your attention" and clicks through to the Inbox for the full list.

[CC determines: whether to hardcode the preview limit or accept optional ?active_limit, ?inbox_limit, ?recent_limit query params. Hardcoded is simpler and matches the dashboard-as-preview pattern.]


11. Inbox endpoints

11.1 GET /operator/inbox

Full paginated list of items needing the Operator's decision, translated from Phase 39's needs_you endpoint.

Implementation:

  1. Authenticate via get_current_person.
  2. Call Phase 39's needs_you query function with the person's memberships.
  3. Translate each item through translate_needs_you_to_inbox — adds the verbs list based on item_kind, and populates download_url for ready_artifact items.
  4. Return InboxResponse with pagination.

Query params: ?limit (default 50, max 100), ?cursor (opaque, passed through to Phase 39).

11.2 POST /operator/inbox/{item_id}/respond

Executes the Operator's chosen action on an inbox item.

Implementation:

  1. Authenticate via get_current_person.
  2. Read InboxRespondRequest — includes item_kind and verb (both typed Literals).
  3. Validate verb is allowed for item_kind (per §7.3 verb mapping table). If not, 422.
  4. If the verb is a stub verb (future), return 501 with OperatorStubResponse.
  5. Resolve item_id to the engine object using item_kind to determine which engine table to query. Verify the object belongs to one of the person's engagements.
  6. Construct ActorRef from the person via actor_from_person (§8.3).
  7. Substitute default rationale if the Operator omitted one and the engine operation requires it (§D7).
  8. For accept/revise verbs (composition decisions): re-query the composition's operational view (render_compositions_view) for current_step_index. Call decide_composition with step_index=current_step_index. The current_step_index is always the review step that triggered waiting_for_operator_decision. (B2 resolution.)
  9. Map verb to engine operation and execute (§7.3).
  10. Return InboxRespondResponse with a companion-style confirmation.

Error responses. If the verb is not valid for the item kind: 422 with OperatorErrorResponse. If the item doesn't exist or doesn't belong to the person's engagements: 404 with "Item not found." If the engine operation fails: 422 with the failure reason in Operator vocabulary.


12. Library endpoints

12.1 GET /operator/library

Paginated list of artifacts across the person's engagements, translated from render event queries.

Implementation:

  1. Authenticate via get_current_person.
  2. Query render events across the person's engagements (membership-scoped, same pattern as Phase 39's recent). Include all renders in ready (produced) or archived (retired) state.
  3. Translate each through translate_render_to_library (operates on SQLAlchemy Row, per R4).
  4. Return LibraryResponse with pagination.

Query params: ?limit, ?cursor, ?project_id (optional — filter to one project), ?status (optional — ready or archived).

Download URL population. For renders with downloadable content kinds (per Phase 35), the download_url is constructed as /operator/library/{artifact_id}/download. For renders without downloadable content (external references, etc.), download_url is null.

12.2 GET /operator/library/{artifact_id}/download

Delegates to the engine download function.

Implementation:

  1. Authenticate via get_current_person.
  2. Resolve artifact_id to the engine render event ID and engagement ID. [CC verifies: the render_events_view query pattern.]
  3. Verify the person's membership on the engagement via verify_project_membership (§8.3). This membership check is the sole auth gate — the engine-level get_contributing_contributor is not on the function-call path. Security-load-bearing. (O3.)
  4. Call the engine download function directly (not via HTTP).
  5. Return the binary file response with content-disposition headers.

13. Converse endpoint

13.1 POST /operator/converse

Wraps Phase 31's conversation engine with Operator vocabulary and server-side project-creation bootstrap (D5).

Implementation:

  1. Authenticate via get_current_person.
  2. Read ConverseRequest.
  3. Determine intent:

Intent: create_project (first turn — no project_id).

  1. Create a candidate engagement server-side: call the engine's engagement-creation function (POST /engagements equivalent) with a minimal draft body. [CC verifies: the minimum required fields for candidate-engagement creation. The Operator's first message may provide a title or description; otherwise derive from the message text or use a placeholder like "New project".]
  2. Call Phase 31's seed conversation endpoint (POST /engagements/{eid}/seed/converse) on the new candidate, passing the Operator's message.
  3. Translate the Phase 31 ConverseTurnResponse into ConverseResponse (§7.5 translation table).
  4. Return with project_id populated (the new candidate's UUID).
  5. Side effect: ConverseSideEffect(description="Started a new project", resource_kind="project", resource_id=<uuid>).

Intent: create_project (subsequent turns — project_id provided).

  1. Verify membership on the candidate engagement.
  2. Call the seed conversation endpoint on the existing candidate.
  3. Translate and return. If Phase 31 returns status="ready_for_review", include readiness="ready_for_review" and suggested action "finalize_project".

Intent: finalize_project (intent_hint="finalize_project", project_id provided).

  1. Verify membership on the candidate engagement.
  2. Call the engine's instantiation endpoint (POST /engagements/{eid}/instantiate or equivalent finalization function). [CC verifies: the instantiation endpoint path and function.]
  3. Return a companion message confirming the project is finalized.
  4. Side effect: ConverseSideEffect(description="Project finalized", resource_kind="project", resource_id=<uuid>).

Unrecognized intent (project_id provided, no matching intent).

Return a companion-style message explaining that additional capabilities are coming, with a suggested action of "Create a new project."

Error handling. If the seed conversation endpoint returns an error, the converse endpoint translates it into a companion message, not an HTTP error. The Operator never sees a 503 from the Orchestration API — errors surface conversationally. Common error cases:

[CC verifies: Phase 31's error handling — what error types are raised and what messages they carry. The converse endpoint catches these and wraps them in companion language.]


14. Composition endpoint

14.1 POST /operator/projects/{project_id}/render_with_review

Live endpoint wrapping Phase 37's composition creation.

Implementation:

  1. Authenticate via get_current_person.
  2. Verify membership via verify_project_membership(person=person, project_id=project_id, db=db) (§8.3).
  3. Read RenderWithReviewRequest.
  4. Translate:
  1. Construct ActorRef from the person via actor_from_person (§8.3).
  2. Call Phase 37's composition creation endpoint function.
  3. Translate the 202 response through translate_composition_response.
  4. Return RenderWithReviewResponse.

Step type vocabulary. The five step types (produce, review, translate, transform, validate) are the same in engine and Operator vocabulary. They describe what the step does, not what engine object it operates on. No translation needed.

Specialist label resolution. [CC determines: how the frontend will identify specialists. Options: (a) pass the specialist's human-readable name and look it up in the specialist registry; (b) pass an opaque specialist ID that the frontend received from a prior API call. Option (a) is more Operator-friendly but requires a name-to-ref lookup function. Option (b) is simpler but exposes an opaque ID. CC chooses based on what the specialist registry supports.]

Error translation. Phase 37's composition creation returns 422 for validation failures (invalid ordering, missing specialist, etc.). The composition router translates these into Operator vocabulary:

| Engine 422 message | Operator 422 message | |---|---| | "No review step before a produce step" | "A review step must come after a production step" | | "specialist_ref not found" | "The selected specialist was not found" | | "confirmed_shape_event_ref invalid" | "The referenced specification was not found" |

[CC determines: whether to catch and translate specific engine validation errors or to use a generic translation. Specific translation is preferred for user clarity.]


15. Stub endpoints

15.1 GET /operator/projects/{project_id}/story

  1. Authenticate via get_current_person.
  2. Verify membership via verify_project_membership(person=person, project_id=project_id, db=db) (§8.3).
  3. Return HTTP 501 with:

OperatorStubResponse(
    status="not_implemented",
    message="Project narratives are coming soon. The companion will be able to tell you the story of your project.",
    expected_capability="Project narrative"
)

Contract schema: ProjectStoryResponse (§7.7) defines the response shape that Arc 2 builds against.

15.2 GET /operator/projects/{project_id}/implied_specifications

  1. Authenticate via get_current_person.
  2. Verify membership via verify_project_membership(person=person, project_id=project_id, db=db) (§8.3).
  3. Return HTTP 501 with:

OperatorStubResponse(
    status="not_implemented",
    message="Implied specifications are coming soon. The companion will identify downstream specifications your project implies.",
    expected_capability="Implied specifications"
)

Contract schema: ImpliedSpecificationsResponse (§7.7) defines the response shape.

Authentication and membership checks run before the 501 response — an unauthenticated request still gets 401, and a request for a non-member project gets 404.


16. Router registration

Mount the /operator/... router alongside existing routers in the app factory.


# In the FastAPI app factory (app.py or equivalent)
from loomworks.orchestration.routers import (
    dashboard, inbox, library, converse, composition, narrative, downstream
)

app.include_router(dashboard.router, prefix="/operator", tags=["operator"])
app.include_router(inbox.router, prefix="/operator", tags=["operator"])
app.include_router(library.router, prefix="/operator", tags=["operator"])
app.include_router(converse.router, prefix="/operator", tags=["operator"])
app.include_router(composition.router, prefix="/operator", tags=["operator"])
app.include_router(narrative.router, prefix="/operator", tags=["operator"])
app.include_router(downstream.router, prefix="/operator", tags=["operator"])

[CC verifies: the app factory location and the existing router registration pattern. The new routers follow the same pattern as the existing engagements, me, and seed routers.]

The engine API stays. All existing /engagements/..., /me/..., and /seed/... endpoints continue to work unchanged. The Orchestration API is additive.


17. Test suite

17.1 Vocabulary-wall tests

  1. test_vocabulary_wall_no_forbidden_field_names — all schemas in orchestration/schemas.py have no engine-vocabulary field names.
  2. test_vocabulary_wall_no_forbidden_literal_values — all Literal type annotations (including OperatorVerb) have no engine-vocabulary values.

17.2 Dashboard tests

  1. test_dashboard_returns_translated_active_items — create an active shaping job, call /operator/dashboard, verify the response uses Operator vocabulary (drafting not shape_production, project_id not engagement_id).
  2. test_dashboard_returns_translated_inbox_preview — create a pending shape and a held assertion, verify Operator vocabulary in the inbox preview.
  3. test_dashboard_returns_translated_recent — create a produced render, verify Operator vocabulary in the recent section.
  4. test_dashboard_empty_state — person with no activity, verify empty lists (not errors).

17.3 Inbox tests

  1. test_inbox_returns_items_with_verbs — create items, verify each has the correct verb list per item kind. Note: only item kinds with backing data are exercisable (pending_shape, held_assertion). Forward-compatible kinds (review_report, external_issue, downstream_spec) are covered by the vocabulary-wall test scanning their Literal values. (O1.)
  2. test_inbox_pagination — create multiple items, verify ?limit and ?cursor work.
  3. test_inbox_respond_save_note — held assertion → respond with item_kind="waiting_note", verb="save" → assertion committed. Verify companion-style confirmation message.
  4. test_inbox_respond_approve_spec — pending shape → respond with item_kind="needs_review", verb="approve" → shape confirmed. Verify confirmation.
  5. test_inbox_respond_discard_note — held assertion → respond with item_kind="waiting_note", verb="discard" → assertion retracted. Verify default rationale "Discarded by Operator" was passed to engine.
  6. test_inbox_respond_discard_with_rationale — same as 11 but with explicit rationale → verify the Operator's rationale was passed, not the default.
  7. test_inbox_respond_invalid_verb — send verb not in allowed list for item_kind → 422.
  8. test_inbox_respond_wrong_item — item_id not found → 404.
  9. test_inbox_respond_stub_verb — send verb="retry" for item_kind="external_issue" → 501 with OperatorStubResponse.

17.4 Library tests

  1. test_library_returns_translated_artifacts — create renders across engagements, verify Operator vocabulary (artifact_id not render_event_id, project_name not engagement_name).
  2. test_library_download_delegates_to_engine — call /operator/library/{id}/download, verify binary response with content-disposition.
  3. test_library_filter_by_project?project_id filters to one engagement's renders.
  4. test_library_non_member_excluded — renders from non-member engagements absent.
  5. test_library_download_non_member_404 — download attempt on non-member artifact → 404. (Confirms O3: membership check is the sole auth gate.)

17.5 Converse tests

  1. test_converse_create_project_first_turn — send message with no project_id, verify candidate engagement created, seed conversation called, response includes project_id, draft_specification, and readiness="drafting".
  2. test_converse_create_project_subsequent_turn — send message with project_id from test 21, verify seed conversation continues on existing candidate.
  3. test_converse_finalize_project — send intent_hint="finalize_project" with project_id, verify instantiation called and confirmation returned.
  4. test_converse_response_includes_side_effects — verify side_effects list populated after project creation.
  5. test_converse_unrecognized_intent — send message with existing project_id but no matching intent, verify companion message explaining limitation.
  6. test_converse_error_surfaces_conversationally — trigger an engine error (e.g., missing key), verify companion-style error message (not HTTP 500).

17.6 Composition tests

  1. test_render_with_review_creates_composition — send valid request, verify 202 with review_id (not composition_id) and status="started".
  2. test_render_with_review_invalid_spec — invalid specification_id → 422 in Operator vocabulary.
  3. test_render_with_review_invalid_ordering — review before produce → 422 in Operator vocabulary.
  4. test_render_with_review_non_member — person not member of project → 404 "Project not found."

17.7 Stub tests

  1. test_story_stub_returns_501 — call /operator/projects/{id}/story, verify 501 with OperatorStubResponse.
  2. test_implied_specs_stub_returns_501 — call /operator/projects/{id}/implied_specifications, verify 501 with OperatorStubResponse.
  3. test_stub_authenticates_first — unauthenticated call to stub endpoint → 401 (not 501).
  4. test_stub_checks_membership — authenticated call with non-member project → 404 (not 501).

17.8 Auth and cross-cutting tests

  1. test_all_operator_endpoints_require_auth — unauthenticated calls to every /operator/... endpoint return 401.
  2. test_project_scoped_endpoints_verify_membership — composition, narrative, implied_specs endpoints with non-member project_id → 404.
  3. test_engine_api_unchanged — existing GET /me/dashboard/active, GET /engagements/{eid}/assertions, GET /engagements/{eid}/compositions/{cid}, and POST /engagements/{eid}/seed/converse still work with engine vocabulary (no regression). (O5 — extended scope.)
  4. test_error_responses_use_operator_vocabulary — trigger a 404 and a 422 on /operator/... endpoints, verify the error messages contain no engine vocabulary.

Estimated total: ~38 new tests.


18. Step structure

Auto-mode posture: Steps 0–12 auto-mode-proceed. Checkpoint A halts until Operator confirms. Checkpoint B halts for tag.

Step 0 — Archive + pre-flight

Archive this CR to docs/phase-crs/phase-40-cr-operator-vocabulary-schemas-v0_2.md.

Run the full pre-flight checklist (§3.2 items 1–13). Report any divergence.

Verification: pre-flight report clean. Baseline reconciled.

Commit: Phase 40 step 0: CR archival and pre-flight.

Step 1 — Module scaffold and helpers

Create src/loomworks/orchestration/ with __init__.py, schemas.py (empty initial), translators.py (empty initial), helpers.py (with actor_from_person and verify_project_membership), and routers/ subdirectory with __init__.py and seven router files (empty initial).

Verification: uv run pytest -v green (no behavioral change — scaffold only). Import from loomworks.orchestration import schemas, translators, helpers succeeds.

Commit: Phase 40 step 1: orchestration module scaffold and helpers.

Step 2 — Operator-vocabulary schemas

Populate schemas.py with all models from §7: OperatorVerb type alias, common schemas, dashboard schemas, inbox schemas, library schemas, converse schemas, composition schemas, stub contract schemas.

Verification: uv run pytest -v green (schemas only — no behavioral change).

Commit: Phase 40 step 2: operator-vocabulary schemas.

Step 3 — Translators

Populate translators.py with all translation functions from §8.1. Each function maps engine objects to Operator-vocabulary objects. Kind-mapping tables as constant dicts.

Verification: uv run pytest -v green.

Commit: Phase 40 step 3: translators.

Step 4 — Vocabulary-wall enforcement test

Write vocabulary-wall tests (§9, tests 1–2). These scan all schemas and the OperatorVerb Literal in orchestration/schemas.py for forbidden engine terms.

Verification: uv run pytest -v green. Both vocabulary-wall tests pass.

Commit: Phase 40 step 4: vocabulary-wall enforcement test.

Step 5 — Dashboard endpoint

Implement GET /operator/dashboard (§10). Wire to Phase 39 endpoint functions. Translate responses.

Write tests 3–6 (§17.2).

Verification: uv run pytest -v green.

Commit: Phase 40 step 5: dashboard endpoint.

Step 6 — Inbox endpoints

Implement GET /operator/inbox and POST /operator/inbox/{item_id}/respond (§11). Translate needs_you items with verbs. Wire respond to engine operations. Default rationale substitution. Composition step_index re-query for accept/revise. Stub-verb 501 handling.

Write tests 7–15 (§17.3).

Verification: uv run pytest -v green.

Commit: Phase 40 step 6: inbox endpoints.

Step 7 — Library endpoints

Implement GET /operator/library and GET /operator/library/{artifact_id}/download (§12). Membership-scoped render query (SQLAlchemy Row). Download delegation with membership-check-as-sole-auth-gate.

Write tests 16–20 (§17.4).

Verification: uv run pytest -v green.

Commit: Phase 40 step 7: library endpoints.

Step 8 — Converse endpoint

Implement POST /operator/converse (§13). Three intents: create_project first turn (bootstrap), create_project subsequent turns, finalize_project. Error-to-companion wrapping. Draft specification and readiness surfacing.

Write tests 21–26 (§17.5).

Verification: uv run pytest -v green.

Commit: Phase 40 step 8: converse endpoint.

Step 9 — Composition endpoint

Implement POST /operator/projects/{project_id}/render_with_review (§14). Wire to Phase 37 composition creation. Membership check via helper. Error translation.

Write tests 27–30 (§17.6).

Verification: uv run pytest -v green.

Commit: Phase 40 step 9: composition endpoint.

Step 10 — Stub endpoints

Implement story and implied_specifications stubs (§15). Full contract schemas, HTTP 501, auth and membership checks via helper.

Write tests 31–34 (§17.7).

Verification: uv run pytest -v green.

Commit: Phase 40 step 10: stub endpoints.

Step 11 — Router registration and cross-cutting tests

Mount all seven routers on the app (§16). Verify all endpoints are accessible.

Write tests 35–38 (§17.8).

Verification: uv run pytest -v green.

Commit: Phase 40 step 11: router registration and cross-cutting tests.

Step 12 — Full suite verification

Full substrate suite passes. No regressions on existing engine endpoints.

Verification: uv run pytest -v green. Test count: ~1,485+ (1,447 baseline + ~38 new), 2 skipped.

Commit: Phase 40 step 12: full suite green.

Checkpoint A — Operator confirms vocabulary wall and live endpoints

Operator verification:

  1. Call GET /operator/dashboard — verify response uses Operator vocabulary throughout. No engine terms visible.
  2. Call GET /operator/inbox — verify items carry typed verbs. Respond to one item — verify engine operation executes.
  3. Call GET /operator/library — verify artifacts list uses Operator vocabulary. Download one artifact.
  4. Call POST /operator/converse with a creation message (no project_id) — verify candidate engagement created, companion-style response in Operator vocabulary, draft_specification populated.
  5. Call POST /operator/converse again with the returned project_id — verify conversation continues.
  6. Call POST /operator/projects/{id}/render_with_review with produce → review steps — verify 202 with review_id.
  7. Call GET /operator/projects/{id}/story — verify 501 stub response.
  8. Call GET /engagements/{eid}/assertions — verify engine API still works unchanged.
  9. Vocabulary-wall tests pass.

On acceptance: proceed to Checkpoint B.

Checkpoint B — Final

Tag substrate repo as phase-40-operator-vocabulary-schemas.

Implementation notes at docs/phase-impl-notes/phase-40-implementation-notes-v0_1.md absorb execution-time surprises and findings.


19. Acceptance gate

This CR is accepted when:

  1. Substrate: all tests pass (~1,485+, 2 skips). Baseline reconciled at Step 0.
  2. New module src/loomworks/orchestration/ exists with schemas, translators, helpers, and seven router files.
  3. Vocabulary-wall enforcement tests pass — no engine vocabulary in /operator/... schemas, including OperatorVerb Literal values.
  4. GET /operator/dashboard returns translated Phase 39 data in Operator vocabulary.
  5. GET /operator/inbox returns items with typed verbs. POST /operator/inbox/{item_id}/respond executes engine operations with correct actor, rationale defaults, and composition step_index re-query.
  6. GET /operator/library returns translated artifacts. Download endpoint returns binary files with membership-check-as-sole-auth-gate.
  7. POST /operator/converse bootstraps project creation (candidate → converse → instantiate) with Operator vocabulary, surfacing draft_specification and readiness.
  8. POST /operator/projects/{id}/render_with_review wraps Phase 37 composition creation with Operator vocabulary.
  9. Stub endpoints return 501 with structured responses and full contract schemas, after auth and membership checks.
  10. Stub verbs in the respond endpoint return 501 with structured response.
  11. All /operator/... endpoints require authentication (401 without).
  12. Project-scoped endpoints verify membership via helper (404 for non-members).
  13. Error responses use Operator vocabulary exclusively.
  14. Existing engine API unchanged — /engagements/..., /me/..., and /seed/... endpoints still work.
  15. No new migrations.

20. Post-CR state

Engine completion. After Phase 40 tags, all seven Operator Layer engine prerequisites (strategy doc §§4–10) are complete:

| Phase | Prerequisite | Tag | |---|---|---| | 34 | External-service polling | phase-34-external-service-polling | | 35 | Render content kinds | phase-35-render-content-kinds | | 36 | Incremental re-render | phase-36-incremental-rerender-and-naming | | 37 | Adapter chaining | phase-37-adapter-chaining-and-composition | | 38 | Specification grammar | phase-38-specification-grammar-declaration | | 39 | Cross-engagement aggregation | phase-39-cross-engagement-aggregation | | 40 | Operator-vocabulary schemas | phase-40-operator-vocabulary-schemas |


21. What this CR does not build


22. Kickoff prompt for the Claude Code session


Read the Change Request document at the path I supply below. This is
CR-2026-053 v0.2, the Phase 40 Change Request. You are the executing
agent named in the CR.

CR path: ~/Downloads/phase-40-cr-operator-vocabulary-schemas-v0_2.md

Phase 40 builds the Orchestration API — a new /operator/... endpoint
surface with the vocabulary wall enforced architecturally. Engine
objects are translated into Operator-vocabulary objects through a
dedicated translators module. This is the final engine phase.

Key points:
  - New module: src/loomworks/orchestration/ with schemas.py,
    translators.py, helpers.py, and routers/ subdirectory.
  - Five LIVE endpoints: dashboard (wraps Phase 39), inbox + respond
    (wraps Phase 39 needs_you with typed verbs), library + download
    (wraps renders), converse (wraps Phase 31 with server-side
    project-creation bootstrap), composition (wraps Phase 37 — LIVE,
    not stub).
  - Two STUB endpoints: story (narrative, 501) and
    implied_specifications (501). Full contract schemas.
  - Vocabulary-wall enforcement test scans all schemas AND the
    OperatorVerb Literal for forbidden engine terms.
  - Converse endpoint handles three-step project creation:
    POST /engagements (candidate) → seed/converse → instantiate.
    Server-side bootstrap — Operator never sees candidate mechanics.
  - Inbox respond: item_kind in request body, typed verb Literal,
    default rationale substitution, composition step_index re-query
    for accept/revise verbs.
  - Helpers: actor_from_person (Person → ActorRef),
    verify_project_membership (person + project_id → 404 if not member).
  - Auth: get_current_person (person-scoped). Library download:
    membership check is the sole auth gate (security-load-bearing).
  - No migrations. Translation layer over existing engine state.
  - ~38 new substrate tests.
  - The [CC verifies] markers in the CR indicate substrate names
    that must be confirmed at pre-flight. The CR specifies
    architectural decisions firmly; substrate locations are verified.

Substrate-only phase. Twelve steps, two checkpoints.

Code baseline: 1,447 tests passed, 2 skipped, Alembic head 0056.

Run pre-flight (Step 0) per Section 3.2. The Step 0 checklist
includes 13 items: Phase 39 dashboard endpoints, Phase 37 composition
endpoints, Phase 31 seed conversation endpoint (at POST
/engagements/{eid}/seed/converse — NOT top-level), engagement
creation endpoint, instantiation endpoint, get_current_person
dependency, render download function, ActorRef construction pattern,
_retire_render location, no existing /operator/ routes.

Per Section 3.3: archive this CR to
docs/phase-crs/phase-40-cr-operator-vocabulary-schemas-v0_2.md
at Step 0 before Step 1 begins.

Per Section 18, twelve steps with two checkpoints. Auto-mode
posture: Steps 0–12 auto-mode-proceed; Checkpoint A halts for
Operator to confirm vocabulary wall, live endpoints, and project-
creation bootstrap.

Pre-flight surprises (Section 3.2 ground-truth divergence) stop
execution at Step 0 and drive a CR amendment; do not proceed
through divergence.

Implementation notes at Checkpoint B:
docs/phase-impl-notes/phase-40-implementation-notes-v0_1.md
absorbs execution-time surprises and findings.

DUNIN7 — Done In Seven LLC — Miami, Florida Phase 40: Operator-vocabulary Response Schemas — CR v0.2 — 2026-05-05