Close registry gate 7 with owner-approved refresh mechanism
This commit is contained in:
@@ -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 — 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 — 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.
|
||||||
|
|||||||
@@ -355,8 +355,9 @@ token. Generated files are disposable derivatives of the registry.
|
|||||||
host service refreshes central OAuth records and rematerializes affected seats;
|
host service refreshes central OAuth records and rematerializes affected seats;
|
||||||
launch-time ensure is the final fail-closed gate. OAuth refresh mechanics must
|
launch-time ensure is the final fail-closed gate. OAuth refresh mechanics must
|
||||||
reuse pi's implementation where possible rather than reimplement provider
|
reuse pi's implementation where possible rather than reimplement provider
|
||||||
protocols; the exact noninteractive refresh trigger is an implementation
|
protocols; the exact noninteractive refresh trigger is resolved by
|
||||||
investigation and acceptance gate.
|
investigation PI-REFRESH-ROCKO-1 (docs/plans/reviews/2026-09-10_pi0851-refresh-investigation.md):
|
||||||
|
`pi auth check --provider <p>`, owner-approved 2026-09-10.
|
||||||
|
|
||||||
## CLI contract (target `mosaic` surface)
|
## 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
|
- Changing runtime choice regenerates atomically; failed generation preserves
|
||||||
previous files and records refusal.
|
previous files and records refusal.
|
||||||
- OAuth central refresh rematerializes affected seats; agents never authenticate.
|
- OAuth central refresh rematerializes affected seats; agents never authenticate.
|
||||||
|
Refresh driver (owner, 2026-09-10): the periodic host service invokes
|
||||||
|
`pi auth check --provider <p>` 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: pi` resolves; unregistered/unsupported harness refuses.
|
||||||
- Harness detection records recognized compatible executables without reading
|
- Harness detection records recognized compatible executables without reading
|
||||||
harness homes; incompatible/ambiguous detections refuse availability.
|
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
|
6. **RESOLVED (owner, 2026-09-10):** forked sessions pin the original account
|
||||||
in immutable session metadata; unpinned relaunches materialize the current
|
in immutable session metadata; unpinned relaunches materialize the current
|
||||||
profile default; selection applies between executions, never mid-session.
|
profile default; selection applies between executions, never mid-session.
|
||||||
7. **PARTIALLY RESOLVED (owner, 2026-09-10):** the pin moves to pi 0.85.1
|
7. **RESOLVED (owner, 2026-09-10):** the pin moves to pi 0.85.1 (exact).
|
||||||
(exact). A bounded read-only investigation of the 0.85.1 package will
|
PI-REFRESH-ROCKO-1 (read-only, package identity verified) established that
|
||||||
determine the supported noninteractive OAuth refresh path; owner approves
|
0.85.1 supports noninteractive host-side OAuth refresh via one HTTPS POST
|
||||||
the mechanism from those findings before implementation. Refresh stays
|
using the stored refresh token, no browser, rewriting auth.json in place
|
||||||
host-side, fail-closed, no token exposure or duplication.
|
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 <p>`
|
||||||
|
— 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`
|
8. **RESOLVED (owner, 2026-09-10):** local Ollama routes via `host-gateway`
|
||||||
(`http://host.docker.internal:11434/v1`). Remote Ollama supports http and
|
(`http://host.docker.internal:11434/v1`). Remote Ollama supports http and
|
||||||
https; plain http requires an explicit per-provider `allowInsecureTransport:
|
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
|
cost. Revisit a separate protected registry root only if re-login pain
|
||||||
proves real (would be a canon change).
|
proves real (would be a canon change).
|
||||||
|
|
||||||
Remaining open item before implementation: gate 7's 0.85.1 refresh investigation
|
Remaining open item before implementation: none. All review gates are
|
||||||
findings and the owner's approval of the refresh mechanism it proposes.
|
owner-resolved. Implementation itself still requires a separately chartered
|
||||||
|
and reviewed increment (author, reviewer, allowed paths, suites, rollback).
|
||||||
|
|||||||
@@ -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 <p>`; `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.
|
||||||
@@ -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 <p>` 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.
|
||||||
@@ -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.
|
||||||
@@ -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/[email protected]`
|
||||||
|
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/[email protected] 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/[email protected] --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<string,string> }`.
|
||||||
|
- 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 "<id>"`.
|
||||||
|
- 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 <provider> [--model <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 <provider> [--model <model>]`
|
||||||
|
(`dist/cli/auth-command.js:6`).
|
||||||
|
- `pi auth print-bearer-token --provider <provider> [--model <model>]
|
||||||
|
[--min-expiry <duration>]` (`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 <p>` (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
|
||||||
|
<provider>` (default behavior, no `--no-refresh`) and `pi auth
|
||||||
|
print-api-key`/`pi auth print-bearer-token --provider <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
|
||||||
|
<provider>` (refresh is the default; `--no-refresh` opts out) or `pi auth
|
||||||
|
print-api-key --provider <provider>` / `pi auth print-bearer-token
|
||||||
|
--provider <provider> [--min-expiry <duration>]`
|
||||||
|
(`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.
|
||||||
Reference in New Issue
Block a user