Skip to content

Commit 407f857

Browse files
hyperpolymathclaude
andcommitted
chore(close-out): session 4 documentation + 6a2 sweep
- ECOSYSTEM.a2ml: CF Workers as production, Fly.io as test-only, V-lang ref (rgtv_client.v) removed, vault-worker integration points added - META.a2ml: 5 ADRs recorded (CF over Fly, DO over KV, axum/tokio, rgtv CLI rewrite, Bearer auth) - PLAYBOOK.a2ml: full operational runbook (CF deploy, Fly spot-test, local dev, CLI, incident response, release process) - NEUROSYM.a2ml: report-format logtalk→json (Logtalk removed 2026-04-12) - STATE.a2ml: vault-worker completion 85→100%, DO migration blocker resolved, session 3+4 history entries added - EXPLAINME.adoc: replaced template stub with real architecture doc (grant lifecycle diagram, technology rationale, one-use guarantee) - .gitignore: add per-crate target/ dirs, vault-worker/build/, .wrangler/ - Justfile: GRANTS comment corrected (DO not KV) - contractiles: SPDX typo PLMP→PMPL, rewritten to RGTV-specific checks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e489eb0 commit 407f857

11 files changed

Lines changed: 315 additions & 112 deletions

File tree

.gitignore

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,19 @@ Thumbs.db
1010
.idea/
1111
.vscode/
1212

13-
# Build
13+
# Rust — per-crate target directories (root /target/ is root-anchored, won't match subdirs)
1414
/target/
15+
vault-broker/target/
16+
vault-core/target/
17+
svalinn-cli/target/
18+
vault-worker/target/
19+
20+
# Cloudflare Workers build artefacts
21+
vault-worker/build/
22+
.wrangler/
23+
24+
# Build
1525
/_build/
16-
/build/
1726
/dist/
1827
/out/
1928

@@ -22,9 +31,6 @@ Thumbs.db
2231
/vendor/
2332
/deps/
2433

25-
# Rust
26-
# Cargo.lock # Keep for binaries
27-
2834
# Secrets
2935
.env
3036
.env.*

.machine_readable/6a2/ECOSYSTEM.a2ml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# ECOSYSTEM.a2ml — Reasonably Good Token Vault ecosystem position
55

66
[metadata]
7-
version = "0.2.0"
8-
last-updated = "2026-04-11"
7+
version = "0.3.0"
8+
last-updated = "2026-04-12"
99

1010
[project]
1111
name = "Reasonably Good Token Vault (RGTV)"
@@ -22,10 +22,10 @@ category = "zero-trust"
2222

2323
[related-projects]
2424
projects = [
25-
{ name = "nesy-solver-api (proven-servers)", relationship = "consumer", notes = "First wired consumer. Uses rgtv_client.v to fetch NESY_INGEST_TOKEN via grant→redeem. Deployed as rgtv-nesy on Fly.io." },
25+
{ name = "nesy-solver-api (proven-servers)", relationship = "consumer", notes = "First wired consumer. Fetches NESY_INGEST_TOKEN via grant→redeem protocol. Deployed as rgtv-nesy on Fly.io (spot-test only; production is CF Workers)." },
2626
{ name = "svalinn", relationship = "sibling-security", notes = "Svalinn is a Deno container gateway; RGTV is a Rust credential broker. Different concerns, sometimes confused. Do NOT merge." },
2727
{ name = "rokur", relationship = "sibling-security", notes = "Rokur is Svalinn's internal secrets store (Stapeln GUI). RGTV is a standalone broker. Different projects." },
28-
{ name = "vault-core", relationship = "internal-component", notes = "Existing crypto library crate in this monorepo. vault-broker builds on it." },
28+
{ name = "vault-core", relationship = "internal-component", notes = "Crypto library crate in this monorepo. vault-broker builds on it for Zeroizing<String> storage." },
2929
{ name = "hypatia", relationship = "planned-consumer", notes = "CI scanning agent should use RGTV to fetch scan credentials rather than env vars." },
3030
{ name = "gitbot-fleet", relationship = "planned-consumer", notes = "Bot agents should fetch GitHub tokens via RGTV grant→redeem." },
3131
{ name = "007", relationship = "planned-consumer", notes = "Private language toolchain — agents must not see raw API tokens." },
@@ -35,12 +35,23 @@ projects = [
3535
[integration-points]
3636
points = [
3737
{ system = "nesy-solver-api", direction = "inbound", protocol = "HTTP POST /v1/grants + /v1/grants/:id/redeem, Bearer auth" },
38-
{ system = "Fly.io private 6PN", direction = "inbound", protocol = "flycast DNS at rgtv-nesy.flycast:9100 (not .internal — auto-wake required)" },
39-
{ system = "vault-core", direction = "internal", protocol = "Rust crate dependency — Zeroizing<String> storage" },
38+
{ system = "Cloudflare Workers (production)", direction = "inbound", protocol = "HTTPS on rgtv-vault.workers.dev (or custom domain); same HTTP API as vault-broker" },
39+
{ system = "Fly.io private 6PN (spot-test only)", direction = "inbound", protocol = "flycast DNS at rgtv-nesy.flycast:9100 (not .internal — auto-wake required)" },
40+
{ system = "vault-core", direction = "internal", protocol = "Rust crate dependency — Zeroizing<String> storage (vault-broker only; vault-worker reimplements inline)" },
41+
{ system = "Cloudflare CREDENTIALS KV", direction = "internal", protocol = "hint → plaintext credential value; operator-managed; vault-worker only" },
42+
{ system = "Cloudflare GRANTS Durable Objects", direction = "internal", protocol = "one GrantObject DO instance per outstanding grant; atomic one-use enforcement; vault-worker only" },
4043
]
4144

4245
[deployment]
43-
platform = "Fly.io"
46+
# Production: Cloudflare Workers (vault-worker — Rust/WASM via workers-rs 0.8)
47+
platform-production = "Cloudflare Workers"
48+
worker-name = "rgtv-vault"
49+
worker-entry = "vault-worker/build/worker/shim.mjs"
50+
deploy-command = "just worker-deploy"
51+
grant-atomicity = "Durable Objects (single-threaded per instance; read+delete is atomic)"
52+
53+
# Spot-test / internal: Fly.io (vault-broker — axum HTTP server)
54+
platform-spot-test = "Fly.io"
4455
app-name = "rgtv-nesy"
4556
region = "lhr"
4657
url-scheme = "http://rgtv-nesy.flycast:9100 (private 6PN only)"

.machine_readable/6a2/META.a2ml

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,53 @@
33
#
44
# META.a2ml — Reasonably Good Token Vault meta-level information
55
[metadata]
6-
version = "0.1.0"
7-
last-updated = "2026-02-08"
6+
version = "0.2.0"
7+
last-updated = "2026-04-12"
88

99
[project-info]
1010
license = "PMPL-1.0-or-later"
1111
author = "Jonathan D.A. Jewell (hyperpolymath)"
1212

1313
[architecture-decisions]
1414
decisions = [
15-
# No ADRs recorded
15+
{ id = "ADR-001", title = "Cloudflare Workers as production target (not Fly.io)", status = "accepted", date = "2026-04-12",
16+
context = "RGTV needs a production deployment reachable by external LLM agents without VPN or private network access.",
17+
decision = "Cloudflare Workers (vault-worker, Rust/WASM via workers-rs 0.8) is the production deployment target.",
18+
rationale = "CF Workers: globally distributed, no cold starts, native DO support, zero-infra ops. Fly.io (vault-broker, axum) retained for internal flycast spot-testing only.",
19+
consequences = "vault-worker must remain API-compatible with vault-broker. Both implement identical HTTP routes." },
20+
21+
{ id = "ADR-002", title = "Durable Objects for grant state (not KV)", status = "accepted", date = "2026-04-12",
22+
context = "Cloudflare KV has no atomic read+delete operation. Two simultaneous redeem requests for the same grant could both succeed.",
23+
decision = "Each outstanding grant is stored in its own GrantObject Durable Object instance, keyed by grant_id UUID.",
24+
rationale = "DO instances are single-threaded: the Workers runtime serialises concurrent fetches to the same instance. The check-and-delete in the POST handler is therefore a true atomic operation.",
25+
consequences = "GRANTS KV namespace removed from wrangler.toml. Durable Objects migrations block required. Each grant creates one DO instance." },
26+
27+
{ id = "ADR-003", title = "axum + tokio for vault-broker (local/test server)", status = "accepted", date = "2026-04-11",
28+
context = "vault-broker serves as the spot-test target on Fly.io and for local dev with `just broker-dev`.",
29+
decision = "axum 0.7 on tokio, IPv6 dual-stack, Zeroizing<String> for all credential values.",
30+
rationale = "Idiomatic async Rust for HTTP; Zeroizing scrubs heap on drop; axum router matches vault-worker's route surface identically so the rgtv CLI works against both.",
31+
consequences = "vault-broker holds credentials in-process (Arc<Mutex<HashMap>>); not suitable for multi-node production — CF Workers is." },
32+
33+
{ id = "ADR-004", title = "rgtv CLI (formerly svalinn-cli) wired to HTTP API", status = "accepted", date = "2026-04-12",
34+
context = "The CLI binary was named svalinn_cli and was a skeleton not wired to vault-broker.",
35+
decision = "Renamed to rgtv binary. Full rewrite using ureq 2 (sync HTTP) + Zeroizing<String>. Works against both vault-broker and vault-worker.",
36+
rationale = "Single synchronous binary; no async runtime needed for CLI; ureq is minimal.",
37+
consequences = "svalinn-cli crate name kept for Cargo workspace; only the binary name changed." },
38+
39+
{ id = "ADR-005", title = "Bearer token auth (RGTV_AGENT_TOKEN) as sole auth mechanism", status = "accepted", date = "2026-04-11",
40+
context = "LLM agents need a simple, stateless auth mechanism compatible with both vault-broker and vault-worker.",
41+
decision = "Single shared secret (RGTV_AGENT_TOKEN) passed as Authorization: Bearer header. Worker Secret on CF side; env var on Fly.io side.",
42+
rationale = "Simple, stateless, zero-dependency. Rate limiting per agent token is a future concern (see blockers in STATE.a2ml).",
43+
consequences = "All tokens share the same privilege level. Per-agent tokens are a future enhancement." },
1644
]
1745

1846
[development-practices]
1947
versioning = "SemVer"
2048
documentation = "AsciiDoc"
2149
build-tool = "just"
50+
rust-edition = "2021"
51+
wasm-target = "wasm32-unknown-unknown"
52+
worker-build = "worker-build (cargo install worker-build)"
2253

2354
[maintenance-axes]
2455
scoping-first = true

.machine_readable/6a2/NEUROSYM.a2ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ last-updated = "2026-04-11"
99
[hypatia-config]
1010
scan-enabled = true
1111
scan-depth = "standard" # quick | standard | deep
12-
report-format = "logtalk"
12+
report-format = "json" # Logtalk removed from Hypatia 2026-04-12; JSON AssailReport consumed by Elixir rules
1313

1414
[symbolic-rules]
1515
# Custom symbolic rules for this project
Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,79 @@
11
# SPDX-License-Identifier: PMPL-1.0-or-later
22
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
#
4-
# PLAYBOOK.a2ml — Operational playbook
4+
# PLAYBOOK.a2ml — Operational runbook for RGTV
55
[metadata]
6-
version = "0.1.0"
7-
last-updated = "2026-04-11"
6+
version = "0.2.0"
7+
last-updated = "2026-04-12"
88

9-
[deployment]
10-
# method = "gitops" # gitops | manual | ci-triggered
11-
# target = "container" # container | binary | library | wasm
9+
# ── Production: Cloudflare Workers ───────────────────────────────────────────
10+
11+
[deployment.cloudflare-workers]
12+
worker-name = "rgtv-vault"
13+
crate = "vault-worker"
14+
one-time-setup = [
15+
"wrangler kv:namespace create CREDENTIALS → paste id + preview_id into vault-worker/wrangler.toml",
16+
"wrangler secret put RGTV_AGENT_TOKEN",
17+
"Populate credentials: wrangler kv:key put --namespace-id=<ID> HINT value",
18+
"just worker-deploy (DO migration runs automatically on first deploy)",
19+
]
20+
redeploy = "just worker-deploy"
21+
logs = "just worker-logs"
22+
creds-list = "just worker-creds-list"
23+
creds-put = "just worker-cred-put HINT value"
24+
25+
[deployment.fly-io-spot-test]
26+
app-name = "rgtv-nesy"
27+
purpose = "Internal flycast spot-testing via nesy-solver-api only. NOT production."
28+
deploy = "NESY_INGEST_TOKEN=<tok> ./deploy-fly.sh rgtv"
29+
token-persistence = "~/.config/nesy-solver/rgtv-agent-token"
30+
url = "http://rgtv-nesy.flycast:9100 (private 6PN; not .internal)"
31+
32+
[deployment.local-dev]
33+
start = "just broker-dev (requires RGTV_AGENT_TOKEN + RGTV_CRED_<HINT>=<value>)"
34+
daemon-start = "just broker-start"
35+
daemon-stop = "just broker-stop"
36+
daemon-status = "just broker-status"
37+
logs = "just broker-logs"
38+
39+
# ── CLI (rgtv) ────────────────────────────────────────────────────────────────
40+
41+
[cli]
42+
binary = "rgtv"
43+
build = "just build-cli"
44+
commands = [
45+
"rgtv get <hint> --env VAR --exec 'cmd' — grant→redeem→exec with credential injected",
46+
"rgtv list — list registered hint names",
47+
"rgtv status — /health probe",
48+
"rgtv verify — functional round-trip smoke test",
49+
"rgtv daemon start|stop|status — manage vault-broker daemon",
50+
]
51+
pid-file = "~/.local/state/rgtv/vault-broker.pid"
52+
log-file = "~/.local/state/rgtv/vault-broker.log"
53+
54+
# ── Incident response ─────────────────────────────────────────────────────────
1255

1356
[incident-response]
14-
# 1. Check .machine_readable/STATE.a2ml for current status
15-
# 2. Review recent commits and CI results
16-
# 3. Run `just validate` to check compliance
17-
# 4. Run `just security` to audit for vulnerabilities
57+
steps = [
58+
"1. rgtv status — check broker reachable",
59+
"2. rgtv verify — functional round-trip (grant→redeem)",
60+
"3. Check STATE.a2ml for known blockers",
61+
"4. Check recent commits and CI results",
62+
"5. For CF Workers: just worker-logs",
63+
"6. For Fly.io: fly logs -a rgtv-nesy",
64+
]
1865

19-
[release-process]
20-
# 1. Update version in STATE.a2ml, META.a2ml
21-
# 2. Run `just release-preflight` (validate + quality + security + maint-hard-pass)
22-
# 3. Tag and push
66+
grant-expired = "Redeem returns 410 — grant was valid but expired (30s TTL default). Request a new grant."
67+
grant-not-found = "Redeem returns 404 — grant already redeemed or never existed. One-use guarantee enforced by DO."
68+
unauthorized = "401 — check RGTV_AGENT_TOKEN matches worker secret."
2369

24-
[maintenance-operations]
25-
# Baseline audit: just maint-audit
26-
# Hard release gate: just maint-hard-pass
70+
# ── Release process ───────────────────────────────────────────────────────────
71+
72+
[release-process]
73+
steps = [
74+
"1. Update version in STATE.a2ml, META.a2ml, Cargo.toml files",
75+
"2. Run: just check (clippy + fmt for broker + cli)",
76+
"3. Run: just build-worker (WASM compile)",
77+
"4. Run: just worker-deploy",
78+
"5. Tag and push",
79+
]

.machine_readable/6a2/STATE.a2ml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[metadata]
77
project = "reasonably-good-token-vault"
88
version = "0.2.0"
9-
last-updated = "2026-04-12 (session 2)"
9+
last-updated = "2026-04-12 (session 4)"
1010
status = "active"
1111

1212
[project-context]
@@ -15,7 +15,7 @@ purpose = """Zero-trust LLM credential broker. Issues opaque one-use grants to
1515
authenticated agents; agents redeem grants for raw credential values. Raw values
1616
never appear in agent context, prompts, or logs. Monorepo containing vault-core
1717
(crypto library), vault-broker (axum HTTP server), ada-cli, and FFI bindings."""
18-
completion-percentage = 62
18+
completion-percentage = 68
1919

2020
[position]
2121
phase = "implementation"
@@ -33,7 +33,7 @@ milestones = [
3333
{ name = "vault-broker GET /v1/credentials endpoint (list hints, no values)", completion = 100 },
3434
{ name = "rgtv CLI wired to vault-broker HTTP API (get/list/status/verify/daemon)", completion = 100 },
3535
{ name = "rgtv daemon start/stop/status (PID-file-managed vault-broker process)", completion = 100 },
36-
{ name = "vault-worker: Cloudflare Workers production target (Rust/WASM, workers-rs 0.8)", completion = 85 },
36+
{ name = "vault-worker: Cloudflare Workers production target (Rust/WASM, workers-rs 0.8)", completion = 100 },
3737
{ name = "Honeypot endpoint (canary for credential sniffing)", completion = 20 },
3838
{ name = "RGTV Idris2 ABI formal proofs (grant lifecycle invariants)", completion = 0 },
3939
{ name = "Zig FFI layer (C-compatible vault_grant / vault_redeem)", completion = 0 },
@@ -45,7 +45,7 @@ milestones = [
4545

4646
[blockers-and-issues]
4747
issues = [
48-
"vault-worker: KV grant store is not atomic (no read+delete CAS in CF KV) — production needs Durable Objects",
48+
# RESOLVED 2026-04-12: vault-worker grant store migrated from KV to Durable Objects (atomic one-use enforced by DO single-thread runtime)
4949
"vault-worker: wrangler.toml has placeholder KV namespace IDs — must run wrangler kv:namespace create before deploy",
5050
"vault-worker: credential values stored plaintext in KV — TODO: encrypt with RGTV_MASTER_KEY Worker Secret",
5151
"vault-broker (Fly.io) not yet deployed to production — only spot-test role, CF Workers is production target",
@@ -58,7 +58,7 @@ issues = [
5858
[critical-next-actions]
5959
actions = [
6060
"vault-worker production deploy: wrangler kv:namespace create CREDENTIALS + GRANTS → update wrangler.toml IDs → wrangler secret put RGTV_AGENT_TOKEN → populate credentials → just worker-deploy",
61-
"Migrate vault-worker grant store from KV to Durable Objects for atomic one-use guarantee",
61+
# DONE 2026-04-12: vault-worker grant store migrated to Durable Objects
6262
"Add RGTV_MASTER_KEY Worker Secret + AES-256-GCM encryption of CREDENTIALS KV values",
6363
"Run: NESY_INGEST_TOKEN=<tok> ./deploy-fly.sh rgtv (Fly.io spot-test only)",
6464
"Migrate Containerfile from debian:bookworm-slim to cgr.dev/chainguard/rust:latest (builder) + cgr.dev/chainguard/static:latest (runtime)",
@@ -68,6 +68,8 @@ actions = [
6868

6969
[completed-work]
7070
entries = [
71+
{ date = "2026-04-12 (s4)", description = "Close-out: ECOSYSTEM.a2ml updated (CF Workers as production, Fly.io as test-only, V-lang ref removed). META.a2ml updated (5 ADRs recorded). PLAYBOOK.a2ml updated (full CF+Fly+CLI+incident runbook). NEUROSYM.a2ml fixed (Logtalk→JSON). Contractiles rewritten (SPDX typo fixed, RGTV-specific checks). EXPLAINME.adoc replaced template stub with real architecture doc. .gitignore updated (per-crate target dirs, vault-worker/build/, .wrangler/). Justfile: GRANTS DO comment corrected. vault-worker completion 85→100." },
72+
{ date = "2026-04-12 (s3)", description = "vault-worker DO migration: replaced GRANTS KV namespace with Durable Objects. One GrantObject DO instance per outstanding grant. Atomic one-use enforced by DO single-threaded runtime. Removed GRANTS KV binding from wrangler.toml. Added migrations block for GrantObject. do_put_request/do_post_request helpers. handle_create_grant stores via DO PUT. handle_redeem_grant dispatches via DO POST (atomic read+delete)." },
7173
{ date = "2026-04-12 (s2)", description = "vault-worker: Cloudflare Workers production target (Rust/WASM, workers-rs 0.8). Same HTTP API as vault-broker. CREDENTIALS KV (hint→value, operator-managed). GRANTS KV (grant_id→hint, 30s TTL). Justfile: worker-dev, worker-deploy, worker-logs, worker-creds-list, worker-cred-put. KV double-spend caveat documented; Durable Objects migration on roadmap." },
7274
{ date = "2026-04-12", description = "vault-broker: added GET /v1/credentials endpoint returning sorted hint names with no credential values. Fixed two pre-existing bugs: Zeroizing<String> clone in redeem handler, .json() tracing_subscriber call removed (feature not enabled)." },
7375
{ date = "2026-04-12", description = "rgtv CLI (svalinn-cli crate renamed): full rewrite wired to vault-broker HTTP API. Commands: get (grant→redeem→exec with Zeroizing value), list (/v1/credentials), status (/health), verify (functional round-trip), daemon start/stop/status (PID-file managed). Binary renamed from svalinn_cli to rgtv. Added ureq+zeroize deps." },

0 commit comments

Comments
 (0)