deckhearth/AGENTS.md

246 lines
42 KiB
Markdown
Raw Permalink Normal View History

bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
# AGENTS.md — AI collaboration (tcg-vault)
Guidance for agents and humans working in this repo. Prefer existing patterns over new abstractions.
> Branding note: this product is **Deck Hearth** as of 2026-05-24
> (`pick-a-name` convoy, squash commit `9abbab6`, PR #21). The GitHub
> repo is now `stwl-labs/deckhearth` (renamed from `stwl-labs/tcg-vault`;
> local checkout folders named `tcg-vault` are fine). Admin email is
> `admin@deckhearth.com`; the prior `admin@tcgvault.com` literal is
> deliberately preserved in `test/lib/permission-middleware.test.js`
> as a historical regression-lock per Risk 4 of the pick-a-name
> convoy.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
> Infra note (2026-08): Deck Hearth is moving off Vercel + Neon onto
> the axiom homelab — Postgres/Redis/MinIO on CT 102, app on Dokploy
> CT 112, public URL `deckhearth.stillwell.cloud`. Runtime DB access
> goes through `lib/sql.js` (the `postgres` package), not
> `@vercel/postgres`; rate limiting reads `REDIS_URL`. CI already gates
> against the homelab deployment; Neon/Vercel decommission is pending
> (`migrate-neon-to-homelab` convoy phases 68). See § 5§ 7 and
> `docs/DOKPLOY_DEPLOY.md` / `docs/HOMELAB_DATABASE.md`.
## Product vocabulary
User-facing copy distinguishes **ownership** (everything you own) from **curated lists** (binders/subsets). Import labels from `lib/collection-vocabulary.js` (`VOCAB`, `collectionDisplayName`) rather than hardcoding strings.
| Concept | UI label | Route / schema | Notes |
| --- | --- | --- | --- |
| Global ownership | **My Collection** | `/my-cards`, `user_cards` | Scanner default destination; replaces "Owned" / "Mark Owned" |
| Curated list / binder | **List** / **Lists** | `/collections`, `/collection/[id]`, `collections` table | URL slug unchanged in v1; nav says "Lists" |
| Auto-sync system list | **Synced binder** (display) | `collections` row with `is_system_collection = true` | DB name stays `'All My Cards'` — never render; use `collectionDisplayName()` |
| Add ownership | **Add to My Collection** | `POST /api/user-cards`, scanner bulk | Replaces "Mark Owned" / "Mark as Owned" |
| Add to curated list | **Add to List** | `POST /api/collections/:id/cards` | Replaces "Add to Collection" in scanner/card flows |
ci: slash GitHub Actions minutes via paths-ignore + consolidation + caching (#126) Standalone infrastructure PR (no convoy ceremony needed — single-file scope). Triggered by the GitHub Actions billing block that gated PRs #124 + #125 today. Three layers of savings applied per the user's max-savings option: 1. paths-ignore on ci.yml + preview-smoke.yml - Doc-only PRs (.convoys/**, **/*.md, docs/**, AGENTS.md, .cursor/**, README.md) now trigger ZERO Actions jobs. - Vercel still builds (it's not on the Actions billing). - visual-diff.yml unchanged — it was already cost-conscious via a positive paths: allowlist (pages/**, components/**, styles/**, etc.). 2. Consolidate 6 grep-only forbidden-* jobs into 1 - Previously 6 independent jobs each ran their own actions/checkout (~3s × 6 = 18s of redundant checkout). - Merged into a single forbidden-patterns job with 6 sequential ::group:: sections, one FAIL flag at the bottom — preserves "see all violations in one run" diagnostic behavior. Per-file ::error file=...::msg annotations work the same way. - Removed jobs: forbidden-endpoints, forbidden-cors-headers, forbidden-client-side-llm-keys, forbidden-modal-shell-without-primitive, forbidden-deprecated-color-aliases, forbidden-stale-strings. - pr-health-rollup.yml only looks up "Lint" and "Schema map up to date" by name — unaffected. 3. Cache node_modules + Playwright browsers - actions/cache@v4 for node_modules keyed by package-lock.json hash, applied to lint / test / migrate / preview-smoke / visual-diff. Cuts npm ci from ~30-45s to ~3-5s on cache hit. setup-node@v4's built-in cache: npm stays (caches ~/.npm) — both layered. - actions/cache@v4 for ~/.cache/ms-playwright keyed by the resolved @playwright/test version. Cache invalidates on any Playwright version bump. On cache hit, only system deps install runs (npx playwright install-deps chromium) — saves ~15-25s/run. AGENTS.md updates: - § 6 Testing § CI behavior: appended "CI minute optimizations" subsection documenting all three layers. - § Product vocabulary table caption: updated "CI job forbidden-stale-strings" reference to "CI check Forbidden patterns (6 checks) → Check 6/6" with a historical pointer. - Gotcha #5: updated the standalone forbidden-endpoints reference similarly. Estimated savings per typical convoy mix (~30% doc PRs based on repo history): - Doc-only PRs: 100% reduction (was ~6-7 min, now 0 Actions min). - Code-touching PRs: ~30-50% reduction (cache hits for npm + Playwright + no redundant 6× checkout). - Weighted average: ~50-60% reduction. This is short of the 70-80% I floated in chat — the real ceiling is limited by lint / vitest / migrate / Playwright runtime itself, all of which are kept on code-touching PRs (they're high-signal). Verification: - All 3 workflow YAMLs parse (python3 -c "yaml.safe_load(...)"). - npm run lint passes (1 pre-existing unrelated warning). - npm run test:run: 118/118 tests pass. - forbidden-patterns logic is byte-equivalent to the 6 original jobs' bash bodies — the differences are: per-check ::group::/::endgroup:: framing, a shared FAIL flag instead of per-job exit 1, and renamed local arrays (FOUND → LLM_FOUND / MODAL_FOUND / STRING_FOUND) to avoid clobbering across the single job's scope. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 17:40:36 -04:00
CI check `Forbidden patterns (6 checks)` → Check 6/6 blocks `"Mark Owned"`, `"Owned Cards"`, and `"All My Cards"` in `pages/` + `components/` (API literals exempt). [Formerly the standalone `forbidden-stale-strings` job; merged into `forbidden-patterns` by the `slash-ci-minutes` convoy on 2026-06-04.]
feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) * feat(design-system): Liquid Glass redesign portfolio — foundation + primitive kit + Layout shell Operator-requested epic to migrate the UI from the current "warm panel + side-highlight + heavy gradient" visual language to a Liquid Glass aesthetic that retains Deck Hearth's fireplace warmth as accent / gradient / motion (not as panel fill). This squash carries the full 8-convoy portfolio drive-through; 5 sub-convoys reach merged state, 3 land architecture-only and queue impl for follow-up turns gated on dedicated visual-diff baseline re-seeds. Sub-convoy #1 (liquid-glass-design-tokens) — MERGED. 29 CSS custom properties: glass-surface {low,mid,high} alpha ramp + blur/saturate + rim-light (inner/outer) + ember-rim (subtle/pronounced; RGB triple) + 3-tier elevation + modal-scrim, both light + dark themes with eye-perception-corrected alphas; @supports not (backdrop-filter) fallback collapsing surfaces toward solid (preserves ramp ordering). Authored docs/DESIGN_TOKENS.md (270 LOC reference with WCAG AA contrast tables, composite recipes, when-NOT-to-use-glass guidance, per-card grid GPU budget). AGENTS.md gains a § Visual language section as the new agent-contract surface. Sub-convoy #2 (liquid-glass-modal-and-surface-primitive) — Brief 1 MERGED. Adds <GlassSurface> (forwardRef composable; tint / rim / elevation / blur props) and <Modal> primitive (focus-trap, ESC + backdrop close, body-scroll lock, ARIA dialog shape, built-in close button) consuming the token surface. lib/use-focus-trap.js — homegrown hook (~60 LOC, no dep). 10 new vitest cases covering open/close render, ARIA, ESC + closeOnEsc gate, backdrop gate, hideCloseButton, body-scroll lock + restore. 4 reference modal migrations as proof-of-pattern: ShareModal, CollectionDeleteModal, CollectionsCreateModal, CardDetailQuantityModal. Brief 2 (11 remaining modals) queued; CI grandfather list locks the pattern in. Sub-convoy #3 (liquid-glass-form-primitives) — Brief 1 MERGED. Adds <Button> (primary ember-gradient with ember-rim-pronounced; secondary glass-mid; danger; ghost), <Input> (glass-high with ember focus ring + label + helperText + error + aria-invalid + describedby wiring + leadingIcon decorative + trailingAction interactive), <SearchBar> (composes Input with leading search icon + conditional clear button). 10 new vitest cases. pages/login.js + pages/signup.js fully migrated — 2 submit buttons + 7 inputs total; existing test/pages/login.test.js assertion ("Sign in to Deck Hearth" button text) preserved. Brief 2 (profile/settings + deck-builder + scanner + card-editor + collection-cluster modal forms) queued. Sub-convoy #4 (liquid-glass-layout-shell) — MERGED. 6 shell surfaces glass-migrated: desktop sidebar rail (glass-mid + rim + ambient elevation), mobile drawer (glass-mid + pronounced elevation), mobile overlay scrim (modal-scrim + blur-high — visually consistent with <Modal>), search header strip (glass-mid + rim), UserProfileDropdown popover (glass-high + ember-rim-subtle + ambient — matches popover recipe), MobileNavigation bottom bar (replaces legacy mobile-nav-backdrop class). The 5 Layout regression-lock tests (logged-out CTA, no maintainer-email default, "Sign in" link present, supplied email renders, no "Guest" placeholder) all still pass — every edit preserved the documented contract. Sub-convoy #5 (liquid-glass-card-surfaces) — ARCHITECTURE RATIFIED; implementation queued. Pixel-sensitive (rarity-glow reconciliation) so wants a dedicated visual-diff baseline re-seed PR. Pre-blocked on a fix-card3d-state convoy (Card3D has pre-existing state-management bug: state setters used without useState declarations). Sub-convoy #6 (liquid-glass-public-and-auth) — ARCHITECTURE RATIFIED; partial impl shipped via #3 (login + signup form primitives migrated). Landing page editorial + public collection/deck views + login/signup outer-wrapper sweep queued. Sub-convoy #7 (motion-system-pass) — MERGED. 8 motion tokens (5-tier duration taxonomy: instant/quick/default/slow/deliberate; 3 easings: ease-out default, spring for delight, linear for progress) added to the token surface. prefers-reduced-motion upgraded from a narrow nav-item rule to a site-wide universal sweep collapsing animation-duration + transition-duration to 0.01ms (preserves end states, no flicker); .motion-essential class is the opt-in escape hatch for state-meaningful animation (loading spinners, scan reticles). Authored docs/MOTION_SYSTEM.md with WCAG SC 2.3.3 contract, composition recipes, audit of existing keyframes, and adding-new-animation checklist. Sub-convoy #8 (cleanup-legacy-design-css) — Brief 1 MERGED. Two new CI jobs in .github/workflows/ci.yml: (1) forbidden-modal-shell-without-primitive (BLOCKING) — fails build if any new file outside the 9 grandfathered legacy modals uses the fixed inset-0 bg-black bg-opacity- shell pattern; locks in the discipline that every modal must compose <Modal> from components/ui. (2) forbidden-deprecated-color-aliases (WARN-only) — audits pre-Deck-Hearth blue/purple/pink aliases (gradient-text-purple/pink/blue, glow-purple/pink/blue, gradient-bg-purple/blue/pink) as a baseline; graduates to FAIL after #8 Brief 2 sweeps consumers. .cursor/rules/ui-and-theming.mdc updated to document the components/ui/ primitive kit and point at the new canonical reference modals. Verification: lint 0 errors (2 pre-existing warnings in unrelated CardEditorForm.js + CollectionsPageView.js — out of scope); vitest 104/104 passing (was 84 — +20 from new primitive tests: 10 Modal + 10 ui-primitives); ci.yml valid YAML; both new CI gates locally exercised and pass on the current tree. Operator follow-ups documented in .convoys/ship-readiness.md § "Design-system redesign portfolio": - Re-seed Linux visual-diff baselines via Docker workflow (AGENTS.md § 6) after this merges. - preview-smoke.yml runs against the preview; auth + scanner specs touch the migrated surfaces. - Vercel promote to production once smoke + visual gates pass. - Queued follow-up implementer turns: #2 Brief 2 (11 modals), #3 Brief 2 (other forms), #5 Brief 1 (cards, after fix-card3d-state), #6 Brief 1 (landing editorial), #8 Brief 2 (legacy CSS deletion + WARN→FAIL graduation). The user-visible promise — "modern fireplace aesthetic; modals blur the page behind them; reusable components" — is delivered TODAY by the merged work. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(use-focus-trap): preserve named useFocusTrap export for ScannerPageView The portfolio squash inadvertently overwrote the pre-existing lib/use-focus-trap.js (named `export function useFocusTrap(active)` returning a ref — used by ScannerPageView, line 21) with a default- only export shaped for the new `<Modal>` primitive. Vercel build failed: "Export useFocusTrap doesn't exist in target module". Fix: the file now exports BOTH — - `useFocusTrap(active)` (named, original) — returns a ref; pre-Liquid-Glass call sites (ScannerPageView) keep working. - `useFocusTrapContainer({ active, containerRef, ... })` (default, new) — takes a caller-owned ref so panel refs can forward through forwardRef chains (Modal.js consumes this shape). Both hooks are commented to document which to use when. Modal.js imports default already, so no change needed there. Verified: npm run build passes (was failing in CI); lint 0 errors; vitest 104/104 still green. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:12:33 -04:00
## Visual language
Deck Hearth's visual direction is **Liquid Glass** (in-progress as of
2026-06-03 — see `.convoys/liquid-glass-redesign.md` umbrella). Every
translucent surface (modals, sidebar, header, popovers, card detail)
composes the canonical token surface defined in `styles/globals.css`
and documented in [`docs/DESIGN_TOKENS.md`](docs/DESIGN_TOKENS.md).
**Do not** hardcode hex in `.js` files; the post-cleanup
`forbidden-hex-in-jsx` gate (sub-convoy #8) will fail the build.
Three rules of thumb:
- **Surfaces are glass.** Modal panels, sidebars, dropdowns, and the
header strip use `--glass-surface-{low,mid,high}` + `backdrop-filter`
composition recipes from `docs/DESIGN_TOKENS.md` § "Composite recipes".
- **Brand warmth is accent, not panel fill.** Ember (`#d84315`), flame
(`#ff6f00`), and gold (`#ffab40`) read as light cast onto glass — via
`--ember-rim-{subtle,pronounced}` rings, focus glow, and gradient
buttons. They are **NOT** the canonical panel-background color.
- **No `backdrop-filter` on card grid items.** GPU budget — glass goes
on grid containers and detail views, not per-card. See
`docs/DESIGN_TOKENS.md` § "Per-card grid performance budget".
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
## 1. Project overview
A web app for managing trading-card-game collections (Magic, Pokémon, Lorcana). Users authenticate, build collections + decks, scan physical cards via a camera+AI-OCR flow, and share publicly. Admin users curate the card database.
docs(AGENTS): reflect bump-next-js outcome (Next 16, ESLint v9, typescript devDep) Doc-writer pass for convoy bump-next-js (PR #4 / commit e57ea17). Single file touched: AGENTS.md (+5 / -1). - § 1 Project overview: Framework line bumped Next.js 15 -> 16, with a cross-reference to new Gotcha #9 for the typescript-is-just-for-lint context. - § 4 Common gotchas: three new entries that future agents need to know about but wouldn't infer from the code: - #9: typescript@^5.9.3 is installed purely so eslint-config-next@16's bundled typescript-eslint chain can satisfy its hard require('typescript') at module load. No tsconfig.json, no .ts files, no @ts-check. Decision C. - #10: ESLint pinned to ^9.39.4 (maintenance), not v10 (latest). v10 surfaced Risk R15 empirically (TypeError: scopeManager.addGlobals) via @typescript-eslint/scope-manager@8.59.4 predating v10 GA. Do not bump independently — wait for queued bump-eslint-10 follow-up convoy. Decision D. - #11: Turbopack is now the default bundler in next dev/build. Fallback per-command is --webpack. Do not pre-emptively switch. - § 7 Deployment: reference VERCEL_AUTOMATION_BYPASS_SECRET (env var name only, no value) for the queued adopt-playwright-smoke convoy to use against protected preview deploys. CHANGELOG.md / DEVELOPER_CHANGELOG.md not created — those are deferred to launch-polish per the convoy's roles section. README.md staleness (line 16 still says "Next.js 15, React 18, TypeScript") flagged in the PR description but NOT fixed here per the docs-pass scope. Pickup: launch-polish. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 03:36:08 -04:00
- **Framework:** Next.js 16 (Pages router) + React 18, JavaScript (not TypeScript — see Gotcha #9)
- **Data:** Postgres 17 + pgvector on the axiom homelab (CT 102, `192.168.68.102:5432`). Runtime DB access goes through `lib/sql.js` — a tagged-template `sql` helper over the `postgres` package returning `{ rows, rowCount }` (the former `@vercel/postgres` shape, so call sites only changed their import). Migrations read `POSTGRES_URL_DIRECT`. 11 `scripts/**` helpers (`setup-neon-db.js`, `reset-db.js`, `migrations/2026-05-24-rename-admin-email.js`, plus 8 historical add-*/fix-*/seed-* jobs) still use `@neondatabase/serverless`'s `neon()` directly — out-of-scope per the no-go-zones rule and tracked as the queued `purge-neondatabase-serverless-fully` follow-up. Schema changes ship as `node-pg-migrate` migrations under `migrations/` at the repo root post-`migration-tool` (PR #32, `de9f334`) — see § 3 Conventions § "Schema changes" and Gotcha #6.
- **Auth:** Custom JWT (jsonwebtoken + bcryptjs), token stored in `localStorage`, sent as `Authorization: Bearer …`. No NextAuth. The secret + canonical 24h TTL come from `lib/auth-secret.js` (single source of truth; throws at module load if `JWT_SECRET` is unset). `getUserFromRequest` returns `null` for unauthenticated requests — no synthetic admin fallback — and login + register are rate-limited via `lib/rate-limit.js` (see Gotcha #12). The seed admin row is created at `admin@deckhearth.com` with a password supplied via the required `ADMIN_INITIAL_PASSWORD` env var (`scripts/setup-neon-db.js` exits with code 1 before touching the DB if the var is unset); no credential ships in the source tree. Operators of envs that pre-date the `drop-public-setup` convoy still have the old `admin123` hash in their DB — rotate manually via the app (see Gotcha #4).
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
- **UI:** Tailwind CSS + custom CSS variables for theming (light/dark via `lib/theme-context.js`)
- **Hosting:** Dokploy on CT 112 (`deckhearth.stillwell.cloud`, Traefik on CT 100). Vercel-era config (`vercel.json`, `.vercel/`) is still in the tree pending decommission (`migrate-neon-to-homelab` phases 68) — see `docs/DOKPLOY_DEPLOY.md`.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
## 2. Architecture quick reference
| Area | Path | Notes |
| --- | --- | --- |
| Pages router views | `pages/*.js` | Public + auth views; uses `components/Layout.js` |
| API routes | `pages/api/**/*.js` | Express-style `handler(req, res)`. **30+ handlers depend on `lib/permission-middleware.js::getUserFromRequest`** |
| Shared UI | `components/*.js` | `Layout`, `CardItem`, `CameraScanner`, modal family |
| Auth + DB libs | `lib/*.js` | `use-auth` (canonical client hook — sole surface post-`single-auth-provider`, PR #31, `0668b0c`), `auth-secret` (single JWT_SECRET + TTL source), `permission-middleware` (server-side `getUserFromRequest` + `withCollectionPermission`), `rate-limit` (6 named limiters — see Gotcha #12), `sql.js` (canonical Postgres client — tagged-template helper over the `postgres` package), `object-storage.js` (MinIO/S3 scan-capture uploads). The legacy `lib/database.js` was deleted by `single-sql-client` (PR #30, `c403ea4`). |
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
| Migration scripts | `scripts/*.js` | 27+ one-off "add column" / "seed" scripts. No formal migration tool |
| Card-import jobs | `pages/api/cards/import-*.js`, `scripts/import-*.js` | Scryfall / Lorcana / Pokémon TCG APIs |
| Database schema | `migrations/` + `scripts/setup-neon-db.js` | `node-pg-migrate` migrations are the source of truth post-`migration-tool`; `setup-neon-db.js` chains `migrate up` + admin seed |
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
| Schema map | `docs/SCHEMA_MAP.md` | Hand-curated; regenerate after schema changes |
Code graph is indexed by `user-code-review-graph` MCP (122 files, 628 nodes, 5602 edges). Ask: *"what calls `getUserFromRequest`?"* before refactoring auth.
## 3. Key conventions
docs: post-convoy cleanup for fix-auth-bypass Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through 1629afb) on the docs side. Code already on main; this PR is docs only. Updates: AGENTS.md - §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic admin, login/register rate limit) - §3 conventions point at lib/auth-secret.js + lib/rate-limit.js - §4 gotchas #2/#3/#5 converted to "Resolved" notes in place (NOT renumbered, to preserve cross-references) - new #12 documents the KV_REST_API_* env-var convention - §5 setup list adds the rate-limit env vars - §6 testing rewritten for Vitest (16 unit tests, blocking CI gate) .cursor/rules/auth-and-permissions.mdc - canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js - token model now 24h (was 7d) with fail-loud explanation - server-side authorization patterns lead with null → 401 contract .cursor/rules/api-routes.mdc - removes the "CRITICAL — known bug" callout (resolved by Brief 2) - adds a "Rate limiting" section with verbatim shape + env-var notes - "Dev/test endpoints" → "Removed" historical note so future agents searching for test-db understand why it's gone .convoys/fix-auth-bypass.md (restored — was on convoy branch only) - frontmatter → status: shipped - new "Convoy outcome" section: briefs + commits + resolved gotchas, R1-R12 risk walk, env-var-rename deviation record, queued follow-up convoys, lessons learned .convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch) - audit-trail completeness; convoy plan references them by name - brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_* across init rules, smoke, pre-deploy checklist - brief 4 has a new "Post-merge addendum" explaining the rename .convoys/ship-readiness.md - P0 #1, #2, #4 → RESOLVED with merge-commit citations - P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers (cors-tighten and add-rate-limiting convoys) - each item gains an "As-shipped" line for self-containment README.md - Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep - auth + rate-limit + testing bullets updated - env-var template extended with KV_REST_API_* - deleted dev-endpoints note added to the API list - "Default Admin Account" section LEFT ALONE — drop-public-setup territory Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass, lint baseline unchanged (128/81/47). Convoy: fix-auth-bypass / role-doc-writer (closeout) Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
- **Auth (server):** `import { getUserFromRequest } from '../../lib/permission-middleware'` → returns `{ userId, email, role }` or `null`. `null` means "send 401" — always early-return when the user is null before doing any work that depends on their identity.
- **Auth (client):** `import { useAuth } from '../lib/use-auth'` is the only client auth surface. Returns `{ user, loading, logout, refreshAuth }`; `user === null` means logged out, `loading === true` means token verification in flight. There is no client-side admin hook — compute `const isAdmin = user?.role === 'admin'` from the same `useAuth()` call. The legacy `lib/auth-context.js` + `lib/admin-auth.js` were deleted by `single-auth-provider` (PR #31, `0668b0c`); do not reintroduce a `<AuthProvider>` / `<AdminProvider>` wrapper in `pages/_app.js`. The login + signup flow uses direct `fetch('/api/auth/{login,register}')` from `pages/login.js` / `pages/signup.js` — there is no `useAuth().login(...)` / `useAuth().register(...)` method; do not add one.
docs: post-convoy cleanup for fix-layout-default-user (+ queue CI-protection follow-up) Reflects the merged fix-layout-default-user convoy (PR #15) and the companion CI permissions fix (PR #16) in repo documentation. Also queues the new fix-vercel-deployment-protection-in-ci convoy that PR #16 exposed. .convoys/ship-readiness.md: - P0 #7: mark RESOLVED 2026-05-24 with squash commit ca302a8. Document the as-shipped Layout default-null change, the 7-page sweep, the 5 new regression-lock vitest assertions, and the queued follow-ups (single-auth-provider, MobileNavigation cleanup) that stayed explicitly out of scope. - Queued convoys: add fix-vercel-deployment-protection-in-ci (P2, CI infra) — PR #16's permissions fix exposed that Vercel Deployment Protection 401s anonymous CI requests; needs a bypass-secret plumb to land cleanly. New section also captures other in-flight follow-ups (rotate-default-admin, cors-tighten, add-rate-limiting, purge-weak-creds-from-helpers, single-auth-provider, cleanup-mobile-nav-dead-props, bump-eslint-10) so the audit trail is centralized. AGENTS.md: - § 4 Gotcha #8: mark RESOLVED with commit ref ca302a8. Mirror the convention used by the prior cleanup commits for #2, #3, #4, #5 (entry kept, not renumbered). - § 3 Key conventions: add a new "Layout user prop" bullet documenting the new default-null + logged-out-CTA contract so the convention is discoverable from the conventions list, not just the resolved-gotcha entry. .convoys/fix-vercel-deployment-protection-in-ci.md (new): - Queued scaffold. Operator must seed VERCEL_AUTOMATION_BYPASS_SECRET as a repo secret before the implementer can run. Decisions to ratify (query param vs. header), known constraints, acceptance criteria, and out-of-scope all enumerated. No changes to: package.json, lib/**, pages/**, components/**, scripts/**, .github/**, README.md, .cursor/rules/**. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 15:37:39 -04:00
- **Layout `user` prop:** pages should pass `user` from `useAuth()` to `<Layout>`. Layout's default is `null` and renders a logged-out "Sign in" CTA when no user is supplied — both paths are valid (some surfaces like `pages/invite/{accept,decline}.js` legitimately render Layout for anonymous visitors). Do not reintroduce a hardcoded user object as a default prop.
docs: post-convoy cleanup for fix-auth-bypass Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through 1629afb) on the docs side. Code already on main; this PR is docs only. Updates: AGENTS.md - §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic admin, login/register rate limit) - §3 conventions point at lib/auth-secret.js + lib/rate-limit.js - §4 gotchas #2/#3/#5 converted to "Resolved" notes in place (NOT renumbered, to preserve cross-references) - new #12 documents the KV_REST_API_* env-var convention - §5 setup list adds the rate-limit env vars - §6 testing rewritten for Vitest (16 unit tests, blocking CI gate) .cursor/rules/auth-and-permissions.mdc - canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js - token model now 24h (was 7d) with fail-loud explanation - server-side authorization patterns lead with null → 401 contract .cursor/rules/api-routes.mdc - removes the "CRITICAL — known bug" callout (resolved by Brief 2) - adds a "Rate limiting" section with verbatim shape + env-var notes - "Dev/test endpoints" → "Removed" historical note so future agents searching for test-db understand why it's gone .convoys/fix-auth-bypass.md (restored — was on convoy branch only) - frontmatter → status: shipped - new "Convoy outcome" section: briefs + commits + resolved gotchas, R1-R12 risk walk, env-var-rename deviation record, queued follow-up convoys, lessons learned .convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch) - audit-trail completeness; convoy plan references them by name - brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_* across init rules, smoke, pre-deploy checklist - brief 4 has a new "Post-merge addendum" explaining the rename .convoys/ship-readiness.md - P0 #1, #2, #4 → RESOLVED with merge-commit citations - P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers (cors-tighten and add-rate-limiting convoys) - each item gains an "As-shipped" line for self-containment README.md - Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep - auth + rate-limit + testing bullets updated - env-var template extended with KV_REST_API_* - deleted dev-endpoints note added to the API list - "Default Admin Account" section LEFT ALONE — drop-public-setup territory Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass, lint baseline unchanged (128/81/47). Convoy: fix-auth-bypass / role-doc-writer (closeout) Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
- **JWT secret + TTL:** `import { JWT_SECRET, JWT_TOKEN_TTL } from '../../lib/auth-secret.js'`. This is the only place either value is defined; do not reintroduce literal fallbacks. `JWT_TOKEN_TTL = '24h'` is canonical.
- **Auth helper (token mint / verify / password hash):** `import { ... } from '../../pages/api/auth-utils'` (`generateToken`, `verifyToken`, `hashPassword`, `verifyPassword`). Reads the secret + TTL from `lib/auth-secret.js` under the hood.
- **Rate limiting:** `import { checkAuthRateLimit } from '../../lib/rate-limit.js'` for any new auth-surface endpoint (`/api/auth/login` + `/api/auth/register` already wired). Returns `{ allowed, remaining, reset }`; on `!allowed` return 429 with a `Retry-After` header. See `.cursor/rules/api-routes.mdc` § "Rate limiting" for the verbatim shape.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
- **Permission gate for collection routes:** wrap handlers with `withCollectionPermission('viewer' | 'editor' | 'owner')` from `lib/permission-middleware.js`.
- **DB access:** Use **tagged-template** style — `import { sql } from '../lib/sql.js'` (path relative to the caller). The legacy `lib/database.js` (`db.query(string, params)` wrapper around `@neondatabase/serverless`, which interpolated params into a string and called `sql.unsafe`) was deleted by `single-sql-client` (PR #30, `c403ea4`); do NOT reintroduce that shape. `lib/sql.js` reads `POSTGRES_URL` (falls back to `DATABASE_URL`) and returns the former `@vercel/postgres` result shape `{ rows, rowCount }`. For `scripts/**` helpers that legitimately need the Neon HTTP driver during the transition (e.g. `reset-db.js`, the rename-email migration), import `{ neon } from '@neondatabase/serverless'` directly and use tagged-template SQL (`await sql\`...\``) — the safe shape, not a string-interpolating wrapper.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
- **Activity logging:** `logCollectionActivity(collectionId, userId, action, details)` — call it from any handler that mutates a collection.
- **File names:** `kebab-case.js` for libs/scripts; `PascalCase.js` for React components.
- **Imports:** No path aliases configured; use relative imports.
- **Slugs:** `lib/slug-utils.js::generateUniqueSlug` for any user-facing identifier (collections, decks).
- **CSS theme tokens:** Components read `var(--bg-primary)`, `var(--text-primary)`, `var(--accent-ember)`, etc. — defined in `styles/`. Don't hardcode hex colors.
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
- **Schema changes (post-`migration-tool`):** new column / constraint / table work ships as a `node-pg-migrate` migration under `migrations/` at the repo root. Generate via `npm run migrate create <name> -- -j js`, edit the `up()` (and `down()` when rollback is safe — for any migration that touches data, prefer a hard-stub `down()` that throws), and apply locally with `npm run migrate up`. `npm run setup-db` now chains `npm run migrate up` then seeds the admin user. The legacy `scripts/add-*.js` / `scripts/fix-*.js` / `scripts/seed-*.js` jobs are append-only history (no-go-zones rule); do NOT add new ones. See `.convoys/migration-tool.md` for the full architect-decision record and Gotcha #6 below for the historical context.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
## 4. Common gotchas
- **#1 — Two SQL clients live in parallel. RESOLVED** by `single-sql-client` convoy (PR #30, squash commit `c403ea4`, 2026-05-26). `lib/database.js` is deleted; the 2 callers (`pages/api/auth-utils.js` source + `test/api/auth-utils.test.js` mock) migrated to `@vercel/postgres` tagged templates (byte-equivalent SQL semantics for the two single-parameter SELECT queries). The convoy's architect audit (D4) confirmed no current call site actually exercised the `sql.unsafe` injection vector — the `userId` callers passed a numeric SERIAL from a verified JWT — so this was foot-gun removal rather than a live security finding. **`@neondatabase/serverless` is still in `package.json` as a runtime dep** because 11 `scripts/*` helpers continue to use `neon()` directly (`setup-neon-db.js`, `migrations/2026-05-24-rename-admin-email.js`, `reset-db.js`, plus 8 historical `add-*` / `fix-*` / `seed-*` jobs). Those scripts use the safe tagged-template shape (`await sql\`...\``), not the deleted wrapper's unsafe `db.query(string, params)` shape. Full dep purge is tracked as the queued `purge-neondatabase-serverless-fully` follow-up (now unblocked by `migration-tool` PR #32 — the migration helpers all use `node-pg-migrate`'s `pg` client, not `@neondatabase/serverless`, so the only remaining direct `neon()` consumers are `setup-neon-db.js` (admin seed), `reset-db.js`, and the historical graveyard). Entry kept (not renumbered) to preserve cross-references.
docs: post-convoy cleanup for fix-auth-bypass Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through 1629afb) on the docs side. Code already on main; this PR is docs only. Updates: AGENTS.md - §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic admin, login/register rate limit) - §3 conventions point at lib/auth-secret.js + lib/rate-limit.js - §4 gotchas #2/#3/#5 converted to "Resolved" notes in place (NOT renumbered, to preserve cross-references) - new #12 documents the KV_REST_API_* env-var convention - §5 setup list adds the rate-limit env vars - §6 testing rewritten for Vitest (16 unit tests, blocking CI gate) .cursor/rules/auth-and-permissions.mdc - canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js - token model now 24h (was 7d) with fail-loud explanation - server-side authorization patterns lead with null → 401 contract .cursor/rules/api-routes.mdc - removes the "CRITICAL — known bug" callout (resolved by Brief 2) - adds a "Rate limiting" section with verbatim shape + env-var notes - "Dev/test endpoints" → "Removed" historical note so future agents searching for test-db understand why it's gone .convoys/fix-auth-bypass.md (restored — was on convoy branch only) - frontmatter → status: shipped - new "Convoy outcome" section: briefs + commits + resolved gotchas, R1-R12 risk walk, env-var-rename deviation record, queued follow-up convoys, lessons learned .convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch) - audit-trail completeness; convoy plan references them by name - brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_* across init rules, smoke, pre-deploy checklist - brief 4 has a new "Post-merge addendum" explaining the rename .convoys/ship-readiness.md - P0 #1, #2, #4 → RESOLVED with merge-commit citations - P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers (cors-tighten and add-rate-limiting convoys) - each item gains an "As-shipped" line for self-containment README.md - Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep - auth + rate-limit + testing bullets updated - env-var template extended with KV_REST_API_* - deleted dev-endpoints note added to the API list - "Default Admin Account" section LEFT ALONE — drop-public-setup territory Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass, lint baseline unchanged (128/81/47). Convoy: fix-auth-bypass / role-doc-writer (closeout) Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
- **#2 — `getUserFromRequest` synthetic-admin fallback. RESOLVED** by `fix-auth-bypass` Brief 2 (commit `258e479`). The helper now returns `null` for unauthenticated requests; `pages/api/auth/verify.js` returns 401 on the no-token branch. The 16 unit tests in `test/lib/permission-middleware.test.js` lock in the contract, including a negative regression against the old synthetic-admin shape. Entry kept (not renumbered) to preserve the audit trail and stable cross-references.
- **#3 — JWT_SECRET hardcoded across 7 files. RESOLVED** by `fix-auth-bypass` Brief 1 (commit `4a10dce`). `lib/auth-secret.js` is now the single source of truth and throws at module load when `JWT_SECRET` is unset. Canonical TTL is `JWT_TOKEN_TTL = '24h'`. The `'your-secret-key-change-in-production'` literal is gone from all 7 sites; CI lint passes against the post-fix tree. Entry kept (not renumbered) to preserve cross-references.
convoy: scripts/rotate-admin-password.js — one-shot admin rotation (rotate-default-admin) (#141) Closes the operator caveat from the `drop-public-setup` convoy: deployed envs that ran `npm run setup-db` BEFORE `ff80753` (2026-05-22) still carry the historical `admin123` bcrypt hash. The seed is idempotent (`ON CONFLICT (email) DO NOTHING`), so re-running setup-db is a no-op on existing rows. ## Design — D1: which option from the 3-option menu? | Option | Picked? | Why | |---|---|---| | A. Close as no-op (defer rotation to manual app login) | No | Leaves a real-world residue if any pre-drop-public-setup env still exists — and an audit is harder than just shipping the script. | | B. One-shot parameterized rotation script | **Yes** | Tightly scoped (~120 lines). Audit-trail-preserving (`updated_at` bump). Reusable for future rotations. No new auth surface in the app. | | C. First-login forced password reset flow in the app | No | Right product answer, but heavier scope (new route, new flag column, UI work). Deferred as the queued `force-admin-password-reset-flow` convoy. | ## Script shape `scripts/rotate-admin-password.js`: - Reads `POSTGRES_URL` + `ADMIN_NEW_PASSWORD` from env (or `.env.local`). - Optional `ADMIN_EMAIL` override; defaults to `admin@deckhearth.com`. Pass `admin@tcgvault.com` for envs that pre-date `pick-a-name` (squash `9abbab6`, 2026-05-24). - Fail-loud-exits BEFORE opening any DB connection if: - `POSTGRES_URL` is unset - `ADMIN_NEW_PASSWORD` is unset or empty - `ADMIN_NEW_PASSWORD` is shorter than 12 chars - Validates the target row EXISTS AND has `role = 'admin'` before touching it. Refuses to rotate non-admin rows even if `ADMIN_EMAIL` points at one. Refuses to rotate when multiple rows match (impossible given the UNIQUE(email) constraint, but checked anyway). - Hashes with bcryptjs at 12 rounds — same as `setup-neon-db.js`. - After UPDATE, re-fetches the row and runs `bcrypt.compare(newPassword, row.password_hash)`; exits non-zero if the compare fails (extremely unlikely, but catches silent UPDATE failures). - NEVER echoes the password to stdout / stderr / shell history. The only output is the row id, email, role, and updated_at. Same import shape as the existing `scripts/migrations/2026-05-24-rename-admin-email.js` (ESM, `dotenv.config({ path: '.env.local' })`, `import { neon } from '@neondatabase/serverless'`, tagged-template SQL) — keeps the "11 scripts/* using neon() directly" graveyard from gaining new patterns; fits the `purge-neondatabase-serverless-fully` follow-up convoy's existing audit shape. ## Out of scope - Sibling test users (alice / bob in `scripts/create-test-users.js`) — dev fixtures, not real auth surfaces. Documented inline + in AGENTS.md Gotcha #4. - First-login forced password reset flow — deferred as the queued `force-admin-password-reset-flow` convoy (it's the right product answer, but heavier scope than this hygiene PR). - Email rotation (already handled by `scripts/migrations/2026-05-24-rename-admin-email.js`). ## Test plan - [x] `node --check scripts/rotate-admin-password.js` — syntax OK - [x] `npm run lint` — clean (1 pre-existing unrelated warning) - [x] `npm run test:run` — 118 tests pass - [ ] CI on this PR - [ ] Operator-side smoke test (NOT covered by CI): - Set `ADMIN_NEW_PASSWORD=test-rotation-12chars` against a throwaway Neon branch DB, run the script, log in via the app with the new password, run the script again with a different password, log in again. Skip if there's no convenient throwaway DB. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 00:23:44 -04:00
- **#4 — Default admin credentials in the seed. RESOLVED** by `drop-public-setup` Brief 1 (commit `ff80753`) + Brief 2 (commit `b63b509`). `scripts/setup-neon-db.js` no longer hardcodes `admin123`; it reads `ADMIN_INITIAL_PASSWORD` from the environment and exits with code 1 before opening a DB connection if the var is unset. README's "Default Admin Account" section is replaced with "First-time admin setup" copy that documents the env var, `openssl rand -base64 24` generation tip, and CI-secret alternative. Brief 2 converted the script from CJS to ESM so `npm run setup-db` actually runs on Node 22.x (the `bump-next-js` convoy's `"type": "module"` flag had silently broken it). **Operator caveat:** the seed is idempotent (`ON CONFLICT (email) DO NOTHING`); re-running setup-db on an env that already has the admin row does NOT rotate the password. Any deployed env that ran setup before this convoy still has the weak `admin123` hash — operators rotate via the new `scripts/rotate-admin-password.js` (see `rotate-default-admin` resolution below). Entry kept (not renumbered) to preserve cross-references.
**Rotation script — `rotate-default-admin` resolution** (2026-06-13). `scripts/rotate-admin-password.js` closes the operator caveat above with a one-shot, audit-trail-preserving rotation:
```bash
POSTGRES_URL=<prod-url> \
ADMIN_NEW_PASSWORD=$(openssl rand -base64 24) \
node scripts/rotate-admin-password.js
```
Fail-loud-exits BEFORE opening a DB connection if `POSTGRES_URL` / `ADMIN_NEW_PASSWORD` are missing or the password is shorter than 12 chars. Validates the target row exists AND has `role = 'admin'` before touching it (refuses to rotate non-admin rows). Verifies the new bcrypt hash matches the supplied plaintext via `bcrypt.compare` post-update. Never echoes the password. Optional `ADMIN_EMAIL` override defaults to `admin@deckhearth.com`; pass `admin@tcgvault.com` to target a pre-`pick-a-name`-rename env. Sibling test users (alice / bob in `scripts/create-test-users.js`) are intentionally NOT rotated — they're dev fixtures.
Post-`pick-a-name` (2026-05-24, squash `9abbab6`), the seeded admin
email is `admin@deckhearth.com` (and alice/bob test users likewise
renamed). **If you are deploying past `9abbab6` and the prod Neon
DB still has `@tcgvault.com` rows, you MUST run
`node scripts/migrations/2026-05-24-rename-admin-email.js` BEFORE
the next admin login attempt** or it 401s. The migration is ESM,
idempotent, UNIQUE-collision-safe (fails loud if `setup-neon-db.js`
already ran post-rename — which would indicate an ordering error).
Order: migration FIRST, then any subsequent `npm run setup-db`.
ci: slash GitHub Actions minutes via paths-ignore + consolidation + caching (#126) Standalone infrastructure PR (no convoy ceremony needed — single-file scope). Triggered by the GitHub Actions billing block that gated PRs #124 + #125 today. Three layers of savings applied per the user's max-savings option: 1. paths-ignore on ci.yml + preview-smoke.yml - Doc-only PRs (.convoys/**, **/*.md, docs/**, AGENTS.md, .cursor/**, README.md) now trigger ZERO Actions jobs. - Vercel still builds (it's not on the Actions billing). - visual-diff.yml unchanged — it was already cost-conscious via a positive paths: allowlist (pages/**, components/**, styles/**, etc.). 2. Consolidate 6 grep-only forbidden-* jobs into 1 - Previously 6 independent jobs each ran their own actions/checkout (~3s × 6 = 18s of redundant checkout). - Merged into a single forbidden-patterns job with 6 sequential ::group:: sections, one FAIL flag at the bottom — preserves "see all violations in one run" diagnostic behavior. Per-file ::error file=...::msg annotations work the same way. - Removed jobs: forbidden-endpoints, forbidden-cors-headers, forbidden-client-side-llm-keys, forbidden-modal-shell-without-primitive, forbidden-deprecated-color-aliases, forbidden-stale-strings. - pr-health-rollup.yml only looks up "Lint" and "Schema map up to date" by name — unaffected. 3. Cache node_modules + Playwright browsers - actions/cache@v4 for node_modules keyed by package-lock.json hash, applied to lint / test / migrate / preview-smoke / visual-diff. Cuts npm ci from ~30-45s to ~3-5s on cache hit. setup-node@v4's built-in cache: npm stays (caches ~/.npm) — both layered. - actions/cache@v4 for ~/.cache/ms-playwright keyed by the resolved @playwright/test version. Cache invalidates on any Playwright version bump. On cache hit, only system deps install runs (npx playwright install-deps chromium) — saves ~15-25s/run. AGENTS.md updates: - § 6 Testing § CI behavior: appended "CI minute optimizations" subsection documenting all three layers. - § Product vocabulary table caption: updated "CI job forbidden-stale-strings" reference to "CI check Forbidden patterns (6 checks) → Check 6/6" with a historical pointer. - Gotcha #5: updated the standalone forbidden-endpoints reference similarly. Estimated savings per typical convoy mix (~30% doc PRs based on repo history): - Doc-only PRs: 100% reduction (was ~6-7 min, now 0 Actions min). - Code-touching PRs: ~30-50% reduction (cache hits for npm + Playwright + no redundant 6× checkout). - Weighted average: ~50-60% reduction. This is short of the 70-80% I floated in chat — the real ceiling is limited by lint / vitest / migrate / Playwright runtime itself, all of which are kept on code-touching PRs (they're high-signal). Verification: - All 3 workflow YAMLs parse (python3 -c "yaml.safe_load(...)"). - npm run lint passes (1 pre-existing unrelated warning). - npm run test:run: 118/118 tests pass. - forbidden-patterns logic is byte-equivalent to the 6 original jobs' bash bodies — the differences are: per-check ::group::/::endgroup:: framing, a shared FAIL flag instead of per-job exit 1, and renamed local arrays (FOUND → LLM_FOUND / MODAL_FOUND / STRING_FOUND) to avoid clobbering across the single job's scope. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 17:40:36 -04:00
- **#5 — `pages/api/setup-database.js` public endpoint. RESOLVED** by `fix-auth-bypass` Brief 3 (commit `fc0dd73`). The file is deleted along with the other three dev endpoints (`/api/simple`, `/api/test-auth`, `/api/test-db`), and `.github/workflows/ci.yml`'s `forbidden-patterns` job (formerly the standalone `forbidden-endpoints` job; consolidated by `slash-ci-minutes` convoy on 2026-06-04) fails the build if any of them are re-introduced (or if a new `pages/api/test-*.js` file appears). Entry kept (not renumbered) to preserve cross-references.
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
- **#6 — Migrations were bare scripts. RESOLVED** by `migration-tool` convoy (2026-05-26). `node-pg-migrate@^8` is the chosen tool (lightweight, raw-SQL-friendly, zero TS surface — matches the repo's JavaScript-only `@vercel/postgres` style). New migrations live under `migrations/` at the repo root and use the default `pgmigrations` tracking table. The initial backfill `migrations/1779853647564_initial-schema.js` reproduces `scripts/setup-neon-db.js`'s 7-table DDL verbatim using `CREATE TABLE IF NOT EXISTS`, so it's idempotent against fresh AND pre-existing envs — first-time `npm run migrate up` on an env that already ran `setup-neon-db.js` pre-convoy is a no-op DDL-wise (only records the `pgmigrations` row). The legacy 27 `scripts/add-*.js` / `scripts/fix-*.js` / `scripts/seed-*.js` jobs are append-only history per the no-go-zones rule — do NOT add new ones. New column / constraint work ships as a `node-pg-migrate` migration. See § 3 Conventions § "Schema changes" above + `.convoys/migration-tool.md`. Entry kept (not renumbered) to preserve cross-references.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
- **#7 — Dual `is_public` semantics.** Collections and decks both have `is_public` columns; check which controls discovery vs. anonymous read in the relevant route.
docs: post-convoy cleanup for fix-layout-default-user (+ queue CI-protection follow-up) Reflects the merged fix-layout-default-user convoy (PR #15) and the companion CI permissions fix (PR #16) in repo documentation. Also queues the new fix-vercel-deployment-protection-in-ci convoy that PR #16 exposed. .convoys/ship-readiness.md: - P0 #7: mark RESOLVED 2026-05-24 with squash commit ca302a8. Document the as-shipped Layout default-null change, the 7-page sweep, the 5 new regression-lock vitest assertions, and the queued follow-ups (single-auth-provider, MobileNavigation cleanup) that stayed explicitly out of scope. - Queued convoys: add fix-vercel-deployment-protection-in-ci (P2, CI infra) — PR #16's permissions fix exposed that Vercel Deployment Protection 401s anonymous CI requests; needs a bypass-secret plumb to land cleanly. New section also captures other in-flight follow-ups (rotate-default-admin, cors-tighten, add-rate-limiting, purge-weak-creds-from-helpers, single-auth-provider, cleanup-mobile-nav-dead-props, bump-eslint-10) so the audit trail is centralized. AGENTS.md: - § 4 Gotcha #8: mark RESOLVED with commit ref ca302a8. Mirror the convention used by the prior cleanup commits for #2, #3, #4, #5 (entry kept, not renumbered). - § 3 Key conventions: add a new "Layout user prop" bullet documenting the new default-null + logged-out-CTA contract so the convention is discoverable from the conventions list, not just the resolved-gotcha entry. .convoys/fix-vercel-deployment-protection-in-ci.md (new): - Queued scaffold. Operator must seed VERCEL_AUTOMATION_BYPASS_SECRET as a repo secret before the implementer can run. Decisions to ratify (query param vs. header), known constraints, acceptance criteria, and out-of-scope all enumerated. No changes to: package.json, lib/**, pages/**, components/**, scripts/**, .github/**, README.md, .cursor/rules/**. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 15:37:39 -04:00
- **#8 — Layout has hardcoded default user. RESOLVED** by `fix-layout-default-user` convoy (PR #15, squash commit `ca302a8`). `components/Layout.js`'s default prop is now `null`; `UserProfileDropdown` renders a `<Link href="/login">Sign in</Link>` CTA when `user === null`. Brief 2 also swept the 7 pages that needed page-level fixes (`scanner` / `decks` / `deck-builder` / `deck/[id]` now pass `user={user}` to Layout; `profile` / `settings` replaced leaky `useState({email:'me@…'})` with `useState(null)` + null-guards on every sync `user.*` read; `card/[id]` swapped a hardcoded `const user = {...}` for `useAuth()` from `lib/use-auth.js`). `test/components/Layout.test.js` adds 5 regression-lock assertions (no maintainer email when user is null/omitted; "Sign in" link present; supplied email renders; no "Guest" placeholder); vitest 21/21 green at merge. New devDeps: `jsdom@^29` + `@testing-library/react@^16`. See `.convoys/fix-layout-default-user.md` and `.convoys/ship-readiness.md` P0 #7. Entry kept (not renumbered) to preserve cross-references.
docs(AGENTS): reflect bump-next-js outcome (Next 16, ESLint v9, typescript devDep) Doc-writer pass for convoy bump-next-js (PR #4 / commit e57ea17). Single file touched: AGENTS.md (+5 / -1). - § 1 Project overview: Framework line bumped Next.js 15 -> 16, with a cross-reference to new Gotcha #9 for the typescript-is-just-for-lint context. - § 4 Common gotchas: three new entries that future agents need to know about but wouldn't infer from the code: - #9: typescript@^5.9.3 is installed purely so eslint-config-next@16's bundled typescript-eslint chain can satisfy its hard require('typescript') at module load. No tsconfig.json, no .ts files, no @ts-check. Decision C. - #10: ESLint pinned to ^9.39.4 (maintenance), not v10 (latest). v10 surfaced Risk R15 empirically (TypeError: scopeManager.addGlobals) via @typescript-eslint/scope-manager@8.59.4 predating v10 GA. Do not bump independently — wait for queued bump-eslint-10 follow-up convoy. Decision D. - #11: Turbopack is now the default bundler in next dev/build. Fallback per-command is --webpack. Do not pre-emptively switch. - § 7 Deployment: reference VERCEL_AUTOMATION_BYPASS_SECRET (env var name only, no value) for the queued adopt-playwright-smoke convoy to use against protected preview deploys. CHANGELOG.md / DEVELOPER_CHANGELOG.md not created — those are deferred to launch-polish per the convoy's roles section. README.md staleness (line 16 still says "Next.js 15, React 18, TypeScript") flagged in the PR description but NOT fixed here per the docs-pass scope. Pickup: launch-polish. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 03:36:08 -04:00
- **#9 — `typescript` is a devDep, but the source is still JavaScript-only.** `package.json` lists `typescript@^5.9.3` purely so `eslint-config-next@16`'s bundled `typescript-eslint` chain can satisfy its hard `require('typescript')` at module load (the `peerDependenciesMeta.typescript.optional: true` flag in `eslint-config-next` only suppresses npm's install-time warning, not the runtime require). There is no `tsconfig.json`, no `.ts`/`.tsx` files, and no `// @ts-check` directives. Do not rename `.js` files to `.ts` or add a `tsconfig.json` without an explicit convoy decision — TypeScript adoption is its own scope. See `.convoys/bump-next-js.md` § Decisions C.
- **#10 — ESLint pinned to v9 (maintenance), not v10 (latest).** `devDependencies.eslint` is `^9.39.4` even though `latest` is `10.4.0`. We tried v10 and `npm run lint` crashed with `TypeError: scopeManager.addGlobals is not a function` because `eslint-config-next@16`'s bundled `typescript-eslint@8.x` predates ESLint v10's redesigned global-ingestion path. Reverted to v9 under Decision D. **Do NOT bump ESLint independently** — wait for the queued `bump-eslint-10` follow-up convoy, which is upstream-blocked until `typescript-eslint` ships a v10-tested release that `eslint-config-next` bundles. See `.convoys/bump-next-js.md` § Decisions D + "Follow-up convoys queued".
- **#11 — Turbopack is now the default bundler.** `next dev` and `next build` use Turbopack by default in Next.js 16. The fallback per command is `--webpack` (e.g. `next build --webpack`). We have no custom `webpack:` block in `next.config.js`, no custom loaders/aliases, and no Sass tilde imports, so Turbopack should "just work" — but if a build/runtime regression appears, reproduce on both bundlers before deciding whether to revert or pin a script to webpack. Do not pre-emptively switch to `--webpack`.
- **#12 — Rate limiting reads `REDIS_URL` (homelab Redis, CT 102).** `lib/rate-limit.js` is backed by `ioredis` + `rate-limiter-flexible` with **six named limiter classes** (`auth`, `search`, `upload`, `generate`, `import`, and the scanner-era `scan` — 15/min user-keyed), each with its own `deckhearth:*` Redis key prefix. The Vercel-Upstash era vars (`KV_REST_API_URL` / `KV_REST_API_TOKEN`) are obsolete — do not wire to them. In production the module **fails closed** if `REDIS_URL` is missing (a single failed login is a better outcome than silently disabling brute-force protection). In dev / test, it warn-and-continues as a no-op so local work is unaffected when Redis isn't reachable.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
**Milestone — `add-rate-limiting` convoy (squash `708ef45`, PR #20, 2026-05-24) closed P0 #6 — all 8 P0s now RESOLVED.** The lib refactored from a single auth-only limiter to **5 named limiters** with a `Map<className, Ratelimit>` cache (one shared Redis client, distinct Redis prefix per class). A sixth (`scan`, from the scanner-era hardening) joined later — current full set:
docs: post-convoy cleanup for add-rate-limiting — MILESTONE, last P0 closed Reflects the merged add-rate-limiting convoy (PR #20, squash commit 708ef45) in repo documentation. **This is the milestone cleanup** — add-rate-limiting closed P0 #6 (No rate limiting anywhere), the LAST open P0 ship-blocker. `.convoys/ship-readiness.md`'s § Status summary flips from "7 of 8 RESOLVED; 1 remains" to **"8 of 8 RESOLVED. Launch-readiness P0 checklist is empty."** One brief in the convoy: Brief 1 shipped as planned with no scope expansions and no implementer deviations from the verbatim spec; all six architect decisions ratified verbatim at gate 1 (D1 operator-ratified Option A; D2-D6 architect-self-ratified). .convoys/add-rate-limiting.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section. Opens with the milestone language pointing back at ship-readiness.md's flipped § Status summary. Decisions section captures all 6 ratifications (D1 operator- ratified Option A — Critical: WHY the atomic admin UI fix in pages/admin/card-import.js was Decision 1's hidden coupling requirement, since API gating alone would have broken every "Import Cards" click; D2 hybrid named-limiter shape with Map<className, Ratelimit> cache; D3 per-class table including the two D3 tuning-evidence raises — search 30 -> 60/min because ShareModal.handleSearch has no debounce so a 17-char email = 16 requests in <5s, and generate kept at 5/hour because DiceBear is free not paid AI; D4 two-extractor shape with defensive THROW on null/empty userId; D5 uniform 429 message; D6 no new vitest or playwright specs deferred to fill-vitest-handler-coverage). As-shipped surface broken into 4 layers (1 lib refactor + 6 route gates + 1 atomic admin UI fix + 1 rule extension) mirroring the cors-tighten cleanup's pattern-split shape. Empirical CI metrics from post-merge run 26382185019 (Playwright smoke 59s 3/3 in 3.8s, forbidden-cors-headers pass, vitest 21/21, lint 128 baseline, Screenshot diff continue-on-error swallow per Decision 4). Cross-validation finding: smoke test 2 still passes against the post-rate-limit preview — that's three convoys in a row (PR #15 Layout default-user, PR #19 CORS-tighten, PR #20 rate-limiting) where the same 3-test smoke spec defended the auth surface through sweeping changes. Operator-action-required: none. What did NOT change audit trail. .convoys/ship-readiness.md: - § Status summary at the top flipped from 7/8 to 8/8 RESOLVED. Header text updated to "Launch-readiness P0 checklist is empty." P0 #6 row in the table flips from PARTIAL to RESOLVED with the two-convoy lineage (fix-auth-bypass Brief 4 + add-rate-limiting). Trailing paragraph rewritten as a milestone note: security gate closed; remaining launch work is P1 quality bar + P2/P3 polish. - P0 #6 entry flipped from PARTIAL to RESOLVED with the full add-rate-limiting as-shipped block (8 sub-bullets covering the lib refactor shape, the per-class table, the defensive THROW, the three import routes' auth-gating, the atomic admin UI fix and WHY, the rule extension, the 6 decisions, and the diff breakdown). Brief 4's 2026-05-23 partial is preserved as the prior as-shipped layer to maintain the audit trail. - Launch sequence step 4 marked RESOLVED 2026-05-24 with the squash commit + smoke metrics inline. - Queued convoys: removed the add-rate-limiting entry (it shipped). Added a new delete-dead-lorcana-import entry (P3 polish; the Lorcana import route was gated defensively in PR #20 despite zero current frontend callers — pages/admin/card-import.js's <select> only offers mtg + pokemon — so if Lorcana stays permanently out of the admin UI, this is the cleanup PR). Added three "flagged but kept out of scope" follow-ups per the convoy's § What did NOT change: harden-multipart-parser (P2; 5MB body still consumed before the 429 path on avatar.js), god-function-split / refactor-cards-search-sql (P2; 240-line 7-branch SQL in cards/search.js), and withAdmin(handler) wrapper extraction (P3 DX; the three import routes are call sites #3-5 in the codebase but uniform inline shape was preserved for convoy atomicity). Updated tighten-visual-diff-path-filter to note PR #20 also tripped the same false-positive. AGENTS.md: - Gotcha #12 extended end-to-end. Was the single-class auth-only lib + the env-var contract; is now the 5-class reality with a full per-class table (helper / limit-window / key / routes), the defensive THROW pattern in extractUserIdentifier, the gate-ordering rule for per-user limiters, and the auth → admin-role → rate-limit ordering for the three import routes. Prominent milestone line opens the new content: "add-rate-limiting convoy (squash 708ef45, PR #20, 2026-05-24) closed P0 #6 — all 8 P0s now RESOLVED." Original env-var contract paragraph (KV_REST_API_URL / KV_REST_API_TOKEN, fail-closed-in-prod / warn-and-noop-in-dev) is preserved verbatim above the new content. - § 6 Testing: intentionally untouched (no test surface changed; vitest 21/21 and smoke 3/3 still apply). - § 7 Deployment: intentionally untouched (no deployment-shape changed; same KV_REST_API_* env vars from Brief 4). .cursor/rules/api-routes.mdc: - The implementer extended § Rate limiting in PR #20 with the per-class table + verbatim call shape + gate-ordering rules + identifier-extraction + uniform 429 + fail-closed env-var contract + fail-open Upstash-outage behavior. Doc-writer pass verified completeness; added a one-sentence convoy-attribution line at the top of § Rate limiting citing the two-convoy lineage (fix-auth-bypass Brief 4 for the auth class + add-rate-limiting for the other four classes and 7 newly-gated routes), mirroring the post-cors-tighten § CORS attribution shape. No other touch-ups needed. No changes to: package.json, package-lock.json, lib/rate-limit.js, pages/**, components/**, scripts/**, test/**, tests/**, .github/workflows/**, README.md, TESTING_GUIDE.md, playwright.config.js, eslint.config.mjs. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 00:09:07 -04:00
| Helper | Class | Limit/window | Key | Redis prefix | Routes |
| --- | --- | --- | --- | --- | --- |
| `checkAuthRateLimit(req)` | `auth` | 5 / 15 min | IP | `deckhearth:auth` | `/api/auth/login`, `/api/auth/register` (Brief 4 contract; byte-identical return shape preserved) |
| `checkSearchRateLimit(req)` | `search` | 60 / 1 min | IP | `deckhearth:search` | `/api/users/search`, `/api/cards/search` |
| `checkUploadRateLimit(req, userId)` | `upload` | 10 / 1 hour | user | `deckhearth:upload` | `/api/user/avatar` |
| `checkGenerateRateLimit(req, userId)` | `generate` | 5 / 1 hour | user | `deckhearth:generate` | `/api/user/avatar/generate` |
| `checkImportRateLimit(req, userId)` | `import` | 5 / 1 hour | user (admin-only) | `deckhearth:import` | `/api/cards/import-mtg`, `/api/cards/import-pokemon` |
| `checkScanRateLimit(req, userId)` | `scan` | 15 / 1 min | user | `deckhearth:scan` | `/api/scan/identify` (one camera verify may escalate L0→L2; vision path is the expensive step) |
Prefixes renamed `tcgvault:*``deckhearth:*` in `pick-a-name` (squash `9abbab6`, 2026-05-24); accepted one-time per-15-min / per-1-hour counter reset; existing Upstash state at `tcgvault:*` keys is now stale and will TTL out naturally.
docs: post-convoy cleanup for add-rate-limiting — MILESTONE, last P0 closed Reflects the merged add-rate-limiting convoy (PR #20, squash commit 708ef45) in repo documentation. **This is the milestone cleanup** — add-rate-limiting closed P0 #6 (No rate limiting anywhere), the LAST open P0 ship-blocker. `.convoys/ship-readiness.md`'s § Status summary flips from "7 of 8 RESOLVED; 1 remains" to **"8 of 8 RESOLVED. Launch-readiness P0 checklist is empty."** One brief in the convoy: Brief 1 shipped as planned with no scope expansions and no implementer deviations from the verbatim spec; all six architect decisions ratified verbatim at gate 1 (D1 operator-ratified Option A; D2-D6 architect-self-ratified). .convoys/add-rate-limiting.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section. Opens with the milestone language pointing back at ship-readiness.md's flipped § Status summary. Decisions section captures all 6 ratifications (D1 operator- ratified Option A — Critical: WHY the atomic admin UI fix in pages/admin/card-import.js was Decision 1's hidden coupling requirement, since API gating alone would have broken every "Import Cards" click; D2 hybrid named-limiter shape with Map<className, Ratelimit> cache; D3 per-class table including the two D3 tuning-evidence raises — search 30 -> 60/min because ShareModal.handleSearch has no debounce so a 17-char email = 16 requests in <5s, and generate kept at 5/hour because DiceBear is free not paid AI; D4 two-extractor shape with defensive THROW on null/empty userId; D5 uniform 429 message; D6 no new vitest or playwright specs deferred to fill-vitest-handler-coverage). As-shipped surface broken into 4 layers (1 lib refactor + 6 route gates + 1 atomic admin UI fix + 1 rule extension) mirroring the cors-tighten cleanup's pattern-split shape. Empirical CI metrics from post-merge run 26382185019 (Playwright smoke 59s 3/3 in 3.8s, forbidden-cors-headers pass, vitest 21/21, lint 128 baseline, Screenshot diff continue-on-error swallow per Decision 4). Cross-validation finding: smoke test 2 still passes against the post-rate-limit preview — that's three convoys in a row (PR #15 Layout default-user, PR #19 CORS-tighten, PR #20 rate-limiting) where the same 3-test smoke spec defended the auth surface through sweeping changes. Operator-action-required: none. What did NOT change audit trail. .convoys/ship-readiness.md: - § Status summary at the top flipped from 7/8 to 8/8 RESOLVED. Header text updated to "Launch-readiness P0 checklist is empty." P0 #6 row in the table flips from PARTIAL to RESOLVED with the two-convoy lineage (fix-auth-bypass Brief 4 + add-rate-limiting). Trailing paragraph rewritten as a milestone note: security gate closed; remaining launch work is P1 quality bar + P2/P3 polish. - P0 #6 entry flipped from PARTIAL to RESOLVED with the full add-rate-limiting as-shipped block (8 sub-bullets covering the lib refactor shape, the per-class table, the defensive THROW, the three import routes' auth-gating, the atomic admin UI fix and WHY, the rule extension, the 6 decisions, and the diff breakdown). Brief 4's 2026-05-23 partial is preserved as the prior as-shipped layer to maintain the audit trail. - Launch sequence step 4 marked RESOLVED 2026-05-24 with the squash commit + smoke metrics inline. - Queued convoys: removed the add-rate-limiting entry (it shipped). Added a new delete-dead-lorcana-import entry (P3 polish; the Lorcana import route was gated defensively in PR #20 despite zero current frontend callers — pages/admin/card-import.js's <select> only offers mtg + pokemon — so if Lorcana stays permanently out of the admin UI, this is the cleanup PR). Added three "flagged but kept out of scope" follow-ups per the convoy's § What did NOT change: harden-multipart-parser (P2; 5MB body still consumed before the 429 path on avatar.js), god-function-split / refactor-cards-search-sql (P2; 240-line 7-branch SQL in cards/search.js), and withAdmin(handler) wrapper extraction (P3 DX; the three import routes are call sites #3-5 in the codebase but uniform inline shape was preserved for convoy atomicity). Updated tighten-visual-diff-path-filter to note PR #20 also tripped the same false-positive. AGENTS.md: - Gotcha #12 extended end-to-end. Was the single-class auth-only lib + the env-var contract; is now the 5-class reality with a full per-class table (helper / limit-window / key / routes), the defensive THROW pattern in extractUserIdentifier, the gate-ordering rule for per-user limiters, and the auth → admin-role → rate-limit ordering for the three import routes. Prominent milestone line opens the new content: "add-rate-limiting convoy (squash 708ef45, PR #20, 2026-05-24) closed P0 #6 — all 8 P0s now RESOLVED." Original env-var contract paragraph (KV_REST_API_URL / KV_REST_API_TOKEN, fail-closed-in-prod / warn-and-noop-in-dev) is preserved verbatim above the new content. - § 6 Testing: intentionally untouched (no test surface changed; vitest 21/21 and smoke 3/3 still apply). - § 7 Deployment: intentionally untouched (no deployment-shape changed; same KV_REST_API_* env vars from Brief 4). .cursor/rules/api-routes.mdc: - The implementer extended § Rate limiting in PR #20 with the per-class table + verbatim call shape + gate-ordering rules + identifier-extraction + uniform 429 + fail-closed env-var contract + fail-open Upstash-outage behavior. Doc-writer pass verified completeness; added a one-sentence convoy-attribution line at the top of § Rate limiting citing the two-convoy lineage (fix-auth-bypass Brief 4 for the auth class + add-rate-limiting for the other four classes and 7 newly-gated routes), mirroring the post-cors-tighten § CORS attribution shape. No other touch-ups needed. No changes to: package.json, package-lock.json, lib/rate-limit.js, pages/**, components/**, scripts/**, test/**, tests/**, .github/workflows/**, README.md, TESTING_GUIDE.md, playwright.config.js, eslint.config.mjs. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 00:09:07 -04:00
All six return the same `{ allowed, remaining, reset }` shape; on `!allowed`, set `Retry-After: Math.ceil((reset - Date.now()) / 1000)` and return 429 with the uniform message `'Too many attempts. Try again later.'` (per-class variation would fingerprint the limits to an attacker — explicitly rejected).
docs: post-convoy cleanup for add-rate-limiting — MILESTONE, last P0 closed Reflects the merged add-rate-limiting convoy (PR #20, squash commit 708ef45) in repo documentation. **This is the milestone cleanup** — add-rate-limiting closed P0 #6 (No rate limiting anywhere), the LAST open P0 ship-blocker. `.convoys/ship-readiness.md`'s § Status summary flips from "7 of 8 RESOLVED; 1 remains" to **"8 of 8 RESOLVED. Launch-readiness P0 checklist is empty."** One brief in the convoy: Brief 1 shipped as planned with no scope expansions and no implementer deviations from the verbatim spec; all six architect decisions ratified verbatim at gate 1 (D1 operator-ratified Option A; D2-D6 architect-self-ratified). .convoys/add-rate-limiting.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section. Opens with the milestone language pointing back at ship-readiness.md's flipped § Status summary. Decisions section captures all 6 ratifications (D1 operator- ratified Option A — Critical: WHY the atomic admin UI fix in pages/admin/card-import.js was Decision 1's hidden coupling requirement, since API gating alone would have broken every "Import Cards" click; D2 hybrid named-limiter shape with Map<className, Ratelimit> cache; D3 per-class table including the two D3 tuning-evidence raises — search 30 -> 60/min because ShareModal.handleSearch has no debounce so a 17-char email = 16 requests in <5s, and generate kept at 5/hour because DiceBear is free not paid AI; D4 two-extractor shape with defensive THROW on null/empty userId; D5 uniform 429 message; D6 no new vitest or playwright specs deferred to fill-vitest-handler-coverage). As-shipped surface broken into 4 layers (1 lib refactor + 6 route gates + 1 atomic admin UI fix + 1 rule extension) mirroring the cors-tighten cleanup's pattern-split shape. Empirical CI metrics from post-merge run 26382185019 (Playwright smoke 59s 3/3 in 3.8s, forbidden-cors-headers pass, vitest 21/21, lint 128 baseline, Screenshot diff continue-on-error swallow per Decision 4). Cross-validation finding: smoke test 2 still passes against the post-rate-limit preview — that's three convoys in a row (PR #15 Layout default-user, PR #19 CORS-tighten, PR #20 rate-limiting) where the same 3-test smoke spec defended the auth surface through sweeping changes. Operator-action-required: none. What did NOT change audit trail. .convoys/ship-readiness.md: - § Status summary at the top flipped from 7/8 to 8/8 RESOLVED. Header text updated to "Launch-readiness P0 checklist is empty." P0 #6 row in the table flips from PARTIAL to RESOLVED with the two-convoy lineage (fix-auth-bypass Brief 4 + add-rate-limiting). Trailing paragraph rewritten as a milestone note: security gate closed; remaining launch work is P1 quality bar + P2/P3 polish. - P0 #6 entry flipped from PARTIAL to RESOLVED with the full add-rate-limiting as-shipped block (8 sub-bullets covering the lib refactor shape, the per-class table, the defensive THROW, the three import routes' auth-gating, the atomic admin UI fix and WHY, the rule extension, the 6 decisions, and the diff breakdown). Brief 4's 2026-05-23 partial is preserved as the prior as-shipped layer to maintain the audit trail. - Launch sequence step 4 marked RESOLVED 2026-05-24 with the squash commit + smoke metrics inline. - Queued convoys: removed the add-rate-limiting entry (it shipped). Added a new delete-dead-lorcana-import entry (P3 polish; the Lorcana import route was gated defensively in PR #20 despite zero current frontend callers — pages/admin/card-import.js's <select> only offers mtg + pokemon — so if Lorcana stays permanently out of the admin UI, this is the cleanup PR). Added three "flagged but kept out of scope" follow-ups per the convoy's § What did NOT change: harden-multipart-parser (P2; 5MB body still consumed before the 429 path on avatar.js), god-function-split / refactor-cards-search-sql (P2; 240-line 7-branch SQL in cards/search.js), and withAdmin(handler) wrapper extraction (P3 DX; the three import routes are call sites #3-5 in the codebase but uniform inline shape was preserved for convoy atomicity). Updated tighten-visual-diff-path-filter to note PR #20 also tripped the same false-positive. AGENTS.md: - Gotcha #12 extended end-to-end. Was the single-class auth-only lib + the env-var contract; is now the 5-class reality with a full per-class table (helper / limit-window / key / routes), the defensive THROW pattern in extractUserIdentifier, the gate-ordering rule for per-user limiters, and the auth → admin-role → rate-limit ordering for the three import routes. Prominent milestone line opens the new content: "add-rate-limiting convoy (squash 708ef45, PR #20, 2026-05-24) closed P0 #6 — all 8 P0s now RESOLVED." Original env-var contract paragraph (KV_REST_API_URL / KV_REST_API_TOKEN, fail-closed-in-prod / warn-and-noop-in-dev) is preserved verbatim above the new content. - § 6 Testing: intentionally untouched (no test surface changed; vitest 21/21 and smoke 3/3 still apply). - § 7 Deployment: intentionally untouched (no deployment-shape changed; same KV_REST_API_* env vars from Brief 4). .cursor/rules/api-routes.mdc: - The implementer extended § Rate limiting in PR #20 with the per-class table + verbatim call shape + gate-ordering rules + identifier-extraction + uniform 429 + fail-closed env-var contract + fail-open Upstash-outage behavior. Doc-writer pass verified completeness; added a one-sentence convoy-attribution line at the top of § Rate limiting citing the two-convoy lineage (fix-auth-bypass Brief 4 for the auth class + add-rate-limiting for the other four classes and 7 newly-gated routes), mirroring the post-cors-tighten § CORS attribution shape. No other touch-ups needed. No changes to: package.json, package-lock.json, lib/rate-limit.js, pages/**, components/**, scripts/**, test/**, tests/**, .github/workflows/**, README.md, TESTING_GUIDE.md, playwright.config.js, eslint.config.mjs. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 00:09:07 -04:00
**Defensive THROW pattern.** `extractUserIdentifier(userId)` THROWS with a named error when `userId` is `null` / `undefined` / `''` / `NaN`. Surfaces gate-ordering bugs at dev time rather than silently falling back to IP and converting a per-user limit into a per-IP limit (which would lock household members out for one user's behavior). Numeric `0` is intentionally accepted (returns `'user:0'`) for forward-compat. **Gate-ordering rule: per-user rate-limit gates (`upload`, `generate`, `import`) MUST sit AFTER the auth check.** For the two `/api/cards/import-*` routes, the ordering is also `auth → admin-role check (403 if not admin) → rate-limit`; the admin-role check sits between auth and rate-limit. IP-keyed gates (`auth`, `search`) can sit anywhere after the method check.
docs: post-convoy cleanup for add-rate-limiting — MILESTONE, last P0 closed Reflects the merged add-rate-limiting convoy (PR #20, squash commit 708ef45) in repo documentation. **This is the milestone cleanup** — add-rate-limiting closed P0 #6 (No rate limiting anywhere), the LAST open P0 ship-blocker. `.convoys/ship-readiness.md`'s § Status summary flips from "7 of 8 RESOLVED; 1 remains" to **"8 of 8 RESOLVED. Launch-readiness P0 checklist is empty."** One brief in the convoy: Brief 1 shipped as planned with no scope expansions and no implementer deviations from the verbatim spec; all six architect decisions ratified verbatim at gate 1 (D1 operator-ratified Option A; D2-D6 architect-self-ratified). .convoys/add-rate-limiting.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section. Opens with the milestone language pointing back at ship-readiness.md's flipped § Status summary. Decisions section captures all 6 ratifications (D1 operator- ratified Option A — Critical: WHY the atomic admin UI fix in pages/admin/card-import.js was Decision 1's hidden coupling requirement, since API gating alone would have broken every "Import Cards" click; D2 hybrid named-limiter shape with Map<className, Ratelimit> cache; D3 per-class table including the two D3 tuning-evidence raises — search 30 -> 60/min because ShareModal.handleSearch has no debounce so a 17-char email = 16 requests in <5s, and generate kept at 5/hour because DiceBear is free not paid AI; D4 two-extractor shape with defensive THROW on null/empty userId; D5 uniform 429 message; D6 no new vitest or playwright specs deferred to fill-vitest-handler-coverage). As-shipped surface broken into 4 layers (1 lib refactor + 6 route gates + 1 atomic admin UI fix + 1 rule extension) mirroring the cors-tighten cleanup's pattern-split shape. Empirical CI metrics from post-merge run 26382185019 (Playwright smoke 59s 3/3 in 3.8s, forbidden-cors-headers pass, vitest 21/21, lint 128 baseline, Screenshot diff continue-on-error swallow per Decision 4). Cross-validation finding: smoke test 2 still passes against the post-rate-limit preview — that's three convoys in a row (PR #15 Layout default-user, PR #19 CORS-tighten, PR #20 rate-limiting) where the same 3-test smoke spec defended the auth surface through sweeping changes. Operator-action-required: none. What did NOT change audit trail. .convoys/ship-readiness.md: - § Status summary at the top flipped from 7/8 to 8/8 RESOLVED. Header text updated to "Launch-readiness P0 checklist is empty." P0 #6 row in the table flips from PARTIAL to RESOLVED with the two-convoy lineage (fix-auth-bypass Brief 4 + add-rate-limiting). Trailing paragraph rewritten as a milestone note: security gate closed; remaining launch work is P1 quality bar + P2/P3 polish. - P0 #6 entry flipped from PARTIAL to RESOLVED with the full add-rate-limiting as-shipped block (8 sub-bullets covering the lib refactor shape, the per-class table, the defensive THROW, the three import routes' auth-gating, the atomic admin UI fix and WHY, the rule extension, the 6 decisions, and the diff breakdown). Brief 4's 2026-05-23 partial is preserved as the prior as-shipped layer to maintain the audit trail. - Launch sequence step 4 marked RESOLVED 2026-05-24 with the squash commit + smoke metrics inline. - Queued convoys: removed the add-rate-limiting entry (it shipped). Added a new delete-dead-lorcana-import entry (P3 polish; the Lorcana import route was gated defensively in PR #20 despite zero current frontend callers — pages/admin/card-import.js's <select> only offers mtg + pokemon — so if Lorcana stays permanently out of the admin UI, this is the cleanup PR). Added three "flagged but kept out of scope" follow-ups per the convoy's § What did NOT change: harden-multipart-parser (P2; 5MB body still consumed before the 429 path on avatar.js), god-function-split / refactor-cards-search-sql (P2; 240-line 7-branch SQL in cards/search.js), and withAdmin(handler) wrapper extraction (P3 DX; the three import routes are call sites #3-5 in the codebase but uniform inline shape was preserved for convoy atomicity). Updated tighten-visual-diff-path-filter to note PR #20 also tripped the same false-positive. AGENTS.md: - Gotcha #12 extended end-to-end. Was the single-class auth-only lib + the env-var contract; is now the 5-class reality with a full per-class table (helper / limit-window / key / routes), the defensive THROW pattern in extractUserIdentifier, the gate-ordering rule for per-user limiters, and the auth → admin-role → rate-limit ordering for the three import routes. Prominent milestone line opens the new content: "add-rate-limiting convoy (squash 708ef45, PR #20, 2026-05-24) closed P0 #6 — all 8 P0s now RESOLVED." Original env-var contract paragraph (KV_REST_API_URL / KV_REST_API_TOKEN, fail-closed-in-prod / warn-and-noop-in-dev) is preserved verbatim above the new content. - § 6 Testing: intentionally untouched (no test surface changed; vitest 21/21 and smoke 3/3 still apply). - § 7 Deployment: intentionally untouched (no deployment-shape changed; same KV_REST_API_* env vars from Brief 4). .cursor/rules/api-routes.mdc: - The implementer extended § Rate limiting in PR #20 with the per-class table + verbatim call shape + gate-ordering rules + identifier-extraction + uniform 429 + fail-closed env-var contract + fail-open Upstash-outage behavior. Doc-writer pass verified completeness; added a one-sentence convoy-attribution line at the top of § Rate limiting citing the two-convoy lineage (fix-auth-bypass Brief 4 for the auth class + add-rate-limiting for the other four classes and 7 newly-gated routes), mirroring the post-cors-tighten § CORS attribution shape. No other touch-ups needed. No changes to: package.json, package-lock.json, lib/rate-limit.js, pages/**, components/**, scripts/**, test/**, tests/**, .github/workflows/**, README.md, TESTING_GUIDE.md, playwright.config.js, eslint.config.mjs. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 00:09:07 -04:00
Adding another class is a one-line `LIMITER_CONFIG` addition + one new exported function (no `init()` restructuring needed). Tuning an existing class is a one-line `LIMITER_CONFIG` edit. The full verbatim call shape + gate-ordering rules + identifier-extraction documentation live in `.cursor/rules/api-routes.mdc` § Rate limiting.
docs: post-convoy cleanup for add-rate-limiting — MILESTONE, last P0 closed Reflects the merged add-rate-limiting convoy (PR #20, squash commit 708ef45) in repo documentation. **This is the milestone cleanup** — add-rate-limiting closed P0 #6 (No rate limiting anywhere), the LAST open P0 ship-blocker. `.convoys/ship-readiness.md`'s § Status summary flips from "7 of 8 RESOLVED; 1 remains" to **"8 of 8 RESOLVED. Launch-readiness P0 checklist is empty."** One brief in the convoy: Brief 1 shipped as planned with no scope expansions and no implementer deviations from the verbatim spec; all six architect decisions ratified verbatim at gate 1 (D1 operator-ratified Option A; D2-D6 architect-self-ratified). .convoys/add-rate-limiting.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section. Opens with the milestone language pointing back at ship-readiness.md's flipped § Status summary. Decisions section captures all 6 ratifications (D1 operator- ratified Option A — Critical: WHY the atomic admin UI fix in pages/admin/card-import.js was Decision 1's hidden coupling requirement, since API gating alone would have broken every "Import Cards" click; D2 hybrid named-limiter shape with Map<className, Ratelimit> cache; D3 per-class table including the two D3 tuning-evidence raises — search 30 -> 60/min because ShareModal.handleSearch has no debounce so a 17-char email = 16 requests in <5s, and generate kept at 5/hour because DiceBear is free not paid AI; D4 two-extractor shape with defensive THROW on null/empty userId; D5 uniform 429 message; D6 no new vitest or playwright specs deferred to fill-vitest-handler-coverage). As-shipped surface broken into 4 layers (1 lib refactor + 6 route gates + 1 atomic admin UI fix + 1 rule extension) mirroring the cors-tighten cleanup's pattern-split shape. Empirical CI metrics from post-merge run 26382185019 (Playwright smoke 59s 3/3 in 3.8s, forbidden-cors-headers pass, vitest 21/21, lint 128 baseline, Screenshot diff continue-on-error swallow per Decision 4). Cross-validation finding: smoke test 2 still passes against the post-rate-limit preview — that's three convoys in a row (PR #15 Layout default-user, PR #19 CORS-tighten, PR #20 rate-limiting) where the same 3-test smoke spec defended the auth surface through sweeping changes. Operator-action-required: none. What did NOT change audit trail. .convoys/ship-readiness.md: - § Status summary at the top flipped from 7/8 to 8/8 RESOLVED. Header text updated to "Launch-readiness P0 checklist is empty." P0 #6 row in the table flips from PARTIAL to RESOLVED with the two-convoy lineage (fix-auth-bypass Brief 4 + add-rate-limiting). Trailing paragraph rewritten as a milestone note: security gate closed; remaining launch work is P1 quality bar + P2/P3 polish. - P0 #6 entry flipped from PARTIAL to RESOLVED with the full add-rate-limiting as-shipped block (8 sub-bullets covering the lib refactor shape, the per-class table, the defensive THROW, the three import routes' auth-gating, the atomic admin UI fix and WHY, the rule extension, the 6 decisions, and the diff breakdown). Brief 4's 2026-05-23 partial is preserved as the prior as-shipped layer to maintain the audit trail. - Launch sequence step 4 marked RESOLVED 2026-05-24 with the squash commit + smoke metrics inline. - Queued convoys: removed the add-rate-limiting entry (it shipped). Added a new delete-dead-lorcana-import entry (P3 polish; the Lorcana import route was gated defensively in PR #20 despite zero current frontend callers — pages/admin/card-import.js's <select> only offers mtg + pokemon — so if Lorcana stays permanently out of the admin UI, this is the cleanup PR). Added three "flagged but kept out of scope" follow-ups per the convoy's § What did NOT change: harden-multipart-parser (P2; 5MB body still consumed before the 429 path on avatar.js), god-function-split / refactor-cards-search-sql (P2; 240-line 7-branch SQL in cards/search.js), and withAdmin(handler) wrapper extraction (P3 DX; the three import routes are call sites #3-5 in the codebase but uniform inline shape was preserved for convoy atomicity). Updated tighten-visual-diff-path-filter to note PR #20 also tripped the same false-positive. AGENTS.md: - Gotcha #12 extended end-to-end. Was the single-class auth-only lib + the env-var contract; is now the 5-class reality with a full per-class table (helper / limit-window / key / routes), the defensive THROW pattern in extractUserIdentifier, the gate-ordering rule for per-user limiters, and the auth → admin-role → rate-limit ordering for the three import routes. Prominent milestone line opens the new content: "add-rate-limiting convoy (squash 708ef45, PR #20, 2026-05-24) closed P0 #6 — all 8 P0s now RESOLVED." Original env-var contract paragraph (KV_REST_API_URL / KV_REST_API_TOKEN, fail-closed-in-prod / warn-and-noop-in-dev) is preserved verbatim above the new content. - § 6 Testing: intentionally untouched (no test surface changed; vitest 21/21 and smoke 3/3 still apply). - § 7 Deployment: intentionally untouched (no deployment-shape changed; same KV_REST_API_* env vars from Brief 4). .cursor/rules/api-routes.mdc: - The implementer extended § Rate limiting in PR #20 with the per-class table + verbatim call shape + gate-ordering rules + identifier-extraction + uniform 429 + fail-closed env-var contract + fail-open Upstash-outage behavior. Doc-writer pass verified completeness; added a one-sentence convoy-attribution line at the top of § Rate limiting citing the two-convoy lineage (fix-auth-bypass Brief 4 for the auth class + add-rate-limiting for the other four classes and 7 newly-gated routes), mirroring the post-cors-tighten § CORS attribution shape. No other touch-ups needed. No changes to: package.json, package-lock.json, lib/rate-limit.js, pages/**, components/**, scripts/**, test/**, tests/**, .github/workflows/**, README.md, TESTING_GUIDE.md, playwright.config.js, eslint.config.mjs. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 00:09:07 -04:00
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
## 5. Running locally
- **Runtime:** Node 20+ (`"type": "module"` — ESM everywhere).
- **Setup:** `npm install`, create `.env.local` with `POSTGRES_URL` (+ `POSTGRES_URL_DIRECT` for migrations) pointing at CT 102 or any Postgres 17, plus `JWT_SECRET` + `ADMIN_INITIAL_PASSWORD` (required for `npm run setup-db`, which exits with code 1 if unset). Optionally `REDIS_URL` to exercise the rate limiter locally — without it, `lib/rate-limit.js` warn-and-no-ops in dev (production fails closed) — and the `S3_*` MinIO vars for scan-capture uploads. Then `npm run setup-db` once. Full env contract in README § Installation and `docs/DOKPLOY_DEPLOY.md`.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
- **Dev server:** `npm run dev` → http://localhost:3000.
## 6. Testing
- **Unit-test runner:** `vitest@^3.2.4` (installed via `fix-auth-bypass` Brief 5, commit `1629afb`). `npm test` for watch mode; `npm run test:run` for the CI / single-shot mode. Config in `vitest.config.js`, setup in `test/setup.js` (sets `JWT_SECRET` + `NODE_ENV=test` before any module loads, and stubs `ResizeObserver` for jsdom). Specs live under `test/` mirroring source layout (`test/lib/*.test.js`, `test/api/*.test.js`, `test/components/*.test.js`). Last green: 231/231 tests across 43 files (2026-08-23).
- **Vitest coverage today:** 231 unit tests spanning auth (`lib/auth-secret.js`, `lib/permission-middleware.js::getUserFromRequest` incl. the negative regression against the old synthetic-admin shape — Gotcha #2), `pages/api/auth-utils.js`, Layout logged-out regressions (Gotcha #8), scanner libs/hooks/components (`use-scanner-identification`, `use-camera-scanner`, `ScannerCamera`, scanner page), card import + reconcile helpers, and catalog sync. The original fix-auth-bypass / fix-layout-default-user contract tests are still present — do not weaken them when refactoring auth or Layout.
docs: post-convoy cleanup for adopt-playwright-smoke Reflects the merged adopt-playwright-smoke convoy (PR #18, squash commit 7b6f751) in repo documentation. Closes the test-infrastructure side of P1 #10 step 2 (launch sequence step 10). One commit in the convoy: Brief 1, with two small lint-baseline-preserving deviations from the brief's verbatim shape that the implementer report flagged. .convoys/adopt-playwright-smoke.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: operator-ratified Decisions (D1 keep .ts, D4 defer baselines, D6 simple scripts); two implementer deviations (removed unused eslint-disable-no-console directive that would have regressed lint 128 -> 129; placed @playwright/test first in devDeps for alphabetical correctness); cross-validation that smoke test 2 ("sign-in page renders") locks in PR #15's logged-out CTA work in components/Layout.js; empirical metrics from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s, 0-leak); operator-action-required note pointing at the queued seed-visual-baselines-on-linux follow-up; What did NOT change audit trail. .convoys/ship-readiness.md: - Queued convoys: new entry seed-visual-baselines-on-linux (Linux- Docker baseline generation per Decision 4 + Boot-the-brief Finding 7; Mac-generated baselines would silently overwrite Linux CI baselines because the custom snapshotPathTemplate has no {platform} token). - Queued convoys: new RESOLVED block for adopt-playwright-smoke (PR #18, 7b6f751) — as-shipped surface, implementer deviations, empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks), the PR #15 cross-validation finding, operator-action-required going forward (the seed-visual-baselines-on-linux follow-up), flagged-but-deferred items, and ownership trail (3 architect- self-ratified decisions + 3 operator-ratified). - Launch sequence step 10: marked RESOLVED 2026-05-24 with the commit + metrics inline. - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the convoy + metrics ref; step 3 (re-enable test: job in ci.yml) called out as the next remaining task; step 5 (wire preview-smoke.yml to the Vercel preview URL) marked RESOLVED across PR #17 + PR #18 since both contributed. AGENTS.md: - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner still pending"; is now "@playwright/test@^1.60.0 wired, two projects (smoke + visual), npm run test:smoke / test:visual / test:visual:update". Documents the local-run convention (boot next dev separately, then BASE_URL=... npm run test:smoke); the one-time npx playwright install --with-deps chromium step; the no-baselines-yet state + the Linux-Docker seed command + the cross-platform mismatch reason (no {platform} token in snapshotPathTemplate); the CI behavior split (vitest blocking, smoke on every PR with pipeline:skip-smoke escape hatch, Screenshot diff path-filtered with the first-red-on-missing- baseline state documented). Updates vitest coverage count 16 -> 21 (the +5 Layout regression-lock tests from PR #15). Notes TESTING_GUIDE.md is being eclipsed and will be renamed to docs/MANUAL_QA.md in a future cleanup convoy. - Section 7 Deployment: rewrites the Vercel-bypass paragraph from a single "query param now / header reserved for future" bullet into a two-shape audit ((1) query param on the wait-action's path: input per PR #17; (2) HTTP header in playwright.config.js's use.extraHTTPHeaders per PR #18). Documents the Decision-2 fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12 as the established precedent (lib/rate-limit.js). Picked Section 7 over a new Gotcha because the bypass plumbing is operationally a deployment concern, not an app pitfall. No changes to: package.json, package-lock.json, playwright.config.js, eslint.config.mjs, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md, tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no future-tense references to clean up). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:33:43 -04:00
- **E2E / smoke runner:** `@playwright/test@^1.60.0` (installed via `adopt-playwright-smoke`, PR #18 squash `7b6f751`). Config in `playwright.config.js` (root, ESM) declares two projects:
- `smoke``tests/smoke/**/*.spec.@(ts|js)`; invoked by `.github/workflows/preview-smoke.yml`. `npm run test:smoke` locally.
- `visual``tests/visual/**/*.spec.@(ts|js)`; invoked by `.github/workflows/visual-diff.yml`. `npm run test:visual` locally; `npm run test:visual:update` to (re-)seed baselines.
Local-run convention: boot `next dev` in one terminal, then in another run `BASE_URL=http://localhost:3000 npm run test:smoke`. CI defaults `BASE_URL` to the homelab deployment (`https://deckhearth.stillwell.cloud`) via `vars.SMOKE_BASE_URL`; a legacy Vercel-preview target still works with `BASE_URL=https://<preview>.vercel.app VERCEL_AUTOMATION_BYPASS_SECRET=<value>` but is pending decommission (§ 7). No `next dev` auto-boot in the test scripts (Decision 6 of `adopt-playwright-smoke`).
docs: post-convoy cleanup for adopt-playwright-smoke Reflects the merged adopt-playwright-smoke convoy (PR #18, squash commit 7b6f751) in repo documentation. Closes the test-infrastructure side of P1 #10 step 2 (launch sequence step 10). One commit in the convoy: Brief 1, with two small lint-baseline-preserving deviations from the brief's verbatim shape that the implementer report flagged. .convoys/adopt-playwright-smoke.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: operator-ratified Decisions (D1 keep .ts, D4 defer baselines, D6 simple scripts); two implementer deviations (removed unused eslint-disable-no-console directive that would have regressed lint 128 -> 129; placed @playwright/test first in devDeps for alphabetical correctness); cross-validation that smoke test 2 ("sign-in page renders") locks in PR #15's logged-out CTA work in components/Layout.js; empirical metrics from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s, 0-leak); operator-action-required note pointing at the queued seed-visual-baselines-on-linux follow-up; What did NOT change audit trail. .convoys/ship-readiness.md: - Queued convoys: new entry seed-visual-baselines-on-linux (Linux- Docker baseline generation per Decision 4 + Boot-the-brief Finding 7; Mac-generated baselines would silently overwrite Linux CI baselines because the custom snapshotPathTemplate has no {platform} token). - Queued convoys: new RESOLVED block for adopt-playwright-smoke (PR #18, 7b6f751) — as-shipped surface, implementer deviations, empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks), the PR #15 cross-validation finding, operator-action-required going forward (the seed-visual-baselines-on-linux follow-up), flagged-but-deferred items, and ownership trail (3 architect- self-ratified decisions + 3 operator-ratified). - Launch sequence step 10: marked RESOLVED 2026-05-24 with the commit + metrics inline. - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the convoy + metrics ref; step 3 (re-enable test: job in ci.yml) called out as the next remaining task; step 5 (wire preview-smoke.yml to the Vercel preview URL) marked RESOLVED across PR #17 + PR #18 since both contributed. AGENTS.md: - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner still pending"; is now "@playwright/test@^1.60.0 wired, two projects (smoke + visual), npm run test:smoke / test:visual / test:visual:update". Documents the local-run convention (boot next dev separately, then BASE_URL=... npm run test:smoke); the one-time npx playwright install --with-deps chromium step; the no-baselines-yet state + the Linux-Docker seed command + the cross-platform mismatch reason (no {platform} token in snapshotPathTemplate); the CI behavior split (vitest blocking, smoke on every PR with pipeline:skip-smoke escape hatch, Screenshot diff path-filtered with the first-red-on-missing- baseline state documented). Updates vitest coverage count 16 -> 21 (the +5 Layout regression-lock tests from PR #15). Notes TESTING_GUIDE.md is being eclipsed and will be renamed to docs/MANUAL_QA.md in a future cleanup convoy. - Section 7 Deployment: rewrites the Vercel-bypass paragraph from a single "query param now / header reserved for future" bullet into a two-shape audit ((1) query param on the wait-action's path: input per PR #17; (2) HTTP header in playwright.config.js's use.extraHTTPHeaders per PR #18). Documents the Decision-2 fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12 as the established precedent (lib/rate-limit.js). Picked Section 7 over a new Gotcha because the bypass plumbing is operationally a deployment concern, not an app pitfall. No changes to: package.json, package-lock.json, playwright.config.js, eslint.config.mjs, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md, tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no future-tense references to clean up). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:33:43 -04:00
- **Browsers must be installed once locally:** `npx playwright install --with-deps chromium`. CI re-runs this on every workflow run (it's cached when possible).
chore(docs): sweep stale visual-baseline references + queue gate-hardening convoy (#135) PR #58 (83a358b, 2026-06-02) committed the first Linux visual baseline, resolving the seed-visual-baselines-on-linux convoy. But the cleanup sweep across docs that referenced the convoy as "queued / not yet done" never landed. Three files still describe the world as if PR #58 hadn't happened, which confuses any agent reading the codebase to understand the visual-diff pipeline: 1. tests/visual/homepage.spec.ts module docblock — described "FIRST RUN (no committed baseline yet)" and "SEEDING THE BASELINE (post-merge follow-up)" as the active state. 2. playwright.config.js snapshotPathTemplate comment — said "Per Decision 4, we don't commit baselines this convoy" and pointed at the queued seed convoy as future work. 3. AGENTS.md § Testing § Visual baselines + § CI behavior § Screenshot diff — claimed "none committed yet" and that the first visual-diff run "will fail at the test step because no baseline exists yet." Sweeping all three to describe the current reality. The Mac-vs-Linux platform footgun (snapshotPathTemplate has no {platform} token) is still live, so that warning stays — just rephrased from "don't have baselines yet" to "don't regenerate them on a Mac." Also surfaces a separate finding the seed work left behind: visual-diff.yml's screenshot capture step still carries `continue-on-error: true`, making the diff advisory rather than gating. Flipping it requires re-seeding the baseline against post-glass-redesign main first (the PR #58 baseline predates unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette + migrate-button-input-mobilenav-to-glass-primitive). New convoy seed at .convoys/harden-visual-diff-gate.md captures the two-step shape (re-seed baseline, then flip the gate) plus the recommended workflow_dispatch approach for repeatable re-seeding on CT 111. No code behavior changes. Documentation + .md convoy seed only. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 21:30:34 -04:00
- **Visual baselines:** committed under `tests/visual/__screenshots__/`. The initial Linux baseline (`home.png`) was seeded by PR #58 (`83a358b`, 2026-06-02). Baselines are committed to git — they are not gitignored — so a `Screenshot diff` failure is reviewable from PR comments + artifacts without bouncing through a regeneration step. **Re-seeding** (when the homepage changes intentionally) MUST happen in a Linux environment so the PNG matches what CI produces. Recommended paths:
docs: post-convoy cleanup for adopt-playwright-smoke Reflects the merged adopt-playwright-smoke convoy (PR #18, squash commit 7b6f751) in repo documentation. Closes the test-infrastructure side of P1 #10 step 2 (launch sequence step 10). One commit in the convoy: Brief 1, with two small lint-baseline-preserving deviations from the brief's verbatim shape that the implementer report flagged. .convoys/adopt-playwright-smoke.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: operator-ratified Decisions (D1 keep .ts, D4 defer baselines, D6 simple scripts); two implementer deviations (removed unused eslint-disable-no-console directive that would have regressed lint 128 -> 129; placed @playwright/test first in devDeps for alphabetical correctness); cross-validation that smoke test 2 ("sign-in page renders") locks in PR #15's logged-out CTA work in components/Layout.js; empirical metrics from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s, 0-leak); operator-action-required note pointing at the queued seed-visual-baselines-on-linux follow-up; What did NOT change audit trail. .convoys/ship-readiness.md: - Queued convoys: new entry seed-visual-baselines-on-linux (Linux- Docker baseline generation per Decision 4 + Boot-the-brief Finding 7; Mac-generated baselines would silently overwrite Linux CI baselines because the custom snapshotPathTemplate has no {platform} token). - Queued convoys: new RESOLVED block for adopt-playwright-smoke (PR #18, 7b6f751) — as-shipped surface, implementer deviations, empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks), the PR #15 cross-validation finding, operator-action-required going forward (the seed-visual-baselines-on-linux follow-up), flagged-but-deferred items, and ownership trail (3 architect- self-ratified decisions + 3 operator-ratified). - Launch sequence step 10: marked RESOLVED 2026-05-24 with the commit + metrics inline. - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the convoy + metrics ref; step 3 (re-enable test: job in ci.yml) called out as the next remaining task; step 5 (wire preview-smoke.yml to the Vercel preview URL) marked RESOLVED across PR #17 + PR #18 since both contributed. AGENTS.md: - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner still pending"; is now "@playwright/test@^1.60.0 wired, two projects (smoke + visual), npm run test:smoke / test:visual / test:visual:update". Documents the local-run convention (boot next dev separately, then BASE_URL=... npm run test:smoke); the one-time npx playwright install --with-deps chromium step; the no-baselines-yet state + the Linux-Docker seed command + the cross-platform mismatch reason (no {platform} token in snapshotPathTemplate); the CI behavior split (vitest blocking, smoke on every PR with pipeline:skip-smoke escape hatch, Screenshot diff path-filtered with the first-red-on-missing- baseline state documented). Updates vitest coverage count 16 -> 21 (the +5 Layout regression-lock tests from PR #15). Notes TESTING_GUIDE.md is being eclipsed and will be renamed to docs/MANUAL_QA.md in a future cleanup convoy. - Section 7 Deployment: rewrites the Vercel-bypass paragraph from a single "query param now / header reserved for future" bullet into a two-shape audit ((1) query param on the wait-action's path: input per PR #17; (2) HTTP header in playwright.config.js's use.extraHTTPHeaders per PR #18). Documents the Decision-2 fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12 as the established precedent (lib/rate-limit.js). Picked Section 7 over a new Gotcha because the bypass plumbing is operationally a deployment concern, not an app pitfall. No changes to: package.json, package-lock.json, playwright.config.js, eslint.config.mjs, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md, tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no future-tense references to clean up). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:33:43 -04:00
chore(docs): sweep stale visual-baseline references + queue gate-hardening convoy (#135) PR #58 (83a358b, 2026-06-02) committed the first Linux visual baseline, resolving the seed-visual-baselines-on-linux convoy. But the cleanup sweep across docs that referenced the convoy as "queued / not yet done" never landed. Three files still describe the world as if PR #58 hadn't happened, which confuses any agent reading the codebase to understand the visual-diff pipeline: 1. tests/visual/homepage.spec.ts module docblock — described "FIRST RUN (no committed baseline yet)" and "SEEDING THE BASELINE (post-merge follow-up)" as the active state. 2. playwright.config.js snapshotPathTemplate comment — said "Per Decision 4, we don't commit baselines this convoy" and pointed at the queued seed convoy as future work. 3. AGENTS.md § Testing § Visual baselines + § CI behavior § Screenshot diff — claimed "none committed yet" and that the first visual-diff run "will fail at the test step because no baseline exists yet." Sweeping all three to describe the current reality. The Mac-vs-Linux platform footgun (snapshotPathTemplate has no {platform} token) is still live, so that warning stays — just rephrased from "don't have baselines yet" to "don't regenerate them on a Mac." Also surfaces a separate finding the seed work left behind: visual-diff.yml's screenshot capture step still carries `continue-on-error: true`, making the diff advisory rather than gating. Flipping it requires re-seeding the baseline against post-glass-redesign main first (the PR #58 baseline predates unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette + migrate-button-input-mobilenav-to-glass-primitive). New convoy seed at .convoys/harden-visual-diff-gate.md captures the two-step shape (re-seed baseline, then flip the gate) plus the recommended workflow_dispatch approach for repeatable re-seeding on CT 111. No code behavior changes. Documentation + .md convoy seed only. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 21:30:34 -04:00
1. **Playwright Docker image** (works from any host):
docs: post-convoy cleanup for adopt-playwright-smoke Reflects the merged adopt-playwright-smoke convoy (PR #18, squash commit 7b6f751) in repo documentation. Closes the test-infrastructure side of P1 #10 step 2 (launch sequence step 10). One commit in the convoy: Brief 1, with two small lint-baseline-preserving deviations from the brief's verbatim shape that the implementer report flagged. .convoys/adopt-playwright-smoke.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: operator-ratified Decisions (D1 keep .ts, D4 defer baselines, D6 simple scripts); two implementer deviations (removed unused eslint-disable-no-console directive that would have regressed lint 128 -> 129; placed @playwright/test first in devDeps for alphabetical correctness); cross-validation that smoke test 2 ("sign-in page renders") locks in PR #15's logged-out CTA work in components/Layout.js; empirical metrics from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s, 0-leak); operator-action-required note pointing at the queued seed-visual-baselines-on-linux follow-up; What did NOT change audit trail. .convoys/ship-readiness.md: - Queued convoys: new entry seed-visual-baselines-on-linux (Linux- Docker baseline generation per Decision 4 + Boot-the-brief Finding 7; Mac-generated baselines would silently overwrite Linux CI baselines because the custom snapshotPathTemplate has no {platform} token). - Queued convoys: new RESOLVED block for adopt-playwright-smoke (PR #18, 7b6f751) — as-shipped surface, implementer deviations, empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks), the PR #15 cross-validation finding, operator-action-required going forward (the seed-visual-baselines-on-linux follow-up), flagged-but-deferred items, and ownership trail (3 architect- self-ratified decisions + 3 operator-ratified). - Launch sequence step 10: marked RESOLVED 2026-05-24 with the commit + metrics inline. - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the convoy + metrics ref; step 3 (re-enable test: job in ci.yml) called out as the next remaining task; step 5 (wire preview-smoke.yml to the Vercel preview URL) marked RESOLVED across PR #17 + PR #18 since both contributed. AGENTS.md: - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner still pending"; is now "@playwright/test@^1.60.0 wired, two projects (smoke + visual), npm run test:smoke / test:visual / test:visual:update". Documents the local-run convention (boot next dev separately, then BASE_URL=... npm run test:smoke); the one-time npx playwright install --with-deps chromium step; the no-baselines-yet state + the Linux-Docker seed command + the cross-platform mismatch reason (no {platform} token in snapshotPathTemplate); the CI behavior split (vitest blocking, smoke on every PR with pipeline:skip-smoke escape hatch, Screenshot diff path-filtered with the first-red-on-missing- baseline state documented). Updates vitest coverage count 16 -> 21 (the +5 Layout regression-lock tests from PR #15). Notes TESTING_GUIDE.md is being eclipsed and will be renamed to docs/MANUAL_QA.md in a future cleanup convoy. - Section 7 Deployment: rewrites the Vercel-bypass paragraph from a single "query param now / header reserved for future" bullet into a two-shape audit ((1) query param on the wait-action's path: input per PR #17; (2) HTTP header in playwright.config.js's use.extraHTTPHeaders per PR #18). Documents the Decision-2 fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12 as the established precedent (lib/rate-limit.js). Picked Section 7 over a new Gotcha because the bypass plumbing is operationally a deployment concern, not an app pitfall. No changes to: package.json, package-lock.json, playwright.config.js, eslint.config.mjs, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md, tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no future-tense references to clean up). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:33:43 -04:00
chore(docs): sweep stale visual-baseline references + queue gate-hardening convoy (#135) PR #58 (83a358b, 2026-06-02) committed the first Linux visual baseline, resolving the seed-visual-baselines-on-linux convoy. But the cleanup sweep across docs that referenced the convoy as "queued / not yet done" never landed. Three files still describe the world as if PR #58 hadn't happened, which confuses any agent reading the codebase to understand the visual-diff pipeline: 1. tests/visual/homepage.spec.ts module docblock — described "FIRST RUN (no committed baseline yet)" and "SEEDING THE BASELINE (post-merge follow-up)" as the active state. 2. playwright.config.js snapshotPathTemplate comment — said "Per Decision 4, we don't commit baselines this convoy" and pointed at the queued seed convoy as future work. 3. AGENTS.md § Testing § Visual baselines + § CI behavior § Screenshot diff — claimed "none committed yet" and that the first visual-diff run "will fail at the test step because no baseline exists yet." Sweeping all three to describe the current reality. The Mac-vs-Linux platform footgun (snapshotPathTemplate has no {platform} token) is still live, so that warning stays — just rephrased from "don't have baselines yet" to "don't regenerate them on a Mac." Also surfaces a separate finding the seed work left behind: visual-diff.yml's screenshot capture step still carries `continue-on-error: true`, making the diff advisory rather than gating. Flipping it requires re-seeding the baseline against post-glass-redesign main first (the PR #58 baseline predates unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette + migrate-button-input-mobilenav-to-glass-primitive). New convoy seed at .convoys/harden-visual-diff-gate.md captures the two-step shape (re-seed baseline, then flip the gate) plus the recommended workflow_dispatch approach for repeatable re-seeding on CT 111. No code behavior changes. Documentation + .md convoy seed only. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 21:30:34 -04:00
```
docker run --rm -v "$PWD":/work -w /work \
mcr.microsoft.com/playwright:v1.60.0-noble \
sh -c "npm ci && BASE_URL=<preview-url> \
VERCEL_AUTOMATION_BYPASS_SECRET=<value> \
npm run test:visual:update"
```
2. **CT 111 directly** (preferred when iterating — same toolchain as the diff workflow, byte-equivalent output). Dispatched via the (queued) `seed-visual-baselines` workflow once it lands; until then, `pct exec 111 -- docker exec gha-runner-1 sh -c "...`" works ad-hoc.
Mac-generated baselines will NOT match Linux CI — `playwright.config.js`'s custom `snapshotPathTemplate` has no `{platform}` token, so a Mac update silently overwrites the canonical Linux baseline. **Never run `npm run test:visual:update` on a Mac** unless you immediately throw the result away.
convoy: flip visual-diff to a hard merge gate (harden-visual-diff-gate brief 2/2) (#140) Removes `continue-on-error: true` from `.github/workflows/visual-diff.yml`'s `Capture screenshots (PR)` step. Visual drift is now a real merge gate on UI-touching PRs. Brief 2/2 of the `harden-visual-diff-gate` convoy. PR #138 shipped the seed workflow (Brief 1); PR #139 (`54495fe`) landed the fresh Linux baseline regenerated against post-glass-redesign main on CT 111. With a known-good baseline committed, the gate can flip without false-failing every UI-touching PR. ## What changes - `.github/workflows/visual-diff.yml` — drop the `continue-on-error: true` flag; add an inline rationale block linking to the convoy + the operator runbook for both intentional changes (dispatch seed workflow → manually open PR → merge → re-run) and unintentional regressions (inspect artifact diff → fix → push). - `.github/workflows/ci.yml` — add 9th `forbidden-patterns` check that greps `visual-diff.yml` for `^\s*continue-on-error:\s*true` and fails the build if it returns. Risk #3 of the convoy made concrete: prevents silent re-introduction via template revert. Scoped narrowly to that one file; other workflows (`seed-visual-baselines.yml`'s PR-open step, etc.) legitimately use the flag. Job name bumped from "Forbidden patterns (8 checks)" → "(9 checks)". All `Check N/8` group labels renumbered to `N/9`. - `AGENTS.md` — § Testing § Visual baselines rewritten to drop the "Known staleness as of 2026-06-12" callout (resolved by PR #139); § Testing § Screenshot diff rewritten to lead with "hard merge gate", document the intentional-change runbook, reference the new ci.yml check, and explicitly mention the org-setting caveat for the seed workflow's auto-PR step. - `tests/visual/homepage.spec.ts` — module docblock rewritten to match the AGENTS.md runbook: drops the "advisory, not gating" language; promotes the seed-visual-baselines workflow as the primary re-seeding path; demotes the Playwright Docker image to the offline fallback. - `.github/workflows/seed-visual-baselines.yml` — patches the `peter-evans/create-pull-request@v6` PR-open failure case discovered during Brief 1's first dispatch (run 27454132468). The PR-open step is now `continue-on-error: true` (narrowly scoped, with an inline rationale callout distinguishing it from the just-removed `visual-diff.yml` flag — that one silently hid real UI regressions; this one fronts a known org-level "Allow GitHub Actions to create and approve pull requests" limitation with a loud failure notice). New steps disambiguate the three possible outcomes (no-changes / pr-opened / branch-pushed-pr-blocked) via a `git ls-remote` check on the bot branch and exit non-zero on the blocked-PR case so the workflow run shows red and the operator gets the exact `gh pr create` command in the run logs. - `.convoys/harden-visual-diff-gate.md` — status: shipping; Step 2 marked SHIPPED; Decision D4 ratified (chose option C: accept org setting, document manual `gh pr create` fallback). Inline links to PR #139 + PR #140. ## Test plan - [x] `npm run lint` — clean (1 pre-existing unrelated warning) - [x] `npm run test:run` — 24 files / 118 tests pass - [ ] CI on this PR: 9th forbidden-patterns check passes; visual-diff job passes against the fresh baseline; convoy-metrics-gate passes (2 new rows added by this commit) - [ ] After merge: smoke test the 9th check by opening a throwaway PR that re-adds `continue-on-error: true` to `visual-diff.yml`; confirm it red-X's. (Skip if confident in the grep.) ## Convoy state - Brief 1: SHIPPED (PR #138, `c100c5f`, 2026-06-13) - Baseline refresh: SHIPPED (PR #139, `54495fe`, 2026-06-13) - Brief 2 (this PR): shipping - Convoy closeout: this PR's merge Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 23:03:17 -04:00
**Current baseline:** refreshed against post-glass-redesign `main` via PR #139 (`54495fe`, 2026-06-13), generated on CT 111 against the `c100c5f` production deployment. Diff is a hard merge gate post-`harden-visual-diff-gate` brief 2 — see the next bullet.
docs: post-convoy cleanup for adopt-playwright-smoke Reflects the merged adopt-playwright-smoke convoy (PR #18, squash commit 7b6f751) in repo documentation. Closes the test-infrastructure side of P1 #10 step 2 (launch sequence step 10). One commit in the convoy: Brief 1, with two small lint-baseline-preserving deviations from the brief's verbatim shape that the implementer report flagged. .convoys/adopt-playwright-smoke.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: operator-ratified Decisions (D1 keep .ts, D4 defer baselines, D6 simple scripts); two implementer deviations (removed unused eslint-disable-no-console directive that would have regressed lint 128 -> 129; placed @playwright/test first in devDeps for alphabetical correctness); cross-validation that smoke test 2 ("sign-in page renders") locks in PR #15's logged-out CTA work in components/Layout.js; empirical metrics from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s, 0-leak); operator-action-required note pointing at the queued seed-visual-baselines-on-linux follow-up; What did NOT change audit trail. .convoys/ship-readiness.md: - Queued convoys: new entry seed-visual-baselines-on-linux (Linux- Docker baseline generation per Decision 4 + Boot-the-brief Finding 7; Mac-generated baselines would silently overwrite Linux CI baselines because the custom snapshotPathTemplate has no {platform} token). - Queued convoys: new RESOLVED block for adopt-playwright-smoke (PR #18, 7b6f751) — as-shipped surface, implementer deviations, empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks), the PR #15 cross-validation finding, operator-action-required going forward (the seed-visual-baselines-on-linux follow-up), flagged-but-deferred items, and ownership trail (3 architect- self-ratified decisions + 3 operator-ratified). - Launch sequence step 10: marked RESOLVED 2026-05-24 with the commit + metrics inline. - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the convoy + metrics ref; step 3 (re-enable test: job in ci.yml) called out as the next remaining task; step 5 (wire preview-smoke.yml to the Vercel preview URL) marked RESOLVED across PR #17 + PR #18 since both contributed. AGENTS.md: - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner still pending"; is now "@playwright/test@^1.60.0 wired, two projects (smoke + visual), npm run test:smoke / test:visual / test:visual:update". Documents the local-run convention (boot next dev separately, then BASE_URL=... npm run test:smoke); the one-time npx playwright install --with-deps chromium step; the no-baselines-yet state + the Linux-Docker seed command + the cross-platform mismatch reason (no {platform} token in snapshotPathTemplate); the CI behavior split (vitest blocking, smoke on every PR with pipeline:skip-smoke escape hatch, Screenshot diff path-filtered with the first-red-on-missing- baseline state documented). Updates vitest coverage count 16 -> 21 (the +5 Layout regression-lock tests from PR #15). Notes TESTING_GUIDE.md is being eclipsed and will be renamed to docs/MANUAL_QA.md in a future cleanup convoy. - Section 7 Deployment: rewrites the Vercel-bypass paragraph from a single "query param now / header reserved for future" bullet into a two-shape audit ((1) query param on the wait-action's path: input per PR #17; (2) HTTP header in playwright.config.js's use.extraHTTPHeaders per PR #18). Documents the Decision-2 fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12 as the established precedent (lib/rate-limit.js). Picked Section 7 over a new Gotcha because the bypass plumbing is operationally a deployment concern, not an app pitfall. No changes to: package.json, package-lock.json, playwright.config.js, eslint.config.mjs, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md, tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no future-tense references to clean up). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:33:43 -04:00
- **CI behavior:**
- **Vitest:** the `test:` job in `.github/workflows/ci.yml` runs `npm run test:run` on every PR and push to `main` and is **blocking** (no `|| true`, no `continue-on-error`). A red test job blocks merge.
convoy: forbidden-pattern gate + AGENTS.md docs (briefs 3+4) (#133) * convoy: forbidden-pattern gate + docs (briefs 3+4) Closes out the migrate-ci-to-self-hosted convoy with the two defensive follow-ups Brief 1+2 (PR #132) intentionally deferred. Brief 3 — Check 8 of `forbidden-patterns` in ci.yml. Greps `.github/workflows/` for `runs-on: ubuntu-latest` and fails unless the match is in the documented allowlist (currently `agent-context-drift.yml` only, per Decision D4). Self-tested locally against the post-migration tree: 0 violations. Renames the job from "Forbidden patterns (7 checks)" → "(8 checks)" and normalizes the older "Check N/6" labels to "N/8" for consistency (the inherited mix of `/6` and `/7` was a known cosmetic from the unify-glass-panel-surfaces convoy). Brief 4 — AGENTS.md § 6 and § 7 updates: - § 6 "CI behavior": Playwright smoke runtime range updated to cover post-migration cold vs. warm cache (was a stale 59s figure from pre-migration ubuntu-latest). - § 6 new top-level bullet "Self-hosted runner pool" alongside "CI minute optimizations" — covers where runners live, where caches are bind-mounted on CT 111, the Postgres rewire on CT 102, and the agent-context-drift.yml exemption + how Check 8 enforces it. - § 7 new bullet for the operational story: PAT rotation cadence + the D5 one-line `sed` revert path for when axiom is offline mid-PR-storm. Cross-references the axiom-server CT 111 README and the Beszel down alert. Convoy doc — status flipped queued → shipped, shipped_in lists both PRs, and follow_ups makes the cleanup-stale-ci-runs-cron + seed-visual-baselines-on-linux items machine-greppable. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: drop placeholder comment now PR #133 number is known Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 01:18:21 -04:00
- **Playwright smoke:** runs on every PR via `preview-smoke.yml`. Gate skip via `pipeline: skip smoke` in the PR body (handled in the `gate:` job's Decide step via env-var routing — see § 7's shell-injection note). Pre-migration runtime: 59s end-to-end on `ubuntu-latest` (PR #18 post-merge run). Post-migration on the axiom pool: cold-cache first run ~6 min (Chromium download); warm cache thereafter ~12 min.
convoy: flip visual-diff to a hard merge gate (harden-visual-diff-gate brief 2/2) (#140) Removes `continue-on-error: true` from `.github/workflows/visual-diff.yml`'s `Capture screenshots (PR)` step. Visual drift is now a real merge gate on UI-touching PRs. Brief 2/2 of the `harden-visual-diff-gate` convoy. PR #138 shipped the seed workflow (Brief 1); PR #139 (`54495fe`) landed the fresh Linux baseline regenerated against post-glass-redesign main on CT 111. With a known-good baseline committed, the gate can flip without false-failing every UI-touching PR. ## What changes - `.github/workflows/visual-diff.yml` — drop the `continue-on-error: true` flag; add an inline rationale block linking to the convoy + the operator runbook for both intentional changes (dispatch seed workflow → manually open PR → merge → re-run) and unintentional regressions (inspect artifact diff → fix → push). - `.github/workflows/ci.yml` — add 9th `forbidden-patterns` check that greps `visual-diff.yml` for `^\s*continue-on-error:\s*true` and fails the build if it returns. Risk #3 of the convoy made concrete: prevents silent re-introduction via template revert. Scoped narrowly to that one file; other workflows (`seed-visual-baselines.yml`'s PR-open step, etc.) legitimately use the flag. Job name bumped from "Forbidden patterns (8 checks)" → "(9 checks)". All `Check N/8` group labels renumbered to `N/9`. - `AGENTS.md` — § Testing § Visual baselines rewritten to drop the "Known staleness as of 2026-06-12" callout (resolved by PR #139); § Testing § Screenshot diff rewritten to lead with "hard merge gate", document the intentional-change runbook, reference the new ci.yml check, and explicitly mention the org-setting caveat for the seed workflow's auto-PR step. - `tests/visual/homepage.spec.ts` — module docblock rewritten to match the AGENTS.md runbook: drops the "advisory, not gating" language; promotes the seed-visual-baselines workflow as the primary re-seeding path; demotes the Playwright Docker image to the offline fallback. - `.github/workflows/seed-visual-baselines.yml` — patches the `peter-evans/create-pull-request@v6` PR-open failure case discovered during Brief 1's first dispatch (run 27454132468). The PR-open step is now `continue-on-error: true` (narrowly scoped, with an inline rationale callout distinguishing it from the just-removed `visual-diff.yml` flag — that one silently hid real UI regressions; this one fronts a known org-level "Allow GitHub Actions to create and approve pull requests" limitation with a loud failure notice). New steps disambiguate the three possible outcomes (no-changes / pr-opened / branch-pushed-pr-blocked) via a `git ls-remote` check on the bot branch and exit non-zero on the blocked-PR case so the workflow run shows red and the operator gets the exact `gh pr create` command in the run logs. - `.convoys/harden-visual-diff-gate.md` — status: shipping; Step 2 marked SHIPPED; Decision D4 ratified (chose option C: accept org setting, document manual `gh pr create` fallback). Inline links to PR #139 + PR #140. ## Test plan - [x] `npm run lint` — clean (1 pre-existing unrelated warning) - [x] `npm run test:run` — 24 files / 118 tests pass - [ ] CI on this PR: 9th forbidden-patterns check passes; visual-diff job passes against the fresh baseline; convoy-metrics-gate passes (2 new rows added by this commit) - [ ] After merge: smoke test the 9th check by opening a throwaway PR that re-adds `continue-on-error: true` to `visual-diff.yml`; confirm it red-X's. (Skip if confident in the grep.) ## Convoy state - Brief 1: SHIPPED (PR #138, `c100c5f`, 2026-06-13) - Baseline refresh: SHIPPED (PR #139, `54495fe`, 2026-06-13) - Brief 2 (this PR): shipping - Convoy closeout: this PR's merge Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 23:03:17 -04:00
- **Screenshot diff:** runs only on PRs touching `pages/**` / `components/**` / `styles/**` / `tailwind.config.js` / `postcss.config.js` (and `tests/visual/**` for baseline updates) via `visual-diff.yml`. **It is now a hard merge gate** post-`harden-visual-diff-gate` brief 2 (PR #140, 2026-06-13) — `continue-on-error: true` was removed. A failed diff blocks merge.
- **Intentional UI change?** Dispatch the seeding workflow first: `gh workflow run seed-visual-baselines.yml -f base_url=<preview-url> -f reason="..."`. It pushes a `bot/visual-baselines-<run_id>` branch with a refreshed `home.png`. The auto-PR-open step **fails** because stwl-labs has "Allow GitHub Actions to create and approve pull requests" disabled at the org level (Settings → Actions → General → Workflow permissions); the operator runs `gh pr create --base main --head bot/visual-baselines-<run_id> ...` manually. Merge the baseline PR, then re-run the UI-touching PR's visual diff.
- **Unintentional regression?** Open the run's artifact bundle, inspect the diff PNG, fix the regression in source, push.
- **Forbidden re-introduction:** `ci.yml`'s `forbidden-patterns` job's 9th check fails any PR that re-adds `continue-on-error: true` to `visual-diff.yml`.
ci: slash GitHub Actions minutes via paths-ignore + consolidation + caching (#126) Standalone infrastructure PR (no convoy ceremony needed — single-file scope). Triggered by the GitHub Actions billing block that gated PRs #124 + #125 today. Three layers of savings applied per the user's max-savings option: 1. paths-ignore on ci.yml + preview-smoke.yml - Doc-only PRs (.convoys/**, **/*.md, docs/**, AGENTS.md, .cursor/**, README.md) now trigger ZERO Actions jobs. - Vercel still builds (it's not on the Actions billing). - visual-diff.yml unchanged — it was already cost-conscious via a positive paths: allowlist (pages/**, components/**, styles/**, etc.). 2. Consolidate 6 grep-only forbidden-* jobs into 1 - Previously 6 independent jobs each ran their own actions/checkout (~3s × 6 = 18s of redundant checkout). - Merged into a single forbidden-patterns job with 6 sequential ::group:: sections, one FAIL flag at the bottom — preserves "see all violations in one run" diagnostic behavior. Per-file ::error file=...::msg annotations work the same way. - Removed jobs: forbidden-endpoints, forbidden-cors-headers, forbidden-client-side-llm-keys, forbidden-modal-shell-without-primitive, forbidden-deprecated-color-aliases, forbidden-stale-strings. - pr-health-rollup.yml only looks up "Lint" and "Schema map up to date" by name — unaffected. 3. Cache node_modules + Playwright browsers - actions/cache@v4 for node_modules keyed by package-lock.json hash, applied to lint / test / migrate / preview-smoke / visual-diff. Cuts npm ci from ~30-45s to ~3-5s on cache hit. setup-node@v4's built-in cache: npm stays (caches ~/.npm) — both layered. - actions/cache@v4 for ~/.cache/ms-playwright keyed by the resolved @playwright/test version. Cache invalidates on any Playwright version bump. On cache hit, only system deps install runs (npx playwright install-deps chromium) — saves ~15-25s/run. AGENTS.md updates: - § 6 Testing § CI behavior: appended "CI minute optimizations" subsection documenting all three layers. - § Product vocabulary table caption: updated "CI job forbidden-stale-strings" reference to "CI check Forbidden patterns (6 checks) → Check 6/6" with a historical pointer. - Gotcha #5: updated the standalone forbidden-endpoints reference similarly. Estimated savings per typical convoy mix (~30% doc PRs based on repo history): - Doc-only PRs: 100% reduction (was ~6-7 min, now 0 Actions min). - Code-touching PRs: ~30-50% reduction (cache hits for npm + Playwright + no redundant 6× checkout). - Weighted average: ~50-60% reduction. This is short of the 70-80% I floated in chat — the real ceiling is limited by lint / vitest / migrate / Playwright runtime itself, all of which are kept on code-touching PRs (they're high-signal). Verification: - All 3 workflow YAMLs parse (python3 -c "yaml.safe_load(...)"). - npm run lint passes (1 pre-existing unrelated warning). - npm run test:run: 118/118 tests pass. - forbidden-patterns logic is byte-equivalent to the 6 original jobs' bash bodies — the differences are: per-check ::group::/::endgroup:: framing, a shared FAIL flag instead of per-job exit 1, and renamed local arrays (FOUND → LLM_FOUND / MODAL_FOUND / STRING_FOUND) to avoid clobbering across the single job's scope. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 17:40:36 -04:00
- **CI minute optimizations (slash-ci-minutes convoy, 2026-06-04):**
- **Doc-only PRs skip ALL of ci.yml + preview-smoke.yml.** Both workflows carry `paths-ignore` for `.convoys/**`, `**/*.md`, `docs/**`, `AGENTS.md`, `.cursor/**`, and `README.md`. A pure-docs PR triggers zero GitHub Actions jobs (Vercel still builds — it's not on the Actions billing). `visual-diff.yml` was already cost-conscious via a positive `paths:` allowlist and is unchanged.
- **The 6 grep-only forbidden-* jobs collapsed into one.** They previously ran as 6 independent jobs (each with its own `actions/checkout`); the consolidated `forbidden-patterns` job runs all 6 checks as labeled `::group::` sections in a single bash step, with a FAIL flag at the bottom so every violation across all 6 checks still surfaces in one run (same diagnostic behavior, ~5/6 of the per-PR checkout overhead removed). The 6 original job names (`forbidden-endpoints`, `forbidden-cors-headers`, `forbidden-client-side-llm-keys`, `forbidden-modal-shell-without-primitive`, `forbidden-deprecated-color-aliases`, `forbidden-stale-strings`) no longer appear in the checks list — references in this file (e.g. CI job `forbidden-stale-strings` blocks ...) are now informational, not check-name lookups. `pr-health-rollup.yml` was unaffected because it only looks up `Lint` and `Schema map up to date` by name.
- **`node_modules` cached between runs** in lint / test / migrate / preview-smoke / visual-diff. Keyed on `package-lock.json` hash so any dep change invalidates correctly. Cuts `npm ci` from ~30-45s to ~3-5s on cache hit. `actions/setup-node@v4`'s built-in `cache: npm` is layered above this (caches `~/.npm`) — both stay because the `setup-node` cache helps on cache-miss days too.
- **Playwright browsers cached** in `preview-smoke.yml` + `visual-diff.yml`. Keyed on the resolved `@playwright/test` version from `package-lock.json`. Cache invalidates automatically on any Playwright version bump. On cache hit, only the system deps install (`npx playwright install-deps chromium`) runs — saves ~15-25s/run.
convoy: forbidden-pattern gate + AGENTS.md docs (briefs 3+4) (#133) * convoy: forbidden-pattern gate + docs (briefs 3+4) Closes out the migrate-ci-to-self-hosted convoy with the two defensive follow-ups Brief 1+2 (PR #132) intentionally deferred. Brief 3 — Check 8 of `forbidden-patterns` in ci.yml. Greps `.github/workflows/` for `runs-on: ubuntu-latest` and fails unless the match is in the documented allowlist (currently `agent-context-drift.yml` only, per Decision D4). Self-tested locally against the post-migration tree: 0 violations. Renames the job from "Forbidden patterns (7 checks)" → "(8 checks)" and normalizes the older "Check N/6" labels to "N/8" for consistency (the inherited mix of `/6` and `/7` was a known cosmetic from the unify-glass-panel-surfaces convoy). Brief 4 — AGENTS.md § 6 and § 7 updates: - § 6 "CI behavior": Playwright smoke runtime range updated to cover post-migration cold vs. warm cache (was a stale 59s figure from pre-migration ubuntu-latest). - § 6 new top-level bullet "Self-hosted runner pool" alongside "CI minute optimizations" — covers where runners live, where caches are bind-mounted on CT 111, the Postgres rewire on CT 102, and the agent-context-drift.yml exemption + how Check 8 enforces it. - § 7 new bullet for the operational story: PAT rotation cadence + the D5 one-line `sed` revert path for when axiom is offline mid-PR-storm. Cross-references the axiom-server CT 111 README and the Beszel down alert. Convoy doc — status flipped queued → shipped, shipped_in lists both PRs, and follow_ups makes the cleanup-stale-ci-runs-cron + seed-visual-baselines-on-linux items machine-greppable. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: drop placeholder comment now PR #133 number is known Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 01:18:21 -04:00
- **Self-hosted runner pool (migrate-ci-to-self-hosted convoy, 2026-06-05):**
- **4 of 5 workflows run on the axiom homelab.** `ci.yml`, `preview-smoke.yml`, `visual-diff.yml`, `pr-health-rollup.yml` use `runs-on: [self-hosted, axiom]` and execute on CT 111 in the `axiom-server` Proxmox homelab (`axiom-runner-1..4`, registered org-scoped to `stwl-labs`, ephemeral one-job-per-container via `myoung34/github-runner`). Net effect: tcg-vault CI no longer consumes GitHub Actions minutes for those four workflows.
- **`agent-context-drift.yml` deliberately stays on `ubuntu-latest`** per Decision D4 of the convoy — it's a weekly cron, costs ~2 min/month, and must run even when axiom is down for maintenance. **Check 8 of `forbidden-patterns` enforces this** as a strict allowlist (anything else reintroducing `runs-on: ubuntu-latest` fails CI).
- **Cache mounts live on the CT 111 host** and are bind-mounted into every runner container, so they survive across the ephemeral-runner lifecycle and are shared across `axiom-runner-1..4`. Paths (on CT 111): `/opt/appdata/gha-runner/shared-cache/{npm,pnpm,yarn,pip,playwright,buildx}` and the per-runner workdirs under `/opt/appdata/gha-runner/runner-N/`. The `actions/cache@v4` keys above still apply on top — the bind mounts just keep the underlying tooling caches (`~/.npm`, `~/.cache/ms-playwright`) primed across jobs.
- **Migrate job uses CT 102 shared Postgres** instead of an in-runner `services.postgres` container. `HOMELAB_CI_POSTGRES_PASSWORD` repo secret (password only — `PGHOST`/`PGUSER`/`PGPORT` are hardcoded in `ci.yml`). Each run creates a per-run database named `ci_run_${run_id}_${run_attempt}` and drops it in an `if: always()` cleanup step so failed migrations don't leak DBs. The `deckhearth_ci` role has `CREATEDB` but no superuser; a compromised runner can't reach other apps' databases on CT 102.
- **Cross-references:** convoy decisions + risks in [`.convoys/migrate-ci-to-self-hosted.md`](.convoys/migrate-ci-to-self-hosted.md); homelab-side infra in `axiom-server/proxmox/ct111/README.md`; revert path in § 7 below.
docs: post-convoy cleanup for adopt-playwright-smoke Reflects the merged adopt-playwright-smoke convoy (PR #18, squash commit 7b6f751) in repo documentation. Closes the test-infrastructure side of P1 #10 step 2 (launch sequence step 10). One commit in the convoy: Brief 1, with two small lint-baseline-preserving deviations from the brief's verbatim shape that the implementer report flagged. .convoys/adopt-playwright-smoke.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: operator-ratified Decisions (D1 keep .ts, D4 defer baselines, D6 simple scripts); two implementer deviations (removed unused eslint-disable-no-console directive that would have regressed lint 128 -> 129; placed @playwright/test first in devDeps for alphabetical correctness); cross-validation that smoke test 2 ("sign-in page renders") locks in PR #15's logged-out CTA work in components/Layout.js; empirical metrics from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s, 0-leak); operator-action-required note pointing at the queued seed-visual-baselines-on-linux follow-up; What did NOT change audit trail. .convoys/ship-readiness.md: - Queued convoys: new entry seed-visual-baselines-on-linux (Linux- Docker baseline generation per Decision 4 + Boot-the-brief Finding 7; Mac-generated baselines would silently overwrite Linux CI baselines because the custom snapshotPathTemplate has no {platform} token). - Queued convoys: new RESOLVED block for adopt-playwright-smoke (PR #18, 7b6f751) — as-shipped surface, implementer deviations, empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks), the PR #15 cross-validation finding, operator-action-required going forward (the seed-visual-baselines-on-linux follow-up), flagged-but-deferred items, and ownership trail (3 architect- self-ratified decisions + 3 operator-ratified). - Launch sequence step 10: marked RESOLVED 2026-05-24 with the commit + metrics inline. - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the convoy + metrics ref; step 3 (re-enable test: job in ci.yml) called out as the next remaining task; step 5 (wire preview-smoke.yml to the Vercel preview URL) marked RESOLVED across PR #17 + PR #18 since both contributed. AGENTS.md: - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner still pending"; is now "@playwright/test@^1.60.0 wired, two projects (smoke + visual), npm run test:smoke / test:visual / test:visual:update". Documents the local-run convention (boot next dev separately, then BASE_URL=... npm run test:smoke); the one-time npx playwright install --with-deps chromium step; the no-baselines-yet state + the Linux-Docker seed command + the cross-platform mismatch reason (no {platform} token in snapshotPathTemplate); the CI behavior split (vitest blocking, smoke on every PR with pipeline:skip-smoke escape hatch, Screenshot diff path-filtered with the first-red-on-missing- baseline state documented). Updates vitest coverage count 16 -> 21 (the +5 Layout regression-lock tests from PR #15). Notes TESTING_GUIDE.md is being eclipsed and will be renamed to docs/MANUAL_QA.md in a future cleanup convoy. - Section 7 Deployment: rewrites the Vercel-bypass paragraph from a single "query param now / header reserved for future" bullet into a two-shape audit ((1) query param on the wait-action's path: input per PR #17; (2) HTTP header in playwright.config.js's use.extraHTTPHeaders per PR #18). Documents the Decision-2 fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12 as the established precedent (lib/rate-limit.js). Picked Section 7 over a new Gotcha because the bypass plumbing is operationally a deployment concern, not an app pitfall. No changes to: package.json, package-lock.json, playwright.config.js, eslint.config.mjs, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md, tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no future-tense references to clean up). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:33:43 -04:00
- **Manual QA:** `TESTING_GUIDE.md` still applies for flows not yet covered by automated tests (scanner camera path, card-import jobs, multi-step UI wizards). The automated smoke + visual suite is steadily eclipsing it; `TESTING_GUIDE.md` will be renamed to `docs/MANUAL_QA.md` and trimmed to truly-manual-only flows in a future cleanup convoy (see `.convoys/ship-readiness.md` § Role-doc-writer findings).
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
## 7. Deployment
> **Status (2026-08): production is the Dokploy homelab deployment** —
> app on CT 112, public URL `https://deckhearth.stillwell.cloud` via Traefik
> on CT 100, data plane on CT 102 (Postgres/Redis/MinIO). Runtime DB access
> goes through `lib/sql.js` (the `postgres` package); rate limiting reads
> `REDIS_URL`. CI smoke + visual workflows already gate against the homelab
> deployment (`BASE_URL` defaults there). `vercel.json` and `.vercel/` are
> removed from the tree. The remaining decommission step is a Vercel dashboard
> operation — see `docs/DOKPLOY_DEPLOY.md` § 6. Runbook:
> [`docs/DOKPLOY_DEPLOY.md`](docs/DOKPLOY_DEPLOY.md).
- **Vercel (legacy, decommissioned code-side).** The `vercel.json` /
`.vercel/` files are removed. The Dokploy deployment at
`deckhearth.stillwell.cloud` is the canonical production target. Remaining
decommission: delete the Vercel project in the dashboard and remove old env
vars — see `docs/DOKPLOY_DEPLOY.md` § 6.
docs: post-convoy cleanup for adopt-playwright-smoke Reflects the merged adopt-playwright-smoke convoy (PR #18, squash commit 7b6f751) in repo documentation. Closes the test-infrastructure side of P1 #10 step 2 (launch sequence step 10). One commit in the convoy: Brief 1, with two small lint-baseline-preserving deviations from the brief's verbatim shape that the implementer report flagged. .convoys/adopt-playwright-smoke.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: operator-ratified Decisions (D1 keep .ts, D4 defer baselines, D6 simple scripts); two implementer deviations (removed unused eslint-disable-no-console directive that would have regressed lint 128 -> 129; placed @playwright/test first in devDeps for alphabetical correctness); cross-validation that smoke test 2 ("sign-in page renders") locks in PR #15's logged-out CTA work in components/Layout.js; empirical metrics from post-merge run 26376162598 (59s workflow, 3/3 in 2.9s, 0-leak); operator-action-required note pointing at the queued seed-visual-baselines-on-linux follow-up; What did NOT change audit trail. .convoys/ship-readiness.md: - Queued convoys: new entry seed-visual-baselines-on-linux (Linux- Docker baseline generation per Decision 4 + Boot-the-brief Finding 7; Mac-generated baselines would silently overwrite Linux CI baselines because the custom snapshotPathTemplate has no {platform} token). - Queued convoys: new RESOLVED block for adopt-playwright-smoke (PR #18, 7b6f751) — as-shipped surface, implementer deviations, empirical metrics (59s workflow / 3/3 in 2.9s / 0 secret leaks), the PR #15 cross-validation finding, operator-action-required going forward (the seed-visual-baselines-on-linux follow-up), flagged-but-deferred items, and ownership trail (3 architect- self-ratified decisions + 3 operator-ratified). - Launch sequence step 10: marked RESOLVED 2026-05-24 with the commit + metrics inline. - P1 #10 No tests Fix sequence: step 2 marked RESOLVED with the convoy + metrics ref; step 3 (re-enable test: job in ci.yml) called out as the next remaining task; step 5 (wire preview-smoke.yml to the Vercel preview URL) marked RESOLVED across PR #17 + PR #18 since both contributed. AGENTS.md: - Section 6 Testing: rewritten end-to-end. Was "E2E/smoke runner still pending"; is now "@playwright/test@^1.60.0 wired, two projects (smoke + visual), npm run test:smoke / test:visual / test:visual:update". Documents the local-run convention (boot next dev separately, then BASE_URL=... npm run test:smoke); the one-time npx playwright install --with-deps chromium step; the no-baselines-yet state + the Linux-Docker seed command + the cross-platform mismatch reason (no {platform} token in snapshotPathTemplate); the CI behavior split (vitest blocking, smoke on every PR with pipeline:skip-smoke escape hatch, Screenshot diff path-filtered with the first-red-on-missing- baseline state documented). Updates vitest coverage count 16 -> 21 (the +5 Layout regression-lock tests from PR #15). Notes TESTING_GUIDE.md is being eclipsed and will be renamed to docs/MANUAL_QA.md in a future cleanup convoy. - Section 7 Deployment: rewrites the Vercel-bypass paragraph from a single "query param now / header reserved for future" bullet into a two-shape audit ((1) query param on the wait-action's path: input per PR #17; (2) HTTP header in playwright.config.js's use.extraHTTPHeaders per PR #18). Documents the Decision-2 fail-loud-in-CI / warn-in-dev predicate and references Gotcha #12 as the established precedent (lib/rate-limit.js). Picked Section 7 over a new Gotcha because the bypass plumbing is operationally a deployment concern, not an app pitfall. No changes to: package.json, package-lock.json, playwright.config.js, eslint.config.mjs, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md, tests/visual/homepage.spec.ts (JSDoc is already neutral-tense, no future-tense references to clean up). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 20:33:43 -04:00
- **Preview protection bypass for automation.** The project has a Protection Bypass for Automation token exposed locally as `VERCEL_AUTOMATION_BYPASS_SECRET` in `.env.local` (not committed) and seeded into GitHub Actions as a repo secret (`gh secret set VERCEL_AUTOMATION_BYPASS_SECRET`, 2026-05-24). The secret is consumed in two shapes:
1. **Query parameter** on `wait-for-vercel-preview@v1.3.2`'s `path:` input in both `preview-smoke.yml` and `visual-diff.yml``path: '/?x-vercel-protection-bypass=…'`, bare form, **without** `&x-vercel-set-bypass-cookie=true` (the cookie variant returns 307 + Set-Cookie and axios in Node has no cookie jar, so it 401s on the redirect). Plumbed by PR #17 (`fix-vercel-deployment-protection-in-ci`, squash `9a3e077`).
2. **HTTP header** in `playwright.config.js`'s `use.extraHTTPHeaders``'x-vercel-protection-bypass': <secret>`. Playwright's browser context has a real cookie jar so this shape works there, and the testOptions surface forwards the header to the test-level `request` fixture's `APIRequestContext` as well, so both `page.goto(...)` calls and `request.get('/api/health')` calls hit the protected preview correctly without per-spec header injection. Plumbed by PR #18 (`adopt-playwright-smoke`, squash `7b6f751`) per Decision 2 of that convoy.
Decision 2 also wires a **fail-loud-in-CI / warn-in-dev** predicate: `if (process.env.CI === 'true' && !process.env.VERCEL_AUTOMATION_BYPASS_SECRET) throw ...` (with an error message that names the env var, the `gh secret set` rotation command, and points at this section); otherwise `console.warn` once and continue with `extraHTTPHeaders` undefined. Same fail-closed / warn-and-no-op shape as `lib/rate-limit.js`'s Upstash predicate — see Gotcha #12.
Do not log or echo the value. If the operator rotates the token in the Vercel dashboard, re-seed the GitHub secret via `gh secret set VERCEL_AUTOMATION_BYPASS_SECRET --body "<new value>"`. See `.convoys/fix-vercel-deployment-protection-in-ci.md` and `.convoys/adopt-playwright-smoke.md`.
docs: post-convoy cleanup for fix-vercel-deployment-protection-in-ci Reflects the merged fix-vercel-deployment-protection-in-ci convoy (PR #17, squash commit 9a3e077) in repo documentation. Closes the CI-infra side-effect of P0 #7. Three commits in the convoy: Brief 1 (bypass plumbing per spec), plus two scope expansions discovered during CI validation (shell-injection hardening, and a Decision-A shape correction to drop the cookie-bypass param). .convoys/ship-readiness.md: - Queued convoys: mark fix-vercel-deployment-protection-in-ci as RESOLVED 2026-05-24 with 9a3e077. Document the 3-commit reality (365e9f0 Brief 1 bypass plumbing, b6f8688 shell-injection hardening of the gate Decide step, 043a6ee dropping &x-vercel-set-bypass-cookie=true), the empirical metrics (wait-action: 10-min timeout -> 194ms; workflow runtime: 10+ min -> 59s), the documented expected red on Playwright smoke (npx playwright test fails because playwright.config.js doesn't exist yet -- adopt-playwright-smoke owns that), and the operator-rotation caveat (R6). AGENTS.md: - Section 7 Deployment: correct the noun "header" -> "query param on wait-action's path:" since that's what actually landed per Decision A. Also document the without-cookie form (the cookie variant 401s through axios's missing cookie jar) and the operator re-seed runbook for token rotation. - Section 7 Deployment: fold in a one-liner about the GitHub Actions ${{ }}-in-shell-is-injection-vector pattern, with the env: + quoted-shell fix shape. Picked Section 7 over a new Gotcha #13 because the existing Gotchas list is dominated by app-level pitfalls (auth, SQL clients, ESLint), and CI YAML hardening is naturally co-located with deployment. .convoys/fix-vercel-deployment-protection-in-ci.md: - frontmatter status: in-progress -> shipped (added shipped: 2026-05-24) - new ## As-shipped section: 3-commit reality, Decision-A shape deviation (we shipped without &x-vercel-set-bypass-cookie=true), empirical timings (194ms wait, 59s total), remaining-red attribution to adopt-playwright-smoke, and the operator-rotation caveat. No changes to: package.json, lib/**, pages/**, components/**, scripts/**, .github/workflows/**, .cursor/rules/**, README.md. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 17:32:45 -04:00
- **Shell-injection hardening in workflow YAML.** Never inline `${{ github.event.* }}` directly into a `run:` block — route the value through the step's `env:` block and quote it (`"$VAR_NAME"`) in shell. PR #17's CI validation caught a real syntax error from a PR body containing `(` because the gate-job's Decide step inlined `${{ github.event.pull_request.body }}` straight into bash; commit `b6f8688` swept both `preview-smoke.yml` and `visual-diff.yml` to the `env:` + quoted-shell pattern. This is GitHub's official Security Hardening guidance ("Security hardening for GitHub Actions" → "Using a third-party action"). Apply to any new workflow that reads PR body / title / branch name / commit messages in shell.
- **CI runs on the axiom homelab (CT 111).** Four of the five workflows execute on `[self-hosted, axiom]` runners managed in the `axiom-server` repo (`proxmox/ct111/`). Day-to-day this is invisible — pushes still trigger jobs, and Dokploy builds `main` on CT 112 (Vercel preview builds continue only until phase 8 decommission) — but two operational notes matter:
convoy: forbidden-pattern gate + AGENTS.md docs (briefs 3+4) (#133) * convoy: forbidden-pattern gate + docs (briefs 3+4) Closes out the migrate-ci-to-self-hosted convoy with the two defensive follow-ups Brief 1+2 (PR #132) intentionally deferred. Brief 3 — Check 8 of `forbidden-patterns` in ci.yml. Greps `.github/workflows/` for `runs-on: ubuntu-latest` and fails unless the match is in the documented allowlist (currently `agent-context-drift.yml` only, per Decision D4). Self-tested locally against the post-migration tree: 0 violations. Renames the job from "Forbidden patterns (7 checks)" → "(8 checks)" and normalizes the older "Check N/6" labels to "N/8" for consistency (the inherited mix of `/6` and `/7` was a known cosmetic from the unify-glass-panel-surfaces convoy). Brief 4 — AGENTS.md § 6 and § 7 updates: - § 6 "CI behavior": Playwright smoke runtime range updated to cover post-migration cold vs. warm cache (was a stale 59s figure from pre-migration ubuntu-latest). - § 6 new top-level bullet "Self-hosted runner pool" alongside "CI minute optimizations" — covers where runners live, where caches are bind-mounted on CT 111, the Postgres rewire on CT 102, and the agent-context-drift.yml exemption + how Check 8 enforces it. - § 7 new bullet for the operational story: PAT rotation cadence + the D5 one-line `sed` revert path for when axiom is offline mid-PR-storm. Cross-references the axiom-server CT 111 README and the Beszel down alert. Convoy doc — status flipped queued → shipped, shipped_in lists both PRs, and follow_ups makes the cleanup-stale-ci-runs-cron + seed-visual-baselines-on-linux items machine-greppable. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: drop placeholder comment now PR #133 number is known Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 01:18:21 -04:00
1. **PAT rotation.** The runners authenticate to GitHub via an org-scoped PAT stored on CT 111 at `/opt/appdata/gha-runner/.env` (key `GH_PAT`, scopes `admin:org`, `repo`, `workflow`). Rotate every 90 days. After updating the value on CT 111, run `./proxmox/scripts/sync.sh restart 111` to re-register all 4 runners. If the PAT lapses silently, new jobs fail registration immediately; check `./proxmox/scripts/sync.sh logs 111 gha-runner-1` for `Http response code: NotFound` to confirm.
2. **1-line revert path (D5) — when axiom is offline mid-PR-storm.** If CT 111 is down for maintenance, hardware swap, or any reason, and a hot fix needs CI to land, swap every `[self-hosted, axiom]` back to `ubuntu-latest`:
```
sed -i '' 's/\[self-hosted, axiom\]/ubuntu-latest/g' .github/workflows/*.yml
# macOS sed needs the empty -i '' argument; on Linux it's `sed -i 's/...//g' ...`.
```
This re-bills GitHub Actions minutes for the duration of the outage. Commit the change directly to `main` (or to the affected PR's branch), let CI run, and revert the sed result once axiom is back. The `forbidden-patterns` Check 8 will block the next normal PR until the revert lands — that's intentional: the gate exists exactly to surface this drift, not to silently re-bill minutes for weeks. Beszel alerts on CT 111 down (axiom-server CT 101) so you usually know before a PR notices.
bootstrap: agent pipeline v0.5.0 + ship-readiness review Installs the three-layer agent-pipeline scaffold (https://github.com/varutasu/agent-pipeline @ v0.5.0): L1 — Context (curated brain) - AGENTS.md: orientation, conventions, 8 explicit gotchas - .cursor/rules/: no-go-zones, api-routes, auth-and-permissions, db-and-schema, ui-and-theming, schema-map - .cursor/skills/: add-api-route, add-page recipes - docs/agent-context/README.md: layer explainer - docs/SCHEMA_MAP.md: hand-curated Neon Postgres reference (replaces Prisma schema map since stack is raw SQL) L2 — Subagent roles (copied verbatim from upstream templates) - 9 .cursor/agents/role-*.md files: Conductor, IA-Architect, UX-Reviewer, Architect, Implementer, Reviewer, Design-System-Auditor, A11y-Auditor, Doc-Writer L3 — Pipeline scaffolding (Vercel variant) - CI: lint + schema-map-drift only (no duplicate build — Vercel handles it). Test job commented out until vitest lands. - preview-smoke + visual-diff via wait-for-vercel-preview - pr-health-rollup sticky comment aggregator - agent-context-drift weekly cron - PULL_REQUEST_TEMPLATE, CODEOWNERS (auth/admin paths tagged) - .convoys/ folder + seed ship-readiness.md review - lib/flags/index.js (JS — converted from TS template) - scripts/wt.sh (Cursor 3.2 deprecation stub), scripts/log-convoy-event.sh - tests/smoke/app.smoke.spec.ts (Playwright skeleton) Manifest - .agent-context-manifest.yml: tracks 31 artifacts by sha256 for future sync-agent-context drift detection Review - .convoys/ship-readiness.md: 16 findings (7 P0 ship-blockers, 5 P1 quality-bar, 4 P2 refactor, P3 UX/IA/a11y/docs) with proposed 13-convoy launch sequence. No production code changed in this commit. All findings in the ship-readiness review will be addressed in follow-up convoys starting with fix-auth-bypass. Structural brain: user-code-review-graph MCP has indexed the codebase (122 files, 628 nodes, 5602 edges, 11 communities, 84 flows). Per-developer; not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 00:16:08 -04:00
## 8. Code graph
A local code-knowledge-graph MCP server (`user-code-review-graph`) is set up for this repo. Ask "what calls X?" or "show me the flow from /api/auth/login" instead of grepping. See [`docs/agent-context/README.md`](docs/agent-context/README.md).