Skip to content

feat(cloud-facts): signed, versioned, cached facts channel (slice 1, flag off) - #5752

Draft
Hmbown wants to merge 8 commits into
mainfrom
feat/cloud-facts-20260830
Draft

feat(cloud-facts): signed, versioned, cached facts channel (slice 1, flag off)#5752
Hmbown wants to merge 8 commits into
mainfrom
feat/cloud-facts-20260830

Conversation

@Hmbown

@Hmbown Hmbown commented Aug 30, 2026

Copy link
Copy Markdown
Owner

What

Slice 1 of the Supabase-backed cloud facts channel: signed + versioned + cached + fail-safe facts (model catalog deltas, provider defaults, release truth, announcements) served by the website at /api/facts/v1/, consumed by a client behind [cloud_facts].enabled (default OFF), provenance in /status, schema ready for per-org override.

Evidence

  • cargo test -p codewhale-cloud-facts → 8 passed; cargo test -p codewhale-config cloud_facts → 18 passed; web npm test → 355 passed; check:facts OK; tsc + lint clean.
  • Supabase 'CodeWhale Web': facts_* tables RLS enabled+forced, anon SELECT-only; seeded stable channel v1 signed by dogfood key cwf-dogfood-2026-08.

Unproven / before merge

  • Adversarial review gate has NOT run on the final tree (draft for that reason).
  • Dogfood signing private key is in /private/tmp scratchpad — founder custody or replace at rotation (see docs/CLOUD_FACTS.md).
  • Slice 2 items listed in the ops ledger.

No-Issue: cloud facts lane per founder dogfood-Supabase direction

CodeWhale Bot and others added 7 commits August 30, 2026 11:48
…st fixtures

Add web/scripts/facts-publish.mjs (zero-dependency Node: keygen, sign, verify,
emit-sql, publish, revoke; private key and service key only from env at run
time, refuses under CI) and docs/cloud-facts/stable.json as the human-edited
source of truth for the facts/v1 stable channel. Signing contract: Ed25519 over
"codewhale-facts/v1\0" || key_id || "\0" || canonical-JSON payload.

docs/cloud-facts/fixtures/ carries a deliberately public TEST-ONLY keypair and
two envelopes signed with it (v7 all-sections, v8 applies_to >=99.0.0) shared by
the Rust and web test suites; the test-only key is never pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
… layer 15

codewhale_config::cloud_facts is network-free: payload types (schema_version 1),
pinned Ed25519 trust anchors (TRUSTED_KEYS; the dogfood key cwf-dogfood-2026-08
generated 2026-08-30, private half held outside every repo), verify_envelope
(size cap -> shape -> pinned key -> ring Ed25519 -> parse -> cross-checks ->
channel -> schema -> applies_to semver scope -> rollback -> not_after grace),
scoped_view (per-item applies_to, announcement windows, https + official-host
allowlist for base_url), a process-wide overlay with /status provenance, and
field-level catalog patch semantics.

CatalogSource::CloudFacts / CatalogCompiler::with_cloud_facts sit at layer 15
(above bundled + live models.dev, below provider /v1/models, config, user).
PricingProvenance gains CloudFacts. Cloud default_model is consulted only after
CLI/env/config declined, so an explicit choice is never overridden.

cargo test -p codewhale-config: 642 passed; 0 failed; 1 ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
New crate codewhale-cloud-facts modeled on the TUI models_dev_live producer:
maybe_load_persisted_cache (bounded sync read, re-verified on every load,
tampered cache deleted), refresh (If-None-Match/304, 404 = no facts, body cap,
persisted exponential backoff, rollback-protected highest_seen_version), and
spawn_background_refresh. Off by default; CODEWHALE_CLOUD_FACTS=1|0,
CODEWHALE_DISABLE_CLOUD_FACTS=1 (hard kill), CODEWHALE_CLOUD_FACTS_URL /
_CHANNEL / _PATH; CI markers suppress the fetch. Fixed identifier-free
User-Agent only. Inert with no active pinned key. examples/fetch_live.rs is
the dogfood/proof harness. No new registry entries in Cargo.lock (ring,
base64, semver already in the graph).

cargo test -p codewhale-cloud-facts: 8 passed; 0 failed (mock HTTP server:
200/304/404/500/oversize, cache round trip, tamper, local path, backoff).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…(default off)

- [cloud_facts] config table (enabled=false, channel, url, ttl_hours) next to
  [update]; documented in config.example.toml.
- Startup seam right after the models.dev pair: seed from the re-verified disk
  cache, then background refresh; nothing blocks startup.
- provider_lake merges cloud patches between models.dev live and per-provider
  live rows (layer 15) and invalidates its memoized merge on overlay updates.
- /status gains 'Catalog:' (models.dev freshness/count/age) and 'Cloud facts:'
  (channel/version/key/age/origin or why bundled is in use) rows; MessageIds
  added to all 15 locale bundles.
- /model refresh also forces the cloud facts fetch when enabled.
- Picker and fleet badges label CatalogSource::CloudFacts rows.

CARGO_BUILD_JOBS=4 cargo check -p codewhale-tui: Finished (0 warnings).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
…nnel>

web/lib/cloud-facts.ts reads public.facts_current from the CodeWhale Web
Supabase project over PostgREST with the publishable (anon) key only,
rebuilds the envelope, recomputes the payload SHA-256 (mismatch -> 502),
and verifies the Ed25519 signature against web/lib/cloud-facts/keys.ts
(failure -> 503; the edge never amplifies a bad row; an empty key table
skips verification and reports it). keys.ts mirrors
crates/config/src/cloud_facts/keys.rs; check:facts fails on divergence.

The route answers GET/HEAD with a strong ETag, If-None-Match -> 304,
CDN cache headers (s-maxage=300, stale-while-revalidate, stale-if-error),
no Set-Cookie/Vary/query params, and a KV last-good copy (CURATED_KV)
served as X-Facts-Source: kv-stale when Supabase errors or times out
(3 s budget); both down -> 503 Retry-After: 600. /api/facts gains an
additive cloudFacts summary block; schemaVersion unchanged.

Env names SUPABASE_URL / SUPABASE_PUBLISHABLE_KEY added to .env.example
and wrangler.jsonc vars (values set by the founder); .dev.vars ignored.
Verification skip now requires a truly empty key table, so a retired-only
table still rejects instead of silently skipping.

npm test: 355 passed, 0 failed (41 files). npm run check:facts OK
(check-cloud-facts: keys.rs == keys.ts, stable.json release.latest ==
latest-published-release.json, fixtures verify). tsc --noEmit and
eslint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
… vocabulary

docs/CLOUD_FACTS.md documents the facts/v1 channel end to end: envelope
and domain-separated signing (Ed25519 over DOMAIN || key_id || payload),
the Supabase schema with forced RLS and immutability/monotonic triggers,
key custody and rotation runbook (the dogfood key cwf-dogfood-2026-08 was
generated 2026-08-30; its private half lives outside every repo, noted
for founder custody), the publish/revoke runbook, the /status vocabulary,
the two-release key-then-flip rule, and the codewhale.net hosting
discrepancy (Cloudflare Worker via OpenNext, not Vercel as the PRD
states; the route is host-agnostic). CATALOG_REFRESH.md gains the
layer-15 paragraph; CHANGELOG records the feature behind
[cloud_facts].enabled (default off).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aDEyM2a4pPZ9qqMDrP5YX
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@gitguardian

gitguardian Bot commented Aug 30, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
36729265 Triggered Generic Private Key 9e83f81 docs/cloud-facts/fixtures/test-only-signing-key.pem View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

…re key

docs/cloud-facts/fixtures/test-only-signing-key.pem is a deliberately
public Ed25519 test fixture (never pinned, signs nothing outside tests).
Silences the false-positive 'Generic Private Key' incident from 9e83f81.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
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