Two bugs surfaced when bascule-gateway pods first reached Running and
attempted config load (F.4 deployment):
1. Env-var override didn't take effect for any BASCULE_* variable.
config::Environment::with_prefix("BASCULE") without an explicit
prefix_separator strips the literal "BASCULE" with no separator,
so BASCULE_ACCORD_PATH became "_ACCORD_PATH" (leading underscore)
which doesn't match the field "accord_path". Result: every env
override silently fell back to the default in config.rs, and the
pod read /accord/accord.yaml instead of /etc/bascule/accord.yaml
from the configured volume. Adds .prefix_separator("_") to match
QM's pattern in services/quartermaster/src/config.rs:150.
2. Embedded fallback accord YAML had `sampled: []` and a stray
`sampleRate: 1`, but the schema has
`sampled: Option<SampledConfig {events, sample_rate}>` — empty
list mis-parses as struct. Result: when accord file lookup failed,
the .expect("empty accord must parse") panicked, crashing the
bascule-gateway container. Now omitted (Option default None).
Both fixes verified against accord-core's schema in
services/accord-core/src/schema.rs.
Signed-off-by: Tyler J King <tking@guildhouse.dev>
Multi-stage rust:bookworm → debian:bookworm-slim build, modeled on
guildhouse/services/Dockerfile (F.2). Build context is the
substrate-project repo root because bascule-gateway's Cargo.toml has
cross-workspace path deps reaching:
- ../../substrate/crates/governance-types (and substrate-rt
transitively, which inherits edition from substrate's workspace
root — substrate must be COPYed as a whole for the inheritance
chain to load)
- ../../guildhouse/services/{accord-core, accord-opa, qm-core,
guildhouse-proto}
- ../../guildhouse/sdk/{guildhouse-mq, guildhouse-tower} via
transitive deps
Image output: git.guildhouse.dev/tking/bascule-gateway:v0.1.0.
Signed-off-by: Tyler J King <tking@guildhouse.dev>
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>
Read the cluster's operational posture level from the posture-current
ConfigMap at ceremony grant time. Derive ShellClass via
derive_shell_class() and stamp into the granted SessionScope.
- Normal posture (5) -> ShellClass::System
- Any DEFCON escalation -> ShellClass::Application
- Fail-closed: missing ConfigMap -> Lockdown -> Application
- posture_level_at_establishment stored for audit/breach comparison
Signed-off-by: Tyler King <tking@guildhouse.dev>
Signed-off-by: Tyler J King <tking727@gmail.com>