-
Notifications
You must be signed in to change notification settings - Fork 1
integrity userapi
title: integrity-userapi created: 2026-07-09 updated: 2026-07-25 type: entity tags: [infrastructure, identity] confidence: high source_files:
- integrity-userapi/app/main.py
- integrity-userapi/app/db.py
- integrity-userapi/app/deps.py
- integrity-userapi/app/security.py
- integrity-userapi/app/login_limiter.py
- integrity-userapi/app/oracle_client.py
- integrity-userapi/app/schemas.py
- integrity-userapi/app/config.py
- integrity-userapi/migrations/0001_init.sql
- integrity-userapi/migrations/0002_jwt_revocation.sql
- integrity-userapi/migrations/0003_user_wallets.sql
- integrity-userapi/tests/conftest.py
- integrity-dashboard/demo/src/integrity_demo/userapi_bridge.py
- docker-compose.yml
FastAPI + Postgres service owning user-facing account data only —
accounts, developer API keys, which agent DIDs a human user has claimed as
theirs, and demo-run request history. It is the strictly non-chain half of
the backend split (see Interface Contract
§6.10, §13): it never imports web3/alloy-equivalent tooling and never
reads a deployments.*.json file. app/oracle_client.py is the only
place this package talks to another service — a plain httpx HTTP client
fanning out to integrity-oracle, never a direct
chain RPC.
- Endpoints
- Custodial app-wallet (added 2026-07-25)
- The oracle tri-state
- Four gaps closed 2026-07-15 (all four of this package's PRODUCTIONGAPS.md §6 findings)
- Tests and Postgres wiring
- CORS (added 2026-07-09, real gap)
POST /auth/register, POST /auth/login, GET /me — JWT bearer auth
(app/security.py: argon2 password hashing via passlib — deliberately
argon2, not bcrypt, since passlib[bcrypt] 1.7.4 breaks against
bcrypt>=4.1's removed __about__.__version__). POST /api-keys /
GET /api-keys / DELETE /api-keys/{id} — developer API keys are random
high-entropy tokens (uak_<43 b64url chars>), returned once, persisted only
as a sha256 hash (fast deterministic hash is correct here since the input is
already 256 bits of entropy, unlike a human password). GET /me/agents /
POST /me/agents — user_agents stores an ownership pointer only
(user_id, agent_did); every GET fans out live to
integrity-oracle's GET /v1/agent/{id} per owned DID rather than caching
agent state locally. POST /demo/run / GET /demo/runs — records that a
run was requested, starting at status='pending'; this service never
orchestrates a demo or fabricates a 'completed' result (that's
integrity-dashboard's demo/ engine, a separate process).
GET /me/wallet / POST /me/wallet/transfer back a per-user custodial $ITK
ledger — the honest counterpart to the dashboard's self-custodial (MetaMask)
path. migrations/0003_user_wallets.sql adds user_wallets(user_id PK, app_wallet_address UNIQUE, itk_balance_wei NUMERIC(78,0) CHECK >= 0) — a
NUMERIC(78,0) covers the full uint256 range exactly (no f64 loss). GET /me/wallet is get-or-create (first call mints a deterministic app-wallet address
- a dev faucet balance);
POST /me/wallet/transferis a real transactional debit/credit (SELECT … FOR UPDATE), not a mocked number — a genuine custodial account whose balance is an authoritative internal ledger. A production deployment would back the address with a real custodied keypair + on-chain settlement; the ledger semantics are already real. Consumed by the dashboard'sTokenWallet.
oracle_client.fetch_agent never raises and never fabricates data. It
returns AgentLookupResult(live_data, error) in exactly one of three
states: live data (error=None), not found (live_data=None, error="agent not found on oracle" on a 404), or oracle unreachable
(live_data=None, error="oracle unreachable: ..." on any httpx.HTTPError
— connection refused, timeout, DNS failure). A single bad lookup in
GET /me/agents's per-agent loop never crashes the whole response; the
caller always gets an honest state, never a silently-empty
live_data: null standing in for "couldn't check."
-
API keys now actually authenticate requests.
app/deps.py::get_current_user_idaccepts either a JWT bearer token or anX-API-Keyheader carrying a rawuak_...key (sha256-hashed, looked up againstapi_keys.key_hash WHERE revoked_at IS NULL) — the gap this section used to describe ("no code path that uses a raw key at all") is closed. Deliberate exception: minting (POST /api-keys) and revoking (DELETE /api-keys/{id}) a key stay JWT-only (a newget_current_tokendependency, notget_current_user_id) — an API key that could mint further keys would let one leaked long-lived credential outlive its own revocation. -
JWTs are now revocable.
create_access_tokenstamps a per-tokenjti(uuid4);decode_access_tokenreturnsDecodedToken(user_id, jti, expires_at). Newmigrations/0002_jwt_revocation.sqladdsrevoked_tokens(jti PK, user_id, revoked_at, expires_at). NewPOST /auth/logoutinserts the presented token'sjtithere — and, since it already has the transaction open, opportunistically sweeps already-expiredrevoked_tokensrows first, so the table self-prunes without a separate cron job (a revoked token whoseexphas already passed could never be replayed anyway). -
Login is now rate-limited. New
app/login_limiter.py(LoginRateLimiter) mirrorsbcc_middleware/app/circuit_breaker.py's in-memory per-key-counter-plus-timed-lockout shape (same accepted single-process state tradeoff), keyed on the lowercased login email rather than an agent DID, with deliberately looser defaults (failure_threshold=5/lockout=300svs. bcc's3/900s) since a login form has a much higher legitimate-typo rate than a signed agent commitment.POST /auth/login429s (with aRetry-Afterheader) once tripped — even against the correct password. -
demo_runshas a real completion path. NewPATCH /demo/runs/{id}(DemoRunUpdateRequest) lets the owning user transition their run throughrunning→completed/failed, stampingfinished_atonly on a terminal status and storing a realresult_summaryJSONB payload (asyncpg now round-tripsjsonbas plain dicts everywhere via a codec registered inapp/db.py::create_pool, previously unregistered since nothing had ever written non-null JSONB before this). Newintegrity-dashboard/demo/src/integrity_demo/userapi_bridge.pycalls this endpoint from the scenario engine itself —main()reportsrunningat start andcompleted/failed(with a real summary) at the end, entirely opt-in via three env vars (USERAPI_URL/USERAPI_TOKEN/USERAPI_RUN_ID) an operator sets when they want a specificmake demoinvocation tied back to ademo_runsrow created beforehand. Still genuinely out of scope, not fixed: nothing inintegrity-dashboard's dashboard UI creates ademo_runsrow or launches this CLI process —make demoremains an operator-run script against live Base Sepolia using a funder private key, not something the frontend can trigger yet.
51 pytest tests (up from 33 — the 2026-07-15 additions above), plus 6
new tests in integrity-dashboard/demo/tests/test_userapi_bridge.py (against a
real local ThreadingHTTPServer, same pattern as this package's own
_FakeOracleServer — no-op when the three env vars are unset,
bearer-vs-X-API-Key header selection, HTTP/connection errors swallowed
not raised). All against a real Postgres — never sqlite, never a
mocked DB. docker-compose.yml has a dedicated userapi-postgres
service (postgres:16-alpine, integrity/integrity_dev_only, db
integrity_userapi, host port 5435 — its own instance, deliberately
never sharing integrity-oracle's postgres service on 5432 or its ad hoc
5434 e2e-test convention, since two separate Postgres instances per trust
domain is the point of the §6.10 split) and a userapi app service
(integrity-userapi/Dockerfile, uv-based like bcc_middleware's, port
8090). tests/conftest.py auto-creates a dedicated
integrity_userapi_test database on the same server, forces DATABASE_URL
and ORACLE_URL before app.config is ever imported (so a dev/CI shell's
ambient ORACLE_URL — a documented shared env var — can never leak into
the "oracle unreachable" test and make it flaky-green), truncates all
tables between tests, and drives the real FastAPI startup/shutdown lifespan
via asgi-lifespan's LifespanManager so app/db.py's real
run_migrations runs on every test's setup — not a hand-rolled schema
substitute. The GET /me/agents tri-state is tested against a real local
ThreadingHTTPServer standing in for the oracle (real socket, real HTTP,
real JSON parsing through fetch_agent), never a mock of
oracle_client's internals. Unlike integrity-oracle's opt-in/env-gated
e2e test, this suite has no skip path — it fails hard at collection if
userapi-postgres isn't reachable, matching the package's stated
completion gate.
Verified manually (not just via pytest): docker compose build userapi
succeeds; docker compose up -d --no-deps userapi boots against
userapi-postgres over the compose network, GET /health returns
{"status":"online","service":"integrity-userapi"}, and
schema_migrations shows 0001_init.sql applied.
app/main.py had no CORS policy before this pass — a real, hard-blocking
gap for the one browser caller this service exists to serve
(integrity-dashboard's dashboard, a different origin by
construction). Fixed with CORSMiddleware (allow_origins=["*"],
allow_credentials=False — every authenticated call carries a bearer JWT,
never a cookie, so this is safe). Verified: all 51 pytest tests unaffected
(CORS is a browser-enforced concern, invisible server-side); a real
cross-origin call from integrity-dashboard now succeeds
(integrity-dashboard/e2e/auth.spec.ts, against a real uvicorn instance
integrity-dashboard/e2e/global-setup.ts now boots on its own ephemeral
Postgres database, port 8093 for E2E — see
Interface Contract §14).
Related: integrity-oracle (the only service this one talks to), DID, Interface Contract §13-§14, integrity-dashboard (the one real caller of this service's auth endpoints).
Generated from INTEGRITY-LATEST/docs/wiki. Edit the canonical repository files, not this mirror.
- A2A Negotiation Protocol [PLANNED]
- AIS API — Versioned Wire Spec
- Agent Integrity Score (AIS)
- Agent Primitives (Self-Sovereign Identity)
- Behavioral Commitment Chain (BCC)
- ComplianceGate & Integrity Health
- Cross-Chain Reputation Sync [PLANNED]
- Decentralized Identifier (DID)
- Identity Ceiling & Verification Ladder [BUILT]
- Integrity Market (Prediction Markets, Binary Options, A2A Capital Allocation)
- Integrity Protocol Specification
- Local Metrology (Client-Side AIS Signal Derivation)
- Merkle Batching & Anchoring Convention
- Observability & PHI Safety Pipeline
- On-Chain Governance
- Persistent Memory Bridge
- Persistent Memory, Genesis Root & Lineage [PARTIALLY BUILT]
- Smart BAA (On-Chain Business Associate Agreement Escrow)
- Telemetry Ingestion Pipeline
- Testing Strategy
- The Four Foundational Primitives
- Xibalba Agent Operating Model
- ZK-ML Model-Inference Verification [PLANNED]
- Zero-Knowledge Proving Pipeline