Decision log
Architecture Decision Records — short entries on the why behind structural choices.
How to read this
Architecture Decision Records (ADR) — short entries on the why behind structural choices. Append-only; supersede with a new entry rather than editing.
Each entry has the same shape: Context (what we were trying to solve), Decision (what we chose), Consequences (what we accepted in return). If a decision is reversed, add a new entry that supersedes the old one — never delete history.
Records
ADR-001 — Use Cloudflare D1 (SQLite) as the read-mirror of NetSuite, not Postgres
Context: Need a queryable mirror of NetSuite data outside the NS Suitelet sandbox. Worker → D1 keeps all platform code on one Cloudflare account; no separate database hosting.
Decision: D1 SQLite. Bound to the Worker as DB binding.
Consequences: Pros: zero ops, point-in-time recovery available, queryable from Worker with sub-ms latency. Cons: no enforced FKs in current schema, single-region writes, 10GB per-DB limit (currently 185K rows, well under).
ADR-002 — Sync runs locally via sync.sh, not as a Worker scheduled() handler
Context: Initial sync needed to ship fast. Chartstone Pro exposes a localhost SuiteAPI passthrough that already authenticates to NetSuite.
Decision: sync.sh on launchd every 15 min, calling Chartstone (localhost:56411) and pushing to D1 via wrangler.
Consequences: Pros: works today, no NS integration role to provision. Cons: single-Mac dependency, no observability, sync stops if laptop is off. Tracked as Tier 1 risk + Tier 2 GAP for migration to Worker-side scheduled() handler calling SuiteAPI directly.
ADR-003 — Cloudflare Worker is the only public API
Context: Frontends (Pages dashboards, internal Suitelets) need REST access to the D1 mirror. NetSuite Suitelet calls are slow and rate-limited.
Decision: Single Worker at api.ai-globalfoodsolutions.co/* exposing GET endpoints. CORS allowlist + Bearer API_KEY auth. CSP/HSTS security headers on every response.
Consequences: Pros: one codebase, one auth model, one deploy. Cons: every endpoint runs under the same API_KEY — finer-grained scopes would need per-route token validation.
ADR-004 — Consolidate to one repo with five fixed UI surfaces
Context: Prior structure had 3 dashboards, 2 master guides, 5 stub redirects, and overlapping references. Made it hard to find anything.
Decision: Single repo at ~/Desktop/gfs-platform. Five fixed UI surfaces (index router + admin-dashboard, netsuite-reference, flows, data-model, infrastructure), two operating docs (GAPS, BLUEPRINT), one runbook, one decisions log. All non-load-bearing originals preserved under archive/.
Consequences: Pros: single source of truth, single builder, single rebrand path. Cons: any new content has to fit one of the fixed surfaces — resists feature creep, which is the point.
ADR-005 — Brand config lives in one JSON block inlined into every HTML
Context: Rebrand/restyle should be a single-config change, not a project-wide refactor.
Decision: assets/brand.json holds all brand strings (name, domains, colors, fonts). build_pages.py inlines it into every generated HTML inside <script id="brand" type="application/json">. Single search/replace renames the system end to end.
Consequences: Pros: rename is one file edit + one rebuild. Cons: pages must be regenerated after brand changes — markdown edits are live, HTML surfaces need the builder.
ADR-006 — Out-of-scope: revenue, sales, profit, customer-facing material
Context: Platform is an admin and operating guide. Prior version mixed admin tools with sales KPI dashboards.
Decision: All revenue/sales/profit/customer-facing artifacts removed from the active surfaces. Preserved under archive/2026-05/ for reference but not surfaced in any new page.
Consequences: Pros: clear audience, no double-purpose pages. Cons: a sales-facing dashboard, if needed, has to live in a separate project — not this one.
ADR-007 — No external CDN / library dependencies in deployed HTML
Context: Single-file HTML is the user's standard. External CDNs add load-time risk and an external trust boundary.
Decision: Every HTML inlines its CSS, brand block, and any JS. Diagrams are inline SVG or pre-rendered. No <script src> tags pointing to public CDNs.
Consequences: Pros: pages work offline, no third-party tracking, no broken when CDN goes down. Cons: pages are larger; some diagram options (Mermaid live, Chart.js) are off the table — replaced with inline equivalents.
ADR-008 — Cloudflare Pages deployed to gfs-netsuite.pages.dev — NOT publicly shareable yet
Context: Need an internal canonical URL but Cloudflare Access setup is a Tier 1 GAP item not yet closed.
Decision: Deploy to gfs-netsuite.pages.dev without Access for now. Do not share the URL with anyone outside Michael Levine until Access (email OTP) is configured.
Consequences: Pros: usable today by the owner. Cons: anyone with the URL can read GAPS_TO_CLOSE (security gaps), netsuite-reference (all script + saved-search names), runbook (procedures). Adding Access is documented in GAPS Tier 1.
ADR-009 — data/system-manifest.json is the canonical NetSuite inventory
Context: Inventory data was scattered across 8 separate markdown files in docs/. Hard to query, easy to drift.
Decision: Single JSON file with 15 categories (record_types, fields, saved_searches, reports, scripts, workflows, roles, permissions, suitelets, printed_forms, dashboards, integrations, lists, forms, kpis). Source markdowns moved to reference/netsuite-sources/ and feed the manifest. The manifest feeds netsuite-reference.html and the admin-dashboard counters.
Consequences: Pros: one place to update, every UI references the same numbers. Cons: refreshing requires re-extracting from NS — not yet automated.
ADR-010 — Reference research kept verbatim, not summarized into surfaces
Context: 280K words of NetSuite research (portlets, Suitelets, pitfalls, AI patterns, automation tiers) exists in docs/01-08. Tempting to inline summaries into surfaces.
Decision: Keep research docs intact in reference/research/. Surfaces cite them but do not absorb them. New best-practice claims must trace to a research doc.
Consequences: Pros: research stays inspectable and rewriteable independent of the surfaces. Cons: readers may not click through; need clear pointers from surfaces.
ADR-011 — Native Cloudflare AI stack for the natural-language query layer over D1
Context: Need a natural-language query layer over the D1 NetSuite mirror so admins can ask questions like 'top vendors by 2026 spend' or 'which scripts are deployed but never run' without writing SQL. Data already sits on Cloudflare. Options: (a) native Cloudflare AI (Workers AI + Vectorize + AI Gateway), (b) external LLM via AI Gateway only, (c) external LLM with own embedding store.
Decision: Native Cloudflare stack for v1: Workers AI for inference (Llama 3.3 70B Instruct) and embeddings (BGE-base-en-v1.5, 768d), Vectorize as the embedding store, AI Gateway for caching/logging/rate-limit and as the seam to swap models. Two query modes — retrieve+answer for descriptive questions, generate-SQL-then-execute for quantitative ones. SQL validator enforces SELECT-only, whitelisted tables, no DDL/DML. Corpus: D1 entities + aggregate transactions + system manifest + reference research docs + SuiteQL library + schema descriptions.
Consequences: Pros: zero data egress, one auth model, one platform to operate, AI Gateway lets us proxy to Claude/GPT later without rewriting app code, native Workers AI pricing scales with traffic. Cons: Llama 3.3 70B is not as strong on complex SQL synthesis as Claude/GPT — we accept this for v1 and monitor query failure rates; recall on BGE-base may require a quality bump (text-embedding-3-small via AI Gateway) if relevance is poor. Native models locked to Cloudflare's catalog cadence — we mitigate via AI Gateway swap. Risk: prompt injection from indexed content; mitigated by explicit instruction-vs-data separation in the system prompt + SQL validator.
Adding a new ADR
- Open data/decisions.json.
- Append a new object to entries[] with id ADR-NNN (next number), date, title, status (Proposed / Accepted / Superseded), context, decision, consequences.
- Run python3 assets/build_pages.py.
- Commit and deploy.