DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path investigations/loomworks-graph-role-elaboration-v0_1.md

Loomworks Graph-Role Elaboration — v0.1

Purpose: Claude.ai produced a plain-English explanation of "how graphs help in Loomworks processes" grounded only in project-knowledge copies (Loom Protocol v0.1, the five-case stress test, several investigations) — it could not reach the current seed, the current-status manifest, or live engine code. This document verifies, corrects, and extends that explanation against those canonical sources, so Claude.ai can check its own explanation against it.

Sources read, in order:

  1. current-status/current-status-manifest-v0_72.md + current-status-manifest-v0_72-amendment-v0_1.md (highest present; the amendment covers only the create-stage doors 2/3 ship and touches nothing graph-related)
  2. candidate-seeds/loomworks/loomworks-candidate-seed-v0_12.md (confirmed canonical — no v0.13 has been cut; see §6)
  3. architecture/loomworks-architecture-specification-v0_4.md
  4. Parked provenance-threads reframe material (session handoff + build-queue; see §6 — this is not a numbered seed, contrary to how the request framed it)
  5. Live engine code at /Users/dunin7/loomworks-engine

Discipline: every implementation claim below is tied to a file path (and line number where useful); every protocol/seed claim is tied to a document and section. Nothing here reflects code I wrote or changed — read-only inspection throughout.


1. What the seed (v0.12) actually commits to

The canonical seed is loomworks-candidate-seed-v0_12.md. Nothing named "v0.13" exists as a seed — see §6 for what does exist under that description.

Assertion structure and provenance — section "Memory": Memory is "accumulated knowledge with provenance — everything that has been contributed, by everyone, over the life of the work." Each contribution "carries who contributed it, when, how — typed, spoken, extracted from a document, described from an image — and which scope it belongs to. The system records the lineage automatically." This is the seed's whole provenance commitment: attribution + method + scope, recorded automatically. It does not specify a data model, a table, or a PROV vocabulary — that's left to the architecture/protocol layer.

Corrections preserved, not smoothed — stated twice. In "Memory": "Memory does not forget. Contributions that are corrected or superseded remain visible as part of the record. The trajectory matters — not just where the work arrived, but how it got there. A correction is a contribution. A retraction is a contribution. Both are preserved. This holds at every scope." Restated as a named constraint in "Constraints": "Corrections preserved, not smoothed. Loomworks inherits the methodology's discipline: superseded assertions remain in memory as corrections, vocabulary renames are recorded, trajectory is preserved alongside destination."

Recall — the seed does not commit to a retrieval/query mechanism. Manifestation is described as "a reading of Memory" that organizes committed assertions into groups, but addressing/lookup mechanics are left unspecified at this layer.

An explicit protocol-gap admission, in "Constraints": "Where the methodology requires commitments Loom v0.1 does not yet specify (supersession severity, access-layer primitives, federation, contribution-trust governance, engagement-as-first-class structure, Memory scopes above the engagement), Loomworks implements them at its layer pending future protocol extension." This is important for grading Claude.ai's explanation: the seed itself says Loom v0.1 is silent on how severe a supersession is (correction vs. retraction-strength vs. minor edit) — Loomworks is improvising above the protocol floor here, not implementing a settled spec.

"Considerations" as a named discipline alongside corrections — "Success conditions" names "the methodology's disciplines (corrections, considerations, drift, promotion, authorisation)" as things the environment should visibly carry. This matters for §4 below: "consideration" is the seed's word for the deliberation act that in the engine produces wasConsideredAndAffirmed / wasDeliberateExceptionTo — and, on paper, wasConsideredAndRejected.

What this means for Claude.ai's explanation to get right: the seed's graph claims are attribution + method + scope-of-origin, automatic lineage recording, and corrections/retractions preserved rather than deleted. It does not itself promise PROV, does not itself name wasConsideredAndRejected, and does not itself promise a queryable dependency graph. Those come from the architecture spec and Loom Protocol, one layer down — see §2 and §3.


2. Architecture Specification v0.4 — the graph as designed

architecture/loomworks-architecture-specification-v0_4.md, §02 ("the assertion," Core concepts): "The atomic unit of Memory. An assertion carries a claim, attribution (who contributed it), provenance (how it entered Memory — typed, spoken, extracted from a document, derived from an image), lifecycle state (held, committed, retracted, superseded), and timestamps... The lifecycle is governance-bearing: held-then-commit is the default ceremony; retraction before commit is silent; retraction after commit is part of the audit trail."

§03 ("Memory: Lifecycle"): "Supersession is the pattern where a new assertion supersedes a prior one without retracting it — the trajectory is preserved." This is the spec-level definition of supersession as a graph edge, not a deletion.

§09 ("Loom · FORAY · OVA"): "Loom is at v0.1 spec (2026-04-12), built underneath Loomworks, rests on PROV." This confirms Loom v0.1 is explicitly PROV-derived — the assertion graph's vocabulary lineage traces to the W3C PROV data model, with Loom's wasConsideredAndRejected as its one named addition beyond PROV (the addition is named in the engine's own code comments — engagement/types.py:342 — rather than spelled out in the architecture-spec text itself, which doesn't enumerate the PROV-relation list).

§13 ("Open questions") is the spec's own admission of the gap Claude.ai should be told about directly: "Cross-Engagement awareness … Not yet a Companion behavior; the substrate supports redirect at the lifecycle level (Phase 17), but the awareness layer that triggers it is missing." In other words: the architecture spec itself flags that redirect exists as substrate but nothing in the product surfaces when a Companion should trigger it — confirmed as still true in the live code (§4 below: redirect is a fully-implemented API operation, but it creates no graph edge, so it can't feed any future "awareness" query even once built).

Note for completeness: v0.4 is not the highest architecture-spec version present in the repo — v0.5 and v0.6 also exist (architecture/loomworks-architecture-specification-v0_6.md is newest). The task specifically named v0.4 as the document to read; the graph-relevant content in v0.4 (assertion definition, supersession, Loom/PROV lineage, the redirect open question) reads as stable across the versions and I did not find contradicting language when spot-checking, but Claude.ai should treat v0.4 as the floor of what's settled, not necessarily the latest word on every open question.


3. How the graph is actually implemented in the engine today

There is no dedicated assertions relational table. A migration comment states this as a deliberate decision (loomworks-engine/migrations/versions/0006_phase_3_assertion_types.py): "Assertions and Relationships are memory objects — they live in the existing memory_events log and the current_memory_objects materialized view. No new table is required." Assertions are one MemoryObject subtype in a single event-sourced substrate, not a bespoke schema.

The two real tables:

Assertion fields live inside payload, defined by the Pydantic model Assertion (src/loomworks/engagement/types.py:275-334): content, grammar_element (definition/constraint), normative_force, state (held/committed/retracted/discarded), committed_at/by, retracted_at/by/rationale, discarded_at/by, was_revision_of, display_number, metadata: dict. The model is frozen=True — append-only is enforced at the type layer, not just by database convention.

Supersession is stored redundantly three ways, none of them a superseded_by column: (a) a monotonic version integer per object_id; (b) Assertion.was_revision_of: MemoryRef | None; (c) Provenance.wasRevisionOf: MemoryRef | None, the same fact embedded a second time in the object's provenance block. revise_assertion additionally writes a wasRevisionOf Relationship edge (see below) — so a single supersession is recorded in up to three places simultaneously (field, embedded provenance, and graph edge).

Provenance is embedded per-object, not a separate table, via the Provenance model (src/loomworks/memory/base.py:106-119):


class Provenance(BaseModel):
    wasAttributedTo: ActorRef
    wasGeneratedBy: UUID
    wasDerivedFrom: list[MemoryRef] = Field(default_factory=list)
    wasRevisionOf: MemoryRef | None = None
    wasInvalidatedBy: MemoryRef | None = None
    wasConsideredAndRejected: list[MemoryRef] = Field(default_factory=list)

ActorRef (memory/base.py:17-91) carries kind: Literal["contributor","agent","person","companion"], id, instruction_version, display_name, capability_ref, approval_mode. AI-vs-human marking is a single frozenset constant, NON_HUMAN_ACTOR_KINDS = {"agent","companion"} (base.py:31), which gates every human-only transition (commit_assertion, retract_assertion, discard_assertion all reject a non-human actor). This constant is the literal implementation of "AI origin marked non-suppressibly" (see §6) for the actor-attribution slice of provenance — though not for the fuller outward/inward origin-thread model, which isn't built (§6).

The graph edges are a second MemoryObject subtype, Relationship (types.py:337-371), also living in memory_events/current_memory_objects, not a separate edges table:


class Relationship(MemoryObject):
    source: MemoryRef
    target: MemoryRef
    vocabulary: Literal[
        "wasDerivedFrom", "wasRevisionOf", "wasInvalidatedBy", "wasAttributedTo",
        "wasGeneratedBy", "wasConsideredAndRejected", "relates-to", "corroborates",
        "wasConsideredAndAffirmed", "wasDeliberateExceptionTo",
        "declaresShapeType", "declaresRenderType", "hasShaping", "explains",
    ]
    qualifier: dict[str, Any] = Field(default_factory=dict)

This is the PROV vocabulary Claude.ai should cite exactly — it is real, live, and this is its only definition site (duplicated verbatim in assertions.py and api/schemas.py, all three copies kept in sync by hand, not a shared import). No Postgres ENUM/CHECK backs it; it is Python-Literal discipline over JSONB only.

wasConsideredAndRejected — declared everywhere, written nowhere

This is the single most important correction to make to any explanation that treats the PROV vocabulary as uniformly implemented. wasConsideredAndRejected is:

But no code path ever populates it with a real value. The ConsiderationEpisode.terminal_state enum (considerations.py) is attest | promote | amend | retire | escalate | no_change | deliberate_exception — there is no reject/rejected terminal state, and the closure-handler dispatch has no branch that would emit this vocabulary term. By contrast, its two siblings are wired: wasConsideredAndAffirmed is written by _close_no_change (considerations.py:1078, shape_confirmation.py:759), and wasDeliberateExceptionTo is written by _close_deliberate_exception (considerations.py:1127). So the seed's "considerations" discipline (§1) is real and shipped for affirm/exception outcomes, but the reject outcome — despite being fully scaffolded in the type system — has no producer. If Claude.ai's explanation asserted that Loom's rejection semantics are implemented end-to-end, that's the specific claim to correct.

Redirect

Fully implemented: src/loomworks/engagement/redirect.py (376 lines) + POST /engagements/{engagement_id}/assertions/{assertion_id}/redirect (api/routers/assertions.py:406-490). Held-assertions-only (raises RedirectAssertionNotHeldError otherwise), requires contributor/operator membership on the target engagement, copies any attached source file, tags the new target assertion's metadata with redirect_from_engagement_id/assertion_id/name, then does add_assertion (target) + retract_assertion (source, rationale "Redirected to {target_engagement_name}") as one atomic operation.

Important nuance, not obvious from the architecture-spec text alone: redirect creates no Relationship/PROV edge between the source and target assertion. The only linkage is the free-form metadata keys and the retraction rationale string. This means the bidirectional Relationship-graph traversal described next (§4) cannot walk a redirect lineage at all — redirect is graph-adjacent in spirit but graph-invisible in fact. This directly substantiates the architecture spec's own §13 admission that "the substrate supports redirect... but the awareness layer... is missing": even if a future awareness layer were built, it would have nothing in the Relationship graph to query — it would have to be taught to read metadata instead.


4. The forward-walk question: which direction of traversal is implemented

Backward (an assertion's own provenance) and edge-level bidirectional traversal: implemented. list_relationships_for_assertion (assertions.py:936-974) queries current_memory_objects WHERE object_type='relationship' AND (payload->'source'->>'id' = :aid OR payload->'target'->>'id' = :aid) — the OR target half of that clause is exactly "what points at this assertion," exposed via GET /engagements/{id}/assertions/{id}/relationships. A narrower reverse lookup exists specifically for the explains vocabulary term (api/routers/explanations.py:272-284). Point-in-time/version-history reconstruction for a single assertion's own chain (get_assertion_history, get_assertion_at_version, memory/queries.py) is also implemented.

Forward into the Shape/Render production layer: not implemented. No query anywhere in src/ performs a containment filter against ShapeEvent.selected_memory_refs (i.e., "find every Shape/Render whose selected_memory_refs array contains this assertion id"). That field is only ever read for a given, already-known shape event — never searched by assertion id. So:

> Plain answer for Claude.ai: the engine can answer "what edges touch this assertion" (bidirectional, at the Relationship-graph level) today. It cannot answer "which Shapes/Renders were built from this now-superseded assertion" today — that's the premise-change-detection question, and it is unbuilt future work, not a solved or even partially-solved query. If Claude.ai's explanation implied that supersession automatically flags downstream dependents, that is the correction to make: supersession updates the assertion's own version chain and (when written via revise_assertion) a wasRevisionOf edge, but nothing walks outward from there into Shapes or Renders.


5. The membership/authority chain and Companion access

MembershipRow (persons/models.py:149-221) is the person↔engagement join: unique on (person_id, engagement_id), soft-deleted via an active boolean (rows are never physically deleted — itself a small instance of "corrections preserved, not smoothed" applied to membership rather than assertions). MembershipDesignationRow stacks designations on a membership via composite PK (membership_id, designation); allowed values are {"operator", "contributor", "domain_expert"} (persons/memberships.py). Scope/authorization checks resolve through get_membership_for_person_on_engagement, used by both redirect's target-membership check and the general auth middleware's 403 gate.

Companion access is not a separate model. There is no Companion-principal table and no distinct Companion access-set computation: the ActorRef docstring and the Stele identity work (surfaced in the v0.72 manifest, §5.1 below) both confirm a Companion's id is the underlying person's own UUID. A Companion's access set is therefore identical to, and computed from, that person's own memberships rows — there is nothing narrower or broader layered on top today.

Where OVA/GRANTHA attach: the v0.72 manifest documents that GRANTHA has replaced OVA in the access-control seat (consuming OVA underneath for "blind ZK verification") — this is stated in passing in queued-work material referenced from the manifest, not elaborated there. Architecture spec §09 still frames OVA as the seam/stub for this function; that framing is stale relative to the manifest's GRANTHA note. Concretely, this seam attaches at the same point identified above — membership/designation resolution — it does not change where in the code the access set is computed (still MembershipRow/MembershipDesignationRow), only what authenticates/attests the check. I did not find GRANTHA-specific code in loomworks-engine itself during this pass; it appears to live as a separate protocol layer referenced by, but not yet merged into, the engine's own membership tables. Treat this as a pointer for a follow-up read (protocols/grantha/) rather than a settled implementation claim.

Phase 39 / cross-engagement aggregation is live at src/loomworks/api/routers/dashboard.py: GET /me/dashboard/active, /needs_you, /recent, all built on a shared person_engagements CTE keyed to active memberships joined to engagements, excluding visibility='personal'. /needs_you pulls held assertions straight from current_memory_objects (object_type='assertion' AND payload->>'state'='held') alongside pending shape-confirmations and renders awaiting decision. The route's own docstring notes no designation-aware filtering ships yet — every member sees every held item on an engagement they belong to; a deferred decision (D2 in the originating CR), not a bug.


6. The provenance-threads material — what it actually is, and how it changes the graph model

This is not a numbered seed v0.13 — nothing with that name exists (verified: candidate-seeds/loomworks/ and its archive/ subdirectory run v0.8 through v0.12 only). What matches the description exactly is a parked reframe, recorded in session-handoffs/loomworks-session-handoff-2026-06-05-c5-arc-v0_1.md, §6, under the heading "Two foundational reframes — PARKED by Operator decision (do not draft without commission)":

> Reframe B — provenance-threads / non-human contribution. Every contribution carries threads to its origin: outward (person, document, sensor/instrument, query, another scope) or inward (inference derived from existing assertions). **Trust axis is tie-to-verifiable-source, not human-vs-machine — a calibrated sensor ranks high, an AI investigation low. AI/non-human origin must be marked non-suppressibly** (invisible as interface/clerk, attributed as knowledge source). Folds AI-as-contributor, query-as-contributor, sensor, other-scope, and inference into one model.

The handoff records this as explicitly parked — "the Operator chose to let them sit" — with an implementation guardrail attached even though the reframe itself isn't commissioned: don't make contributor person-only (origin should be a typed reference that can point at a person, AI actor, query, sensor, or another scope); don't fuse read/write access; don't treat an assertion's source as always external (a provenance edge must be able to point at another assertion, for inference/recall-of-dependents).

This material is treated as more than merely parked in one place: current-status/loomworks-companion-build-queue-v0_4.md, Item 6 ("Provenance display, cross-cutting"), restates the same model as an active build concern: "Provenance is the surface manifestation of the attestation substrate (FORAY) and of the provenance-threads model (each contribution carries typed origin threads — outward to person / document / sensor / query / scope, or inward via inference from existing assertions; trust axis is tie-to-source, not human-vs-machine; AI origin marked non-suppressibly; origin extensible, threads walkable)." So: parked as a seed commitment, but already load-bearing as a build-queue requirement for how provenance should display.

Does it change the graph model beyond seed v0.12? Yes, in one specific way worth flagging precisely: seed v0.12's provenance commitment (§1) is attribution + method + scope — a flat description. Reframe B would generalize this into a typed, directional origin edge (outward vs. inward) with a trust ranking independent of human/AI status. The live Provenance.wasDerivedFrom: list[MemoryRef] field already technically supports an "inward" edge (a provenance pointer to another assertion, for inference), and ActorRef.kind already supports non-human origin marking (NON_HUMAN_ACTOR_KINDS, §3) — so the engine has pieces of Reframe B's plumbing already in place, built for other reasons, without ever having formally adopted the reframe. What's missing is the outward/inward typing itself and any trust-axis ranking — those exist only in the parked document, nowhere in code.

One caution for Claude.ai: a competitive-positioning investigation (investigations/competitive-positioning/loomworks-cognisee-as-referenced-domain-investigation-v0_1.md:137) paraphrases the seed as saying "AI origin marked non-suppressibly" — but that exact phrase is not in seed v0.12 itself; it's that investigation's characterization blending the seed's spirit with the parked Reframe B. Don't cite it as a direct seed quote.


Summary of corrections to make against a project-knowledge-only explanation

  1. There is no assertion table — assertions and relationships are both MemoryObject variants living in one event log (memory_events) plus one materialized-view projection (current_memory_objects).
  2. Supersession is not a superseded_by pointer — it's a version counter, a was_revision_of field, a duplicated Provenance.wasRevisionOf, and (when written via the revise path) a wasRevisionOf edge — up to four representations of one fact.
  3. wasConsideredAndRejected exists everywhere in the type system and is written by nothing — treat "considerations" as implemented only for affirm and deliberate-exception outcomes, not rejection.
  4. Redirect is real, shipped, atomic — and deliberately graph-invisible (metadata only, no edge). The architecture spec's own open-questions section already flags the missing "awareness layer" this implies.
  5. Reverse traversal exists at the edge level (bidirectional Relationship query) but not into the Shape/Render layer — "which Shapes/Renders depend on this superseded assertion" is unbuilt, not partially built.
  6. Companion access is not a distinct model — it's the underlying person's own membership rows, full stop, with GRANTHA (successor to OVA) attaching at that same membership/designation seam rather than replacing it.
  7. The richest provenance model on record — outward/inward typed origin threads, trust-axis-not-species — is parked, not seeded, though its display consequences are already an active build-queue item, and some of its plumbing (non-human actor marking, derivation-from-another-assertion) already exists in code for unrelated reasons.

DUNIN7 — Done In Seven LLC Loomworks — Graph-Role Elaboration — v0.1