deckhearth/.convoys/.metrics.jsonl

119 lines
24 KiB
Text
Raw Normal View History

{"ts": "2026-05-23T04:18:51Z", "role": "role-conductor", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": ["ia", "ux", "visual", "a11y", "design"], "classification": "server-only", "duration_s": 0}
{"ts": "2026-05-23T04:44:56Z", "role": "role-conductor", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": ["ia", "ux", "flag"], "classification": "feature", "duration_s": 0}
{"ts": "2026-05-23T04:54:59Z", "role": "role-architect", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": ["ia", "ux", "flag"], "classification": "feature", "duration_s": 101}
{"ts": "2026-05-23T05:37:11Z", "role": "role-architect", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": ["ia", "ux", "flag"], "classification": "feature", "duration_s": 311, "outcome": "scope-expanded"}
{"ts": "2026-05-23T05:50:00Z", "role": "role-architect", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": ["ia", "ux", "flag"], "classification": "feature", "duration_s": 357, "outcome": "eslint-v10-pivot"}
{"ts": "2026-05-23T05:55:05Z", "role": "role-implementer", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 180}
{"ts": "2026-05-23T06:12:57Z", "role": "role-architect", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "duration_s": 319, "outcome": "typescript-devdep-add"}
{"ts": "2026-05-23T06:28:25Z", "role": "role-implementer", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 78}
{"ts": "2026-05-23T06:48:30Z", "role": "role-architect", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "duration_s": 347, "outcome": "eslint-v9-fallback-decision-d"}
{"ts": "2026-05-23T06:51:22Z", "role": "role-implementer", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 50}
{"ts": "2026-05-23T07:07:35Z", "role": "role-design-system-auditor", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "duration_s": 114, "multitask_group": "audit-bump-next-js-4"}
{"ts": "2026-05-23T07:08:15Z", "role": "role-a11y-auditor", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "multitask_group": "audit-bump-next-js-4"}
{"ts": "2026-05-23T07:08:55Z", "role": "role-reviewer", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 42, "multitask_group": "audit-bump-next-js-4"}
{"ts": "2026-05-23T07:34:56Z", "role": "role-doc-writer", "convoy": "bump-next-js", "repo": "tcg-vault", "skip_flags": [], "duration_s": 26, "outcome": "complete"}
{"ts": "2026-05-23T07:57:10Z", "role": "role-architect", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": ["ia", "ux", "visual", "a11y", "design"], "classification": "server-only", "duration_s": 900}
{"ts": "2026-05-23T14:44:38Z", "role": "role-implementer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 192}
{"ts": "2026-05-23T14:52:56Z", "role": "role-implementer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 3, "duration_s": 240}
{"ts": "2026-05-23T15:00:15Z", "role": "role-reviewer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 3, "duration_s": 129}
{"ts": "2026-05-23T15:52:10Z", "role": "role-reviewer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "duration_s": 209}
{"ts": "2026-05-23T15:56:43Z", "role": "role-reviewer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 4, "duration_s": 227}
{"ts": "2026-05-23T16:04:17Z", "role": "role-reviewer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 6, "duration_s": 34}
{"ts": "2026-05-23T16:10:21Z", "role": "role-reviewer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 5, "duration_s": 131}
{"ts": "2026-05-23T16:25:52Z", "role": "role-doc-writer", "convoy": "fix-auth-bypass", "repo": "tcg-vault", "skip_flags": [], "brief": 0, "duration_s": 724, "outcome": "complete"}
{"ts": "2026-05-23T17:31:18Z", "role": "role-architect", "convoy": "drop-public-setup", "repo": "tcg-vault", "skip_flags": [], "duration_s": 720}
{"ts": "2026-05-23T19:58:01Z", "role": "role-implementer", "convoy": "drop-public-setup", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 327}
{"ts": "2026-05-23T20:07:35Z", "role": "role-implementer", "convoy": "drop-public-setup", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "duration_s": 174}
{"ts": "2026-05-23T23:00:44Z", "role": "role-architect", "convoy": "fix-layout-default-user", "repo": "tcg-vault", "skip_flags": [], "duration_s": 1500}
{"ts": "2026-05-24T14:14:24Z", "role": "role-implementer", "convoy": "fix-layout-default-user", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 403}
{"ts": "2026-05-24T15:05:32Z", "role": "role-implementer", "convoy": "fix-layout-default-user", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "duration_s": 1800}
{"ts": "2026-05-24T19:37:56Z", "role": "role-doc-writer", "convoy": "fix-layout-default-user", "repo": "tcg-vault", "skip_flags": [], "duration_s": 242, "outcome": "complete"}
{"ts": "2026-05-24T20:26:29Z", "role": "role-implementer", "convoy": "fix-vercel-deployment-protection-in-ci", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 101}
{"ts": "2026-05-24T21:32:57Z", "role": "role-doc-writer", "convoy": "fix-vercel-deployment-protection-in-ci", "repo": "tcg-vault", "skip_flags": [], "duration_s": 207, "outcome": "complete"}
{"ts": "2026-05-24T23:44:57Z", "role": "role-implementer", "convoy": "adopt-playwright-smoke", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 310}
{"ts": "2026-05-25T01:25:55Z", "role": "role-implementer", "convoy": "cors-tighten", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 900}
{"ts": "2026-05-25T03:46:40Z", "role": "role-implementer", "convoy": "add-rate-limiting", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 600}
{"ts": "2026-05-27T12:55:50Z", "role": "role-conductor", "convoy": "scanner-audit-portfolio", "repo": "tcg-vault", "skip_flags": [], "classification": "docs-only", "duration_s": 0}
{"ts": "2026-05-27T19:09:46Z", "role": "role-reviewer", "convoy": "redesign-scanner-flow", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "outcome": "comment-only", "multitask_group": "audit-redesign-scanner-flow-44"}
{"ts": "2026-05-27T19:09:46Z", "role": "role-design-system-auditor", "convoy": "redesign-scanner-flow", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "outcome": "comment-only", "multitask_group": "audit-redesign-scanner-flow-44"}
{"ts": "2026-05-27T19:09:46Z", "role": "role-a11y-auditor", "convoy": "redesign-scanner-flow", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "outcome": "comment-only", "multitask_group": "audit-redesign-scanner-flow-44"}
{"ts": "2026-06-02T05:33:45Z", "role": "role-implementer", "convoy": "collection-vocabulary", "repo": "tcg-vault", "skip_flags": [], "brief": 3, "duration_s": 120}
{"ts": "2026-06-02T05:37:04Z", "role": "role-implementer", "convoy": "seed-visual-baselines-on-linux", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 180}
{"ts": "2026-06-02T05:37:36Z", "role": "role-implementer", "convoy": "purge-neondatabase-serverless-fully", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 300}
{"ts": "2026-06-02T13:48:03Z", "role": "role-implementer", "convoy": "wire-migrate-into-ci", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 300}
{"ts": "2026-06-02T13:48:43Z", "role": "role-doc-writer", "convoy": "convoy-doc-housekeeping", "repo": "tcg-vault", "skip_flags": [], "duration_s": 0, "outcome": "complete"}
{"ts": "2026-06-02T14:37:35Z", "role": "role-implementer", "convoy": "seed-visual-baselines-on-linux", "repo": "tcg-vault", "skip_flags": [], "brief": 3, "duration_s": 120}
{"ts": "2026-06-03T22:42:56Z", "role": "role-conductor", "convoy": "liquid-glass-design-tokens", "repo": "tcg-vault", "skip_flags": ["ux", "ia", "qa", "flag"], "classification": "feature", "duration_s": 180}
{"ts": "2026-06-03T23:23:20Z", "role": "role-design-system-auditor", "convoy": "liquid-glass-design-tokens", "repo": "tcg-vault", "skip_flags": [], "duration_s": 420}
{"ts": "2026-06-03T23:43:49Z", "role": "role-architect", "convoy": "liquid-glass-design-tokens", "repo": "tcg-vault", "skip_flags": [], "duration_s": 540}
{"ts": "2026-06-03T23:48:23Z", "role": "role-a11y-auditor", "convoy": "liquid-glass-design-tokens", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120}
{"ts": "2026-06-03T23:48:23Z", "role": "role-implementer", "convoy": "liquid-glass-design-tokens", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 600, "outcome": "merged"}
{"ts": "2026-06-03T23:48:23Z", "role": "role-reviewer", "convoy": "liquid-glass-design-tokens", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 180, "outcome": "approved"}
{"ts": "2026-06-03T23:54:18Z", "role": "role-architect", "convoy": "liquid-glass-modal-and-surface-primitive", "repo": "tcg-vault", "skip_flags": [], "duration_s": 720}
{"ts": "2026-06-03T23:54:18Z", "role": "role-implementer", "convoy": "liquid-glass-modal-and-surface-primitive", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 1800, "outcome": "merged"}
{"ts": "2026-06-03T23:58:42Z", "role": "role-architect", "convoy": "liquid-glass-form-primitives", "repo": "tcg-vault", "skip_flags": [], "duration_s": 600}
{"ts": "2026-06-03T23:58:42Z", "role": "role-implementer", "convoy": "liquid-glass-form-primitives", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 1500, "outcome": "merged"}
{"ts": "2026-06-04T00:00:32Z", "role": "role-architect", "convoy": "liquid-glass-layout-shell", "repo": "tcg-vault", "skip_flags": [], "duration_s": 300}
{"ts": "2026-06-04T00:00:32Z", "role": "role-implementer", "convoy": "liquid-glass-layout-shell", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 900, "outcome": "merged"}
{"ts": "2026-06-04T00:02:28Z", "role": "role-architect", "convoy": "motion-system-pass", "repo": "tcg-vault", "skip_flags": [], "duration_s": 300}
{"ts": "2026-06-04T00:02:29Z", "role": "role-implementer", "convoy": "motion-system-pass", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 420, "outcome": "merged"}
{"ts": "2026-06-04T00:03:43Z", "role": "role-architect", "convoy": "liquid-glass-card-surfaces", "repo": "tcg-vault", "skip_flags": [], "duration_s": 240, "outcome": "architecture-only"}
{"ts": "2026-06-04T00:04:10Z", "role": "role-architect", "convoy": "liquid-glass-public-and-auth", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "outcome": "architecture-only"}
{"ts": "2026-06-04T00:07:19Z", "role": "role-implementer", "convoy": "cleanup-legacy-design-css", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 900, "outcome": "merged"}
{"ts": "2026-06-04T18:36:17Z", "role": "role-conductor", "convoy": "unify-glass-panel-surfaces", "repo": "tcg-vault", "skip_flags": ["ia"], "classification": "feature", "duration_s": 0}
{"ts": "2026-06-04T18:36:17Z", "role": "role-conductor", "convoy": "cleanup-card-item-list-and-share-modal-palette", "repo": "tcg-vault", "skip_flags": ["ia", "ux", "arch"], "classification": "feature", "duration_s": 0}
{"ts": "2026-06-04T18:45:32Z", "role": "role-architect", "convoy": "unify-glass-panel-surfaces", "repo": "tcg-vault", "skip_flags": [], "duration_s": 0}
convoy: add seed-visual-baselines workflow (harden-visual-diff-gate brief 1) (#138) Adds a workflow_dispatch-triggered job on the self-hosted axiom runner that captures fresh `tests/visual/__screenshots__/*.png` against a caller-provided URL and opens a `chore(visual): refresh baselines from <url>` PR via peter-evans/create-pull-request@v6. This is brief 1 of 2 of the harden-visual-diff-gate convoy. The workflow exists but is not invoked by this PR — operator dispatches via the GitHub UI or `gh workflow run seed-visual-baselines.yml` once they want a fresh baseline against post-glass-redesign main. Brief 2 (flip continue-on-error: true off visual-diff.yml, add 9th forbidden-patterns check) is unblocked once a fresh baseline lands via this workflow's auto-PR. Convoy decision D1 ratified: Option B (workflow_dispatch + auto-PR) chosen over Option A (ad-hoc SSH-into-CT-111 + manual commit). The workflow gives the baseline regeneration a reviewable Git-native shape; the auto-PR carries a checklist for visual sanity-check before merge. Workflow shape: - Inputs: base_url (required, no default to avoid wrong-target accidents), reason (optional, used in PR body). - Permissions: contents: write + pull-requests: write — sufficient for default GITHUB_TOKEN; no PAT needed. - Caches: shares the node_modules + Playwright browser caches with visual-diff.yml so the byte-equivalence guarantee holds without cache miss overhead. - Idempotent: peter-evans/create-pull-request short-circuits to a ::notice:: annotation if the captures match the existing committed baselines (no PR opened). Metrics: logged role-conductor + role-architect + role-implementer events for this convoy in .convoys/.metrics.jsonl. Satisfies the convoy-metrics-gate (PR #134, 9eef8d9) that fires on convoy:-titled PRs requiring at least one new metrics row. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 22:37:50 -04:00
{"ts": "2026-06-13T02:21:43Z", "role": "role-conductor", "convoy": "harden-visual-diff-gate", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 120, "outcome": "routed-to-architect"}
{"ts": "2026-06-13T02:21:43Z", "role": "role-architect", "convoy": "harden-visual-diff-gate", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 420, "outcome": "architecture-only"}
{"ts": "2026-06-13T02:21:43Z", "role": "role-implementer", "convoy": "harden-visual-diff-gate", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "classification": "ci", "duration_s": 900, "outcome": "pr-open"}
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
{"ts": "2026-06-13T02:57:31Z", "role": "role-implementer", "convoy": "harden-visual-diff-gate", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "classification": "ci", "duration_s": 1200, "outcome": "pr-open"}
{"ts": "2026-06-13T02:57:31Z", "role": "role-reviewer", "convoy": "harden-visual-diff-gate", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "classification": "ci", "duration_s": 300, "outcome": "approved"}
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
{"ts": "2026-06-13T04:14:48Z", "role": "role-architect", "convoy": "rotate-default-admin", "repo": "tcg-vault", "skip_flags": [], "classification": "security", "duration_s": 240, "outcome": "option-B-chosen"}
{"ts": "2026-06-13T04:14:48Z", "role": "role-implementer", "convoy": "rotate-default-admin", "repo": "tcg-vault", "skip_flags": [], "classification": "security", "duration_s": 900, "outcome": "pr-open"}
convoy: enable no-undef ESLint rule + fix 3 latent bugs it surfaced PR #144 (`31da384`, 2026-06-13) shipped a `ReferenceError: useFocusTrap is not defined` to production because the flat ESLint config did NOT enable the core `no-undef` rule — only `react/jsx-no-undef` (which catches undefined JSX components, not plain JS identifier references). This PR closes that gap, narrowly. ## What changes - `eslint.config.mjs`: enable `no-undef: 'error'` for source files + define the ~40 browser / Node / Vitest globals the rule needs. Hand-curated globals list (rejected pulling in the `globals` npm package for one config block). - 3 latent bugs surfaced + fixed (NOT silenced with disables): | Site | Bug | Fix | |------|-----|-----| | `components/CollectionPageView.js:238` | `onClick={toggleFavorite}` — fn defined in `lib/use-collection-view.js:269` (collection-level favorite) but missing from the hook's `return {}` | Added to hook return + component destructure | | `components/CollectionPageView.js:532` | `onTogglePublic={togglePublic}` — same pattern, fn at line 315 of the hook | Same shape: hook return + destructure | | `components/ShareModal.js:99` | `fetchInvitedUsers()` scoped inside the useEffect body but called from `handleInvite` outside | Extracted to component scope via `useCallback`; effect dep array updated | Bugs 1 + 2 broke the "Favorite collection" button and the public-toggle in the Share modal on the collection-detail page. Bug 3 broke the "refresh invitee list" path after a successful invite. None had been flagged because the operator hadn't exercised those exact flows since the relevant hooks were last refactored. - `components/ShareModal.js`: also adds an eslint-disable for `react-hooks/set-state-in-effect` on the moved `fetchInvitedUsers()` call. Matches the canonical pattern in `pages/profile.js:90` — async fetch; setState fires post-resolve, not synchronously to the effect body. ## Why not pull in @eslint/js/recommended wholesale? The recommended bundle also enables `no-unused-vars`, `no-prototype-builtins`, `no-empty`, `no-cond-assign`, and ~10 others — each would generate dozens of pre-existing violations on this codebase. The right rule-by-rule sweep is the deferred `adopt-eslint-recommended-set` convoy. This PR is scoped to the one rule that would have caught PR #144's bug class. ## Test plan - [x] `npm run lint` — clean (1 pre-existing unrelated warning on `CollectionsPageView.js`'s `eslint-disable` directive — out of scope) - [x] `npm run test:run` — 25 files / 123 tests pass - [ ] CI on this PR - [ ] Post-merge: exercise the three formerly-broken paths (favorite a collection from its detail page; toggle a collection public via Share modal; invite a user and confirm the invitee list refreshes) ## Convoy doc `.convoys/enable-no-undef-eslint-rule.md` documents the surfaced bugs, D1 (no-undef only vs recommended bundle), D2 (hand-curated globals vs `globals` package), risks, and acceptance. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 02:17:18 -04:00
{"ts": "2026-06-13T06:16:56Z", "role": "role-architect", "convoy": "enable-no-undef-eslint-rule", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 180, "outcome": "architecture-only"}
{"ts": "2026-06-13T06:16:56Z", "role": "role-implementer", "convoy": "enable-no-undef-eslint-rule", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 900, "outcome": "pr-open"}
convoy: render-test regression-lock for ScanDisambiguationDialog (PR #144) PR #144 (`31da384`, 2026-06-13) shipped a runtime `ReferenceError: useFocusTrap is not defined` to production because the component called the hook without importing it. The sibling `enable-no-undef-eslint-rule` convoy closes that bug class at LINT time. This PR locks the same regression at RENDER time so the bug would still fail CI even if the lint rule were dropped or disabled. ## What changes - `test/components/ScanDisambiguationDialog.test.js` — 8 tests: 1. `renders without crashing (PR #144 regression-lock)` — the direct lock-in. Mutation-tested: commenting out the `useFocusTrap` import causes all 8 tests to fail with the same `ReferenceError` shape that hit prod. 2. `returns null when disambiguation is falsy` 3. ARIA shape (`role`, `aria-modal`, `aria-labelledby`) 4. One button per candidate with accessible labels 5. `onPick` callback receives the selected candidate 6. Vision-hint branch renders when provided 7. Submitting state disables the "send for review" button 8. `onCancel` callback fires on Cancel click ## Why vitest + jsdom and not Playwright smoke | Path | Catches PR #144 | Setup | Runtime | |------|-----------------|-------|---------| | Playwright smoke | ✓ if disambiguation mounts in the smoke run | High (auth bypass, stable multi-candidate fixture image) | ~10s + browser | | Vitest render | ✓ directly — render-throw → test fail | Low | <100ms | Re-scoped the queued `scanner-disambiguation-smoke-test` task to the vitest shape because a render test catches the exact same bug class at 1/100th the cost and matches the existing `test/components/*.test.js` pattern (`Modal.test.js`, `ScannedCardItem.test.js`, etc.). A Playwright disambiguation smoke is still useful as integration-layer coverage and is queued as `scanner-disambiguation-playwright-smoke`. ## Verification - [x] `npm run test:run` — 26 files / 131 tests pass (up from 25/123) - [x] Mutation test: with `useFocusTrap` import commented out, all 8 tests fail with `ReferenceError`. With import restored, all pass. ## Test plan - [ ] CI on this PR green - [ ] Squash + merge - [ ] Smoke test post-merge: scan a card that triggers disambiguation in prod and confirm no console errors (the original PR #144 bug shape) ## Convoy doc `.convoys/scanner-disambiguation-render-test.md` documents D1 (cover the early-return branch explicitly), D2 (`fireEvent` not `userEvent`), D3 (do NOT mock `useFocusTrap` — the missing-hook is exactly what we're locking), and the two queued follow-ups (`add-component-render-smoke-pattern`, `scanner-disambiguation-playwright-smoke`). Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 02:20:21 -04:00
{"ts": "2026-06-13T06:20:00Z", "role": "role-architect", "convoy": "scanner-disambiguation-render-test", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 180, "outcome": "architecture-only"}
{"ts": "2026-06-13T06:20:00Z", "role": "role-implementer", "convoy": "scanner-disambiguation-render-test", "repo": "tcg-vault", "skip_flags": [], "classification": "ci", "duration_s": 600, "outcome": "pr-open"}
{"ts": "2026-06-14T13:06:36Z", "role": "role-implementer", "convoy": "multi-game-bulk-sync", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "duration_s": 600}
{"ts": "2026-08-14T23:57:39Z", "role": "role-conductor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "classification": "feature", "duration_s": 180, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:14:27Z", "role": "role-ia-architect", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 95, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:15:31Z", "role": "role-ui-designer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 90, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:16:36Z", "role": "role-ux-reviewer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:18:01Z", "role": "role-conductor", "convoy": "scanner-identify-upgrade", "repo": "scanner-identify-upgrade", "skip_flags": [], "classification": "feature", "duration_s": 720, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:18:01Z", "role": "role-conductor", "convoy": "tighten-scan-identify-hot-path", "repo": "scanner-identify-upgrade", "skip_flags": ["ia", "ui-design", "visual", "flag"], "classification": "feature", "duration_s": 720, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:18:01Z", "role": "role-conductor", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": ["ia", "ui-design", "flag"], "classification": "feature", "duration_s": 720, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:18:01Z", "role": "role-conductor", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": ["ia", "ui-design", "visual", "a11y", "design", "flag"], "classification": "feature", "duration_s": 720, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:18:40Z", "role": "role-architect", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 420, "model": "composer-2.5", "model_tier": "standard"}
{"ts": "2026-08-15T00:19:56Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 1, "duration_s": 240, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:20:23Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 2, "duration_s": 120, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:22:15Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 3, "duration_s": 600, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:24:15Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 4, "duration_s": 600, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T00:25:33Z", "role": "role-security-auditor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 480, "model": "gpt-5.6-terra-medium", "model_tier": "security"}
{"ts": "2026-08-15T00:25:54Z", "role": "role-reviewer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 210, "multitask_group": "audit-scanner-mobile-checkout-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T00:26:05Z", "role": "role-a11y-auditor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 78, "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T00:26:08Z", "role": "role-design-system-auditor", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "duration_s": 82, "multitask_group": "audit-scanner-mobile-checkout-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T00:55:56Z", "role": "role-reviewer", "convoy": "tighten-scan-identify-hot-path", "repo": "scanner-identify-upgrade", "skip_flags": [], "brief": 0, "duration_s": 180, "multitask_group": "audit-tighten-scan-identify-hot-path-uncommitted", "model": "cursor-grok-4.5-high", "model_tier": "fast"}
{"ts": "2026-08-15T00:56:39Z", "role": "role-implementer", "convoy": "scanner-mobile-checkout", "repo": "tcg-vault", "skip_flags": [], "brief": 4, "duration_s": 240, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:24:40Z", "role": "role-ux-reviewer", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 120, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:24:40Z", "role": "role-architect", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 300, "model": "composer-2.5", "model_tier": "standard"}
{"ts": "2026-08-15T01:24:40Z", "role": "role-implementer", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": [], "brief": 1, "duration_s": 900, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:24:40Z", "role": "role-implementer", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": [], "brief": 2, "duration_s": 300, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:24:40Z", "role": "role-reviewer", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 180, "outcome": "approved", "multitask_group": "audit-improve-scan-card-detection-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T01:24:41Z", "role": "role-security-auditor", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 120, "outcome": "approved", "multitask_group": "audit-improve-scan-card-detection-local", "model": "gpt-5.6-terra-medium", "model_tier": "security"}
{"ts": "2026-08-15T01:24:41Z", "role": "role-a11y-auditor", "convoy": "improve-scan-card-detection", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 60, "outcome": "approved", "multitask_group": "audit-improve-scan-card-detection-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T01:35:27Z", "role": "role-ux-reviewer", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 90, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:35:27Z", "role": "role-architect", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 360, "model": "composer-2.5", "model_tier": "standard"}
{"ts": "2026-08-15T01:35:28Z", "role": "role-implementer", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "brief": 1, "duration_s": 300, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:35:28Z", "role": "role-implementer", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "brief": 2, "duration_s": 600, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:35:28Z", "role": "role-implementer", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "brief": 3, "duration_s": 900, "outcome": "complete", "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T01:35:28Z", "role": "role-reviewer", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 180, "outcome": "approved", "multitask_group": "audit-scan-visual-catalog-search-local", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T01:35:28Z", "role": "role-security-auditor", "convoy": "scan-visual-catalog-search", "repo": "scanner-identify-upgrade", "skip_flags": [], "duration_s": 120, "outcome": "approved", "multitask_group": "audit-scan-visual-catalog-search-local", "model": "gpt-5.6-terra-medium", "model_tier": "security"}
{"ts": "2026-08-15T12:41:53Z", "role": "role-conductor", "convoy": "scanner-identify-upgrade", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "model": "auto", "model_tier": "auto"}
{"ts": "2026-08-15T21:30:59Z", "role": "role-doc-writer", "convoy": "reconcile-historical-add-scripts", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "outcome": "complete", "model": "auto", "model_tier": "auto"}
{"ts": "2026-08-15T21:39:17Z", "role": "role-conductor", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": ["ui-design"], "classification": "feature", "duration_s": 90, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T21:42:03Z", "role": "role-reviewer", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "outcome": "comment-only", "model": "cursor-grok-4.5-high", "model_tier": "audit"}
{"ts": "2026-08-15T21:42:19Z", "role": "role-ia-architect", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 120, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T21:42:20Z", "role": "role-ux-reviewer", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 90, "model": "composer-2.5-fast", "model_tier": "fast"}
{"ts": "2026-08-15T21:42:20Z", "role": "role-architect", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 180, "model": "composer-2.5", "model_tier": "standard"}
{"ts": "2026-08-15T21:42:20Z", "role": "role-implementer", "convoy": "dashboard-home-realignment", "repo": "tcg-vault", "skip_flags": [], "duration_s": 600, "model": "composer-2.5-fast", "model_tier": "fast"}