UAI x BharatGrid Integration HLD
The target system for the UAI deployment in BharatGrid — components, flows, stages to reach the target, and where we deliberately stop.
Scope. The target system for the UAI deployment in BharatGrid: the components, the flows between them, the stages to reach the target, and where we deliberately stop. The platform-wide UAI High-Level Design remains the general system shape; this page is the deployment-specific integration design built on top of it.
1. Context
BharatGrid is a chat platform. A user opens OpenWebUI, sends a query, and one of 7 predefined agents answers through CDAC’s LLM infrastructure (vllm / llm-connect). We deploy the UAI control plane around this path: identity (Registry), authorization (Trust Server), and accountability (Audit Ledger), with the Orchestrator handling registration, enforcement, and accountability for every chat.
Every chat must register with UAI and write Audit Ledger receipts, regardless of whether the user selected Auto or a specific agent. Only agent discovery/selection is Auto-specific. The Orchestrator sits on both paths: Auto adds discovery, direct agent skips it, but registration, gate, and audit happen on every query.
2. Target architecture
2.1 Modes
| User selects | Discovery (pick agent) | UAI register (Trust / Registry / token / gate) | Audit Ledger |
|---|---|---|---|
| UAI Auto | Yes – orchestrator selects best agent | Yes | Yes – open on request + complete on response |
| Specific agent (e.g. Varya) | Skipped – user already chose | Yes – still register with UAI | Yes – open on request + complete on response |
Only discovery is skipped for a direct agent. Everything else still goes through the Orchestrator so the call is registered in UAI and accounted in the Audit Ledger.
2.2 System overview
flowchart TB
subgraph control ["Control plane, never sees payloads"]
TS["Trust Server<br/>issues short-lived tokens"] -->|5. checks before issuing| RG["Registry<br/>identity, status, capability"]
AL["Audit Ledger<br/>hash-only receipt chain"]
end
U[User] -->|1| UI["OpenWebUI<br/>auto or direct agent"]
UI -->|2| OR["Orchestrator<br/>seeker + provider gate"]
OR -->|6. route via the token gate| M["CDAC LLM infra<br/>7 agents"]
M -->|7. stream response| OR
OR -->|8| UI
UI -->|9| U
OR -.->|3. agent list, startup + interval| RG
OR -.->|4. get token, per TTL| TS
OR -.->|10. receipt after response, async| ALSequence (same numbers as on the arrows)
| # | Hop | When |
|---|---|---|
| 1 | User → OpenWebUI | Every query |
| 2 | OpenWebUI → Orchestrator | Every query (Auto and direct agent) |
| 3 | Orchestrator → Registry | Background: startup + interval agent-list sync |
| 4 | Orchestrator → Trust Server | Get / refresh short-TTL token (per TTL, per provider) |
| 5 | Trust Server → Registry | Before mint: seeker / status checks |
| 6 | Orchestrator → CDAC LLM | Route via the token gate |
| 7 | CDAC LLM → Orchestrator | Stream chunks back |
| 8–9 | Orchestrator → OpenWebUI → User | Streamed answer |
| 10 | Orchestrator → Audit Ledger | After response: hash-only receipt (request_hash, response_hash, token_id, status), async |
The Registry says who exists, the Trust Server says what they may do, the Orchestrator applies it on the path (observing at launch, enforcing from the September flip), and the Ledger proves what happened – for every chat, not just Auto.
3. Components
3.1 Orchestrator (in the request path, dual mode)
The Orchestrator is on every UAI chat path, not just Auto. Two modes, one control plane:
| Mode | Discovery | Registration + gate | Audit |
|---|---|---|---|
| Auto | Yes – select best agent | Yes | Yes – receipt after response |
| Direct agent | Skipped – user chose | Yes | Yes – receipt after response |
Does:
- Receives each query from OpenWebUI – both Auto and direct agent selections.
- Discovery (Auto only): deterministic match of the query to a capability and selection of the agent. Direct agent: uses the agent specified by the user, skips discovery.
- Seeker side: holds the Orchestrator’s did:webvh identity, requests tokens from the Trust Server (capability plus selected provider), caches one token per provider per TTL window. This happens on every chat, not just Auto.
- Provider gate: verifies the attached token offline. Signature against the Trust Server’s published keys, expiry, capability match, audience matches the selected agent, agent still active per the Registry snapshot. Target behavior: missing or invalid means reject, fail-closed. Launch posture (1.1.1): the gate verifies and logs allow or deny with the token id, and always passes the request through. Enforcement is a config flip in September, not a code change.
- Routes to the selected agent through vllm / llm-connect using CDAC’s existing API auth, streams the response back.
- Builds the receipt after the response completes: Orchestrator computes
request_hashon inlet andresponse_hashon outlet (pkg/jcs+pkg/hashing), then emits a single hash-only receipt (request_hash, response_hash, agent_did, token_id, status) async via the durable local spool. Zero added latency on the user path. Retries from spool. - Fallback: if the selection step fails or times out (Auto only), route to the named default model, still through the token gate once.
Does not:
- Hold business logic. Agents answer, the Orchestrator selects (Auto), enforces, routes, and accounts.
- Persist state beyond config, caches, and the receipt spool. Receipts live in the Ledger.
- Hardcode endpoints or capability IDs. Endpoints resolve from the Registry record, the capability comes from discovery output (Auto) or the user selection (direct).
Internal structure: seeker client and provider handler as separate components behind interfaces, a RegistryClient interface (live implementation: startup plus interval pull, last-good cache, seed fallback for first boot), an LLM connect client, and a ReceiptEmitter. The full build — module layout, ports, flows, config, OpenWebUI edge contract, and the O-P decision series — is in the UAI Orchestrator LLD.
3.2 OpenWebUI (edge UI; multi-connection)
OpenWebUI is a multi-connection chat UI. It does not hardcode GPT/Claude/Gemini/UAI. Each backend is a configured connection. OpenWebUI:
- Asks every connection for its model list
- Merges them into one dropdown
- On send, routes the chat to whichever connection owns the selected model
UAI is one connection. Selecting Auto (auto) is what puts the request on the Orchestrator path. Other models (if configured) go straight to their providers and never enter the UAI control plane.
flowchart TB
subgraph Conns["1. Configured connections"]
direction LR
C1["UAI Orchestrator<br/>URL: …/v1<br/>Key: UAI Orchestrator API key"]
C2["OpenAI<br/>URL: api.openai.com/v1<br/>Key: OpenAI API key"]
C3["Other providers<br/>OpenAI-compatible /v1"]
end
subgraph OWUI["2. OpenWebUI backend"]
direction TB
AGG["Model aggregator<br/>on connect / refresh"]
MAP["Remembers per model:<br/>id → connection urlIdx + API key"]
AGG --> MAP
end
subgraph UI["3. What the user sees"]
DD["Model dropdown<br/>Auto · gpt-4o · claude-… · gemini-…"]
end
C1 -->|"GET /v1/models<br/>← data: [auto]"| AGG
C2 -->|"GET /v1/models<br/>← data: [gpt-4o, …]"| AGG
C3 -->|"GET /v1/models<br/>← data: […]"| AGG
MAP --> DDSame idea, left to right: each connection is asked for models; OpenWebUI merges them into one dropdown.
| User selection | Path | Discovery? | UAI registration + audit? |
|---|---|---|---|
| UAI Auto | OpenWebUI → Orchestrator → select / gate → agent → receipt | Yes | Yes |
| Specific agent (e.g. Varya) | OpenWebUI → Orchestrator → gate → agent → receipt | Skipped | Yes |
| GPT / Claude / Gemini / other | OpenWebUI → that provider directly | N/A | No |
Both Auto and direct agent selections go through the Orchestrator. The difference: Auto triggers discovery (capability match, agent selection), while a direct agent pick skips discovery and uses the user-selected agent. In both cases, the Orchestrator registers the call with the Trust Server, runs the gate, and emits receipts to the Audit Ledger.
Only non-UAI models (GPT, Claude, etc. on separate connections) bypass the Orchestrator entirely.
If BharatGrid only adds the Orchestrator connection, the dropdown may show only Auto (plus any local/Ollama models). Wire-level connection config, API key, and response shapes live in the Orchestrator LLD §8.1.
3.3 Registry
Does:
- Registration and re-registration (POST /agents, ordered checks), three-layer validation, lifecycle (PATCH with optimistic locking, soft delete, status transitions), internal admin listener on an isolated port.
- Proof-of-control: challenge issuance with single-use Redis nonces, verification of the signature over did, nonce, and sha256 of JCS(body), replay protection.
- DID lookup and the agent list endpoint (GET /agents, active only) that feeds the Orchestrator.
- Status is the kill switch: suspend is a registry action, auditable, and traffic stops at the next Orchestrator snapshot without CDAC involvement. Open item for 1.1.1: confirm a minimal admin SetStatus path is in the launch set. Until it is, the operational kill switch is the seeding script, which weakens this claim.
Does not:
- Discovery.
- Accept open registration at launch. Proof-of-control exists (#24, #25) and the seeding flow exercises it. The write path stays closed to everyone except the seeding operator until September.
Stores: Postgres (pgx v5, goose migrations, agents and capabilities tables), Redis (nonce store). Contract: OpenAPI (#223), frozen before dependents build.
3.4 Trust Server
Does:
- Verifies the seeker’s proof-of-control (challenge, sign, verify against the resolved DID).
- Checks the Registry: seeker registered, verified, active. Fail-closed on Registry outage: 503, no cached provider record (#228).
- Policy: loads the corridor risk policy file (one file per corridor, named owner, #226). The capability must exist and carry a declared tier. Unknown: reject. At launch (1.1.1): static config policy instead, one capability scope and a fixed Tier 1 TTL, with the policy-file loader as the named September seam.
- Issuance provider gate (#129): the selected provider’s status, scope support, and tier ceiling are checked before minting. This is why selection precedes issuance. September scope: at launch, issuance checks the seeker via the Registry client (#128), and selection still precedes issuance.
- Mints the token: EdDSA, short TTL, claims in section 6.3. Writes an issuance record. Publishes its DID document and keys for offline verification, with publish-ahead rotation from a KMS-backed ring. At launch: a single Ed25519 signing key from sealed config and a static DID document, with the KMS-backed ring and publish-ahead rotation as the named September seam.
Does not:
- Sit in the request path. It never sees a query.
- Run consent checks or DPoP verification today. Both are seams: the consent module activates with the first Tier 2 capability, DPoP with Tier 3 or the first external seeker.
3.5 Audit Ledger
Does:
- Accepts one hash-only receipt per query (request_hash, response_hash, agent_did, token_id, status), emitted by the Orchestrator after the response completes.
- Accepts receipts on an intake API (parse, sanity, append), appends under the chain_head lock with dense sequence numbers. Append-only enforced in layers: grants and guard triggers at launch, the full three-layer audit (grants, guard triggers, no mutation path) completed in September.
- Serves a demo read endpoint at launch: latest receipts and get by id, internal, behind service auth. The full filtered, participant-scoped, paginated query API is September scope.
- Live at launch (1.1.1): receipt signature verification through the DID resolver (#172, #173), idempotent resubmits (#179), and the fail-fast intake pipeline (#177). September adds: published hash vectors, scheduled verification walks, checkpoints signed and shipped to an external WORM sink outside our trust domain, and the negative-path suites.
Does not:
- Store payloads or personal data, ever. Hashes and metadata only. This is the DPDP position (#208): receipts hold no Data Principal data.
One-witness limitation, written down: the receipt proves what the Orchestrator attests. Model nodes do not countersign. Named upgrade: node countersigning or per-node proxies.
3.6 Shared primitives (pkg)
RFC 8785 canonicalization (jcs), hashing, keys, ed25519x, SSRF-hardened transport with canonical errors (uaierr), the did:webvh resolver (two-phase: hardened log fetch, then entry hash, SCID, Data Integrity proof, and pre-rotation verification; witnesses deferred), the receipt package (build, sign, verify), and shared conformance vectors. pkg purity per LLD-P7: byte-identical, stateless, log-free, environment-free. Error codes across all services follow the SDE Technical Reference vocabulary exactly.
4. End-to-end flows
A. Setup (once per identity)
- Identity created: did:webvh log, keys generated into custody (BG-D5). Genesis-only logs at launch; ceremony, rotations, and witnesses are September scope.
- Registered in the Registry with capability and status. The 7 launch agents seed through the registration API with proof-of-control; open registration switches on in September.
- Proof-of-control: challenge, sign, resolve, verify. Pass: verified. Fail: suspended, fail-closed. The resolver (#62) shipped in July, so seeding runs the real flow and no later conversion pass is needed.
B. Authorize (once per token TTL, per provider)
- The seeker side proves control of its identity to the Trust Server.
- The Trust Server checks Registry status and the policy tier (static config at launch). The provider checks at issuance (#129) join in September.
- Token minted (aud = selected agent DID), issuance recorded. The Trust Server is out of the picture until the next issuance.
C. Request – UAI Auto (every Auto query, filter-based)
The UAI Filter (uai_filter) is an OpenWebUI global pre/post filter. On every chat, the inlet (pre-filter) calls the Orchestrator’s POST /v1/inlet to select, token, and gate before the agent runs. After the agent responds, the outlet (post-filter) calls POST /v1/outlet to build a single hash-only receipt.
- Query enters via OpenWebUI when the user selects Auto (
model=auto). The UAI Filter inlet fires. - Inlet calls
POST /v1/inletwithmessages. Orchestrator computesrequest_hash(pkg/jcs+pkg/hashing), runs discovery: matches the capability, selects the agent from the Registry snapshot. At launch (1.1.1): route-to-default – single seed capability andDEFAULT_MODEL_DIDif active; the selection engine is a September upgrade. - Orchestrator gets or reuses the cached token for that capability plus provider, runs the gate (observe at launch).
- Inlet receives the selected
agent_model_idand Orchestrator-computedrequest_hash, rewritesbody["model"]so OpenWebUI executes the correct agent pipe (not the UAI Auto stub). - OpenWebUI runs the rewritten agent pipe; agent calls its upstream API; streams the response to the user.
- Outlet fires: sends assistant response text/bytes + stashed
token_id/request_hashtoPOST /v1/outlet. Orchestrator computesresponse_hash, builds and signs one hash-only receipt, spools to disk, async emits to Audit Ledger.
sequenceDiagram
participant U as User
participant OW as OpenWebUI
participant F as UAI Filter<br/>(inlet/outlet)
participant OR as Go Orchestrator
participant TS as Trust Server
participant RG as Registry
participant AP as Agent pipe<br/>(e.g. varya.video)
participant API as Agent upstream API
participant AL as Audit Ledger
Note over OR,RG: Background: snapshot sync (startup + ~60s)
OR->>RG: GET /agents (ETag)
RG-->>OR: agents + pipe/model ids
Note over OR: atomic in-memory snapshot
U->>OW: chat model=uai_auto
OW->>F: inlet(body)
F-->>U: status "selecting best model…"
F->>OR: POST /v1/inlet<br/>{mode:auto, model:uai_auto, messages[]}
Note over OR: request_hash = hash(jcs(messages))<br/>snapshot check → select agent<br/>→ seeker token → gate observe
OR->>TS: challenge + proof-of-control
TS->>RG: seeker status
RG-->>TS: active
TS-->>OR: short-TTL token (aud=agent DID)
OR-->>F: {agent_model_id, token_id, request_hash, …}
Note over F: body.model = agent_model_id<br/>(uai_auto stub must NOT run)<br/>stash metadata.uai — no Ledger yet
F-->>OW: return body + metadata.uai
OW->>AP: run rewritten model pipe
AP->>API: agent HTTP (existing CDAC path)
API-->>AP: stream / response
AP-->>OW: chunks to UI
OW-->>U: streamed answer
OW->>F: outlet(body, metadata)
F->>OR: POST /v1/outlet<br/>{token_id, request_hash, response, status}
Note over OR: response_hash = hash(response)<br/>ONE ReceiptDraft sign → spool.jsonl fsync
OR--)AL: async POST /audit/v1/receipts (retries)
F-->>OW: return bodyC’. Request – Direct agent (every direct agent query, filter-based)
Same filter, different mode. The UAI Filter inlet still fires on every chat, but passes mode=direct to the Orchestrator – discovery is skipped.
7’. Query enters via OpenWebUI when the user selects a specific agent (e.g. Varya). Non-UAI models (GPT, Claude, etc.) on separate connections bypass the Orchestrator entirely (see section 3.2).
8’. Inlet calls POST /v1/inlet with mode=direct. Orchestrator skips discovery, uses the agent the user selected, still runs token + gate.
9’–12’. Same as Auto from here: token, gate, agent pipe runs unchanged, outlet calls POST /v1/outlet for the single receipt.
sequenceDiagram
participant U as User
participant OW as OpenWebUI
participant F as UAI Filter<br/>(inlet/outlet)
participant OR as Go Orchestrator
participant TS as Trust Server
participant RG as Registry
participant AP as Agent pipe<br/>(user-selected)
participant API as Agent upstream API
participant AL as Audit Ledger
U->>OW: chat model=varya.video
OW->>F: inlet(body)
Note over F: discovery SKIPPED<br/>keep body.model=varya.video<br/>(no "selecting best model" UI)
F->>OR: POST /v1/inlet<br/>{mode:direct, model:varya.video,<br/>discovery_skipped:true, messages[]}
Note over OR: request_hash = hash(jcs(messages))<br/>snapshot check → NO select<br/>use selected agent → token → gate observe
OR->>TS: challenge + proof-of-control
TS->>RG: seeker status
RG-->>TS: active
TS-->>OR: short-TTL token
OR-->>F: {agent_model_id=varya.video, token_id, request_hash, …}
Note over F: stash metadata.uai — no Ledger yet
F-->>OW: return body + metadata.uai
OW->>AP: run user-selected agent pipe
AP->>API: agent HTTP
API-->>AP: stream / response
AP-->>OW: chunks to UI
OW-->>U: streamed answer<br/>(no Auto discovery UI)
OW->>F: outlet(body, metadata)
F->>OR: POST /v1/outlet<br/>{token_id, request_hash, response, status}
Note over OR: response_hash = hash(response)<br/>ONE ReceiptDraft sign → spool.jsonl fsync
OR--)AL: async POST /audit/v1/receipts (retries)
F-->>OW: return bodyC’’. Filter-based integration — reference sections
The following subsections are the definitive reference for the filter-based integration between OpenWebUI and the Go Orchestrator. The full detail, including request/response shapes, validation caps, and error matrix, lives in the Orchestrator design doc (04-orchestrator.md).
OpenWebUI components (what to deploy)
| Artifact | Function id / name | Type | Must be |
|---|---|---|---|
| UAI Filter | uai_filter | Filter (inlet + outlet) | Active + Global ON |
| UAI Auto | uai_auto | Pipe (stub) | In dropdown; never the final executor if filter works |
| Agent(s) | e.g. varya / varya.video | Pipe(s) | Existing CDAC integrations |
| Seed | functions/seed_pipes.py | Deploy helper | Uploads + attaches filter |
If the user still sees the UAI Auto stub message (“Enable the global UAI Filter…”), inlet did not rewrite model – fix filter Active/Global (and Auto model id valves).
Auto vs direct (same filter)
| User selects | Discovery UI | Orchestrator POST /v1/inlet | Who runs after inlet | Single POST /v1/outlet → AL |
|---|---|---|---|---|
UAI Auto (uai_auto) | Yes | mode=auto → returns agent_model_id | Rewritten agent pipe | Yes (after response) |
| Direct agent | No | mode=direct, discovery_skipped=true | Same agent pipe | Yes (after response) |
Orchestrator HTTP contract (filter ↔ Go sidecar)
Day-1: two Orchestrator calls max – inlet before the agent runs, outlet after. One Ledger-bound receipt, created only on /v1/outlet.
| When | Call | Orchestrator does | Returns / side effect |
|---|---|---|---|
| Inlet | POST /v1/inlet | Parse/cap, compute request_hash, snapshot, select (Auto only), seeker token, gate observe | agent_model_id / pipe id, token_id, request_hash, status – no spool/Ledger |
| Outlet | POST /v1/outlet | Compute response_hash from response body, build one hash-only receipt, sign, spool, async POST /audit/v1/receipts | ack; never blocks user (answer already streamed) |
Auth: Bearer UAI Orchestrator API key (same as edge auth). Payloads may appear on the wire to Orchestrator for hashing/selection; receipts and spool remain hash-only (O-P7). The filter does not implement canonical hashing.
Step-by-step: what happens on each query
- User → OpenWebUI: selects UAI Auto or a specific agent; sends chat.
- OpenWebUI → UAI Filter
inlet: pre-filter runs on every chat (when Global). - Inlet (
POST /v1/inlet→ Orchestrator):- Orchestrator hashes
messages→request_hash(pkg/jcs+pkg/hashing). - Snapshot must be present/fresh.
- Auto: discovery/select → UI “selecting best model…” → pick agent.
- Direct: skip discovery; keep selected agent.
- Seeker token + gate (observe at launch) → registers the call with UAI.
- Orchestrator hashes
- Rewrite (Auto only):
body["model"] = agent_model_idso OpenWebUI will not executeuai_auto. - Metadata: stash
token_id, Orchestratorrequest_hash,agent_model_id,auto_modefor outlet. No Audit Ledger write here. - OpenWebUI chat pipeline: executes the agent pipe; agent calls its upstream API; UI streams to user.
- UAI Filter
outlet: send response text/bytes →POST /v1/outlet→ Orchestrator hashes response, signs one receipt → spool → Audit Ledger (async). User is not blocked.
D. Account (every query, both modes)
- Receipt built after response completes: seeker DID, agent DID, capability, request hash, response hash, token ID, timestamps, status. One receipt per query, containing both hashes (computed by Orchestrator).
- If issuance failed under the observe posture, the receipt records the missing token explicitly. The honest claim: a token id in every receipt where issuance succeeded, missing tokens logged and visible, never silent.
- Async append to the Ledger chain via spool (
POST /audit/v1/receiptsrecord-by-record; checkpoint advances on202). Day-1: if unemitted depth or oldest age crosses soft thresholds (SPOOL_ALERT/ configured age), warn-log for later alerting — do not drop spool lines. Prometheus paging is September.
Failure matrix
| Failure | Behavior | Rationale |
|---|---|---|
| Registry unreachable | Serve from the last-good verified snapshot within max age N, then reject | Availability first while claims are modest, verified-only once identity is live |
| Trust Server unreachable | Cached tokens serve until TTL expiry. At launch, failed issuance is logged and the request still flows under the observe posture, with the missing token recorded in the receipt. From the September flip, new issuance rejects and requests stop once caches expire | Offline verification limits the blast radius to one TTL window |
| Token missing, expired, wrong audience, wrong capability | Launch: log the deny with the SDE error vocabulary and pass through. From the September flip: reject | The observe posture protects live requests while the gate is young; enforcement is a config flip, not a code change |
| Agent suspended | Dropped from the snapshot at the next pull. The provider gate logs the status deny at launch and rejects from the September flip | The double check closes the in-flight token window |
| Selection agent fails or times out (Auto only) | Route to the named default model, token-gated | The chat platform must not block on selection quality |
| Direct agent not in snapshot or inactive | Reject (agent not found / agent suspended) | Direct pick must still be valid in the Registry snapshot |
| LLM infra unreachable | Surface the error to the user, the receipt records the failure with status=upstream_error | Not ours to mask |
| Ledger unreachable | Async spool with retry and alerting, fail-open at launch | Revisit as a named decision at the September enforcement flip, when “every transaction has a receipt” becomes a compliance claim |
5. Identity and key custody
did:webvh (decision D15) for every identity: the 7 agents, the Orchestrator (one identity, roles as declared capabilities), and the UAI services themselves. Gate 1 is closed: did:webvh confirmed for launch, D15 stands unchanged, no exception ADR needed. Launch identities carry genesis-only logs authored by the seeding tooling and verified against the resolver (#62) and the shared vectors (#71); ceremony, rotations, and witnesses stay September scope. Key custody sits with the UAI deployment initially. Custody transfer to CDAC for the agents is maturity step 2, and it is the moment proof-of-control becomes adversarially meaningful.
6. Authorization model
6.1 Tiers
Tier is a property of what the action retrieves, not of who calls or what the user types into a prompt. User-volunteered personal details in a query do not change the tier, and receipts stay hash-only regardless. All 7 current capabilities: Tier 1. The tripwire (BG-D4) is a standing rule in the policy ADR.
6.2 Capabilities
Hierarchical taxonomy, one entry today (bharatgrid.advisory, or as ratified in the taxonomy ADR). The ID appears in exactly one place, the seed taxonomy. Discovery output feeds the token request. Adding per-agent capabilities later: new taxonomy entries, new Registry record values, new policy rows. No code change in the token or verification path.
6.3 Token claims
iss (Trust Server DID), sub (seeker DID), aud (selected agent DID), capability, tier, iat, exp (short TTL, minutes), jti, cnf (empty seam until DPoP). EdDSA signed, verified offline. The aud claim must match the receipt’s agent DID, a mechanical auditor check.
7. Security posture
TLS 1.3 everywhere, no 1.2 fallback. Per-service Postgres with separate credentials. Fail-closed as the default posture, with three named availability exceptions in the failure matrix, each written as a decision, not an accident: the Registry snapshot max age, Ledger fail-open at launch, and the observe posture at the token gate until the September enforcement flip. depguard enforces pkg purity and service boundaries. Rate limiting per IP and per DID on edge writes, tighter on challenge and token endpoints. Input caps on body sizes and array lengths. Admin listeners isolated, never gateway-exposed. No payloads or personal data at rest anywhere in the control plane. Structured logging with hygiene rules: never log signatures, bodies, or tokens.
8. Trust boundary
The control plane (Registry, Trust Server, Audit Ledger) is never in the query path and never sees payloads. The Orchestrator is in the query path by design and handles content, so it runs on the application side inside the BharatGrid environment. Queries flow only between OpenWebUI, the Orchestrator, and the model nodes.
9. Deployment topology and stages
Topology: inside the BharatGrid environment. Initially one VM with docker-compose: 4 services, 3 Postgres instances (Registry, Trust Server, Ledger), Redis (nonces, later the jti guard). TLS and domain per CDAC setup. The Orchestrator holds the only credentials to the model APIs (BG-D11).
| Stage | Ships | Gate to next |
|---|---|---|
| 1.1.1 launch (Aug 15) | All four layers thin and live: seeded Registry (7 agents through the real registration flow with proof-of-control, open registration off), Trust Server issuing short-TTL tokens in observe posture, append-only Ledger with signature verification and idempotent intake, Orchestrator handling both Auto and direct agent chats – Auto with route-to-default selection, direct agent with discovery skipped – token acquisition, observing gate, streaming, and single hash-only receipt per query (request_hash + response_hash) carrying token ids on every query | E2E steel thread green on staging for both modes, deploy repeatable, demo rehearsed |
| September upgrade (1.1.2 / 1.2) | Enforcement flip at the gate (config change), selection engine + golden-set bar, corridor policy file loader, KMS-backed key ring with publish-ahead rotation, provider checks at issuance (#129), webvh ceremony and custody preparation, Ledger checkpoints, verification walks, WORM sink, full query API, conformance and negative-path suites | Every transaction tied to a checkable permission, enforced |
| Maturity | Items as triggered | Named triggers only (section 12) |
10. Observability (system)
Cross-service posture for BharatGrid × UAI. Orchestrator health endpoints: LLD §8.2. Basic logging pattern for debugging (fields, levels, hygiene, how to trace a request): Orchestrator LLD.
What we must be able to see at launch (1.1.1)
| Signal | Where | Why |
|---|---|---|
| Request path health | Orchestrator /healthz, /readyz (snapshot fresh + spool writable; no LLM probe) | Safe deploy / restart without flapping on upstream blips |
| Per-query debug trail | Orchestrator structured logs (see LLD logging pattern) | Trace one chat without opening payloads |
| Receipt progress | Spool depth / oldest age / emit failures (day-1: structured warn logs when soft thresholds cross; September: Prometheus counters + paging); Ledger intake success | “Account” step not silently stuck; lag ≠ drop |
| Control-plane health | Registry / Trust / Ledger service health as deployed | Separate from Orchestrator readiness |
Hygiene (system rule): never log message content, raw JWTs, or signatures on any service. Control plane never sees payloads; the Orchestrator LLD spelling of the log fields is authoritative for implementers.
September: full Prometheus + OTel on the path (request/selection/upstream/mint/spool/emit), alerting on spool depth and gate deny rates once GATE_MODE=enforce.
11. End-to-end verification (steel thread)
System-level proof that BharatGrid chat exercises the UAI path in both modes. This section is the E2E gate.
Launch steel thread – Auto (staging)
OpenWebUI (model=auto)
-> Orchestrator (auth -> select route-to-default -> token -> observe gate)
-> CDAC LLM (or agreed mock upstream until CDAC wire lands)
-> SSE back to user
-> hash-only receipt (request_hash, response_hash, token_id, status) -> spool -> Audit Ledger
Launch steel thread – Direct agent (staging)
OpenWebUI (model=varya.video or agent id)
-> Orchestrator (auth -> discovery SKIPPED -> token -> observe gate)
-> CDAC LLM (user-selected agent)
-> SSE back to user
-> hash-only receipt (request_hash, response_hash, token_id, status) -> spool -> Audit Ledger
Pass criteria (1.1.1)
| Check | Expect |
|---|---|
| Auto UI | Select Auto, send a chat, tokens stream (not one delayed dump) |
| Direct agent UI | Select a specific agent, send a chat, tokens stream |
| Orchestrator (Auto) | Ready with fresh snapshot; gate decision logged; selection method fallback-default at launch |
| Orchestrator (Direct) | Discovery skipped; gate decision still logged; agent resolved from user selection |
| Receipt | Lands in Ledger (or spool then drain) with request_hash + response_hash + agent DID + token_id or explicit empty under observe miss. One receipt per query, present for both modes |
| Bypass | Non-UAI models (GPT, Claude, etc. on separate connections) do not hit Orchestrator |
Depends on: identity ceremony / seed, CDAC OpenWebUI + LLM wire (or mockllm for pre-staging), edge auth contract confirmation. Nightly/CI may run the same path against mockllm before real CDAC credentials.
September E2E adds: enforce-mode rejects, selection-engine golden queries, spool crash/replay drill, latency budget on the full path.
12. Non-goals for BharatGrid
No consent artifacts (no citizen data is accessed for anyone). No registering human officials as Seeker agents. No tokens in front of the chat UI itself. No DPoP until Tier 3 or an external seeker. No witnesses or watchers on the resolver. No VC revocation, no Elasticsearch, no IGM. Crossing any of these needs a named trigger, not enthusiasm.