DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path substrate/loomworks-substrate-review-v0_3.html
DUNIN7 · Loomworks · Substrate Review

A Professional Opinion on the Foundations

Vocabulary tightened, scope precise
v0.3·2026-05-24·Working draft
What this document is

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.

What changed v0.2 → v0.3 — vocabulary correction

What I had imprecise

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.

What changes in v0.3 as a result

The recommendations and priorities don't change. The framing changes in three places:

  • Section 1.5 added (this section before Section 2) — names the engine's actual state precisely so future readers don't have to reconstruct the vocabulary from inference.
  • Section 6 cleaned — removes the "I'm not addressing the engine-deployment question" bullet because no such question is open. Deployment isn't in scope at all.
  • Section 7 reframed — Phase 63 Parts 2 and 3 (audit wiring, audit viewer) are sequenced behind substrate work because audit wiring should target the protocol-grounded substrate. The earlier "blocked on engine deployment" framing was wrong; the engine is browser-reachable via Cloudflare today.

What changed v0.1 → v0.2 (still relevant)

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.

Why I'm naming this prominently

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 headline

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.

Section 1

What's working — protect this

Before naming what to fix, name what's right. Unchanged from v0.1.

Keep · Protect

The append-only event log with typed projections

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.

Keep · Protect

The Engagement as the universal addressable unit

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.

Keep · Protect

The migration history as a searchable provenance record

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.

Section 1.5

Where Loomworks actually is right now

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.

The engine

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 users

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.

The data

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.

Implications for the review

Three things this section settles:

  • Phase 63 Parts 2 and 3 are sequenced behind substrate work, not behind deployment. The audit wiring and audit viewer need the engine browser-reachable from record.dunin7.com — which is the same Cloudflare arrangement that already serves the Operator's MacBook testing. No deployment is required. Phase 63 Parts 2 and 3 wait on substrate work because they should target the protocol-grounded FORAY substrate, not the current three-sibling shape.
  • The FORAY rebuild can wipe existing data. All FORAY data is test data; recreating it is small effort. The four-table FORAY substrate is built fresh; existing data is wiped; the Operator recreates whatever test fixtures are needed.
  • Any other refactor work in this review carries the same permission. KV store consolidation, Contributor retirement, identifier scheme reset — all can wipe and recreate. There is no preservation constraint.

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.

The five constructs

FORAY is a five-construct protocol. Four constructs form a typed Directed Acyclic Graph; the fifth is structurally distinct.

The FORAY five-construct model
Four DAG components answer "what is the transaction" — one cross-cutting fifth answers "who vouches for it" Arrangements ARR what we agreed to Accruals ACC how amounts compute Anticipations ANT what we expect Actions ACT what happened Reference flow (backward in time): ACT → ANT → ACC → ARR Each later kind may reference any earlier kind (plural arrays) Attestations ATT — cross-cutting fifth who vouches for it · polymorphic subject_refs[] · attestation chains subject_refs[]

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.

Correction from v0.1

The protocol-respecting target is four tables, not one

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).

The three-layer storage model

FORAY architecturally separates three storage layers. Loomworks is implementing one of them.

LayerWhereWhat it holdsLoomworks 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.

Section 3

The entity model — as it stands, as it should be

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.

Entity model as it stands
Person global identity Contributor vestigial · Phase 3 Engagement universal unit 10 cols, 6 stub ORMs Membership + designations memory_events the canonical log + 8 typed projections credit.foray value flows audit.foray narrative events memory readiness mostly dormant Three incomplete implementations of one protocol component credentials api_keys system_config person_settings 4 KV stores, not unified 8 job/queue tables 4 status vocabularies, no shared base Solid: live · Dashed: vestigial or dormant · Red: action recommended
Entity model as it should be — protocol-grounded
Person global identity Engagement universal unit · one ORM Membership + designations memory_events cleaned event log readiness wiring removed FORAY substrate — Layer 2 only foray_transactions the envelope transaction_id status merkle_root blockchain_anchor foray_components four DAG types component_type ARR/ACC/ANT/ACT foray_core · audit_data arrangement_refs[] accrual_refs[] anticipation_refs[] foray_attestations cross-cutting fifth attestor + credentials subject_refs[] polymorphic validity_period outcome · dependencies[] Layer 1: Kaspa anchor FORAY anchor service — not Loomworks Layer 3: sealed archive out of scope for now settings — unified KV substrate jobs.* — shared base

The recommended shape:

  • The core stays the same. Person, Engagement, Membership, memory_events. No change.
  • FORAY becomes four tables, not one. 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.
  • Layer 1 is out of scope. Kaspa anchoring is the FORAY anchor service's responsibility. Loomworks hands the envelope off; the engine doesn't talk to Kaspa directly.
  • Layer 3 is out of scope. Sealed archive for litigation-grade preservation isn't part of Loomworks' current scope.
  • The KV stores still unify. One settings table replacing credentials, engagement_api_keys, system_config, person_settings.
  • Job tables still share a base. Common status enum and base columns.
  • Contributor still retires. Bearer-token auth migrates to person-based.
Section 4

Prioritized recommendations — revised

The priorities themselves are mostly unchanged from v0.1. Priority 1 is rewritten to reflect the protocol-grounded target. The other five are unchanged.

1 Rebuild FORAY against the protocol — four tables, not one ~4-5 days CC work

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.

2 Tighten the database boundary: CHECK constraints + ORM alignment ~1 day CC work

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.

3 Retire Contributor; migrate to person-based auth ~1-2 days CC work

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.

4 Unify KV stores into one settings substrate ~1-2 days CC work

What it is: Collapse credentials, engagement_api_keys, system_config, person_settings into one settings table with polymorphic scope.

Unchanged from v0.1.

5 Identifier scheme reset + foundational reservation half-day CC work

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.

6 Job table base (eventually) ~1-2 days CC work, can wait

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.

Section 5

Three things the FORAY project flagged to surface

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.

Watch · Spec drift

The published v4.1 spec still calls Attestations an "Extension"

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.

Watch · Likely pushback

"Two state tables, not one" is the most contentious recommendation

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.

Watch · Scope boundary

Layer 1 and Layer 3 are deliberately out of scope

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.

Section 6

Where I'm being conservative — honest about trade-offs

The six priorities above are bounded. A few places where I'm choosing conservatism over ambition, with the reasoning surfaced. Unchanged from v0.1.

I'm not recommending a comprehensive rewrite

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.

I'm not pushing on the methodology vocabulary drift

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.

The six priorities together are about two weeks of CC work

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.

Section 7

What I think happens next

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).

Option A — Ship Phase 63 Part 1 (editor) first, then start the substrate phases

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.

Option B — Start the substrate phases immediately; defer Phase 63

The architecturally pure move. FORAY rebuild first; editor friction stays for now. Phase 63 reopens (all three Parts) after the substrate work is solid.

Option C — Phase 63 Part 1 in parallel with substrate work

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.

Companion documents

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.