Overview  ·  Project plan  ·  Lead spec
Build source of truth · v1 · living doc

System design: how the whole machine fits together

The Overview (/) is what we promised. The lead-comms spec is how the comms behave. This page is how it's all built: every component, every data flow, every interface contract, and the order of the build. Any session building Foothold backend reads this first and keeps the statuses current.

0. How to use this doc

For future build sessions (Claude Code included): (1) Read this page before touching any backend piece. (2) Every engine on the Overview maps to a numbered flow below; build to the flow, not to memory. (3) The Constitution (§2) is binding; violating a rule needs Brent's explicit sign-off, not a judgment call. (4) When you build or change something: update the component status here, add a changelog line at the bottom, and update Marketing/30_Foothold/CONTEXT.md. Diagrams are Mermaid source in this file's HTML; edit them as text.
LIVE in production · BUILT exists, not serving real traffic · IN PROGRESS being worked · BLOCKED waiting on someone else · DECIDED spec locked, not started · PLANNED intended, spec open · PARKED deliberately deferred

1. The system map

Every box that exists or will exist, and what talks to what. The one architectural fact that shapes everything: Entrata only accepts calls from the VPS's static IP (5.78.139.104), and that box is outbound-only. So nothing else ever calls Entrata; everything Entrata-bound goes through an outbox the VPS pulls.

flowchart LR
  subgraph FRONT["Front doors (renters & residents)"]
    SITE["footholdboise.com
Astro · CF Pages"] ILS["Apartments.com / Zillow
via Entrata ILS syndication"] NUM["Ownership # + per-property #
Twilio SMS · calls fwd to LAG"] end subgraph EDGE["Cloudflare edge (Workers)"] WAIT["/api/waitlist"] AGENT["AI leasing agent
Claude · v1 chat, v2 Agents SDK"] ENG["Recognition + review engines
cron Workers"] KV[("KV
availability cache")] OUTBOX[("Entrata outbox
Queues, pull-consumer")] end subgraph VPS["VPS 5.78.139.104 — the only IP Entrata sees"] POLL["Availability poller
12-min, serial gate"] RELAY["Entrata relay
outbox consumer, deterministic"] end subgraph SAAS["Owned SaaS"] GHL["GoHighLevel
lead store · tags · drips"] SUPA[("Supabase
agent datastore")] HW["Handwrytten
mailed cards + gift cards"] TRE["Tremendous
digital gift cards"] end ENT["ENTRATA
Liberty's system of record"] LAG["Liberty Asset Group
works natively in Entrata"] FD["FlightDeck
owner analytics"] SITE --> WAIT --> GHL SITE --> AGENT ILS -- "lead email (1 of 6)" --> AGENT ILS -- "native CoStar feed" --> ENT NUM --> AGENT AGENT --> GHL AGENT -- "reads" --> KV AGENT -- "state" --> SUPA AGENT -- "intents" --> OUTBOX ENG --> HW ENG --> TRE ENG -- "asks via Twilio" --> NUM ENG -- "log notes" --> OUTBOX POLL -- "getUnitsAvailabilityAndPricing" --> ENT POLL -- "publish" --> KV KV -- "/rentals reads" --> SITE OUTBOX -- "HTTPS pull" --> RELAY RELAY -- "sendLeads / updateLeads / sendWorkOrders" --> ENT ENT --- LAG ENT -- "financials + WOs + leases" --> FD GHL -- "funnel metrics" --> FD classDef live fill:#e2efe5,stroke:#1f6b3a,stroke-width:1.5px classDef built fill:#e6e9f3,stroke:#3a4a86,stroke-width:1.5px classDef planned fill:#fff,stroke:#7a7264,stroke-dasharray:5 4 classDef ext fill:#f7ede4,stroke:#bb6238,stroke-width:1.5px class SITE,WAIT,GHL live class POLL,RELAY built class AGENT,ENG,KV,OUTBOX,SUPA,HW,TRE,FD,NUM planned class ENT,ILS,LAG ext

Component inventory

ComponentRuns onRepo / locationStatusNotes
Public site + /rentalsCloudflare Pagesfoothold-webLIVEGated (NPIfoothold) + noindex until launch. /rentals still static; wires to KV in Phase 1.
Waitlist → GHLCF Workerfoothold-web src/pages/api/waitlist.tsLIVEUpsert + tags; ?ref=<email> referral capture. Proves the GHL write path.
Ops binderCloudflare Pagesfoothold-opsLIVEThis site. Push = deploy (~30s).
Entrata service (poller + relay)VPS 5.78.139.104/opt/foothold-entrata, systemdBUILTMock mode. Serial gate + rate-limit capture in. Live client stubbed; config.js still maps old username/password vars — wire ENTRATA_API_KEY.
Entrata accessAgreement + dev portal + API keyBLOCKEDKey on VPS. Full OpenAPI spec captured (/opt/foothold-entrata/entrata-openapi.yaml). Blocker: Liberty's Add-API-User grant (403 code 115 as of 2026-07-28). Subdomain libertyassetgroup (high confidence).
KV availability cacheCloudflare KVnamespace TBDPLANNEDCF token is Pages-scope only; namespace creation needs dashboard.
Entrata outboxCF Queues (recommended)TBDPLANNEDPull-consumer over HTTPS keeps the VPS outbound-only. Decide transport at build (§13).
AI leasing agentCF Worker → Agents SDKspec: strategy/10-ai-agents-spec.mdDECIDEDv1 chat, v2 multi-channel. Tools: check_availability, capture_lead, book_tour, start_application. Daily Opus QA cron.
Agent datastoreSupabasenot provisionedDECIDEDLead-comms §8: tiered freshness. Conversation state, resident facts, recognition ledger.
TelephonyTwilio directnot provisionedDECIDEDBrand # + per-property 208 #s, A2P 10DLC, STOP handling. GHL stays for bulk drips only.
Recognition engineCF cron WorkerDECIDEDHandwrytten (mail + gift) / Tremendous (digital). Touch map locked in lead-comms §4.
Review / social-proof engineCF cron WorkerDECIDEDEvent-triggered, never sentiment-gated. Smart links Google + Apartments.com.
Unit media packagesLiberty marketing teamIN PROGRESSPhotography + 2D floor plans requested of Liberty 2026-07-20 (3D later, on turns).
Lead magnet + funnel ladderGHL workflowslead-magnets/ (Drive)IN PROGRESSGuide outlined, not drafted. Tags: foothold-guide-rent, foothold-sms-optin.
FlightDeck wiringCloudflareflightdeck-crePLANNEDPulls Entrata financials + engine metrics per property. Phase 5.
AI voicePARKEDv1 = all calls forward to LAG. Same brain points at voice later (sequencing call, not a liability one).

2. The constitution (binding rules, every build)

These came out of research, the Entrata agreement, and the EliseAI post-mortem. They are not preferences. A build that violates one is wrong even if it works.

  1. Only the VPS talks to Entrata. Its static IP is what Entrata allowlists. The box stays outbound-only (no inbound ports); Workers reach it only via the pull-based outbox. One serial gate per the whole account — Entrata forbids concurrent calls.
  2. The AI never calls the Entrata API. The access application states no AI touches Entrata; integration code is deterministic. The agent emits structured intents; the relay validates and executes them. Gate: before the agent answers prospects from Entrata-derived data (availability), do the Entrata AI-disclosure review — that crosses into their AI/ML rules.
  3. Every 1:1 touch logs to Entrata (updateLeads note): recognition sends, escalation replies, agent messages, both directions. Liberty sees everything. Only bulk marketing (drips/broadcasts) doesn't log per-contact.
  4. Minimum-info gate: no guest card without first name + one contact method. Until then the conversation lives in our stack only.
  5. Never state availability or price without a fresh read. Stale cache or API down → "let me confirm that and get right back to you." This is the EliseAI-proof and the reason Liberty fired the last vendor.
  6. Review asks trigger on events and go to everyone at that event. Sentiment-gating is prohibited (FTC, Google, Apartments.com). Never re-ask within ~12 months of a public review.
  7. Fair housing: no steering, same facts to everyone, no demographic characterizations, no promises. The move-in-timing priority rule stays written, neutral, and mechanical. HUD treats AI answers as FHA-subject.
  8. All Entrata dates/times are Mountain Time. Parse as explicit MT; the VPS runs Pacific, and a lazy parse is a one-day error on dates near midnight.
  9. Handoff line: ownership owns through tour-booked or application-submitted (plus one post-tour feedback text); LAG owns everything after. The agent owns the conversation channel; LAG works from the logged notes — two people never reply to the same renter.
  10. Secrets live in /opt/foothold-entrata/.env (600) and Worker secrets. Never in repos, never echoed in logs or error paths, never in this binder.

3. Data topology: four stores, four jobs

Each store owns one kind of truth. When two docs disagree about "where data lives," this table governs.

StoreOwns (the truth for)Written byRead byFreshness
EntrataOperational record: units, availability, guest cards, leases, work orders, residentsVPS relay only (our side); LAG nativelyVPS pollerSource of truth
Cloudflare KVAvailability cache the site + agent readVPS poller/rentals, agent check_availability≤12 min + live re-check at commit
GoHighLevelMarketing: leads, tags, drips, referral attributionWaitlist Worker, agent capture_lead, GHL workflowsAgent, FlightDeck funnel metricsReal-time
SupabaseAgent state: conversations, resident facts (pets, birthdays, tenure), recognition ledgerAgent, engines, daily VPS pullAgent, enginesTiered (lead-comms §8)
Reconciliations this table settles: (1) "GHL is the lead store, no second DB" (CONTEXT.md) was the waitlist-era answer and still holds for leads; Supabase is the agent's datastore per lead-comms §8 — different truths, both stand. (2) The authoritative conversation transcript is ours (agent datastore); Entrata gets the complete mirror. (3) Applications are ProspectPortal links, not API writes — the Overview's "files the application" means "sends the apply link and follows up," per the locked edge case.

Contracts to pin at build time

InterfaceProposed shape (finalize in Phase 1–2)
KV keysavail:<propertyId>{updatedAt, propertyName, units:[{unitId, unitNumber, floorPlan, beds, baths, sqft, rent, availableDate, status}]} + avail:index (property list + last-sync). Mock poller already writes this shape to cache/availability.json.
Outbox message{intent: "logNote"|"sendLead"|"sendWorkOrder", propertyId, payload, dedupeKey, createdAt}. Relay validates against a whitelist of intents — the deterministic boundary of rule 2.
VPS env (names only)MODE, ENTRATA_API_KEY, ENTRATA_DOMAIN, ENTRATA_PROPERTY_IDS, POLL_INTERVAL_MIN, CACHE_DIR, CF_ACCOUNT_ID, CF_KV_NAMESPACE_ID, CF_API_TOKEN, GHL_API_KEY, GHL_LOCATION_ID. (ENTRATA_USERNAME/PASSWORD are legacy from the pre-gateway guess; remove when wiring the key.)
GHL tagsfoothold-guide-rent (lead magnet), foothold-sms-optin (Insider list), waitlist source tags per foothold-web src/lib/ghl.ts (code is SoT).

4. The Entrata protocol (applies to every flow below)

Established by live probes + the captured OpenAPI spec (2026-07-28). Spec copies: /opt/foothold-entrata/entrata-openapi.yaml (VPS) and Marketing/30_Foothold/entrata/entrata-openapi-2026-07-28.yaml (Drive). 18 groups, 128 methods, 302 schemas.

FactDetail
Base URLhttps://apis.entrata.com/ext/orgs/<subdomain>/v1/<group> — subdomain is the client's (libertyassetgroup, pending written confirm), group is the URL, method goes in the body. POST only.
AuthHeader X-Api-Key + Content-Type: application/json. Body envelope: {"auth":{"type":"apikey"},"requestId":"n","method":{"name":"...","version":"r1","params":{...}}}.
Method → group map (the gotcha)getUnitsAvailabilityAndPricing, getPropertyUnits, getAmenities, getSpecialspropertyunits. getProperties, getFloorPlans, getPropertyMediaproperties. sendLeads, updateLeads, getLeads, getLeadPickListsleads. getWorkOrders, sendWorkOrders, updateWorkOrders, getWorkOrderPickListsmaintenance. getLeases, getLeaseDetails, getExpiringLeasesleases. getCustomerscustomers. getGlTransactions, getBudgets, getBudgetActuals, getBankAccountsfinancial.
DiscoverygetAccessibleClients on /ext/orgs/rsync/v1/appinfo (literal rsync) returns every org the key can reach: {subdomain, id, name}. First call to run when Liberty's grant lands — confirms subdomain + scope. getProperties then populates ENTRATA_PROPERTY_IDS. Health: getStatusstatus.
Rate limitsPer-minute / hour / day, per service, returned in every response's headers. No concurrent calls, ever (serial gate). Poll cadence 10–15 min per plan; ~8–12K calls/mo declared in the application.
Maintenance windowsScheduled: Tue late night → Wed early morning MST, weekly. Unscheduled: late evening/early morning Sun/Mon/Wed/Thu. The poller treats downtime as normal: back off, serve last-good cache, no alarms unless stale > 2h.
TimezoneEverything MT unless stated. Parse explicit; display MT (Boise is MT).
Error vocabulary400/1415 missing Content-Type · 400/1404 method not found (wrong group path) · 401/405 client exists, key not permissioned · 403/115 key valid, webservice not enabled · 404/111 no such client · 405/1405 wrong verb.
Open architecture question (answer before wiring Phase 1): the dev portal documents Webhooks. If Entrata pushes availability + lead events, the 12-minute poll may shrink to a reconciliation pass and most of the 8–12K calls/mo disappear. Read that portal page first. (Webhooks would need an inbound endpoint — put it on a Worker, not the VPS, to keep the box outbound-only.)

5. Flow A — availability pipeline BUILT (mock) BLOCKED (live)

Feeds: /rentals, the agent's check_availability, ILS freshness. KPI: apps-per-tour + vacancy days. The EliseAI failure this design exists to prevent lives here.

sequenceDiagram
  autonumber
  participant T as VPS timer (12 min)
  participant G as Serial gate
  participant E as Entrata
  participant K as CF KV
  participant W as /rentals
  participant A as AI agent
  T->>G: tick (skip inside maintenance windows)
  G->>E: POST /{org}/v1/propertyunits — getUnitsAvailabilityAndPricing (per property)
  E-->>G: units + pricing (MT dates) + rate-limit headers
  G->>K: publish avail:{propertyId} + avail:index
  W->>K: read on page view (never hits Entrata)
  A->>K: check_availability tool
  Note over A,E: Commit moment (quote / apply link):
one targeted live re-check via the relay — sparingly, it spends rate budget Note over G,E: Backoff ladder: rate-header low → stretch interval;
5xx/maintenance → serve last-good, alert only if stale > 2h

6. Flow B — lead capture & speed-to-lead PLANNED

KPI 1 (apps per tour). The agent answers in seconds on its own number; Entrata gets the complete mirror. Handoff at tour-booked or application-submitted.

sequenceDiagram
  autonumber
  participant P as Prospect
  participant I as Apartments.com / Zillow
  participant A as AI agent
  participant H as GHL
  participant Q as Outbox
  participant V as VPS relay
  participant E as Entrata
  participant L as Liberty
  P->>I: inquiry (real name, email, phone, move-in date)
  I->>E: native CoStar feed → guest card
  I->>A: lead email (one of 6 destinations)
  A->>P: reply in seconds, own number, 24/7
  Note over A: Min-info gate: first name + one contact method
before anything touches Entrata A->>H: capture_lead + tags + source A->>Q: intent: logNote / sendLead Q->>V: HTTPS pull (outbound-only box) V->>E: getLeads by email — dedupe first alt card exists (ILS path) V->>E: updateLeads — note on existing card else no card (site / chat / SMS lead) V->>E: sendLeads — create guest card end Note over V,E: EVERY message both directions logs as a note.
Notes are record-only — no send API exists, so no duplicate risk. A->>P: book tour or ProspectPortal apply link Note over A,L: ── HANDOFF: tour booked OR application submitted ── L->>E: tour, leasing, move-in — natively A->>P: one post-tour feedback text (~1–2 hrs)
Abandoned applications: poll getLeads for an in-progress status gone stale (verify per-property status IDs with one test application), then the agent re-engages. ILS listing freshness rides on Entrata's native syndication (Liberty flips it on; Marketing Hub → ILS Portal) — not our API problem, but our weekly report card grades it.

7. Flow C — turn engine DECIDED

KPI 2 (vacancy days → ~0). Ready date = move-in date; the only downtime is the ~3-day make-ready.

flowchart LR
  N["Notice to vacate
(Entrata: getLeases / getExpiringLeases)"] --> D["Compute available date
vacate + Liberty turn SLA (~3–4 days)"] D --> C["Liberty confirms ready date
(pre-inspection walk; nothing goes out before)"] C --> M["Match waitlist on optional profile
beds / budget / pet / timing"] M --> W1["1. Matched Insiders — 48-hr early window
texted first, ordered by stated timing"] W1 --> W2["2. Broader waitlist"] W2 --> W3["3. Public listing — ILS syndication fires"] W1 -.-> APP["Applications"] W2 -.-> APP W3 -.-> APP APP --> R["Allocation rule (written, neutral, mechanical):
among QUALIFIED applicants, completed app
closest to the ready date wins"] R --> S["Lease signed → move-in = ready date"] classDef lag fill:#f7ede4,stroke:#bb6238 class C lag
Fair-housing guardrails baked in: priority attaches at application, not interest; screen everyone by the same criteria first; timing rule applies among the qualified; stay open to reasonable accommodations; never set the window so tight it predictably excludes families/disabled.

8. Flow D — recognition engine DECIDED

KPI 3 (renewal 57% → 70%). Real cards in the mail, zero staff tasks. Touch map + tone rules locked in lead-comms §4.

sequenceDiagram
  autonumber
  participant V as VPS (daily pull)
  participant S as Supabase
  participant C as Recognition cron
  participant HW as Handwrytten
  participant TR as Tremendous
  participant T as Twilio (ownership #)
  participant Q as Outbox → Entrata
  V->>S: resident facts — leases, renewal dates, pets, move-ins (getLeases / getCustomers)
  C->>S: read trigger matrix (move-in, birthday, pre-renewal −90d, tenure, life events)
  alt mailed touch (welcome / birthday / pre-renewal / tenure / sympathy)
    C->>HW: handwritten card (+ $25 gift card where the map says)
  else digital gift
    C->>TR: gift card by text
  end
  C->>T: follow-up text the night it lands — "make sure you got your package"
  C->>Q: log note — "mailed birthday card + $25 gift card"
  Note over Q: PM sees every touch in the Entrata thread (rule 3)
  Note over C: Pre-renewal card lands ~90 days out, AHEAD of the ~60-day offer.
Gift-then-offer reads genuine; offer-then-gift reads like a bribe.

9. Flow E — review / social-proof engine DECIDED

KPI 4 (3.9★ → 4.5★+). Event-triggered asks at goodwill peaks; reviews feed ILS rank and the site funnel.

sequenceDiagram
  autonumber
  participant E as Entrata events
  participant S as Supabase
  participant C as Review cron
  participant T as Twilio
  participant R as Resident
  participant G as Google / Apartments.com
  E->>S: move-in +2wk · WO completed · renewal (via VPS pulls)
  S->>C: recognition sends (from the ledger)
  C->>C: suppression — public review in last ~12 mo? spaced enough? → skip
  C->>T: the ask, from the ownership number
  T->>R: text + smart link (or the service-call card, left on site)
  R->>G: public review
  G-->>C: monitor → ILS scorecard + site testimonials
  C->>E: log the ask (outbox note)
  Note over C,R: Trigger on EVENTS, ask EVERYONE at that event.
Sentiment-gating is prohibited — that's rule 6, not a style choice.

10. Flow F — escalation & edge handling DECIDED

flowchart TD
  M["Message on the ownership channel"] --> GD{"Guardrail check
(Haiku pre-pass)"} GD -- "leasing, in scope" --> RE["Agent replies
logs note via outbox"] GD -- "service ticket" --> SR["Acknowledge instantly + redirect gently
'Liberty's responsibility, we passed it on'"] SR --> WO["sendWorkOrders via relay
(emergency → maintenancePriorityId from
getWorkOrderPickLists, mapped per property)"] WO --> LAG["Liberty resolves — natively, their SLA"] GD -- "out of its depth" --> ESC["'Let me get a person' + urgent note
on the guest card → Liberty takes over"] GD -- "agent / API down" --> FB["Fallback email to the Liberty PM
with lead + full context — nothing lost"] LAG -.-> AGG["Foothold watches the AGGREGATE
(rating trend, review velocity, renewals)
never per-ticket policing"] classDef crit fill:#f3e0dd,stroke:#9a3b2b class FB crit

11. FlightDeck & reporting PLANNED

The shared cockpit from the Overview: per-property KPIs, NOI vs budget, DSCR, flags. Sources, per property:

Board metricSource
Phys/econ occupancy, NOI vs budget, DSCR, delinquencyEntrata financial group (getGlTransactions, getBudgets, getBudgetActuals, getBankAccounts) + artransactions / arpayments, via VPS pulls
Apps/tour, speed-to-lead, cost per leaseGHL funnel + Entrata getLeads / getLeadEvents source tagging
Renewal rate, vacancy daysEntrata getLeases / getExpiringLeases + turn-engine ledger
Rating, review velocityReview-engine monitor (Google + Apartments.com)
ILS grade (weekly report card to Liberty)Listing-freshness checks + review presence + photo completeness — the scorecard the agent grades weekly

12. Scope traceability: the Overview's promises → this design

Everything promised on / maps here. If a promise has no flow, the design is incomplete; if a flow serves no promise, cut it.

Overview promiseKPI targetFlowComponents on the critical path
Demand engine: owned list, 24/7 agent, Insider blast, sight-unseen media0.4 → 3.0 apps/tourB (+ A)Agent, GHL funnel, Twilio, availability cache, unit media packages, ProspectPortal links
Turn engine: pre-leased before empty~41 → ~0 vacancy daysC (+ A)Lease pulls, waitlist matching, Insider 48-hr window, Liberty ready-date confirm
Recognition engine: auto-mailed cards + gifts57% → 70% renewalDResident-facts pull, recognition cron, Handwrytten, Tremendous, Twilio, Entrata logging
Review engine: ask at goodwill peaks3.9 → 4.5★+EEvent triggers, suppression ledger, smart links, service-call card
Econ occupancy (rollup)~90% → 95%+A–EAll of the above + FlightDeck delinquency/concession visibility
Brand-rent premium (the proof)+5–8%allThe system working; measured in FlightDeck vs market comps (HelloData)
Escalations as clean tickets; full PM visibilityFGuardrails, work-order relay, note logging
FlightDeck, one board both sides see§11Financial pulls, engine metrics, ILS grade

13. Build sequence & gates

PhaseDeliverableStatusGate / dependency
0Site, waitlist→GHL, binder, VPS + mock service, Entrata agreement + portal + key + full OpenAPI specDONE
1Availability live: wire ENTRATA_API_KEY into config, live parse (MT dates), KV publish, /rentals reads KV, backoff + maintenance windows. First calls: getAccessibleClientsgetProperties → populate property IDs.BLOCKEDLiberty's Add-API-User grant (asked 2026-07-28; email drafted). Read the Webhooks portal page before wiring. Dev happens against Liberty's training/test company first.
2Lead loop: outbox transport (Queues recommended), relay intents, sendLeads/updateLeads with dedupe, CoStar lead-email destination pointed at the agent inbox, ILS syndication flipped on by LibertyPLANNEDPhase 1 + test-company validation of writes. Liberty flips ILS Portal on.
3Agent v1: /api/chat Worker + widget, tools (check_availability on KV, capture_lead, book_tour, start_application), transcripts + daily Opus QA cron, Twilio numbers + A2PDECIDEDPhase 1 (availability data) + Entrata AI-disclosure review (rule 2) + A2P registration lead time
4Recognition + review engines: Supabase provisioned, resident-facts pull, Handwrytten/Tremendous accounts, trigger matrix, suppression ledgerDECIDEDPhase 1 (lease/resident reads on real properties — needs Aspen/Summerset live in Liberty's Entrata post-PMA)
5FlightDeck wiring + weekly ILS report cardPLANNEDPhase 1 financial reads; report card can start semi-manual earlier
6Voice (same brain, Vapi/Retell-class building block) + applications-push if ever neededPARKEDAgent proven on chat first — a maturity decision, not a liability one
🏁Launch: remove gate + noindex on both sites (gate.js here; Base.astro + robots.txt on foothold-web)PLANNEDBrent's call

14. Open questions (decide at the marked phase)

QuestionPhaseCurrent lean
Webhooks vs 12-min polling1Read the portal Webhooks page; if events cover availability + leads, poll becomes a reconciliation pass. Inbound endpoint goes on a Worker, never the VPS.
Outbox transport2CF Queues pull-consumer (HTTPS pull keeps the VPS outbound-only). Alternatives: D1-backed table + tiny Worker API. Current CF token is Pages-scope — either needs a dashboard visit.
Subdomain written confirmation1libertyassetgroup per error-code probes; confirm via getAccessibleClients the day the grant lands.
Abandoned-app status IDs2Run one test application in the test company; record per-property status IDs here.
North River onto Entrata4Moving to LAG/Entrata (2026-07-20) — then all three properties ride this one integration. PMS adapter stays anyway.
Resident life-event data source (birthdays, pets)4Welcome Profile + pet form (our data), not the lease. Liberty call transcripts idea is consent-gated — ask Liberty before touching.
Cyber-liability insurance (agreement §3.2 rep)Confirm carried; Brent.

15. Doc registry & supersessions

DocOwns
/ (Overview)Scope + the promises. What "done" means. Update it first, align everything else to it.
This pageBuild SoT: architecture, contracts, statuses, build order.
lead-comms-specComms behavior: channels, sequence, touch map, edge cases, freshness tiers. Still iterating.
project-planExecution: Gantt to the Aug-31 close, who-owns-what, KPI scorecard, deliverables hub.
entrata-integrationConnection points + lead-flow research. Partially superseded: its Access & Cost section predates the executed agreement ($0 Year 1, signed 2026-07-20) and the gateway protocol in §4 here. Trust §4 over it on anything API-mechanical.
Drive: strategy/10-ai-agents-spec.mdAgent architecture, guardrails, QA loop.
Drive: CONTEXT.mdOperational state + changelog, cross-project. More current than repo docs when they disagree.
Project memory: entrata-api-auth-shape.mdThe probe-established protocol facts + error vocabulary (Claude Code sessions).

Changelog

2026-07-28v1. Drawn from the Overview, lead-comms-spec v3, entrata-integration, CONTEXT.md, ai-agents-spec, and the captured Entrata OpenAPI spec. Statuses as of today: Entrata key on VPS, grant blocked on Liberty, mock poller running.