This is the professional substrate review you asked for after reading the inventory. v0.1 was written from the engine's current implementations of FORAY. v0.2 was rewritten after the FORAY project provided a protocol-level primer correcting several substantive misunderstandings I had about FORAY itself. v0.3 corrects vocabulary imprecision around the engine's actual state — specifically, I had been using "deployed" loosely when nothing in Loomworks is deployed and the only user is the Operator (working from two browsers across two machines to test multi-user code paths).
The review's structure, judgment of what's working, and the six prioritized recommendations stay the same as v0.2. What changes is the framing of where Loomworks is right now — and one consequence: Phase 63's audit wiring and audit viewer are sequenced behind substrate work, not behind any "deployment readiness" question.
All prior versions stay in the record. v0.1 documents the pre-protocol-grounding position. v0.2 documents the post-protocol-grounding position with imprecise deployment language. v0.3 absorbs the vocabulary correction. The trajectory matters because the project's discovery-record discipline says to preserve how the picture was reached, not just where it landed.
I had been using "deployed" loosely. v0.2 contained phrases like "engine-not-yet-deployed" and "I'm not addressing the engine-deployment question." Both were sloppy. Nothing in Loomworks is deployed. The engine is built. It runs on DUNIN7-M4 (the MacMini M4). It is reachable from a browser on another machine (your MacBook) via Cloudflare tunnel, which is a testing arrangement, not a deployment. There is no development server, staging environment, or remote engine — only the local MacMini M4. The word "deployed" in the loose sense I was using it doesn't describe anything in Loomworks' current or near-future state.
I had been imprecise about users. The substrate contains two Person identities. Both are controlled by you. The first is an Operator. The second can be both an Operator and a Contributor to the first Person's primary identity. The setup exists so you can test multi-user code paths from two browsers on two machines without needing a second human. "Two users" therefore means two identities exercising multi-user behaviors, not two humans using the system.
The recommendations and priorities don't change. The framing changes in three places:
I had FORAY wrong at the protocol level. v0.1 treated FORAY as if the three Loomworks tables were the protocol. They aren't — they're three incomplete implementations of the Layer 2 component of a five-construct protocol. The actual FORAY protocol is Arrangements, Accruals, Anticipations, Actions (the four "A"s forming a typed DAG), plus Attestations as a structurally distinct fifth.
My "unify into one substrate table" recommendation was wrong. The protocol-respecting target is four tables, not one. The right shape — per the primer — is foray_transactions (envelope) + foray_components (the four DAG types) + foray_attestations (the cross-cutting fifth) + memory_events (cleaned of readiness wiring).
I missed the three-layer storage architecture. Layer 1 (Kaspa anchoring), Layer 2 (queryable substrate), Layer 3 (sealed archive). Loomworks is implementing Layer 2 only.
The discovery-record discipline asks that corrections be visible alongside the corrected position. v0.1 stays in the record. v0.2 stays. The primer stays. v0.3 absorbs both prior corrections (FORAY protocol grounding from v0.2, vocabulary precision now) and shows the trajectory. A future reader can see how the substrate review reached its current position without having to reverse-engineer the path.
The foundation is not broken. The core architectural commitment — memory_events as a canonical append-only event log with typed projections — is exactly right, and it's been protected consistently across 70 migrations. The foundation work was done well.
What's drifted around the foundation is real, and the FORAY drift specifically runs deeper than v0.1 saw. The three sibling FORAY tables aren't just three shapes of one substrate — they're three incomplete implementations of one component (the Layer 2 queryable store) of a five-construct protocol that the engine has only partially modeled. Properly grounding FORAY means rebuilding to four tables that honor the protocol's typed DAG and its cross-cutting Attestation primitive.
The window to do this cleanly is wide open. The engine is built but not deployed. The only user is the Operator (you), working from two browsers across two machines to exercise multi-user code paths. Every row in the database is something the Operator created during development; no third party is using the system. The permission to wipe data, refactor substrate, or restructure tables is total. The constraint isn't user data preservation — it's only the Operator's willingness to recreate test scaffolding after a wipe, which is small.
My professional advice: two architectural pieces of work first, then proceed. Rebuild FORAY against the protocol (four tables, not one), and tighten the database-level type discipline so the substrate enforces what the application currently only validates. The other four cleanups (KV stores, identifier scheme, Contributor retirement, job table base) are smaller and can ride along or wait.
Before naming what to fix, name what's right. Unchanged from v0.1.
Every change to every MemoryObject is a row in memory_events. Typed views (Assertion, ShapeEvent, RenderEvent, Manifestation, RenderComposition) project subsets into scalar columns for queryability, but the event log is the source of truth and everything else is rebuildable from it. This is the strongest design coherence in the substrate, and it's why "knowledge accumulates and never disappears" works as more than a slogan.
Whatever else changes, this stays. The pattern is correct. The primer reinforces this — the FORAY-respecting design keeps memory_events as the event log, just cleaned of the readiness-wiring columns that don't belong there.
Engagements are the spine of the substrate. Persons get memberships to them; events scope to them; the dashboard aggregates across them; the Companion participates within them. The decision to make Engagement the universal adapter has held up across 70 migrations.
The Engagement's table itself has accumulated cruft (more on that below), but the conceptual centrality of "engagement is the unit" is exactly right.
Nine empty-DDL migrations exist purely as lineage records when a Pydantic vocabulary was extended without a schema change. Heavy data migrations duplicate slices of append_event inline to stay self-contained against future code evolution. This is intentional discipline — the migration chain is documentation, not just schema state.
This pattern is unusual and correct. It means the substrate's history is interrogable. Don't lose it in any cleanup.
This section is new in v0.3. It names the engine's actual state precisely so that the framing in the rest of the review (and any subsequent substrate work) doesn't drift into imprecision.
Loomworks-engine is built — the code exists, the substrate exists, migrations run, tests pass. It is running on DUNIN7-M4, the MacMini M4. It runs nowhere else. There is no development server, no staging environment, no production deployment, no remote CI fixture. The engine is local.
The engine is browser-reachable via Cloudflare. A Cloudflare tunnel makes the engine accessible from a browser running on the Operator's MacBook — and would, in principle, make it accessible from a browser running on record.dunin7.com. This is a testing arrangement, not deployment infrastructure. The engine remains on the MacMini M4; Cloudflare tunnels connections to it.
Loomworks is not deployed. The word "deployed" should be reserved for a future state where the engine is accessible to people other than the Operator. That state is not in scope for this review and is not planned for the foreseeable. Anywhere v0.2 used "deployed" loosely, it should have used "running locally" or "browser-reachable via Cloudflare." v0.3 corrects that.
The substrate currently contains two Person identities. Both are controlled by the Operator (Marvin). The first is an Operator. The second can be both an Operator and a Contributor to the first's primary identity. The setup exists so the Operator can test multi-user code paths — designations, memberships, cross-Operator visibility, Contributor-to-Operator relationships — by working from two browsers across the MacMini M4 and the MacBook simultaneously.
This is functionally meaningful: the substrate has to handle real two-Person behaviors because that's what's being exercised in testing. But operationally, there is no third party using Loomworks. The total user count is one human in two identities.
Every row in the engine database is something the Operator created during development. There is no real user data, no third-party data, no data the Operator is bound to preserve. The permission to wipe is total. The only thing lost by wiping the database is the test scaffolding (the two Person identities and whatever test engagements, memory events, FORAY rows, etc. were created to exercise multi-user code paths). Recreating that scaffolding is small operational cost; nothing meaningful is destroyed.
This permission shapes the substrate review's recommendations decisively. Every priority below assumes that data restructuring, table renaming, column removal, and full FORAY table replacement can happen without any backward-compatibility concern. There is no migration burden, no production downtime to plan for, no users to notify. The substrate is malleable in a way it will not be later.
Three things this section settles:
The vocabulary for the rest of this document: "built," "running on DUNIN7-M4," "browser-reachable via Cloudflare," "test data." The word "deployed" appears only in negation — "Loomworks is not deployed" — until the day comes that it is.
This section is essentially new in v0.2. It replaces v0.1's "three siblings sharing a grammar" framing with the protocol-grounded picture from the primer. The primer is the authoritative document for protocol detail; this section summarizes what matters for the substrate review.
FORAY is a five-construct protocol. Four constructs form a typed Directed Acyclic Graph; the fifth is structurally distinct.
The four DAG components describe the transaction. The cross-cutting fifth (Attestations) describes claims about the transaction. The reference shapes differ structurally — DAG components have typed forward references to earlier kinds; Attestations have a single polymorphic subject_refs[] that can point at any component including other attestations.
This is why "one polymorphic table over all five" is the wrong unification target. The protocol's typing carries validation semantics — an Action can reference Anticipations; an Anticipation cannot reference Actions; an Attestation can reference anything. Collapsing into one shape hides this type information from the database.
v0.1 recommended one unified substrate table with a polymorphic event_type discriminator. The primer is explicit: that path was considered and rejected at the protocol level because it collapses typed DAG references with polymorphic subject references, which loses protocol-level type information.
The correct shape is foray_transactions (the envelope) + foray_components (one polymorphic table over the four DAG kinds, with typed reference columns) + foray_attestations (sibling table for the cross-cutting fifth) + memory_events (cleaned of readiness-wiring, returns to being a pure append-only event log).
FORAY architecturally separates three storage layers. Loomworks is implementing one of them.
| Layer | Where | What it holds | Loomworks scope |
|---|---|---|---|
| Layer 1 | Kaspa | Envelope hash + Merkle root + anchor metadata (~500 bytes) | FORAY anchor service |
| Layer 2 | Operational store (PostgreSQL for Loomworks) | Full components + audit_data + attestations (~15-20 KB per transaction) | Yes — this is what we build |
| Layer 3 | Arweave + Glacier | Sealed encrypted archive for litigation/dispute (~5 KB compressed) | Out of scope (unless litigation-grade preservation joins roadmap) |
The blockchain is not a database. It's a cryptographic commitment store that enables off-chain logic to operate with on-chain guarantees. Loomworks holds the queryable substrate; the FORAY anchor service (separate component, not built into the engine) hands the envelope to Kaspa.
Two diagrams. The first shows the substrate as it is — the same diagram as v0.1, since the current state didn't change. The second is rewritten to reflect the four-table FORAY shape from the primer.
The recommended shape:
foray_transactions holds the envelope (one row per transaction). foray_components holds the four DAG kinds with typed reference columns. foray_attestations holds the cross-cutting fifth with polymorphic subject_refs. memory_events stays as event log, cleaned of the Phase 25 readiness wiring.settings table replacing credentials, engagement_api_keys, system_config, person_settings.The priorities themselves are mostly unchanged from v0.1. Priority 1 is rewritten to reflect the protocol-grounded target. The other five are unchanged.
What it is: Implement the four-table FORAY substrate from the primer. foray_transactions (envelope, one row per transaction). foray_components (one polymorphic table over ARR/ACC/ANT/ACT with typed reference columns and the dual foray_core / audit_data JSONB separation). foray_attestations (sibling table for the cross-cutting fifth with polymorphic subject_refs[] and validity-period semantics). Strip the Phase 25 readiness-wiring columns from memory_events so it returns to being a pure append-only event log.
Retire credit.foray_action_flows and audit.foray_events. The existing rows migrate into foray_components (or, for the parts that are events about state rather than state itself, into memory_events). The existing readiness-wiring columns on memory_events become derived state — readiness is computed from foray_components.*_refs[] and status rather than stored as columns on the event log.
Why first: Highest leverage piece of substrate work in the project. Every future audit-shaped feature (Phase 63's audit viewer is a current example, and there will be more) will reach for FORAY. Getting the substrate right once removes the parallel-solution risk permanently. Doing it wrong locks in three-tables-for-one-concept indefinitely.
Why now: The data permission is real. All existing FORAY data is essentially test data. Rebuild to the protocol now; the alternative is migration plus backward-compatibility cost when real data accumulates.
What unblocks: Phase 63's audit wiring and audit viewer execute against a coherent substrate. Future business transactions (when Loomworks starts modeling real ARR/ACC/ANT/ACT transactions, not just engine setting-changes) plug into a protocol-respecting substrate. The FORAY anchor service integration becomes a clean hand-off rather than a question of which table the envelope comes from.
The estimate increased from v0.1's "2-3 days" to "4-5 days" because the protocol-respecting target is a meaningfully larger piece of substrate than v0.1's single-table sketch — four tables to design, four schemas to enforce, plus the derived-readiness logic that replaces today's column-based readiness wiring.
What it is: A focused discipline pass. (a) Add DB CHECK constraints to every state column that has a Pydantic Literal so the database enforces the same vocabulary the application validates. (b) Eliminate ORM stub proliferation by establishing one authoritative ORM mapping per table; consumers import from it. (c) Reconcile the specific ORM/migration disagreement on memory_events.actor_kind (and any others discovered).
Unchanged from v0.1. The discipline gap is real and the fix is cheap.
What it is: The contributors table is a Phase 3 artifact that should have retired when persons + memberships landed in Phase 14. Migrate every code path using contributors to person-based equivalents; drop the table.
Unchanged from v0.1.
What it is: Collapse credentials, engagement_api_keys, system_config, person_settings into one settings table with polymorphic scope.
Unchanged from v0.1.
What it is: Drop zero-padding (E0001 → E1), reserve E1-E999 for foundational engagements, regular allocation starts at E1000. Voice recognizer regex update, dev data reset.
Unchanged from v0.1.
What it is: Shared base for the eight job tables — common status enum, shared base columns, per-agent specifics in subclass tables or discriminator columns.
Unchanged from v0.1.
Total estimated work across all six priorities: ~10-12 days of CC time. About one day longer than v0.1's estimate because of the FORAY scope increase. Still bounded; still substantial but tractable. Each piece closes cleanly in priority order.
The primer arrived with three explicit pointers the FORAY project asked me to surface in the substrate review. They're worth carrying into v0.2 verbatim because they name points of tension where the substrate review and the published protocol documentation might disagree.
The FORAY documentation at foray.dunin7.com describes the model as "4-Component Model (4A) + Attestations Extension", with Attestations as an optional fifth for third-party validation. The primer reframes Attestation as a native cross-cutting capability — the fifth structurally distinct construct.
Both framings produce the same JSON output. The difference is conceptual. The primer's framing is what drives the four-table substrate shape. A future FORAY spec update (perhaps v4.2) is expected to resolve this. Until then, if someone reads the public spec and sees "4A + Extension" while this review says "five constructs," the apparent disagreement is real but resolvable.
The instinct to put all five constructs in one polymorphic table is natural and would simplify the SQL surface. The primer's §4 is explicit about why this is wrong at the protocol level — typed DAG references and polymorphic subject references are structurally different, and collapsing them hides type information from the database.
If someone (later, or in a different conversation) pushes back on the four-table shape with "wouldn't one table be simpler," the answer is in the primer: it would be simpler at the cost of pushing all type-checking into application code where it's easier for the next person to forget. The two-state-table shape is the protocol-respecting minimum.
Layer 1 (Kaspa anchoring) is the FORAY anchor service's responsibility — a separate component that Loomworks hands envelopes off to. Layer 3 (sealed archive for litigation-grade preservation) is out of scope entirely unless that requirement joins the roadmap.
Loomworks is implementing Layer 2 — the queryable operational store. That's the right scope. If the roadmap later adds requirements that touch Layer 1 directly (Loomworks calling Kaspa) or Layer 3 (sealed archive), the substrate shape may need extension. But starting Layer 2-only is the protocol-respecting minimum.
The six priorities above are bounded. A few places where I'm choosing conservatism over ambition, with the reasoning surfaced. Unchanged from v0.1.
Some substrate reviews end with "rebuild from scratch." This one doesn't, because the core architectural decisions are right. Building a new substrate would be six months of work to arrive at roughly the same place architecturally, minus the migration history that documents how you got there. Bad trade. The right move is targeted refactor against a substrate whose foundations are already correct.
Things like "engagements.state" carrying lifecycle states while the Engagement MemoryObject's own .state field carries version-chain state — same word, different concepts — are real terminology drift. Mostly cosmetic; behaviors are correct. Worth doing in your v0.21 absorption work, not as a substrate priority.
Realistic estimate, slightly larger than v0.1's because of the FORAY scope correction. FORAY rebuild is the biggest piece (~4-5 days). DB boundary tightening is one day. Contributor retirement is one-to-two days. KV unification is one-to-two. Identifier scheme is half a day. Job table base is one-to-two days, eventually.
I'd suggest treating each as its own phase. By the time real Operator data starts accumulating, the foundation is in the shape you want.
Three options for sequencing. Mostly unchanged from v0.1 but with two clarifications: the FORAY rebuild is a larger piece than v0.1 framed (4-5 days vs 2-3), and Phase 63 Parts 2 and 3 (audit wiring, audit viewer) are sequenced behind substrate work because they should target the protocol-grounded FORAY substrate, not because of any deployment question (the engine is browser-reachable via Cloudflare today; the constraint is target-substrate readiness, not engine reachability).
The editor work is small and independent; it can land in a day or two. After that, the substrate phases run in priority order with FORAY first. Phase 63 Parts 2 and 3 (audit wiring and audit viewer) open after the FORAY rebuild lands, targeting the new substrate cleanly.
The architecturally pure move. FORAY rebuild first; editor friction stays for now. Phase 63 reopens (all three Parts) after the substrate work is solid.
Phase 63 Part 1's editor improvements live entirely in record.dunin7.com with no engine dependency. The substrate work lives entirely in loomworks-engine. The two can proceed in parallel without conflict. Parts 2 and 3 of Phase 63 still wait on the FORAY rebuild because they should target the new substrate.
I lean Option C, with one caveat: the FORAY rebuild is now a larger piece than v0.1 framed (4-5 days vs 2-3). If parallel work splits your attention enough that the substrate work suffers, sequential is the better path. The Operator's attention budget is the real constraint, not technical conflict.
The conversation that should happen now is which of A/B/C you prefer, and whether the four-table FORAY shape feels right to you as the protocol-grounded target. Once those settle, the next step is scoping the first piece. Phase 64 (FORAY rebuild) becomes a real piece of work with the primer as its protocol grounding and this review as its architectural orientation.
This review depends on two earlier documents in the substrate section. The substrate inventory v0.1 describes the current state of the engine substrate. The FORAY protocol primer v0.1 describes FORAY at the protocol level. v0.1 of this review (without the FORAY correction) and v0.2 of this review (with FORAY correction but imprecise deployment vocabulary) both stay in the record as prior positions. Together the five documents form the substrate analysis arc for 2026-05-23 and 2026-05-24.