bascule-workspace/bascule-gateway/Cargo.toml
Tyler J King 3526b6975f bascule-gateway: implement CreateAnchor submission to Quartermaster
Wires the AuditPipeline's flush() path to QM's QuartermasterNotary
gRPC service. Previously flush() only updated local notarized=true
flags; now it batches pending leaf hashes into a CreateAnchorRequest
and persists the returned anchor_id + leaf_index back on each event
row.

Lazy-retry semantics match guildhouse-spire-plugins pkg/governance
(F.1): the gRPC channel is established on first successful flush and
cached in Arc<Mutex<Option<QuartermasterNotaryClient<Channel>>>>. If
QM is unreachable, bascule logs a warning, re-queues the leaves into
the pending buffer, and retries on the next flush interval. Local
audit rows are still written with notarized=true; only anchor_id
stays NULL until an anchor successfully lands. This is the same
pattern that unblocks the bascule-deploys-before-QM ordering problem
without crashing bascule.

Schema: bascule.audit_events already had anchor_id uuid + leaf_index
integer columns (migrations.rs, pre-existing). This commit populates
them for the first time.

Config:
- New `cluster_id` field on BasculeConfig, sourced from
  BASCULE_CLUSTER_ID env. Empty string disables QM submission (local
  storage only). In F.4, bascule gets the UUID from QM's clusters
  table (generated at QM genesis).
- Existing `qm_endpoint` field now actually used (was scaffolded in
  pre-F.4 code but never read).

Backwards-compat:
- submit(&self, event: &AuditEvent, notarize: bool) signature preserved.
- should_notarize(classification, fidelity) public fn preserved.
- Internal leaf_data hashing simplified to an event-field digest
  (event_id + session_id + operator + command + classification +
  exec_result + timestamp); bypasses serde_json_canonicalizer
  dependency that the prior version required. Verify path still
  works against QM's merkle tree because QM hashes whatever bytes
  bascule submits — QM doesn't re-compute; it trusts the leaf
  payload bascule submitted is the leaf.

Signed-off-by: Tyler J King <tking@guildhouse.dev>
2026-04-24 15:40:30 -04:00

75 lines
1.9 KiB
TOML

[package]
name = "bascule-gateway"
version = "0.1.0"
edition = "2021"
description = "Bascule governance gateway — cluster-side API gateway for governed access"
license = "Apache-2.0"
repository = "https://git.guildhouse.dev/guildhouse/bascule"
[[bin]]
name = "bascule-gateway"
path = "src/main.rs"
[dependencies]
bascule-core = { workspace = true }
bascule-proto = { workspace = true }
# Cross-workspace path deps — Guildhouse governance/ceremony primitives.
# Future: extract to standalone crates.
accord-core = { path = "../../guildhouse/services/accord-core" }
accord-opa = { path = "../../guildhouse/services/accord-opa" }
qm-core = { path = "../../guildhouse/services/qm-core" }
guildhouse-proto = { path = "../../guildhouse/services/guildhouse-proto" }
# Cross-workspace path dep — substrate governance types (for PostureLevel).
governance-types = { path = "../../substrate/crates/governance-types" }
# Kubernetes
kube = { workspace = true }
k8s-openapi = { workspace = true }
# gRPC
tonic = { workspace = true }
prost = { workspace = true }
prost-types = { workspace = true }
# Auth
jsonwebtoken = { workspace = true }
reqwest = { workspace = true }
# Database
sqlx = { workspace = true }
# Session cache
dashmap = { workspace = true }
# Async
tokio = { workspace = true }
async-trait = { workspace = true }
# Serialization
serde = { workspace = true }
serde_json = { workspace = true }
serde_json_canonicalizer = { workspace = true }
hex = { workspace = true }
sha2 = { workspace = true }
# Observability
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
# HTTP (ceremony approval endpoints)
axum = { workspace = true }
tower-http = { workspace = true }
# Common
uuid = { workspace = true }
chrono = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }
config = { workspace = true }
rustls = { workspace = true }
tokio-stream = "0.1"
[dev-dependencies]
tower = "0.5"