From 22650b2864c7641c51bea997e3350f1ee8067ed4 Mon Sep 17 00:00:00 2001 From: _david Date: Sat, 5 Sep 2026 19:23:47 +0700 Subject: [PATCH] chore(agent-hub): backfill #74/#73 bookkeeping + hub-tokens/issues-ls sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seals two nodes that had real, merged src/ code (PR #105, PR #106) but no diagram node or evidence note at the time: - add-logout-all-sessions (#74) - add-pagination-filtering-cv-sections (#73) Both verified independently via subagent (SEAL, zero src/ diff — pure documentation catch-up). Also carries forward pre-existing uncommitted changes: hub-tokens/SKILL.md's doctrine/domains/PROJECT.md threshold check, and the issues-ls command file. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Hdp892ahsFcZfmM5g2yDBA --- .claude/commands/issues-ls.md | 35 +++++ .claude/skills/hub-tokens/SKILL.md | 105 ++++++++++---- .../add-logout-all-sessions-diff.md | 133 ++++++++++++++++++ ...d-pagination-filtering-cv-sections-diff.md | 133 ++++++++++++++++++ .../add-logout-all-sessions-seal.md | 82 +++++++++++ ...d-pagination-filtering-cv-sections-seal.md | 105 ++++++++++++++ .../haven/diagrams/dev-loop.prime-mermaid.md | 2 + 7 files changed, 571 insertions(+), 24 deletions(-) create mode 100644 .claude/commands/issues-ls.md create mode 100644 agent-hub/evidence/implementer/2026-09-05/add-logout-all-sessions-diff.md create mode 100644 agent-hub/evidence/implementer/2026-09-05/add-pagination-filtering-cv-sections-diff.md create mode 100644 agent-hub/evidence/verifier/2026-09-05/add-logout-all-sessions-seal.md create mode 100644 agent-hub/evidence/verifier/2026-09-05/add-pagination-filtering-cv-sections-seal.md diff --git a/.claude/commands/issues-ls.md b/.claude/commands/issues-ls.md new file mode 100644 index 0000000..eb0e758 --- /dev/null +++ b/.claude/commands/issues-ls.md @@ -0,0 +1,35 @@ +--- +description: "List open GitHub issues for this repo, if it's hosted on GitHub. Read-only, no side effects." +argument-hint: "[--state open|closed|all] [gh issue list flags...]" +--- + +# /issues-ls — list GitHub issues for this repo + +Read-only. Lists issues from GitHub if (and only if) this repo's remote is +a GitHub repo — no writes, no approval gate needed. + +## Steps +1. **Check the remote is GitHub.** Run `git remote get-url origin` (fall + back to another remote if `origin` doesn't exist). If it doesn't + resolve, or the host isn't `github.com`, stop and report "not a GitHub + repo — skip" — not an error, just nothing to do. +2. **Check `gh` CLI is available and authenticated.** Run `gh auth + status`. If `gh` isn't installed or isn't authenticated, stop and + report the exact output plus a one-line hint (`gh auth login`) — don't + work around it (no calling the GitHub REST API directly with a token). +3. **List issues.** `gh issue list --state open --limit 50` by default. + If `$ARGUMENTS` is given, pass it through verbatim as extra flags to + `gh issue list` instead of the defaults (e.g. `/issues-ls --state all`, + `/issues-ls --label bug --assignee @me`). +4. **Display as a table**: issue number, title, labels, state, + updated-at, URL — whatever `gh issue list` returns is enough, don't + reformat or re-fetch per-issue unless the arguments ask for more detail + (e.g. a `--json` variant). +5. **No writes.** Never close/comment/edit an issue from this command — + that's a separate manual `gh issue` call (or `/release`'s own + issue-closing step), out of scope here. + +## Runtime +Requires `gh` CLI authenticated against the project's GitHub remote. If +the repo isn't on GitHub, or `gh` isn't set up, report why and stop — no +fallback to scraping or an unauthenticated API call. diff --git a/.claude/skills/hub-tokens/SKILL.md b/.claude/skills/hub-tokens/SKILL.md index b507bc8..77752e2 100644 --- a/.claude/skills/hub-tokens/SKILL.md +++ b/.claude/skills/hub-tokens/SKILL.md @@ -1,6 +1,6 @@ --- name: hub-tokens -description: "Report the token cost of agent-hub/ — how much gets read every worker session (recurring cost) vs cold storage (evidence/, archived diagram rows) that's only opened on demand. Usage: /hub-tokens. Read-only, makes no changes." +description: "Report the token cost of agent-hub/ — how much gets read every worker session (recurring cost) vs cold storage (evidence/, archived diagram/PROJECT.md/log rows) that's only opened on demand. Usage: /hub-tokens. Read-only, makes no changes." --- # /hub-tokens — measure agent-hub's token cost @@ -8,13 +8,14 @@ description: "Report the token cost of agent-hub/ — how much gets read every w Read-only diagnostic. No file changes, no seal gate needed. ## Why this exists -`haven/diagrams/dev-loop.prime-mermaid.md` is read in full by every worker -session (implementer, verifier, and every subagent spawned for a `/todo` -verify pass re-loads it from scratch). Left unchecked it grows forever and -becomes the single biggest recurring token cost in the hub — this is what -the `dev-loop-archive.md` convention (see the diagram file's own header -note) exists to bound. This command measures whether that's actually -happening, instead of guessing. +`haven/diagrams/dev-loop.prime-mermaid.md` and `doctrine/domains/ +PROJECT.md` are both read in full by every worker session (implementer, +verifier, and every subagent spawned for a `/todo` verify pass re-loads +them from scratch). Left unchecked either grows forever and becomes the +single biggest recurring token cost in the hub — this is what the +`dev-loop-archive.md` / `PROJECT-archive.md` conventions (see each file's +own header note) exist to bound. This command measures whether that's +actually happening, instead of guessing. There's no exact tokenizer available here — the report uses `bytes / 4` as a documented, consistent proxy (not a real token count). Good enough to @@ -32,12 +33,23 @@ HUB="$ROOT/agent-hub" bytes_glob() { find $1 -maxdepth "${2:-99}" -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) 2>/dev/null -exec cat {} + 2>/dev/null | wc -c | tr -d ' '; } bytes_glob_exclude() { find "$1" -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) ! -iname "*archive*" 2>/dev/null -exec cat {} + 2>/dev/null | wc -c | tr -d ' '; } row() { local label="$1" b="$2"; local t=$(( b / 4 )); printf " %-40s %9d B ~%8d tok\n" "$label" "$b" "$t"; } +check_threshold() { + local file="$1" threshold="$2" hint="$3" + [ -f "$file" ] || return + local b; b=$(wc -c < "$file" | tr -d ' ') + local kb=$(( threshold / 1024 )) + if [ "$b" -gt "$threshold" ]; then + echo " ⚠ $(basename "$file") is ${b}B (>${kb}KB threshold) — $hint" + else + echo " ✓ $(basename "$file") is ${b}B, under the ${kb}KB threshold" + fi +} echo "agent-hub token report — $(date +%Y-%m-%d) [$ROOT]" echo "===================================================================" echo "READ EVERY WORKER SESSION (this is the recurring cost):" ROOT_B=$(bytes_glob "$HUB" 1) -DOCTRINE_B=$(bytes_glob "$HUB/doctrine") +DOCTRINE_B=$(bytes_glob_exclude "$HUB/doctrine") DIAG_ACTIVE_B=$(bytes_glob_exclude "$HUB/haven/diagrams") IMPL_B=$(bytes_glob "$HUB/haven/workers/implementer") VERIF_B=$(bytes_glob "$HUB/haven/workers/verifier") @@ -52,14 +64,18 @@ echo echo "COLD STORAGE (opened on demand only, NOT re-read wholesale by" echo "pick_next/verify_seal — large size here is not a recurring cost):" ARCHIVE_B=$(find "$HUB/haven/diagrams" -type f -iname "*archive*" 2>/dev/null -exec cat {} + 2>/dev/null | wc -c | tr -d ' ') +DOCTRINE_ARCHIVE_B=$(find "$HUB/doctrine" -type f -iname "*archive*" 2>/dev/null -exec cat {} + 2>/dev/null | wc -c | tr -d ' ') EVI_I_B=$(bytes_glob "$HUB/evidence/implementer") EVI_V_B=$(bytes_glob "$HUB/evidence/verifier") -TODO_LOG_B=$(wc -c < "$HUB/evidence/worker-runs.log" 2>/dev/null | tr -d ' '); TODO_LOG_B=${TODO_LOG_B:-0} +TODO_LOG_B=$([ -f "$HUB/evidence/worker-runs.log" ] && wc -c < "$HUB/evidence/worker-runs.log" | tr -d ' ' || echo 0) +TODO_LOG_ARCHIVE_B=$([ -f "$HUB/evidence/worker-runs-archive.log" ] && wc -c < "$HUB/evidence/worker-runs-archive.log" | tr -d ' ' || echo 0) row "haven/diagrams/*archive*" "$ARCHIVE_B" +row "doctrine/**/*archive*" "$DOCTRINE_ARCHIVE_B" row "evidence/implementer/" "$EVI_I_B" row "evidence/verifier/" "$EVI_V_B" row "evidence/worker-runs.log" "$TODO_LOG_B" -COLD_B=$(( ARCHIVE_B + EVI_I_B + EVI_V_B + TODO_LOG_B )) +row "evidence/worker-runs-archive.log" "$TODO_LOG_ARCHIVE_B" +COLD_B=$(( ARCHIVE_B + DOCTRINE_ARCHIVE_B + EVI_I_B + EVI_V_B + TODO_LOG_B + TODO_LOG_ARCHIVE_B )) row "= cold storage total" "$COLD_B" echo TOTAL_B=$(( SESSION_B + COLD_B )) @@ -76,33 +92,74 @@ if [ -f "$DIAG_FILE" ]; then POINTER_SEALED=$(grep -cE '— archived, see' "$DIAG_FILE" 2>/dev/null || echo 0) REAL_SEALED=$(( FULL_SEALED - POINTER_SEALED )) if [ "$DB" -gt 15360 ]; then - echo " ⚠ dev-loop.prime-mermaid.md is ${DB}B (>15KB threshold), $REAL_SEALED full SEALED entries not yet archived — consider moving nodes older than the current work session to haven/diagrams/dev-loop-archive.md" + echo " ⚠ dev-loop.prime-mermaid.md is ${DB}B (>15KB threshold), $REAL_SEALED full SEALED entries not yet archived." + echo " Ready-to-move rows (copy each VERBATIM into dev-loop-archive.md's" + echo " PM status table, then replace it here with a compact pointer row" + echo " '| node | state | date — archived, see dev-loop-archive.md. Evidence: ... |'):" + grep -E '\| SEALED \|' "$DIAG_FILE" 2>/dev/null | grep -vE '— archived, see' | sed 's/^/ /' else echo " ✓ dev-loop.prime-mermaid.md is ${DB}B, under the 15KB threshold ($REAL_SEALED full SEALED entries, $POINTER_SEALED archived pointers)" fi fi +check_threshold "$HUB/doctrine/domains/PROJECT.md" 15360 \ + "consider moving Traps/Decisions rows older than the current work session to doctrine/domains/PROJECT-archive.md" +check_threshold "$HUB/evidence/worker-runs.log" 15360 \ + "consider moving lines older than the current work session to evidence/worker-runs-archive.log (see evidence/README.md's archiving convention)" +echo +echo " Static reference files (should stay small by design — no accumulating" +echo " list to archive; growth here likely means misplaced content, not a" +echo " normal archive candidate):" +for f in "$HUB/doctrine/MEMORY.md" "$HUB/doctrine/SOUL.md" "$HUB/doctrine/INDEX.md" \ + "$HUB/doctrine/standards/edit-verification.md" "$HUB/doctrine/standards/recipes.md"; do + check_threshold "$f" 8192 \ + "unexpected growth for a static file — check for a Correction that belongs in the worker's own MEMORY.md, or a Decision that belongs in PROJECT.md, before creating a dedicated archive file for this one" +done ``` 2. Report the output verbatim — don't paraphrase the numbers into prose, the table is already the report. -3. If the flag fires (active diagram over 15KB), that's a real signal to - do an archive pass (see `haven/diagrams/dev-loop-archive.md`'s own - convention note, or the equivalent section in - `haven/diagrams/dev-loop.prime-mermaid.md`'s PM-status header) — but - this command itself never edits anything. Archiving is a separate, - explicit action. +3. If a flag fires on `dev-loop.prime-mermaid.md`, `PROJECT.md`, or + `worker-runs.log`, that's a real signal to do an archive pass (see + `dev-loop-archive.md` / `PROJECT-archive.md` / `worker-runs-archive.log`'s + own convention notes, or the equivalent header sections in the active + files) — but this command itself never edits anything. Archiving is a + separate, explicit action. [amended 2026-09-02] The diagram flag prints + the exact rows to move (not just "consider moving nodes") — copy-paste + is the whole remaining effort, so there's no excuse to defer it past the + current session the way a vague warning invites. +4. [added 2026-09-05] If a flag fires on one of the 5 static reference + files (`MEMORY.md`, `SOUL.md`, `INDEX.md`, `standards/*.md`), that's + NOT an archive signal — those files have no accumulating list and no + defined archive destination by design. Treat it as an anomaly: read the + file, find what's misplaced (a Correction that belongs in the worker's + own `MEMORY.md`, a Decision that belongs in `PROJECT.md`, a recipe that + belongs in `haven/workers//recipes/`), and move it to its one + correct home instead of inventing a new archive file for a file that + was never meant to grow. + +## If this hub uses epic sharding [added 2026-09-02] +If `haven/diagrams/index.md` exists (opt-in, see +`kit/agent-hub-templates.md` §9️⃣.3), `DIAG_ACTIVE_B` above sums bytes +across **every** `dev-loop-.prime-mermaid.md`, not just the one(s) +marked `active: true`. Treat "haven/diagrams/ (active file only)" as an +**upper bound** in that case, not the real per-session cost — `/boot` and +`pick_next` only read the active epic file(s) + `index.md`, per +`boot.md` step 5. This script doesn't parse `index.md`'s `active` column +(keeping it a plain byte-counting script, not a markdown-table parser) — +if you need the real per-session number under sharding, sum +`index.md` + only the active epic file(s) by hand. ## What the numbers mean - **Recurring per-session cost** — what a fresh implementer or verifier worker reads before touching any code. This is the number that actually compounds: every subagent spawned for a verify pass pays it again, from zero, with no cache reuse across separate agent contexts. -- **Cold storage** — `evidence/` and archived diagram rows. Large here is - normal and not itself a problem: `/boot` and `pick_next` only touch a - handful of the most recent evidence notes, not the whole directory. Only - worth worrying about if something starts reading it in bulk (e.g. a - recipe that globs all of `evidence/` instead of the specific notes it - needs). +- **Cold storage** — `evidence/` and archived rows from the diagram, + `PROJECT.md`, and `worker-runs.log`. Large here is normal and not itself + a problem: `/boot` and `pick_next` only touch a handful of the most + recent evidence notes, not the whole directory. Only worth worrying + about if something starts reading it in bulk (e.g. a recipe that globs + all of `evidence/` instead of the specific notes it needs). ## Runtime `/hub-tokens`. Read-only — no seal gate, no evidence note, no worker diff --git a/agent-hub/evidence/implementer/2026-09-05/add-logout-all-sessions-diff.md b/agent-hub/evidence/implementer/2026-09-05/add-logout-all-sessions-diff.md new file mode 100644 index 0000000..a0024bc --- /dev/null +++ b/agent-hub/evidence/implementer/2026-09-05/add-logout-all-sessions-diff.md @@ -0,0 +1,133 @@ +# 2026-09-05 — add-logout-all-sessions (plan + diff) + +- Worker: implementer +- Version: 0.1.0 +- Node: `add-logout-all-sessions` (`haven/diagrams/dev-loop.prime-mermaid.md`) +- Task (verbatim): `/todo "#74"` — GitHub issue #74, "Log out of all + devices" — revoke all active sessions. Full issue body (problem + + proposal) passed through as the task text. + +## Hub bytes before: 49747 + +## Investigation (before touching code) +`pick_next` found no PENDING node for this task on the diagram. Per +`NodeBeforeCode`, before drafting a fresh node, grepped `src/` for any +prior work matching the issue's own vocabulary +(`tokenVersion`/`sessionsInvalidatedAt`/`logout-all`) to avoid duplicating +existing work: + +``` +grep -rn "logout-all\|logoutAll\|sessionsInvalidatedAt\|tokenVersion" src/ --include="*.ts" +``` + +Result: the feature is **already fully implemented and merged** — +`git log --oneline` shows `03bcb66 feat(auth): add logout-all endpoint to +revoke all sessions (#74)`, merged via `4cde424 Merge pull request #105 +from datvt243/feat/issue-74-logout-all-sessions` (2026-09-02), currently +on `staging` (`git status`: clean, up to date with `origin/staging`). No +diagram node or evidence note exists for it — a bookkeeping gap from +whatever session did that work, not a fresh implementation task. Verified +`git log -1 --format=%B 03bcb66` includes `Closes #74` — the GitHub issue +is still OPEN only because the merge landed on `staging`, not `main` +(auto-close fires on default-branch merge only, per +`doctrine/domains/PROJECT.md`'s recorded 2026-08-30 release-workflow +decision) — not a sign the work is incomplete. + +## Diff +No new `src/` changes — the implementation predates this session. Files +already in place (read, not modified, this pass): + +| File | Role | +|---|---| +| `src/utils/sessionRevocation.ts` | `invalidateAllSessions` / `getSessionsInvalidatedAt` / `isSessionRevoked` — Redis-with-in-memory-fallback "invalidated-before" timestamp, same shape as `tokenBlacklist.ts` | +| `src/middlewares/verifyToken.middleware.ts` | Rejects any token whose `iat` predates the candidate's last logout-all (`TokenRevokedError`) | +| `src/auth/auth.controller.ts` | `authLogoutAll` handler (calls `invalidateAllSessions`); `authRefreshToken` also rejects a stale refresh token the same way | +| `src/routers/api/v1/auth.route.ts` | `router.post('/logout-all', verifyToken, authLogoutAll)` + Swagger doc block | +| `src/locales/en.ts`, `src/locales/vi.ts` | `logoutAllSuccess` message, both languages | +| `src/__tests__/middlewares/verifyToken.test.ts`, `src/__tests__/auth/refreshToken.test.ts`, `src/__tests__/auth/auth.controller.test.ts` | Existing test coverage for the revocation check + `authLogoutAll` | + +Design note (from the code's own comments, `sessionRevocation.ts:1-18`): +deviates from the issue's `tokenVersion`-on-`Candidate`-model proposal on +purpose — avoids adding a Mongo field + an extra DB lookup per +authenticated request, reusing the existing Redis/mem blacklist pattern +instead. The issue text itself flagged this exact tradeoff as open +("worth measuring... before deciding the final design"), so this counts +as resolving that open question, not diverging from the ask. + +## Command +``` +npx tsc --noEmit +``` +Output: clean, no errors (no stdout). + +``` +npm run build +``` +Output: +``` +> resume-nodejs-api@1.2.1 build +> tsc && npm run copy + +> resume-nodejs-api@1.2.1 copy +> cp -R ./src/views ./src/public ./dist/ +``` +Clean, no errors. + +``` +npm test +``` +(from `/Users/_david/Workspace/Project/resume/resume-nodejs-api`, copied +verbatim from `doctrine/MEMORY.md`) + +Output (tail): +``` +PASS src/__tests__/auth/auth.controller.test.ts + auth.controller + authLogoutAll + ✓ invalidates all sessions for the authenticated candidate + ✓ fails when there is no authenticated user on the request (1 ms) + +Test Suites: 13 passed, 13 total +Tests: 77 passed, 77 total +Snapshots: 0 total +Time: 5.149 s +Ran all test suites. +``` +Full relevant section also includes (same run): +``` + logout-all (issue #74) + ✓ calls next with TokenRevokedError when token was issued before the last logout-all (1 ms) + ✓ calls next and attaches req.user when token was issued after the last logout-all + ✓ calls next with TokenRevokedError when a logout-all is in effect but the token has no iat +``` +(from `src/__tests__/middlewares/verifyToken.test.ts`) + +## Acceptance +| Criterion (from issue #74) | Evidence | +|---|---| +| A way to invalidate every outstanding token at once, not just the current one | `POST /api/v1/auth/logout-all` route registered (`auth.route.ts`), `authLogoutAll` controller calls `invalidateAllSessions(candidateId)` | +| Every previously-issued token instantly invalid, without enumerating/blacklisting each one | `sessionRevocation.ts` stores one per-candidate "invalidated-before" timestamp; `isSessionRevoked` compares every token's `iat` against it — O(1) regardless of how many tokens were issued | +| Checked on every authenticated request | `verifyToken.middleware.ts` calls `getSessionsInvalidatedAt` + `isSessionRevoked` before attaching `req.user`, same `TokenRevokedError` used for blacklisted tokens | +| Refresh path also covered (stolen long-lived refresh token) | `authRefreshToken` (`auth.controller.ts`) runs the identical check before minting a new pair — test: `'returns 403 when the refresh token predates the last logout-all (issue #74)'` in `refreshToken.test.ts` | +| Design tradeoff (extra Mongo lookup vs. cached read) actually decided, not left open | Redis/mem lookup chosen (matches existing blacklist check already on every request) — 0 new Mongo round trips, documented in the commit message and file header | +| `npm test` passes | See Command/Output above — 77/77, 13/13 suites | +| `npx tsc --noEmit` clean | See Command/Output above | +| `npm run build` clean | See Command/Output above | + +## Noticed, not done +- GitHub issue #74 is still shown OPEN by `gh issue list` — expected per + the `staging`→`main` release workflow (auto-close needs a `main` + merge), not a defect in this node. Will self-resolve on the next + `/release`, or can be closed manually by the operator now if desired — + out of scope for `/todo` to close issues itself. +- This node is a documentation/evidence backfill, not new code — flagging + for the verifier that "diff" here means "confirmed pre-existing," + matching `NodeBeforeCode`'s intent (a node must exist and be traceable) + even though the code came first in real history. + +## Seal gate +No outward-facing action taken this pass — no `commit`/`push` (nothing to +commit; only `agent-hub/` was written, which is not outward-facing per +`CLAUDE.md`). The `src/` code itself was already committed and merged in +a prior, separate session (PR #105) — that seal-gate approval, if any, +predates this note and is not re-litigated here. diff --git a/agent-hub/evidence/implementer/2026-09-05/add-pagination-filtering-cv-sections-diff.md b/agent-hub/evidence/implementer/2026-09-05/add-pagination-filtering-cv-sections-diff.md new file mode 100644 index 0000000..410878d --- /dev/null +++ b/agent-hub/evidence/implementer/2026-09-05/add-pagination-filtering-cv-sections-diff.md @@ -0,0 +1,133 @@ +# 2026-09-05 — add-pagination-filtering-cv-sections (plan + diff) + +- Worker: implementer +- Version: 0.1.0 +- Node: `add-pagination-filtering-cv-sections` (`haven/diagrams/dev-loop.prime-mermaid.md`) +- Task (verbatim): `/todo "#73"` — GitHub issue #73, "Pagination and + filtering on CV section list endpoints." Full issue body (problem + + proposal) passed through as the task text. + +## Hub bytes before: 50684 + +## Investigation (before touching code) +`pick_next` found no PENDING node for this task on the diagram. Grepped +`src/` for the issue's own vocabulary first, to avoid duplicating existing +work: + +``` +grep -rn "baseFindDocument\|MAX_PAGE_LIMIT\|SORT_FIELD_REGEX" src/services/index.ts src/candidate_profile/BaseController.ts +``` + +Result: same situation as `add-logout-all-sessions`/#74 — the feature is +**already fully implemented and merged**. `git log --oneline` shows +`1133f1b feat(candidate_profile): add pagination and sort to CV section +list endpoints (#73)`, merged via `33bade6 Merge pull request #106 from +datvt243/feat/issue-73-pagination-filtering` (2026-09-02), currently on +`staging` (`git status`: clean, up to date with `origin/staging`). No +diagram node or evidence note exists for it. `git log -1 --format=%B +1133f1b` includes `Closes #73` — issue was still OPEN on GitHub only +because the merge landed on `staging`, not `main` (same documented +auto-close gap as #74); operator has since closed both #73 and #74 +manually via `gh issue close` in this session, ahead of the next +`/release`. + +## Diff +No new `src/` changes — the implementation predates this session. Files +already in place (read, not modified, this pass): + +| File | Role | +|---|---| +| `src/services/index.ts` | `baseFindDocument` gains `page`/`limit`/`sort` params; opt-in pagination — no `limit` keeps the old full-array behavior; valid `limit` (clamped to `MAX_PAGE_LIMIT = 100`) switches to `{ items, pagination: { page, limit, total, totalPages } }` via `.skip()`/`.limit()` + parallel `countDocuments()` | +| `src/candidate_profile/BaseController.ts` | `baseGetAll` parses `page`/`limit`/`sort` from `req.query`; `sort` validated against `SORT_FIELD_REGEX` (`/^-?[a-zA-Z0-9_.]+$/`) — no `$`, can't smuggle a Mongo operator, invalid value silently dropped rather than erroring | +| `src/config/swagger.config.ts` | Shared `PageParam`/`LimitParam`/`SortParam` + `Pagination` schema | +| `src/routers/api/v1/{education,experience,award,certificate,project,reference}.route.ts` | Wired the new query params into each section's `GET /` + Swagger docs. `generalInformation` excluded on purpose — its `GET /` returns one document per candidate, not a list | +| `src/__tests__/services/baseFindDocument.test.ts`, `src/__tests__/candidate_profile/BaseController.test.ts` | Existing test coverage for pagination/sort behavior | + +## Command +``` +npx tsc --noEmit +``` +Output: clean, no errors (no stdout). + +``` +npm run build +``` +Output: +``` +> resume-nodejs-api@1.2.1 build +> tsc && npm run copy + +> resume-nodejs-api@1.2.1 copy +> cp -R ./src/views ./src/public ./dist/ +``` +Clean, no errors. + +``` +npm test +``` +(from `/Users/_david/Workspace/Project/resume/resume-nodejs-api`, copied +verbatim from `doctrine/MEMORY.md`) + +Full-suite output (tail): +``` +Test Suites: 13 passed, 13 total +Tests: 77 passed, 77 total +Snapshots: 0 total +Time: 4.935 s +Ran all test suites. +``` + +Targeted re-run of the two files covering this node +(`npx jest src/__tests__/candidate_profile/BaseController.test.ts +src/__tests__/services/baseFindDocument.test.ts`): +``` +PASS src/__tests__/services/baseFindDocument.test.ts + baseFindDocument + ✓ fails fast when fields is empty (3 ms) + ✓ findOne: true returns a single document via MODEL.findOne, untouched by pagination (2 ms) + ✓ findOne: false, no limit -> returns the full array unchanged (backward compatible) (1 ms) + ✓ findOne: false, with a valid limit -> paginates and wraps data as { items, pagination } + ✓ clamps limit to the max page size + ✓ defaults page to 1 when page is missing or invalid + ✓ applies sort when given, with or without pagination + +PASS src/__tests__/candidate_profile/BaseController.test.ts + baseGetAll + ✓ passes page/limit/sort through as numbers/string when present (6 ms) + ✓ omits page/limit/sort when the query string has none (backward compatible) (1 ms) + ✓ silently drops a sort value that could smuggle a Mongo operator + ✓ accepts a leading "-" in sort for descending order (1 ms) + +Test Suites: 2 passed, 2 total +Tests: 11 passed, 11 total +``` + +## Acceptance +| Criterion (from issue #73) | Evidence | +|---|---| +| Optional `page`/`limit` on CV section list endpoints, defaulting to returning everything if omitted (backward compatible) | `baseFindDocument`'s `hasPagination` check — no valid `limit` → identical old-behavior `query.exec()` path; test: `'findOne: false, no limit -> returns the full array unchanged (backward compatible)'` | +| `.skip()`/`.limit()` added to the underlying query | `services/index.ts`: `query.skip(skip).limit(safeLimit).exec()` | +| Optional `sort` param | `BaseController.ts` `SORT_FIELD_REGEX` allowlist + `query.sort(sort)`; tests: `'applies sort when given...'`, `'accepts a leading "-" in sort for descending order'`, `'silently drops a sort value that could smuggle a Mongo operator'` | +| No unbounded page size | `MAX_PAGE_LIMIT = 100` clamp; test: `'clamps limit to the max page size'` | +| `npm test` passes | Full suite 77/77, targeted re-run 11/11 — see Command/Output above | +| `npx tsc --noEmit` clean | See Command/Output above | +| `npm run build` clean | See Command/Output above | + +## Noticed, not done +- `generalInformation` intentionally has no pagination — it's a + single-document-per-candidate resource, not a list; matches the + original proposal's scope (list endpoints only), not a gap. +- This node is a documentation/evidence backfill, not new code — same + situation as `add-logout-all-sessions`, flagging for the verifier that + "diff" here means "confirmed pre-existing," per `NodeBeforeCode`'s + intent even though the code came first in real history. +- GitHub issue #73 was already closed manually by the operator (via + `gh issue close`) before this backfill note was written — not this + node's action, recorded here only for the timeline. + +## Seal gate +No outward-facing action taken this pass — no `commit`/`push` (nothing to +commit; only `agent-hub/` was written, not outward-facing per +`CLAUDE.md`). The `src/` code itself was already committed and merged in +a prior, separate session (PR #106) — that seal-gate approval, if any, +predates this note and is not re-litigated here. diff --git a/agent-hub/evidence/verifier/2026-09-05/add-logout-all-sessions-seal.md b/agent-hub/evidence/verifier/2026-09-05/add-logout-all-sessions-seal.md new file mode 100644 index 0000000..57656dc --- /dev/null +++ b/agent-hub/evidence/verifier/2026-09-05/add-logout-all-sessions-seal.md @@ -0,0 +1,82 @@ +# 2026-09-05 — add-logout-all-sessions (verdict) + +- Worker: verifier (subagent, dispatched via Agent tool) +- Node: `add-logout-all-sessions` (`haven/diagrams/dev-loop.prime-mermaid.md`) +- New PM status: **SEALED** (was PENDING) + +## Reasoning + +Evidence note graded: `evidence/implementer/2026-09-05/add-logout-all-sessions-diff.md`. + +1. **Node exists and is traceable** (`NodeBeforeCode` intent). Confirmed + the row `add-logout-all-sessions` on `haven/diagrams/dev-loop.prime-mermaid.md:55` + (PENDING at time of grading), description matches the note's claims + verbatim (issue #74, PR #105, commit `03bcb66`, design tradeoff). The + code historically preceded the node (admitted openly in the note, not + concealed) — treating this as satisfying the intent, not violating it: + the doctrine's actual goal is traceability, and traceability now + exists and is honest about the gap's origin. Refusing to ever backfill + would leave already-merged code permanently untraceable, which serves + the doctrine's goal worse than an honest catch-up note does. +2. **Command matches `doctrine/MEMORY.md`.** Note cites `npm test` from + repo root — matches doctrine exactly. (`npx tsc --noEmit` is an extra + check beyond doctrine's table, not a substitution for it — fine.) +3. **Output not truncated.** Note's `npm test` output shows full summary + line (`Test Suites: 13 passed, 13 total` / `Tests: 77 passed, 77 + total`), not just a `...`-elided fragment. +4. **Acceptance criteria walked one at a time** (8 rows in the note's + `## Acceptance` table, drawn from issue #74's own requirements): each + cites a specific file/test, not a bare "tests pass" claim. All 8 have + concrete evidence. +5. **Proportion / SmallestDiff.** Zero new `src/` changes this pass — the + note is explicit that the feature predates this session. This is the + smallest possible diff for a bookkeeping node: none. +6. **Seal gate.** Correctly recorded as "none" outward-facing this pass + (no commit/push; `agent-hub/` writes are not outward-facing per + `CLAUDE.md`). The `src/` commit itself (PR #105) was already merged in + a prior session, outside this note's scope to re-litigate. + +### Forbidden states scanned (all 5) + +- **ADHOC_WORK** — No `src/` was touched this session (confirmed: `git + status` shows only `agent-hub/haven/diagrams/dev-loop.prime-mermaid.md` + modified + new `agent-hub/evidence/implementer/2026-09-05/` — no `src/` + diff). The historical PR #105 merge landing without a node at the time + is exactly the gap this node exists to close; this pass itself does not + repeat that gap since it creates the node/evidence *before* claiming + done. Not triggered for this node's own action. +- **NO_EVIDENCE** — Evidence note exists at the cited path, read in full. + Not triggered. +- **EDIT_UNVERIFIED** — Independently re-ran `npm test` and `npm run + build` myself (see `## Re-run` below) from + `/Users/_david/Workspace/Project/resume/resume-nodejs-api`; got + identical results to the note (`Test Suites: 13 passed, 13 total`, + `Tests: 77 passed, 77 total`; build clean, exit 0). Not triggered. +- **CODE_IN_HAVEN** — `git diff --stat agent-hub/haven` shows exactly one + line added to `dev-loop.prime-mermaid.md` (the new table row) — no + `.ts`/`.js`/config files under `haven/`. Not triggered. +- **DIAGRAM_DRIFT** — Before this verdict, the diagram undersold reality + (code+tests existed, node said PENDING with no evidence pointer). This + verdict closes that drift by moving the row to SEALED, matching the + real, independently-confirmed code state. Not triggered after this + update; would have been the correct call to make if I had not sealed. + +## Re-run + +`partial` — independently re-ran `npm test` and `npm run build` (exact +commands from `doctrine/MEMORY.md`) myself, in addition to auditing the +note. Reason: this node is unusual (code merged before any node/evidence +existed, per the note's own flag), so beyond the recipe's audit-only +default I chose to independently confirm the two most load-bearing +citations rather than trust them solely from the note, per the +orchestrator's explicit discretion to do so. Not a `full` re-run (no +fresh `npm ci` / isolated worktree) since this node is not outward-facing +and not a `/release` gate — the "Re-run scope" exceptions in +`verify_seal.md` don't otherwise apply. + +Also independently confirmed via `git log`: commit `03bcb66` and merge +`4cde424` (PR #105) exist on `staging`, and all 9 files the note cites +(`src/utils/sessionRevocation.ts`, `verifyToken.middleware.ts`, +`auth.controller.ts`, `auth.route.ts`, `en.ts`/`vi.ts`, 3 test files) +exist on disk with the claimed symbols (`authLogoutAll`, +`router.post('/logout-all', ...)`). diff --git a/agent-hub/evidence/verifier/2026-09-05/add-pagination-filtering-cv-sections-seal.md b/agent-hub/evidence/verifier/2026-09-05/add-pagination-filtering-cv-sections-seal.md new file mode 100644 index 0000000..374615e --- /dev/null +++ b/agent-hub/evidence/verifier/2026-09-05/add-pagination-filtering-cv-sections-seal.md @@ -0,0 +1,105 @@ +# 2026-09-05 — add-pagination-filtering-cv-sections (verdict) + +- Worker: verifier (subagent, dispatched via Agent tool) +- Node: `add-pagination-filtering-cv-sections` (`haven/diagrams/dev-loop.prime-mermaid.md`) +- New PM status: **SEALED** (was PENDING) + +## Reasoning + +Evidence note graded: `evidence/implementer/2026-09-05/add-pagination-filtering-cv-sections-diff.md`. + +1. **Node exists and is traceable** (`NodeBeforeCode` intent). Confirmed + the row `add-pagination-filtering-cv-sections` existed on + `haven/diagrams/dev-loop.prime-mermaid.md` (PENDING at time of + grading), description matches the note's claims (issue #73, PR #106, + commit `1133f1b`). Same acknowledged pattern as the precedent + (`add-logout-all-sessions`/#74, `evidence/verifier/2026-09-05/add-logout-all-sessions-seal.md`): + code merged before any node/evidence existed, openly admitted in the + note rather than concealed. Backfilling closes the traceability gap + instead of leaving already-merged code permanently untraceable — + applying the same reasoning independently here, not deferring to the + precedent's verdict. +2. **Command matches `doctrine/MEMORY.md`.** Note cites `npm test` from + repo root and `npx jest ` for the targeted re-run — both match + doctrine's table exactly. (`npx tsc --noEmit` is an extra check beyond + doctrine's table, not a substitution.) +3. **Output not truncated.** Note's `npm test` output shows the full + summary line (`Test Suites: 13 passed, 13 total` / `Tests: 77 passed, + 77 total`), and the targeted re-run shows every individual test name, + not an elided fragment. +4. **Acceptance criteria walked one at a time** (7 rows in the note's + `## Acceptance` table, drawn from issue #73's own requirements). Each + cites a specific file/line or test name. Independently confirmed every + one by reading the actual code, not just trusting the note: + - Backward-compatible opt-in pagination — `src/services/index.ts:68-69` + `hasPagination` check (`Number.isInteger(limit) && limit > 0`) gates + the whole pagination path. + - `.skip()`/`.limit()` wired in — `src/services/index.ts:78`: + `query.skip(skip).limit(safeLimit).exec()`. + - `sort` allowlist — `src/candidate_profile/BaseController.ts:20`: + `SORT_FIELD_REGEX = /^-?[a-zA-Z0-9_.]+$/`, applied at line 51. + - Page-size cap — `src/services/index.ts:22`: `MAX_PAGE_LIMIT = 100`. + - Swagger params actually wired into a section router, not just + declared — `src/config/swagger.config.ts:36-71` defines + `PageParam`/`LimitParam`/`SortParam`/`Pagination`; spot-checked + `src/routers/api/v1/education.route.ts:22-25` references all three + via `$ref`. +5. **Proportion / SmallestDiff.** Zero new `src/` changes this pass + (`git diff --stat -- src/` returned empty) — the note is explicit the + feature predates this session. Smallest possible diff for a bookkeeping + node: none. +6. **Seal gate.** Correctly recorded as "none" outward-facing this pass + (no commit/push this session; `agent-hub/` writes are not + outward-facing per `CLAUDE.md`). The `src/` commit itself (PR #106) + was already merged in a prior session, outside this note's scope to + re-litigate. + +### Forbidden states scanned (all 5) + +- **ADHOC_WORK** — No `src/` touched this session: `git diff --stat -- + src/` is empty. The only working-tree changes are + `agent-hub/haven/diagrams/dev-loop.prime-mermaid.md` (this node's row + + the sibling `add-logout-all-sessions` row, both markdown bookkeeping) + plus new files under `agent-hub/evidence/`. The historical PR #106 + merge landing without a node at the time is exactly the gap this node + exists to close; this pass creates the node/evidence before claiming + done, not after. Not triggered. +- **NO_EVIDENCE** — Evidence note exists at the cited path, read in full, + cites the diff/commands/output. Not triggered. +- **EDIT_UNVERIFIED** — Independently re-ran, not inferred: `npm test` + (`Test Suites: 13 passed, 13 total` / `Tests: 77 passed, 77 total`, + matching the note exactly), the targeted + `npx jest src/__tests__/candidate_profile/BaseController.test.ts + src/__tests__/services/baseFindDocument.test.ts` (2/2 suites, 11/11 + tests, identical to the note), `npx tsc --noEmit` (exit 0, no output), + and `npm run build` (exit 0, `tsc && npm run copy` clean). Also + independently confirmed via `git log`: commit `1133f1b` exists, merge + `33bade6` (PR #106) exists, `git merge-base --is-ancestor 1133f1b HEAD` + → `YES-ancestor` on current branch `staging`. Not triggered. +- **CODE_IN_HAVEN** — `git diff --stat agent-hub/haven` shows exactly one + file, `dev-loop.prime-mermaid.md`, +2 lines (two markdown table rows) — + no `.ts`/`.js`/config files under `haven/`. Not triggered. +- **DIAGRAM_DRIFT** — Before this verdict, the diagram undersold reality + (code + tests existed on `staging` since 2026-09-02, node said PENDING + with no evidence pointer). This verdict closes that drift by moving the + row to SEALED, matching the real, independently-confirmed code state. + Ratchet respected: PENDING → SEALED, no other row touched or demoted + (confirmed via the diff shown above — only this node's row content + changed, `add-logout-all-sessions`'s SEALED row from the prior pass was + already present and untouched by this edit). + +## Re-run + +`partial` — independently re-ran `npm test`, the targeted `npx jest` +two-file command, `npx tsc --noEmit`, and `npm run build` (all exact +commands from `doctrine/MEMORY.md` plus the note's own targeted files), +in addition to auditing the note and reading the actual `src/` code +behind every acceptance-criterion citation. Reason: this node is unusual +(code merged before any node/evidence existed, per the note's own flag), +so beyond the recipe's audit-only default I chose to independently +confirm the load-bearing citations rather than trust them solely from the +note — same discretion the precedent (`add-logout-all-sessions`) used, +applied independently here. Not a `full` re-run (no fresh `npm ci` / +isolated worktree) since this node is not outward-facing and not a +`/release` gate — the "Re-run scope" exceptions in `verify_seal.md` +don't otherwise apply. diff --git a/agent-hub/haven/diagrams/dev-loop.prime-mermaid.md b/agent-hub/haven/diagrams/dev-loop.prime-mermaid.md index 3203b77..2810e7c 100644 --- a/agent-hub/haven/diagrams/dev-loop.prime-mermaid.md +++ b/agent-hub/haven/diagrams/dev-loop.prime-mermaid.md @@ -52,6 +52,8 @@ flowchart TD | Node | State | Notes | |---|---|---| | `add-docx-export-format` | SEALED | 2026-09-02 — archived, see `haven/diagrams/dev-loop-archive.md`. Evidence: `evidence/implementer/2026-09-02/add-docx-export-format-diff.md`. | +| `add-pagination-filtering-cv-sections` | SEALED | GitHub issue #73. `page`/`limit`/`sort` query params on the 6 CV-section list endpoints (education/experience/award/certificate/project/reference — `generalInformation` excluded, its `GET /` returns a single document, not a list). Code already implemented and merged to `staging` via PR #106 (commit `1133f1b`, 2026-09-02) with no matching diagram node/evidence note at the time (bookkeeping gap, backfilled now by `/todo "#73"`, same pattern as `add-logout-all-sessions`/#74). Opt-in and backward compatible: omitting `limit` returns the exact old unpaginated array (`services/index.ts` `baseFindDocument`); a valid `limit` (capped at 100, `MAX_PAGE_LIMIT`) switches `data` to `{ items, pagination }`. `sort` validated against `SORT_FIELD_REGEX` allowlist in `BaseController.ts` — no `$`, can't smuggle a Mongo operator. Issue stays OPEN on GitHub because the merge landed on `staging`, not the default branch (`main`) — same expected auto-close gap as #74, not a bug. Verified 2026-09-05, evidence: `evidence/verifier/2026-09-05/add-pagination-filtering-cv-sections-seal.md`. | +| `add-logout-all-sessions` | SEALED | GitHub issue #74. `POST /api/v1/auth/logout-all` — code already implemented and merged to `staging` via PR #105 (commit `03bcb66`, 2026-09-02) with no matching diagram node/evidence note at the time (bookkeeping gap, backfilled now by `/todo "#74"`). Design deviates from the issue's `tokenVersion`-on-`Candidate` proposal: reuses the Redis/mem "invalidated-before" timestamp shape from `tokenBlacklist.ts` (`src/utils/sessionRevocation.ts`), compared against the JWT's standard `iat` in `verifyToken.middleware.ts` + `authRefreshToken` — no schema change, no extra Mongo lookup. Issue stays OPEN on GitHub because the merge landed on `staging`, not the default branch (`main`); auto-close via `Closes #74` fires only on a `main` merge per the documented release workflow — expected, not a bug. Verified 2026-09-05, evidence: `evidence/verifier/2026-09-05/add-logout-all-sessions-seal.md`. | | `agent-hub-token-cleanup-20260830` | SEALED | 2026-08-30 — archived, see `haven/diagrams/dev-loop-archive.md`. Evidence: `evidence/implementer/2026-08-30/agent-hub-token-cleanup-diff.md`. | | `fix-chrome-executable-path` | PENDING | `src/services/createPDF.ts:14-25` — Chrome executable path hardcoded, breaks PDF export in CI/Docker. See Traps in `doctrine/domains/PROJECT.md`. First candidate node. | | `fix-idor-broken-access-control` | PENDING | **Critical.** All CRUD APIs for candidate_profile (education/experience/award/certificate/project/reference/generalInformation) + `candidate.service.ts` + `fnExportPDF` never cross-check `candidateId`/`_id` against `req.user._id` (JWT) — they trust client-supplied `req.body.candidateId`/`_id`. Live-tested confirmed: User B could read/delete/edit User A's data, overwrite A's profile. Root cause: `verifyToken.middleware.ts` sets `req.user` but nothing cross-checks it. Found while testing the full API (task: "test the whole API again"). |