Post-M6 enhancement: when /dev/substrate-hfl is loaded and the
bascule-core `hfl` feature is enabled, the new
compose_via_hfl_or_local() entry point hands a serialized
SessionClaim to the kernel's attestation::SAT_BUNDLE function and
uses the kernel-composed SatBundle (proto-encoded) in place of the
locally-composed M1 bundle. Kernel composition has access to TPM
state, governance state, and platform-claim producers Bascule can't
reach from userspace.
Without the `hfl` feature: M1 path unchanged.
With the `hfl` feature but no kernel module: graceful fallback to
the M1 local compose path. Per ADR D9, the SAT chain stays alive
regardless of HFL availability.
bascule-core::hfl_sat (NEW, behind --features hfl):
- compose_via_hfl_or_local(inputs) tries the kernel path first.
On any failure (device missing, ioctl error, decode error)
it logs at debug and returns the local M1 compose result.
- try_compose_via_hfl() encodes the SessionClaim with prost,
dispatches via HflClient::dispatch(0x0005, 1, claim_bytes,
[0u8;32], current_epoch), and decodes the result as a
proto SatBundle.
- 2 unit tests cover the device-absent fallback path (+ structure
equivalence with the M1 local compose).
Cargo.toml:
- Workspace deps: hfl-types + substrate-hfl as path deps to the
substrate workspace (cross-workspace, CI mounts both checkouts
side by side).
- bascule-core gains a `hfl` feature gating hfl-types +
substrate-hfl + prost (the last for SessionClaim::encode_to_vec
on the substrate-proto-generated types).
Tested (Docker rust:1.88-bookworm):
cargo build -p bascule-core clean
cargo test -p bascule-core --lib sat 7/7 (M1 regression)
cargo build -p bascule-core --features hfl clean
cargo test -p bascule-core --features hfl --lib 26/26
+2 hfl_sat tests on top of the existing bascule-core suite
Branched off main (post-merge of the M1..M5 stack).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Claude Code <claude@guildhouse.dev>
Replaces the opaque BASCULE_ATTESTATION_HASH (a SHA over a
"pcrN:val;ima:hash;" evidence string) with a real proto-canonical
SatBundle composed from the operator's identity + local platform
attestation, anchored on the L4 SessionClaim.
bascule-core::sat (NEW): pure composer module.
- build_session_claim(SessionInputs) -> SessionClaim builds the
L4 leaf from {principal, auth_method, actor_type,
identity_verified, platform_attested, software_verified,
nonce_seed}, computes posture per SAT-SPEC-0002 §7, and
populates the L1/L2/L3 binding fields with zero-padded
placeholders until upstream producers exist.
- compose_local(SessionClaim) -> ComposedSat assembles the proto
SatBundle via SatBundleBuilder. Hot path stays local per ADR D9
(zero network); QM's gRPC ComposeSat is the warm-path surface.
- 7 unit tests cover layer/actor wiring, posture math at each
evidence level, deterministic nonce, sat_hash uniqueness across
principal changes.
bascule-shell: composes the SAT in main() right before execvp
of the inner shell — that's the OSS equivalent of an "Authenticated
-> ShellActive" transition (the OSS Bascule has no russh state
machine; it's a CLI wrapper). Exports the new env var surface:
BASCULE_SAT_HASH hex of proto sat_hash (canonical)
BASCULE_SESSION_CLAIM_HASH hex of L4 leaf hash
BASCULE_SESSION_ID UUID from SessionClaim
BASCULE_POSTURE_LEVEL SAT-SPEC-0002 §7 posture
BASCULE_ATTESTATION_HASH retained as compat alias (gsh /
dashboard consumers); now points at
the proto sat_hash, not the old
evidence-string SHA.
Cross-workspace path dep: substrate-proto via
../substrate-project/substrate/crates/substrate-proto. CI mounts
~/projects as one volume so the path resolves. Switching to a git
dep is post-MVP.
Note: russh-keys pulls `home` which requires Rust 1.88; CI bumps
the docker image accordingly. No code change.
Tested:
cargo build -p bascule-core -p bascule-shell clean
cargo test -p bascule-core --lib sat 7/7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Claude Code <claude@guildhouse.dev>
New crates:
bascule-dashboard — shared Dioxus component library
SessionTable: live active sessions with auth/backend/TPM status
StatsCards: active count, 24h total, TPM attested %, failed auth
StatusBar: connection health indicator
types.rs: DashboardSession, DashboardStats, HealthResponse
bascule-dashboard-web — WASM web target (Dioxus 0.6 + web features)
Compiles to wasm32-unknown-unknown
Dark-first CSS (light mode via prefers-color-scheme)
Monospace data display, clean stat cards
bascule-core/store.rs — in-memory session store
SessionStore with active sessions + aggregate stats
Updated via SessionHandler hooks
Both dashboard library and web WASM target compile clean.
Server and shell builds unaffected. Zero substrate deps.
Signed-off-by: Tyler King <tking@guildhouse.dev>
New crate: bascule-shell (471 lines, 1.8MB binary)
Login shell that detects identity + platform attestation at startup.
Wraps bash/zsh/fish — operator works normally, identity travels with them.
Identity detection (priority order):
1. Entra via WSL2 interop
2. Azure CLI
3. Kerberos TGT
4. Cached OIDC token
5. System user (fallback)
Platform attestation:
TPM 2.0 PCR values via tpm2_pcrread (PCRs 0,1,2,7,10,14)
IMA measurement log hash + count
Keylime agent state
Entra device compliance (WSL2 only)
Composite SHA-256 hash over all evidence
Shell features:
Banner with identity + attestation summary
BASCULE_* env vars injected into inner shell
--info mode for dry-run display
--json mode for machine-readable output
--exec mode for single-command execution
Configurable via ~/.config/bascule/shell.toml
Tested on Fedora with real TPM 2.0:
6 PCRs successfully read from hardware
All env vars propagated to inner shell
1.8MB binary, 0 substrate deps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New crate: bascule-auth-agent-id
Microsoft Entra Agent ID authentication for AI agents
Validates OAuth tokens against Entra JWKS (60min cache)
Extracts agent metadata: type, blueprint, sponsor, scopes
Detects on-behalf-of (delegated) agents
Token-as-password pattern for SSH auth
Cleanup:
Removed all governance-specific references from comments
SessionHandler trait is the only extension point
Zero substrate/chronicle/gsap dependencies
Config example uses neutral terminology
Config:
[auth.agent_id] section for Entra configuration
tenant_id, audiences, multi_tenant fields
3 crates: bascule-core, bascule-server, bascule-auth-agent-id
938 lines total, 5.6MB binary, 0 substrate deps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>