mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 01:27:09 +01:00
## Thinking Path > - Paperclip manages agent work as issues and pull requests. > - This change lives in the Codex local adapter path. > - The host auth flow needs a stable cache per identity. > - The cache must not change the copy-back path or the default store overwrite. > - This pull request adds that cache and keeps the existing flow intact. > - The benefit is repeatable host auth with safer identity scoping. ## Linked Issues or Issue Description **Subsystem affected** - packages/adapters: Codex local adapter and host auth flow **Problem or motivation** - The host auth flow needs one usable credential per identity. - The current flow does not keep that identity state in a separate cache. **Proposed solution** - Add an identity-keyed host credential cache. - Keep the cache company scoped. - Add an opt-in seed mode for the merge decision helper. - Write the cache at copy-back time without changing the default store overwrite. **Alternatives considered** - Store the cache in the instance-global root. - Seed the host default store through an environment flag. - Both choices weaken isolation or caller control, so I did not use them. **Roadmap alignment** - I checked `ROADMAP.md`. - I found no direct overlap with an active roadmap item. - The change fits the core auth and secrets direction. **Additional context** - Local tests passed before I opened this pull request. - I found no direct duplicate pull request for this branch. ## What Changed - Added `codex-auth-cache.ts` for company scoped cache storage and identity anchored vending. - Added `codex-auth-merge-decision.cjs` support for a leading `--seed-if-dest-absent` flag. - Updated `codex-auth-copyback.ts` to write the cache at teardown under the merge lock. - Wired `execute.ts` to use the identity anchored vend before the managed home seed step. - Added `CODEX-AUTH-CACHE.md` for the cache rules, directions, state matrix, off switch, and clear action. - Added and updated tests for cache behavior, merge decision flow, and copy-back flow. ## Verification - `pnpm --filter @paperclipai/adapter-codex-local exec vitest run`. - `node_modules/.bin/vitest run --project @paperclipai/adapter-utils workspace-restore-merge`. - `tsc --noEmit` in `packages/adapters/codex-local`. - `git log --oneline origin/master..origin/feat/codex-auth-identity-cache` shows the expected commits. ## Risks - This change touches credential storage. - A mistake in the cache path could break host auth for one identity. - The tests reduce this risk, but the surface stays sensitive. ## Model Used - OpenAI Codex, GPT-5, tool use enabled. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used with version and capability details - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either linked existing issues with `Fixes:` / `Closes:` / `Refs:` or described the issue in the pull request body - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
102 lines
5.2 KiB
Markdown
102 lines
5.2 KiB
Markdown
# Codex identity-keyed host credential cache
|
|
|
|
This document describes the host credential cache for Codex authentication. The
|
|
cache keeps one usable subscription credential per identity (`account_id`) in a
|
|
separate host store. The cache is additive. It does not change the copy-back
|
|
path, the fail-closed decision predicate, or the host default store overwrite.
|
|
|
|
## Where the cache lives
|
|
|
|
The cache root is company-scoped, under the same isolation boundary as the
|
|
managed Codex home:
|
|
|
|
```
|
|
<instanceRoot>/companies/<companyId>/codex-auth-cache/<account_id>/auth.json
|
|
```
|
|
|
|
The root sits outside the shared Codex home (`resolveSharedCodexHomeDir`) and
|
|
outside the symlink allowlist. The `account_id` is sanitized to one safe path
|
|
segment. Each cache root and each identity directory is private (mode `0700`).
|
|
|
|
## The two directions
|
|
|
|
The board set one rule for both directions. A side that has no credential can
|
|
receive one, but only when a real credential on one side names the expected
|
|
identity. The harness never picks a credential from the cache at random.
|
|
|
|
- **Host to sandbox (inbound).** A sandbox that starts with no Codex credential
|
|
takes the host credential. The cache does not change this. At provision the
|
|
cache also refreshes the host credential with a strictly-newer cached copy of
|
|
the same identity (the vend, below).
|
|
- **Sandbox to host (copy-back).** A host that already holds an identity keeps a
|
|
strictly-newer same-identity credential from the sandbox. The cache does not
|
|
change this. At teardown the cache also writes the sandbox credential into its
|
|
per-identity slot (the cache write, below).
|
|
|
|
## The identity anchor rule
|
|
|
|
The identity anchor rule is the load-bearing constraint:
|
|
|
|
- The **cache vend** only refreshes an identity the host **already holds** in the
|
|
shared home. It replaces the staged host credential with a strictly-newer
|
|
cached credential of the **same** `account_id`. It never introduces a new
|
|
identity.
|
|
- When the host shared home holds **no** credential, the vend does **nothing**.
|
|
The harness never selects a cache entry to seed an empty host. This is the
|
|
"no random pick from the cache when the host is empty" rule.
|
|
- The **cache write** keys each entry by the real `account_id` of the credential
|
|
that flows back from the sandbox. It writes to a per-identity cache slot, never
|
|
to the host default store. The cache write is best-effort: it runs after the
|
|
host copy-back finishes, so a cache-write failure never replaces the successful
|
|
copy-back result. The failure is logged with its errno code and the next
|
|
teardown re-attempts the write.
|
|
|
|
The host never learns an identity from the cache. The host only refreshes an
|
|
identity a real credential already states.
|
|
|
|
## State matrix
|
|
|
|
"Host has auth" means the shared source store `resolveSharedCodexHomeDir(env)/auth.json`
|
|
holds a usable subscription credential. "Sandbox has auth" means the run's
|
|
sandbox `auth.json` holds a usable credential at teardown. `X` and `Y` are two
|
|
different subscription identities (`account_id`).
|
|
|
|
| # | Host store | Sandbox cred | Inbound: sandbox home gets | Copy-back: host store | Cache write | Cache vend | Identity anchor |
|
|
|---|---|---|---|---|---|---|---|
|
|
| 1a | HAS `X` | HAS `X`, newer | fresher of the two (`X`) | overwrite with newer `X` | write slot `X` | may stage a strictly-newer cached `X` | host and sandbox both name `X` |
|
|
| 1b | HAS `X` | HAS `Y` (`Y != X`) | host `X` (predicate rejects `Y`) | keep host `X` | write slot `Y` (per identity) | may stage a strictly-newer cached `X` | host names `X`; `Y` is cached, never adopted |
|
|
| 2 | HAS `X` | NONE | host `X` | keep host (sandbox absent) | no write (no source) | may stage a strictly-newer cached `X` | host names `X` |
|
|
| 3 | NONE | HAS `Y` | image-login fallback; host store **never seeded** | keep host **empty** (never seed) | write slot `Y` | **none** (host empty, no random pick) | sandbox names `Y`; host is silent |
|
|
| 4 | NONE | NONE | image-login fallback, or the run fails | keep host **empty** | no write | **none** | no side names an identity |
|
|
|
|
The cache changes an outcome only in the "stage a strictly-newer cached copy of
|
|
an identity the host already holds" cases (rows 1a, 1b, 2, vend column). It never
|
|
changes which identity a side uses. It never seeds an empty host store (rows 3,
|
|
4).
|
|
|
|
## Off-switch
|
|
|
|
The cache is on by default. Set the environment flag `PAPERCLIP_CODEX_AUTH_CACHE`
|
|
to an explicit falsy value (`0`, `false`, `no`, or `off`) to turn it off. When
|
|
off, the teardown cache write and the provision vend become no-ops. The host
|
|
default overwrite is unchanged in both states.
|
|
|
|
## Cache-clear action
|
|
|
|
Two operator actions remove cached credentials:
|
|
|
|
- `clearCodexAuthCacheEntry(env, accountId, companyId)` removes exactly one
|
|
identity slot.
|
|
- `clearCodexAuthCache(env, companyId)` removes every slot in the company-scoped
|
|
cache root.
|
|
|
|
To disable the cache without a code revert, use the off-switch. To remove a
|
|
single cached identity, use `clearCodexAuthCacheEntry`. To remove every cached
|
|
credential, delete the cache root or use `clearCodexAuthCache`. Neither action
|
|
affects the host default store.
|
|
|
|
## No secret in logs
|
|
|
|
The cache logs the decision and the outcome only. It never logs token bytes and
|
|
never logs a raw `account_id`.
|