DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-inbound-seam-minimal-slice-v0_1.md

Change Request — Inbound seam, minimal observable slice — v0.1

Document: cr-inbound-seam-minimal-slice-v0_1 Version: v0.1 Date: 2026-06-29 Type: Phase change request — engine (one route, one auth dep, one token table, one event kind, one MemoryObject). Frontend: none new (reuses the held-assertion card). Consumer: Claude Code (executor). Branch base: engine main @ d32c99b. Origin: The minimal testable inbound slice (loomworks-scoping-note-inbound-seam-minimal-slice-v0_2), grounded by Step 0 against engine d32c99b. Reads with: the bidirectional-substrate investigation (the inbound half) and the source-provider investigation (the outbound half, sequenced after).


Plain-language summary

The smallest real thing that proves the inbound seam, and that you can test with one curl. An external system POSTs a JSON fact at an engagement, authenticated with a per-engagement inbound token. The fact lands in that engagement's Memory as a held assertion — a proposed fact, FORAY-attested, waiting for the Operator to commit or discard it. You test it by POSTing, then opening the Memory room and seeing the held card appear. It accepts data only and can do nothing but propose a fact, so the bright line (data, not commands) holds by construction — a held assertion can't even enter the record without the Operator committing it.

What Step 0 corrected (recorded, not smoothed)

The scoping note got three things wrong about the real engine; Step 0 found them and the CR uses the real model:

  1. No "provenance source" field. Provenance is PROV-aligned (wasAttributedTo, wasGeneratedBy) — there is no source enum to set to inbound_seam. Origin is carried by event_kind + actor_kind. So "this came from the inbound seam" is a new event_kind="inbound_event_received" plus the inbound actor's kind — not a provenance string.
  2. No existing event kind fits. A new inbound_event_received kind is needed (free-form column + one anchor-priority line; no migration).
  3. No "Memory event list." The Memory room renders assertions, not raw events. A raw attested event would land correctly but be invisible. So the slice drafts a held assertion (the way the upload pathway does) — heavier than a raw event by a little, but the only path observable through the existing surface, and the correct eventual shape anyway (an inbound fact should be a reviewable assertion).

The slice

POST /engagements/{id}/inbound — authenticated by a per-engagement inbound token (a dedicated machine principal, auth A2). An external JSON fact arrives → drafts a held assertion in the engagement's Memory → FORAY-attested automatically at the write seam → visible as a held card in the Memory room. The Operator commits or discards it. Curl-testable.

What it reuses (Step 0, the thin part)

What's net-new (Step 0, small and mechanical)

Decisions (settled)

Scope boundary

The build — steps

Per-step commits, suite green at each, halt-before-push. Engine only.

Step 1 — the inbound-token table + auth dep (auth A2)

Dedicated per-engagement inbound-token table (labeled, sha256-hashed, compared not decrypted). Mint/verify helpers modeled on contributors/tokens.py. An auth dep modeled on the get_resolved_actor bearer branch, resolving the token scoped to the engagement and yielding an inbound/machine actor (kind ∈ {contributor, agent} — no CHECK migration).

Suite expectation: a valid inbound token resolves to the inbound actor scoped to its engagement; an invalid/wrong-engagement token is rejected.

Step 2 — the MemoryObject + event kind

The external-fact MemoryObject subclass + OBJECT_TYPE_REGISTRY registration; the inbound_event_received event kind + its _ANCHOR_PRIORITY line.

Suite expectation: the object serializes/deserializes through the registry; the event kind is recognized.

Step 3 — the route (drafts a held assertion, attested)

POST /engagements/{id}/inbound: the inbound auth dep + get_db_session → build the external-fact MemoryObject with Provenance → draft a held assertion from it (the upload pathway's assertions_drafted shape) → append_event(... event_kind="inbound_event_received" ...) (FORAY fires automatically).

Suite expectation: a POSTed JSON fact drafts a held assertion in the target engagement; the event is written and attested (the reserved seam fires); a malformed UUID 422s; a missing/invalid token is rejected.

Acceptance — the curl test (the live-check proof)

A real POST against the running engine:


curl -X POST https://<dev>/engagements/<id>/inbound \
  -H "Authorization: Bearer <inbound-token>" \
  -H "Content-Type: application/json" \
  -d '{"fact": "a coaching client completed their 360 assessment, score 72"}'

Then open the engagement's Memory room → a held card carrying that fact appears, FORAY-attested. The Operator commits or discards it.

This is the acceptance — not a headless-only check. The slice exists to be observed: POST, look at Memory, see the held card. (The card won't say "inbound" yet — deferred.)

What this proves

The inbound half of the bidirectional thesis, walking: external system → inbound seam → governance band (FORAY + provenance via actor/event-kind) → Memory (as a reviewable held assertion), bright line holding by construction. With that confidence, the outbound (provider) half — where the thread began — gets scoped next, against a proven governed-transit pattern.


DUNIN7 — Done In Seven LLC — Miami, Florida Change Request — Inbound seam, minimal observable slice — v0.1 — 2026-06-29 POST /engagements/{id}/inbound, per-engagement inbound token (dedicated table, auth A2). External JSON fact → drafts a HELD ASSERTION in Memory (Fork A, observable — not a raw invisible event) → FORAY-attested automatically via append_event's reserved seam → visible as a held card, Operator commits/discards. Bright line by construction (held assertion, no path to action). Thin: reuses append_event + the submit_manual_content template + the held-assertion card; net-new is small (an inbound-token table, an event kind, a MemoryObject, the route). Step 0 corrected three scoping premises (no provenance-source field → origin = event_kind + actor_kind; new inbound_event_received kind; no Memory event-list → it's an assertion list, so draft an assertion). Acceptance is the curl test, observed in Memory. Base engine d32c99b.