-
Notifications
You must be signed in to change notification settings - Fork 1
bcc_middleware
title: bcc_middleware created: 2026-07-07 updated: 2026-08-04 type: entity tags: [infrastructure, compliance, cryptography, metrics] confidence: high source_files:
- bcc_middleware/app/main.py
- bcc_middleware/app/canonical.py
- bcc_middleware/app/baa.py
- bcc_middleware/app/chain.py
- bcc_middleware/app/merkle.py
- bcc_middleware/app/reputation.py
- bcc_middleware/app/scoring_loop.py
- bcc_middleware/app/config.py
- bcc_middleware/app/nonce_lock.py
- bcc_middleware/app/verification_token.py
- bcc_middleware/policies/bcc.rego
The pre-execution policy gate (FastAPI + OPA). An agent signs a
BCC commitment to what it's about to do and POSTs it to
POST /v1/bcc/intercept; this service decides allow/deny before the agent acts.
It also runs a second, independent responsibility: a periodic background loop
that pushes each agent's oracle-computed AIS on-chain and
raises slashing disputes (see "Reconciled this cycle" below) — the only place
in the monorepo that closes that loop.
- Pipeline
- Hermes runtime gate bridge (2026-08-04)
- Reconciled this cycle (2026-07-14)
- Reconciled 2026-07-11
- Reconciled previous cycle
- Async hot-path + hardening fixes, 2026-07-15
- State
- Resolved gap (found stale during integrity-dashboard/demo work, 2026-07-09)
Schema validation → circuit breaker → signature verification → nonce-replay
check → freshness window → OPA policy (now including a
verification-tier gate) → on-chain BAA
check (if OPA flags requires_baa) → admit to
Merkle batch + best-effort anchor.
Fail-closed vs. best-effort is the one property to get right: OPA and BAA are authorization decisions and fail closed (any inability to positively confirm = deny); anchoring happens after authorization and is best-effort. The circuit breaker only counts violations attributable to the agent — an OPA/RPC outage denies but never trips the breaker (else one outage locks out the whole fleet). The reputation-sync loop below follows the same best-effort posture for score pushes (a stale on-chain score, not a wrongly-trusted one) but the opposite for disputes — see below.
Hermes' shell-hook adapter now has a real per-session context bridge instead of
arriving at pretool_gate.evaluate_tool_intent(...) empty-handed:
-
integrity_telemetrypersists the latest turn rationale / assistant response in~/.claude/xibalba/cache/hermes_session_context/<session_id>.json. -
agent/turn_finalizer.pynow forwardslast_reasoningintopost_llm_callso the plugin can prefer same-turn reasoning when a provider exposed it. -
hermes_gate.pyreads that cache and bridges it intoINTENT_RATIONALE(withAGENT_THOUGHTas a legacy alias) before it calls the shared pretool gate, preserving the single-source-of-truth BCC path. -
tools/code_execution_tool.pynow forwardsHERMES_SESSION_IDinto nested sandbox tool dispatch soexecute_code->terminalkeeps the session identity the BCC gate needs to recover trace/span context.
This is an operational repair, not a semantic completion of the protocol: the live
OPA rule now prefers the signed intent_rationale field, while agent_thought
remains a compatibility alias. The long-term contract should keep the rationale
public-safe and signed, not imply access to private chain-of-thought.
-
Reputation-sync & slashing loop, new.
app/reputation.py+app/scoring_loop.pyadd a background asyncio task (started at FastAPIlifespanstartup,SCORE_SYNC_INTERVAL_SECONDS, default 300s; also triggerable on-demand viaPOST /v1/reputation/sync) that lists every agent the oracle knows about and, per agent: (1) treatsGET /v1/agent/{id}/ais's geometric, tier-cappedaisas authoritative, divides out only its reportedzk_boost, and signs+submits a realReputationRegistry.updateScore(agent, baseScore); it never reconstructs the formula fromcomponents/weights; (2) if the oracle's flagged-telemetry ratio for that agent crossesDISPUTE_FLAGGED_RATIO_THRESHOLDover a lookback window, signs+submits a realSlasher.raiseDispute(agent, amount, reason)lockingDISPUTE_STAKE_BPSof the agent's available stake (subject to a per-agentDISPUTE_COOLDOWN_SECONDS).integrity-oracleitself stays strictly read-only (see its ownchain.rsdocstring) — this is what makesbcc_middlewarethe load-bearing signer for this role rather than a decorative one. ReusesANCHOR_SIGNER_PRIVATE_KEYas theREPUTATION_SIGNER_PRIVATE_KEYfallback, a deliberate tradeoff on today's single-operator testnet deployment where oracle-signer/disputer/anchor-signer are already the same key (seePRODUCTION_GAPS.md§1 and Interface Contract §7a). Automated dispute-raising is safe to run unattended because raising only locks stake — a separate arbiter role and challenge window (seeSlasher.sol's NatSpec) is required to actually resolve/burn anything.
sequenceDiagram
participant Loop as scoring_loop (periodic, 300s)
participant Oracle as integrity-oracle
participant RR as agent's ReputationRegistry
participant Slasher as agent's Slasher
Loop->>Oracle: GET /v1/agents
loop each agent
Loop->>Oracle: GET /v1/agent/{id}/ais
Loop->>RR: updateScore(agent, preBoostBaseScore)
Loop->>Oracle: GET /v1/agent/{id}/telemetry/volume
alt flagged ratio over threshold and cooldown elapsed
Loop->>Slasher: raiseDispute(agent, amount, reason)
Note right of Slasher: only LOCKS stake —<br/>a separate arbiter resolves/burns
end
end
-
Interface-contract §4.2 schema doc caught up to reality.
agent_public_key(required) andcovered_entity_address(optional) have been real, signed, load-bearing fields inapp/schemas.py/app/canonical.pysince the previous cycle's signature-scheme/BAA reconciliation (below) — butdocs/INTERFACE_CONTRACT.md's own §4.2 JSON example never caught up and still showed the original 6-field shape. Fixed in the same pass as this entry; see BCC, which already had the correct shape. -
Two stale artifacts found and fixed while verifying the above:
.env.example'sBAA_CONTRACT_NAME=SmartBAA(must beSmartBAAFactory— the per-pairSmartBAAescrow instances don't implementisBAAActive; the actualapp/config.pydefault was already correct, only the example file was wrong) andapp/canonical.py's module docstring (still described the pubkey/fingerprint binding as an open "INTEGRATION FLAG" guess, now updated to state its actual ✅ RECONCILED status).
-
Verification-tier gate, real for the first time.
input.verification_tier(resolved byapp/chain.py::resolve_verification_tierfrom the oracle'sGET /v1/agent/{id}, fails closed to tier 0 on any lookup failure — see that function's docstring for why this differs fromagent_id_to_address's hard-fail) now feedsbcc.rego's newmin_tier_by_intent_typerule. This closes the gap identity-ceiling.md used to describe as "0% enforced" — it's now enforced for the clinical intent-type set, as defense-in-depth on top of (not a replacement for) the existing allowlist. See that page for why thresholds are capped at 1 until Tier 2/3 verification is real. -
verification_tieris no longer client-asserted.integrity-oracle'sregister_agenthandler previously stored whatever tier value the client sent — a real hole, since nothing stopped a client from self-assertingverification_tier: 3. It now always computesSERVER_VERIFIED_TIER(=1) itself; the client-supplied field is accepted on the wire but ignored. This is what makes the gate above meaningful rather than trivially bypassable.
-
Signature scheme: the commitment carries a signed
agent_public_key(multibase), bound bysha256(pubkey) == did_fingerprintbefore the Ed25519 check — because the DID fingerprint issha256(pubkey), not the raw key. Canonical JSON usesensure_ascii=True, matching the SDK/CLI byte-for-byte. -
BAA check: the real two-arg
SmartBAAFactory.isBAAActive(coveredEntity, businessAssociate); the hospital comes from the commitment's signedcovered_entity_address. -
OPA clinical allowlist: now data-driven — static demo set UNION
data.clinical_allowlist.agents, so a real-DID agent is authorized by a loaded data document, no policy edit.
run_intercept now wraps resolve_verification_tier, check_baa_status,
and _flush_and_anchor in asyncio.to_thread(...) — these were blocking
synchronous calls (httpx.get, web3.py, wait_for_transaction_receipt)
running directly inside an async FastAPI handler, stalling the whole event
loop per request. Making that concurrency real for the first time exposed
two follow-on gaps, both fixed in the same pass:
-
A genuine, reproduced race: two concurrent requests using the same
signer key (
anchor_root's Merkle-anchor tx andreputation.py'spush_score/raise_disputetx) could both read the same starting nonce and submit conflicting transactions. Newapp/nonce_lock.py(signer_lock(address) -> threading.Lock, a process-wide per-address registry) now wraps every signed on-chain call. This one did reproduce empirically — 6/8 real"nonce too low"RPC failures with the lock temporarily removed, 0/8 with it restored (test_nonce_lock.py). -
A code-level race, not empirically reproduced:
MerkleBatcher.add/flushhad no lock of their own, and were only ever single-threaded beforeasyncio.to_threadmade concurrent access possible. Added athreading.Lockaround all mutating/reading methods. Honestly documented as based on a direct code-level trace of the unguarded multi-op sequence, not a captured failure — attempted to force it viasys.setswitchinterval(0.00001)stress testing and it did not reproduce, unlike the nonce race above. -
verification_token.py: replaced an unsigned, publicly-recomputablesha256(...)"verification token" (proved nothing) with an HMAC-SHA256 keyed token (issue_token/verify_token, unforgeable withoutBCC_VERIFICATION_SECRET), exposed via a newPOST /v1/bcc/verify_tokenendpoint. Bounded to_MAX_ISSUED_TOKENS = 50_000with oldest-first eviction to prevent unbounded growth. -
force_flushnow returns each agent's real per-agent Merkleroot(fromAnchorResult.root) instead of the discarded full-batch root. -
scoring_loop.pynow skips a redundantpush_scorecall when an agent's score hasn't changed since the last confirmed submission (_last_pushed_scorecache). -
Test-chain-id fix: 11 tests were failing with a chain-ID mismatch
because the repo-root
.envsetsCHAIN_ID=84532(Base Sepolia) globally, silently overriding what local-anvil tests expect (31337). Fixed at the source:tests/conftest.py'sanvil_chainfixture now setsos.environ["CHAIN_ID"]from the real connected chain's id, so everySettings()constructed in that test session inherits it automatically.
91 pytest + 28 OPA tests (up from 75 pytest — the hardening pass
above). Real coverage: a fail-closed test points at a dead
OPA port; test_baa_health_integration.py deploys the real
Integrity Health contracts on a local anvil and exercises
the real two-arg BAA call; test_reputation.py/test_scoring_loop.py cover the
reputation-sync loop above, including real updateScore/raiseDispute
transactions against MockReputationRegistry.sol/MockSlasher.sol fixtures.
This page previously said app/chain.py::agent_id_to_address derives the
agent's EVM address with a placeholder keccak256(pubkey)[-20:]. Re-read
against current source while building integrity-dashboard/demo (2026-07-09): this
is no longer true — agent_id_to_address now resolves the real
SovereignAgent contract address via resolve_agent_primitives(oracle_url, agent_id) (an oracle lookup), matching what EHRGate.checkAccess/
ComplianceGate actually treat as msg.sender. Not independently re-verified
end-to-end against a live current-schema oracle this session (see
integrity-dashboard's demo section: no such oracle instance was
running), but the placeholder code path itself is confirmed gone from source.
Related: BCC, ComplianceGate, Merkle batching.
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