04 — Data Architecture

Ownership rule

Every table has exactly one owning service. Others read it through that service's API, never by connecting to its database. Cross-service joins are a design smell here, not a shortcut.

Store Owner Contents
Gateway DB hunter-gateway API keys, usage ledger
Platform DB hunter-platform Users, chats, entitlements [PLANNED], signals + outcomes
Agent DB hunter-agent Rooms, chat history, memory documents (pgvector)
Data plane hunter-scrapers Ingested corpora [PLANNED rebuild]
Object storage hunter-podcast Audio, raw scrape payloads
Base L2 hunter-contracts contracts Locks, stakes, subscriptions, burns — the entitlement source of truth

Gateway schema (hunter-gateway)

apikey
  id            PK
  key_hash      TEXT  UNIQUE INDEX   -- SHA-256 of the plaintext, never the key
  prefix        TEXT                 -- "hk_live_ab12", shown in the console
  name          TEXT
  wallet        TEXT  INDEX          -- owner, lowercase 0x…
  created_at    TIMESTAMPTZ
  revoked_at    TIMESTAMPTZ NULL     -- soft revoke, preserves ledger joins
  retain_prompts BOOL DEFAULT FALSE  -- per-key privacy override

usagerecord                          -- the billing source of truth
  id                PK
  api_key_id        INT INDEX        -- 0 for x402 (no key involved)
  wallet            TEXT INDEX       -- API-key owner or x402 payer
  model             TEXT             -- public model id
  prompt_tokens     INT
  completion_tokens INT
  credits           FLOAT            -- (tokens/1000) × model multiplier
  created_at        TIMESTAMPTZ INDEX

Note what is absent: no prompt column, no completion column. Only counts. That is a schema-level privacy guarantee — there is nowhere for prompt content to be stored even by accident (§08).

Quota is computed as SUM(credits) WHERE wallet = ? AND created_at >= month_start against the tier's monthly allowance. Keeping the ledger append-only means billing disputes are answerable from raw rows.

Platform schema (hunter-platform)

signals
  id             PK
  external_id    TEXT UNIQUE     -- idempotency key from the publisher
  posted_at      TIMESTAMPTZ     -- when the call went public
  asset          TEXT            -- upper-cased symbol
  direction      TEXT            -- bullish | bearish | neutral  (CHECK)
  thesis         TEXT
  source_url     TEXT NULL

signal_outcomes
  id                PK
  signal_id         INT UNIQUE FK → signals(id) ON DELETE CASCADE
  horizon_hours     INT
  price_change_pct  FLOAT NULL
  verdict           TEXT          -- hit | miss | inconclusive  (CHECK)
  notes             TEXT NULL
  scored_at         TIMESTAMPTZ

Design points: - external_id UNIQUE + ON CONFLICT DO NOTHING makes publication idempotent — a retried worker cannot inflate the record. - One outcome per signal (signal_id UNIQUE, upsert on conflict): a call can be re-scored but not double-counted. - Accuracy counts only hit/miss; inconclusive and unresolved signals are reported but excluded from the ratio, so the number cannot be gamed by publishing vague calls.

[PLANNED] entitlements(wallet PK, tier, source, expires_at, updated_at) written by the indexer, read by the gateway.

Agent schema (hunter-agent)

room(id, name UNIQUE)            -- one conversation context
chathistory(id, room_id FK, role, content, timestamp)
usagestats(id, room_id FK, total_tokens)
document(id, hash UNIQUE, text, embedding VECTOR)   -- pgvector knowledge base

document.hash deduplicates on normalised text, so re-ingesting a guide is a no-op. Retrieval is exposed to the agent as the search_knowledge tool rather than stuffed into the prompt — the model decides when it needs the corpus.

Data classification and retention

Class Examples Retention Notes
Never stored Prompts, completions Enforced by schema, not policy
Billing Usage records Indefinite Required for disputes and revenue accounting
Identity Wallet addresses Until account deletion Pseudonymous; no email/KYC collected
Credentials API key hashes Until revoked + audit window Irreversible hash
Public record Signals, outcomes Indefinite, public Deleting these would destroy the point
Curated Knowledge documents Until removed by admin Team-curated, not user data
Ingested Scraped public data TTL by source Provenance columns required

Consistency model

HUNTER · $DRPXBT · drpxbt.xyz edit this page