From 86e009dde52bd588b4ade344bc292d5518843e1d Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 10 Sep 2026 15:08:40 -0500 Subject: [PATCH] Close registry gate 7 with owner-approved refresh mechanism --- docs/SESSIONS.md | 2 + ...26-09-03_auth-provider-harness-registry.md | 29 +- .../2026-09-10_gate7-final-review-request.md | 9 + .../reviews/2026-09-10_gate7-final-verdict.md | 23 ++ ...10_pi0851-refresh-investigation-request.md | 13 + ...2026-09-10_pi0851-refresh-investigation.md | 250 ++++++++++++++++++ 6 files changed, 317 insertions(+), 9 deletions(-) create mode 100644 docs/plans/reviews/2026-09-10_gate7-final-review-request.md create mode 100644 docs/plans/reviews/2026-09-10_gate7-final-verdict.md create mode 100644 docs/plans/reviews/2026-09-10_pi0851-refresh-investigation-request.md create mode 100644 docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index 19686e5e..af9dca8d 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -172,3 +172,5 @@ are never rewritten or removed; corrections are new entries. - 2026-09-10 — darkwing — REGISTRY-GATE7-FILBERT-R2 follow-up APPROVED at 2ec6634f; owner rulings and draft revision committed at b8008eda and pushed, fresh remote identity verified. Documentation only; gates 2/3/4/7/8/9/10 remain owner decisions, no implementation authorized. - 2026-09-10 — darkwing — ms-grill-me rounds 1-2: owner resolved remaining registry gates 2/3/4/8/9/10 and new-model Q8 (per-execution selection); pi pin moves to 0.85.1; remote Ollama http needs allowInsecureTransport flag; fixtures now with optional later real Ollama; OpenBao recorded as future seam only. Draft gate list updated. Gate 7 refresh investigation authorized read-only against 0.85.1. + +- 2026-09-10 — darkwing — Gate 7 closed: owner selected refresh mechanism (a) pi auth check --provider; registry draft updated (driver rule, suites note, resolution section). Investigation findings cfab6c00 to be committed after independent review. All ten gates now resolved; implementation still requires a separately chartered increment. diff --git a/docs/plans/2026-09-03_auth-provider-harness-registry.md b/docs/plans/2026-09-03_auth-provider-harness-registry.md index ff69168c..18b922b0 100644 --- a/docs/plans/2026-09-03_auth-provider-harness-registry.md +++ b/docs/plans/2026-09-03_auth-provider-harness-registry.md @@ -355,8 +355,9 @@ token. Generated files are disposable derivatives of the registry. host service refreshes central OAuth records and rematerializes affected seats; launch-time ensure is the final fail-closed gate. OAuth refresh mechanics must reuse pi's implementation where possible rather than reimplement provider -protocols; the exact noninteractive refresh trigger is an implementation -investigation and acceptance gate. +protocols; the exact noninteractive refresh trigger is resolved by +investigation PI-REFRESH-ROCKO-1 (docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md): +`pi auth check --provider

`, owner-approved 2026-09-10. ## CLI contract (target `mosaic` surface) @@ -454,6 +455,11 @@ No named accounts currently exist, so migration is state-free: - Changing runtime choice regenerates atomically; failed generation preserves previous files and records refusal. - OAuth central refresh rematerializes affected seats; agents never authenticate. +Refresh driver (owner, 2026-09-10): the periodic host service invokes +`pi auth check --provider

` per provider — noninteractive, refresh-if- +expiring (5-minute default window), no credential output. `print-api-key`/ +`print-bearer-token` must not be used by the service because they print +credential material to stdout. - `harness: pi` resolves; unregistered/unsupported harness refuses. - Harness detection records recognized compatible executables without reading harness homes; incompatible/ambiguous detections refuse availability. @@ -488,11 +494,15 @@ No named accounts currently exist, so migration is state-free: 6. **RESOLVED (owner, 2026-09-10):** forked sessions pin the original account in immutable session metadata; unpinned relaunches materialize the current profile default; selection applies between executions, never mid-session. -7. **PARTIALLY RESOLVED (owner, 2026-09-10):** the pin moves to pi 0.85.1 - (exact). A bounded read-only investigation of the 0.85.1 package will - determine the supported noninteractive OAuth refresh path; owner approves - the mechanism from those findings before implementation. Refresh stays - host-side, fail-closed, no token exposure or duplication. +7. **RESOLVED (owner, 2026-09-10):** the pin moves to pi 0.85.1 (exact). + PI-REFRESH-ROCKO-1 (read-only, package identity verified) established that + 0.85.1 supports noninteractive host-side OAuth refresh via one HTTPS POST + using the stored refresh token, no browser, rewriting auth.json in place + under an exclusive lock (pre-existing since 0.83.0/0.84.1). Owner selected + mechanism (a): the periodic host service runs `pi auth check --provider

` + — refresh-if-expiring with no token output. `print-*` commands are excluded + because they emit credential material on stdout. Launch-time ensure remains + the final fail-closed gate. No token exposure or duplication. 8. **RESOLVED (owner, 2026-09-10):** local Ollama routes via `host-gateway` (`http://host.docker.internal:11434/v1`). Remote Ollama supports http and https; plain http requires an explicit per-provider `allowInsecureTransport: @@ -509,5 +519,6 @@ No named accounts currently exist, so migration is state-free: cost. Revisit a separate protected registry root only if re-login pain proves real (would be a canon change). -Remaining open item before implementation: gate 7's 0.85.1 refresh investigation -findings and the owner's approval of the refresh mechanism it proposes. +Remaining open item before implementation: none. All review gates are +owner-resolved. Implementation itself still requires a separately chartered +and reviewed increment (author, reviewer, allowed paths, suites, rollback). diff --git a/docs/plans/reviews/2026-09-10_gate7-final-review-request.md b/docs/plans/reviews/2026-09-10_gate7-final-review-request.md new file mode 100644 index 00000000..0c6919c8 --- /dev/null +++ b/docs/plans/reviews/2026-09-10_gate7-final-review-request.md @@ -0,0 +1,9 @@ +# GATE7-FINAL-FILBERT-1 + +Final focused review closing registry gate 7. Owner ruling 2026-09-10 (Q15: a): the periodic host refresh service invokes `pi auth check --provider

`; `print-api-key`/`print-bearer-token` are excluded because they print credential material. + +Darkwing edits since commit 224147ec: registry draft gate 7 marked RESOLVED with the mechanism and investigation pointer; refresh-driver sentence added to the resolution/mechanics sections; suites line notes the driver and the print-command exclusion; remaining-open-item text replaced with "none — all gates resolved, implementation needs a separately chartered increment." Companion files: Rocko's read-only investigation (cfab6c00) and its request; SESSIONS registration line. + +Verify frozen four-file snapshot /tmp/gate7-final-znmnih6v at SHA256SUMS ea9c539d4ca030278440332fe3f1291e2494e50b475f4a2c46b35b39c290b628, pin 224147ec. Check: (1) draft deltas confined to the gate-7/refresh-driver statements; (2) the investigation's technical claims about 0.85.1 (`pi auth check` default refresh, no TTY/browser, in-place auth.json rewrite under lock, 5-min validity window, print-* emitting secrets, package identity) are internally consistent and cited; flag any claim the file does not actually support; (3) mechanism (a) matches the investigation's findings; (4) no gate is silently reopened or closed beyond owner rulings; no implementation authorization is inferred; (5) no credential material or runtime identifiers appear in any candidate. + +Read-only. No live pi commands, no package fetches, no private reads, no shared-log edits. Write ONLY docs/plans/reviews/2026-09-10_gate7-final-verdict.md and reply to Darkwing/default. APPROVED permits committing exactly these reviewed bytes; REQUEST CHANGES names the fix. diff --git a/docs/plans/reviews/2026-09-10_gate7-final-verdict.md b/docs/plans/reviews/2026-09-10_gate7-final-verdict.md new file mode 100644 index 00000000..5ef0acb4 --- /dev/null +++ b/docs/plans/reviews/2026-09-10_gate7-final-verdict.md @@ -0,0 +1,23 @@ +# GATE7-FINAL-FILBERT-1 — verdict + +Reviewer: Filbert (independent). Author of the draft edits: Darkwing; investigation author: Rocko (PI-REFRESH-ROCKO-1). Frozen snapshot `/tmp/gate7-final-znmnih6v` at manifest SHA-256 `ea9c539d4ca030278440332fe3f1291e2494e50b475f4a2c46b35b39c290b628`, pin `224147ec…`; all four hashes verified before and after review; snapshot unchanged. Read-only: no live pi commands, no package fetches, no private reads. + +## Verdict: APPROVED + +Committing exactly these four reviewed files is approved. This closes gate 7 as documentation; it authorizes no implementation, package change, or credential operation. + +## Findings by assigned check + +1. **Draft deltas confined.** Against pin `224147ec…`, the registry draft changes only: the mechanics-section sentence resolving the refresh trigger via PI-REFRESH-ROCKO-1 with owner approval; the refresh-driver paragraph inside the verification/suites bullet list; gate 7 moving PARTIALLY RESOLVED → RESOLVED (owner, 2026-09-10, mechanism (a)); and the remaining-open-item text. Nothing else moved. + +2. **Investigation consistent, cited, and corroborated.** The 0.85.1 claims are internally consistent (shasum `4cd00f65…` appears identically in three places; the `refresh: false` function default vs `refresh: !noRefresh` CLI wiring contradiction is explicitly reconciled; the two independent 5-minute layers — stored-value safety margin and read-time validity window — are explained coherently; the browser/PKCE login flow is correctly separated from the POST-only refresh). Citations name concrete files and unique anchors. Because the investigation itself establishes the capability as pre-existing since 0.83.0/0.84.1, I corroborated read-only against this repository's pinned 0.84.4 install: the same command set (`check`/`print-api-key`/`print-bearer-token`/`--no-refresh`), the verbatim help sentence, the `0o600`/`proper-lockfile`/read-only-refusal anchors in `auth-storage.js`, the refresh timeout constant, and the Anthropic token URL all exist locally as described. One claim the investigation supports only by inference — that `pi auth check` produces no credential output — is corroborated by the pinned package's own help text: "`--credentials` emits the credential," i.e. only on explicit opt-in. Limit recorded honestly: I could not re-derive the 0.85.1-specific evidence (no fetches permitted); the tarball identity rests on the investigation's recorded npm outputs, and my corroboration covers the shared pre-existing behavior. + +3. **Mechanism (a) matches the findings.** The owner-selected `pi auth check --provider

` is exactly the entry point the investigation verifies as noninteractive, refresh-if-expiring (5-minute default window), no browser/TTY, with the in-place locked `auth.json` rewrite; `print-*` exclusion matches their documented credential-export semantics. + +4. **No gate silently moved.** Gates 1–6 and 8–10 read RESOLVED at the pin and are unchanged; only gate 7 changed, per the owner's Q15(a) ruling. The new remaining-open text ("none — all gates resolved; implementation needs a separately chartered increment") states the boundary correctly and infers no implementation authority. + +5. **No credential material or new identifiers.** The candidates contain no secrets; the investigation records only a public token URL, package digests, and code anchors. The UUID hits in SESSIONS.md are baseline published history; the added registration line carries none. + +## Condition + +Commit exactly the four reviewed units at manifest `ea9c539d…`. Any byte change, including any later 0.85.1 pin bump or refresh-service work, requires its own chartered and reviewed increment. diff --git a/docs/plans/reviews/2026-09-10_pi0851-refresh-investigation-request.md b/docs/plans/reviews/2026-09-10_pi0851-refresh-investigation-request.md new file mode 100644 index 00000000..4688aa8f --- /dev/null +++ b/docs/plans/reviews/2026-09-10_pi0851-refresh-investigation-request.md @@ -0,0 +1,13 @@ +# PI-REFRESH-ROCKO-1 — pi 0.85.1 OAuth refresh investigation (read-only) + +Rocko: bounded read-only investigation under your existing seat. No repo writes, no credential reads, no live OAuth attempt, no config changes, no model use beyond your own session. + +Target: @earendil-works/pi-coding-agent exactly 0.85.1. + +1. Fetch the published package into a temp directory (npm pack or equivalent); verify the tarball identity/integrity the registry provides. Record the exact version and any checksum evidence. +2. Extract and read docs and source for the authentication/credential model: where auth.json lives, its schema for OAuth vs API key, and any noninteractive/headless refresh path (CLI flags, documented env, internal functions exported for it). +3. Answer precisely: (a) does 0.85.1 document or expose a noninteractive token refresh usable host-side without a browser flow; (b) what exact entry point (flag/command/module) drives it; (c) does it rewrite auth.json in place or emit new material; (d) any expiry/staleness metadata it tracks. +4. Note any 0.84.4 → 0.85.x auth-relevant changes you can see in changelogs or release notes. +5. Write findings ONLY to docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md with exact file/line citations from the extracted package and the package identity evidence. Do not modify the repo beyond that single new file. Do not install anything system-wide; temp directory only. + +Report back via agent-send.sh to Darkwing on the default socket when done. This is fact-finding for registry gate 7; it authorizes no implementation and no credential use. diff --git a/docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md b/docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md new file mode 100644 index 00000000..fb56d56f --- /dev/null +++ b/docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md @@ -0,0 +1,250 @@ +# PI-REFRESH-ROCKO-1 — pi 0.85.1 OAuth refresh investigation (read-only) + +Request: `docs/plans/reviews/2026-09-10_pi0851-refresh-investigation-request.md`. +Scope: bounded read-only investigation of `@earendil-works/pi-coding-agent@0.85.1` +for registry gate 7 (does 0.85.1 support noninteractive host-side OAuth token +refresh?). All work was done against a package fetched into a private temp +directory (`npm pack`/extraction, not this repository); no credentials were +read, no live OAuth flow or model call was made, and no files beyond this one +were written to the repository. Currently pinned version in this repo is +`0.84.4` (`package.json:8`); 0.85.1 is the candidate under evaluation. + +## 1. Package identity and integrity evidence + +- Registry metadata (`npm view @earendil-works/pi-coding-agent@0.85.1 dist --json`): + - `shasum`: `4cd00f653c3dabeb193b46f511044e7fbfe0f947` + - `integrity`: `sha512-FGRN+OHbWaefBPGaTggAdLjrIHW+s2PzLyglz/5dfLzb9of7uuXMXYC0fJIeZTw+shS32o2cuQ9jF7YSDuL/oQ==` + - `fileCount`: 1056, `unpackedSize`: 21935887 + - `tarball`: `https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.85.1.tgz` + - Registry-attached npm provenance attestation present (`predicateType: + https://slsa.dev/provenance/v1`), plus two detached signatures under + `dist.signatures` (keyid `SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U`). + - `dist-tags.latest`: `0.85.1`. +- Local fetch (`npm pack @earendil-works/pi-coding-agent@0.85.1 --json`) reported + `shasum: 4cd00f653c3dabeb193b46f511044e7fbfe0f947`, `integrity: + sha512-FGRN+OHbWaefBPGaTggAdLjrIHW+s2PzLyglz/5dfLzb9of7uuXMXYC0fJIeZTw+shS32o2cuQ9jF7YSDuL/oQ==` — + matches the registry's `dist` record exactly. +- Local re-hash of the downloaded tarball: `sha1sum` = `4cd00f653c3dabeb193b46f511044e7fbfe0f947` + (matches npm's `shasum` field bit-for-bit), `sha256sum` = + `1f498729649bdce647d1160993b4d92bf3c614cc819213bee2f91dd34f2a7af4` (recorded + as an independent local fingerprint; npm does not publish a sha256 for the + tarball itself, only the sha512 `integrity` digest above, which the local + fetch reproduced). +- Extracted `package.json`: `name: "@earendil-works/pi-coding-agent"`, + `version: "0.85.1"`, `bin.pi: "dist/bundle/cli.js"`. Package identity confirmed. +- Conclusion: the tarball inspected is exactly the registry-published + 0.85.1 artifact; no substitution or corruption evidence. + +## 2. Auth model: where credentials live, schema, and refresh entry points + +### 2.1 `auth.json` location and schema + +- Default path: `~/.pi/agent/auth.json` (per `docs/providers.md:26,111`, and + the code default `join(getAgentDir(), "auth.json")` in + `dist/core/auth-storage.js:19` (`FileAuthStorageBackend` constructor) and + `dist/core/auth-storage.js:281` (`AuthStorage.create`)). +- File permissions: written with mode `0o600` (`dist/core/auth-storage.js:15`, + `AUTH_FILE_WRITE_OPTIONS = { encoding: "utf-8", mode: 0o600 }`), parent + directory created with `0o700` (`dist/core/auth-storage.js:23-26`). +- Credential schema, validated on every load (`dist/core/auth-storage.js:184-200`, + inside `FileAuthStorageBackend`'s parse/validate path — the same shape is + re-validated by the writable `AuthStorage` class): + - API key: `{ type: "api_key", key?: string, env?: Record }`. + - OAuth: `{ type: "oauth", access: string, refresh: string, expires: number }` + (`dist/core/auth-storage.js:195-199`), where `expires` is a Unix + millisecond timestamp. Any entry not matching one of these two shapes + throws `Invalid auth.json credential for provider ""`. +- Two storage implementations exist: + - `FileAuthStorageBackend`/`ReadOnlyAuthStorage` (`dist/core/auth-storage.js:17`, + `:157`) — read-focused; `ReadOnlyAuthStorage.modify()` + (`dist/core/auth-storage.js:227`) unconditionally throws `"Read-only + credential storage cannot modify auth.json"`. + - `AuthStorage` (`dist/core/auth-storage.js:263`) — the writable, file-lock-backed + store actually used at runtime. Its `modify(provider, fn, options)` + (`dist/core/auth-storage.js:378-390`) takes an exclusive file lock + (`proper-lockfile`, `dist/core/auth-storage.js:33-114`), re-reads current + disk content, calls `fn(currentCredential)`, and if `fn` returns a new + credential object, merges it into the parsed JSON and rewrites the whole + file in place (`JSON.stringify(merged, null, 2)`, same `0o600` write + options). This is the in-place rewrite path referenced in §2.3. + +### 2.2 Noninteractive refresh entry points (CLI) + +Three CLI subcommands are defined in `dist/cli/auth-command.js` and answer the +brief's question (a)/(b) directly — all are plain argv-parsed commands with no +TTY prompt in their code path: + +- `pi auth check --provider [--model ] [--json] [--credentials] + [--no-refresh]` (`dist/cli/auth-command.js:5,23`). Per its own help text + (`dist/cli/auth-command.js:25`): *"Checks refresh expired OAuth credentials + by default; --no-refresh prevents this."* Implementation: + `checkProviderAuth(args, modelRuntime, options = { refresh: false })` + (`dist/cli/auth-check.js:5`) — note the function default is `refresh: + false`, but the CLI wiring passes `refresh: !noRefresh` (i.e. refresh is + the *default* CLI behavior; `--no-refresh` is what sets it to `false`, + matching the printed help text at `auth-command.js:25`). When + `options.refresh` is true, line `dist/cli/auth-check.js:27` calls + `modelRuntime.getAuth(provider)`, which is the code path that performs the + actual token refresh (see §2.3). +- `pi auth print-api-key --provider [--model ]` + (`dist/cli/auth-command.js:6`). +- `pi auth print-bearer-token --provider [--model ] + [--min-expiry ]` (`dist/cli/auth-command.js:7`), default minimum + validity `DEFAULT_BEARER_TOKEN_MIN_EXPIRY_MS = 30 * 60_000` (30 minutes; + bundled at `dist/bundle/chunks/chunk-JVUZSMYM.js`, `resolveCredentialForPrint`). + `--min-expiry` accepts durations like `30m`/`1h` (`dist/cli/auth-command.js:47-53`). + +These two `print-*` commands are documented in `CHANGELOG.md:487,497` (added +in 0.83.0, PR #7168): *"Credential export for external clients — `pi auth +print-api-key` and `pi auth print-bearer-token` export configured credentials +with **automatic OAuth refresh** and minimum-validity enforcement."* +`pi auth check` was added earlier still, in 0.84.1 (`CHANGELOG.md:261,268`, +issue #7152). Both predate 0.84.4, the version currently pinned in this repo, +so this capability is not new to 0.85.1 — see §4. + +### 2.3 What actually performs the refresh, and how it writes back + +The refresh call is lazy/on-demand, triggered by any credential resolution +(`getAuth`) once a stored OAuth credential is within its validity window of +expiring — this is the mechanism both `pi auth check --provider

` (no +`--no-refresh`) and `pi auth print-api-key`/`print-bearer-token` invoke, and +it is also what an ordinary `pi` model call does transparently before making +a request. Two equivalent implementations of this "refresh-if-expiring" guard +are bundled (`dist/bundle/chunks/chunk-IDDQWTHI.js`, function +`resolveStoredOAuth`, and a second call site in the same chunk used by +`ModelRuntime.getAuth`, `dist/core/model-runtime.js:339`): + +``` +DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 300 * 1000 // 5 minutes +DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15 * 1000 // 15 seconds + +expiresSoon(credential) = Date.now() + minimumValidityMs >= credential.expires + +if (expiresSoon(stored)) { + post = await credentials.modify(providerId, async (current) => { + if (current?.type === "oauth" && expiresSoon(current)) { + const refreshSignal = AbortSignal.any([signal, AbortSignal.timeout(15000)]); + return await oauth.refresh(current, refreshSignal); // network call, no browser + } + }); +} +``` + +(`dist/bundle/chunks/chunk-IDDQWTHI.js`, matched around the `resolveStoredOAuth` +function body; the file is a minified single-line bundle so no meaningful +line number applies beyond line 1 — the function and constant names above are +verbatim from the source and are unique, greppable anchors.) + +`credentials.modify(...)` here resolves to `AuthStorage.modify` +(`dist/core/auth-storage.js:378-390`): it re-reads `auth.json` under an +exclusive lock, calls the refresh callback, and — if it returns a value — +**rewrites `auth.json` in place**, replacing only that provider's entry with +the new `{ type: "oauth", access, refresh, expires }` object, preserving all +other providers' entries. It does not append a separate file or emit +material anywhere else; the only output artifact is the updated `auth.json`. + +`oauth.refresh` is a per-provider object with a `refresh(credential, signal)` +method. For Anthropic specifically (`dist/bundle/chunks/anthropic.js`, +function `refreshAnthropicToken`, exposed as +`anthropicOAuth.refresh = (credential, signal) => refreshAnthropicToken(credential.refresh, signal)`): + +``` +TOKEN_URL = "https://platform.claude.com/v1/oauth/token" + +async function refreshAnthropicToken(refreshToken, signal) { + const body = await postJson(TOKEN_URL, { + grant_type: "refresh_token", + client_id: CLIENT_ID, // fixed public OAuth client id, base64-decoded in source + refresh_token: refreshToken, + }, signal); + const data = JSON.parse(body); + return { + type: "oauth", + refresh: data.refresh_token, + access: data.access_token, + expires: Date.now() + data.expires_in * 1000 - 300 * 1000, // 5-minute safety margin baked in + }; +} +``` + +This is a single unauthenticated-by-browser HTTPS POST using only the +already-stored `refresh` token — no interactive browser/PKCE flow, no +loopback callback server, no manual code paste. The browser/PKCE dance +(`loginAnthropic`, same file) is a *separate* function used only for the +initial `/login`; refresh does not invoke it. + +## 3. Direct answers to the brief's questions + +- **(a) Does 0.85.1 document or expose a noninteractive token refresh usable + host-side without a browser flow?** Yes. `pi auth check --provider + ` (default behavior, no `--no-refresh`) and `pi auth + print-api-key`/`pi auth print-bearer-token --provider ` all + trigger the same lazy refresh path described in §2.3 when the stored OAuth + credential is within 5 minutes (or the caller's `--min-expiry`, for + `print-bearer-token`) of expiry. The refresh itself is one HTTPS POST to + the provider's token endpoint with the stored refresh token; no browser or + TTY interaction occurs in this path. +- **(b) What exact entry point drives it?** CLI: `pi auth check --provider + ` (refresh is the default; `--no-refresh` opts out) or `pi auth + print-api-key --provider ` / `pi auth print-bearer-token + --provider [--min-expiry ]` + (`dist/cli/auth-command.js`, `dist/cli/auth-check.js`). Underlying module + entry point: `ModelRuntime.getAuth()` (`dist/core/model-runtime.js:339`) → + bundled `resolveStoredOAuth`/credential resolution in + `dist/bundle/chunks/chunk-IDDQWTHI.js` → provider-specific `oauth.refresh()` + (e.g. `anthropicOAuth.refresh` in `dist/bundle/chunks/anthropic.js`). There + is no separate `pi auth refresh` subcommand; refresh is a side effect of + the read/check/print commands above, or of ordinary model use. +- **(c) Does it rewrite `auth.json` in place or emit new material?** Rewrites + in place. `AuthStorage.modify` (`dist/core/auth-storage.js:378-390`) merges + the refreshed `{access, refresh, expires}` object into the existing parsed + `auth.json` and writes the whole file back to the same path with `0o600` + permissions under an exclusive lock. No new file or side channel is used. +- **(d) Any expiry/staleness metadata it tracks?** Yes: the stored credential + itself carries `expires` (Unix ms). Two related constants govern refresh + timing: `DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 300_000` (refresh triggers + once a credential is within 5 minutes of its stored expiry) and + `DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15_000` (network timeout for the + refresh call itself). Additionally, both the initial token mint + (`exchangeAuthorizationCode`) and every refresh (`refreshAnthropicToken`) + compute `expires` as `Date.now() + expires_in*1000 - 300_000`, i.e. the + provider's own `expires_in` is shortened by a 5-minute safety margin before + being stored, so the two 5-minute buffers are independent layers (one baked + into the stored value, one applied again at read time). + +## 4. 0.84.4 → 0.85.x auth-relevant changes + +Reviewed `CHANGELOG.md` sections for `[0.85.1]`, `[0.85.0]`, and `[0.84.4]` +(lines 3-116 of the extracted `CHANGELOG.md`) for anything touching +`auth`/`oauth`/`refresh`/`token`/`credential`: none of the entries in that +range mention authentication, OAuth, or credential handling — the changes in +that window are model-catalog/provider-setting and reasoning-token-cap fixes +unrelated to auth. The `pi auth check` (0.84.1, `CHANGELOG.md:268`, #7152) +and `pi auth print-api-key`/`print-bearer-token` (0.83.0, `CHANGELOG.md:497`, +#7168) commands both predate 0.84.4 and are unchanged in 0.85.1 as far as the +changelog records. The most recent auth-adjacent history visible anywhere in +the full changelog is older: `auth.json` file-permission/ACL hardening +(`CHANGELOG.md:176`), a requirement that extension OAuth `refreshToken` +callbacks honor an abort signal (`CHANGELOG.md:304`), a fix for stalled OAuth +refreshes leaking the credential-store lock (`CHANGELOG.md:409`, #7508), and a +fix for concurrent-process `auth.json` staleness (`CHANGELOG.md:431`, #7319) — +all several versions prior to 0.84.4 and included for context only, not as +0.84.4→0.85.1 deltas. + +**Conclusion for gate 7:** 0.85.1 supports a documented, noninteractive, +host-side OAuth refresh (`pi auth check` / `pi auth print-api-key` / `pi auth +print-bearer-token`, all requiring only network access and the existing +`refresh` token in `auth.json`, no browser). This is pre-existing behavior +carried unchanged from 0.83.0/0.84.1 through 0.85.1, not a new capability of +0.85.1 itself, and is a code-level finding suitable for reconciliation with +prior "no headless refresh" assumptions if any are recorded elsewhere in gate +7 material — that reconciliation is Darkwing's/the coordinator's call. + +## 5. What this investigation did not do + +No credential file was read (real or synthetic). No live OAuth request or +model call was made — all `refresh`/`access`/`expires` behavior above is +static-analysis of bundled source, not observed execution. No package was +installed system-wide; extraction was confined to a private temp directory +under this session's scratchpad and is not part of the repository. No files +other than this one were added to the repository.