Source of Truth Architecture
This is the canonical reference for where each FinMatch asset lives, who owns it, and how it gets to production. It reflects the post-cleanup state achieved by PRs #149-#171 (Jun 2026), in which all stale mirror copies, ghost runtime files, and duplicate snapshots were removed from Git in favour of a single source of truth per asset.
If anything below ever drifts from the live workflows or repository
state, the live files (.github/workflows/*.yml, scripts/deploy-workflow.sh,
cloud-run/SERVICES.md) are authoritative and this doc should be
updated to match.
1. Guiding principle
Every protected runtime asset has exactly one writable home. Git never holds a parallel snapshot of an asset that a Cloud Run service manages in GCS. Mirror copies are not a safety net — they are a drift hazard, so we removed them.
This applies to:
- Runtime modules (
scripts/*.js,css/finmatch.css) - Storefront configs (
configs/finmatch-merchant-config.json,configs/lender-overrides.json,configs/lender-styling.json) - Runtime infrastructure files (
cors.json,merchants.json,merchant-router.json,finmatch-rate-card.json,lender-originated-quotes.json,auth-credentials.json,partner-api-keys.json) - The SDK entrypoint (
finmatch-sdk.js)
2. Branch → bucket → role map
| Branch | Authoritative for | Synced to |
|---|---|---|
finmatch-shared | Admin UI, Cloud Run services, Cloud Functions, GitHub Actions workflows, Docusaurus docs, canonical SDK entrypoint (finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js), shared lender styling/overrides authored by admin. | gs://finmatch-shared, gs://finmatch-admin, gs://finmatch-login, gs://finmatch-developer, gs://finmatch-finance-marketing-assets |
finmatch-p | Production storefront runtime: scripts/, css/finmatch.css, configs/ (non-protected fields). | gs://finmatch-p |
finmatch-s | Staging storefront runtime. | gs://finmatch-s |
finmatch-t | Test storefront runtime (downstream mirror of finmatch-p for shared scripts//css/ only — explicit p → t sync PRs). | gs://finmatch-t |
finmatch-shared is the trunk for development; environment branches are
runtime deploy targets, not upstream code branches for storefront
JS/CSS.
3. What lives where (and what does NOT)
3.1 finmatch-shared
Yes:
admin/— admin dashboard (HTML, JS, CSS).admin-docs/— Docusaurus admin docs.cloud-run/<service>/— Cloud Run service source (each with its ownDockerfile,package.json, deploy workflow).cloud-functions/getMerchantConfig/— Cloud Function source..github/workflows/*.yml— every workflow (single source of truth).scripts/deploy-workflow.sh— the maintainer script that propagates workflow files to env branches.scripts/analytics/— operational analytics utilities.finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js— the canonical SDK entrypoint. The only file in the repo allowed to write togs://finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js.finmatch-shared/(subtree) — shared finance formulas, images, and the SSOT JSON files that the admin/API ecosystem reads.platform/—login.finmatch.ioandapi.finmatch.iostatic sites.developer-docs/— Docusaurus developer docs..cursor/skills/finmatch-architecture/SKILL.md— agent-facing source of truth for architecture and PR discipline.
No (deliberately removed; do not re-add):
- ❌ Root
scripts/*.jsstorefront runtime modules (deleted in PR #154; guardrail insync-to-gcs.ymlrejects re-introduction). - ❌ Root
css/finmatch.css(deleted in PR #154). - ❌ Root
configs/*.jsonstorefront configs (deleted in PR #154). - ❌
cors.jsonat repo root (untracked in PR #169 — managed bymerchant-apidirectly ings://finmatch-shared/cors.json). - ❌ Numbered duplicate files (
* 2.js,* 3.md, etc.) — Finder/iCloud artefacts, blocked by.gitignorelines 35-39.
3.2 finmatch-p (and other env branches)
Yes:
scripts/*.js— storefront runtime modules (finance-container.js,bon-voyage.js,finance-assistant.js, all<lender>-logic.js, shared utilities).css/finmatch.css.configs/finmatch-merchant-config.jsonand otherconfigs/storefront files (non-protected fields only — protected fields are written bymerchant-api)..github/workflows/*.yml— propagated fromfinmatch-sharedviascripts/deploy-workflow.sh. Drift-checked byworkflow-drift-check.yml.
No (deliberately removed; do not re-add):
- ❌
finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js— SDK ghost mirror deleted from every env branch in PRs #160-#167. The canonical SDK lives only onfinmatch-sharedand is uploaded directly to its own GCS bucket from there. - ❌
cors.json(untracked in PRs #170-#171). - ❌ Historical migration scripts (
migrate_merchants.py,scripts/migration/*) retired in PR #168. - ❌ Duplicate root
*.mdplanning docs (retired fromfinmatch-pin PR #158, fromfinmatch-tin PR #159).
4. Protected runtime files (single-writer)
These files are written exclusively by an admin/API runtime path and must never be edited via Git:
| File | Owning writer | Lives in |
|---|---|---|
merchants.json | merchant-api (/api/merchants/...) | gs://finmatch-shared/merchants.json (no Git copy) |
merchant-router.json | merchant-api (switch-env, create) | gs://finmatch-shared/merchant-router.json (no Git copy) |
cors.json | merchant-api (CORS bootstrap) | gs://finmatch-shared/cors.json (no Git copy) |
auth-credentials.json | auth-api | gs://finmatch-shared/auth-credentials.json (no Git copy) |
users.json / users-enhanced.json | users-api | gs://finmatch-shared/users.json |
partner-api-keys.json | partner-api | gs://finmatch-shared/partner-api-keys.json |
finmatch-rate-card.json | credit-products-api | gs://finmatch-shared/finmatch-rate-card.json |
lender-originated-quotes.json | credit-products-api | gs://finmatch-shared/lender-originated-quotes.json |
stripe-customers.json | stripe-api | gs://finmatch-shared/stripe-customers.json |
lender-styling.json / lender-overrides.json | merchant-api (admin UI) | gs://finmatch-shared/ |
configs/finmatch-merchant-config.json | merchant-api (atomic write) | gs://finmatch-{p,s,t}/configs/ |
sync-to-gcs.yml snapshots these objects pre/post sync and rejects any
push that mutates them on disk.
Why no Git copies (Jun 2026 router-drift incident): stale Git copies of
merchant-router.json/merchants.json/auth-credentials.jsonlingered onfinmatch-shared, plus a legacyconfigs/merchant-router-finmatch-shared.jsonmirror onfinmatch-p/finmatch-t. The router copy still showed a migrated merchant on envswhile GCS correctly servedp, sending a debugging agent down a false trail. These copies are never synced (the rsync-x-excludes them) and never read at runtime —getMerchantConfigandmerchant-apiread/write the GCS object directly. They were removed and.gitignored so they can't return.
5. Workflow guardrails
| Workflow | Defends against |
|---|---|
sync-to-gcs.yml | Mutations to protected runtime files; ghost-runtime additions on finmatch-shared (the post-cleanup guardrail added in PR #153 blocks any add/modify to root scripts/, css/, configs/ on finmatch-shared). |
workflow-drift-check.yml | Workflow files diverging on env branches from finmatch-shared. |
merchant-config-monitoring.yml | Cron-based tiered alerts on merchant config integrity. |
admin-merchant-api-url-guard.yml | Admin embedding the wrong merchant-api URL. |
snap-rate-card-test.yml | Numeric drift in Snap finance calculations (£280 anchor + four scenarios). |
The ghost-runtime guardrail is the keystone of the post-cleanup architecture: it prevents the very drift we just spent the cleanup removing.
6. The SDK entrypoint (special case)
finmatch-sdk.js is the snippet entry that merchant storefronts load.
It used to be mirrored to every env branch in
finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js, with a
"SDK canonicalization guardrail" trying to keep the mirrors in sync.
This was both ineffective (drift accumulated) and unnecessary (the SDK
is uploaded directly from finmatch-shared to its own legacy bucket).
Post-cleanup state (PRs #157, #160-#167):
- The SDK lives only on
finmatch-sharedatfinmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js. sync-to-gcs.ymluploads it fromfinmatch-sharedtogs://finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js.- Env branches have no copy. The SDK guardrail and the SDK
propagation step in
deploy-workflow.shwere removed.
If a merchant snippet ever 404s on the SDK URL, that means
gs://finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js is
missing — fix it on finmatch-shared.
7. Decision tree: where does my change go?
Does the change touch admin UI, Cloud Run, workflows, docs, or SDK?
└── YES → finmatch-shared
Does the change touch storefront runtime (scripts/, css/, configs/)?
└── YES → finmatch-p (or the merchant's current env branch)
Is the change a protected runtime file mutation (merchants.json, etc.)?
└── DO NOT EDIT IN GIT — use the admin dashboard / Cloud Run API.
For details on PR workflow, base branch selection, and the propagation
of workflow updates, see
.cursor/skills/finmatch-architecture/SKILL.md
on finmatch-shared.
8. Related docs
- Cleanup runbook (Jun 2026) — the PR-by-PR record of how this state was reached.
- Dashboard data architecture —
read/write split between direct GCS and
merchant-api. ARCHITECTURE_OVERVIEW.md(repo root) — narrative architecture overview..github/WORKFLOW_GUIDE.md— operational workflow reference.cloud-run/SERVICES.md— Cloud Run service URLs and roles.cloud-run/API_COMPARISON.md—merchant-apivspartner-apidecision guide.