Review store server and client
The exact behavior of artifactize server and of the client side of the shared
review store. The Team review store guide shows
how to set one up.
Review store server
artifactize server token add alice-laptop --scopes read,publish
artifactize server token add ci --scopes read
artifactize server run [--listen 127.0.0.1:8417]
artifactize server token list
artifactize server token revoke alice-laptop [--purge]
artifactize server rm KEY
artifactize server keeps a shared remote review store
in its own review-store.sqlite under --state-dir, separate from state.sqlite.
It keeps every record of each reuse key and
answers a lookup with the latest one by completion time, GREEN or RED, whoever
published it and in whatever order records arrived. server run serves plain HTTP
on loopback by default (it warns when bound elsewhere) and stops on Ctrl-C/SIGTERM;
put a TLS proxy or tunnel in front, because clients require HTTPS except on
loopback. Token commands work on the same file while the server runs, and take
effect immediately.
Token names are bounded ASCII display labels, not secrets; the CLI identity rules give the syntax and pre-store validation.
token add prints a random bearer token once; the store keeps only its SHA-256.
Scopes are read (look up), publish (publish runtime and Agent records) and
human (additionally publish Human sign-offs, together with publish). Give
untrusted CI read only. token list shows names, scopes and creation/revocation
times, never tokens. token revoke NAME rejects the token at once; --purge
also deletes every record it published, and may be repeated later. Revocation always
prints a JSON report, even without --json. Names of revoked tokens are never reused. rm KEY deletes every record of a key and prints
{"removed":true} (false if absent).
The API takes Authorization: Bearer TOKEN and answers JSON ({"error":...} on
failure; 401 for a missing, unknown or revoked token, 403 for a missing scope):
| Route | Scope | Result |
|---|---|---|
GET /v1/whoami | any | {"principal":NAME,"scopes":[...]} |
POST /v1/lookup with {"keys":[KEY,...]} (at most 1000) | read | {"entries":[record,...]}: the latest record of each found key |
PUT /v1/entries/{key} with a record | publish (+ human for Human records) | 201 {"created":true}, or 200 {"created":false} when the store already holds this execution of the key |
The server checks a record’s envelope (schema 2, the path’s key, a GREEN/RED
verdict, a profile kind, an execution ID and an RFC 3339 completedAt) and size:
256 KiB for a summary, 16 MiB for a full record carrying execution. It stamps
publisher (the token name) and publishedAt (server clock), replacing any client
values. Clients check that a record’s key matches its evalDefHash and
fingerprints and additive artifactKinds before they use it. The kind map
covers exactly the fingerprint names for new keys. Changing or dropping it
cannot satisfy a kind-aware key. Lookups update the last use of the record they
return; inserts evict least-recently-used records above 100,000 records or 4 GiB.
Upgrading. A schema 1 or 2 review-store.sqlite (artifactize 0.3 or 0.4) is
upgraded in place to schema 3 when the server opens it. Tokens are kept. Stored
records are dropped: they were keyed by fingerprint and Eval definition hash as 0.4
computed them, which no 0.5 key matches, so clients review again once and publish
anew. A 0.5 server answers 0.4 and 0.3 clients (their lookup keys and their
PUT /v1/entries/{evalDefHash}/{fingerprint} route) with 410 and a message to
upgrade; the 0.3 staleKey alias is gone. Upgrade the server and its clients
together.
0.9 uses reuse-key v2, so it reuses no pre-0.9 record, including script-fingerprint results and Human sign-offs. Existing store records cannot satisfy the new keys; clients review once and publish new records. This is separate from local state upgrades: schemas 1–5 need replacement, while existing schemas 6 and 7 upgrade automatically to schema 8 with Runs, cache records and Human approvals preserved.
Remote review store client
printf '%s\n' "$TOKEN" | artifactize remote login https://reviews.example/ [--share full]
artifactize remote status [--json]
artifactize remote push [--dry-run] [--json]
artifactize remote logout
A client is configured only through the state directory and the environment,
never .artf declarations, so a cloned repository cannot redirect a token. remote login URL reads one token line from stdin (never argv). It attempts to hide
terminal input; on Windows, if that fails, it warns that the token will be visible
and continues reading. Non-terminal stdin is read without changing terminal echo.
It verifies the token with GET /v1/whoami, then stores it in
$STATE/auth/remote-token.json (0700 directory,
0600 single-link file, never followed through a symlink) and writes
$STATE/remote.json: {"url":"https://reviews.example/","share":"summary"}.
URLs must use HTTPS with the system trust store; plain http:// is accepted only
for localhost, 127.0.0.0/8 and [::1]. Credentials, queries and fragments are
rejected. A stored token is bound to the origin it was issued for.
ARTIFACTIZE_REMOTE (a URL, or off), ARTIFACTIZE_REMOTE_TOKEN (for CI) and
ARTIFACTIZE_REMOTE_SHARE (summary or full) override remote.json and the
stored token. remote status prints the URL, share level, token source
(env, file or none), reachability, principal and scopes; it exits 0 when the
store accepts the token. Requests use a 2 s connect and 5 s total timeout and never
follow redirects. remote logout deletes the stored token and remote.json; the
server keeps the token valid until server token revoke. Tokens are never printed
or logged.
With a store configured, verify reads and writes through it. Before the Run claims
anything, it looks up every key once in POST /v1/lookup batches of at most 1000
keys, including keys that already have local records. More than 1000 keys require
multiple requests. It reuses whichever record completed last:
the key’s latest local record or the store’s latest. A store record that completed
after the local latest (or with no local record at all) is appended to the local
history as a self-contained remote-<executionId> execution with an origin, and
reused like a local record, so run show, cache, GC, the monitor and the Run
summary count it as reuse. A key that still has no result is looked up again just
before a local claim and during the artifactize-owned worker’s Human-wait
reconciliation, at most once per second. A remote result for a key that waits for
a Human settles the waiting requests; their never-reviewed waiting execution
becomes ERROR (SUPERSEDED). Text output names the source:
app/check [run-Hq2b9X-1]: GREEN (reused from remote: alice@laptop, run-x05qFq)
brand/signoff [run-Hq2b9X-2]: GREEN (reused from remote: Human sign-off by alice, published by alice-signoff, run-Ksl1Qr)
The producer (user@host) and the Human reviewer are what the publishing machine
recorded; the publisher is the server-authenticated token name. An Agent result’s
producer also carries session, the reference to its
saved conversation on the producing
machine; the conversation itself is never published. Older clients ignore the field,
and the server stores records as they are, so the protocol and record schema (2) are
unchanged. A reused remote result’s request shows the reference, and session show
on it names the machine and state where the conversation lives. Once the local settle
records a GREEN/RED with a reuse key (after the fingerprint recheck), verify sends
its summary record, or the full record with share full, outside any database
transaction. A --speculate Run holds that automatic publication until its scheduling
finishes, even while Human reviews are still pending; it then publishes only the
results that survived, after blocked work has been withdrawn from reuse. Records carry the key, the Eval definition hash, the fingerprint of
each Artifact the key covers, its artifactKinds map (folder/file/graph), and the
execution options (backend, model,
reasoning, limits and profile variant) and an Agent result’s executionPaths pins. request submit publishes Human sign-offs.
A token without read looks nothing up and one without publish publishes nothing,
so a read-only CI token only reuses. Human sign-offs also need human; with any
other token they stay local with a warning. Nothing is published for evals without a
reuse key. --force never reads
from the store, but a forced result is a new record
and is published like any other, so it becomes the store’s latest too (unless a
newer one exists). status makes the same comparison with a read-only lookup,
without mirroring, so its reuse prediction includes newer remote results;
status --force makes no remote call.
remote push sends, for each key, the latest GREEN/RED record this machine
produced: results produced while the store was unreachable, before remote login
or with ARTIFACTIZE_REMOTE=off. It never sends mirrors. A token with read first looks up
the store’s latest record of each key, and a record that already is the latest is
not sent again. The store keeps each execution once, so a record it already holds
answers created: false. Both count as existing, and pushing twice is harmless.
Human sign-offs without the human scope, and records over their size limit, are
skipped with a reason on stderr. --dry-run sends nothing and reports what would
be pushed. The output is Pushed N, already in the store M, skipped K., or JSON
{"dryRun":false,"pushed":N,"existing":M,"skipped":K}. Unlike verify, push
fails on any remote failure, and it needs the publish scope.
Connection errors, request timeouts and 5xx answers fail open: one warning, then the
process continues without the remote, reusing the local latest record of each key and
reviewing locally what has none. 401/403, TLS failures, other error answers, invalid
configuration, and a successful answer whose body cannot be read or decoded (a body
timeout included) fail closed. A 401/403 gets artifactize’s own message that
the store rejected the token; another 4xx answer carries the store’s own error message
when it sends one. Verification looks up remote evidence after durably accepting
the Run: a failed lookup leaves an ERROR Run, not a missing Run. Submit-only
verify exits 0 on acceptance; verify --wait observes that ERROR and exits 2.
Invalid configuration can still reject submission before acceptance. Synchronous
commands such as status report a fail-closed lookup with exit 2 directly.
A record that does not validate is ignored with a warning, and its eval is
reviewed locally.