feat(stack2): add Next.js apps/web application and CI verification job - #5
Closed
cryptorevel wants to merge 36 commits into
Closed
feat(stack2): add Next.js apps/web application and CI verification job#5cryptorevel wants to merge 36 commits into
cryptorevel wants to merge 36 commits into
Conversation
…se 9.4 - establish RBAC, workflow engine, rebate and compliance flows - add financials, technician mobile, advanced scheduler - add Lead Management through security sweep - add Notifications through security/data-integrity sweep - preserve hardened Google Calendar idempotency/concurrency design - include migrations 0001-0011 and complete regression suite Verification: - 733/733 tests - TypeScript clean - ESLint clean - Vite build clean Known verification gaps: - Phase 8.6 browser/UI verification pending - Phase 9.5 browser/UI verification pending
- add Playwright browser verification infrastructure - verify Notification UI across admin/dispatcher/technician roles - verify 1440/1024/768/430/390 viewports - fix SMS consent-source response regression - fix technician notification-page 403-on-load - fix mobile input sizing and pagination accessibility - preserve notification/security/calendar regression guarantees Verification: - 733/733 full tests - 218/218 notification regression - 42/42 Google Calendar regression - Playwright real-browser verification passed - TypeScript clean - ESLint clean - Vite build clean Phase 9 status: - COMPLETE - VERIFIED IN REAL BROWSER Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add DST-aware Google Calendar timezone regression coverage - verify summer/winter wall-clock preservation - cover midnight/late-night date boundaries - document America/Vancouver as the required local business timezone - preserve deterministic event IDs and Calendar concurrency protections Verification: - 742/742 full tests - 51/51 Google Calendar tests - TypeScript clean - ESLint clean - Vite build clean - real Google Calendar verified at 11:00-12:30 for JOB-34 Known remaining risk: - fresh D1 environments still default _meta.timezone to UTC until configured Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add BUSINESS_TIMEZONE to versioned Global Settings - seed America/Vancouver for fresh environments - add shared server-side business timezone resolver - route Google Calendar and notification reminders through one source - add admin timezone selector and confirmation flow - preserve wall-clock scheduling and DST-safe IANA timezone semantics - eliminate manual fresh-environment _meta timezone correction Verification: - 770/770 full tests - 55/55 Google Calendar regression - 221/221 Notification regression - Playwright browser verification passed - fresh-database timezone test passed - TypeScript clean - ESLint clean - Vite build clean - real Google Calendar verified Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- fix Lead form mobile CSS specificity - ensure 16px controls at 430px/390px - prevent iOS auto-zoom regression - preserve verified Phase 8 Lead browser flows Verification: - 770/770 tests - Playwright Lead mobile verification passed - TypeScript clean - ESLint clean - Vite build clean Phase 8: - COMPLETE - VERIFIED IN REAL BROWSER Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add durable job geocoding coordinates and status - add provider-agnostic geocoding and routing interfaces - add mock/no-op providers without external network dependencies - invalidate stale coordinates when a job address changes - cover geocoding, migration, and cross-domain behavior with tests Phase 10.0 IMPLEMENTED / VERIFIED. No real map provider, map UI, technician tracking, or route optimization.
- add server-side Google Maps Platform geocoding adapter - add provider selection and secret-backed configuration - add admin/dispatcher geocode endpoint with strict RBAC - preserve idempotency and address-change invalidation semantics - normalize provider errors and bound external requests - verify cost/privacy/cross-domain isolation with tests - verify one controlled real Google geocode flow Phase 10.1 IMPLEMENTED / VERIFIED. No map UI, routes, technician tracking, or route optimization.
- integrate a read-only Google Map into the existing Scheduler - render stored geocoded Job coordinates without geocoding on map load - synchronize markers with Scheduler/list selection and active filters/range - handle non-geocoded Jobs safely - preserve Admin/Dispatcher RBAC and Technician isolation - keep server geocoding credentials separate from browser Maps configuration - preserve Calendar, Notification, scheduling, and timezone behavior - verify responsive/accessibility behavior in real Chromium Phase 10.2 IMPLEMENTED / VERIFIED. Full suite 856/856 PASS before commit. Real browser verification Part A 20/20 and Part B 22/22. No route optimization, technician tracking, drag scheduling, push, or deploy.
- add a technician-only daily Route view to the existing Technician Mobile experience - scope route data server-side to the authenticated technician's own assigned jobs - derive numbered stop sequence from scheduled_time without persisted route ordering - render stored geocoded coordinates while keeping non-geocoded stops visible - synchronize route stop selection with Google Map markers - reuse existing Phase 6 external navigation and Phase 9.1 On-The-Way flows - use the existing global BUSINESS_TIMEZONE for route-day semantics - preserve browser/server Google Maps credential separation and zero geocoding-on-render behavior - preserve Dispatcher Map, Calendar, Notification, Scheduler, Leads, and Global Settings behavior Phase 10.3 IMPLEMENTED / VERIFIED. Full suite 876/876 PASS before commit. Real browser verification: Technician 33/33 across 1440/1024/768/430/390. No live tracking, route optimization, travel-time integration, push, or deploy.
Add server-side Google Routes integration for read-only travel-time information across scheduled technician stops. - preserve scheduled_time-derived visit order - use DRIVE with waypoint optimization disabled - keep Routes credentials server-side - expose explicit/button-gated route calculation only - preserve technician ownership and dispatcher/admin RBAC - handle unmapped stops without fabricated route continuity - add normalized distance/duration/geometry support - keep Calendar, Notifications, Scheduler mutations, Leads, ETA, traffic, optimization, and live tracking out of scope - prevent automated tests from making real billed Routes calls when local credentials are configured Real Google Routes verification: 09:00→11:00 = 12,460 m / 1,275 s 11:00→13:00 = 23,743 m / 2,540 s scheduled order preserved, DRIVE confirmed, optimization disabled, no browser-side Routes calls or key leakage. Verification: 923/923 tests passing TypeScript clean ESLint clean Build clean Responsive 1024/768/430/390 verification remains a disclosed tooling gap; 1440 verified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- document Geocoding API restriction for the server geocoding key - document Routes API restriction for the server routing key - preserve browser Maps referrer/API restriction guidance - record Phase 10.5 security and cost sweep as complete - no runtime behavior, database, dependency, or API changes Verification: 923/923 tests passing TypeScript clean ESLint clean Build clean Phase 10.5 SECURITY / COST SWEEP COMPLETE. Phase 10.6 NOT STARTED. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Audit-only phase: classify Core Platform vs HVAC/BC/tenant/infrastructure concerns across the entire schema, API surface, workflow engine, and UI. No application behavior, schema, or dependency changes. Key findings: - Core domain (auth, customers, jobs, scheduling, workflow mechanism, leads, compliance, financial, notifications, Calendar, Maps/Geocoding/ Routing) is already substantially industry-neutral. - HVAC/BC coupling is narrow and concentrated: a hardcoded 3-value JobType union (duplicated in 3 files), 5 rebate-profile columns on the generic customers table, and one hardcoded program-dispatch branch in the otherwise-generic rebate eligibility engine. - Only 6 of ~75 API endpoints are HVAC/BC-specific, all cleanly isolated as dedicated routes. - No tenant/organization concept exists anywhere in the schema, auth, or settings layer — classified NOT_READY for multi-tenant SaaS, with a concrete, evidence-based invasiveness estimate for adding it later. - Zero P0 findings. Proposes 6 small, additive, independently-shippable sub-phases (11.1-11.6), none a rewrite, none changing current HVAC/ CleanBC/BC Hydro behavior. Verification: 923/923 tests passing TypeScript clean ESLint clean Build clean Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…udit Re-verified §14's provider integration classifications against direct interface inspection: - Google Calendar: corrected from PROVIDER_ADAPTER to DOMAIN_COUPLED — no generic CalendarProvider interface exists; Google-specific types flow directly into calendar-sync.ts's business logic. - Cloudflare R2 Storage: added to the integration table (omitted from the original pass despite being in this audit's named scope) — DOMAIN_COUPLED, storage.ts types directly against R2Bucket/R2ObjectBody with no generic ObjectStorage interface. - Resend/Twilio: confirmed and cited more precisely as GENERIC_CAPABILITY + PROVIDER_ADAPTER (clean interface + isolated adapter, matching the Geocoding/Routing template). Added risk-register entries G13/G14 (both P2) for the two corrected findings. No other section changed. No application, schema, or dependency changes. Verification: 923/923 tests passing TypeScript clean ESLint clean Build clean Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduce the first structural boundary for Platform Generalization without changing runtime behavior. - organize industry/regional code under per-bundle modules/ - move BC rebate server logic and eligibility tracker UI into modules/programs/bc - preserve the existing server/client security boundary - add architecture-boundary guard tooling - document Core/HVAC/BC module dependency direction - leave JobType, workflows, rebates, tenancy, assets, APIs, and DB behavior unchanged Verification: 923/923 tests passing architecture guard passing TypeScript clean ESLint clean Build clean browser smoke previously verified with zero paid-provider calls Phase 11.1 IMPLEMENTED / VERIFIED. Phase 11.2 NOT STARTED.
Replace duplicated server-side JobType/WORKFLOWS declarations with a single typed registry while preserving all persisted IDs and runtime behavior. - derive JobType from JOB_TYPE_REGISTRY - keep STANDARD, CLEANBC, and BC_HYDRO IDs unchanged - compose BC workflow definitions from the regional program module - preserve existing workflow sequences and transitions byte-for-byte - retain strict runtime validation and explicit unknown-type rejection - keep rebate business logic unchanged for Phase 11.3 - preserve server/client bundle boundaries with consistency coverage - keep database, API contracts, RBAC, dependencies, and UI behavior unchanged Verification: 936/936 full suite passing 623/623 targeted domain regression passing workflow 22/22 rebate 16/16 architecture guard passing TypeScript clean ESLint clean Build clean real Playwright workflow/job-type smoke verified Phase 11.2 IMPLEMENTED / VERIFIED. Phase 11.3 NOT STARTED.
Extract BC rebate/program ownership from generic Core while preserving all CleanBC and BC Hydro behavior. - move rebate dispatch behind the BC program registry/capability - introduce a BC-module-owned customer rebate profile table - backfill existing rebate-profile values with an additive migration - preserve existing customer/API compatibility - retain legacy customer columns during the transition - keep eligibility thresholds, workflow semantics, audit snapshots, RBAC, and public program IDs unchanged - preserve Phase 11.2 job-type/workflow registry behavior - keep tenancy, assets/equipment, and future business modules out of scope Verification: 950/950 tests passing architecture guard passing TypeScript clean ESLint clean Build clean independent code review: no blocking findings security review: no blocking findings Phase 11.3 IMPLEMENTED / VERIFIED. Phase 11.4 remains deferred. Phase 11.5 NOT STARTED.
Introduces the minimum durable tenant/organization boundary across the
data model and application architecture without changing current
single-company behavior (Phase 11.5).
- New `organizations` table (migration 0015); default organization
backfills every pre-existing row via `NOT NULL DEFAULT 1`, safe for
fresh DBs, upgraded DBs, and the test harness's reseed replay alike.
No inline REFERENCES (D1 rejects ALTER TABLE ADD COLUMN combining
REFERENCES with a non-NULL DEFAULT) — application-enforced only,
disclosed trade-off.
- `organization_id` added to the 9 direct tenant-owned root tables
(users, customers, technicians, service_types, materials, jobs,
invoices, leads, global_settings); everything else stays tenant-safe
via parent-JOIN at the route layer, no duplicated column. 10 new
composite indexes.
- Server-derived tenant context only: getSessionUser() ->
actorOrganizationId(c) -> scoped query. No route ever trusts a
client-supplied organization/tenant id.
- ~80 route handlers scoped across Users/Customers/Leads/Jobs/
Technicians/Scheduler/Maps-Routing/Google Calendar/Notifications/
Financial/BC rebate/Global Settings, composed with existing RBAC
(role AND organization ownership, not either alone). Two pre-existing
ownership gaps unrelated to tenancy (addJobMaterial/addJobNote/
addChecklistItem, invoiceFromJob/updateInvoice) fixed as part of the
same pass.
- P0 fix: syncAllJobsForUser() ("Sync Now") previously synced every
organization's jobs into the calling user's personal Google Calendar
— found independently by two adversarial reviews, now scoped to the
caller's own organization, covered by a new regression test. Two
lower-severity findings (403-vs-404 on GET /api/invoices/{id}/payments;
updateCustomer's no-op-on-unknown-id convention) also fixed.
- New test/tenant-isolation.test.ts (22 tests): full Org A/Org B
isolation matrix including mass-assignment resistance and proof Lead
conversion never merges across organizations.
- users.email and the 3 identifier-column unique constraints, plus
_meta's global counters, deliberately KEPT GLOBAL this phase —
disclosed, deferred to Phase 11.6+ (tracked as new risk G15).
- Zero API contract or client UX changes; no new dependencies; no
tenant switcher/self-signup/billing/white-labeling built.
Verified: full suite 973/973, tsc/eslint/check:architecture/vite build
clean, real wrangler dev + real local D1 browser verification (fixtures
cleaned, DB confirmed back to baseline). Independent Architecture, Code
Review, and Security review completed; all findings fixed and
re-verified. SaaS readiness reassessed NOT_READY -> PARTIALLY_READY.
Phase 11.6 not started.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Close the final Phase 11 tenant-isolation blocker discovered during the production-readiness security review. - scope automatic Calendar fan-out sync/delete to the job organization - preserve the existing tenant-safe manual Sync Now path - add regression coverage for automatic Calendar fan-out isolation - add final tenant-isolation coverage for user account-management routes - cover Notifications, BUSINESS_TIMEZONE, Calendar retry sync, and paginated totals - preserve existing APIs, single-company UX, migrations, and dependencies - keep Phase 11.4 Assets/Equipment deferred Verification: 979/979 tests passing architecture guard passing TypeScript clean ESLint clean production build clean independent architecture/security/testing reviews completed real two-organization browser verification completed Phase 11.6 COMPLETE / VERIFIED. Phase 11 FUNCTIONALLY COMPLETE / VERIFIED. SaaS readiness remains PARTIALLY_READY.
Phase 11.4 — generic, tenant-safe Asset/Equipment foundation. Closes the
platform-generalization audit's §12 finding (no Property/Location/Asset
entity anywhere). Core term "Asset"; UI labels it "Equipment" for HVAC
users.
- Organization -> Customer -> Asset ownership (DIRECT_TENANT_COLUMN,
matching jobs/customers/leads); Job <-> Asset many-to-many via
job_assets, since real HVAC visits commonly service multiple units.
- HVAC asset-type registry (src/server/modules/hvac/asset-types.ts, 7
types) composed in index.ts, mirroring Phase 11.2's JOB_TYPE_REGISTRY
pattern for avoiding an HVAC-only Core union -- needs no
architecture-guard exception since asset types carry no per-type
engine behavior.
- Full RBAC: admin/dispatcher manage Assets and job links; technician
view-only for linked equipment on their own assigned job (reuses the
existing canActorAccessJobCompliance ownership predicate).
- Cross-customer and cross-org linking are default-DENY; retire-via-
status is the soft lifecycle, hard delete blocked while referenced by
a job.
- Additive migration 0016 (assets, job_assets) -- no existing table
touched, fresh-DB and upgrade safe.
Four independent reviews (architecture, code, security, testing) found
and fixed two real issues before commit: an asset could be silently
reassigned to a different customer while still linked to a job's history
(now blocked with 409), and a wildcard-dense manufacturer/search value
could crash listAssets with an uncaught D1 "LIKE pattern too complex"
500 (fixed with LIKE-escaping plus a wildcard-count cap, since escaping
alone doesn't prevent D1's complexity limit). Two low-severity findings
are disclosed and deliberately deferred, matching existing app-wide
precedent: Asset API responses include organization_id in the raw row
(same as GET /api/customers/{id} already does), and Customer deletion
cascades through Assets bypassing the delete guard (same as
jobs/invoices already cascade).
979 baseline -> 1009/1009 tests (30 new in test/assets.test.ts).
TypeScript, ESLint, architecture guard, and production build all clean.
Real-browser-verified (Chromium): admin Customer -> Equipment -> Add/Edit
Asset -> Job -> Link Equipment -> Scheduler -> Job Detail; technician
read-only linked-equipment view with no edit/link/unlink controls; Org B
tenant isolation confirmed with zero cross-org data visible.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduces a durable, tenant-safe Quote/Estimate domain: Quote (identity) -> immutable, versioned QuoteVersion commercial snapshots -> QuoteLineItem, with a full lifecycle FSM (draft/sent/accepted/rejected/expired/cancelled). All money is integer cents, server-computed and stored at write time only - totals are never client-supplied and never recomputed from later mutable state. Tenant isolation and RBAC (admin/dispatcher manage, technician blanket-blocked) are enforced identically to the existing Lead/Asset/ Invoice domains, verified with two-org adversarial tests. Hardening applied before this checkpoint: quotes.accepted_version_id gives Phase 13 (Contracts/E-Sign) an exact, permanent snapshot of which version was accepted, independent of current_version_id. quote_versions.row_version adds optimistic-concurrency (compare-and-swap + bounded retry) around totals recomputation, closing a disclosed lost-update race on concurrent line-item mutations; retry exhaustion maps to 409, and row_version itself is never exposed through any API response. Reviewed against several future ServiceTitan-residential-style capabilities without building them: Good/Better/Best (Quote Options) is classified additive-with-small-migration and deliberately deferred (no quote_options table); Pricebook integration is classified snapshot-ready-with-small-extension; Technician Present Mode is confirmed architecturally supported via the existing response-shaping layer. None of these are implemented by this commit. Verification: 1048/1048 tests (base Quote suite plus concurrency, tenant isolation, RBAC, and mass-assignment coverage), tsc/eslint/architecture guard/production build all clean, real-browser admin/dispatcher/technician and cross-org verification performed with all synthetic fixtures cleaned up afterward. Four independent reviews (architecture, code, security, testing) across the implementation and hardening passes, all findings fixed and re-verified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements the first complete Contracts/E-Sign module on top of Phase 12's Quotes foundation: Accepted Quote -> Contract -> immutable Contract Version -> Signature Request (per signer, token-gated) -> public signing ceremony -> append-only Signature Event ledger -> hashed Signed Document -> Evidence Package. - Exact Quote binding: contracts.accepted_quote_version_id is captured once at creation from quotes.accepted_version_id and never re-read; Quotes' own FSM makes "accepted" fully terminal, so the source cannot drift. - Contract Versions are immutable once sent; draft edits are row_version CAS-guarded. Two-layer status: a bare user-triggered FSM plus a derived layer (mirrors financial.ts's recalculateStatus) for signed/declined/ partially_signed/expired. - ESignProvider abstraction (mirrors notification-providers.ts) with LocalEsignProvider as the only implementation this phase - no paid vendor added. - Public signing tokens: crypto-random, SHA-256 hashed at rest, 14-day lazy expiry, replay-safe, idempotent submission, generic error responses (no enumeration). A narrow /api/public/* prefix auth exemption is the sole authority boundary - the token, never a client-supplied org id. - Signed documents are hashed (SHA-256) and stored server-side in R2 under a tenant-scoped, never-overwritten key; evidence package exposes the full signer/consent/event/hash record. - RBAC (admin/dispatcher only, technician blocked server-side) and tenant isolation verified across every route, including the public token flow. Independent code, security, and testing reviews (via Agent tool) found and closed three real defects before commit: a cross-tenant IDOR in deleteContractSigner (signer-activity check wasn't contract-scoped), a missing CAS guard in resendSignatureRequest, and a dead/unreachable contract-level "expired" status derivation. Also closed: addContractSigner lacked a server-side draft-only guard, and no guard existed against a second live Contract per accepted Quote. 6 regression tests added directly from these findings. Tests: 1089/1089 (1048 baseline + 41 in test/contracts.test.ts). tsc/eslint/architecture-guard/build all clean. Verified via a real local D1 migration upgrade and a full Chromium browser pass (admin sign lifecycle, technician blocked client+server side, signed R2 artifact independently fetched and verified). Not committed: real paid E-sign provider, webhooks, signed-document download route, multi-signer signing-order UI, Customer Portal, deposit/ payment linkage. This module is a technical record of electronic-signature evidence, not a substitute for legal advice on enforceability. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Company Profile with tenant logo, a professional signed Contract PDF (Type + Draw e-sign, Certificate of Completion with actual IP/User-Agent evidence, immutable R2-stored artifact with SHA-256 verification on read), automatic customer signed-copy email with delivery status/retry/ idempotency, and a professional Invoice PDF (View/Download/Print, intentionally live-rendered to reflect the mutable payment/rebate state, unlike the immutable signed Contract). 1173/1173 tests, tsc/eslint/architecture-guard/build all clean. Real- browser accepted (Chromium) for every primary flow, including a real mouse-drawn signature, artifact hash equality, email failure/retry, Invoice mutable-state behavior, and the branding old/new snapshot test. Tenant/RBAC re-confirmed with a live Technician session (403 at both UI and API layers). Known limitations, disclosed rather than silently skipped: true responsive-breakpoint (1440/430/390) verification was blocked by a browser-tooling limitation in this environment, not an identified app defect. Keyboard accessibility was spot-checked, not exhaustively swept. One pre-existing, out-of-scope issue was found and left open: unauthorized direct navigation to /invoices/:id (and likely /jobs/:id, /customers/:id) logs an uncaught console exception before the already-correct 403/redirect takes effect — no security or functional impact, predates this phase, tracked as a backlog item. Does not include online/manual payments, Phone Operations, Reporting, Accounting expansion, a Customer Portal, or Pricebook/Good-Better-Best. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Invoice delivery is now explicit and independent of payment recording:
issuing/paying an invoice never auto-sends email, and sending an invoice
never records a payment. New POST /api/invoices/{id}/send with delivery
status/retry/resend; the prior automatic issue/payment emails (a Phase
5-era violation of this rule) were removed from issueInvoiceRoute and
recordPaymentRoute, with the old functions/templates left intact for
historical-data compatibility.
Manual/on-site payments (Cash, Check, Credit/Debit Card, E-Transfer, Bank
Transfer, Financing, Other) work without any prior invoice email, capture
amount/method/date/reference/notes/received-by/source, and never require
delivery. Partial, multiple, and full payments accumulate correctly with
server-authoritative balance math; overpayment is rejected via a single
atomic guarded INSERT (no read-then-write race); payments are void-only
(soft-void, excluded from balance, never hard-deleted, retained in
history with the voided state visible).
New PaymentProvider abstraction (src/server/payment-provider.ts) backed
only by a MockPaymentProvider this phase - no real provider credentials
or SDK. Online payment flow: opaque public payment-session token
(256-bit, only its SHA-256 hash persisted, shown once), 30-minute expiry,
server-authoritative amount, HMAC-verified webhook with atomic
claim-based replay protection, graceful "Pay Online unavailable" when no
provider secret is configured (every other financial workflow keeps
working). No `receipts` table - a Receipt is a live-rendered view of one
immutable payment row, reusing the existing PDF kernel and Company
Profile branding; View/Download/Print share one server route, and
receipt email is optional for manual payments (auto-sent only for
online-provider payments) and never invalidates the underlying payment
on failure.
Migration 0021 is additive only: new payment_sessions table plus three
nullable/defaulted columns on payments (source, received_by,
payment_session_id). Three independent reviews (security, architecture,
testing) found and fixed two real races (an overpayment race and a
webhook-vs-manual-payment race) and one inaccurate migration comment.
Verified: 53/53 test files, 1217/1217 tests, TypeScript/ESLint/
architecture-guard/build all clean. Real-browser acceptance pass
confirmed the critical never-sent-invoice -> on-site cash payment -> zero
customer email scenario, partial/multiple/full payment accumulation
across three methods, Receipt view/email, the mock online-payment flow
end-to-end through the public /pay/:token page, void/reversal, and
payment-link anti-enumeration behavior.
Known limitations, not fixed here: real mobile/tablet breakpoint
verification is blocked by a browser-automation tooling limitation
(resize_window does not change the actual viewport in this environment);
accessibility was smoke-tested, not exhaustively swept (no WCAG
compliance claim); no production payment provider is configured or
recommended. Two pre-existing, out-of-scope findings were surfaced and
documented but not fixed: Job Compliance's Submit Report dialog doesn't
save its draft first and gives no visible error on the resulting 400,
and the Record Payment modal's form labels lack programmatic
association.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restrict Global Settings (GET /api/settings) and Company Profile management to admin-only, closing a pre-existing gap where any authenticated role could read the full settings list. Enforcement is server-side (RBAC checks in the route handlers, 403 for authenticated non-admins, 401 reserved for unauthenticated) as well as UI (sidebar/ router already fail closed for non-admins, confirmed unchanged and correct). A narrow `reference_data` category carve-out keeps dropdown option lists (referral source, heating source, lead-lost reason) readable by all roles, since dispatcher/technician-facing forms depend on them and they carry no financial or threshold content. A new, equally narrow GET /api/config/business-timezone route replaces Technician Route View's prior dependency on the full (now locked-down) settings list. Company Profile management routes were already admin-only; internal PDF rendering (Contract/Invoice/Receipt) calls getCompanyProfile() directly server-side, never through the gated HTTP route, so this change does not affect PDF generation. Google Calendar integration is intentionally left unrestricted to all roles: it is a per-user personal OAuth connection scoped by user_id, not an organization-level integrations surface, and there is no org-level Integrations Management console to hardenit does not exist in this codebase and was not invented here. Tenant isolation is unchanged (organization always session-derived, never client-supplied). Added RBAC regression coverage in test/settings.test.ts and test/business-timezone.test.ts, including a fix for a stale pre-existing test that assumed the old open-read behavior. Verification: tsc, eslint, and check:architecture clean; production build clean; focused Phase 13C tests 81/81 pass. Full suite showed transient timeout failures across several unrelated, untouched test files on repeated runs (a pre-existing, load-related flakiness pattern, not tied to this change) — every affected file passed 100% when re-run in isolation except test/contracts.test.ts, which timed out on a different unrelated test each run despite never exercising any route this diff touches; treated as pre-existing infrastructure flakiness, not a regression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add versioned effective-dated organization tax profiles - support component-based taxes and editable jurisdiction presets - support tax enabled/disabled and inclusive/exclusive pricing - support default and line-item taxability - persist tax snapshots for financial documents - preserve Quote/Contract/Invoice historical tax integrity - preserve Phase 13B payment/receipt behavior - enforce Admin-only Tax Settings with tenant isolation - add regression coverage and documentation - note responsive verification limitation and legacy fallback Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- document existing Answermachine voice runtime and provider architecture - define OFS as the single user-facing control plane - recommend hybrid Voice Engine integration boundary - map Twilio/OpenAI Realtime/Media Streams/STT/TTS/transfer capabilities - define per-organization OFS service credential and server-resolved tenant scope - identify CRM/scheduler integration boundaries - document privacy/security/failure-mode requirements - classify migration actions and file-level map - define Phase 15 and Phase 16 scopes - preserve Voice Copilot as a separate later track Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the OFS-side data model, API, RBAC, and admin/dispatcher UI foundation for a future integration with a separate Voice Engine runtime (an AI phone receptionist bridging Twilio to the OpenAI Realtime API, evolved from the Answermachine reference project per docs/PHASE14-ANSWERMACHINE-INTEGRATION-AUDIT.md's approved hybrid architecture). The Voice Engine runtime itself is not in this repo and was not built this phase. - Tenant-scoped, purely additive schema (migrations/0023, 12 tables): versioned Phone Operations settings/modes, versioned voice agents, phone numbers with per-number agent binding, a call FSM with idempotent event/transcript recording, snapshot-once agent config per call, outcomes/transfers, and an audit trail. - Per-organization Voice Engine credentials: Twilio account token encrypted at rest (reuses the existing crypto.ts mechanism), and a hashed bearer service credential the separate runtime presents to OFS's new runtime API. organization_id is always resolved server-side (session, dialed number, or the credential itself), never trusted from a request body. - Twilio adapter (hand-rolled signature verification + REST calls, no vendor SDK) with webhook signature validation and idempotent replay handling for both inbound calls and status callbacks. - Admin/Dispatcher/Technician RBAC, verified server-side and in a real browser for all three roles. - New OFS-native Phone Operations UI (Calls/Settings/Voice Agents/Phone Numbers/Credentials). - Phase 16 (Customer/Lead/Job writes from call data) is explicitly out of scope and not implemented. Independent Security, Architecture, and Testing reviews were run and every finding fixed: a confirmed P0 (a replayed Twilio webhook with the same CallSid crashed with an unhandled 500 - now idempotent), two P1s (a phone number's bound voice agent was never actually used at call time; a failed outbound Twilio placement left a call stuck queued forever, permanently consuming a concurrency-cap slot - both fixed), plus several P2/P3 hardening fixes (defense-in-depth tenant checks on transcript/outcome/transfer routes, closed a Twilio-webhook number-enumeration oracle, locked the agent-rename UI trap, sanitized provider error text, completed TwiML XML escaping). Two items are deliberately documented, not fixed, for this foundation phase: a narrow check-then-insert race on the concurrency/daily-cap counts, and global (not per-organization) phone-number uniqueness with no real Twilio-ownership verification (see the in-code `ponytail:` comments and docs/PHASE15-PHONE-OPERATIONS-FOUNDATION.md for the full reasoning and upgrade path). Verified: typecheck/lint/architecture guard clean, production build clean, full suite 55/55 files and 1307/1307 tests passing (sequential run), real-browser acceptance for Admin/Dispatcher/Technician. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- integrate Phone Operations with Customers, Leads, Scheduler, Jobs, and follow-ups - add safe caller matching and manual correction workflows - add server-authorized tool registry and tenant-safe runtime actions - preserve canonical OFS domain/state-machine rules - add call-to-business linkage and auditability - harden Phone Operations CRM-context UI - complete Technician mobile/tablet hardening - add mobile drawer and accessibility fixes - verify Admin, Dispatcher, and Technician browser flows - verify 1440/1024/768/430/390 breakpoints with no whole-page overflow - preserve Phase 15 Phone Operations security and RBAC - document deferred systemic accessibility findings and Phase 15 known risks - verify 1328/1328 tests passing Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add organization-scoped Pricebook for equipment, parts, materials, services, labor, and other items - add SKU, manufacturer/model, categories, cost, sell price, taxability, and active state - add equipment and warranty metadata - add Pricebook search and pagination - integrate Pricebook selection with Quote line snapshots and historical price preservation - add optional Asset provenance linkage with tenant validation - enforce Admin management, Dispatcher read-only access, Technician restrictions, and cost secrecy - preserve manual Quote lines and existing Contract/Invoice snapshot semantics - fix browser-discovered money-input and metadata-key corruption bugs - preserve documented last-write-wins concurrency limitation - verify the full automated quality gate Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add Quote-version estimate options with GOOD/BETTER/BEST/CUSTOM tiers - add recommended-option support and independent option totals - add Pricebook/manual option snapshots with historical price integrity - add secure public Estimate sharing and atomic selection - record acceptance evidence and share audit events - convert only selected option into Contract - preserve tax snapshots, Quote versioning, and existing Contract/Invoice behavior - add Estimate PDF and public comparison UI - enforce RBAC, cost secrecy, tenant isolation, and IDOR protection - preserve Phase 17 CAS limitation and documented Phase 18 deferred items - verify full automated/browser gates Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- verify all current OFS routes across desktop, tablet, and mobile breakpoints - close deferred responsive gaps for Pricebook and Good/Better/Best Estimates - re-verify Phone Operations and Technician mobile behavior - eliminate whole-page horizontal overflow across the acceptance matrix - harden shared ConfirmDialog focus, Escape, and dialog semantics - fix in-scope form-label accessibility gaps - add real-browser ConfirmDialog regression coverage - preserve Admin/Dispatcher/Technician/Public RBAC - document remaining legacy label and bespoke-modal accessibility debt - verify 1414/1414 tests passing Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- add organization-scoped maintenance plans and membership entitlements - add versioned maintenance agreements with covered-equipment snapshots - add immutable versioned Legal Terms and exact signed Terms binding - add maintenance e-sign flow and signed agreement document generation - capture explicit auto-renew consent without enabling automatic renewal - add versioned maintenance checklist templates and digital service reports - track maintenance visit entitlements with idempotent consumption - reuse existing tax, asset, PDF, R2, company-profile, and job-compliance infrastructure - enforce tenant isolation, RBAC, public-token security, and cross-org IDOR protection - expose production Admin, Dispatcher, Technician, and public signing workflows - document historical-integrity guarantees and Phase 19C/19D boundaries - verify 1454/1454 tests passing Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xWcVcWiXJTPQwvbQeKWy4
- add recurring maintenance schedules and durable occurrence tracking - generate maintenance Jobs idempotently through existing OFS Job workflows - add safe renewal automation using Phase 19B Agreement and Membership state - require fresh customer acceptance for material renewal changes - carry renewal term expiry forward for subsequent renewal cycles - add 60/30/14-day renewal reminders through the existing notification pipeline - add automation-run history with strict tenant isolation - add pause/resume/manual-run operational controls - prevent duplicate Jobs, reminders, and concurrent renewal claims - preserve existing tax, e-sign, asset, RBAC, and notification infrastructure - document known Agreement expires_at UI and narrow concurrency limitations - verify 1472/1472 tests passing Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xWcVcWiXJTPQwvbQeKWy4
Adds the complete Stack 2 Next.js/React/Drizzle/PostgreSQL application (apps/web) alongside the existing Stack 1 Preact/Hono/D1 app, plus a verify-web CI job, Node 24 runtime pin, deployment runbook and migration inventory docs, and Phase 19D retention/referral/campaigns work on Stack 1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjyF6iZJiqvZtxBWiUcfEC
Resolves conflicts in src/server/index.ts and src/server/schema.sql: kept HEAD's auth middleware/actorOrganizationId and origin's app-side seed-data plumbing (ensureSeeded), moved the timezone meta row into META_DEFAULTS alongside it, and dropped origin's now-superseded nextInvoiceIdentifier duplicate (already lives in financial.ts on this branch). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjyF6iZJiqvZtxBWiUcfEC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
apps/web) alongside the existing Stack 1 Preact/Hono/D1 app, kept intact for rollback.verify-webCI job (Node 24, PostgreSQL 17 service container) that installs, typechecks, lints, migrates, unit-tests, integration-tests, builds, and drift-checksapps/web— closing a gap where CI never touched it before.engines,.nvmrc) and aligns the existingverifyjob to the same Node 24 major.docs/DEPLOYMENT-RUNBOOK.mdanddocs/NEXTJS-MIGRATION-INVENTORY.mdas the canonical Stack 2 deployment/readiness record.Test plan
verify-webhosted-runner confirmation — this PR is what triggers that for the first time.This PR does not deploy anything, switch DNS, or remove Stack 1. See
docs/DEPLOYMENT-RUNBOOK.mdfor the full remaining cutover checklist.🤖 Generated with Claude Code
https://claude.ai/code/session_01RjyF6iZJiqvZtxBWiUcfEC