From a40a459dab93b460de5866a877b520073151c7eb Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Thu, 23 Jul 2026 21:29:23 -0400
Subject: [PATCH] =?UTF-8?q?docs(contract):=20=C2=A75=20=E2=80=94=20headles?=
=?UTF-8?q?s=20env=20var=20wins=20over=20the=20saved=20token=20file?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
§5 mandates a headless `{SERVICE}_*` variable but never said how it resolves
against a saved token file, so the two CLIs that have both landed on opposite
answers: liftoff lets the env var win, withings-export-cli#43 lets the file
win. Same variable pattern, same invocation, different behavior — exactly the
drift this contract exists to prevent.
Rules the environment wins, and pins the two consequences that fall out of it:
`auth status` reports the env source and exits 0 without a network call, and
for rotating upstreams the headless path writes the rotated token back
best-effort — a failed write is a stderr warning, not an error, because a
read-only rootfs is a normal container shape and a minted access token is
usable whether or not the cache write landed.
Verified against the family before writing it down: liftoff already conforms
(internal/auth/auth.go:84, env checked before load, never persisted); crono
has no token file, so precedence is vacuous there — hence the new n/a cell.
withings-export-cli#43 is the one that changes to match.
Co-Authored-By: Claude Opus 4.8 (1M context)
Claude-Session: https://claude.ai/code/session_012MvqxTC64Z9EEDewCUbNNo
---
CONTRACT.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/CONTRACT.md b/CONTRACT.md
index 8f04332..7202a63 100644
--- a/CONTRACT.md
+++ b/CONTRACT.md
@@ -11,6 +11,7 @@ Each cell shows whether the section is implemented and how it is attested for th
- **machine** — covered by a [`compat/`](compat/README.md) bundle that self-tests green in `quantcli/common`'s own CI on every PR. During this partial-adoption window, **machine** attests that the bundle exists and is green here; the explicit next milestone is the per-exporter consumer-wire PRs that will run the bundle directly in each `*-export-cli`'s own CI.
- **human** — implemented; verified only by review at merge time.
- **—** — not implemented yet.
+- **n/a** — does not apply to this CLI's upstream (e.g. precedence rules for a token file the CLI never writes).
| Section | crono-export | liftoff-export | withings-export |
|---|---|---|---|
@@ -23,6 +24,7 @@ Each cell shows whether the section is implemented and how it is attested for th
| `--format json` | human | human | **machine** |
| `--format csv` | — | — | **machine** |
| `auth status` subcommand | human | human | human |
+| Headless auth precedence (env over token file) | n/a | human | human |
| `prime` subcommand | human | human | human |
| Hermetic `--help` / flag-validation (§7) | **machine** | **machine** | **machine** |
@@ -92,6 +94,14 @@ Auth flows differ legitimately across upstreams (env-var basic auth, OAuth2, int
- An `auth status` subcommand that prints one line summarizing readiness — e.g. `logged in as you@example.com (token expires 2026-05-01)` or `missing CRONOMETER_PASSWORD`. Exit code 0 if usable, non-zero otherwise.
- A headless path: where the upstream's auth model permits it, every CLI accepts environment variables that let a fresh container run without an interactive login. The variable names are `{SERVICE}_*` (e.g. `CRONOMETER_PASSWORD`, `WITHINGS_REFRESH_TOKEN`). Document them in `prime`.
+### Precedence
+
+Where a CLI has both a headless variable and a saved token file, the environment wins:
+
+- **`{SERVICE}_REFRESH_TOKEN` (or the equivalent headless variable) takes precedence over any saved token file when it is set.** A container with a stale mounted config and a freshly injected secret must use the secret. Precedence is uniform across the family — an invocation that honors the variable in one CLI must not silently ignore it in another.
+- `auth status` reports the environment as the source and exits 0 without a network call. Exit 0 there means "a token was supplied", not "the token works" — validity is unknowable locally.
+- Where the upstream **rotates** refresh tokens on each use, the headless path writes the rotated value back to the token file on a best-effort basis, so a repeat caller can read it and re-inject it. A failed write there is a stderr warning, not an error (§4): a minted access token is usable whether or not the cache write landed, and read-only rootfs is a normal container shape.
+
Where the upstream forbids headless auth (e.g. interactive OAuth consent), `auth status` still works and the CLI still exposes `auth login` / `auth logout` / `auth refresh`.
## 6. The `prime` subcommand