Skip to content

Harness in both subprojects, module docs aligned with the code, Markdown gate - #29

Merged
bpstack merged 44 commits into
mainfrom
claude/compassionate-planck-gh6aof
Oct 14, 2026
Merged

bpstack merged 44 commits into
mainfrom
claude/compassionate-planck-gh6aof

Conversation

@bpstack

@bpstack bpstack commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Changes

  • Harness: layer 1 adopted in backend/ and frontend/, each with its own AGENTS.md; the root AGENTS.md keeps what both share, and every module doc links up to its subproject and the root. A session runs from the repo root and switches Node per side with fnm use; the root .nvmrc is gone.
  • Toolchain: Node major pinned per project (backend/ 22, frontend/ 24) in .nvmrc and engines; pnpm 10.28.0 with a 7-day hold on new releases; a single verify script per side in CI; Prettier 3.9.9 on both sides.
  • Markdown: every tracked .md reformatted to the repo rules, code blocks left untouched in both projects, and a CI step that checks them all.
  • Module docs: DB, scheduling (solver, backend, UI), parking, cashier, groups, maintenance, blacklist, backoffice, checklist, logbook and F&B brought in line with the code.
  • Fixes:
    • F&B imports pdf-parse/lib/pdf-parse.js: the package entry ran a debug block on import that needed backend/test/, now removed.
    • Restaurant memoizes the daily revenue rows, the only lint warning in the frontend.

Why

The harness rules and the module docs had drifted from the code, and nothing gated the Markdown. The fixes clear what blocked the new gates.

Checked

  • CI green on every push to the branch, including the new Markdown step.
  • Preview deployed green on Vercel and Render.
  • The F&B parser is covered by the backend tests against the Opera fixture.

No database migrations.

- Add scripts/ to the directories the error-code scan skips: it walks the disk, not what git tracks, so the git-ignored scripts/update-fixture-expected.ts made it fail locally with its shift codes (PI, IT, FO) read as error codes

- No tracked script sends error codes; they are CLI tools, not API responses, so the scan loses nothing
- Add backend/.nvmrc (22) and frontend/.nvmrc (24): fnm only reads the version file in the current directory, so both projects fell back to the open engines range and picked the newest installed Node (24) locally

- Set engines.node to 22.x in backend and 24.x in frontend: Render builds the backend on 22.23.3 and Vercel builds the frontend on 24.x; the open >=22.16.0 range let Vercel auto-upgrade to the next major

- Read each CI job's Node version from its project's .nvmrc: CI tested the frontend on 22 while production builds it on 24
- Add backend/pnpm-workspace.yaml with minimumReleaseAge: 10080, so a dependency version published less than 7 days ago is never resolved: most compromised releases are pulled from the registry well within that window

- Bump packageManager from pnpm 10.13.1 to 10.28.0, the same as the frontend: minimumReleaseAge exists since pnpm 10.16 and an older pnpm ignores it without any warning

- The lockfile is unchanged: a frozen install accepts it as is
- Add a verify script to the backend (lint with --max-warnings=0, format check, typecheck, tests): one command that runs the same gate locally and in CI, and eslint no longer passes with warnings

- Make the backend CI job call pnpm run verify instead of four separate steps, so CI and the local check cannot drift apart

- Use pnpm run <script> in the frontend CI job and in the backend README command table: the short form runs a pnpm built-in instead when a script name collides with one

- Point the README's 'What CI runs' row at verify
- Add *.ps1 text eol=crlf to .gitattributes: Windows PowerShell 5.1 can misread scripts saved with LF only, and backend/db-mysql/backup/backup.ps1 is one

- The repository copy stays LF, so no renormalization is needed; only the working copy on checkout changes
- Stop ignoring skills-lock.json: skills are rebuilt from it on every machine, while .claude/ and .agents/ stay ignored like node_modules

- Add backend/skills-lock.json with the seven backend skills and their source and hash, so the three machines rebuild the same set
… from CLAUDE.md

- Add backend/AGENTS.md with the harness layer 1, a declared exception (state and decisions stay in the root docs/, since backend/docs/ is git-ignored), its Security section (L3 plus the backend areas) and the owner's note on where the model goes wrong: backend is treated as its own project inside the repo

- Add @AGENTS.md to the root CLAUDE.md and a one-line CLAUDE.md importing it next to backend/AGENTS.md and the 16 module AGENTS.md files: a CLAUDE.md in or above a directory turns off Claude Code's own reading of AGENTS.md, and a module AGENTS.md without a pointer was verified not to load

- Root AGENTS.md: say that Claude Code reads it through the import instead of keeping CLAUDE.md in sync, note that the security level line is repeated in backend/AGENTS.md, and drop a duplicated Spanish fragment in the Security areas
- Add ADR-042, accepted: the harness is adopted per subproject starting with backend, with CLAUDE.md pointers next to each AGENTS.md; ADR-017 is marked as revised by it, since it had deferred the harness until the generator supported repos without a root package.json

- TODO: remove 'check which Node Render uses' (its build log shows 22.23.3 via .nvmrc, and engines is now pinned) and 'check that Claude Code loads module AGENTS.md' (verified and fixed with the pointers)

- TODO: narrow the harness item to adopting it in frontend and splitting the root AGENTS.md, and add moving the backend to Node 24 through preview
- Say Render reads backend/.nvmrc: since f934b4d the preview deploy of
  72b5d3c picks Node from backend/.nvmrc, not from the root one
- Drop the root .nvmrc from the upgrade steps: neither Render nor CI read it
- Wrap rows in useMemo in DailyRevenueTab: data?.rows ?? [] built a new array on every render while there was no data, so lookupDate's useCallback was rebuilt each time and eslint reported it under react-hooks/exhaustive-deps; it is the only lint warning in the frontend and blocks a gate that fails on warnings
- Add frontend/pnpm-workspace.yaml with minimumReleaseAge: 10080, as in the backend, so a dependency version published less than 7 days ago is never resolved: most compromised releases are pulled from the registry well within that window

- The frontend already pins pnpm 10.28.0, which supports the setting; the lockfile is unchanged and a frozen install accepts it as is
- Add a verify script to the frontend (lint with --max-warnings=0, format check, typecheck): one command that runs the same gate locally and in CI, and eslint no longer passes with warnings

- Make the frontend CI job call pnpm run verify instead of three separate steps, as the backend job does, so CI and the local check cannot drift apart

- Use pnpm run <script> in the frontend README command table and point its CI row at verify: the short form runs a pnpm built-in instead when a script name collides with one
- Add frontend/skills-lock.json with the fifteen frontend skills and their source and hash, so the three machines rebuild the same set; .claude/ and .agents/ stay ignored like node_modules
- Add frontend/AGENTS.md with the harness layer 1, the same as in backend/AGENTS.md, and frontend/CLAUDE.md importing it: frontend is treated as its own project inside the repo, as ADR-042 set for backend

- Declare the exception to rules 12 and 13 (state and decisions stay in the root docs/, since frontend/docs/ is git-ignored), so no frontend/docs/ is created

- Add a Security section with the L3 line shared with the root and backend AGENTS.md and the frontend areas (auth and route protection, UI-only role gating, API clients, client-side uploads, personal data, CSP and markdown rendering, configuration), so the security agent reviews the frontend against its own paths

- Add the owner's note on where the model goes wrong: several tasks in one prompt are worked one at a time
- Keep in the root only what both subprojects share: Context7 policy,
  Markdown formatting, commits and hooks, the security level, a new
  frontend-backend contract section and the module map, so each side's
  details load where they apply
- Move each side's stack, installed versions, commands, architecture,
  auth and messaging paths into backend/AGENTS.md and frontend/AGENTS.md,
  each with its list of module docs, so the subproject links down to them
- Drop the root scheduling section: all of it was already in the three
  scheduling module docs
- Point the root Security areas at the lists both subprojects already
  carry, so there is a single list per side
- Explain how the levels load: OpenCode reads only the first AGENTS.md
  walking up from where it starts, so the files link to each other; drop
  Codex, which is not used
- Fix what had gone stale while moving it, checked against the code and
  the lockfiles: the demo-admin role and disabled demo user (ADR-038),
  the pool in config/config.ts (it is config/db.ts), a Vercel preview
  pattern in CORS that does not exist, mysql2 in the frontend, the
  debug-*.js scheduling helpers that do not exist, and the full script
  lists (verify, typecheck, setup:local)
- Open every module AGENTS.md with an Up line to its subproject
  AGENTS.md (both, when the doc covers both sides) and to the root,
  because OpenCode only loads the first AGENTS.md it finds and a module
  doc otherwise has no way back to the shared rules
- Point cross references at AGENTS.md instead of the one-line CLAUDE.md
  that imports it, which tells a non-Claude client nothing
- Scheduling solver: refer to backend/services/scheduling/AGENTS.md for
  the end-to-end view and the cross-language constraint guide, which no
  longer live in the root
- Reflow the touched files with Prettier, as the repo rule asks for
  Markdown edited on purpose
…O item

- Add ADR-043: the root keeps what is shared, each subproject its own
  context, and the three levels link explicitly, because OpenCode reads
  a single AGENTS.md
- TODO: remove the frontend harness item; both halves are done
- SESSION: decisions up to ADR-043 and the current harness state
- Root, backend and frontend AGENTS.md: OpenCode 1.18.12 loads every
  AGENTS.md from where it starts up to the repo root, plus a module one
  when it reads a file there; the earlier text said it loaded only the
  first, read from its docs instead of its source
- State what neither client does: load the other side's AGENTS.md, which
  is what the cross links are for
- ADR-043: fix the motive accordingly and reject declaring instructions
  in opencode.json, since OpenCode already loads the root
- Add revert to the root commit types, as the harness layer 1 lists it
- Drop the PowerShell code search section, which agents do not need
- Layout: the frozen baseline ends at 20_checklist.sql, and scripts/
  also holds the local helpers and scheduling-seed.sql sits next to the
  mock, so the tree matches the folder
- Reconstruct from scratch: point at pnpm setup:local and say what it
  runs, since MASTER_INSTALL.sql by hand fails on the two baseline bugs
  the script patches in memory
- Flag recreate-local.sh as older and narrower than setup:local
- Aiven: the credentials come from backend/.env; aiven-conexion.md no
  longer exists
- Table count: 67 declared plus 3 views, not about 50
- Constraint signatures: only the cross-month constraints take
  virtual_days_by_emp, and locked_cells takes emp_idx_by_id, so the doc
  no longer promises one shared signature
- Node client: the daemon has idle, starting and ready phases; the
  semaphore, not a busy phase, serializes requests
- day_blocks: the minimum block is the MIN_SHIFT_BLOCK constant, not a
  config value
- H4/H5 debt: say where the TS validator actually drifts (H4 counts the
  previous streak without the exemption, H5 checks no cross-month
  window)
- Local setup: install from requirements.txt, which pins what Render
  installs through pnpm build; the TODO section the doc pointed to no
  longer exists
- Validator: validate() registers only the coverage and employee-rules
  classes; H2, H4, H5 and the libre counts run inline in
  runFinalValidation (H4 and H5 inside the month only, H5 and the libre
  limits as warnings), and the other four classes are only used by the
  unit tests. The doc presented those classes as the live rules
- Solver doc: correct the H4/H5 drift written in the previous commit,
  which described the unused class instead of the live validator
- Access: the router is admin only; a published month answers 409 to
  edits, it is not a read-only view for staff
- Generation: name the 404/409/422 checks, and say that approved
  requests and fixed days are inserted without source_constraint_id,
  so they are editable afterwards, and that libre numbers are
  recalculated
- Deleting an approved constraint unlocks its cells (ON DELETE SET
  NULL) instead of removing them
- setSchedulableEmployees no longer loses dates: drop the known-bug note
- Endpoints: PUT for assignments and approvals, /rules and /employees,
  and the real middlewares; one repository function name
- Daemon phases: no busy phase, the semaphore serializes
- Flag soft-weight @emitter tags that point at the unused classes
- Access is admin only in the layout too, not admin and group-admin
- PDF export uses pdf-lib, not jsPDF with autotable
- PresenciasTab converts a schedule pasted from Excel; it is not an
  editor for P employees. shift-stats and presencias have no tab
  button and are reached by URL only
- Drop lib/scheduling/server.ts from the tree (removed on 2026-10-02),
  the DashboardLayout that never existed, and the setSchedulableEmployees
  bug, which is fixed
- Names: getMonthById, downloadSchedulePdf(monthData), formatLocalDate
  in config/utils/date.ts, dictionaries in messages/<locale>/
- An infeasible result opens a modal, not the warnings banner; cells a
  generation fills from requests or fixed days stay editable
- The iOS DayPicker workaround is a suggestion, not applied in the code
- Fix the parking reference path
- services/parking/ holds target-spot.ts, and parking.repository.ts
  handles availability, not invoice metadata
- parking_availability is also extended by a cron job at startup and
  daily at 03:00, not only by the triggers
- Only booking status and payment method are duplicated in the frontend
  constants; flag that SpotType in models/parking does not match the
  spot_type ENUM
- GET /bookings: document quickFilter and the date-range mode, and
  page + limit pagination instead of limit + offset
- The frontend constants copy only booking status and payment method,
  not source, spot type or level
- React Query is used by useParkingStatus and the dashboard, with local
  keys; the bookings list keeps its data in state and calls parkingApi
  directly. Use the real method names (getAllSpots, getAllBookings)
- Pagination is page + limit (50), and the list is server-rendered for
  the requested page, not page 0
- No trend charts and no invoicing on either side: drop "coming soon"
  and the backend PDF toggle that no longer exists
- Mantenimiento sees the nav link; the backend answers 403
- The list of days and a shift's history need canViewReports (admin),
  not just any role
- Add the eight endpoints the doc missed (vouchers active, stats and
  detail, payment summary, update and delete of single payments and
  denominations) and drop POST /vouchers, which does not exist
- A shift is deleted unless closed, not only when open; vouchers are
  justified, cancelled or deleted only from pending
- can_close also requires the day to be open; the totals path is
  recalculateTotals then updateDailyTotals
- Replace the shifted roleCheck line numbers with the middleware names
  and roles
- Routes: /dashboard/cashier is an overview page, not a redirect; the
  layout belongs to hotel/, and there is an error boundary
- PDF: exportDailyToPdf(daily, selectedDate, labels, shiftType) exports
  the active shift plus a day summary and downloads it itself; only
  DaySummarySidebar calls it, and ExportLabels lives in that file
- Use the real hook and selector names (useUpdateDenominations,
  useUpdatePayments, useLogsFilters, useReportsDate...) and add the
  ones the doc missed; drop the HistoryStats component, which does not
  exist
- The overview, reports and logs read admin-only endpoints and the UI
  does not gate them; the demo-admin role no longer exists
- Dictionaries live in messages/<locale>/
- Drop the email service: email-service.ts and nodemailer were removed on
  2026-10-02, and manual notifications are in-app only
- Document payment-rules.ts (payment coherence, 2026-10-11) and that the
  recalculation runs from PUT /:id in group-controller
- Contacts: /contacts and /contacts/primary need canViewGroupContacts,
  and the history hides contact entries from mantenimiento (2026-10-12).
  Flag that GET /:id still returns contacts to every viewing role
- Deleting a group keeps its history since the 2026-10-11 migration
- Status sync happens in the repositories, both ways
- Route order: the global payment routes come after GET /:id and only
  work because /:id matches one segment
- Replace the shifted roleCheck line numbers with names and roles
- Hooks: list the ones that exist; the list, group create/edit/delete,
  the status cards and the history call groupsApi directly. Six hooks
  the doc named (useGroups, useCreateGroup...) do not exist
- Cache keys come from groupsKeys; the server action lives in
  groups/actions/, not under [id]
- NotificationModal creates an in-app notification, now or scheduled;
  there is no email preview or SMTP
- Contacts are gated in the UI since 2026-10-12 (tab hidden, URL falls
  back to the summary); flag that the detail still carries them
- Store fields, component sizes, cards/ and StatusTimeline; dictionaries
  in messages/<locale>/
- Images are private (authenticated) since 2026-10-04 and served by
  the backend through GET /:id/images/:imageId/file; document the route
  (16 routes, not 14) and private-files.ts
- Uploads go through requireStorage and the shared imageUpload
  middleware, not a multer config in the routes file
- The history action is an ENUM column, not a CHECK constraint
- useMaintenanceList takes one options object; the keys factory lives
  in its hook file
- canAccessMaintenance no longer lists demo-admin
- Update the file sizes
- Photos are private since 2026-10-04: the entry stores API paths
  (/api/blacklist/images/<file>), upload returns that path, and
  GET /images/:file signs and sends the file on the server. The doc
  still described Cloudinary URLs in the row
- Upload checks the file bytes and the 5 MB limit; list services/uploads
- CloudinaryService is shared by auth, backoffice, maintenance and
  checklist, not only backoffice
- Frontend: add the server actions folder, Highlight.tsx and the
  current file sizes
- Invoice transitions are enforced: every UPDATE carries the status it
  starts from, and editing needs pending. The doc still warned that any
  status could move to any other. Add unpay (paid -> validated)
- isRealAdmin does not exist; the routes use isAdmin
- PDFs, stamps and signatures are private files: uploads go through
  requireStorage and backofficeUpload.ts (10 MB, real PDF bytes),
  downloads through fetchStoredFile, assets through /assets/:id/file,
  and backoffice-files.ts swaps the signed URLs for API paths
- Frontend: the tabs use React Query after the server render; add the
  lazy tabs, the modals and PdfEditorModal, which stamps the PDF in the
  browser; the fourth tab is settings
- AssetType is stamp | signature; CloudinaryService has five importers
- Attachments are private files: the client gets an API path from
  attachment-response.ts and reads the file through
  GET /attachments/:attachmentId/file; add both to the doc
- Uploads go through requireStorage and singleImage('file')
- Say what the optimistic toggle does, and update the file sizes
- useLogbooks takes one options object and also returns useReaders and
  the priority mappers; the mapping is defined twice (LogbooksList and
  useLogbooks)
- The day is read from /day/:day, not ?day=; document the /all filters,
  include_trashed among them
- Flag the open TODO item: trash, history and comments of deleted
  entries reach any role with access
- Read/solve history is written by logbookReads-repository; add
  logbook-errors.ts and the current file sizes
- The parser now checks the PDF bytes, reads at most 20 pages and
  gives up after 15 s; failures throw FnbPdfError
- A PDF with no tracked code also answers 422, and the upload has no
  requireStorage because the PDF is not stored
- Update the file sizes; the import script does exist
- High: the group detail still returns contacts to maintenance, which
  the 2026-10-12 decision meant to keep from it
- Medium: the scheduling rules H2, H4, H5 and libres exist twice and
  disagree; cells filled from approved requests and fixed days stay
  editable after a generation, pending a decision
- Low: SpotType does not match the DB ENUM; the local solver venv runs
  another Python than production and has no pytest

Found while checking the module AGENTS.md files against the code.
- Import pdf-parse/lib/pdf-parse.js instead of the package entry: v1's
  index.js runs a debug block when imported from ESM, which reads
  ./test/data/05-versions-space.pdf from the working directory, rewrites
  a .txt next to it on every boot, and crashes with ENOENT without it
- Remove backend/test/, which only existed to feed that block; its PDF
  was a byte-for-byte copy of tests/fnb/fixtures/opera-revenue-sample.pdf
- Add types/pdf-parse.d.ts, since @types/pdf-parse only types the entry
- Explain both in the F&B AGENTS.md, where the folder was undocumented

Checked: importing the old entry from a directory without test/ throws
ENOENT; the lib import parses the fixture with no test/ present.
- Split the tests by what they need: 4 auth files need SECRET_JWT_KEY
  and 7 need the database (NEEDS_DATABASE in vitest.config.ts). The old
  "8 files" line named whole folders and mixed both groups
- The database tests run against the one DB_ENVIRONMENT selects, local
  by default, not always the shared Aiven database
- Say to run from backend/, describe frontend/, scheduling-corpus/ and
  the F&B fixture, and note that backend/test/ is gone

Measured by running the suite without .env, with and without the key.
…ects

The root AGENTS.md says Prettier must not reformat code inside Markdown
code blocks, and the root config sets embeddedLanguageFormatting off.
The *.md overrides in backend/ and frontend/ did not repeat it, so any
Markdown under them had its embedded JSON and code reformatted.
18 tracked Markdown files failed prettier --check: hand-wrapped prose,
unaligned tables and blockquotes without continuation markers. Only
whitespace, table padding and quote markers change; the text is the
same. The checklist guides stay as they are, excluded on purpose by
frontend/.prettierignore.
Each project's format:check only covers code, so the root AGENTS.md,
docs/ and the module docs had no gate and had drifted. The step runs
in the frontend job with its Prettier, the one Markdown binary: the
backend ships a different version that wraps some lines differently.
AGENTS.md now documents the same check to run before committing.
Reflowing joined inline code that had been split across lines and kept
the continuation indent inside the span, so `handicapped |     electric`
and three more rendered with a run of spaces.
- Declare an exception to layer-1 rule 9 in backend/ and frontend/
  AGENTS.md: the session starts at the repo root and every Node or pnpm
  command first runs fnm use, which reads the subproject's .nvmrc
- Remove the root .nvmrc: Render and CI read each subproject's file
  since f934b4d, and its 22 only made a root session run frontend/
  commands on the wrong major, which pnpm merely warns about
- Record the decision as ADR-044 and update SESSION and TODO

The two sides run Node 22 and 24, and tasks that cross the contract
between them need both in view, so one session per side does not fit.
- Bump prettier from 3.6.2 to 3.9.9 and reformat the 9 frontend files
  and 2 module AGENTS.md the new version wraps differently, so lint,
  format:check and the Markdown step in CI stay green in this commit
- Point the root AGENTS.md at keeping both versions together

The two projects shipped different Prettier versions that disagree on
some Markdown lines, and CI checks every Markdown file with the
frontend's binary. The code changes are layout only: union types on
one line without the leading pipe, and one call rewrapped.
pnpm format runs prettier --write over the whole frontend, and the
lockfile was not ignored, so it rewrote pnpm-lock.yaml in a layout pnpm
does not produce. The backend already ignores its own lockfile.
@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
four-points Ready Ready Preview Oct 14, 2026 2:43pm UTC

@bpstack
bpstack merged commit 47f77b9 into main Oct 14, 2026
6 checks passed
bpstack added a commit that referenced this pull request Oct 14, 2026
- SESSION: main now matches the branch (PR #29, merge 47f77b9), the
  OpenCode review is in main since PR #28, and the next steps start at
  the post-publication TODO list
- TODO: update the last merge into main, which still said PR #4

This branch was successfully deployed

1 active deployment
Preview — aa975624 Deployed Oct 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant