Files
tilbudgivern/docs/PR_31_REVIEWER_GUIDE.md
T
f37adae2cb
CI - Test & Build / Lint & Type Check (push) Waiting to run
CI - Test & Build / Backend Unit Tests (push) Waiting to run
CI - Test & Build / Frontend Build (push) Waiting to run
CI - Test & Build / E2E Tests (Playwright) (push) Blocked by required conditions
CI - Test & Build / Security Scan (push) Waiting to run
CI - Test & Build / CI Summary (push) Blocked by required conditions
feat: deliver auditable Smart Pakke quote flow and free site geometry (#31)
* feat: move login credentials to a DB-backed users table with an admin management page

Replaces the hardcoded AUTH_USERNAME/AUTH_PASSWORD login check with a new
auth_accounts table (bcrypt-hashed passwords, admin/user roles). Adds
admin-only /api/users CRUD routes and a "Brugere" admin page in the
frontend for managing logins without redeploying. Removes the unused,
unmounted duplicate login route in src/routes/auth.js.

* docs: add architecture codemaps with diagrams for the whole system

Adds codemaps/architecture.md, backend.md, frontend.md, and data.md —
Mermaid-diagrammed design documentation verified against the live
codebase and database rather than assumed from CLAUDE.md. Covers the
unified-server.js request flow (mounted routers + ~183 inline routes),
68 backend services grouped by domain, the frontend's state-driven
view-switch (no React Router in practice despite BrowserRouter being
present), and the full 122-table DB schema with the auth_accounts vs
unrelated users table naming trap flagged explicitly. Links added from
the root README.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* feat: ship canonical roof quote workflow

* fix: keep migration dry-run idempotent

* [verified] feat: complete Smart Pakker management

* [verified] fix: ignore blank task dependencies

* [verified] fix: align package duplication with schema

* [verified] fix: enforce Discord status limits

* [verified] fix: link Smart Pakke materials safely

* [verified] fix: harden material link review

* [verified] feat: improve material matching

* fix: scope pitch validation to roof packages

* fix: support canonical snapshots on production schema

* [verified] fix: hide internal package metadata from PDF

* [verified] feat: deliver sales-ready customer PDF

* [verified] feat: ship sales-ready PDF with AI overview

* [verified] fix: authenticate project list requests

* [verified] fix: refresh project-list authentication

* [verified] fix: open existing project details

* [verified] fix: keep roof components searchable in builder

* [verified] fix: expose all Smart Package categories

* [verified] fix: authenticate project creation

* [verified] feat: make Smart Pakker the universal project flow

* [verified] feat: preview Smart Package contents

* [verified] test: keep generic release isolated from downpipe work

* feat: add first-class Smart Pakke rentals

* [verified] feat: add gutter and downpipe smart packages

* [verified] fix: prepare six-house gutter quote flow

* [verified] fix: open generic quotes without roof geometry

* [verified] fix: review generic quotes with authenticated APIs

* [verified] fix: calculate generic Smart Package quotes

* [verified] fix: return generic calculation breakdown

* feat: checkpoint generic signed snapshot validation with red-green tests

* feat: complete fail-closed generic quote approval and customer PDF flow

* feat: use generic signed snapshot in final review

* feat: redesign generic quote final review

* fix: harden generic review summaries

* feat: add auditable six-house package basis

* [verified] feat: finish auditable Smart Pakke UI

* [verified] fix: bind auditable quantity and price bases

* [verified] fix: keep six-house basis across package versions

* [verified] fix: complete smart package discovery management

* [verified] fix: simplify composition and generic scope

* [verified] test: keep explicit roof contracts fail closed

* [verified] fix: harden generic quote snapshots

* fix: make generic quote delivery customer safe

* [verified] fix: secure package catalog reads

* [verified] fix: close workspace provenance blockers

* fix: harden customer document language boundary

* [verified] fix: secure smart package internal reads

* fix: version package child mutations atomically

* feat: add generic customer quote text flow

* [verified] fix: allow manual customer numbers

* [verified] fix: expose optional roof geometry

* [verified] fix: rebase hydrated packages after geometry edits

* [verified] feat: add free editable site area map

* [verified] fix: harden map recovery and geocoding gate

* fix: bind map quantities to authoritative geometry

* fix: release geocoder lock before dispatch

* fix: separate roof and site geometry provenance

* fix: revoke stale admin authorization

* fix: migrate task geometry basis

* fix: make backend CI dependency-complete

* ci: seed isolated e2e login account

* fix: allow clean database bootstrap

* fix: skip indexes for optional tables

* test: use canonical mansard geometry in e2e

* [verified] fix(auth): enforce live operator boundary

* fix: fail close Ordrestyring offer transport

* fix(frontend): authenticate customer project requests

* fix: align canonical roof type contract

* [verified] fix: reconcile legacy package labor safely

* [verified] fix: audit site geometry deletion

* docs: add PR 31 reviewer guide

* docs: synchronize Obsidian vault

* docs: sync integrated reviewer guide to Obsidian

* ci: seed isolated auth account explicitly

* fix: close offer bootstrap and service readiness gaps

* fix: authenticate protected package callers

* fix: provision initial admin and disable generic send

* [verified] fix: close final quote release blockers

* [verified] fix: seed gutter packages before deployment

---------

Co-authored-by: alexpolo1 <[email protected]>
Co-authored-by: Claude Sonnet 5 <[email protected]>
2026-09-26 22:39:18 +02:00

14 KiB

PR #31 reviewer guide

Scope. This guide maps the release architecture added or tightened by PR #31. It is a review aid, not a replacement for the domain documentation. The most useful companion documents are the generic quote snapshot contract, the generated architecture, backend, frontend, and data maps.

Suggested review order. Workspace provenance → geometry trust → snapshot/PDF boundaries → auth and external mutations → frontend orchestration. This follows the direction in which untrusted browser state becomes a customer document or an Ordrestyring mutation.

Architecture at a glance

React ProjectFlow
  ├─ EnhancedGeometry / SiteGeometryModal
  ├─ InlineSmartPackage → SmartPackageBuilder
  │    └─ versioned workspace autosave
  └─ FinalReview
       ├─ canonical roof snapshot → PDF → live Ordrestyring offer boundary
       └─ canonical generic snapshot → PDF → intentionally disabled send boundary
                         │
                         v
Express routers → domain/services → MariaDB
                         │
                         ├─ OpenStreetMap tiles (browser display only)
                         ├─ Nominatim (server-side address candidates)
                         └─ Ordrestyring GraphQL (roof path only in this scope)

The browser is an editor and renderer, not an authority for package children, prices, geometry measurements, economics, approval, or delivery. The server re-reads persisted inputs and current source records before it creates a signed artifact.

Request and data flow

1. Smart Package selection and workspace provenance

  1. The project flow hydrates project resources in parallel through projectHydrationService.js. Workspace failure is handled separately and blocks forward navigation rather than silently falling back to session data.
  2. SmartPackageBuilder.js creates editable instances from catalog packages. smartPackageWorkspace.js carries sourcePackageId, sourcePackageVersion, quantity basis, formula/provenance fields, and calculated child lines.
  3. InlineSmartPackage.js coalesces edits and sends PUT /api/customer-projects/projects/:id/smart-package-workspace with expectedVersion.
  4. smartPackageWorkspaceService.js locks the project, workspace, source packages, relevant package children, active material prices, and required geometry. It rejects stale versions or provenance, reconstructs canonical instances, and atomically updates:
    • project_smart_package_workspaces (the versioned source for quote snapshots);
    • package-owned rows in project_materials and project_rentals;
    • the package-owned labor breakdown in project_labor.
  5. The response contains the new workspace version and server-canonicalized instances. The frontend must use that response for subsequent saves.

The public catalog list is deliberately narrower than management data. See publicSmartPackageDto.js and the route split in smartPackagesRoutes.js: public reads expose active packages through an allow-list; management and mutations require the configured operator.

2. Site geometry, OpenStreetMap, and Nominatim

  1. EditableAreaMap.js uses OpenStreetMap tiles only as a visual base. SiteGeometryModal.js labels the polygon as user-drawn and unverified; its area is only a preview.
  2. Address text goes to the backend geocode endpoint. nominatimService.js validates the query, rate-limits across processes, and caches normalized Nominatim candidates. A candidate only recenters the map; it does not become project geometry.
  3. The browser submits polygon coordinates plus expectedRevision. siteGeometryService.js validates bounds/topology, computes geodesic area and perimeter, increments the locked revision, adds operator audit data, signs the canonical JSON, and records an audit row.
  4. A workspace instance that claims map-derived area is accepted only when the workspace transaction can lock the same project_site_geometry revision/signature. The service recalculates the quantity from the canonical area. A manual numeric area remains explicitly manual and cannot inherit map provenance.

OpenStreetMap/Leaflet attribution and license details are in THIRD_PARTY_NOTICES.md.

3. Canonical snapshots, approval, PDF, and delivery

FinalReview.js selects one of two server-owned contracts:

Path Snapshot PDF Delivery
Canonical roof GET /api/customer-projects/projects/:id/roof-quote-snapshot POST /api/pdf/generate POST /api/ordrestyring/offers/create
Canonical generic GET /api/customer-projects/projects/:id/generic-quote-snapshot POST .../generic-quote-snapshot/pdf POST .../generic-quote-snapshot/send (disabled; see below)

For both paths:

  1. The server rebuilds the artifact from persisted project/workspace/source data.
  2. Canonical serialization is hashed with SHA-256. This is an integrity/content signature, not a key-based digital signature.
  3. Approval is bound to that exact signature.
  4. PDF or send actions accept only project identity plus expectedSnapshotSignature; they re-read state and fail on drift.
  5. Customer output is built from an explicit field allow-list. Internal audit/provenance fields stay out of the document.

The roof contract is implemented by roofQuoteSnapshotService.js, roofQuoteCompleteness.js, pdfGenerationService.js, and offers.js. The Ordrestyring boundary also requires approved realism analysis, normalizes lines, reconciles remote totals, and uses a persisted operation lease/idempotency state.

The generic contract is implemented by genericQuoteSnapshotService.js, genericQuoteDeliveryService.js, and genericQuoteRoutes.js. Its detailed data contract is documented in GENERIC_QUOTE_SNAPSHOT.md.

Trust boundaries to review

Boundary Untrusted input Server-owned decision
Authentication Bearer token JWT verification; sensitive project routes additionally require req.user.username === AUTH_USERNAME
Public package discovery Query parameters and catalog response use Active-only filtering and DTO allow-list
Workspace save Client instances, quantities, prices, provenance claims Optimistic version check; locked package/version/children/prices/geometry; canonical reconstruction
Map geometry Nominatim candidate, tile display, polygon, preview area Polygon validation, geodesic calculation, revision, signature, audit
Quote review React state and locally rendered totals Persisted canonical snapshot, completeness/readiness, signature-bound approval
PDF Requested project/signature Revalidated approved artifact and customer-field allow-list
Ordrestyring Button click Server snapshot, approval/readiness, idempotency/lease, remote reconciliation

verifyToken proves token validity, while the configured-operator guard limits project quote data to the deployment's named operator. Admin authorization is a separate database re-check used by user management. Login accounts live in auth_accounts; the unrelated users table is an Ordrestyring employee roster. Relevant code: auth.js, the live login handler in unified-server.js, and users.js.

Invariants worth checking in the diff

  • A workspace save supplies the last server expectedVersion; conflicts return 409 and do not partially update projections.
  • Every persisted instance has a live, active, verified source package and the current sourcePackageVersion.
  • Current package children and active material price versions are reloaded by the server; browser prices are not accepted as authority.
  • Map-derived quantities carry project id, geometry revision, and signature that match the locked canonical site geometry row.
  • Manual overrides remain distinguishable and include server-owned operator/time audit data.
  • Workspace rows are the canonical generic quote line source; denormalized project_* rows must not introduce unbound costs.
  • Inactive lines are excluded consistently before snapshot economics and signing.
  • Approval matches the current snapshot signature; any relevant edit invalidates later PDF/send actions.
  • PDF data and Ordrestyring data originate from the same approved server artifact, never editable FinalReview state.
  • Customer documents do not expose Smart Package ids, formulas, provenance, or internal terminology.
  • Generic and roof approvals use separate stored keys and must not authorize each other.
  • Public Smart Package reads stay active-only even if includeInactive is supplied.

Key files by review question

Question Start here
How does the UI move from project to review? ProjectFlow.js, InlineSmartPackage.js, FinalReview.js
How are package quantities and provenance built? SmartPackageBuilder.js, smartPackageWorkspace.js
What is authoritative when saving? customerProjects.js, smartPackageWorkspaceService.js
How is map data trusted? siteGeometryRoutes.js, siteGeometryService.js, nominatimService.js
How is roof output frozen and delivered? roofQuoteSnapshotService.js, roofQuoteCompleteness.js, offers.js
How is generic output frozen and rendered? genericQuoteSnapshotService.js, genericQuoteDeliveryService.js, genericQuoteRoutes.js
Where are schema changes? 20260904_smart_package_workspace.sql, 20260910_complete_roof_package_contract.js, 20260918_fail_closed_roof_lifecycle.js, 20260910_ordrestyring_offer_operations.sql

Focused checks

Run from a dependency-installed checkout:

# Backend: provenance, geometry trust, both snapshot contracts, PDF boundary,
# auth, and live roof Ordrestyring boundary.
cd backend
npm test -- --runInBand \
  src/__tests__/smartPackageWorkspaceService.test.js \
  src/__tests__/smartPackageSiteGeometryTrust.test.js \
  src/__tests__/genericQuoteSnapshotService.test.js \
  src/__tests__/genericQuoteRoutes.test.js \
  src/__tests__/genericQuoteDelivery.test.js \
  src/__tests__/roofQuoteSnapshotService.test.js \
  src/__tests__/roofQuoteCompleteness.test.js \
  __tests__/siteGeometryRoutes.test.js \
  __tests__/smartPackagesReadAuthorization.test.js \
  __tests__/offersRoute.test.js

# Frontend: orchestration, workspace editing, geometry UI, and both API adapters.
cd ../frontend
CI=true npm test -- --watchAll=false --runInBand \
  src/components/ProjectFlow.test.js \
  src/components/FinalReview.test.js \
  src/components/smartPackages/SmartPackageBuilder.test.js \
  src/components/siteGeometry/SiteGeometryModal.test.js \
  src/utils/smartPackageWorkspace.test.js \
  src/services/genericQuoteSnapshotApi.test.js \
  src/services/roofQuoteSnapshotApi.test.js

For comment-only source edits, the relevant static checks are:

cd backend && npm run lint
cd ../frontend && npx eslint src/components/FinalReview.js

Intentionally disabled generic Ordrestyring path

The generic POST .../generic-quote-snapshot/send route is intentionally fail-closed in production. customerProjects.js mounts createGenericQuoteRouter without a transport, so the route validates and rebuilds the approved payload but returns HTTP 503 with GENERIC_TRANSPORT_DISABLED before any external mutation. Tests inject a mock transport solely to verify the contract and idempotency key.

Do not interpret the visible generic “Send til Ordrestyring” button or the payload builder as proof that production delivery is enabled. Enabling it requires an explicit production adapter and review of payload mapping, remote reconciliation, idempotency, and failure recovery. The canonical roof route in offers.js is separate and live; disabling the generic route does not disable roof offer creation.