Skip to main content

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

BranchAuthoritative forSynced to
finmatch-sharedAdmin 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-pProduction storefront runtime: scripts/, css/finmatch.css, configs/ (non-protected fields).gs://finmatch-p
finmatch-sStaging storefront runtime.gs://finmatch-s
finmatch-tTest 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 own Dockerfile, 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.jsthe canonical SDK entrypoint. The only file in the repo allowed to write to gs://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.io and api.finmatch.io static 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/*.js storefront runtime modules (deleted in PR #154; guardrail in sync-to-gcs.yml rejects re-introduction).
  • ❌ Root css/finmatch.css (deleted in PR #154).
  • ❌ Root configs/*.json storefront configs (deleted in PR #154).
  • cors.json at repo root (untracked in PR #169 — managed by merchant-api directly in gs://finmatch-shared/cors.json).
  • ❌ Numbered duplicate files (* 2.js, * 3.md, etc.) — Finder/iCloud artefacts, blocked by .gitignore lines 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.json and other configs/ storefront files (non-protected fields only — protected fields are written by merchant-api).
  • .github/workflows/*.yml — propagated from finmatch-shared via scripts/deploy-workflow.sh. Drift-checked by workflow-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 on finmatch-shared and 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 *.md planning docs (retired from finmatch-p in PR #158, from finmatch-t in 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:

FileOwning writerLives in
merchants.jsonmerchant-api (/api/merchants/...)gs://finmatch-shared/merchants.json (no Git copy)
merchant-router.jsonmerchant-api (switch-env, create)gs://finmatch-shared/merchant-router.json (no Git copy)
cors.jsonmerchant-api (CORS bootstrap)gs://finmatch-shared/cors.json (no Git copy)
auth-credentials.jsonauth-apigs://finmatch-shared/auth-credentials.json (no Git copy)
users.json / users-enhanced.jsonusers-apigs://finmatch-shared/users.json
partner-api-keys.jsonpartner-apigs://finmatch-shared/partner-api-keys.json
finmatch-rate-card.jsoncredit-products-apigs://finmatch-shared/finmatch-rate-card.json
lender-originated-quotes.jsoncredit-products-apigs://finmatch-shared/lender-originated-quotes.json
stripe-customers.jsonstripe-apigs://finmatch-shared/stripe-customers.json
lender-styling.json / lender-overrides.jsonmerchant-api (admin UI)gs://finmatch-shared/
configs/finmatch-merchant-config.jsonmerchant-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.json lingered on finmatch-shared, plus a legacy configs/merchant-router-finmatch-shared.json mirror on finmatch-p/finmatch-t. The router copy still showed a migrated merchant on env s while GCS correctly served p, sending a debugging agent down a false trail. These copies are never synced (the rsync -x-excludes them) and never read at runtime — getMerchantConfig and merchant-api read/write the GCS object directly. They were removed and .gitignored so they can't return.

5. Workflow guardrails

WorkflowDefends against
sync-to-gcs.ymlMutations 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.ymlWorkflow files diverging on env branches from finmatch-shared.
merchant-config-monitoring.ymlCron-based tiered alerts on merchant config integrity.
admin-merchant-api-url-guard.ymlAdmin embedding the wrong merchant-api URL.
snap-rate-card-test.ymlNumeric 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-shared at finmatch-finance-marketing-assets/s/scripts/finmatch-sdk.js.
  • sync-to-gcs.yml uploads it from finmatch-shared to gs://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.sh were 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.

  • 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.mdmerchant-api vs partner-api decision guide.