Skip to content

feat(enclave): package scaffold — settings, sealed-state schema, audit writer, redacting logs, healthz (ENC-2) - #670

Open
p-diogo wants to merge 8 commits into
mainfrom
feat/enc-2-enclave-scaffold
Open

p-diogo wants to merge 8 commits into
mainfrom
feat/enc-2-enclave-scaffold

Conversation

@p-diogo

@p-diogo p-diogo commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator

ENC-2 — enclave package scaffold

Refs p-diogo/totalreclaw-internal#531 (Enclave MCP v1 spec tracking issue). Plan: 2026-09-27-enc-2-enclave-scaffold.md.

What

  • New top-level package enclave/ (totalreclaw_enclave, Python 3.12, distribution totalreclaw-enclave 0.1.0, Private :: Do Not Upload). Pins: mcp>=2.2,<3, starlette>=1.7,<2, uvicorn>=0.54,<1, totalreclaw>=2.5.2,<3 (dev/CI install ./python), totalreclaw-core>=2.6.0,<3.
  • settings.py: env → frozen Settings. ENCLAVE_ENV has no default. Relay, DataEdge and derivation root are fixed per env, and the production relay is refused outside prod. ENCLAVE_ALLOW_OWNER_EOA=1 is refused when ENCLAVE_ENV=prod or on totalreclaw-enclave-v1/prod (E3-4). The check runs on every Settings construction, so dataclasses.replace cannot bypass it.
  • db/: stdlib sqlite3 on one DB thread, WAL, synchronous=FULL, secure_delete. Migration 1 creates every spec §3.1 table (STRICT, exact column names, ON DELETE CASCADE from vaults so unpair is one delete).
  • audit.py: AuditLogger.record refuses plaintext-shaped fields and stores a 16-hex vault_hash, never the address.
  • logs.py + redaction.py: one redacting JSON root handler (arguments, extras, exception messages, third-party loggers, warnings, uncaught exceptions). Ruff G/LOG rules forbid building log templates from values.
  • web.py/subsystems.py/deps.py: Starlette app from ordered Subsystem(name, routes, lifespan) values; frozen Deps with RootProvider/Sealer placeholders for ENC-3.
  • GET /healthz, GET /.well-known/totalreclaw-enclave.json (spec §4.1 keys).
  • CI job enclave-tests (path-scoped); enclave/README.md added to check-docs.sh.

Not in this PR

Sealing (ENC-3), /mcp (ENC-4), OAuth (ENC-5; authlib intentionally not added), tools, worker, NEAR, inference, Dockerfile.

Tests

172 tests (enclave/tests/), including test_logging_redaction.py (no-plaintext invariant), test_migrations.py::test_initial_schema_matches_spec_3_1_exactly, and test_web.py::test_mcp_sdk_streamable_http_mounts_as_a_subsystem (proves the ENC-4 mount pattern against mcp 2.2).

E2E (open item)

No staging E2E is possible until a staging enclave exists (ENC-13). This PR makes no network calls; load_settings refuses the production relay/DataEdge outside ENCLAVE_ENV=prod. Staging E2E is owned by ENC-15.

Review focus

f-string/concat logging in later leaves (ruff G); non-ASCII / multi-line / huge values in logs and audit; env typos (PROD, true); EIP-55 checksummed vault addresses; opening a DB twice or after a newer build migrated it.

@p-diogo

p-diogo commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Whole-PR review (Sonnet): verdict FIX-FIRST (a short fix-up, then merge)

I ran the 172 tests and ruff; the worktree is unchanged. Sources and tests are byte-identical to the verified prototype, and the diff matches the plan's File Structure. CI is green, and enclave-tests ran (1m46s).

Fix before merge

  1. Ruff G/LOG rules don't fire on _log. Ruff recognises loggers by name, so _log.info(f"…") passes lint, and test_ruff_logging_rules_stay_enabled is vacuous: it only checks the select list.
    • Rename _log to logger in web.py.
    • Replace the test with one that runs ruff check --select G,LOG --isolated on a bad snippet and expects a nonzero exit.
    • Document in the README that module loggers must be named logger.
  2. Database.run() leaves COMMIT outside the try/ROLLBACK. A failure at COMMIT (disk full, I/O) leaves the transaction open, and every later BEGIN IMMEDIATE fails, so the enclave is wedged until restart. Move COMMIT inside the try, and roll back if conn.in_transaction. Add an injected-failure test.
  3. _https_origin accepts userinfo. https://enclave.totalreclaw.xyz@evil.example becomes the OAuth issuer in ENC-5. Reject a username, password or empty hostname, and lowercase the host. Apply the same checks to transparency_url. Route dev public_url through the same function, with http allowed for loopback.
  4. Settings.env isn't validated in __post_init__. replace(s, env="PROD", …) gets past the env == "prod" check; the derivation-root check still catches the prod root. Refuse any value outside ENVS, and add a test.

Confirmed:

Can wait for a later leaf: migration contiguity, underscore-joined prose in value_violation, and an audit_log (vault_hash, ts) index.

… commit-failure rollback, origin/env validation

Review items on PR #670 (internal#548):

1. web.py module logger renamed _log -> logger: ruff's G/LOG rules only
   check loggers they recognise by name, so _log escaped lint entirely.
   The rename surfaced a latent G201 (.error(exc_info=True)); now
   logger.exception(). The vacuous pyproject-select-list test is replaced
   by one that runs `ruff check --select G,LOG --isolated` on a bad
   snippet and asserts a nonzero exit with G004 (skips without ruff on
   PATH). README documents the naming rule.
2. Database.run(): COMMIT moved inside the try; on any failure ROLLBACK
   runs only when conn.in_transaction, then re-raises. A COMMIT-time
   failure no longer wedges the shared connection (test injects a
   connection proxy whose execute("COMMIT") raises and asserts the next
   run() succeeds).
3. settings: _https_origin rejects userinfo and an empty host, lowercases
   the host, and guards parts.port; dev public_url now goes through it
   with http allowed only for localhost/127.0.0.1/::1; transparency_url
   gets the same host checks.
4. Settings.__post_init__ refuses env values outside ENVS, closing the
   dataclasses.replace(env="PROD") sidestep of the owner-EOA refusal.

Suite: 172 -> 188 tests (187 passed + 1 ruff-PATH skip under the plain
`python -m pytest` invocation; 188 passed with the venv bin on PATH).
ruff check / format clean; check-docs and phrase-safety guards pass.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@p-diogo

p-diogo commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator Author

Review fix-up landed in be17a84, covering all four items:

  • _log is renamed to logger, and a real ruff G/LOG subprocess test replaces the vacuous one. The rename let ruff reach web.py, which surfaced and fixed a latent G201 (logger.exception).
  • A failed COMMIT now rolls back, with a red→green test of the wedge.
  • _https_origin rejects userinfo and empty hosts, lowercases the host and validates the port. Dev public_url allows http only on loopback.
  • Settings.env is validated in __post_init__.

188 tests pass and CI is green. The ruff test skips only when ruff isn't on PATH; CI and an activated venv run it. D13 (the owner-EOA flag allowed in dev) is acknowledged as a spec-wording difference. Ready to merge.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant