DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path investigations/loomworks-operator-tools-menu-pre-cr-verification-investigation-v0_1.md

Operator tools menu — the five pre-Change-Request verifications — investigation — v0.1

Version. 0.1 Date. 2026-07-17 Author. Claude Code (CC) on DUNIN7-M4, read-only inspection; for Marvin Percival (DUNIN7 Operator). Status. Findings document; no CR. Markdown primary (technical consumer). Answers the five questions in section 11 of scoping-notes/loomworks-operator-tools-menu-scoping-note-v0_2.md — the pre-Change-Request verifications the note said could not be answered from Claude.ai — from the running system, the Cloudflare dashboard (via API), and source read this session. Method per question: Q1 — live probe of the running cloudflared tunnel, DNS, and HTTP (not the repo); Q2 — Cloudflare API read of the Pages project; Q3/Q4 — read of engine source at /Users/dunin7/loomworks-engine; Q5 — grep of /Users/dunin7/loomworks-marketing plus the Workers runtime capability. Inspection only; nothing designed, recommended, or built; nothing changed; no prior session's findings relied upon.


Plain-language summary

What this reports. Answers to the five pre-Change-Request verification questions from section 11 of the Operator tools menu scoping note (v0.2) — engine production reachability, the marketing project's Cloudflare compatibility date, whether the engine's secret mechanism suits an asymmetric private key, the full require_founder consumer list, and whether a marketing Pages Function can set a cookie and redirect for a cross-site form POST. Four are answered from the running system, the dashboard, or source; the fifth rests partly on the Workers runtime's documented behaviour.

What was found.

What decision rests on it. Whether the pre-CR gate is clear enough to write a Change Request against the scoping note's design. Four of the five are settled here; the residual unknowns — all named in the closing section — are operational/policy decisions, not blockers a further inspection could clear.

Out of scope. Designs, recommends, and resolves nothing; writes no Change Request. It reports what is running and what the source and runtime settle, and marks plainly what they do not. Read-only; nothing changed.


Q1 — Is the engine reachable from a browser in production, and at what hostname?

Established by probing what is actually running, not the repo. The repo commits no tunnel/deploy config (confirmed); the answer lives in the running tunnel and DNS.

What is running:

What is reachable, probed live:

Answer. Yes, the engine is reachable from a browser in production — as **loomworks-dev.dunin7.com/api/***, via tunnel → Next dev server (:3001) → /api/ proxy → engine (:8000), same-origin, session-cookie-gated, no Access in front. This is exactly the path the scoping note's design assumes ("the menu talks to the engine the way the rest of the Operator Layer already does"), and it works today (the 401 proves the route is live). Two facts correct the note's assumptions: the reachable host is the dev-labelled loomworks-dev.dunin7.com, not a hardened api.; and api.loomworks.dunin7.com is not wired to the engine at all. The engine's production hostname, in the sense of a dedicated public engine endpoint, does not exist — it is fronted indirectly by the frontend's own host.


Q2 — What compatibility date does the marketing Cloudflare Pages project use?

A dashboard fact — retrieved from the Cloudflare API (the repo pins none; confirmed there is no committed wrangler.toml/wrangler.jsonc, so nothing in-repo sets it).

Assessment (runtime capability, asserted from the Workers runtime, not the repo). 2026-05-10 is recent. Ed25519 in the Workers WebCrypto is a standard, supported algorithm well before that date, so crypto.subtle.importKey(..., {name:'Ed25519'}, …, ['verify']) and crypto.subtle.verify('Ed25519', …) work at this compatibility date with no flag and no date bump required. The scoping note's concern ("requires a recent one, and the repository pins none") resolves: the repo pins none, but the project is set to a recent-enough date. This is the one thing that flips from "unknown" to "settled" purely by reading the dashboard, which is now done.


Q3 — Where would an asymmetric private key live in the engine, and how would it be supplied?

Established from engine source.

The mechanism that exists. Secrets are environment variables, read by a single pydantic-v2 Settings(BaseSettings) (config.py:10-93) with .env as fallback (SettingsConfigDict(env_file=".env", …), config.py:11-13); module singleton settings = Settings() (config.py:93). No secrets manager, no KMS, no file-mount reader. Secret material carried this way is all single-line str: loomworks_secret_key (the KEK / Fernet key, config.py:17), loomworks_secret_keys_previous (rotation KEKs as a comma-separated string, :18), database_url (:15), openai_api_key (:49), object-store keys (:61-62). The KEK is read live per call through a provider seam (credentials/kek.py:61-114) whose only implementation is env-backed; the seam exists so a "Level C" KMS could be swapped in later, but only the env provider exists today. The Anthropic key, TOTP secrets, and the founder id are not env-loaded — they live in the DB, Fernet-encrypted under the same single KEK.

Does it suit an asymmetric private key?

Answer. The env-var/.env string path would carry an Ed25519 private key if supplied as a base64 seed (a single-line string), reusing exactly the mechanism loomworks_secret_key uses. But the engine has no code that loads, holds, or signs with an asymmetric private key today — that (and any rotation story, which also does not exist) would be new. Carriage: reuseable. Loading and signing: does not exist yet.


Q4 — What else consumes require_founder?

Established from engine source.

Complete consumer list — exactly three, all via Depends(require_founder), all in admin_grants.py:

  1. admin_grants.py:214POST /admin/grants — issue a credit grant by hand.
  2. admin_grants.py:456GET /admin/grants — list credit grants the caller issued.
  3. admin_grants.py:641POST /admin/provisioning — provision a credit-reservoir tier.

(The prior "three credit-grant routes" is two grant routes plus one provisioning route — all credit-admin.) Every other match is a non-consumer: the import and docstrings in admin_grants.py, the definition at deps.py:598, and doc references in authority/founder.py. No router, service, or script outside admin_grants.py consumes it.

Would a new "mint" consumer disturb anything? No. require_founder (deps.py:598-622) is a pure read-only auth check: it resolves the session principal, reads the founder UUID (get_platform_founder_person_id, a single decrypt read of the system_config row), and raises 403 on mismatch or if unset (fail-closed) — no writes, no mutation, no single-use/nonce, no counter, no shared mutable state. Its only shared dependency is the read-only system_config founder row (whose sole writer is the out-of-band scripts/bootstrap_platform_founder.py, never an HTTP route). A new endpoint adding Depends(require_founder) uses it independently, with zero interaction with the three existing consumers — they share only the policy (same founder, same 403), not any resource.


Q5 — Can a Pages Function set a cookie and return a redirect to a cross-site form POST, on this project's configuration?

Established from marketing source (what exists) plus the Workers runtime (capability).

What the project does today: no cookie handling, no redirects. A grep of functions/ for Set-Cookie, cookie, headers.set/append, Response.redirect, 30x, and new Response finds only the JSON helper functions/api/_lib.js:139 (return new Response(JSON.stringify(obj), …)) — a JSON body, no cookie, no Location. Every onRequestPost handler is a JSON API under functions/api/… (analyze, illustrate, foundation-draft, sales/{analyze,shape,render,draft}); none sets a cookie, none redirects, and there is no non-/api/ entry Function. As the earlier inspection also found, the project imports no crypto. So the project has zero of the pieces this step needs.

What the runtime supports (asserted from the Workers runtime, not the repo). A Pages Function returns a Response; it can set cookies (headers.append('Set-Cookie', …)) and redirect (status 302 + Location). Setting a cookie in response to a cross-site form POST is allowedSameSite governs when a cookie is sent, not when it is set; a SameSite=Lax cookie set here is then sent on the top-level redirect GET and on subsequent same-origin /api/* calls, which is exactly the scoping note's intent. One runtime nuance to note plainly: Response.redirect() returns an immutable response whose headers cannot be extended, so to set a cookie and redirect in one response, the Function must construct new Response(null, { status: 302, headers: { Location, 'Set-Cookie' } }) — a construction the project has no example of.

What the project would need (reported, not designed). A new Function (e.g., a non-/api/ onRequestPost entry point) that: parses the form body, verifies the Ed25519 pass via WebCrypto, and returns new Response(null, {status:302, headers:{Location, 'Set-Cookie': …HttpOnly; Secure; SameSite=Lax…}}) on success (refusing and setting nothing on failure); plus the public-key delivered as a Pages env var (the ANTHROPIC_API_KEY mechanism). The runtime settles "can it" — yes. The project settles "does it" — no, none of it exists.


What remains genuinely unsettleable from here

Four of the five are now answered from the running system, the dashboard, or source. What this inspection cannot settle:


DUNIN7 · inspection only · 2026-07-17 · nothing designed or built; answers drawn from the running system, the Cloudflare API, and source this session.