DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/phase-52-jurisdiction-routing/phase-52-cr-jurisdiction-routing-v0_1.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-067 — Phase 52: Jurisdiction Routing (v0.1)

Version. v0.1 Date. 2026-05-10 Author. Claude (drafting) / Marvin Percival (approving). Target. /Users/dunin7/loomworks-engine on DUNIN7-M4 (MacMini M4). No work in loomworks (Operator Layer) or loomworks-marketing repos by default. Baseline reference. Tag phase-51-marketing-site-and-companion-email:

Phase 51 close per impl notes v0.2: 2,121 substrate tests passed, 25 skipped, Alembic head 0064, working tree clean (engine); 139 vitest passed, 11 prerendered routes, eslint/tsc/build clean (Operator Layer, unchanged from Phase 50 baseline per Phase 51 V7).

Priority. Standard. Confidential. Internal DUNIN7. Supersedes. No prior Phase 52 CR — v0.1 is the first. CR number. CR-2026-067 is the expected value if Phase 51 was CR-2026-066. [CC verifies at Step 0 pre-flight item 1] against engine repo docs/phase-crs/ directory listing; advance if taken. Companion to. loomworks-phase-52-scoping-note-v0_2.md (authoritative scope; absorbs Step 0 findings); loomworks-phase-52-cr-drafting-handoff-v0_1.md (drafting instructions for this CR); phase-52-step-0-findings-v0_1.md (engine repo docs/phase-impl-notes/; verified live-codebase state, absorbed into scoping v0.2); phase-51-cr-marketing-site-and-companion-email-v0_1.md (substrate Phase 52 extends; structural template for this CR); phase-50-cr-companion-as-authority-and-public-form-v0_1.md §10 (Memory event registration template per V4); phase-45-cr-delegation-contract-and-approval-cards-v0_1.md (register_action_dispatcher mirror per V3); current-status-manifest-v0_36.md. Status. Pre-execution CR. Ready for Operator review and approval. Step 0 pre-flight runs against this version.


Contents


1. Executive summary

Phase 52 is the smallest substrate phase since the credit-system arc opened. Single sub-arc; engine repo only; ~6–9 new substrate tests; build-time estimate 0.75–1.25 hours.

Sub-arc 1 — Substrate (engine). Phase 52 lands the Authority-side jurisdiction routing primitive deferred from Phase 51 §19 item 3. A new routing-skill registry mirrors Phase 45's register_action_dispatcher pattern (replace-on-duplicate, Optional resolve), exposing register_jurisdiction_route(jurisdiction, destination_instance) and resolve_destination(jurisdiction). Default registration at module load points all jurisdictions at the Authority instance via a new settings field loomworks_authority_instance_url: str = "". A new routing-decision step inserts into the email composition seam before build_claim_url is called: it resolves the source GrantRequestReceived event's jurisdiction value via the existing _load_grant_request helper, calls resolve_destination, writes a new jurisdiction_routing_decided Memory event mirroring Phase 50's grant_request_received registration, and threads the destination URL into an extended build_claim_url(..., base_url=...) whose new optional base_url: str | None = None parameter defaults to settings.webauthn_rp_origin for backward compatibility.

Sub-arc 2 — Operator Layer. Empty by default. If Phase 51 §19 item 1 (modify-the-email-body) trigger fires during build, mid-build amendment scoping per substrate-friction-discipline-pattern absorbs.

Sub-arc 3 — Marketing site. Empty. Phase 52 doesn't touch the marketing repo.

By the close of Phase 52, a form submission carrying jurisdiction=US|EU|Other produces an audit-trailed routing decision in CME Memory and a destination-aware claim URL in the Companion-composed email. The routing primitive is in place for the multi-instance fleet trajectory; the default destination (Authority) means single-instance behavior is unchanged for any caller.

Tag at completion: phase-52-jurisdiction-routing on engine repo only.

Build-time estimate: 0.75–1.25 hours total (substrate sub-arc 1 only).


2. In-scope and out-of-scope

2.1 In-scope

Per scoping note v0.2 §1 + §5.1, fully transposed:

2.2 Out-of-scope (per scoping v0.2 §8 / handoff §6)

These items are deferred-pending-trigger, conditional, opportunistic, speculative, or out of Phase 52 entirely. Phase 53+ candidates listed in §18; the dispositions below match scoping v0.2 §8.

2.3 No voice content shipping

Unlike Phases 49 / 50 / 51 (which shipped voice templates with as-shipped baseline content), Phase 52 ships no voice content. The routing-skill produces no Companion-rendered prose; jurisdiction_routing_decided is a substrate Memory event without a voice surface. There is no §7-equivalent voice-template section in this CR.


3. Prerequisites

3.1 Baseline at Phase 52 Step 0

If the baseline diverges (substrate test count off by more than ±2 routine noise, Alembic head different, working tree dirty, tag missing on engine, marketing site not live), CC stops at the start of Step 0 and reports before running any pre-flight items.

3.2 Step 0 verifications absorbed in scoping v0.2

The scoping note v0.2 absorbed six verifications run by CC against the live codebase, recorded in phase-52-step-0-findings-v0_1.md at engine repo docs/phase-impl-notes/. All resolved as HOLDS or PARTIALLY-HOLDS-with-clear-resolution. Consolidated state per the brief's framework: State 1 — proceed to CR drafting after v0.2 absorption.

| # | Verification | Verdict | Where absorbed | |---|--------------|---------|----------------| | V1 | Email composition seam shape (Phase 51 post-tag) | PARTIALLY HOLDS — claim_url before composition; build_claim_url extension path | scoping v0.2 §3 V1; §4 P52-D2 reshape; §5.1.2 | | V2 | jurisdiction field flow from GrantRequestReceived to seam | HOLDS via Memory event read; _load_grant_request mirror | scoping v0.2 §3 V2; §4 P52-D10 (selects option (a)); §5.1.2 | | V3 | Declare-and-register pattern (Phase 38 vs 16 vs 45) | HOLDS with naming-only divergence — Phase 45's register_action_dispatcher is the cleaner mirror | scoping v0.2 §3 V3; §4 P52-D1 (redirected from Phase 38 to Phase 45) | | V4 | Memory event schema registration | HOLDS — mirrors Phase 50's grant_request_received exactly | scoping v0.2 §3 V4; §4 P52-D3; §5.1.3 | | V5 | Settings field for Authority instance URL | HOLDS — exists but needs a Phase 52 addition (loomworks_authority_instance_url: str = "") | scoping v0.2 §3 V5; §4 P52-D9; §5.1.4 | | V6 | Phase 51 impl notes v0.2 §11 addendum | PARTIALLY HOLDS — sharper generalization than v0.1's paraphrase; two-cascade enumeration with two-question absorption recipe | scoping v0.2 §3 V6; §13 trajectory note |

These six are not re-run at CR-execution time. The pre-flight Step 0 list in §3.3 below covers CR-time deltas only — exact strings, line numbers, module paths that the scoping verifications didn't resolve.

3.3 Pre-flight Step 0 (CR-time)

Before Step 1 begins, CC archives this CR at docs/phase-crs/phase-52-cr-jurisdiction-routing-v0_1.md in the engine repo and runs the following pre-flight reads. These are scoped to confirm specific live strings, signatures, and patterns that the CR specifies but did not read directly during drafting (see handoff §4 — drafter could not access phase-52-step-0-findings-v0_1.md from the CR-drafting environment, so several exact-string verifications carry through to CR-execution time as [CC verifies at Step 0 pre-flight item N] markers).

  1. CR registry. Confirm CR-2026-067 is the next available CR number against engine repo docs/phase-crs/ directory listing; advance if taken.
  1. seam.issue_grant exact module path and current grant_kind='form_initiated' branch shape. Per V1 confirmation; CR drafter could not specify exact line numbers. Read the seam module (likely src/loomworks/credit/seam.py or per V1 evidence in findings doc). Confirm complete signature, the grant_kind='form_initiated' branch shape, the existing position of build_claim_url(...) call within that branch (V1 confirmed: claim_url is constructed before composition; composer takes claim_url via context). Identify the exact insertion site for §10's routing-decision step (immediately before the build_claim_url call, after context-prep).
  1. _load_grant_request exact import path and signature. Per V2 confirmation; the helper exists and is reusable for the _load_grant_request mirror approach. Read the exact module path (likely a sibling or co-located helper in the seam / credit module) and signature so §10 can specify the import statement and call shape directly. Confirm sync vs async; confirm whether it requires a transaction context or other ceremony.
  1. build_claim_url exact module path, signature, and call-site count. Per V1 confirmation; the helper is the extension target for P52-D2's option (a). Read the exact signature, the existing call-sites, and any type-checking implications of adding base_url: str | None = None so §11 specifies the extension exactly.
  1. webauthn_rp_origin settings field exact location. Per V5 evidence. Read the exact field declaration in the settings module (likely src/loomworks/config/settings.py or per V5 evidence) so §8 (new field) and §11 (extended helper default) specify the convention precisely.
  1. register_action_dispatcher exact module path and signature. Per V3 confirmation; Phase 45's pattern is Phase 52's mirror for P52-D1's default-then-override semantics. Read the exact signature, replace-on-duplicate behavior, and Optional return shape (resolve_action_dispatcher or comparable) so §7 specifies register_jurisdiction_route against verbatim sibling.
  1. Phase 50 grant_request_received Memory event registration exact module location. Per V4 confirmation; the mirror is exact. Read the registration block (typed MemoryObject subclass + Literal[…] object_type + append_event(event_kind="…") call + optional OBJECT_TYPE_REGISTRY entry per the Phase 50 deferral precedent) so §9 ships a parallel block against verbatim template.
  1. Settings module exact location and pydantic-settings class shape. Per V5 confirmation; Phase 52 adds a new field (loomworks_authority_instance_url: str = ""). Read the exact module path and pydantic-settings class declaration so §8 specifies the field add (insertion location, default value, ordering against existing fields) precisely.
  1. Routing-skill module location convention. Scoping v0.2 §5.1.1 sketches loomworks/seam/routing/jurisdiction_router.py as the provisional path. Confirm against actual seam directory layout (existence of loomworks/seam/; subdirectory conventions; __init__.py patterns; whether sibling routing / dispatch modules already exist at that level). If the sketched path doesn't fit the existing seam convention, §7 adjusts to the closest conventional path; the v0.2 sketch is provisional.

If any of items 1–9 reveal architectural divergence (not just naming or exact-string), CC halts and surfaces — does not draft and hope. Naming-only divergences absorb in-flight per the standard discipline.

3.4 CR archival

Archive this CR at:


/Users/dunin7/loomworks-engine/docs/phase-crs/phase-52-cr-jurisdiction-routing-v0_1.md

before Step 1 begins. Per Phase 47 / 48 / 49 / 50 / 51 standard pattern.


4. Construction decisions this CR closes

Ten decisions, settled in scoping note v0.2 §4. CC executes against them; does not re-decide. No decisions remain [Operator confirms] at CR-execution time.

P52-D1. Routing-skill registry mirrors Phase 45's register_action_dispatcher pattern. Replace-on-duplicate; Optional resolve. Per V3 finding. Phase 38 grammar dict (raises on duplicate) and Phase 16 ExtractionSkillRegistry class are alternative templates available if drafting / Step 0 surfaces a fit problem with Phase 45 — but the v0.2 baseline is Phase 45. Function naming follows Phase 45's verbatim shape: register_jurisdiction_route and resolve_destination. [CC verifies at Step 0 pre-flight item 6] exact register_action_dispatcher signature.

P52-D2. Routing-decision step lands BEFORE build_claim_url is called; build_claim_url extended with optional base_url: str | None = None parameter. Per V1 finding. The actual seam sequence has claim_url construction before composition (composer takes claim_url via context), not after as v0.1 read. v0.2 sub-decision: extend the helper additively (option (a) of V1's three options); routing-aware variant (option (b) — build_claim_url_for_destination) and inline formatting (option (c)) set aside. Default behavior when base_url is None reads settings.webauthn_rp_origin, preserving backward compatibility with all existing callers.

P52-D3. jurisdiction_routing_decided Memory event mirrors Phase 50's grant_request_received exactly. Per V4 finding. Typed MemoryObject subclass; Literal[…] object_type; append_event(event_kind="jurisdiction_routing_decided"); optional OBJECT_TYPE_REGISTRY entry per the Phase 50 deferral precedent. Schema: jurisdiction (string), destination_instance_label (string, e.g., 'Authority'), destination_instance_url (string), source_grant_request_event_id (FK reference to the source GrantRequestReceived event), decided_at (timestamp).

P52-D4. Items 1 and 2 deferred-pending-trigger. Phase 51 §19 item 1 (modify-the-email-body) waits for Operator iteration to surface a concrete need. Phase 51 §19 item 2 (CORS wildcard pattern support) waits for Cloudflare Pages preview-URL form testing to become load-bearing. If either trigger fires during Phase 52 build, mid-build amendment scoping per substrate-friction-discipline-pattern absorbs; reserved buffer slots accommodate.

P52-D5. Operational items as parallel non-CR track; deploy-target-naming discipline applied. Engine deployment via M4 tunnel, mailbox provisioning, cloudflared upgrade proceed independently of Phase 52 build. The deploy target is named in scoping (api.loomworks.dunin7.com via M4 tunnel as the Authority instance URL; loomworks_authority_instance_url settings field per P52-D9; marketing site domain loomworks.doneinseven.com inherited live from Phase 51) per the V6 two-question absorption recipe ("where does the deploy target live?" and "when does it go live?") applied to each cascade. Smoke-test acceptance gate 7 (live engine smoke-test) is conditional: passes with explicit deferred-until-deployment annotation if engine deployment hasn't landed by Phase 52 close; mock-engine smoke tests run unconditionally.

P52-D6. Build-time estimate. 0.75–1.25 hours total. Substrate sub-arc 1 only. Smallest substrate phase since the credit-system arc opened.

P52-D7. Reserved buffer slots: 3 slots, "unconsumed if no amendment arises." Mirrors Phase 50 / 51 reserved-slot pattern (which Phase 50 left fully unconsumed; Phase 51 left fully unconsumed per impl notes v0.2).

P52-D8. Tag name. phase-52-jurisdiction-routing on engine repo only. No tags on Operator Layer or marketing repos by default — Phase 52 has no work in those repos. If sub-arc 2 lands actual work via Item 1 trigger during build (per P52-D4), marker-tag convention follows Phase 51 V7 precedent (Operator Layer tag lands on the Phase 51 commit as a marker).

P52-D9. Settings field name: loomworks_authority_instance_url: str = "". Per V5 finding. No Authority-engine-URL setting exists today (webauthn_rp_origin is Operator-Layer surface — different surface, not reusable). Phase 52 introduces the new field per the loomworks_* convention. Empty-string default signals "not configured"; routing-skill default registration handles the empty case by reading the settings value at module-load time and registering Authority with the (possibly empty) URL.

P52-D10. Jurisdiction-read approach: re-load source GrantRequestReceived event via metadata["source_grant_request_event_id"] using _load_grant_request mirror. Per V2 finding. Two options were available:

Option (a) is the no-Phase-50-touch path consistent with "Cleanup and tighten." Option (b) is recorded as set-aside-but-available — if a future phase introduces a need to thread additional fields onto held-proposal metadata, the _author_proposal_assertion extension is the available path. Phase 52 doesn't take it (see §18 Phase 53+ candidates).


5. Migration

No Migration 0065 anticipated. Alembic head remains 0064 at Phase 52 close.

V4 confirmed reuse of the Phase 50 Memory event registration mechanism — typed MemoryObject subclass declaration plus append_event call do not require a schema or column change. V5 confirmed a new pydantic-settings field is a code-only addition. V1, V2, V3, V6 confirm no migration-touching surfaces.

If CC discovers a need during implementation (e.g., the jurisdiction_routing_decided event registration turns out to require a new Phase 38 grammar entry that hits a migration boundary, or the settings field add reveals a column-backed config table), halt and surface per §16 halt conditions. The expected case is no migration.


6. Backend modules and changes (sub-arc 1)

Sub-arc 1 touches the engine repo at the following surfaces. Section references below specify each in detail. All paths below are provisional and verified at Step 0; live evidence in phase-52-step-0-findings-v0_1.md and Step 0 pre-flight item N reads (per §3.3) substitutes exact paths.

| File / module | Change | Section | |---------------|--------|---------| | loomworks/seam/routing/jurisdiction_router.py (provisional path; per Step 0 pre-flight item 9) | New module — routing-skill registry; default registration block | §7 | | Settings module (per V5 evidence; per Step 0 pre-flight items 5, 8) | Additive field loomworks_authority_instance_url: str = "" | §8 | | Memory event registration module (per V4 evidence; per Step 0 pre-flight item 7) | New JurisdictionRoutingDecided MemoryObject subclass + OBJECT_TYPE_REGISTRY entry (if pattern requires) + append_event(event_kind="jurisdiction_routing_decided") call site in seam | §9 | | seam.issue_grant (per V1 evidence; per Step 0 pre-flight item 2) | New routing-decision step inserted into grant_kind='form_initiated' branch, before the build_claim_url call | §10 | | build_claim_url (per V1 evidence; per Step 0 pre-flight item 4) | Additive optional parameter base_url: str | None = None; default falls back to settings.webauthn_rp_origin | §11 | | _load_grant_request (per V2 evidence; per Step 0 pre-flight item 3) | No changes — reused as-is for the jurisdiction read | §10 |

No Operator Layer changes per P52-D4 default (§12). No marketing repo changes per P52-D8 (§13).


7. Routing-skill registry

7.1 Path

Provisional: loomworks/seam/routing/jurisdiction_router.py. [CC verifies at Step 0 pre-flight item 9] against actual seam directory layout. Adjacent considerations:

7.2 Mirror to Phase 45

Per P52-D1 / V3, Phase 45's register_action_dispatcher is the verbatim sibling. Phase 52 mirrors:

[CC verifies at Step 0 pre-flight item 6] exact register_action_dispatcher signature and resolve_action_dispatcher (or comparable) return shape; §7.3 below adopts verbatim.

7.3 Module surface

Approximate shape (CC adapts at Step 1 per live evidence):


# loomworks/seam/routing/jurisdiction_router.py
# [CC verifies module path at Step 0 pre-flight item 9]

from typing import Optional
from pydantic import BaseModel

from loomworks.config import settings  # [CC verifies import at Step 0 pre-flight item 5]


class DestinationInstance(BaseModel):
    label: str          # e.g., "Authority"
    url: str            # destination instance base URL


# Module-level registry — mirrors Phase 45's action_dispatcher dict shape.
# [CC verifies registry container shape at Step 0 pre-flight item 6]
_JURISDICTION_ROUTES: dict[str, DestinationInstance] = {}


def register_jurisdiction_route(
    jurisdiction: str,
    destination_instance: DestinationInstance,
) -> None:
    """
    Register a destination instance for a jurisdiction. Replace-on-duplicate
    per P52-D1 / Phase 45 mirror — re-registering a jurisdiction silently
    replaces the prior destination.
    """
    _JURISDICTION_ROUTES[jurisdiction] = destination_instance


def resolve_destination(jurisdiction: str) -> Optional[DestinationInstance]:
    """
    Resolve the destination instance for a jurisdiction. Returns None if the
    jurisdiction key is unknown — mirrors Phase 45's Optional return shape.
    """
    return _JURISDICTION_ROUTES.get(jurisdiction)


def _register_default_destinations() -> None:
    """
    Default registration at module load: Authority instance for all known
    jurisdiction values per Phase 51's marketing-site form (US, EU, Other).
    Reads the destination URL from settings.loomworks_authority_instance_url
    per P52-D9; an empty-string value means "not configured" — registration
    proceeds with the empty URL and the operational track populates the
    field at deploy time.
    """
    authority = DestinationInstance(
        label="Authority",
        url=settings.loomworks_authority_instance_url,
    )
    for jurisdiction in ("US", "EU", "Other"):
        register_jurisdiction_route(jurisdiction, authority)


_register_default_destinations()

Exact shape: [CC verifies at Step 0 pre-flight items 6 and 9]. Naming-only divergences from register_action_dispatcher / resolve_action_dispatcher (e.g., parameter ordering, container naming) absorb in-flight per the standard discipline.

7.4 Default registration timing

Default registration runs at module import. If the settings module is imported lazily and _JURISDICTION_ROUTES is read before _register_default_destinations has run, resolve_destination returns None — the seam treats None as the no-routing-information case (see §10.5). The substrate test list includes a registry-empty-case test (§17 test 1c) that asserts the explicit None return, even though normal startup ordering ensures the default registration always runs.

7.5 Phase 38 grammar dict and Phase 16 ExtractionSkillRegistry as alternatives

Per P52-D1, if Step 1 surfaces a fit problem with Phase 45's pattern, Phase 38 (raises on duplicate) and Phase 16 (ExtractionSkillRegistry class-based) are alternative templates. Halt-and-surface per §16 — a fit problem is a substrate-friction-discipline-pattern threshold, not an in-flight resolution.


8. Settings field — loomworks_authority_instance_url

8.1 What

Add loomworks_authority_instance_url: str = "" to the engine's pydantic-settings class per P52-D9 / V5.

8.2 No Authority-URL setting exists today

Per V5 confirmation. The existing webauthn_rp_origin is the Operator-Layer's origin — different surface, not reusable for the Authority instance URL. Phase 52 introduces the new field as a direct addition; no field repurposing or renaming.

8.3 Pydantic-settings class addition

Approximate shape (CC adapts at Step 1 per live evidence):


# Settings module — provisional path; [CC verifies at Step 0 pre-flight items 5 and 8]

class LoomworksSettings(BaseSettings):
    # ... existing fields, including webauthn_rp_origin per V5 evidence ...

    loomworks_authority_instance_url: str = ""  # NEW IN PHASE 52 — P52-D9 / V5

    # ... remaining existing fields ...

The new field's position in the class is additive (no reordering of existing fields). Convention per loomworks_* prefix; [CC verifies at Step 0 pre-flight item 8] whether existing fields use the loomworks_ prefix consistently or follow another convention.

8.4 Empty-string default semantics

"" signals "not configured." The routing-skill's default-registration code (§7.3) reads the field at module load and registers Authority with the (possibly empty) URL. Substrate tests inject a fixture value via monkeypatch / env var override; smoke tests use the live deploy value when available.

When the field is empty:

8.5 Operational track populates at deploy time

Per P52-D5 / scoping v0.2 §13 deploy-target-naming discipline: the Authority instance URL is named in scoping (api.loomworks.dunin7.com) but the operational work to make it live runs in parallel. When engine deployment lands via M4 tunnel, the field gets set in the engine's runtime environment (env var LOOMWORKS_AUTHORITY_INSTANCE_URL per the loomworks_* convention and pydantic-settings env-var binding). Module load on app startup re-reads the field's value.

The smoke-test acceptance gate 7 (§15) is conditional on this operational deployment landing; if not live by Phase 52 close, the gate passes with an explicit deferred-until-deployment annotation.


9. Memory event registration — jurisdiction_routing_decided

9.1 What

Register a new typed MemoryObject subclass for the jurisdiction_routing_decided event, mirroring Phase 50's grant_request_received registration exactly per P52-D3 / V4.

9.2 Mirror to Phase 50

Per V4, Phase 50's grant_request_received is the verbatim template. CC reads the Phase 50 source at Step 0 pre-flight item 7 and produces a parallel registration block. The mirror covers four elements:

  1. Typed MemoryObject subclassJurisdictionRoutingDecided(MemoryObject) with explicit fields.
  2. Literal[…] object_typeLiteral["jurisdiction_routing_decided"] matching the event-kind literal.
  3. append_event(event_kind="jurisdiction_routing_decided") call site in the seam (see §10.6 below).
  4. Optional OBJECT_TYPE_REGISTRY entry — per the Phase 50 deferral precedent. If Phase 50's GrantRequestReceived was deferred from registry inclusion, JurisdictionRoutingDecided is deferred similarly; if Phase 50 included it, Phase 52 includes JurisdictionRoutingDecided. [CC verifies at Step 0 pre-flight item 7] against the Phase 50 registration block.

9.3 Schema

Per P52-D3:


# Memory event module — [CC verifies module path at Step 0 pre-flight item 7]

from datetime import datetime
from typing import Literal
from loomworks.memory.objects import MemoryObject  # [CC verifies import path at Step 0 pre-flight item 7]


class JurisdictionRoutingDecided(MemoryObject):
    """
    Memory event recording a jurisdiction-to-destination-instance routing
    decision made during email composition for a form-initiated grant.

    Mirrors Phase 50's GrantRequestReceived registration (per P52-D3 / V4).
    """
    object_type: Literal["jurisdiction_routing_decided"] = "jurisdiction_routing_decided"
    jurisdiction: str
    destination_instance_label: str         # e.g., "Authority"
    destination_instance_url: str           # may be "" if settings field is empty (P52-D9)
    source_grant_request_event_id: str      # FK to source GrantRequestReceived event
    decided_at: datetime

Exact shape: [CC verifies at Step 0 pre-flight item 7] against Phase 50's GrantRequestReceived registration — field-order convention, base-class import path, frozen / mutable posture, datetime tz handling. Naming-only divergences from Phase 50's conventions absorb in-flight.

9.4 OBJECT_TYPE_REGISTRY entry (if required)

Per the Phase 50 precedent. [CC verifies at Step 0 pre-flight item 7] whether the registry pattern requires explicit registration or whether MemoryObject subclasses register automatically via a metaclass / __init_subclass__ mechanism.

If explicit registration is required (Phase 50 pattern):


# Same module or registry index — [CC verifies at Step 0 pre-flight item 7]

OBJECT_TYPE_REGISTRY: dict[str, type[MemoryObject]] = {
    # ... existing entries (including "grant_request_received": GrantRequestReceived per Phase 50) ...
    "jurisdiction_routing_decided": JurisdictionRoutingDecided,  # NEW IN PHASE 52
}

If automatic registration is in place, the subclass declaration alone suffices and the explicit registry entry is omitted.

9.5 Event-kind literal canonicalization

The event-kind literal "jurisdiction_routing_decided" follows Phase 50's snake_case past-tense convention (grant_request_received, grant_decision_proposal, etc.). [CC verifies convention at Step 0 pre-flight item 7]. If a different canonical form is in use elsewhere (e.g., a string constant module enumerating event kinds), the literal lands in that location alongside Phase 50's existing entries.

9.6 No migration

Memory event registration is a code-only addition. The events themselves are appended to the existing event log via append_event (§10.6). No new tables, no new columns, no Alembic migration. Per §5.


10. Routing-decision step in the email composition seam

This is the substantive substrate section. Per scoping v0.2 §5.1.2 and V1 / V2 / P52-D2 / P52-D10.

10.1 Architectural shape

The routing-decision step is synchronous inline code in the seam.issue_grant(grant_kind='form_initiated', ...) post-grant-write phase, inserted into the Phase 51 email composition path before build_claim_url is called. Distinguishing characteristics:

This is architecturally distinct from Phase 51's Companion-driven inline email composition step (which makes a Companion-voice LLM call) and from Phase 50's grant-decision dispatcher (which produces a held assertion). Phase 52's routing-decision is the simplest of the three: a deterministic table lookup with audit-trail emission.

10.2 Insertion site in seam

Per V1 / Step 0 pre-flight item 2, CC reads the existing grant_kind='form_initiated' branch in seam.issue_grant. The Phase 51 substrate has this branch handling row-write, then build_claim_url(...) construction (passed via context to the composer), then Companion-driven inline composition, then send_email fire-and-forget. Phase 52 inserts a new step immediately before the build_claim_url call, after context-prep.

The full sequence per P52-D2 reshape:

  1. Context-prep (existing). Recipient, grant context loaded.
  2. Resolve jurisdiction from source GrantRequestReceived event via _load_grant_request(metadata["source_grant_request_event_id"]) per P52-D10. (See §10.5.)
  3. Routing-decision (NEW). destination = resolve_destination(jurisdiction). Write jurisdiction_routing_decided Memory event per §10.6.
  4. Extended claim_url construction (MODIFIED). claim_url = build_claim_url(..., base_url=destination.url if destination else None). Helper signature gains base_url: str | None = None per §11; when base_url is None or empty, helper falls back to settings.webauthn_rp_origin for backward compatibility.
  5. Composition (existing). Template loads; composer reads context including the destination-instance-aware claim_url.
  6. Send (existing). Phase 48 SMTP path, unchanged.

[CC verifies at Step 0 pre-flight item 2] exact insertion site, line numbers, and the surrounding Phase 51 inline-composition code so §10.4 and §10.5 below land cleanly.

10.3 Implementation sketch

Approximate shape (CC adapts at Step 2 per live evidence):


# seam.issue_grant body (form_initiated branch) — [CC verifies module path at Step 0 pre-flight item 2]

async def issue_grant(grant_kind: GrantKind, ...) -> Grant:
    # ... Phase 50 / 51 substrate: branch resolution, validation, row write,
    #     post-write Phase 51 composition setup, etc. ...

    if grant_kind == 'form_initiated':
        # ... existing Phase 51 context-prep (recipient + grant context) ...

        # NEW IN PHASE 52: routing-decision step (P52-D2 / P52-D10)
        source_event_id = metadata.get("source_grant_request_event_id")  # [CC verifies metadata key at Step 0 pre-flight item 2]
        source_event = await _load_grant_request(source_event_id)  # [CC verifies signature at Step 0 pre-flight item 3]
        jurisdiction = source_event.jurisdiction  # additive optional Phase 51 field
        destination = resolve_destination(jurisdiction)  # §7.3 / P52-D1

        # Write jurisdiction_routing_decided Memory event (§9 / P52-D3)
        await append_event(  # [CC verifies append_event signature at Step 0 pre-flight item 7]
            event_kind="jurisdiction_routing_decided",
            payload={
                "jurisdiction": jurisdiction,
                "destination_instance_label": destination.label if destination else "",
                "destination_instance_url": destination.url if destination else "",
                "source_grant_request_event_id": source_event_id,
                "decided_at": datetime.utcnow(),  # [CC verifies tz convention at Step 0]
            },
            # ... actor / engagement / assertion-context per Phase 50 pattern ...
        )

        # MODIFIED: build_claim_url now accepts base_url override (P52-D2 / §11)
        base_url = destination.url if destination and destination.url else None
        claim_url = build_claim_url(..., base_url=base_url)  # [CC verifies signature at Step 0 pre-flight item 4]

        # ... existing Phase 51 composition + send (unchanged) ...

Exact shape: [CC verifies at Step 0 pre-flight items 2, 3, 4, 7]. The implementation lands as a contiguous insertion between Phase 51's context-prep and build_claim_url invocation; no Phase 51 code is moved or restructured.

10.4 Error-recovery semantics

Three failure modes surface:

  1. source_grant_request_event_id is missing from metadata. This shouldn't occur for form-initiated grants (Phase 50 substrate threads the source event id through; Phase 51 V5 confirmed the additive jurisdiction field is captured at form submission), but defensive handling: log warning, treat jurisdiction as None / unknown, resolve_destination(None) returns None, base_url falls back to settings.webauthn_rp_origin (= today's behavior). Grant write is already committed; email sends to the default destination. The form-initiated flow is preserved.
  1. _load_grant_request raises (event not found, transaction context issue). Halt-and-surface per §16 — V2 verdict was HOLDS via Memory event read; mid-build / runtime surface that contradicts warrants amendment scoping. At runtime in production (post-CR), the same applies: log error, Companion notifies Operator. Phase 52 alpha does not add retry / fallback behaviors beyond the default-to-Authority posture.
  1. resolve_destination(jurisdiction) returns None. The jurisdiction value isn't in the routing registry — either the form posted an unexpected value (e.g., 'XX' from a manual API call per Phase 51 §11.3) or the default registration didn't run. The seam treats None as "fall back to default webauthn_rp_origin-based URL" (per §11). The Memory event is still written with empty destination_instance_label and destination_instance_url so the audit trail records the "no routing decision" case explicitly. Test 2c (§17) asserts this behavior.

10.5 Jurisdiction read approach (P52-D10)

Per V2 / P52-D10, the seam re-loads the source GrantRequestReceived event via _load_grant_request(metadata["source_grant_request_event_id"]) and reads jurisdiction from the loaded event's payload. This is option (a) — pure additive, no Phase 50 surface modification.

Option (b) — threading jurisdiction onto held-proposal metadata at _author_proposal_assertion — was set aside per scoping v0.2 §8 and is recorded in §18 as a Phase 53+ candidate available if a future phase introduces a need to thread additional fields onto held-proposal metadata.

[CC verifies at Step 0 pre-flight item 3] _load_grant_request exact import path, signature, sync vs async, and any required transaction context. If the helper requires async / transaction-context / other ceremony that the routing-decision step location can't provide, halt-and-surface per §16 — V2 verdict was HOLDS, mid-build surface that contradicts warrants amendment scoping.

10.6 Memory event write at the routing-decision step

The jurisdiction_routing_decided Memory event is written via append_event immediately after resolve_destination returns, before build_claim_url is called. Per §9 schema and P52-D3.

The event records the jurisdiction → destination mapping with source_grant_request_event_id linking back to the originating form submission. The FORAY trail captures the decision with full provenance. The Memory event is the audit primitive for the multi-instance fleet trajectory: when multiple Authority / specialized instances exist, the per-grant routing decision is reconstructable from CME Memory.

The Memory event write does not gate the rest of the seam's flow — build_claim_url proceeds whether the event write succeeds or fails (in alpha; production posture follows existing append_event error-handling conventions per Phase 50). [CC verifies error-handling convention at Step 0 pre-flight item 7] against Phase 50's grant_request_received write site.


11. build_claim_url extension

11.1 What

Extend build_claim_url with an optional base_url: str | None = None parameter. Per P52-D2 / V1.

11.2 Backward-compat-preserving signature change

Per V1 baseline assumption: one existing call-site (the form-initiated grant path). The extension is additive — existing callers (the form-initiated path is the only known one; [CC verifies call-site count at Step 0 pre-flight item 4]) continue to pass no base_url argument and receive the same webauthn_rp_origin-based URL as today.

Approximate shape (CC adapts at Step 2 per live evidence):


# build_claim_url — [CC verifies module path at Step 0 pre-flight item 4]

def build_claim_url(
    # ... existing required params (likely: claim_token or grant_id) ...
    base_url: str | None = None,  # NEW IN PHASE 52 — P52-D2 / V1 option (a)
) -> str:
    """
    Build a claim URL.

    base_url: optional override for the claim URL's base. When None or empty
    string, falls back to settings.webauthn_rp_origin (existing default per
    V5). Phase 52 introduces this parameter for jurisdiction-aware routing
    per P52-D2.
    """
    effective_base = base_url or settings.webauthn_rp_origin
    # ... existing URL construction logic against effective_base ...

Exact shape: [CC verifies at Step 0 pre-flight item 4]. The empty-string-falsy semantic matters: §8.4 settings field's empty default produces an empty destination.url, which §10.3 passes through as the base_url argument; the base_url or settings.webauthn_rp_origin expression evaluates to webauthn_rp_origin for both None and "" arguments.

11.3 Backward-compat test surface

The extension creates a backward-compat test surface: every existing call-site must continue to produce identical output when base_url is unset or set to None / empty string. The test list (§17) includes:

If [CC verifies at Step 0 pre-flight item 4] reveals more call-sites than the v0.2 baseline of one, the backward-compat surface widens proportionally. If the count is more than 3–4, halt-and-surface per §16 — scoping v0.3 absorbs the wider surface before §11 implements.

11.4 No alternative variants

Per P52-D2 sub-decision, options (b) — routing-aware variant build_claim_url_for_destination — and (c) — inline formatting that drops the helper — were set aside. Phase 52 takes option (a) only. If Step 2 surfaces that option (a) doesn't compose cleanly (e.g., type-checking implications across the codebase), halt-and-surface per §16 — the alternatives are available but require scoping v0.3 to select.

11.5 No webauthn_rp_origin change

The existing webauthn_rp_origin settings field is unchanged. It remains the default base URL for the Operator Layer's surface and for any callers of build_claim_url that don't supply a base_url. Phase 52 adds a new field (loomworks_authority_instance_url per §8) that the routing skill reads at module-load time; the two fields serve distinct purposes (Operator Layer origin vs Authority instance URL) and don't overlap.


12. Frontend changes (Operator Layer)

Empty by default per P52-D4.

If Phase 51 §19 item 1 (modify-the-email-body extension) trigger fires during build, mid-build amendment scoping per substrate-friction-discipline-pattern absorbs sub-arc 2 work; reserved buffer slots accommodate. Per P52-D8, if sub-arc 2 lands actual work via this trigger, marker-tag convention follows Phase 51 V7 precedent (Operator Layer tag lands on the Phase 51 commit as a marker).

This section exists for symmetry with Phase 51's §13 frontend section and to mark the absence explicitly; it is not a placeholder for unbuilt work.


13. Marketing site

Empty. Phase 52 doesn't touch the marketing repo.

The marketing site (DUNIN7/loomworks-marketing, live at https://loomworks.doneinseven.com per Phase 51 close) records the jurisdiction field on form submission per Phase 51 V5 / P51-D7. Phase 52 consumes that value at the Authority side — no marketing-site changes are required for the routing primitive to function.

This section exists for symmetry with Phase 51's §14 marketing-site section and to mark the absence explicitly; it is not a placeholder for unbuilt work.


14. Build steps

Six slots: 3 active + 3 reserved buffer per P52-D7. Two checkpoints (A and B). Inverted-pyramid ordering: smallest independent slice first; foundations next; dependents on top. Phase 52's three-active-step shape is tighter than Phase 50's five-active or Phase 51's five-active because Step 0 absorbed six scoping-time verifications cleanly (consolidated State 1).

Step 1 — Routing-skill registry + settings field + Memory event registration (engine)

What. Three substrate primitives land together as one step because they're independently testable and form the foundation the §10 routing-decision step depends on:

Step 0 inspection. Before implementing, CC reads:

Build. Per §7 (registry + default registration), §8 (settings field), §9 (Memory event registration). Each lands as an independent module / class addition; the three are wired into the seam at Step 2.

Tests. ~3–4 tests per scoping v0.2 §6.2:

Acceptance gate. All Step 1 tests pass; Phase 51 substrate test count baseline + ~3–4 new tests; JurisdictionRoutingDecided MemoryObject registers cleanly (instantiation does not raise; OBJECT_TYPE_REGISTRY entry — if Phase 50 pattern requires — present).

Halt threshold. Per §16 step-specific: routing-skill registry shape requires more than Phase 45's register_action_dispatcher pattern can absorb (i.e., V3's verdict contradicts at Step 1); settings module shape requires more than a single-line additive field add (i.e., V5's verdict contradicts); Phase 50 grant_request_received registration shape doesn't accommodate JurisdictionRoutingDecided parallel (i.e., V4's verdict contradicts).

Mode posture. Auto-mode-proceed.

Step 2 — Routing-decision step in seam + build_claim_url extension (engine)

What. Insert the routing-decision step into the email composition seam per §10; extend build_claim_url with the optional base_url parameter per §11; wire the three Step 1 primitives into the seam flow.

Step 0 inspection. Before implementing, CC reads:

Build. Per §10 (routing-decision step insertion + jurisdiction read + Memory event write), §11 (build_claim_url extension). The build is a contiguous insertion between context-prep and the existing build_claim_url call; no Phase 51 code is moved or restructured.

Tests. ~3–5 tests per scoping v0.2 §6.2:

Acceptance gate. All Step 2 tests pass; Phase 51 regression check holds (existing email composition tests pass unchanged when jurisdiction is unset / source event lacks the field — default to Authority routing, default base_url behavior); Memory event audit trail captures jurisdiction → destination mapping with source_grant_request_event_id linkage.

Halt threshold. Per §16 step-specific: _load_grant_request mirror reveals shape Phase 52 didn't anticipate (async ceremony, transaction context, etc. — V2 verdict contradicts); build_claim_url has more call-sites than v0.2 baseline (more than 3–4 triggers halt); build_claim_url extension surfaces tighter coupling than V1's read implies (e.g., type-checking propagation across modules); routing-decision step location can't accept the _load_grant_request shape; jurisdiction field flow contradicts V2 verdict.

Mode posture. Auto-mode-proceed.

Checkpoint A — substrate complete

CC produces implementation notes draft at /Users/dunin7/loomworks-engine/docs/phase-impl-notes/phase-52-implementation-notes-v0_1.md. Records build summary across Steps 1–2; any in-flight resolutions (naming-only divergences absorbed); test count delta. Operator confirms before Step 3.

Acceptance gate (Checkpoint A).

Step 3 — Smoke-test posture (engine)

What. Verifies form submission with each jurisdiction value (US / EU / Other) routes correctly through the seam end-to-end. Acceptance work, not new substrate. Per scoping v0.2 §6.1 / P52-D5.

Step 0 inspection. Before implementing, CC reads:

Build. Two test postures per P52-D5:

Tests. 0 substrate test additions. Smoke-test posture only.

Acceptance gate. Mock-engine smoke test green; real-engine smoke test green or annotated deferred-until-deployment per P52-D5.

Halt threshold. Per §16 step-specific: smoke-test reveals an architectural divergence in the routing flow that contradicts Steps 1–2 verdicts (e.g., the routing decision is recorded but the destination URL isn't reaching the email body); engine deployment is partially live in a way that produces inconsistent smoke results.

Mode posture. Auto-mode-proceed.

Checkpoint B — final, before tagging

CC produces implementation notes v0.2 (or higher if amendments consumed reserved slots) at /Users/dunin7/loomworks-engine/docs/phase-impl-notes/phase-52-implementation-notes-v0_2.md. Records build summary across all Steps; any in-flight resolutions; methodology findings to record (deploy-target-naming discipline applied at Phase 52 — first phase to name both deploy targets at scoping per V6's two-question recipe; smallest substrate phase since the credit-system arc opened; etc.).

Acceptance gate (Checkpoint B). Per §15.

Tag. phase-52-jurisdiction-routing on DUNIN7/loomworks-engine at the post-Step-3 engine commit. Annotated tag. Pushed to origin.

No tags on Operator Layer or marketing repos by default per P52-D8. If sub-arc 2 lands actual work via Item 1 trigger during build, marker tag follows Phase 51 V7 precedent.

Step 4 — Reserved buffer

(reserved — buffer for amendments arising from steps 1–3)

Unconsumed if no amendment arises. Phase 50 left all three reserved slots unconsumed; Phase 51 left all three reserved slots unconsumed per impl notes v0.2; Phase 52 expects the same posture given Step 0 absorbed six verifications cleanly.

Step 5 — Reserved buffer

(reserved — buffer)

Step 6 — Reserved buffer

(reserved — buffer)


15. Acceptance gates

Eight items per scoping v0.2 §7. Two per-step gates (one per substrate active build step) + four cross-cutting / integration gates + one conditional smoke-test gate + one Checkpoint B clean-tree gate. Mirrors the Phase 51 v0.1 16-item shape, scaled down for Phase 52's single-sub-arc / smaller-substrate posture (no marketing-site sub-arc gates, no Operator-Layer sub-arc gates, no Phase 45 e2e-tests gate, no SMTP wiring gate, no voice-template gate).

| # | Gate | Where verified | |---|------|---------------| | 1 | All Step 1 tests pass; routing-skill registry + settings field + JurisdictionRoutingDecided MemoryObject register cleanly. | Step 1 | | 2 | All Step 2 tests pass; routing-decision step inserts cleanly; build_claim_url extension preserves backward compatibility. | Step 2 | | 3 | Routing-skill registry green. register_jurisdiction_route and resolve_destination work per P52-D1 / Phase 45 mirror; default Authority registration covers US / EU / Other; replace-on-duplicate behavior verified. | Step 1 / cross-cutting | | 4 | Routing-decision step writes jurisdiction_routing_decided Memory event per P52-D3 / V4. FORAY trail captures jurisdiction → destination-instance mapping with source_grant_request_event_id linkage back to the originating form submission. | Step 2 / cross-cutting | | 5 | claim_url destination-instance-aware via build_claim_url extension. Extended build_claim_url(..., base_url=...) produces a claim_url whose base URL matches the routed destination-instance URL. Backward-compatible: callers that don't pass base_url (or pass None / empty string) still get webauthn_rp_origin-based URLs identical to Phase 51 baseline. | Step 2 / cross-cutting | | 6 | Phase 51 regression check. All Phase 51 tests pass unchanged. Email composition for grants without jurisdiction field (legacy / direct-call paths where the source event lacks the additive optional field) routes to Authority by default; existing build_claim_url callers unaffected. | Cross-cutting | | 7 | Smoke-test gate (real engine). Conditional per P52-D5. If engine deployment is live by Phase 52 close, smoke-test verifies form submission with each jurisdiction value (US / EU / Other) routes end-to-end through the live engine; the jurisdiction_routing_decided Memory event lands with the correct destination URL. If not live, gate passes with explicit deferred-until-deployment annotation in the implementation notes. Mock-engine smoke test (per Step 3) runs unconditionally and gate 7 includes its passing. | Step 3 / Checkpoint B | | 8 | Working tree clean on main for engine repo before tag. Operator Layer and marketing repos: untouched (no tags, no commits per P52-D8 default). | Checkpoint B |


16. Halt conditions per build step

Per scoping v0.2 §9 and Phase 49 / 50 / 51 substrate-friction-discipline-pattern. Mid-build friction at any of these thresholds halts the build and surfaces for Operator-elective amendment scoping. Phase 52 thresholds are tightened against Step 0 findings absorbed in scoping v0.2.

General (any step).

Step-specific.

In all cases, halt-and-surface is preferred to draft-and-hope. Reserved buffer slots 4–6 absorb amendment scoping if needed; if no amendment arises, slots stay unconsumed (Phase 50 / 51 precedent).


17. Test list

Total substrate: ~6–9 new tests across Steps 1–2 (Step 3 = smoke-test posture only, 0 substrate). Operator Layer: 0 by default per P52-D4. Marketing site: 0.

| Step | Substrate | Frontend (Operator Layer) | Marketing | |------|-----------|---------------------------|-----------| | Step 1 | 3–4 | — | — | | Step 2 | 3–5 | — | — | | Step 3 | 0 (smoke-test posture only) | — | — | | Total | ~6–9 | 0 | 0 |

Build time estimate: 0.75–1.25 hours total per P52-D6. Smallest substrate phase since the credit-system arc opened.

The tests follow the green-against-mocked-substrate-shapes posture for Step 1 (registry + settings + event registration land green against unit-level mocks); Step 2 exercises the email composition path with the V1-baseline sequence (mock destination-instance URLs; existing Phase 51 e2e fixture extends); Step 3 runs against mocked engine unconditionally and live engine conditional on operational track per P52-D5.

Compared to Phase 50's ~66–86 substrate + ~10–12 frontend, and Phase 51's ~21–32 substrate + ~smoke-test, Phase 52 is the lightest by a factor of ~3–10. The audit-trail discipline + routing-primitive scaffolding + applied test of deploy-target-naming discipline justifies the phase cadence overhead at this size.


18. Carry-forward to Phase 53

Items intentionally not built in Phase 52, recorded for Phase 53 scoping. Mirrors Phase 50 / 51 §19 explicit-list shape.

18.1 Phase 53+ candidates surfaced by Phase 52 work

  1. Multi-instance fleet groundwork. Per queued directions §7 / scoping v0.2 §13 trajectory note. Phase 52's routing-skill primitive ships single-instance default with multi-instance scaffolding; the fleet build-out (additional Authority instances, jurisdiction-aware deployment cascade, fleet-level ops surface) is Phase 53+ candidate work.
  1. Engagement creation arc kickoff. Per queued directions §1.1 + §1.2. Carried forward from Phase 51 §19 carry-forward as Phase 53+ candidate; Phase 52 didn't surface anything that changes its readiness.
  1. Threading additional fields onto held-proposal metadata at _author_proposal_assertion (V2 set-aside option (b)). Phase 52 took V2 option (a) — the _load_grant_request mirror — instead. If a future phase introduces a need to thread additional fields onto held-proposal metadata, the _author_proposal_assertion extension is the available path; Phase 52 didn't take it but recorded the option for future selection.

18.2 Carried from Phase 51 §19, deferred-pending-trigger

  1. Modify-the-email-body extension (Phase 51 §19 item 1). Conditional. Surfaces if Operator iteration after Phase 51 / 52 produces a concrete need to inspect / edit the email body before send. Phase 52 design is compatible — the routing-decision step writes the destination-instance-aware claim_url into the email body; a modify-the-body extension would intercept post-composition / pre-send.
  1. CORS wildcard / regex pattern support in loomworks_cors_origins (Phase 51 §19 item 2). Conditional. Surfaces if Cloudflare Pages preview-URL form testing becomes load-bearing.

18.3 Carried from Phase 51 §19, conditional or speculative

  1. Captcha for the public form (Phase 51 §19 item 4). Conditional on abuse signals.
  1. Threshold-driven Companion autonomy for grant decisions (Phase 51 §19 item 5). Gated on Operator content authoring being mature.
  1. Modify-the-proposal action on <GrantDecisionApprovalCard> (Phase 51 §19 item 6). Opportunistic.
  1. Multi-Companion-instance cross-checking on grant decisions (Phase 51 §19 item 7). Speculative future work.
  1. Real-LLM end-to-end spike test cadence formalization (Phase 51 §19 item 8). If quarterly verification cadence becomes useful.

18.4 Continuing parallel work

  1. Operator content authoring (continuation) (Phase 51 §19 item 9). Continuing parallel work, not phase-shaped.
  1. Voice tuning iterations on grant_proposal.md and grant_email.md (Phase 51 §19 item 10). Continuing parallel work.

18.5 Substrate-gap-dependent or deferred

  1. Phase 42 conversation_turns reconciler coverage (Phase 51 §19 item 11). Substrate-gap-dependent; future Phase 42 amendment.
  1. Reactivation in-session chrome (Phase 51 §19 item 12). Conditional.

18.6 Methodology consolidation

  1. Methodology v0.21 consolidation (Phase 51 §19 item 13). Continuing parallel work, runs separately. Phase 52 contributes:

These contributions land in Phase 52 implementation notes during construction and feed the next manifest update / methodology consolidation pass.


19. Kickoff prompt for CC

Paste-ready. Mirrors the Phase 51 §20 kickoff shape, adapted for Phase 52's single-sub-arc / engine-only shape. Run on DUNIN7-M4 in a fresh Claude Code session against the engine repo at /Users/dunin7/loomworks-engine.


> Read the Change Request document at the path I supply below. This is
> CR-2026-067 v0.1, the Phase 52 Change Request (first version; no prior
> Phase 52 CR exists). You are the executing agent named in the CR.
>
> CR path: ~/Downloads/phase-52-cr-jurisdiction-routing-v0_1.md
> (confirm the latest approved version if more than one is present in
> Downloads).
>
> v0.1 drafts against:
>   - loomworks-phase-52-scoping-note-v0_2.md (authoritative scope; absorbs
>     Step 0 findings)
>   - phase-52-step-0-findings-v0_1.md (engine repo
>     docs/phase-impl-notes/; verified live-codebase state, absorbed into
>     scoping v0.2)
>   - loomworks-phase-52-cr-drafting-handoff-v0_1.md (drafting handoff)
>
> Code baseline:
>   - engine: tag phase-51-marketing-site-and-companion-email at 876ff66
>     (annotated tag object 2065637); main one commit ahead at 25294ca
>     carrying impl notes v0.2 §11 addendum. 2,121 tests passed, 25
>     skipped, Alembic head 0064, working tree clean.
>   - Operator Layer (DUNIN7/loomworks): marker tag
>     phase-51-marketing-site-and-companion-email at e4c09e0 (Phase 50
>     commit, sub-arc 2 was empty per Phase 51 V7). 139 vitest passed,
>     11 prerendered routes, eslint/tsc/build clean. NOT IN PHASE 52
>     SCOPE BY DEFAULT.
>   - Marketing (DUNIN7/loomworks-marketing): tag at bf2f694 (annotated
>     tag object 76071b0); main one commit ahead at 78c26fa. Live at
>     https://loomworks.doneinseven.com. NOT IN PHASE 52 SCOPE.
>
> Per CR §3.4: archive this CR to
> docs/phase-crs/phase-52-cr-jurisdiction-routing-v0_1.md
> at Step 0 before Step 1 begins.
>
> Per CR §3.3: run pre-flight Step 0 (9 items). The six scoping-time
> verifications absorbed into v0.2 (V1–V6) are not re-run; the pre-flight
> items here are CR-time deltas only — exact strings, line numbers,
> signatures, module paths the CR drafter could not access from project
> knowledge. Naming-only divergences absorb in-flight per the standard
> discipline; architectural divergences halt and surface (see CR §16
> halt conditions).
>
> Per CR §14: three active build steps + three reserved buffer slots
> (Steps 4–6 reserved-not-skipped per P52-D7). Two checkpoints —
> Checkpoint A after Step 2, Checkpoint B after Step 3 (final, before
> tagging). Standard auto-mode posture: Steps 1–2 accept auto-mode-
> proceed; Checkpoint A halts until Operator confirms; Step 3 auto;
> Checkpoint B (final) halts for tagging.
>
> Per CR §4: ten construction decisions (P52-D1 through P52-D10) are
> settled. CC executes against them; does not re-decide. No decisions
> remain [Operator confirms].
>
> Per CR §16: halt-and-surface is preferred to draft-and-hope. Halt
> thresholds include: substrate-friction-discipline-pattern; routing-
> skill registry shape requires more than Phase 45 pattern can absorb;
> _load_grant_request mirror reveals shape Phase 52 didn't anticipate;
> build_claim_url has more call-sites than v0.2 baseline of one
> (>3–4 triggers halt); jurisdiction field flow contradicts V2 verdict;
> Phase 51 §19 item 1 or item 2 trigger fires; >30 tests touched by a
> single change; any divergence from v0.2 scoping decisions.
>
> Implementation notes at Checkpoints A and B:
> docs/phase-impl-notes/phase-52-implementation-notes-v0_1.md (and
> v0_2.md if revised at Checkpoint B). Records build summary for each
> step, in-flight resolutions, methodology findings (deploy-target-
> naming discipline applied; smallest-substrate-since-credit-arc;
> two-options-with-recommendation pattern repeated; carry-forward
> thinness as cadence signal).
>
> Tag at completion: phase-52-jurisdiction-routing
> (annotated, on engine repo only per P52-D8). No tags on Operator
> Layer or marketing repos by default — Phase 52 has no work in those
> repos. If sub-arc 2 lands actual work via Phase 51 §19 item 1
> trigger during build, marker tag follows Phase 51 V7 precedent.
> Push tag after Checkpoint B.

After CC reports build summary at completion, a fresh scoping chat opens for Phase 53 with the carry-forward from §18 as relevant.

If a mid-build amendment surfaces (Phase 49 Finding 6 trajectory — Operator-elective amendment scoping), the discipline is established: build doesn't halt; Operator decides architecturally with options + halt-threshold review; sub-step lands before continuing. Phase 49's phase-49-step-4-amendment-scoping-v0_1.md is the canonical instance; Phase 50 left all three reserved slots unconsumed; Phase 51 left all three reserved slots unconsumed per impl notes v0.2. Phase 52's most likely mid-build amendment trigger is Phase 51 §19 item 1 (modify-the-email-body) firing during build per P52-D4.


DUNIN7 — Done In Seven LLC — Miami, Florida Phase 52: Jurisdiction Routing — CR v0.1 — 2026-05-10