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 ` (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
+