Skip to content

Cache-server auth scoping: split publish from read - #88

Merged
davet47 merged 1 commit into
mainfrom
cache-token-scoping
Jul 26, 2026
Merged

Cache-server auth scoping: split publish from read#88
davet47 merged 1 commit into
mainfrom
cache-token-scoping

Conversation

@davet47

@davet47 davet47 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

First item of the v0.5 hosted-store theme (ROADMAP: "auth scoping — split publish from read: CI can write greens, a laptop can only consume them. Today one bearer token does both.").

Serverpython -m hashloom.cache_server:

  • Optional --publish-token (or HASHLOOM_CACHE_PUBLISH_TOKEN). When set: publishes (POST /verification, POST /blob) require it; reads (GET) accept either token — publish implies read, so CI holds one token.
  • A valid read token on a publish route gets 403 read_only (scope problem); an unknown token gets 401 unauthorized (identity problem) — diagnosable apart.
  • Every comparison stays constant-time hmac.compare_digest, with both tokens always compared (no short-circuit string equality on the 401/403 split).
  • Back-compat: with only --token, that token grants both roles — byte-identical to today, pinned by test. No existing deployment or config changes.

Client.hashloom/config.json:

  • shared takes an optional "publish": false: a read-only client skips publish requests entirely instead of collecting one rejected POST per verdict/blob during index/verify. Explicit opt-in, so existing single-token CI configs keep publishing. A rejected publish was already a swallowed no-op — the client-side degradation semantics are unchanged.

Untouched by design: shared.py (LayeredStore is role-agnostic) and every contract — the role lives in the constructor/transport where the token already was, so Store's backend-agnostic invariant holds without edits. The three hosted-store files remain deliberately uncontracted while the theme reshapes them.

Tests (6 new in test_remote_store.py): read token 200-on-GET / 403-on-POST; publish token both verbs (implies read); unknown token 401 both verbs; single-token back-compat pin; end-to-end CI-publishes-laptop-consumes (laptop gets cached-pass with zero test runs, and its own local green never lands on the server); publish: false suppression (nothing lands — verdicts or blobs) + config validation.

Live smoke against the real server process: all six token/verb combinations returned the designed codes (404/403/404/200/401/401).

Full suite 239 passed, coverage 90.48% (gate 85), benchmark ≥5x.

Per-project/team tokens remain deferred (docs/hosted-store.md item 2 says so); next theme items are concurrent writers and cross-graph invalidation.

🤖 Generated with Claude Code

…m 1)

Optional --publish-token (HASHLOOM_CACHE_PUBLISH_TOKEN): when set, POSTs
require it and GETs accept either token — publish implies read — so CI
writes greens and a laptop with the read token only consumes them. A
valid read token on a publish route is 403 read_only; an unknown token
stays 401 unauthorized. All checks remain constant-time compare_digest.
Back-compat: with only --token, that token grants both roles, unchanged.
Client side, shared config takes optional "publish": false so read-only
clients skip publish requests entirely (a rejected publish was already a
swallowed no-op). shared.py and all contracts untouched — the role lives
in the transport, per Store's backend-agnostic invariant. Per-project/
team tokens remain deferred (docs/hosted-store.md).
@davet47
davet47 merged commit 4a1b733 into main Jul 26, 2026
10 checks passed
@davet47
davet47 deleted the cache-token-scoping branch July 26, 2026 04:06
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