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 selectsDiscovery (pick agent)UAI register (Trust / Registry / token / gate)Audit Ledger
UAI AutoYes – orchestrator selects best agentYesYes – open on request + complete on response
Specific agent (e.g. Varya)Skipped – user already choseYes – still register with UAIYes – 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| AL

Sequence (same numbers as on the arrows)

#HopWhen
1User → OpenWebUIEvery query
2OpenWebUI → OrchestratorEvery query (Auto and direct agent)
3Orchestrator → RegistryBackground: startup + interval agent-list sync
4Orchestrator → Trust ServerGet / refresh short-TTL token (per TTL, per provider)
5Trust Server → RegistryBefore mint: seeker / status checks
6Orchestrator → CDAC LLMRoute via the token gate
7CDAC LLM → OrchestratorStream chunks back
8–9Orchestrator → OpenWebUI → UserStreamed answer
10Orchestrator → Audit LedgerAfter 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:

ModeDiscoveryRegistration + gateAudit
AutoYes – select best agentYesYes – receipt after response
Direct agentSkipped – user choseYesYes – 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_hash on inlet and response_hash on 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:

  1. Asks every connection for its model list
  2. Merges them into one dropdown
  3. 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 --> DD

Same idea, left to right: each connection is asked for models; OpenWebUI merges them into one dropdown.

User selectionPathDiscovery?UAI registration + audit?
UAI AutoOpenWebUI → Orchestrator → select / gate → agent → receiptYesYes
Specific agent (e.g. Varya)OpenWebUI → Orchestrator → gate → agent → receiptSkippedYes
GPT / Claude / Gemini / otherOpenWebUI → that provider directlyN/ANo

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)

  1. Identity created: did:webvh log, keys generated into custody (BG-D5). Genesis-only logs at launch; ceremony, rotations, and witnesses are September scope.
  2. 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.
  3. 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)

  1. The seeker side proves control of its identity to the Trust Server.
  2. The Trust Server checks Registry status and the policy tier (static config at launch). The provider checks at issuance (#129) join in September.
  3. 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.

  1. Query enters via OpenWebUI when the user selects Auto (model=auto). The UAI Filter inlet fires.
  2. Inlet calls POST /v1/inlet with messages. Orchestrator computes request_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 and DEFAULT_MODEL_DID if active; the selection engine is a September upgrade.
  3. Orchestrator gets or reuses the cached token for that capability plus provider, runs the gate (observe at launch).
  4. Inlet receives the selected agent_model_id and Orchestrator-computed request_hash, rewrites body["model"] so OpenWebUI executes the correct agent pipe (not the UAI Auto stub).
  5. OpenWebUI runs the rewritten agent pipe; agent calls its upstream API; streams the response to the user.
  6. Outlet fires: sends assistant response text/bytes + stashed token_id / request_hash to POST /v1/outlet. Orchestrator computes response_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 body

C’. 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 body

C’’. 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)

ArtifactFunction id / nameTypeMust be
UAI Filteruai_filterFilter (inlet + outlet)Active + Global ON
UAI Autouai_autoPipe (stub)In dropdown; never the final executor if filter works
Agent(s)e.g. varya / varya.videoPipe(s)Existing CDAC integrations
Seedfunctions/seed_pipes.pyDeploy helperUploads + 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 selectsDiscovery UIOrchestrator POST /v1/inletWho runs after inletSingle POST /v1/outlet → AL
UAI Auto (uai_auto)Yesmode=auto → returns agent_model_idRewritten agent pipeYes (after response)
Direct agentNomode=direct, discovery_skipped=trueSame agent pipeYes (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.

WhenCallOrchestrator doesReturns / side effect
InletPOST /v1/inletParse/cap, compute request_hash, snapshot, select (Auto only), seeker token, gate observeagent_model_id / pipe id, token_id, request_hash, status – no spool/Ledger
OutletPOST /v1/outletCompute response_hash from response body, build one hash-only receipt, sign, spool, async POST /audit/v1/receiptsack; 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

  1. User → OpenWebUI: selects UAI Auto or a specific agent; sends chat.
  2. OpenWebUI → UAI Filter inlet: pre-filter runs on every chat (when Global).
  3. Inlet (POST /v1/inlet → Orchestrator):
    • Orchestrator hashes messagesrequest_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.
  4. Rewrite (Auto only): body["model"] = agent_model_id so OpenWebUI will not execute uai_auto.
  5. Metadata: stash token_id, Orchestrator request_hash, agent_model_id, auto_mode for outlet. No Audit Ledger write here.
  6. OpenWebUI chat pipeline: executes the agent pipe; agent calls its upstream API; UI streams to user.
  7. 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)

  1. 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).
  2. 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.
  3. Async append to the Ledger chain via spool (POST /audit/v1/receipts record-by-record; checkpoint advances on 202). 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

FailureBehaviorRationale
Registry unreachableServe from the last-good verified snapshot within max age N, then rejectAvailability first while claims are modest, verified-only once identity is live
Trust Server unreachableCached 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 expireOffline verification limits the blast radius to one TTL window
Token missing, expired, wrong audience, wrong capabilityLaunch: log the deny with the SDE error vocabulary and pass through. From the September flip: rejectThe observe posture protects live requests while the gate is young; enforcement is a config flip, not a code change
Agent suspendedDropped from the snapshot at the next pull. The provider gate logs the status deny at launch and rejects from the September flipThe double check closes the in-flight token window
Selection agent fails or times out (Auto only)Route to the named default model, token-gatedThe chat platform must not block on selection quality
Direct agent not in snapshot or inactiveReject (agent not found / agent suspended)Direct pick must still be valid in the Registry snapshot
LLM infra unreachableSurface the error to the user, the receipt records the failure with status=upstream_errorNot ours to mask
Ledger unreachableAsync spool with retry and alerting, fail-open at launchRevisit 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).

StageShipsGate 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 queryE2E 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 suitesEvery transaction tied to a checkable permission, enforced
MaturityItems as triggeredNamed 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)

SignalWhereWhy
Request path healthOrchestrator /healthz, /readyz (snapshot fresh + spool writable; no LLM probe)Safe deploy / restart without flapping on upstream blips
Per-query debug trailOrchestrator structured logs (see LLD logging pattern)Trace one chat without opening payloads
Receipt progressSpool 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 healthRegistry / Trust / Ledger service health as deployedSeparate 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)

CheckExpect
Auto UISelect Auto, send a chat, tokens stream (not one delayed dump)
Direct agent UISelect 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
ReceiptLands 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
BypassNon-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.