The scoping note for Phase 63 — the work that closes the friction surfaced during Phase 62's first day of real use. The editor at record.dunin7.com works, but the operator must hold several pieces of state in their head to use it without producing wrong output. Phase 63 fixes the friction at the source and adds the audit substrate that lets the operator (or anyone else) see what happened on the system over time.
Three pieces of work, named precisely in this document. Three design decisions resolved, two settled and one explicitly deferred with rationale. Out-of-scope explicit. The note is written to be read in full without referencing other documents; everything needed to understand Phase 63's shape is on these pages.
v0.2 absorbs three load-bearing deltas from the Step 0 inspection (commit 0e8b272). 8 of 11 verifications confirmed v0.1's assumptions cleanly. Three did not, and required revision.
V5 + V10 together identified a Piece 3 scoping gap. v0.1 named the deployment-architecture question (how record.dunin7.com calls the engine) and the UUID-resolution question separately but did not resolve either. The Step 0 inspection grounded both: engine production deployment is named but not reliably live, engine CORS allows only Content-Type, no service-to-service authentication mechanism exists today, and an HTTP endpoint resolving email-to-UUID also does not exist. v0.2 resolves both questions — see Section 4.
V8 promoted the propose-and-approve scanner from a good idea to a load-bearing constraint. The inspection surveyed six distinct version-string pattern families in the repository's HTML documents. Patterns A through C (titles, version lines, footers) are mostly safe to auto-substitute. Patterns D through F (transition prose, arrow notation, cross-document references) are polysemic and would produce wrong content if auto-substituted. The scanner design therefore cannot ship without operator approval per occurrence. v0.2 names this as a load-bearing constraint rather than a design preference — see Piece 1 in Section 3.
One operational dependency surfaced. Piece 3 (the audit viewer) requires the loomworks-engine to be reachable at a production host with appropriate CORS. The Step 0 inspection noted the engine's production deployment status is uncertain. v0.2 names this dependency explicitly so Phase 63 doesn't ship a viewer that cannot reach its data source.
Phase 62 closed on 2026-05-22 with the editor at record.dunin7.com functional and verified end-to-end. Five findings were captured in that close, named as queued items for future polish. Within twelve hours of close, on the morning of 2026-05-23, the editor was used to update the Loomworks Architecture Specification from v0.2 to v0.3 in preparation for substantive content work.
That update surfaced two additional findings and made several of the original five matter more than they did in the abstract. Specifically:
Surfacing these in conversation, a fourth question emerged: what about audit? The Phase 62 close named "commits show generic email attribution" as Finding 3. The fix initially imagined was forwarding the Cloudflare Access authentication header. But the project already has FORAY as the universal transaction grammar and audit substrate. And each operator already carries a UUID independent of any access-control framework. So the right framing isn't "fix the email forwarding" — it's "wire editor commits as FORAY transactions, with UUID as the actor identity, and provide an audit viewer to read them back."
Closing these together as Phase 63 produces a coherent piece of work. Editor refinement and audit-substrate wiring share a build target (the editor's commit path) and share a verification surface (the operator using the editor and being able to see the trail of their work). Splitting them into two phases would duplicate the build context and defer the audit work past its natural moment.
Before scoping Phase 63's audit work, an inspection was performed on the loomworks-engine repository (commit 2f04e7a) and the loomworks-record repository (commit 0e8b272) to ground both the Operator's and Claude.ai's working understandings. The inspection found the following facts, relevant to Phase 63:
FORAY is implemented inside loomworks-engine, not as a separate package. Three distinct substrates exist under the FORAY name:
credit.foray_action_flows table. Used for credit transactions; carries a balance-update trigger.audit.foray_events table. Used today only for setting-change events (when an operator changes a Companion tunable). Append-only, no trigger.memory_events table. Content hashes are computed on every event; the foray_tx_ref column is structurally present but always NULL today.For Phase 63, the narrative-event substrate (audit.foray_events) is the right home for editor commits. The migration that created this table explicitly anticipates editor-style events as future event types — adding "editor_commit" requires no schema migration, only a new event-type constant and a new writer helper.
The blockchain-hashing/anchoring layer is missing. Three nested layers of incompleteness exist:
None of this blocks Phase 63. Existing FORAY callers today write rows that are not anchored; those rows will retroactively gain anchors when the anchoring layer eventually lands. Phase 63's editor-commit rows join that population. The schema is positioned so anchoring can be added without further migrations.
The Step 0 inspection grounded three specific gaps that v0.1 of this scoping note named generally:
get_person_by_email but no router exposes it. The Pages Function on record.dunin7.com has no way to ask the engine "what UUID is this email?".Content-Type header today.Phase 63 builds these three things. The work is bounded but real.
FORAY's narrative-event writer accepts actor_person_id: UUID as a typed identity parameter. No role-grant chain composition; identity is opaque to FORAY itself. OVA does not enter Phase 63's scope.
Three pieces of work, named with enough specificity that the Change Request can draft execution steps and acceptance gates.
The version-bump operation gets a content-aware behavior: when the operator chooses "Save as New Version," the editor scans the document for self-references to the current version string before creating the new versioned file. Each found occurrence is surfaced to the operator with line context and a proposed replacement. The operator approves or declines each occurrence individually. Approved changes are applied atomically with the version-bump commit; declined occurrences remain unchanged.
The Step 0 inspection surveyed six distinct version-string pattern families across the repository's HTML documents. Patterns A (title element), B (version-line and sidebar), and C (footer paragraph) are the canonical self-references that need updating. Patterns D (transition prose like "we considered X in v0.1"), E (arrow notation like "v0.1 → v0.2"), and F (cross-document references to other files at specific versions) are intentionally polysemic — a backward reference to v0.1 is meant to stay v0.1, and a mention of another document's v0.2 should never be auto-bumped just because this document is going from v0.2 to v0.3.
Propose-and-approve is therefore a load-bearing constraint, not a design preference. An auto-substitution path would produce wrong output for patterns D, E, and F. The scanner's UX must surface each match with enough context (3-5 lines of surrounding text) that the operator can judge whether it's a self-reference (approve) or a polysemic reference (decline). The CR specifies this as an acceptance gate, not a design choice left open at execution time.
After the bump commit lands, the editor closes its current session and re-opens automatically pointing at the new versioned file. The operator continues editing the version they just created, not the predecessor that was just archived. The current behavior (editor remains pointed at the now-archived path) is replaced. The natural attachment point for this behavior was identified in Step 0: in tools/static/editor.js, after the success status renders, by closing the modal and calling openEditFlow(json.newPath) to reopen against the new versioned path.
The save action labels and visual treatment get reworked so the two save operations are clearly different. "Save as New Version" gets a visual treatment that signals consequence; "Save (Minor Correction)" reads as an in-place edit. The exact treatment is a design decision left to execution time, but the requirement is that an operator looking at the buttons can tell at a glance which produces a new version and which doesn't.
Every commit from the editor at record.dunin7.com emits a FORAY narrative-event row to the audit.foray_events table in the loomworks-engine database. The row carries the operator's UUID as the actor (resolution mechanism settled in Section 4), the repository as engagement context (the loomworks-record repository itself; per-document context as a payload field), and a structured payload describing what changed.
Three new pieces ship in the engine:
editor_commit, registered in the audit module's event types.write_editor_commit_event(...), taking actor UUID, document path, prior content hash, new content hash, commit message, and optional engagement ID. Constructs the FORAY row and persists it via the existing audit substrate. The signature follows the existing write_setting_change_event pattern named in Step 0 finding V1.No schema migration required. No anchoring required. The row lands; the row accumulates; the operator's audit trail begins to exist.
A new section on record.dunin7.com presents FORAY narrative events back to the operator. The section is reachable from the landing page like any other section. The viewer surface has three behaviors:
The viewer requires three new pieces of substrate (the engine work named in Section 4) plus a viewer page on record.dunin7.com that consumes the new endpoint. The viewer follows the engine's existing API conventions surfaced in Step 0 V4: FastAPI router with Pydantic response models, opaque-cursor pagination matching the dashboard.py and admin_grants.py conventions, dependency-injection-based authentication.
v0.1 of this scoping note named these as open questions. v0.2 resolves them based on the Step 0 inspection's grounding. The CR drafting proceeds from these decisions.
The Pages Function on record.dunin7.com authenticates to the loomworks-engine API by sending a long-lived service token in the Authorization: Bearer header. The token is generated once, stored in Cloudflare Pages secrets (alongside the GITHUB_TOKEN used by Phase 62's editor), and configured in the engine's environment as a known service token. The engine accepts the token on Phase-63-introduced endpoints via a simple equality check against the configured value.
The engine's CORS configuration widens to include Authorization in allow_headers alongside the existing Content-Type, and includes https://record.dunin7.com in LOOMWORKS_CORS_ORIGINS.
Why this path: Simplest mechanism that works for a single-operator self-hosted system. Service tokens are routine in service-to-service contexts; the engine already has analogous patterns (the Phase 47/48 credit-grant signing keys live in secrets). The mechanism doesn't depend on JWT verification (currently deferred in the Pages Function), doesn't require a new mapping table, and doesn't collapse two identity systems. It's bounded in scope.
What it explicitly is not: a durable solution for multi-tenant production. A future phase that opens record.dunin7.com to multiple Operators or to non-DUNIN7 deployments will need OAuth-style flows, per-Operator tokens, or JWT-based identity propagation. The shared service token is a single-Operator simplification, named as such, designed to be replaced cleanly later.
POST /persons/resolve-email, called by the Pages Function, cached client-side per sessionA new engine endpoint resolves an email address to a person UUID by calling the existing get_person_by_email substrate function. The endpoint accepts the service-token authentication from Decision 1. Returns 404 if no person matches the email; returns the first match's UUID otherwise (per the substrate function's existing semantics).
The Pages Function calls this endpoint at editor session start (when the operator opens any document for editing) and caches the result in the static editor.js module for the duration of the operator's session. Subsequent commits reuse the cached UUID without re-calling the engine.
Why this path: Minimal new work — one endpoint, one substrate function it already calls, one cache layer in the editor. Doesn't require JWT verification, doesn't require a new mapping table, doesn't require changes to how the Pages Function reads Cloudflare Access identity.
The non-uniqueness consideration: The engine's get_person_by_email substrate function returns the first match by created_at when multiple persons share an email. Phase 63 accepts this looseness because in the current single-Operator system, only one person has edit access to record.dunin7.com — Marvin Percival as the DUNIN7 Operator. The first-match semantics produce the right person. A future phase opening edit access to additional Operators would need to either constrain email uniqueness at the database layer or pivot to a different identity primitive; that work is named as a future concern, not absorbed into Phase 63.
The Cloudflare Access JWT alternative considered and deferred: The Access JWT carries a stable per-Access-user identifier in its sub claim. Using sub as the identity primitive (with a new Access-sub-to-person-id mapping table) would be more durable. It's deferred because (a) JWT verification in the Pages Function is currently deferred, (b) the mapping table would need a signup-time flow to populate, and (c) the deeper work belongs to its own phase or arc. Path B is named as the future direction; Phase 63 uses the simpler Path A.
The audit viewer in Phase 63 covers narrative events only (the substrate the editor commits land in). Value flows and memory-event activity are out of scope for Phase 63's viewer. A future phase can unify the three substrates if and when that becomes load-bearing.
This produces a narrower but coherent surface that the Operator can use immediately. Reflects FORAY's framing as "the audit substrate" specifically as applied to narrative events; the unification work has its own design questions (consolidated event schema, merged time-ordering across schemas, performance implications of querying three tables) that deserve their own scoping rather than absorbing into Phase 63.
Piece 3 of Phase 63 cannot complete until the loomworks-engine is reachable at a production host with appropriate CORS configuration. The Step 0 inspection found that the production engine deployment status is uncertain — the host is named in Phase 51 and 52 implementation notes (api.loomworks.doneinseven.com, possibly via M4 tunnel as api.loomworks.dunin7.com) but neither memory nor inspection confirms it is reliably live today.
Phase 63 names this as an explicit dependency rather than an assumption. The CR's execution steps include verifying engine reachability from record.dunin7.com's Pages Function as a prerequisite gate for Piece 3 ship. If the engine is not reachable at CR execution time, the Operator decides whether to (a) deploy/redeploy the engine as part of Phase 63's work, (b) defer Piece 3 acceptance gates until separate deployment work lands, or (c) re-scope Phase 63 to defer Piece 3 entirely.
Pieces 1 and 2 are not blocked by this dependency. Piece 1 lives entirely in record.dunin7.com. Piece 2 emits FORAY rows to whatever database the engine connects to; the rows can accumulate even if the production engine API is not serving traffic.
Named explicitly so the boundary is visible.
FORAY's anchoring layer. No Kaspa connection, no signing primitives, no anchor-batch pipeline. Editor-commit FORAY rows accumulate without anchoring; they will gain anchoring when that work eventually lands as its own phase or arc.
Cross-substrate FORAY consolidation. The three FORAY substrates (value flows, narrative events, memory events) remain independent. The audit viewer reads narrative events only.
OVA development. OVA is not a dependency of FORAY's current implementation. Phase 63 uses opaque UUID identity throughout. OVA's seam-and-stub work continues on its own track.
JWT-based identity at the Pages Function. Decision 2 above explicitly defers this. Cloudflare Access JWT verification, an Access-sub-to-person-id mapping table, and a signup-time mapping flow are named as the future direction beyond Phase 63's shared-service-token approach.
Email-uniqueness enforcement at the database layer. Decision 2 accepts the engine's existing first-match semantics. A future phase that needs strict uniqueness will introduce a constraint and a migration.
The smaller Phase 62 polish findings unrelated to the version-bump operation. Specifically: new-document creation accepting only a slug (the operator can post-edit the title); the Monaco loader double-load warning (visual noise only). These remain in queued directions for a future polish phase.
Content updates to existing documents. The architecture specification update that the editor friction surfaced is itself substantial work; it is not part of Phase 63. The architecture spec gets updated separately, either through Path A or Path B, on its own track, once Phase 63 has made the editor trustworthy enough that Path B is the natural choice.
The scoping conversation that produced v0.1 contributed several methodology candidates. v0.2 adds two more from the Step 0 inspection arc. They will be carried to the v0.21 methodology consolidation alongside the existing backlog.
Inventory existing substrate before designing new. Before proposing design work, inventory what existing project substrate already addresses the question. Failing to do so produces redundant work and obscures the value of substrate that already exists. The instinct should be inventory-first, design-second. The architecture specification is the natural starting point — if a section says "Substrate operating," the substrate exists and design proceeds from there.
Architecture spec answers "what is operating"; code-level inspection answers "what does that mean concretely." When design depends on substrate specifics, code-level inspection isn't optional even when the architecture spec says the substrate is operating. The two documents serve different purposes. The spec orients; the inspection grounds the design in implementation reality.
Claude.ai-and-Claude Code collaboration as a session-opening method. For navigational reading of repository documents or codebases, route the work to Claude Code with a question-shaped kickoff, not a data-dump kickoff. Claude Code reads and synthesizes; the report comes back as orientation, not raw material. Claude.ai then reasons over the orientation.
References versus inline material in operator-facing responses. In operator-facing responses, include the actual text you want the operator to consider, written out, in the message. References are for things the operator already has open or doesn't need to engage with directly.
Step 0 inspection has load-bearing return value, not ceremonial. The Step 0 inspection for Phase 63 contradicted one assumption (V10 around UUID resolution) and nuanced two others (V5 around engine deployment, V8 around scanner safety). Without those findings, the CR would have been drafted against ground that did not exist. Step 0 is not a procedural formality; it is the mechanism by which scoping discovers what it does not yet know about the implementation reality it depends on.
Polysemic patterns require operator approval per occurrence, never auto-substitution. When a scanner operates on document content and the patterns it recognizes carry multiple meanings depending on context (V8's transition prose, arrow notation, and cross-document references), auto-substitution will produce wrong output for some fraction of matches. The cost of asking the operator to approve N occurrences is bounded; the cost of wrong substitutions propagating into the repository's permanent record is not. Propose-and-approve is the correct default; auto-substitute is a future affordance only after the substitution rules are demonstrably unambiguous.
Phase 63 proceeds to Change Request drafting. The Step 0 findings have been absorbed; the scoping note is v0.2; the design decisions are settled; the operational dependency is named explicitly. CR drafting opens with:
| Step | What it produces | Surface |
|---|---|---|
| Change Request drafting | The Change Request document — step-by-step execution plan with acceptance gates. Sections cover Piece 1 (editor refinement + scanner), Piece 2 (FORAY writer wiring), Piece 3 (engine endpoints + audit viewer page), the three Decision-2 endpoints, and the engine-deployment dependency gate. | Claude.ai drafts; cross-check cycle absorbs any findings |
| Execution | The work itself. Editor refinement in the loomworks-record repository's Pages Functions and editor.js; FORAY writer plus new endpoints in loomworks-engine; audit viewer page on record.dunin7.com. | Claude Code executes against the CR; Claude.ai surfaces any halt-and-surface events |
| Close | Tag both relevant repositories at the phase name. File close handoff. Trigger manifest absorption — this will be the first absorption since v0.41, closing several other phases of carry-forward at the same time, separately. | Standard close protocol |
Once Phase 63 closes, the architecture specification update that triggered this whole arc resumes against an editor that the Operator can trust as a Path B working surface — and against an audit substrate that records the trail of the update work itself. The discipline the architecture specification names ("each substantive change to Loomworks should update this document") becomes operationally observable.