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).
Blockers resolved:
POST /engagements/{eid}/seed/converse) and requires an existing candidate engagement. v0.2 specifies a three-step server-side bootstrap: create candidate → converse → instantiate. New §D7 and rewritten §13.needs_you SQL surfaces object_id (composition UUID) but not step_index. v0.2 specifies re-querying the composition's operational view for current_step_index in the respond handler. Updated §11.2.retract_assertion and _retire_render require a non-null rationale parameter. v0.1's InboxRespondRequest.rationale was optional. v0.2 specifies default rationale strings when the Operator omits one. Updated §7.3 verb mapping table and §11.2.composition, external_polling, external_failure, downstream_specification). v0.2 adds them. Updated §9.1.download inbox verb to "redirect to library download" without specifying the HTTP semantics. v0.2 drops download from the verb set and adds download_url to InboxItem instead. The library endpoint handles downloads; the inbox presents the URL. Updated §7.3.Recommendations absorbed:
actor_from_person helper in §8.3 for engine ops that take actor: ActorRef.item_kind field to the request body. Explicit, no extra DB roundtrip. Updated §7.3.confirmed, amended_superseded, invalidated (shape/render terminal states not surfaced through the Orchestration API). Updated §6.draft_specification and readiness to ConverseResponse. Updated §7.5.verify_project_membership helper in §8.3. Referenced in §§14, 15.1, 15.2.Observations addressed:
get_contributing_contributor on the function-call path) and is therefore security-load-bearing.POST /engagements/{eid}/seed/converse.get_current_person dependency is override-friendly, so the Phase 37 cookie-auth carry-forward does not affect Phase 40 tests.
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.
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:
GET /operator/dashboard — wraps Phase 39's three cross-engagement aggregation endpoints into a single dashboard response. Translates all field names to Operator vocabulary.GET /operator/inbox + POST /operator/inbox/{item_id}/respond — wraps Phase 39's needs_you with actionable verbs. Respond executes the engine operation.GET /operator/library + GET /operator/library/{artifact_id}/download — wraps render queries and the download endpoint with Operator vocabulary.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.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):
GET /operator/projects/{project_id}/story — project narrative. Requires Arc 2 Companion work.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.
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.
CC verifies at Step 0:
/operator/... routes in the application. [CC verifies: grep router registrations in the app factory.]GET /me/dashboard/active, GET /me/dashboard/needs_you, GET /me/dashboard/recent. [CC verifies: router file location and function names.]DashboardActiveItem, DashboardNeedsYouItem, DashboardRecentItem. [CC verifies: schema file location.]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.]RenderComposition with six event kinds. [CC verifies: model file location.]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).]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.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.]src/loomworks/orchestration/ directory._retire_render function exists and is callable (underscore-prefixed private function). [CC verifies: module location.]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.
Archive this CR to docs/phase-crs/phase-40-cr-operator-vocabulary-schemas-v0_2.md at Step 0 before Step 1 begins.
All six scoping decisions from the Phase 40 scoping note v0.1 are adopted. This CR additionally closes the following construction-level decisions.
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.
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).
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.
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.
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:
POST /engagements (with draft body) → creates a candidate engagement.POST /engagements/{eid}/seed/converse → iterate on the seed.POST /engagements/{eid}/instantiate → finalize.
The converse endpoint handles this transparently. On the first create_project turn (no project_id), the endpoint:
POST /engagements function with a minimal draft derived from the Operator's first message).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.]
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.]
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.
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).
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.
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.
# 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
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.
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.
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.
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 |
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_id → confirmed_shape_event_ref, steps[].specialist_label → steps[].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.
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.
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."""
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.download_url=None. A composition with no failure detail translates cleanly. [CC verifies: null-field behavior in the engine response schemas being wrapped.]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.]
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.]
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.
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",
}
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.]
Wraps Phase 39's three dashboard endpoints into a single aggregated response.
Implementation:
get_current_person.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.]
Full paginated list of items needing the Operator's decision, translated from Phase 39's needs_you endpoint.
Implementation:
get_current_person.needs_you query function with the person's memberships.translate_needs_you_to_inbox — adds the verbs list based on item_kind, and populates download_url for ready_artifact items.InboxResponse with pagination.
Query params: ?limit (default 50, max 100), ?cursor (opaque, passed through to Phase 39).
Executes the Operator's chosen action on an inbox item.
Implementation:
get_current_person.InboxRespondRequest — includes item_kind and verb (both typed Literals).verb is allowed for item_kind (per §7.3 verb mapping table). If not, 422.OperatorStubResponse.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.ActorRef from the person via actor_from_person (§8.3).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.)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.
Paginated list of artifacts across the person's engagements, translated from render event queries.
Implementation:
get_current_person.recent). Include all renders in ready (produced) or archived (retired) state.translate_render_to_library (operates on SQLAlchemy Row, per R4).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.
Delegates to the engine download function.
Implementation:
get_current_person.artifact_id to the engine render event ID and engagement ID. [CC verifies: the render_events_view query pattern.]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.)Wraps Phase 31's conversation engine with Operator vocabulary and server-side project-creation bootstrap (D5).
Implementation:
get_current_person.ConverseRequest.
Intent: create_project (first turn — no project_id).
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".]POST /engagements/{eid}/seed/converse) on the new candidate, passing the Operator's message.ConverseTurnResponse into ConverseResponse (§7.5 translation table).project_id populated (the new candidate's UUID).ConverseSideEffect(description="Started a new project", resource_kind="project", resource_id=<uuid>).
Intent: create_project (subsequent turns — project_id provided).
status="ready_for_review", include readiness="ready_for_review" and suggested action "finalize_project".
Intent: finalize_project (intent_hint="finalize_project", project_id provided).
POST /engagements/{eid}/instantiate or equivalent finalization function). [CC verifies: the instantiation endpoint path and function.]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.]
Live endpoint wrapping Phase 37's composition creation.
Implementation:
get_current_person.verify_project_membership(person=person, project_id=project_id, db=db) (§8.3).RenderWithReviewRequest.specification_id → confirmed_shape_event_refsteps[].specialist_label → steps[].specialist_ref (resolve specialist by label or accept opaque reference)steps[].step_type → passthrough (step types are vocabulary-neutral: produce, review, translate, transform, validate)reason → trigger_reasonActorRef from the person via actor_from_person (§8.3).translate_composition_response.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.]
get_current_person.verify_project_membership(person=person, project_id=project_id, db=db) (§8.3).
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.
get_current_person.verify_project_membership(person=person, project_id=project_id, db=db) (§8.3).
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.
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.
test_vocabulary_wall_no_forbidden_field_names — all schemas in orchestration/schemas.py have no engine-vocabulary field names.test_vocabulary_wall_no_forbidden_literal_values — all Literal type annotations (including OperatorVerb) have no engine-vocabulary values.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).test_dashboard_returns_translated_inbox_preview — create a pending shape and a held assertion, verify Operator vocabulary in the inbox preview.test_dashboard_returns_translated_recent — create a produced render, verify Operator vocabulary in the recent section.test_dashboard_empty_state — person with no activity, verify empty lists (not errors).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.)test_inbox_pagination — create multiple items, verify ?limit and ?cursor work.test_inbox_respond_save_note — held assertion → respond with item_kind="waiting_note", verb="save" → assertion committed. Verify companion-style confirmation message.test_inbox_respond_approve_spec — pending shape → respond with item_kind="needs_review", verb="approve" → shape confirmed. Verify confirmation.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.test_inbox_respond_discard_with_rationale — same as 11 but with explicit rationale → verify the Operator's rationale was passed, not the default.test_inbox_respond_invalid_verb — send verb not in allowed list for item_kind → 422.test_inbox_respond_wrong_item — item_id not found → 404.test_inbox_respond_stub_verb — send verb="retry" for item_kind="external_issue" → 501 with OperatorStubResponse.test_library_returns_translated_artifacts — create renders across engagements, verify Operator vocabulary (artifact_id not render_event_id, project_name not engagement_name).test_library_download_delegates_to_engine — call /operator/library/{id}/download, verify binary response with content-disposition.test_library_filter_by_project — ?project_id filters to one engagement's renders.test_library_non_member_excluded — renders from non-member engagements absent.test_library_download_non_member_404 — download attempt on non-member artifact → 404. (Confirms O3: membership check is the sole auth gate.)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".test_converse_create_project_subsequent_turn — send message with project_id from test 21, verify seed conversation continues on existing candidate.test_converse_finalize_project — send intent_hint="finalize_project" with project_id, verify instantiation called and confirmation returned.test_converse_response_includes_side_effects — verify side_effects list populated after project creation.test_converse_unrecognized_intent — send message with existing project_id but no matching intent, verify companion message explaining limitation.test_converse_error_surfaces_conversationally — trigger an engine error (e.g., missing key), verify companion-style error message (not HTTP 500).test_render_with_review_creates_composition — send valid request, verify 202 with review_id (not composition_id) and status="started".test_render_with_review_invalid_spec — invalid specification_id → 422 in Operator vocabulary.test_render_with_review_invalid_ordering — review before produce → 422 in Operator vocabulary.test_render_with_review_non_member — person not member of project → 404 "Project not found."test_story_stub_returns_501 — call /operator/projects/{id}/story, verify 501 with OperatorStubResponse.test_implied_specs_stub_returns_501 — call /operator/projects/{id}/implied_specifications, verify 501 with OperatorStubResponse.test_stub_authenticates_first — unauthenticated call to stub endpoint → 401 (not 501).test_stub_checks_membership — authenticated call with non-member project → 404 (not 501).test_all_operator_endpoints_require_auth — unauthenticated calls to every /operator/... endpoint return 401.test_project_scoped_endpoints_verify_membership — composition, narrative, implied_specs endpoints with non-member project_id → 404.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.)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.
Auto-mode posture: Steps 0–12 auto-mode-proceed. Checkpoint A halts until Operator confirms. Checkpoint B halts for tag.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Operator verification:
GET /operator/dashboard — verify response uses Operator vocabulary throughout. No engine terms visible.GET /operator/inbox — verify items carry typed verbs. Respond to one item — verify engine operation executes.GET /operator/library — verify artifacts list uses Operator vocabulary. Download one artifact.POST /operator/converse with a creation message (no project_id) — verify candidate engagement created, companion-style response in Operator vocabulary, draft_specification populated.POST /operator/converse again with the returned project_id — verify conversation continues.POST /operator/projects/{id}/render_with_review with produce → review steps — verify 202 with review_id.GET /operator/projects/{id}/story — verify 501 stub response.GET /engagements/{eid}/assertions — verify engine API still works unchanged.On acceptance: proceed to Checkpoint B.
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.
This CR is accepted when:
src/loomworks/orchestration/ exists with schemas, translators, helpers, and seven router files./operator/... schemas, including OperatorVerb Literal values.GET /operator/dashboard returns translated Phase 39 data in Operator vocabulary.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.GET /operator/library returns translated artifacts. Download endpoint returns binary files with membership-check-as-sole-auth-gate.POST /operator/converse bootstraps project creation (candidate → converse → instantiate) with Operator vocabulary, surfacing draft_specification and readiness.POST /operator/projects/{id}/render_with_review wraps Phase 37 composition creation with Operator vocabulary./operator/... endpoints require authentication (401 without)./engagements/..., /me/..., and /seed/... endpoints still work.src/loomworks/orchestration/ module with schemas, translators, helpers, and seven endpoint routers. Five live endpoints, two stubs. Vocabulary-wall enforcement test. OperatorVerb type alias. actor_from_person and verify_project_membership helpers.phase-40-operator-vocabulary-schemas on substrate repo.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 |
/operator/converse. Ships create_project and finalize_project intents only. Arc 2 adds: add knowledge, request draft, approve, revise, request artifact, request download, ask about progress, ask about past input, request redirect.retry, dismiss, start_project, and request_revision are in the contract but return 501 when invoked. The engine operations for these verbs don't exist yet.retire_render wrapper. Phase 40 imports _retire_render directly (cross-module private access). A public wrapper is deferred unless CC judges it trivial.
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