Skip to main content

Requirements change log (historical)

Moved out of REQUIREMENTS.md §13 on 2026-08-26 — this is a phase-by-phase record of decisions and completions from the rebuild, kept for history alongside the other docs/history/ phase notes. It is not current-decision content; for what the app actually does today, start at docs/domain/ and docs/LIFECYCLE_ASSESSMENT.md. For the decisions that are still binding (D1–D21, glossary, capability parity, domain model), see REQUIREMENTS.md. Post-Phase-8 work (Rungs 1–6, map, violations, Work-tab rework) is indexed in docs/domain/README.md.

DateChange
2026-08-20Initial version. Decisions D1–D12 captured from owner interview.
2026-08-20D15 backend: Supabase-only for MVP, NestJS deferred behind an explicit trigger. Added §10.1 defining the five mandatory migration seams. D6 reframed: the abstraction axis is agency and compliance program within NYC, not region. §8 rewritten around Jurisdiction → Agency → ComplianceProgram, with ingestion agency-wide and workflow program-scoped.
2026-08-20OPEN-1 resolved as D14 (tenant = organization, occupant = resident). Added D13 capability parity as the governing constraint (§4). Phase 1 split into 1a capability inventory and 1b dependency trace + table triage.
2026-08-20OPEN-2 → D16, OPEN-3 → D17, OPEN-4 → D18, OPEN-5 → D20, OPEN-7 → D19 (keep active agency feeds per Phase 1a). OPEN-6 deferred. §12 trimmed.
2026-08-20Contradiction 9 / owner ruling #6 (tenant roles as owner-toggleable permission bundles) implemented, not deferred: profiles.role fixed enum replaced with profiles.permission_bundle_idpermission_bundlespermission_bundle_grants, gated by a new has_permission(key) helper. The four §4.2 staff role names (tenant_admin, project_manager, inspector, back_office) are seeded as every tenant's starting preset bundles — names are labels only, not authorization checks. Owner confirmed: RoleSwitcher.tsx's admin "preview as" tool is not a capability-parity gap — it already matches §4.2's audited platform-operator impersonation, build later (Phase 5/8) as-is.
2026-08-21Phase 5 (Core UI) complete: new frontend package (Vite/React/shadcn) on the new schema, §10.1's seams enforced by ESLint (not just convention), phase-gate logic ported to domain/src/phases/, advance-project-status edge function as the sole phase-transition authority. Exit criterion met and verified twice: one project per origin (violation/obligation/occupant_request) independently driven through all five gate boundaries to closed. Flagged for owner review, not resolved: the new project_event_links table was named to avoid, not settle, Contradiction 13's deployments/project_violations naming question — see docs/history/PHASE-5-NOTES.md.
2026-08-21Phase 6 (Field) complete: inspections, XRF (readings/calibration/canonicalization/reports), dust wipe, paint chip, abatement, lab chain of custody, floor plans. OPEN-6 resolved: online-only for v1, no offline queue-and-sync (§9.3 reworded, §12 cleared). Contradiction 13 resolved: deployments/deployment_violations folded into inspections as the generic field-visit table. Contradiction 8b resolved: xrf_friction_components DB mirror dropped — frictionSurface.ts stays the sole source. The XRF "brain" was recovered from the archived legacy remote and ported as pure TypeScript (domain/src/field/); the recovery research corrected two of the plan's own assumptions — no legacy code ever computed a per-reading inconclusive state or detected ceramic substrates, and the XRF-reuse (docs/RULEBOOK.md §3) mechanism was documented domain spec that was never actually implemented — see docs/history/PHASE-6-NOTES.md for the full account. The real field->docs_qa gate replaces the Phase 5 stand-in boolean. Field->docs_qa now reads real inspection/XRF/sample/floor-plan/chain-of-custody data via domain/src/field/gate.ts. MERGE-NOTES.md item 6 (a field_execution collaborator needing field-table write access) resolved via a new, narrowly-scoped has_active_collaboration_grant_for_role() helper. Exit criterion met and verified via the actual browser UI: a real XRF inspection driven end-to-end (CSV ingest → calibration validation → a genuine rendered PDF report) with the project advancing field→docs_qa on the real computed gate. Two real bugs caught by this phase's own leakage tests and browser verification (not code review) are documented in PHASE-6-NOTES.md: a tenant-ID-spoofing gap in the new field tables' RLS, and a missing service_role table-grants migration. Deferred, not dropped: floor_plan_revisions (existence gates the boundary, revision history doesn't); photo capture (a legacy capability with no schema slot in this phase's plan — flagged for owner review); a UI surface for the XRF-reuse mechanism.
2026-08-22Phase 7 (Docs & money) complete: documents, licenses, filing_packages/filing_package_documents, rate_cards, invoice_number_counters/allocate_invoice_number(), invoices, invoice_line_items, vendor_payments, plus a private documents Storage bucket. All three remaining Phase 5/6 stand-ins (intake_proposal_confirmed, docs_qa_confirmed, billing_confirmed) dropped and replaced with real computed gates (domain/src/docs/gate.ts, domain/src/billing/gate.ts), and Phase 6's own recorded forward dependency (base64 rendered_pdf_url/sketch_url/final_url) is now backed by real Storage objects. Every one of the six phase-gate boundaries now has a real, multi-condition, server-computed shape — the coarse-stand-in era is over. Two real bugs caught by this phase's own leakage-suite tests and browser verification (not code review), same pattern every prior phase recorded: the tenant-ID-spoofing gap (documents/filing_packages/invoices/vendor_payments trusted a client-supplied tenant_id) and a missing service_role grant on project_event_links (never read server-side before this phase). Exit criterion met and verified via the actual browser UI: a real project driven intake→scheduling→field→docs_qa→billing→closed, with a generated+signed proposal, a real XRF inspection report and floor plan backed by Storage objects, an HPD-response document generated from a real linked violation, a signed affidavit, an assembled filing package, a real atomic invoice number, and a paid invoice + vendor payment — full trail in project_phase_transitions. See docs/history/PHASE-7-NOTES.md for the full account, including owner-review flags on the docs/gate.ts condition predicates and the tenant-only (no collaborator scope) RLS choice for documents/filing_packages/licenses.
2026-08-22Owner correction: the SecureEnv launch tenant's name/slug is SecureEnv/secureenv, not Securi — every reference in this document, the tenants table seed migration, and Phase 8's own seed/notes/verification below uses the corrected name.
2026-08-22Phase 8 (Portal & white-label) complete — the last feature phase before Cutover (Phase 9). tenant_branding/client_branding (portal-chrome-only per-client override, per PHASE-8-NOTES.md decision #1 — never PDF letterhead), a private branding Storage bucket, notification_preferences/notification_log, a plain polled email_outbox/email_send_log queue plus global-by-address email_suppressions/email_unsubscribe_tokens, and the SECURITY DEFINER enqueue_email(). The client-portal RLS branch already present on projects/tenant_buildings since Phase 2 is extended onto documents/filing_packages/filing_package_documents/invoices/invoice_line_items (invoices/line items additionally exclude draft status for the client branch — D4.3-adjacent, a client's own bill is not "sharing financials with a collaborator"). supabase/functions/_shared/pdfWriter.ts extracts the SimplePdfWriter class duplicated across all 8 Phase 6/7 document generators into one shared, letterhead-capable (drawImage/setAccentColor) writer — a real, verified refactor of previously-shipped code, not just new-phase scaffolding. New edge functions: notify (the sole notification_log write path), process-email-outbox/process-notification-digest (service-role/cron, verify_jwt = false), handle-email-suppression/handle-email-unsubscribe (public webhooks, same posture), preview-email-template. New portal namespace (/portal/*, RequirePortalAuth, PortalLayout resolving branding via domain/src/branding/resolve.ts) plus a Branding card on the staff SettingsPage. A real bug caught by this phase's own browser verification (not code review), the same pattern every prior phase recorded: the client-portal RLS branch was added to the documents table but not to the documents Storage bucket's own RLS — a client_user could see a documents row but got a 400 creating a signed URL for the underlying object, since documents_bucket_select only ever checked the tenant_id path segment. Fixed in 20260823091200_documents_bucket_client_portal_rls.sql by re-deriving the same client-visibility predicate from the object path's tenant_id/project_id segments. Exit criterion met and verified via the actual browser UI: SecureEnv (teal, #0F766E) and Abated (amber, #B45309) both carry distinct, real tenant_branding rows; a tenant admin on each generated a real letterhead-branded document through the UI (confirmed via pdftotext against the actual rendered PDF — company name, accent-colored header, and tenant-specific footer text all present, no Complied branding leaked); a real seeded client_user on each tenant logged into /portal/dashboard, saw their own tenant's chrome (including a per-client display-name/footer override on SecureEnv's portal, colors still resolving from tenant branding underneath it), and could view their own project's document via a real signed URL. The email pipeline was verified live end-to-end without a RESEND_API_KEY present: notifyenqueue_email()process-email-outbox reached queued_unsent with a clear last_error at every stage short of the actual Resend call; a repeat enqueue_email() call for a suppressed address short-circuited to 'suppressed', and a second call for the same recipient reused rather than duplicated the unsubscribe token. See docs/history/PHASE-8-NOTES.md for the full account, including owner-review flags on the narrow client_branding scope, the absence of a shared_with_client per-document gate, and the simplified (non-Svix-spec) webhook HMAC verification.
2026-08-22Pre-Cutover polish (not a numbered phase): the frontend had shipped Phases 5–8 on the stock shadcn default theme with no brand identity. Ported the old app's real "Command" design system (Space Grotesk/Inter/DM Mono, full light/dark HSL palette, brand green, motion system) into src/index.css on Tailwind v4's CSS-first @theme shape — deliberately fixed rather than reproduced a duplicate---accent bug present in the old app's CSS. Ported the old public marketing landing page (archive/src/pages/Welcome.tsxsrc/pages/LandingPage.tsx, plus HeroMap/heroCamera/CompliedLogo) as a new public / route, live Mapbox hero and real NYC Open Data HPD lead-order feed intact (not mocked). Owner clarified during this work that the app has never actually been live/deployed for real users — the Phase 9 gate's data-migration caution is largely moot in practice. Verified: clean build/lint. Scoped entirely to the frontend; domain and supabase untouched.
2026-08-22Phase 9 file-promotion (Cutover, step 1 of 2): rebuild/web promoted to the repo root, rebuild/domaindomain/, rebuild/new-schema/supabasesupabase/, the rest of rebuild/new-schemadb-tests/, the pre-rebuild app → archive/, phase notes/design docs → docs/history/. rebuild/ no longer exists — the promoted app is the root now. Fixed the @complied/domain alias's path depth, all 13 edge functions' domain imports, the domain fidelity test's archive/src imports, db-tests' script imports, Vitest's default test discovery (scoped to src/** so it stops wandering into archive/'s broken-import old tests), and the pre-commit hook's bare vitest run (now goes through npm test's --passWithNoTests). README.md/CLAUDE.md updated, including README's stale "frozen on old backend" framing — everything has been on main since the reset. Verified: build green, root npm test exits 0, domain suite still 241/241, scripts/ci/guardrails.sh clean against the promoted supabase/. Lint reported (pre-existing archive//supabase/functions/ debt) but not fixed.
2026-08-25Ingestion Layer 1 (buildings identity via NYC Building Footprints + PLUTO) and Layer 2 (citywide public_events sync) built, fixing the bugs that had been gating a citywide run (commit 8ffd439). Layer 3 deliberately narrowed from "all 8 feeds every 6h for tracked buildings" to just NYC 311 (the only feed proven to update intraday) — see docs/compliance-ingestion-layers.md.
2026-08-26Cloudflare Pages hosting confirmed live (auto-deploys on push to main) — the "pending owner authorization" framing in this document, CLAUDE.md, and README.md was stale and has been corrected. Remaining Cutover work is deploying schema/edge functions and running Layer 1/2 ingestion against the real production Supabase project (a distinct project from whatever the local CLI has linked) — see docs/domain/CUTOVER.md. This change-log table itself moved here, out of REQUIREMENTS.md §13, since all planned phases are complete and the phase-by-phase narrative is no longer active-decision content.