All connectors registered conditionally based on settings.
CredentialResolver with Entra backend (production) or Stub
backend (dev mode). 15 new tests covering credential resolution,
session lifecycle, orchestrator workflows, and device routing.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Routes operations to Intune, PowerShell, Bascule, or Ansible
based on operation type and target device OS. API-mediated ops
always go to Intune, fleet ops to Ansible, session ops routed
by OS. Single invoke endpoint for all device operations.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Multi-step workflow base class with plan/execute lifecycle and
partial-completion reporting. Ansible connector stubbed —
ansible-runner integration in future sprint. Credentials
resolved per-host at runtime via CredentialResolver, never stored.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Bascule: session-based connector using AC as credential.
Transport stubbed — Shellstream integration in future sprint.
PowerShell: session-based connector using Kerberos credentials
from CredentialResolver. PSRP transport stubbed — pypsrp
integration in future sprint.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Session-based connectors acquire credentials at invocation time
from CredentialResolver, manage transport lifecycle with cleanup
guarantees, and never store credentials.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Entra backend resolves OAuth tokens via MSAL client_credentials
(OBO flow wired in future sprint) and passes ACs through for
Bascule. Kerberos stubbed pending hybrid environment config.
Stub backend for dev/testing without real IdP.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Zero-credential-storage architecture. The broker holds ACs
(authorization). Pluggable CredentialBackend implementations
hold secrets. Transports acquire short-lived, scoped credentials
at invocation time and discard after use.
Credential types: BasculeCredential, KerberosCredential,
OAuthCredential, SSHCertCredential.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Verifies MSAL token acquisition, error handling, and
Authorization header inclusion in Graph API requests.
Signed-off-by: Tyler King <tking@guildhouse.dev>
MCP tools for list_devices, get_device_compliance, sync_device,
remote_lock. All route through governed IntuneConnector
invocation with Chronicle audit.
Signed-off-by: Tyler King <tking@guildhouse.dev>
AC issuance can now require device compliance via Intune.
Configurable per-accord and globally. Disabled by default
for backward compatibility. Emits DEVICE_COMPLIANCE_CHECKED
Chronicle event. Adds device_id, device_compliant, and
compliance_checked_at fields to AuthorizationContext.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Implements ConnectorPlugin for Intune Graph API operations.
Governed invocation: every Intune call requires an active AC
and emits a Chronicle CONNECTOR_INVOKED event.
Operations: list, get, compliance check, sync, lock, retire, wipe.
In-memory compliance cache with configurable TTL.
Conditional registration via intune_enabled setting.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Validates Entra JWTs directly via JWKS verification.
Extracts device_id for compliance gating, MFA status,
roles, and constructs DID from Entra tenant + oid.
Adds device_id field to AuthResult dataclass.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Creates gsap_broker/intune/graph_client.py with MSAL
client_credentials auth and typed Graph API methods.
Entra registrar refactored to consume the shared client.
Signed-off-by: Tyler King <tking@guildhouse.dev>
Merges the standalone llm-principal-broker (1,132 LOC) into fastapi-gsap
as an in-process module. The previous architecture had two FastAPI
processes where the broker called GSAP over HTTP on every delegation
creation; now the lifecycle code uses GSAP's own async DB engine
directly and inserts AuthorizationContextDB rows in the same
transaction context.
New module: gsap_broker/delegations/
models.py Pydantic request/response shapes
storage.py DelegationDB SQLModel sharing the GSAP engine
lifecycle.py DelegationManager — in-process AC issuance via
AuthorizationContextDB.insert (no HTTP self-call)
cleanup.py 30s background task for stale delegations
router.py /delegations/* FastAPI router (4 endpoints)
registrars/
base.py AgentRegistrar Protocol + AgentCredentials
stub.py dev-mode no-op
keycloak.py Keycloak Admin REST API
entra.py Microsoft Entra Agent ID via Graph (lazy import)
factory.py driver selection (auto/stub/keycloak/entra)
Wiring:
app.py mounts the delegations router and starts the cleanup task in
the existing lifespan context manager.
settings.py absorbs the keycloak_admin_*, entra_*, and
agent_registrar fields from the old broker's settings.
pyproject.toml adds an optional `entra` extra for the msal dep.
Behaviour preservation:
- Endpoints kept identical: POST /, POST /{id}/revoke, GET /{id}, GET /
- Chronicle event codes preserved: 0x3001 / 0x3003 / 0x3004
- DelegationScope defaults unchanged (max_ttl_minutes=60, max_commands=500)
- Capability ceiling -> capability_mask conversion documented inline
Smoke test: `python -c "from gsap_broker.app import app"` loads cleanly
with 26 routes including the four /delegations/ endpoints.
The standalone llm-principal-broker repo is archived to
~/projects/archive/llm-principal-broker.
Signed-off-by: Tyler King <tking@guildhouse.dev>
POST /mcp — Streamable HTTP JSON-RPC 2.0 (MCP spec 2024-11-05)
11 governance tools for consortia builders:
request_ac — AC issuance (wraps /governance/authorize/)
validate_ac — AC validation (wraps /governance/authorize/{token}/)
post_cr — CR posting (wraps /governance/complete/)
query_accord — AccordTemplate lookup
request_delegation — proxy to LLM Principal Broker
revoke_delegation — proxy to LLM Principal Broker
get_delegation — proxy to LLM Principal Broker
list_agents — proxy to LLM Principal Broker
get_posture — DEFCON level and restrictions (30s cache)
check_operation — dry-run operation check against posture
session_info — current session context
Tool handlers call existing broker internals — no logic duplication.
Delegation tools proxy to LLM Principal Broker via HTTP.
Every tool call recorded in Chronicle (MCP_TOOL_CALL 0x3020).
Any MCP-compatible agent can discover and use governance operations
through standard protocol — no Capstone, no Django required.
All 7 smoke tests pass (init, list, posture, check_op, session, accord, error).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a trusted caller (Bascule SA) sets on_behalf_of in the authorize
request, the AC principal is the specified operator DID instead of the
SA's JWT identity. Display name extracted from last DID segment.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds session_mode flag to AC lifecycle. When session_mode=true:
- AC transitions to 'active' (not 'consumed') on first CR
- Stays active for subsequent CRs during the session
- 'session_end' outcome transitions AC to 'consumed'
- Non-session ACs behave as before (consumed on first CR)
Schema:
- ACStatus: add ACTIVE enum value
- Outcome: add SESSION_END enum value
- AuthorizeRequest: add session_mode bool field
- AuthorizationContextDB: add session_mode column
- Auto-migration via ALTER TABLE on startup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs in the complete handler:
1. SQLAlchemy greenlet: ORM model attribute access triggers sync
lazy-loads in async context. Fix: raw SQL via text() for all
DB operations in the CR handler.
2. UUID format: SQLite stores UUIDs without hyphens (via SQLModel).
Raw SQL comparisons must strip hyphens: str(uuid).replace("-","")
3. Missing received_at: NOT NULL constraint on completion_receipts.
Raw INSERT was missing the column. Added received_at=now().
Full AC/CR cycle now verified:
AC → 200, principal DID resolved from Keycloak token
CR → 200, receipt ID + Chronicle CID returned
Session → 200, chain of custody queryable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added _extract_token_data() to authorize endpoint. Extracts JWT
from Authorization header and passes it to the Keycloak identity
driver as _token_data. This was the missing link — the driver
needs the token to resolve the principal DID.
Verified on Hetzner:
AC issued for tyler@bxnet.io →
did:web:bxnet.capstone.guildhouse.dev/principal/tyler@bxnet.io
Chronicle event emitted (GSAP_AC_ISSUED)
Known issue: CR endpoint has SQLAlchemy async greenlet bug
(MissingGreenlet on the select+update in complete handler).
AC issuance works. CR needs async session fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reference implementation of GCAP-SPEC-SHELLBOUND-BROKER-0001
in FastAPI. Designed for ISVs and enterprises implementing
the governed shell authorization protocol.
Architecture:
FastAPI + SQLModel + Pydantic + async SQLite
Single container deployment: Dockerfile included
OpenAPI schema at /docs is the machine-readable GSAP contract
Broker Interface (§5):
POST /governance/authorize/ — Issue AC
GET /governance/authorize/{p}/ — Poll elevation
POST /governance/complete/ — Receive CR
GET /governance/session/{id}/ — View chain of custody
POST /governance/elevate/ — JIT elevation
GET /governance/drivers/ — List drivers
Identity Driver Interface (§2.2):
IdentityDriver — abstract base (ISV extension point)
KeycloakDriver — Keycloak implementation
DriverRegistry — driver lookup and registration
Chronicle integration (§1.4):
Optional CloudEvents emission via CHRONICLE_WEBHOOK_URL
Forgejo push event format for receiver compatibility
Models:
Pydantic schemas for AC, CR, Principal, Accord, Operation
SQLModel DB models for persistence
Tests: 6 async tests including full AC→CR cycle
695 lines across 27 files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>