App Flow • Reconciled against the Aug 18 Activity List
User Journeys — Four Personas
Solid lines and normal nodes are built and testable today. Dotted lines and muted nodes are not built, stubbed, or unreachable. Hover any node for detail. Build state is taken from HQ-Lite-Activity-List-v1.0 (18 Aug 2026), which enumerates 176 activities across four personas — 124 built, 52 carrying a gap tag.
What changed since the last version of this map
Everything the June 2nd version listed as "not built" has since shipped: the Planning module, the points engine, member invitations, LicenseContract, the floor-plan pipeline, and the visual booking canvas. The gaps have moved. They are now concentrated in
host lifecycle (nothing built),
operator tunability (every point factor is a code constant),
reporting on both sides, and
soft delete.
Super Admin Flow — internal only
HQ Lite platform operators — the thinnest persona, 11 of 27 activities built
flowchart TD
SA_LOGIN[Login - allowlisted]
SA_PANEL[Super Admin Panel]
SA_LOGIN --> SA_PANEL
SA_PANEL --> SA_DASH[Platform Overview Dashboard]
SA_PANEL --> SA_AUDIT[Audit Log - actor and event kind]
SA_PANEL --> SA_COGNITO[Cognito User Admin]
SA_PANEL --> SA_TENANT[Tenant Data Management]
SA_PANEL --> SA_COMPLIANCE[Compliance and Data Ops]
SA_COGNITO --> SA_MFA[TOTP MFA with QR registration]
SA_COGNITO --> SA_SOFTDEL[Soft delete - 30 day, 90 day purge]
SA_TENANT --> SA_HARDDEL[Hard-delete property or listing]
SA_TENANT --> SA_RESET[Reset inventory - ops Lambda]
SA_PANEL -.->|Nothing built| SA_HOSTLC
SA_HOSTLC[Host Lifecycle - 0 of 6]:::intended
SA_HOSTLC -.-> SA_HOSTCREATE[Admin-created host]:::intended
SA_HOSTLC -.-> SA_HOSTINVITE[Send host invite]:::intended
SA_HOSTLC -.-> SA_HOSTDEACT[Deactivate, keep history]:::intended
SA_PANEL -.->|Code constants only| SA_TUNABLES
SA_TUNABLES[Over-sell and Point Factors]:::intended
SA_TUNABLES -.-> SA_OVERSELL[Over-sell factor 1.2x]:::intended
SA_TUNABLES -.-> SA_WEIGHTS[Day and seat-type weights]:::intended
SA_TUNABLES -.-> SA_SENS[Sensitivity analysis - CLI only]:::intended
SA_PANEL -.->|Not built| SA_PROVISION[Sq ft provisioning and hard cap]:::intended
SA_PANEL -.->|Not built| SA_IMPERSONATE[Support impersonation]:::intended
SA_PANEL -.->|2 of 4 built| SA_REPORT[Platform Reporting]:::intended
classDef intended fill:#1c2330,stroke:#30363d,color:#636e7b,stroke-dasharray:4 4
Host Flow — the largest surface, 52 of 70 built
Property → floor plan → listing → capacity → contract → bookings
flowchart TD
LOGIN[Login with MFA]
HOST_DASH[Host Dashboard]
LOGIN --> HOST_DASH
LOGIN -.->|Should be invite-only| SELFSIGNUP[Self signup as host]:::intended
HOST_DASH --> PROPS[Properties List]
HOST_DASH --> PLANNING[Planning and Analysis]
HOST_DASH --> CONTRACTS[Member Agreements]
HOST_DASH --> HOST_BOOKINGS[Booking Visibility]
HOST_DASH -.->|2 of 10 built| HOST_REPORT[Reporting]:::intended
PROPS --> PROP_NEW[Create Property]
PROPS --> PROP_DETAIL[Property Detail]
PROP_NEW -.->|Storage not connected| PHOTO[Property Photos]:::intended
PROP_DETAIL --> FP_UPLOAD[Upload Floor Plan]
FP_UPLOAD --> FP_JOB[Extraction Job Status]
FP_JOB --> FP_REVIEW[Draft Review and Refine]
FP_REVIEW --> FP_ACCEPT[Accept Map]
FP_ACCEPT --> UNITS[Bookable Units materialised]
FP_UPLOAD -.->|Silently fails in prod| FP_PNG[PNG or JPG upload]:::broken
FP_UPLOAD -.->|Not built| FP_FLOORS[Multiple floors per listing]:::intended
PROP_DETAIL --> LIST_NEW[Create Listing]
LIST_NEW --> LIST_DETAIL[Listing Detail and Edit]
LIST_DETAIL --> CAPACITY[Capacity and Points Config]
CAPACITY --> BOOKABLE[Physical seats x 1.2 rounded down]
CAPACITY -.->|403 platform-managed| MULT_EDIT[Edit multiplier]:::intended
LIST_DETAIL --> AVAIL[Availability Rules and Closed Dates]
AVAIL -.->|Not built| HOLIDAYS[Statutory holiday defaults]:::intended
LIST_DETAIL -.->|No gate - dead end risk| ACTIVE_NOFP[Set ACTIVE without floor plan]:::broken
PLANNING --> SCENARIO[Scenario Modeler - seats in, pct share out]
SCENARIO --> ACTUALS[Compare against signed contracts]
SCENARIO --> MARGIN[Monthly revenue and net margin]
CONTRACTS --> AGREEMENT[Create Member Agreement]
AGREEMENT --> EXHIBIT[Contract Exhibit - seats per weekday]
AGREEMENT --> INVITE[Invite and copy magic link]
INVITE --> AUTOACTIVATE[Auto-activates on acceptance]
CONTRACTS --> TERMINATE[Terminate, keep history]
CONTRACTS -.->|UI stub| PDF_UPLOAD[Upload signed contract PDF]:::intended
CONTRACTS -.->|Not built| AMEND[Amend active contract]:::intended
HOST_BOOKINGS --> BOOK_DETAIL[Booking Detail]
HOST_BOOKINGS --> UNITMAP[Unit map for a date]
HOST_BOOKINGS -.->|Reopened Aug 18| ONBEHALF[Adjust on member behalf]:::intended
PROP_DETAIL -.->|Hard delete today| DELETE[Delete property or listing]:::broken
DELETE -.->|Subsystem does not exist| NOTIFY[Notify affected members]:::intended
classDef intended fill:#1c2330,stroke:#30363d,color:#636e7b,stroke-dasharray:4 4
classDef broken fill:#2a1a1c,stroke:#f78166,color:#f78166,stroke-dasharray:4 4
Member Admin Flow — 28 of 42 built
Onboard the company, manage people, allocate seats, plan the 20-day window
flowchart TD
MA_INVITE[Receive Agreement Invite]
MA_REVIEW[Review Agreement and Exhibit]
MA_ACCEPT[Accept - becomes Member Super Admin]
MA_DASH[Member Admin Dashboard]
MA_INVITE --> MA_REVIEW --> MA_ACCEPT --> MA_DASH
MA_DASH --> MASTER[Company Master File]
MASTER --> TERMS[View license contract terms]
MASTER --> BILLING[Edit billing address]
MASTER --> CONTACTS[Operational and on-site contacts]
MA_DASH --> PEOPLE[Set Up Individual Members]
PEOPLE --> USERINVITE[Invite - email plus magic link]
PEOPLE --> ROLES[Change role Admin or User]
PEOPLE --> QUOTA[Set per-member points or seat quota]
PEOPLE --> ACCESS[Scope listing access per guest]
PEOPLE -.->|Not built| BULKCSV[Bulk CSV upload]:::intended
PEOPLE -.->|Not built - 8B| BULKQUOTA[Bulk quota management]:::intended
PEOPLE -.->|Not built - 9B| BULKREMOVE[Bulk user removal]:::intended
PEOPLE -.->|Not built| PAUSE[Pause or disable a member]:::intended
PEOPLE -.->|Not built| DOMAIN[Restrict to corporate email domain]:::intended
MA_DASH -.->|V2| GROUPS[Member Groups]:::intended
MA_DASH --> BOOKINGS[Seat Bookings]
BOOKINGS --> SELFBOOK[Book a seat for self]
BOOKINGS --> ASSIGN[Assign member to a seat-day in a block]
BOOKINGS -.->|Built - not in nav| BLOCK[Reserve a block of seats]:::orphan
BOOKINGS -.->|Not built| EDITBOOK[Edit an existing booking]:::intended
MA_DASH --> PLAN[Planning and Analysis]
PLAN --> BALANCE[Points balance, cap, next accrual]
PLAN --> CALENDAR[20-business-day planner]
PLAN --> ESTIMATE[Estimate cost before committing]
PLAN -.->|Required, not built| CONVERT[Live seats to points conversion]:::intended
MA_DASH -.->|4 of 8 built| MA_REPORT[Reporting]:::intended
MA_REPORT -.->|Should be removed| LEGACY[Legacy credits screens]:::orphan
classDef intended fill:#1c2330,stroke:#30363d,color:#636e7b,stroke-dasharray:4 4
classDef orphan fill:#2a2416,stroke:#d29922,color:#d29922,stroke-dasharray:4 4
Member / Guest Flow — the most complete persona, 33 of 37 built
Onboard, find space, book visually, manage bookings
flowchart TD
G_INVITE[Accept guest invite - magic link]
G_LOGIN[Create account, log in with MFA]
G_DASH[Guest Dashboard]
G_INVITE --> G_LOGIN --> G_DASH
G_DASH --> FIND[Find Space]
FIND --> BROWSE[Browse listings - scoped by access]
FIND --> LISTDETAIL[Listing detail, amenities, floor plan]
FIND --> AVAILCAL[Month availability calendar]
FIND -.->|Page orphaned| FAV[Favourite a listing]:::orphan
G_DASH --> BOOK[Book a Seat]
BOOK --> DATES[Select date or date range]
BOOK --> VISUAL[Select seat on the floor plan - click, drag, shift]
BOOK --> COST[Live points cost by date and seat type]
BOOK --> REMAIN[Balance remaining after booking]
BOOK --> CONFIRM[Review and confirm]
BOOK -.-> RESTRICT[Booking Restrictions]
RESTRICT --> R1[Insufficient points]
RESTRICT --> R2[Beyond 20-business-day horizon]
RESTRICT --> R3[Closed day or date]
RESTRICT --> R4[Unit already booked - DB guard]
RESTRICT --> R5[Listing access not granted]
RESTRICT -.->|Only shown at submit| R6[No active license contract]:::broken
RESTRICT -.->|80pct or 100pct - unresolved| R7[Company over daily floor capacity]:::intended
G_DASH --> MANAGE[Manage Bookings]
MANAGE --> UPCOMING[Upcoming, past and cancelled]
MANAGE --> CANCEL[Cancel - refund if over 24h out]
MANAGE --> CLAIM[Claim a seat-day from a company block]
MANAGE -.->|Not built| GEDIT[Edit a booking]:::intended
G_DASH --> GPLAN[Planning and Analysis]
GPLAN --> GBAL[Balance, cap, next accrual]
GPLAN --> GWEIGHTS[Day and seat-type cost weights]
GPLAN --> GEXHIBIT[What my contract allows]
G_DASH -.->|Does not exist| EMAIL[Booking confirmation email]:::intended
classDef intended fill:#1c2330,stroke:#30363d,color:#636e7b,stroke-dasharray:4 4
classDef orphan fill:#2a2416,stroke:#d29922,color:#d29922,stroke-dasharray:4 4
classDef broken fill:#2a1a1c,stroke:#f78166,color:#f78166,stroke-dasharray:4 4
Source: HQ-Lite-Activity-List-v1.0, 18 Aug 2026 — four persona columns, 30 sections, 176 activities, plus a second sheet of seven end-to-end journeys and 43 steps. The map is the pre-meeting version; roughly ten requirements added during the Aug 18 walkthrough (bulk CSV, 8B, 9B, sq ft cap, member pause, delete notifications, contract start date, analytics rename, legal artifacts) are reflected in the diagrams above but not yet in the spreadsheet.
Data Model
Entity Relationships
The schema has grown well beyond the original booking core. Three subsystems now sit alongside it: the floor-plan pipeline, the contract and points engine, and access control. Double-booking prevention still lives where it always did — a unique constraint on the BookingUnit junction table.
Core booking and inventory
erDiagram
User {
string id PK
string email
string role "HOST MEMBER MEMBER_SUPER_ADMIN"
string companyId FK
string seatAllocation
string deletedAt
}
Company {
string id PK
string name
string billingAddress
string operationalContact
string onSiteContact
}
Property {
string id PK
string hostId FK
string name
string address
string deletedAt
}
Listing {
string id PK
string propertyId FK
string name
string hostType "OWNER or HEAD_LESSEE"
int totalUnits "physical seats"
int bookableUnits "floor physical x 1.2"
string grossRentAnnual
string sqFootage
string leaseTerm
string defaultIncludedServices
string status "DRAFT ACTIVE INACTIVE ARCHIVED"
string deletedAt
}
Unit {
string id PK
string listingId FK
string name
string unitType "WORKSTATION DESK OFFICE"
boolean pointsBearing
string deletedAt
}
AvailabilityRule {
string id PK
string listingId FK
string date
string reason
}
Booking {
string id PK
string memberId FK
string listingId FK
string startDate
string endDate
string status
string referenceCode
string cancelledAt
}
BookingUnit {
string id PK
string bookingId FK
string unitId FK
string date
}
Company ||--o{ User : employs
User ||--o{ Property : owns
Property ||--o{ Listing : contains
Listing ||--o{ Unit : has
Listing ||--o{ AvailabilityRule : blocks
User ||--o{ Booking : makes
Booking ||--o{ BookingUnit : includes
Unit ||--o{ BookingUnit : fills
Contracts, points and planning
erDiagram
LicenseContract {
string id PK
string hostMembershipId FK
string companyId FK
string hostMemberInviteId FK
string monthlyFee
string startDate "first Monday of month"
string endDate
string status "DRAFT ACTIVE TERMINATED"
string paymentStatus
json pointWeightsSnapshot "frozen at activation"
}
HostMembership {
string id PK
string listingId FK
string companyId FK
float pctShare "0.0 to 1.0"
int dailyPointsBudget "floor bookableUnits x pctShare"
}
PointsTransaction {
string id PK
string companyId FK
string bookingId FK
decimal change "10,2 - fractional costs"
string date "the seat-day"
string createdAt "drives balance replay"
string kind
}
MemberPlanScenario {
string id PK
string contractId FK
json grid "20 business days x seat type"
string updatedAt
}
ListingPlanScenario {
string id PK
string listingId FK
json memberRows
string updatedAt
}
UserListingAccess {
string id PK
string userId FK
string listingId FK
}
AuditEvent {
string id PK
string actorId FK
string kind "20+ event kinds"
json payload
string createdAt
}
HostMembership ||--o| LicenseContract : governs
LicenseContract ||--o{ PointsTransaction : meters
LicenseContract ||--o| MemberPlanScenario : plans
LicenseContract ||--o{ UserListingAccess : scopes
Floor plan pipeline
erDiagram
FloorPlanDocument {
string id PK
string propertyId FK
string filename
string mimeType "PDF works, PNG and JPG fail in prod"
string status
string storagePath
}
FloorPlanPage {
string id PK
string documentId FK
int pageNumber
string rasterPath
}
FloorMap {
string id PK
string pageId FK
string status "DRAFT ACCEPTED"
string annotatedPreviewPath
float confidence
}
FloorMapObject {
string id PK
string floorMapId FK
string objectType "WORKSTATION OFFICE CONFERENCE_ROOM"
json bbox
boolean confirmed
}
FloorPlanDocument ||--o{ FloorPlanPage : renders
FloorPlanPage ||--o| FloorMap : extracts
FloorMap ||--o{ FloorMapObject : detects
Double-booking prevention
A unique constraint on [unitId, date] in BookingUnit is the database-level guarantee. One row per unit per day. A three-desk booking for five days creates fifteen rows. Enforced in the DB, not in application logic.
Points balance is replayed, never stored
There is no stored balance. replayBalance walks the PointsTransaction ledger ordered by createdAt, applying a weekly accrual of seats x 5 clamped to a cap of seats x 20 at every accrual. Unused capacity is forfeited, not banked. The model is path-dependent.
Contracts freeze their own pricing
pointWeightsSnapshot is written at activation, so re-tuning the platform day weights cannot change what a signed contract costs. The client prices only from what GET /points/window returns — never from imported constants.
Soft delete is inconsistent
User, Property, Listing and Unit carry deletedAt, and Matt's super admin panel implements a real 30-day soft delete with a ~90-day purge for users. But deleting a property or listing still hard-deletes the related Booking and BookingUnit rows. Agreed Aug 18: soft delete everywhere, plus a hidden trash bucket. Cascade semantics are undefined.
No notification entity
There is no notification subsystem at all. It became a blocking dependency on Aug 18: if a host deletes a listing, affected members must be told their bookings were cancelled. Email is stubbed everywhere — confirmation, cancellation, password reset.
Multiple floors is a modelling question
Not just a UI gap. Two competing shapes: N listings of one floor each, versus one listing containing N floors. The forcing case is a member renting several floors who needs to see and book across all of them. Live today at Brandon's own office. No decision.
CreditLedger and CompanySubscription still exist
The pre-points billing system. Preserved only in the legacy /credits/* routes and the CreditLedger / CompanySubscription tables. The word "credits" must not appear in any new user-facing text or DTO field. The orphaned UI screens that read from them are flagged for removal.
dailyPointsBudget is misnamed
It is not a daily budget. It is the per-seat coefficient: the cap is dailyPointsBudget x 20 and the weekly accrual is dailyPointsBudget x 5. Left unrenamed deliberately to avoid churn. Similarly, listings.capacityMultiplier is retained and backfilled to 1.2 but business logic reads the platform constant instead.
Completeness Gaps • Post Aug 18
What Still Stands Between This and a Product
The build is 85–90% complete and the team has shifted from building features to finding bugs. These are the gaps between "it functions" and "an enterprise customer would trust it with financial data." Ordered by what blocks the next milestone, not by size.
The framing that matters
Matt, Aug 18:
"It's code, it's features. It's going to be data. And so it's a product that's not a couple workflows that someone else could have automated on Excel. There's going to be a hardening process of moving away from that vibe-coded term to, this is an enterprise production-level application." And the reason:
"It's financial data, it's financial implications to their business. If something goes wrong and we can't answer why, they're going to say goodbye."
Blocking — nothing else proceeds until these are fixed
✗
Points balance miscalculation BUG
Seat count is being conflated with the points multiplier somewhere in the calculation. Seen live on Aug 4 and still open. Tony on Aug 10: "if none of the numbers match up you don't know what you're looking at and it doesn't make sense to try to get into testing anyway." This gates the entire test plan.
✗
Floor plan and file linkage broken BUG
The floor plan image and its associated file do not link correctly in the live build. Seen during the Aug 4 demo review.
✗
PNG and JPG floor plans silently fail in production BUG PROD
OCR is gated to macOS, so any raster upload that is not a PDF fails without surfacing an error to the host. Found in the activity map, never raised verbally in any meeting. This is the sharpest engineering find in the document — the failure is silent, and floor plan upload is the first meaningful action a new host takes.
✗
A listing can go ACTIVE with no floor plan BUG
No gate. A host can publish a listing with no bookable units behind it, which dead-ends any member who finds it. Also surfaced only in the activity map.
Structural — the hardening work
✗
Host lifecycle — zero of six activities built NOT BUILT
No admin-created host, no host invite, no enter or modify host info. Deactivation is a Cognito disable only. GDPR deletion is API-only with no UI. Tony: "we're middlemanning it right now by manually creating the accounts and then operating those accounts." Also decided: hosts should be invited, never self-serve — the existing self-signup path is now considered wrong.
✗
Operator tunables have no interface at all NO UI CLI ONLY
The over-sell factor (1.2×) and every day and seat-type point weight are hard-coded constants. Changing a Wednesday weight today means a code change and a redeploy. Sensitivity analysis is npm run simulate:points from a terminal. Aug 18 asked for both to be settable globally and per-account. This is the difference between a product and a bespoke deployment.
✗
Soft delete for properties and listings PARTIAL
Hard delete today, which destroys booking history and any analytics built on it. Rob: "maybe they can call it delete, but it's like in your email when you delete it, it's still sitting in the trash bucket. And maybe they don't have access to the trash bucket." Soft delete already exists for users in Matt's super admin panel with a 30-day window and ~90-day purge — the pattern is proven, it just needs extending.
✗
Notification subsystem does not exist NOT BUILT
Every email is stubbed: booking confirmation, cancellation, password reset. It became a hard dependency on Aug 18 — deleting a listing must tell affected members their bookings are gone. Password reset in particular means any user who forgets their password is locked out today.
~
Four features are built but unreachable ORPHANED
Working code with no navigation entry point: the favourites page, Reserve a block of seats, Company bookings list with filters, and the Member (company) utilization table. Plus dead legacy credits and subscription screens still rendering. Matt independently asked for a decluttering pass: "there's a lot of leftover mess after all the work that's been happening."
Workflow — decided, specced, not built
✗
Bidirectional seats ↔ points conversion
Rob: "the person that's the member admin doesn't care about points. If they don't have the clear visibility link between the points and the seats, they're going to spin." Input is seats; output is points spent and seats remaining, priced by the matrix — e.g. 20 seats Wednesday, 30 seats Friday. Required at every decision point, not just on the balance card.
✗
Block booking as a surface separate from individual desk selection
Block booking is a commitment that consumes points (member admin). Picking a specific desk within an existing block is not a booking and should be freely adjustable up to a cutoff about a week out (end user). Conflating them produces bad UX and bad accounting. Unselected seats release to the general pool at the cutoff.
✗
Cancellation tiers and booking edit
More than a week out: self-serve full refund. Two to three days: member admin discretionary override. Day-of and no-show: forfeited. Separately, editing a booking well in advance must not be penalised — today the only path is cancel and rebook, which forfeits points. Open: is 24 hours the right cutoff, and does the host get an override? Steve asked for one and it was left dangling.
✗
Bulk member operations — CSV, 8B, 9B
Bulk CSV upload for adding members, bulk quota management, bulk removal. One-by-one entry breaks past roughly 50 users. Numbered 8B and 9B live in the Aug 18 meeting to slot into the existing list without renumbering.
✗
Member pause and disable state
Active and terminated are not enough. Hosts need to suspend a member without deleting them — for payment disputes, overbooking abuse, or conduct. Reached by argument on Aug 18: the payment-status field was nearly cut, then retained once Rob reframed the need as enforcement rather than billing.
✗
Square-footage provisioning and hard cap
Pricing is per square foot, so access must be throttled to what the host paid for. Tony's model: one blanket number on the account rather than per-property accounting — "you've got a million square feet, and once you hit that cap you're going to be blocked from creating any more listings." Placement in the map deliberately left open between Super Admin and Host.
~
Reporting is thin on both sides
Host: 2 of 10 built. Missing member list with terms, projected contract revenue, upcoming expiries, seat usage by member over day/month/year, daily on-site occupant roster, and a churn or declining-usage alert. Super admin: 1 of 4. Member admin: 4 of 8. Rob and Brandon both flagged churn risk and customer health as the reason this matters commercially.
~
Floor plan colour-coding with external anonymisation
Internal seats show names; seats belonging to another company must be colour-coded only, with no name revealed on hover. A GDPR concern in a multi-tenant floor plan, and enforcement has to sit at the data layer rather than the UI layer.
~
Property photo upload — cloud storage not connected UI STUB
The UI accepts photos but S3 is not wired, so nothing persists and listings appear visually empty. Long-standing, still open.
Deliberately deferred
Floor plan automation
Explicitly deprioritised for five weeks on Aug 18. Two real training samples; a synthetic-training experiment scored 0%. White-glove manual processing for the first 20–50 clients instead, doubling as a training-data harvest. Matt: "if we spent the next five weeks just working on the upload, I would say we're spending our time on the wrong thing."
DWG support
Preferred format — exact dimensions, more automatable — but a significantly larger build. PDF stays for V1. Trigger for revisiting: onboarding friction with real client files.
Member Groups
V2. The underlying need (bulk reserve for a recurring team) is real and was decoupled from the mechanism — "maybe it's not member group, just some other flag." Robin and Envoy cited as prior art.
Payment processing
Still out of scope. Monthly fee is recorded on the contract for display and planning only. HQ Lite is explicitly not a billing system.
Badge and swipe integration
The enforcement model needs an entry audit log, but hardware integration is a V2+ spoke. Network isolation (per-tenant IDF closet and private SSID) is the host's responsibility, not the platform's.
Boilerplate LOI and licence templates
Agreed valuable in July, then deferred on Aug 10 — "we're dealing with more sophisticated users, so I don't think there's even a point of spending time on that yet."
Open questions that block design, not code
?Is the daily capacity cap 80% of sold seats or 100% of physical seats — and what does the system actually enforce today?Brandon thought it had been removed. Rob argued it must stay but be re-based on physical seats. Tony: "I'm not 100% what logic we've implemented."
?When exactly is a point transaction confirmed?Brandon: "that's an area we just haven't fully thought through." Points are spent at booking confirmation, but the edit, refund and host-override paths around that are not designed.
?How are multiple floors modelled?One listing with N floors, or N listings? A data-model decision, and it is already live at a real office.
?Does the offices-versus-workstations taxonomy survive a real floor plan?Matt: "when we get a real floor plan does that actually work? We'll find out." The point matrix assumes two seat types.
?Where does the standalone points-calculation logic live?Rob: "the calculations of the bookings are not an activity of a person, so it's hard to fit it in the structure." A green-highlight overlay on the activity map was agreed; the separate logic section was not created.
?What operational data does a super admin need to run the business?Matt asked the group directly on Aug 18 and got no answer.
Infra Status
Production Reality and Open Items
The stack is live. Web on Amplify, API on App Runner, Postgres on RDS, auth on Cognito, all managed with Terraform. The open items are no longer "stand it up" — they are migrations waiting to be applied, observability that is provisioned but switched off, and the compliance work that an enterprise buyer will ask about.
Deployed and running
AWS Amplify
hqlite.tonymeekes.ca — custom domain and TLS are live. Vite + React 19 + TypeScript.
AWS App Runner
Node + Express 5 + Prisma 7. An ECS Express Mode migration plan exists (ADR-022) and should be executed before App Runner is sunset.
Cognito, live in prod
AUTH_PROVIDER=cognito. TOTP MFA with QR registration works. Fake auth remains for local dev only. Matt: "I don't know if it's going to be Cognito all the time" — the auth layer is kept thin and swappable.
RDS Postgres, private
Not publicly accessible. Backup retention set to 14 days with a documented restore runbook. Production data is clean — all seeded inventory was wiped in April 2026.
VPC-attached ops Lambda
hqlite-prod-reset-inventory (ADR-025). The only safe path to production data operations, since RDS has no public endpoint. Supports a dry-run payload.
Audit and access controls
AuditEvent schema and service with 20+ event kinds, instrumented across the app. User deletion endpoint and purge script. Cognito export and stale-account disable scripts. Q2 2026 access review documented.
Open — blocking or near-blocking
| Item | Detail | Priority | Owner |
| Five pending Prisma migrations |
Not yet applied to prod: 20260418020000 (audit kinds), 20260615000000 (LicenseContract), 20260706000000 (plan scenarios + capacity lock), 20260707000000 (member agreement flow — drops properties.hostType), 20260721000000 (points change Int → Decimal), 20260727000000/01 (Round 2), 20260729000000/01 (weights snapshot + user listing access). Apply via the ops Lambda or a one-off task. |
P0 |
Matt |
| prisma generate not run |
Blocked in the agent sandbox (403 on the schema-engine binary). New models are reached via raw SQL so TypeScript compiles without it, but the client must be regenerated before merge — and the as never casts tagged // TODO(matt) removed. |
P0 |
Matt |
| Prisma migration automation |
Migrations are not run by CI/CD. Every schema change requires manual intervention after deploy — which is precisely why five are outstanding. |
P0 |
Matt |
| Observability provisioned but off |
CloudTrail, GuardDuty, Security Hub, Config, the security-alerts SNS topic and CloudWatch alarms are all written in Terraform but not applied. Needs enable_observability = true in env/prod/terraform.tfvars and a terraform apply. |
P0 |
Matt |
| S3 not connected for photos |
Property photo upload accepts files in the UI and persists nothing. Also relevant to floor-plan storage, which currently defaults to the OS temp directory unless FLOOR_PLAN_STORAGE_DIR is set in production. |
P1 |
Matt |
| Email delivery |
No provider wired. Every notification path is stubbed, including password reset — which means a locked-out user has no self-service recovery. Became a hard dependency of soft delete on Aug 18. |
P1 |
Matt |
| Environment-safe seed strategy |
Dev and staging only, with a hard block preventing a seed run against production. |
P1 |
Matt |
| ECS Express Mode migration |
Plan written in docs/plans/ADR-022-ecs-express-migration.md. Must be executed before App Runner is sunset. |
P1 |
Matt |
| OCR macOS dependency |
The root cause of PNG and JPG floor-plan uploads failing silently in production. An infrastructure problem surfacing as a product bug. |
P0 |
Matt |
Compliance — the enterprise-readiness track
SOC 2
Assessed on Aug 18 as the long-term, most complex item: "SOC 2 has distinct requirements. That one actually will need some specific focus. But most of that's just technical." The observability stack sitting unapplied is a direct prerequisite.
GDPR
Framed as more tractable — data handling and right to be forgotten. Host deletion exists but is API-only with no UI. Mitigating argument is data minimisation: "we get their name and their email, but we're not keeping anything crazy about them."
PIPEDA
Named on Aug 18 alongside GDPR. The US was assessed as the least demanding of the three jurisdictions. Data classification and retention policy are already documented.
Pre-launch legal artifacts — none of these exist yet
Added as line items on Aug 18:
terms and conditions, a
legally approved privacy agreement, and the
HQ Lite to host license agreement. All three are required before any public launch, and none is a code task.
Architecture decision reaffirmed Aug 18: cloud-only on AWS
On-prem, self-hosted, and per-host isolated instances were explicitly rejected. The reason is not cost — it is the cross-host member case:
"if you ever had users who had cross-host use cases where they're like, I'm in this location on Mondays and I go to this location on Thursdays, you have the ability for all that to be managed in one system." A single escape hatch was reserved for a mega-customer with a hard security or hybrid requirement.
Where the risk actually sits: the infrastructure is in better shape than the operational discipline around it. Five migrations are queued because nothing applies them automatically; the security monitoring stack is written but switched off; and the one bug most likely to be hit by a brand-new customer — a floor plan upload failing silently — traces back to a platform dependency rather than application code. None of these is hard. All of them are the difference between a demo and a product.