CR identifier. CR-2026-027
Version. 0.1
Date. 2026-04-26
Status. Draft for Operator review.
Scoping note. loomworks-phase-15-scoping-note-v0_1.md — five settled decisions (S1–S5). Consumed; not relitigated here.
Candidate seed. loomworks-candidate-seed-v0_4.md — thirteen declared render-types. This is what enters the database.
A new person signs up, lands on the welcome page, proceeds to the dashboard, and sees the Loomworks engagement as their first engagement. They can open it and see its Memory. The product has stopped being empty.
Specifically:
Phase 15 is grounded in three layers, consulted in order. Claims in this CR cite the layer they rest on.
Layer 1 — Methodology document v0.17. The Loomworks engagement's Memory is knowledge about the methodology. The five founding Memory assertions (Section 8) must be consistent with the methodology's vocabulary and concepts. The methodology names Memory as accumulated engagement knowledge; the engagement as the durable container; the seed as the commitment at creation; designations as per-engagement, stackable, independent. The commons purpose (Decision 9, Discovery record v0.2) names the Loomworks engagement as the universal commons — the engagement whose Memory is visible to every person in the system.
Layer 2 — Reference Design v0.1.2. The Reference Design's assertion model (content, provenance, state, non-erasure discipline) governs how the founding Memory enters. Assertions are held, then committed. The Operator is the confirming party per R-A3.
Layer 3 — Playground Spec v0.10. Specific requirements:
| ID | Decision | Summary | |----|----------|---------| | S1 | Seed v0.4 | Commons purpose + build purpose. Thirteen render-types (eleven build, two commons). | | S2 | Silent at signup, visible on arrival | Automatic membership in signup transaction. No mention during signup ceremony. | | S3 | Static welcome page with engagement link | Living surface deferred. Static content stays; card added. | | S4 | Five authored topics as founding Memory | Deeper content accumulates organically through use. | | S5 | Induction + automatic membership only | No invitation flow, no Rendered content inline, no specialist registrations for commons render-types. |
Phase 2's engagement creation flow (draft_seed → induct_seed → commit_engagement) is the full ceremonial path with agent-driven seed examination and convergence. The three prior engagement inductions (PartsPilot, FieldPilot, Agricultural Engagement) used the seed induction driver script (scripts/seed_induction_driver.py), which calls the Phase 2 machinery programmatically with a live LLM.
Phase 15 uses a different path: an Alembic migration. The Loomworks engagement must exist before the signup flow can reference its engagement_id for automatic membership. This ordering constraint makes a migration the correct mechanism — the migration runs at alembic upgrade head and guarantees the engagement exists before any code that depends on it.
The migration creates the engagement row, records the Engagement and Seed memory objects in the event log, and sets visibility to 'automatic'. The seed content comes from loomworks-candidate-seed-v0_4.md. This is a direct database operation, consistent with Phase 2's patterns (engagement row in engagements table, memory objects in memory_events, materialized view updated via projector) but skipping the agent-driven examination loop. The Operator has reviewed and approved the seed through the scoping process; the induction agent's role (verifying seed completeness against R-A5–R-A11) is satisfied by that review.
R-A17 (walkable induction history) is satisfied by recording the migration as an event in the memory log with appropriate provenance. The migration records a single-cycle induction history: one SeedInductionLoopCycle with convergence_reached=True and zero findings, reflecting the Operator's pre-approval.
The Loomworks engagement receives a deterministic UUID, stored as a constant alongside the existing ADMINISTRATIVE_ENGAGEMENT_ID:
LOOMWORKS_ENGAGEMENT_ID = UUID("00000000-0000-0000-0000-000000000002")
The signup flow references this constant when creating the automatic membership. A lookup-based approach (query the engagement by name at startup, cache the ID) would work but adds a failure mode (engagement not yet inducted) that a constant avoids.
The engagements.visibility column is TEXT with default 'private'. Phase 14 added it as schema preparation. No CHECK constraint exists on the column (TEXT columns in SQLite and PostgreSQL accept any string by default). Phase 15 inserts 'automatic' as the Loomworks engagement's visibility value. If a CHECK constraint is found during pre-flight, it must be updated to accept the three values: 'private', 'discoverable', 'automatic'.
'automatic' means: every person in the system is a member of this engagement from signup. This is distinct from 'discoverable' (visible and joinable by choice) and 'private' (invitation-only). The Loomworks engagement is the only engagement that will carry this value.
The five founding Memory topics (S4) are authored as assertions in a migration that runs after the engagement induction migration. The assertions go through the normal assertion pathway: each is recorded as a memory event with event_kind='assertion_added', then committed with event_kind='assertion_committed'. The Operator (Marvin Percival, person_id 6d867b23-655a-440a-b377-e6bf4fb3882b) is the confirming party per R-A3 / R-B20.
The migration inserts both the Add and Commit events for each assertion in the same migration. This is consistent with the prior induction pattern where seeds are drafted and committed in the same operation. The assertions are pre-approved by the Operator through the scoping process.
The signup transaction currently creates: person record, WebAuthn credential, TOTP secret, recovery codes — all atomic. Phase 15 adds: one membership row (person_id, loomworks_engagement_id) and one membership_designations row ('contributor') in the same transaction.
The signup endpoint that completes the transaction is /auth/signup/totp-verify (Phase 14, Section 12.1, Step 3 — the TOTP verification step is where the person record is committed). The membership insert happens in the same database transaction, after the person record insert and before the session mint.
The existing test person (Phase 14's second person, created via the signup flow for testing, with no memberships) receives a Loomworks membership via the same migration that inducts the engagement. This ensures the test person's state matches what new signups will get.
If CC finds that the test person does not exist in the dev database at execution time (e.g., database was reset), this step is a no-op — the migration handles the case gracefully.
Phase 15 follows Phase 2's data model conventions (engagement row in engagements, memory objects in memory_events, materialized view via projector) but diverges in three ways:
| Aspect | Phase 2 pattern | Phase 15 divergence | Reason |
|--------|----------------|---------------------|---------|
| Induction path | Agent-driven loop via induct_seed | Direct migration | Ordering constraint: engagement must exist before signup can reference it. Operator pre-approved the seed. |
| Visibility | Not set (column did not exist) | 'automatic' | Third visibility tier per Decision 9. Unique to the Loomworks engagement. |
| Founding Memory | No Memory at induction; assertions arrive later via API | Five assertions authored in migration | S4: founding Memory is part of the induction — new Contributors need orientation content from the first moment. |
Phase 14 is complete. Substrate at tag phase-14-person-layer, 1038 tests passed, 2 environment-gated skips. Frontend at tag phase-14-person-layer, lint + tsc + build clean. Phase 15 builds against main HEAD.
CC verifies the following before Step 1 runs:
engagements table (Administrative, PartsPilot, FieldPilot, Agricultural Engagement). All visibility 'private'.engagements.visibility column exists, is TEXT, default 'private'. Check for CHECK constraints.6d867b23-655a-440a-b377-e6bf4fb3882b, four memberships) and a test person (zero or more memberships — the test person may have been created by the signup flow during Phase 14 acceptance testing, but has no guaranteed state)./auth/signup/totp-verify is the endpoint that commits the person record and issues the session. Locate the handler function in the codebase.00000000-0000-0000-0000-000000000002 exists.loomworks-candidate-seed-v0_4.md exists in the repo (at docs/seeds/ or project root). If only v0.3 exists, note this at Checkpoint A — the CR includes the seed content inline for the migration and the v0.4 file is committed in Step 1.
engagements table:
id: 00000000-0000-0000-0000-000000000002
name: "Loomworks"
engagement_version: <set by append_event>
visibility: "automatic"
created_at: <migration timestamp>
updated_at: <migration timestamp>
# src/loomworks/engagement/constants.py (or alongside ADMINISTRATIVE_ENGAGEMENT_ID)
LOOMWORKS_ENGAGEMENT_ID = UUID("00000000-0000-0000-0000-000000000002")
The Seed is a MemoryObject in the event log, consistent with Phase 2's Seed type. The seed content fields are populated from loomworks-candidate-seed-v0_4.md:
Seed(
object_type="seed",
what_the_work_is=<from v0.4 "What the work is">,
who_consumes_the_work=<from v0.4 — four consumers>,
voice_of_the_work=<from v0.4 "Voice">,
constraints=<from v0.4 — six constraints plus "Public Memory by default">,
success_conditions=<from v0.4 "Success conditions">,
initial_contributors=[MARVIN_PERSON_ID],
initial_agents=[],
additional_assertions={
"the_universal_commons": <from v0.4 "The universal commons">,
"authorisation": <from v0.4 "Authorisation">,
"declared_render_types": <summary of thirteen render-types>,
},
is_divergent=False,
divergence_rationale=None,
)
CC reads the full seed content from the v0.4 file at execution time and populates these fields. The seed content is not reproduced verbatim in this CR to avoid stale duplication — the v0.4 file is authoritative.
Five Assertion memory objects, each with content authored in Section 8 of this CR:
Assertion(
object_type="assertion",
content=<see Section 8>,
state="committed",
committed_at=<migration timestamp>,
committed_by=ActorRef(kind="contributor", id=MARVIN_PERSON_ID),
)
Each assertion is recorded as two events: assertion_added (state "held") then assertion_committed (state "committed"). The confirming party is the Operator.
On signup, the transaction inserts:
memberships table:
id: <generated UUID>
person_id: <new person's id>
engagement_id: 00000000-0000-0000-0000-000000000002
active: true
created_at: <transaction timestamp>
membership_designations table:
membership_id: <from above>
designation: "contributor"
The five founding Memory assertions are authored below. Each assertion is a self-contained piece of knowledge that enters the Loomworks engagement's Memory through the normal Add/Commit pathway. The content is consistent with the methodology document v0.17's vocabulary and concepts.
> Memory is the accumulated knowledge of an engagement. Every piece of knowledge enters Memory as an assertion — a statement contributed by a person or an agent, carrying provenance that records who contributed it, when, and in what context. > > Memory grows through contribution. Assertions can be added, committed, revised, and retracted, but never erased. A correction does not delete what came before; it adds a new assertion that supersedes the prior one, with the relationship between them preserved. This non-erasure discipline means the full history of what an engagement has learned — including what it learned was wrong — is always walkable. > > Memory is not a database dump or a file archive. It is considered, weighted, source-identified knowledge that compounds over time. The more an engagement accumulates, the richer its Memory becomes — not just in volume but in the relationships between what it knows.
> An engagement is a persistent space where knowledge work happens. It has a name, a seed that defines its purpose and commitments, and Memory that accumulates as people and agents contribute knowledge. > > The engagement holds a pipeline of transformations. Memory is the raw accumulated knowledge. A Manifestation is a snapshot — a bookmark of Memory at a specific moment, against which downstream work runs. Shaping prepares material from a Manifestation for a particular consumer, selecting and recasting what that consumer needs. Rendering produces the final-form artifact a consumer reads — a document, a specification, a reference, a guide. > > Each engagement is independent. Its Memory, its Manifestations, its Shapings, and its Renders belong to it alone. A person can participate in many engagements, each with its own knowledge and its own pipeline.
> A designation is a role flag on a membership — it describes what a person does within a specific engagement. The three current designations are Operator, Contributor, and Domain Expert. > > An Operator governs the engagement. They approve state transitions, confirm assertions, manage the seed, and make decisions about the engagement's direction. Every engagement has at least one Operator. > > A Contributor participates in the engagement's knowledge work. They can add assertions to Memory, contribute knowledge, and read the engagement's content. Every person in the Loomworks engagement is a Contributor. > > A Domain Expert carries recognised expertise in the engagement's subject matter. This designation is earned through demonstrated knowledge and is a persistent recognition — it stays unless explicitly removed. > > Designations are per-engagement and stackable. A person can be an Operator on one engagement and a Contributor on another. A person can hold multiple designations on the same engagement — an Operator who is also a Domain Expert, for instance.
> A seed is the founding document of an engagement. It declares what the work is, who consumes it, what voice the work takes, what constraints it operates under, and what success looks like. The seed is written before the engagement exists and is the basis on which the engagement is created. > > The seed is not a plan that gets discarded after launch. It is a continuously-held commitment that the engagement carries throughout its life. It can be amended — through a deliberate, recorded process — but it is never abandoned. The seed constrains what the engagement does, what render-types it commits to producing, and what quality discipline it maintains. > > Every engagement has exactly one seed. The seed for the Loomworks engagement declares both a build purpose (the environment in which DUNIN7's own work runs) and a commons purpose (the educational and community space that every person in the system participates in).
> Contributing knowledge to an engagement means adding an assertion to its Memory. An assertion is a statement you believe to be true, relevant, and worth preserving. It might be a fact, an observation, a correction, a clarification, or a connection between existing knowledge. > > Every assertion carries provenance — your identity, the timestamp, and the context of the contribution are recorded and preserved. This provenance is permanent. It means your contributions are always attributable, always traceable, and always part of the engagement's history. > > Contributions are considered and deliberate. The engagement's Operator reviews and commits assertions, ensuring that what enters Memory meets the engagement's quality standards. A contribution that is revised or superseded is not lost — the original and its successor both remain in Memory, with their relationship recorded. > > The Loomworks engagement's Memory is visible to every person in the system. Contributions to this engagement are part of a shared knowledge base that helps everyone — from newcomers learning what an engagement is, to experienced Operators deepening the methodology.
The /welcome page (Phase 14, Section 14 — ceremonial surface, vertical lockup) retains all existing static content. A new section is added after the designations explanation and before the dashboard door:
Content. A card or section:
/engagement/<LOOMWORKS_ENGAGEMENT_ID> (the engagement overview page).
Brand compliance. Render against the brand guide HTML (loomworks-brand-guide-v0_15.html) side-by-side during development. The card uses the same visual language as the dashboard engagement cards. Ceremonial register (vertical lockup) is maintained.
No dashboard code changes are expected. The dashboard reads from /me/memberships (Phase 14, Step 13). The automatic membership makes the Loomworks engagement appear on the dashboard naturally. The engagement card shows "Contributor" as the role label.
If pre-flight reveals that the dashboard does not read from /me/memberships or that the Loomworks engagement does not appear, the CR step addresses whatever wiring is needed.
| File | Tests (projected) |
|------|-------------------|
| test_loomworks_engagement_induction.py | 8 |
| test_founding_memory.py | 6 |
| test_automatic_membership.py | 8 |
| test_loomworks_visibility.py | 4 |
| Total (projected) | ~26 |
Per Phase 9 Finding F discipline: per-file projections are the authoritative shape; the aggregate may drift ~20% at execution.
test_loomworks_engagement_induction.py
test_engagement_exists — after migration, the engagements table contains a row with UUID 00000000-0000-0000-0000-000000000002 and name "Loomworks".test_engagement_visibility_automatic — the Loomworks engagement's visibility is 'automatic'.test_seed_recorded — the event log contains a Seed memory object for the Loomworks engagement with what_the_work_is, who_consumes_the_work, and all R-A5–R-A10 fields populated.test_seed_convergence_recorded — the event log contains a SeedInductionLoopCycle with convergence_reached=True.test_engagement_memory_object_recorded — the event log contains an Engagement memory object for the Loomworks engagement.test_induction_history_walkable — per R-A17, the induction history can be walked from the event log: seed → cycle → findings (none) → convergence.test_existing_engagements_unchanged — the four prior engagements remain with visibility 'private' and all existing tests pass.test_loomworks_engagement_id_constant — the constant LOOMWORKS_ENGAGEMENT_ID matches the UUID in the database.
test_founding_memory.py
test_five_assertions_exist — five committed assertions exist in the Loomworks engagement's Memory.test_assertions_committed_by_operator — all five assertions have committed_by referencing the Operator's person_id.test_assertion_content_nonempty — each assertion has non-empty content.test_assertions_in_event_log — each assertion has both an assertion_added and assertion_committed event in the log.test_assertions_in_materialized_view — each assertion appears in current_memory_objects with state 'committed'.test_assertions_cover_five_topics — a content check: the five assertions collectively cover the five declared topics (Memory, engagements, designations, seeds, contributing). This test can match on keywords or content substrings.
test_automatic_membership.py
test_signup_creates_loomworks_membership — a new person completing signup has a membership on the Loomworks engagement.test_signup_membership_has_contributor_designation — the automatic membership has the 'contributor' designation.test_signup_membership_is_active — the membership's active flag is true.test_signup_still_creates_person — the person record is created correctly (regression — ensure the membership insert doesn't break person creation).test_signup_still_creates_credentials — passkey and TOTP are created correctly (regression).test_signup_still_issues_session — the session is issued correctly (regression).test_signup_transaction_atomicity — if the membership insert fails, the entire signup transaction rolls back (no orphaned person records).test_test_person_has_loomworks_membership — the test person (if present) has a Loomworks membership with 'contributor' designation after migration.
test_loomworks_visibility.py
test_automatic_visibility_accepted — an engagement with visibility 'automatic' can be created (the column accepts the value).test_private_still_default — a new engagement created without specifying visibility gets 'private'.test_existing_engagements_still_private — the four prior engagements still have visibility 'private'.test_discoverable_accepted — an engagement with visibility 'discoverable' can be created (forward-compatibility — the column accepts all three values).Each scoping decision has at least one test that would fail if the decision were reversed:
test_seed_recorded verifies the seed content reflects v0.4 (commons purpose present).test_signup_creates_loomworks_membership verifies the membership exists after signup.test_five_assertions_exist and test_assertions_cover_five_topics.Auto-mode posture: Steps 0–3 auto, Checkpoint A. Steps 4–5 auto, Checkpoint B. Steps 6–8 auto, Checkpoint C (final).
Step 0 — Pre-flight and CR archival.
Archive this CR to docs/phase-crs/phase-15-cr-loomworks-universal-commons-v0_1.md. Run pre-flight checks (Section 6.2). Confirm baseline: uv run pytest -v reports 1038 passed, 2 skipped. Create branch phase-15-loomworks-universal-commons.
Verification: all pre-flight checks pass. Baseline green.
Commit: Phase 15 step 0: CR archival and branch creation.
Step 1 — Seed v0.4 in repo.
If docs/seeds/loomworks-candidate-seed-v0_4.md does not exist, create it from the project knowledge copy of loomworks-candidate-seed-v0_4.md. If v0.3 exists, it remains alongside (not deleted — non-erasure discipline).
Verification: docs/seeds/loomworks-candidate-seed-v0_4.md exists and matches the project knowledge version.
Commit: Phase 15 step 1: seed v0.4 in repo.
Step 2 — Engagement constant and induction migration.
Add LOOMWORKS_ENGAGEMENT_ID constant alongside ADMINISTRATIVE_ENGAGEMENT_ID.
Write migration 0034_phase_15_loomworks_engagement_induction.py:
00000000-0000-0000-0000-000000000002, name "Loomworks", visibility 'automatic'.append_event (or direct insert matching the established event-log pattern).loomworks-candidate-seed-v0_4.md.convergence_reached=True, zero findings, Operator as actor.The migration reads the seed file content at migration time. The downgrade reverses: delete the Loomworks engagement row and its memory events.
Verification: uv run alembic upgrade head succeeds. The Loomworks engagement exists in the database with correct visibility. uv run pytest -v still green (1038 passed — no regressions).
Rollback: uv run alembic downgrade -1.
Commit: Phase 15 step 2: Loomworks engagement induction.
Step 3 — Founding Memory assertions migration.
Write migration 0035_phase_15_founding_memory.py:
assertion_added event (state "held"), then assertion_committed event (state "committed", committed_by Operator).00000000-0000-0000-0000-000000000002).
Verification: uv run alembic upgrade head succeeds. Five committed assertions exist in the Loomworks engagement's Memory. uv run pytest -v still green.
Rollback: uv run alembic downgrade -1.
Commit: Phase 15 step 3: founding Memory assertions.
Checkpoint A — Loomworks engagement inducted. Seed recorded. Five assertions committed. Operator confirms before signup modification.
Step 4 — Automatic membership in signup.
Modify the signup endpoint handler (the function behind /auth/signup/totp-verify) to add a membership row and designation row for the Loomworks engagement in the same transaction that creates the person record. The LOOMWORKS_ENGAGEMENT_ID constant is imported and used directly.
Write migration 0036_phase_15_test_person_loomworks_membership.py:
Write test files per Section 10.2.
Verification: uv run pytest -v reports 1038 + ~26 = ~1064 passed, 2 skipped.
Rollback: git revert.
Commit: Phase 15 step 4: automatic membership and tests.
Step 5 — Iterate to green.
Run uv run pytest -v. For each failing test, diagnose and fix the implementation (not the test, unless the test is wrong against this CR). Continue until all tests pass.
Verification: uv run pytest -v reports all tests passing.
Rollback: if iteration cannot reach green within reasonable effort, surface to Operator with the failing tests' output.
Commit: Phase 15 step 5: acceptance suite green.
Checkpoint B — Substrate complete. All tests green. Operator confirms before frontend work begins.
Step 6 — Welcome page update.
Update the /welcome page to add the Loomworks engagement card (Section 9.1). Render against the brand guide HTML side-by-side during development. The card links to /engagement/<LOOMWORKS_ENGAGEMENT_ID>.
Verification: lint + tsc + build clean. The welcome page shows the Loomworks engagement card.
Commit (frontend repo): Phase 15 step 6: welcome page Loomworks engagement card.
Step 7 — Dashboard verification.
Verify the Loomworks engagement appears on the dashboard for a person with a Loomworks membership. If the dashboard already reads from /me/memberships (Phase 14, Step 13), no code change is needed — confirm visually. If the engagement does not appear, diagnose and fix.
Verification: lint + tsc + build clean. Dashboard shows the Loomworks engagement card with "Contributor" label.
Commit (frontend repo, if changes needed): Phase 15 step 7: dashboard Loomworks engagement verification.
Step 8 — Implementation notes and tagging.
Create docs/phase-impl-notes/phase-15-implementation-notes-v0_1.md recording: what Phase 15 built, any findings surfaced during execution, any divergences from this CR.
Verification: file exists and is reviewable.
Commit: Phase 15 step 8: implementation notes.
Checkpoint C — Final. Both repos green. A new person can sign up and see the Loomworks engagement on their dashboard. The Loomworks engagement has five committed assertions in its Memory. Tag both repos as phase-15-loomworks-universal-commons.
Phase 15 is accepted when:
On acceptance: tag both repos as phase-15-loomworks-universal-commons. Write implementation notes.
The Operator (Marvin Percival) already has four memberships (one per existing engagement), each with the 'operator' designation. Phase 15's migration (Step 4, migration 0036) adds a Loomworks membership for all persons without one. The Operator's Loomworks membership should carry the 'operator' designation (not 'contributor') — the Operator governs this engagement per the seed's Authorisation section.
The migration logic: for Marvin Percival specifically (by person_id), create the membership with 'operator' designation. For all other persons (the test person, any future persons), create with 'contributor' designation.
Depends on.
phase-14-person-layer.loomworks-candidate-seed-v0_4.md).loomworks-phase-15-scoping-note-v0_1.md).Enables.
> Read this CR from top to bottom before starting.
>
> Execute the eight steps in Section 11 in order. After each step, run the verification command named in that step, report the result, and commit the step's work to git with the named commit message. Proceed automatically through Steps 0–3, but stop and wait for explicit Operator confirmation at Checkpoints A, B, and C.
>
> If any verification fails, stop and report the failure with the full error output. Do not improvise around a CR deficiency. If you encounter a problem the CR's choices cannot solve cleanly, stop, report the problem, and wait for the CR to be amended.
>
> The construction decisions in Section 4 are closed; do not relitigate them. The assertion content in Section 8 is concrete; use it exactly. The test specifications in Section 10 are concrete; follow them. The order of operations in Section 11 is concrete; do not reorder steps without explicit direction.
>
> The seed content for the Seed memory object comes from docs/seeds/loomworks-candidate-seed-v0_4.md (committed in Step 1 or already present). Read the file at execution time; do not rely on inline snippets from this CR.
>
> The founding Memory assertion content comes from Section 8 of this CR. Use the text exactly as written.
>
> Begin with the pre-flight check when ready.
v0.1 (2026-04-26). Initial draft. Full Phase 15 scope per scoping note v0.1: Loomworks engagement induction from seed v0.4, five founding Memory assertions, automatic membership at signup, welcome page update, dashboard verification. Three Alembic migrations (engagement induction, founding Memory, test person membership). Eight steps with three checkpoints.
DUNIN7 — Done In Seven LLC — Miami, Florida Phase 15: Loomworks engagement as universal commons — CR v0.1 — 2026-04-26