Skip to content

feat(assurance): gateway, audit, /assurance routes, sandbox, MCP resources+prompts (KR-A06..A08) - #93

Merged
elasticdotventures merged 4 commits into
mainfrom
feat/assurance-thread
Oct 7, 2026
Merged

elasticdotventures merged 4 commits into
mainfrom
feat/assurance-thread

Conversation

@elasticdotventures

Copy link
Copy Markdown
Member

Follow-up to #89 (merged with KR-A01..A05). This completes the assurance thread's enforcement side and the MCP surface. Also pins ufo-types to the merge commit of PromptExecution/ufo-types#31 (1846e6f).

What this adds

Req What Evidence
KR-A06 gateway Per-identity grants (tokens stored as SHA-256), every route classified in gateway::ROUTES, deny by default (unclassified or unmatched = admin). Enforced in the server, so MCP and direct HTTP are covered alike. tests/assurance_a06.rs: a read-only identity reads (200) and its commit is refused (403) over direct HTTP and through the real bridge.py started with the same token. The commit tool is listed; visibility is not what protects it.
KR-A08 audit Hash-chained log called from the gateway: decision written before the handler runs, outcome after, one correlation id; fails closed (503) if the log cannot be written. GET /assurance/audit needs audit.read. tests/assurance_a08.rs: records for the permitted and denied calls over both transports with caller, operation, decision, model revision, correlation id; tokens never appear in the log; chain verifies; reading the log without the grant is denied and itself recorded.
KR-A07 sandbox deploy/sandbox/run-sandboxed.sh (bubblewrap): read-only root and baseline, one scratch tmpfs, no network, cleared env. Enforcement stays in the deployment component; kr0ki holds the evidence. tests/assurance_a07.rs: scratch output permitted; writes to the baseline (append, truncate, create, rm, mv, ..) refused and the host file's digest unchanged. The test found /dev writable (bwrap's --dev tmpfs isn't covered by a root remount); fixed and now asserted.
MCP resources/* and prompts/* in the bridge, manifest-driven like tools; resource reads go through the same gateway as the equivalent HTTP request. X-Kr0ki-Transport: mcp recorded in audit (informational; nothing is authorised on it). test_bridge.py (14), and end-to-end in assurance_a06.rs.
routes /assurance/*: requirements, trace, view (json/table/d2/svg), evidence, propose/validate/commit (expected revision), verify, audit, skills. exercised by the cases above

Baseline: implementation paths recorded for CTL-A06..A08; kr0ki-assurance lint now reports 0 dangling links and only satisfied_untested x8 (no evidence recorded yet).

Verification

cargo fmt --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace --no-fail-fast on the merged result: all suites pass except the one noted below, which this PR fixes.

  • The route-consistency guard failed after merging main: /render/rust-behavior (new on main) was unclassified and would have required admin. It parses a body and renders, writes nothing to the model, so it is classified render. That is the guard working as designed.
  • Two assurance_service tests encoded "later controls are still unimplemented", which stopped being true. They now build their own conditions (a temp baseline with a deliberately dangling path and test target).

Needs a human decision / still open

🤖 Generated with Claude Code

https://claude.ai/code/session_01GgXQRGsprSZc2oXGNj9n8X

elasticdotventures and others added 3 commits October 5, 2026 09:02
…urces+prompts (KR-A06..A08)

- gateway: per-identity grants (tokens stored as SHA-256), every route classified in
  gateway::ROUTES (a test fails if a route is added unclassified or a row names no route),
  deny by default, decision audited BEFORE the handler runs, fail closed if the audit
  log cannot be written. Enforced in the server, so MCP and direct HTTP are covered alike.
- audit: hash-chained log called from the gateway; GET /assurance/audit (audit.read).
- /assurance/* routes over AssuranceService; propose/validate/commit with expected revision.
- KR-A07: deploy/sandbox/run-sandboxed.sh (bubblewrap): read-only root and baseline, one
  scratch tmpfs, no network, cleared env; /dev closed after the test found it writable.
- bridge: resources/* and prompts/* (manifest-driven), X-Kr0ki-Transport: mcp on MCP calls.
- acceptance cases VC-A06, VC-A07, VC-A08 incl. the real Python bridge as a subprocess.
- baseline: implementation paths for CTL-A06..A08; AGENTS.md env table.

cargo fmt/clippy -D warnings clean; cargo test --workspace --no-fail-fast: 44 suites ok.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgXQRGsprSZc2oXGNj9n8X
…ehavior in the gateway

The route-consistency guard failed after merging main: /render/rust-behavior (new on main)
was not in gateway::ROUTES, so it would have required admin. It parses a request body and
renders; it writes nothing to the model, so it is a render operation.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgXQRGsprSZc2oXGNj9n8X
@elasticdotventures

Copy link
Copy Markdown
Member Author

Evidence recorded on the clean committed tree

just assurance-verify at implementation revision abb46f1 (clean tree), model revision sha256:0213bc9c...a67d4b. Then just assurance-view:

requirement satisfied by (assertion) verification (evidence) state
KR-A01 ModelService 1 fresh pass (9 tests) verified
KR-A02 ModelService 1 fresh pass (6) verified
KR-A03 ModelService 1 fresh pass (9) verified
KR-A04 VerificationRunner 1 fresh pass (3) verified
KR-A05 ModelService 1 fresh pass (2) verified
KR-A06 ToolGateway 1 fresh pass (21) verified
KR-A07 ExecutionSandbox 1 fresh pass (8) verified
KR-A08 AuditRecorder 1 fresh pass (18) verified

All 8 artifacts verify intact against their recorded digests; kr0ki-assurance lint: 0 defects, 0 dangling links, ReqIF round trip lossless (36 nodes / 147 attributes / 48 relations).

Read this before relying on it

  • This evidence is revision-bound and goes stale on merge. It binds to abb46f1; merging creates a new commit, so on main these will show stale until just assurance-verify is run again. That is the system working, not a bug. The store (.kr0ki-evidence/) is local and gitignored, so it is not part of this PR.
  • Test counts for VC-A06 and VC-A08 include the server's own unit tests, which are compiled into those test crates when they include app.rs; the acceptance tests themselves are 7 (A06) and 4 (A08).
  • Live checks are separate and were not part of this run: KR-A03's render through a real Kroki backend and KR-A05 against the real OMG pilot server are #[ignore] tests, run and reported on feat(assurance): versioned assurance thread (KR-A01..A05 done; A06-A08 in progress) #89. This PR adds no new live check; the sandbox (KR-A07) is exercised for real (bubblewrap), the MCP path for real (the actual bridge.py).
  • "Verified" means: the declared acceptance case passed at these revisions. It does not mean the requirement set is complete or the statements are right.

@elasticdotventures
elasticdotventures merged commit 0d10929 into main Oct 7, 2026
3 checks passed
elasticdotventures added a commit that referenced this pull request Oct 7, 2026
…#94)

* feat(playbook): Assurance view (requirements, drawer, diagram, audit) + session-only token + opt-in public UI

Playb00k Assurance tab over the /assurance/* routes:
- requirement table with the five assurance states shown as label + symbol (never colour alone),
  state chips with counts, server-side filters (state/status/owner/gap), source-kind badges;
- detail drawer with the whole thread (obligations, element, control + implementation path,
  case, revision-bound evidence with which revision moved, links resolved/dangling, gaps);
- run a case from the page: explicit confirm step, runs at the revision shown, refusals explained;
- diagram panel (authenticated blob, fit/2x/3x zoom in a scrolling frame, open full size);
- audit panel (hash-chain status, decisions, denials).
Setup gains an optional access token kept in sessionStorage only. lib/assurance.js is the client.

Server: KR0KI_PUBLIC_UI (default off) serves only the static /playbook GET routes without a token,
because a browser navigation cannot send Authorization; data routes still require one. Tested
both ways (default behind the gateway; opt-in public; data/POST/lookalike paths still 401).

Verified in a real browser against a real server (gateway on, real baseline and evidence):
401 -> Setup token -> list, drawer, diagram, audit, and a VC-A02 run flipping the row from
stale to verified. Fixtures are the server's real JSON. Playb00k tests 206; cargo workspace
50 suites; fmt and clippy -D warnings clean.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgXQRGsprSZc2oXGNj9n8X

* test(playbook): scrub a local path from a captured fixture

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgXQRGsprSZc2oXGNj9n8X

* feat: SysMD constraint-solver sidecar (container, soundly-parsing client, /sysmd/solve, MCP solve_constraints)

SysMD runs headless in a hardened container (read-only root, tmpfs store, 1 GiB, cap-drop ALL, loopback). kr0ki talks REST only.
- sysmd_client: project+session per solve, deleted afterwards; parses SysMD's display strings into closed intervals widened outward for
  the Representer's 5-decimal rounding and its dropped-upper-bound / literal-0 cases (verified live), unbounded/empty/NaN kept distinct,
  SI-base compound units mapped by dimension, standard library and bookkeeping variables filtered
- POST /sysmd/solve (gateway: model.read), MCP solve_constraints (33 tools), KR0KI_SYSMD_URL, sysmd health check
- just sysmd-image|sysmd-up|sysmd-down|test-live-sysmd, systemd unit, scripts/build-sysmd.sh (fork branch kr0ki/sidecar)
- docs/SYSMD-SIDECAR.md

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgXQRGsprSZc2oXGNj9n8X

* fix(sysmd): store on a uid-owned named volume instead of mode=1777 tmpfs; document revision-bound evidence policy

Evidence is re-verified on minor releases, not patch bumps.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgXQRGsprSZc2oXGNj9n8X

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant