DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path phases/phase-47-credit-substrate-foundation/loomworks-invitation-credit-referral-scoping-note-v0_7.md

Loomworks — Invitation Codes, Credit System, and Referral Architecture — Scoping Note

Version. 0.7 Date. 2026-05-07 Provenance. Claude.ai scoping session. Operator: Marvin Percival. Status. Scoping note. Discovery-stage. Seventh draft. Three architectural insights from v0.6 discussion.

Prior versions and what changed.

  1. Account lifecycle with exhaustion choice. When trial credits exhaust, the user is offered three paths: add own key (convert to Maker), suspend (hold N weeks, reactivate via original auth), or delete now. Suspension expires to deletion if not reactivated. Deletion is FORAY-attested and registers the email hash so a future signup is treated as a returning recognized identity, not a fresh trial. Replaces v0.6's structural-only soft exhaustion.
  2. Inverted abuse model — issuance-time gating. The check moves from "does this code work?" (redemption-time) to "should we send to this email?" (issuance-time). Credits are not requested as freeform codes; credits are delivered to specific email addresses. The Authority binds a grant to an email at issuance and registers the email hash. Future requests from the same email-hash are recognized — eligible for grants only at the Authority's discretion (e.g., after a cooling-off period or as a deliberate Operator decision). Casual abuse (multiple Gmail addresses) becomes self-defeating because each email is consumed against the registry on its first grant.
  3. Public request form with Companion-as-Authority. Anyone can request credits via a public form with three or four questions. The Companion on Instance A reads the answers, consults model profile assertions in Memory, decides which credit type fits the requester, and issues a targeted grant. Replaces the v0.6 "Operator-issued freeform code" path as the primary public mechanism. Operator-curated grants and referrer-initiated grants remain as additional flavors.

Additionally: invitation_code table renamed to credit_grant; email_grant_registry table added; account_status and related person columns added; suspension/deletion mechanism scoped to Accounting evaluator (Phase 48); request form acknowledged as requiring a public marketing website.

Discovery trajectory. Conventional accounting (v0.1) → FORAY-native (v0.2) → external engagement (v0.3) → full architecture (v0.4) → co-located infrastructure engagements (v0.5) → model-identified credits (v0.6) → grant-based delivery with email registry and lifecycle accountability (v0.7). The v0.7 insight: the Authority controls issuance, not just redemption. Credits are not requested in the freeform-code sense; they are delivered.


1. What this is

A growth infrastructure layer serving three purposes:

  1. Access — by delivery, not by code. No freeform code redemption. Credits are delivered to specific emails by the Credits Authority, which decides eligibility at issuance based on the email registry.
  2. Metered trial. Each grant carries a credit allocation that determines both how much and at what model tier.
  3. Lifecycle accountability. When credits exhaust, the user chooses: add own key, suspend, or delete. Each path is attested. Re-eligibility for trial after deletion is governed by the email registry.

The credit system is governed through two Loomworks engagements — a Credit Management Engagement (the Authority) and an Accounting Engagement — whose data co-locates with the engine under a credit schema.


2. Architecture: two engagements, one schema, engine co-location

2.1 The two engagements

Credit Management Engagement (the Credits Authority). Writes FORAY flows for all credit-affecting events. Owns policy: oracle rates, grant decisions, email registry, referral policy, campaign data, model profiles. Its specialist writes issuance flows, consumption flows, suspension and deletion flows. The Companion advises the Operator on grant decisions and serves as the Authority's decision-making surface for inbound requests.

Accounting Engagement. Maintains balances from flows. Does not originate flows. Two mechanisms:

2.2 Why co-located

Three constraints drive co-location:

  1. Trigger atomicity. Flows and balances must share a database.
  2. Hot-path reads. The engine reads balances on every system-key converse turn.
  3. Transactional signup and grant claim. Email registry check + claim token validation + credit issuance + person creation in one transaction.

The data lives under credit.* in the engine database. Logical separation (different schema) without operational separation (single connection pool, single backup).

2.3 Infrastructure engagements vs. operational engagements

Operational engagements. External data store, engine doesn't depend on it. Goosey storybook, FarmGuard.

Infrastructure engagements. Engine depends on the data operationally. Credit Management and Accounting. Data co-locates in engine database under separate schema.

The management surface is identical. Four rooms, approval cards, Companion, FORAY.

2.4 Archetype generality

Same as v0.6. The pattern reuses for any metered-resource credit engagement (GPU minutes, filament, translation characters). The Accounting archetype reuses for any environment with FORAY flows.


3. The asset model

3.1 Principles

3.2 Asset taxonomy

Same as v0.6. Three credit asset types (haiku, sonnet, opus), six provider token types (per model × input/output), Whisper seconds, USD cents.

3.3 The oracle

Same as v0.6. Per-credit-type rates. No cross-model conversion.

3.4 Per-credit-type model selection

Same as v0.6. Classifier always Haiku. Responder model determined by the credit asset_id resolved for this turn. Multi-credit-type users use highest-tier first, automatic tier-drop when exhausted.


4. FORAY-native events in the credit schema

Same as v0.6 with two additions:

4.1–4.5

Unchanged. Credit issuance, token consumption, referral, token purchase, reconciliation correction.

4.6 Account suspension (NEW)


Action flows:
  { asset_id: "loomworks_account_status", quantity: -1, from: "person_alice", to: "credit_authority",
    metadata: { reason: "user_chose_suspension", expires_at: "2026-05-28T...", original_auth: "passkey_xyz" } }

The loomworks_account_status is a synthetic asset whose flow records lifecycle transitions for FORAY auditing. The trigger updates a synthetic balance (1 = active, 0 = suspended, -1 = deleted) but the operative state lives on the person table.

4.7 Account deletion (NEW)


Action flows:
  { asset_id: "loomworks_account_status", quantity: -1, from: "person_alice", to: "credit_authority",
    metadata: { reason: "user_chose_deletion", deletion_kind: "user_initiated" | "suspension_expired" } }
  { asset_id: "loomworks_credit_haiku", quantity: -<remaining>, from: "person_alice", to: "credit_authority",
    metadata: { reason: "deletion_zero_balance" } }

The deletion flow zeroes any remaining balances and records the lifecycle transition. The person record is then anonymized (email replaced with deleted_<hash>, identifying fields nulled). The email hash persists in the registry. FORAY flows persist with the anonymized party_id.


5. The Accounting engagement — balance and lifecycle maintenance

5.1 The balance table

Same as v0.6. One row per party per asset.

5.2 The trigger function

Same as v0.6. UPSERT on flow insert.

5.3 The pipeline boundary expanded

Below the pipeline (trigger). Balance arithmetic from flow inserts.

Through the pipeline (time-filtered evaluator). Phase 48+ responsibilities expanded to cover:

The evaluator is the same Phase 44 trigger evaluator pattern applied to a wider set of conditions.

5.4 Companion surfacing per engagement

Credit Management Companion. Policy questions. Grant decisions. Email registry queries. Campaign performance.

Accounting Companion. State questions. Aggregate liability. Reconciliation health. Approaching expirations. Recent lifecycle transitions.


6. Integration seam: engine → credit schema

6.1 Balance check and model resolution

Same as v0.6. Multi-credit-type resolution returns highest-tier with positive balance.

6.2 Consumption recording

Same as v0.6. Multi-flow per turn. Trigger fires.

6.3 Grant issuance (replaces "Credit issuance" in v0.6)


def issue_grant(
    recipient_email: str,
    asset_id: str,         # "loomworks_credit_haiku" etc.
    amount: int,
    grant_kind: str,       # "form_initiated", "operator_curated", "referrer_initiated"
    initiated_by: UUID | None,  # person_id of Operator or referrer (null for form_initiated)
    campaign_ref: str | None,
    metadata: dict,        # form answers, referrer context, etc.
) -> Grant:
    """Check email_grant_registry. If eligible, create credit_grant row, generate claim_token,
    register email hash, return grant object with claim_token. Caller sends the email."""

6.4 Grant claim


def claim_grant(claim_token: str, person_id: UUID) -> ClaimResult:
    """Verify token, check not expired, check email matches the verifying person.
    Write credit issuance flow. Mark grant as claimed. Trigger updates balance."""

6.5 Email registry check


def check_email_eligibility(email: str) -> EmailEligibility:
    """Hash the email (and the normalized form). Look up in email_grant_registry.
    Returns: ELIGIBLE_NEW (never seen), ELIGIBLE_COOLED (last grant > N months ago),
    INELIGIBLE_RECENT (last grant within N months), INELIGIBLE_DELETED (deleted account on this email).
    Eligibility decision is made; the Authority can override (Operator-curated grants always issue)."""

6.6 Account lifecycle


def suspend_account(person_id: UUID, requested_by: UUID) -> None
def reactivate_account(person_id: UUID, auth_method: str) -> ReactivationResult
def delete_account(person_id: UUID, deletion_kind: str) -> None

6.7 Other seam functions


def check_credit_balance(person_id: UUID, asset_id: str) -> int
def resolve_credit_model(person_id: UUID) -> CreditResolution | None

6.8 Implementation

In-process Python module for alpha. Direct database calls. Interface abstraction for future HTTP transport.


7. Credit grants and the email registry

This section replaces v0.6 §6 (Invitation codes). The conceptual shift: grants are delivered to specific emails by the Authority, not requested via freeform codes. The email registry is the abuse boundary.

7.1 The grant lifecycle


Initiated  →  Issued  →  Delivered  →  Claimed  →  Consumed  →  Exhausted  →  [Choice]
                                                                                  │
                                              ┌───────────────────────────────────┼───────────────┐
                                              │                                   │               │
                                          Add own key                          Suspend         Delete
                                              │                                   │               │
                                          Maker                            (N weeks hold)        │
                                                                                  │               │
                                                                       ┌──────────┴──────────┐    │
                                                                       │                     │    │
                                                                  Reactivate            Expire    │
                                                                       │                     │    │
                                                                   Active              Delete ────┘
                                                                                              │
                                                                                       Email hash retained

Phases:

7.2 Three grant flavors

Form-initiated grant. Anyone can request via the public form (§8). Companion-as-Authority decides eligibility and credit type. The primary public path.

Operator-curated grant. The Operator (with Companion advice) issues grants to specific email lists. Used for known audiences: conference attendee lists, VIP demos, partner outreach. Companion advises on credit type per audience profile.

Referrer-initiated grant. A current Loomworks user invites someone by providing the friend's email. Same flow as form-initiated but with referred_by recorded.

All three flavors share: the email registry check, the claim token mechanism, the email delivery, and the FORAY attestation.

7.3 The email registry


credit.email_grant_registry
├── email_hash               VARCHAR PRIMARY KEY  — SHA-256 of normalized email
├── email_normalized_hash    VARCHAR              — SHA-256 of aggressively normalized form (gmail+plus stripped, etc.)
├── first_grant_at           TIMESTAMPTZ
├── last_grant_at            TIMESTAMPTZ
├── total_grants_issued      INTEGER DEFAULT 1
├── last_grant_status        VARCHAR              — 'pending_claim', 'claimed', 'expired', 'deleted'
├── last_status_at           TIMESTAMPTZ

Two hash columns:

Eligibility check uses both: a match on either hash means "we've seen this human before." This catches casual alias variations without requiring DUNIN7 to maintain the email itself.

The registry persists past person deletion. That's the whole point — deletion zeros the credits and anonymizes the person, but the email_hash registry remembers that this email previously received and exhausted a grant. A new signup attempt with the same email (by hash) returns INELIGIBLE_DELETED from the eligibility check; the user can still create an account, but as a Maker with their own key, not as a fresh trial.

7.4 Schema (lives in credit schema)


credit.credit_grant
├── id                  UUID PRIMARY KEY
├── claim_token         VARCHAR(64) UNIQUE       — opaque token in the email link
├── recipient_email     VARCHAR                  — stored only until claim; then nulled
├── recipient_email_hash VARCHAR                  — links to email_grant_registry
├── grant_kind          VARCHAR                  — 'form_initiated', 'operator_curated', 'referrer_initiated'
├── asset_id            VARCHAR                  — 'loomworks_credit_haiku' etc.
├── credit_amount       INTEGER
├── initiated_by        UUID | NULL              — Operator or referrer person_id
├── campaign_ref        VARCHAR | NULL
├── metadata            JSONB                    — form answers, referrer note, etc.
├── status              VARCHAR                  — 'pending_claim', 'claimed', 'expired', 'revoked'
├── expires_at          TIMESTAMPTZ              — claim deadline (e.g., 30 days from issuance)
├── claimed_at          TIMESTAMPTZ | NULL
├── claimed_by_person_id UUID | NULL
├── created_at          TIMESTAMPTZ

After claim, recipient_email is nulled (the hash remains in the registry; the cleartext email is no longer needed). After 30 days unclaimed, status becomes expired and a new grant can be issued to that email if eligibility allows.

7.5 Claim flow

  1. User clicks claim link in email. URL: https://app.loomworks.com/claim?token=<claim_token>.
  2. Loomworks frontend posts the token to POST /claim/grant.
  3. Backend looks up the grant by claim_token. If status not pending_claim or expires_at passed → error.
  4. User is required to verify the recipient email — magic link to that email, or passkey enrollment that binds to that email at registration.
  5. Once verified: create person record (or attach grant to existing person if email is already known to a person record), write credit issuance flow, mark grant claimed, update email_grant_registry status to claimed.
  6. Trigger fires on issuance flow, balance row created.
  7. User lands on the Companion with credits loaded.

All within one database transaction.

7.6 No code request, only delivery

There is no POST /signup-with-code endpoint. There is no freeform code field on signup. The substrate does not accept "I have this code, give me credits." It only accepts "I have this claim_token, give me the credits that token references." The token is generated by the Authority at issuance time and only sent via email. The user cannot manufacture a token; they can only consume one that was sent to them.

The require_invitation_code config flag from v0.6 D5 goes away. There is no flag because there is no path for codes-without-grants. Cold signup (no grant) goes to Maker with own key — a separate path from the trial path.


8. The request form (NEW)

8.1 Public form

A web form accessible without authentication. Located on the public Loomworks marketing website (which is itself a separate deliverable — see §16). Three or four questions.

Candidate questions. Tunable based on early signal:

  1. What kind of work do you do that you think Loomworks might help with? (free text, ~one or two sentences, required)
  2. Are you a developer, knowledge worker, professional, researcher, or something else? (single select, with "other / tell us")
  3. Are you exploring out of curiosity, or do you have a specific project in mind? (radio, two options)
  4. Anyone refer you to Loomworks? (free text, optional — captures organic word-of-mouth without requiring a referral code mechanism)

Plus the required field: email address.

The form is short by design. The questions are not gates — every requester gets something. The questions decide which credit type fits.

8.2 The Companion as Authority

Form submissions arrive as messages in a Companion conversation on Instance A. The Companion's system prompt for grant decisions:

> You are the Credits Authority for Loomworks. Someone has requested trial credits via the public form. Read their answers below. Consult the model profile assertions in your Memory (which describe what each model is good at and which audience benefits from each). Decide which credit type and amount is appropriate. Issue the grant via the seam. Briefly note your reasoning in the grant metadata.

The Companion reads:

The Companion decides:

The Companion writes:

8.3 Form alpha vs production

Alpha (Phase 47). No public form. Operator-curated grants only. The Operator manually triggers issue_grant calls (via the admin endpoint) for known emails. The substrate supports the form-initiated flow (the database tables, the seam, the claim mechanism), but no public surface yet.

Phase 48. Public form on the Loomworks marketing website. SMTP for email delivery. Companion-driven grant decisions. Form answers stored as Memory in the Credit Management Engagement.

Future. Form questions iterated based on observed signal. Operator can refine via the engagement pipeline (update model profile assertions; revise grant logic in the Companion's prompt; introduce additional questions if Memory observations show gaps).


9. Referrer-initiated grants

Replaces v0.6 §7 (Referral system). Conceptually simpler with the grant model.

9.1 The flow

  1. Current user says "I want to invite a friend" or "send credits to alice@example.com".
  2. Companion confirms intent, asks for the friend's email if not given.
  3. Companion calls check_email_eligibility(alice@example.com).
  4. If ELIGIBLE: Companion calls issue_grant(recipient=alice@example.com, asset_id=<policy choice>, amount=<policy>, grant_kind="referrer_initiated", initiated_by=<referrer's person_id>).
  5. Email goes to alice. Alice claims, signs up, becomes a person.
  6. When alice converts (adds her own key), a referral conversion flow writes credit to the referrer's balance.
  7. If INELIGIBLE: Companion tells the referrer "this email is already known to us — they've previously received Loomworks credits. They can sign up directly with their own key any time."

9.2 Referrer rate limit

The previous v0.6 D8 (default referral max_uses = 5) shifts meaning. There is no shareable code with multiple uses; each referral is a specific grant to a specific email. The rate limit becomes: how many referral grants a referrer can initiate per period (default: 5 per 30 days, admin-adjustable per person).

The trust escalation pattern still applies — referrers whose referrals convert at high rates earn higher rate limits.

9.3 Conversion credit

When alice (referee) adds her own API key (converts to Maker), the engine detects this state transition and calls issue_referral_credit via the seam. A flow writes credit to the referrer's balance. Idempotent: at most one conversion credit per referee.

The credit type for the conversion credit is policy: default is to mirror what alice received (Haiku referee → Haiku conversion credit to referrer). Operator can override per-campaign.


10. The token purchase loop

Same as v0.6.


11. The Credit Management Engagement (Authority)

11.1 Four rooms

11.2 Companion-as-Authority

The Companion serves as the Authority's decision-making surface. Two modes:

The Companion is autonomous on inbound by default (small grants, registered eligibility, low risk). Operator can require pre-approval if needed by setting a config flag or by Companion threshold (e.g., grants over a certain amount require approval card). Default: pre-approval not required for form-initiated grants up to standard amount; required for unusual amounts or distinguished requests.

11.3 Model profile assertions

Same as v0.6 §9.2. Three core assertions describe what each model is suited for. Updated through the engagement pipeline when new models ship.

11.4 Companion exhaustion choice messaging

When a person's credits exhaust (no other credit type available), the Companion presents the choice:

> "Your trial credits are used up. You've built [N engagements] and [M Library artifacts] in your time here. What would you like to do? > > Add your own API key — keep going with Loomworks at full capability. The Companion gets smarter with your own Sonnet key, and everything you've built stays. > > Hold for now — your account stays accessible for [N weeks]. You can come back any time within that window and add a key. After that, the account closes. > > Close the account now — clean exit. Everything you've built goes away."

The Companion is suitability-aware (per v0.6 §9.4) — for a user who's been doing specification work, the "add key" option mentions Sonnet's reasoning depth specifically.


12. License tiers and account states

12.1 License tiers

Same as v0.6 §10. Trial / Maker / Professional / Team. License tier gates feature capability; credit type gates model selection (system-key path).

12.2 Account states (NEW)

person.account_status:

Transitions:


active ──exhaust──→ exhausted
exhausted ──add_key──→ active (now Maker)
exhausted ──suspend──→ suspended
exhausted ──delete──→ deleted
suspended ──reactivate──→ active (no fresh credits)
suspended ──expires_at_passed──→ deleted

12.3 Suspension mechanics

expires_at set at suspension to N weeks out (default: 21 days, admin-adjustable). Reactivation requires authenticating with the original auth method (passkey or org SSO from the original signup). Reactivation does NOT issue fresh credits — credits at suspension were zero (that's how we got to the choice). Reactivation just restores access to the existing account so the user can add their own key.

12.4 Deletion mechanics

Triggered by either user choice (delete now) or suspension expiry. Sequence:

  1. Warning email. 24 hours before deletion fires (for suspension expiry only — user-initiated deletion is immediate). Email reminds the user that the account will close, with a link to reactivate.
  2. Deletion flow. Write FORAY flows: status transition, balance zeroing for any remaining credits, deletion event.
  3. Anonymization. Person record updated: email replaced with deleted_<hash>@deleted.local, identifying fields nulled, account_status = 'deleted'.
  4. Engagement cleanup. Engagements where this person is sole Operator: anonymized similarly. Engagements with other contributors: this person's contributions retracted (per Loom Protocol, not erased — tombstoned).
  5. Conversation history retention. Conversation turns with this person are retained but the person reference is to the anonymized record.
  6. Render artifacts. Retained per existing render retention policy.
  7. Email hash registry. Updated: last_grant_status = 'deleted', last_status_at = now(). Hash retained for future eligibility checks.

The deletion is FORAY-attested. Subsequent reconciliation can verify the deletion was complete and consistent.

12.5 Re-eligibility after deletion

A future signup attempt with the same email (by hash) returns INELIGIBLE_DELETED from check_email_eligibility. The user can still create an account, but as a Maker with their own key (no trial credits issue). The Authority can override this for specific cases via Operator decision through the engagement pipeline.


13. Deployment reference

Same as v0.6 §11. Per-instance credit schema. Cross-instance consumption reporting. DUNIN7 HQ (Instance A) holds the Authority and the registry.

Public marketing website. Required for the form-initiated grant flow. See §16 (What this does not build).


14. Construction decisions

All v0.6 decisions carry forward except D5 (which is now obsolete — see §7.6). New v0.7 decisions D13–D17.

| # | Decision | Setting | |---|----------|---------| | D1 | Trial credit amount | 10,000 per grant (admin-settable per code) | | D2 | Referral credit amount (conversion credit to referrer) | 10,000 | | D3 | Oracle rate seeding | Per-credit-type, Anthropic-mirrored | | D4 | Grant token format | Opaque 64-char tokens (replaces freeform codes from v0.6) | | D5 | ~~Signup gate~~ | Obsolete — trial path is grant-claim by construction | | D6 | Credit exhaustion behavior | Three-choice: add-key / suspend / delete | | D7 | Integration seam | In-process for alpha | | D8 | Default referrer rate limit | 5 referrals per 30 days, admin-adjustable per person | | D9 | License tier pricing | Maker $0, Pro $29, Team $19/seat (min 3) | | D10 | Credit data location | Engine DB, credit schema | | D11 | Migration management | Single Alembic chain, schema-qualified | | D12 | Consumption recording timing | Fire-and-forget | | D13 | Email hashing scheme | SHA-256 of (a) lowercase trimmed email and (b) aggressively normalized form. Both hashes stored. Match on either = same human. | | D14 | Suspension period | 21 days from suspension to deletion. Admin-adjustable. | | D15 | Deletion warning window | 24 hours before deletion fires. Email reminder with reactivation link. | | D16 | Account state machine | active / exhausted / suspended / deleted with transitions per §12.2 | | D17 | Companion authority for form-initiated grants | Autonomous up to default amount (10,000). Approval card required for amounts above default or for INELIGIBLE_DELETED override. |


15. What this does not build


16. What already exists

Same as v0.6 §14. System key store, three-tier resolution, token capture, person table, FORAY hooks, trigger evaluator, SSE channel.


17. Surface estimate

Phase 47 (alpha substrate):

Credit schema:

Engine modifications:

Estimated: ~80–100 new tests for Phase 47 (up from v0.6's 60–80 because of the grant lifecycle, email registry, and account state additions).

Phase 48+ (Companion intelligence and lifecycle automation):


18. Companion documents


19. Changelog

v0.1 (2026-05-07). Conventional accounting.

v0.2 (2026-05-07). FORAY-native.

v0.3 (2026-05-07). External engagement.

v0.4 (2026-05-07). Full system architecture, nine open decisions.

v0.5 (2026-05-07). Co-located data, two-engagement architecture, trigger-based accounting, per-tier model selection.

v0.6 (2026-05-07). Model-identified credits, suitability-aware Companion, per-credit-type routing.

v0.7 (2026-05-07). Three architectural insights, all decisions extended:

Discovery trajectory: conventional accounting → FORAY-native → external engagement → full architecture → co-located infrastructure engagements → model-identified credits → grant-based delivery with email registry and lifecycle accountability. Seven iterations. The credit became the instruction (v0.6); the credit became something delivered, not requested (v0.7).


DUNIN7 — Done In Seven LLC — Miami, Florida Invitation codes, credit system, and referral architecture — scoping note v0.7 — 2026-05-07