chore(guards): add durable markers so fixes aren't silently dropped
Introduce AGENTS.md (root, primary agent instruction file) documenting six hard-won fixes with explicit DO NOT / WHY, plus executable enforcement so a future AI cannot delete or reintroduce them: 1. package-lock.json must be generated with npm 10 (Docker's npm 10.9.8). npm 11 drops the top-level @emnapi/core + @emnapi/runtime entries npm 10 needs, breaking the tag-triggered Docker build at `npm ci` (happened on v1.0.14). Add scripts/verify-lockfile-npm10.mjs + .npmrc + a Dockerfile fail-fast check + a CI step + tests/unit/lockfile-npm10-guard.test.js. Also re-fix the lockfile itself (regenerated with npm 10.9.8). 2. Tests must never write to the real ~/.9router DB (isolateDataDir). 3. Hidden providers must not leak into Usage (usageProviders !p.hidden). 4. codebuddy-intl connection test + OAuth identity. 5. Fork-only features that must survive upstream syncs. 6. Upstream sync procedure. Each marker cross-references AGENTS.md and the covering test. CLAUDE.md now points to AGENTS.md at the top. Verified: build ok, guard script passes, full suite leaves the real DB count unchanged (38), 0 new regressions.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
# AGENTS.md — MIBP fork guardrails
|
||||
|
||||
> **READ THIS BEFORE EDITING.** This is the **MIBP fork** of `decolua/9router`
|
||||
> (`github.com/mhiqrambg/9router-mibp-version`). Upstream is tracked as the
|
||||
> `upstream` git remote. This file records hard-won fixes that are easy to
|
||||
> silently delete or reintroduce. Each entry has a **DO NOT** and a **WHY**.
|
||||
>
|
||||
> If you are about to change something listed here, stop and read the whole
|
||||
> entry first. If you believe an entry is obsolete, say so explicitly and get
|
||||
> confirmation before removing it — do **not** quietly drop it.
|
||||
|
||||
---
|
||||
|
||||
## 1. `package-lock.json` MUST be generated with npm 10 (Docker's npm)
|
||||
|
||||
**DO NOT** regenerate `package-lock.json` with your global `npm` (npm 11+).
|
||||
**DO NOT** delete `package-lock.json` or add it to `.gitignore`.
|
||||
|
||||
**WHY:** The Docker image pins `node:22-alpine` by digest (see `Dockerfile`
|
||||
`ARG NODE_IMAGE`), which ships **npm 10.9.8**. The `Dockerfile` runs `npm ci`
|
||||
against the committed lockfile. npm 11 **drops the top-level optional entries**
|
||||
`@emnapi/core` and `@emnapi/runtime` that npm 10's platform-complete resolve
|
||||
requires. The tag-triggered **"Build and Push Docker Image"** workflow then
|
||||
fails at `npm ci`:
|
||||
|
||||
```
|
||||
npm error `npm ci` can only install packages when your package.json and package-lock.json are in sync.
|
||||
npm error Missing: @emnapi/runtime@1.11.3 from lock file
|
||||
npm error Missing: @emnapi/core@1.11.3 from lock file
|
||||
```
|
||||
|
||||
This **already happened on tag `v1.0.14`** (run 35422006940) and on earlier
|
||||
releases (`a2c6187a`, `470c8ca7`, `79294644`, `76b139b3`).
|
||||
|
||||
**Correct way to (re)generate the lockfile:**
|
||||
|
||||
```bash
|
||||
npx -y npm@10.9.8 install --package-lock-only
|
||||
node scripts/verify-lockfile-npm10.mjs # must print ✅
|
||||
```
|
||||
|
||||
**Enforcement:** `scripts/verify-lockfile-npm10.mjs` fails (exit 1) if the
|
||||
top-level `@emnapi/core` / `@emnapi/runtime` entries are missing. Run it after
|
||||
any dependency change; CI runs it before the Docker build.
|
||||
|
||||
---
|
||||
|
||||
## 2. Tests must NEVER write to the real database (`~/.9router`)
|
||||
|
||||
**DO NOT** remove `tests/setup/isolateDataDir.js` from `tests/vitest.config.js`
|
||||
`setupFiles`. **DO NOT** delete `tests/unit/test-data-dir-isolation.test.js`.
|
||||
|
||||
**WHY:** Route-level tests (e.g. `zed-live-models.test.js`,
|
||||
`zed-native-auth.test.js`) call the real `createProviderConnection`, which
|
||||
persists to `$DATA_DIR/db/data.sqlite`. With `DATA_DIR` unset — the default for
|
||||
`npx vitest run` — that resolved to the developer's **real** `~/.9router` DB and
|
||||
appended fake connections on every run: `zed-live-*@example.com`,
|
||||
`guard-*@example.com`, `zed "Account N"` (token `decrypted-token-xyz`), and the
|
||||
fake provider `kimchi-nope`. These then showed up in the Usage dashboard. 42
|
||||
such rows had to be cleaned out of the live DB once.
|
||||
|
||||
**Isolation contract:**
|
||||
- Default: `DATA_DIR` → throwaway temp dir (auto-removed on exit).
|
||||
- `RUN_REAL=1` or an explicit `DATA_DIR` → opt out (used by `*.real.test.js`
|
||||
suites that intentionally read live credentials).
|
||||
|
||||
**Verify a change is safe:** run the suite, then confirm the real DB is
|
||||
untouched:
|
||||
|
||||
```bash
|
||||
node -e 'const D=require("better-sqlite3");const db=new D(process.env.HOME+"/.9router/db/data.sqlite",{readonly:true});console.log(db.prepare("SELECT COUNT(*) c FROM providerConnections").get().c)'
|
||||
```
|
||||
|
||||
The count must be identical before and after `npx vitest run`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Hidden providers must not leak into the Usage page
|
||||
|
||||
**DO NOT** remove the `!p.hidden` filter in `src/shared/utils/usageProviders.js`
|
||||
(`buildUsageProviderList`). **DO NOT** inline the old unfiltered
|
||||
`Object.values(FREE_PROVIDERS).filter(p => p.noAuth && ...)` logic back into
|
||||
`src/shared/components/UsageStats.js`.
|
||||
|
||||
**WHY:** The Usage page auto-adds every `noAuth` free provider so connectionless
|
||||
providers (e.g. `opencode`) still appear. It must also honor the registry
|
||||
`hidden` flag, matching the Providers page (`providers/page.js` filters
|
||||
`!info.hidden`). Without it, `devin-cli` and `mimo-free` (both
|
||||
`category:"free"`, `noAuth:true`, `hidden:true`) appear in Usage with zero
|
||||
connections and zero traffic.
|
||||
|
||||
**Covered by:** `tests/unit/usage-provider-list.test.js`.
|
||||
|
||||
---
|
||||
|
||||
## 4. `codebuddy-intl` connection test + OAuth identity
|
||||
|
||||
**DO NOT** remove the `"codebuddy-intl"` entry from `OAUTH_TEST_CONFIG` in
|
||||
`src/app/api/providers/[id]/test/testUtils.js`.
|
||||
**DO NOT** remove `email` / `displayName` from `codebuddy-intl`'s `mapTokens`
|
||||
(`src/lib/oauth/providers/codebuddy-intl.js`).
|
||||
**DO NOT** remove `backfillCodeBuddyIntlIdentity` or its calls in
|
||||
`GET /api/providers` and `/api/providers/client`.
|
||||
|
||||
**WHY (two bugs, both fixed 2026-09-19):**
|
||||
1. **Test Connection** returned `"Provider test not supported"` because
|
||||
`codebuddy-intl` was missing from `OAUTH_TEST_CONFIG`, so
|
||||
`testOAuthConnection` bailed before probing. It now probes the Keycloak
|
||||
realm's `userinfo` endpoint (URL derived from the token's `iss` claim).
|
||||
2. **OAuth logins** were named `"Account N"` with no email. The access token is
|
||||
a Keycloak JWT carrying `email`/`name`; `mapTokens` now extracts them, and a
|
||||
run-once backfill self-heals pre-existing rows.
|
||||
|
||||
**Covered by:** `tests/unit/codebuddy-intl-connection.test.js`,
|
||||
`tests/unit/codebuddy-intl-backfill.test.js`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Fork-only features — NEVER drop during an upstream sync
|
||||
|
||||
When merging `upstream/master`, these fork additions must survive conflict
|
||||
resolution. If a merge conflict touches them, **resolve fork-priority** and
|
||||
re-verify after.
|
||||
|
||||
| Area | Key files / markers |
|
||||
|---|---|
|
||||
| **Freebuff provider** | `open-sse/executors/freebuff.js`, `open-sse/providers/registry/freebuff.js`, `open-sse/services/usage/freebuff.js`, `src/lib/oauth/providers/freebuff.js`, `public/providers/freebuff.png`, and its entries in `open-sse/executors/index.js` + `open-sse/providers/registry/index.js` |
|
||||
| **Proxy-pool fitness** | `open-sse/services/proxyPoolFitness.js`, `open-sse/services/poolGeo.js`, `src/lib/network/poolEgressProbe.js`, `src/lib/network/stateSweeper.js`, `src/app/(dashboard)/dashboard/proxy-fitness/`, `src/app/api/proxy-pools/**` |
|
||||
| **Docker hardening** | `Dockerfile`: digest-pinned `NODE_IMAGE`, tracked `package-lock.json`, `npm ci`, `HEALTHCHECK`. `.github/workflows/docker-publish.yml`. |
|
||||
| **dompurify security override** | `package.json` `overrides.dompurify` + the direct `dompurify` dependency |
|
||||
| **MIBP branding** | `README.md`, `docker-compose.yml`, `.env.example`, the `MIBP Edition` link in `src/app/(dashboard)/dashboard/profile/page.js` |
|
||||
| **Cline free-tier models** | `open-sse/providers/registry/cline.js` `authModes: ["oauth","apikey"]` + `cline-free/*` models; `open-sse/shared/clineAuth.js` product headers |
|
||||
|
||||
---
|
||||
|
||||
## 6. Upstream sync procedure
|
||||
|
||||
- Remote layout: `origin` = this fork, `upstream` = `decolua/9router`.
|
||||
- **Merge, do not rebase** — the fork already has merge-based history; rebasing
|
||||
rewrites public history.
|
||||
- Work on a branch (`sync/upstream-<ver>`), tag a rollback point
|
||||
(`backup/pre-sync-<ver>`), then fast-forward `master`.
|
||||
- After resolving conflicts: `npm run build`, `npx vitest run`, and the
|
||||
baseline scripts (`tests/__baseline__/verify-providers.mjs`,
|
||||
`verify-oauth-urls.mjs`, `verify-alias.mjs`).
|
||||
- The suite is **not** expected to be all-green (see `CLAUDE.md`). Judge only
|
||||
for **new** regressions vs. a pre-merge run; a handful of upstream test-drift
|
||||
failures are known.
|
||||
- Bump `package.json` version (fork cadence `1.0.x`); `CHANGELOG.md` mirrors
|
||||
upstream and is **not** edited by the fork.
|
||||
- Regenerate the lockfile with npm 10 (see §1) after any dependency change.
|
||||
|
||||
---
|
||||
|
||||
## 7. Do not re-add `package-lock.json` to `.gitignore`
|
||||
|
||||
**WHY:** Upstream does not track a lockfile, but this fork **must** — the
|
||||
Docker build depends on `npm ci` against a committed, npm-10-compatible
|
||||
lockfile. The fork explicitly removed `package-lock.json` from `.gitignore`
|
||||
(commit `76b139b3`).
|
||||
Reference in New Issue
Block a user