-
Notifications
You must be signed in to change notification settings - Fork 0
docs: finance/books/ledger boundary contracts + proposals #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6dae57e
e81aec5
f7f8b34
4108560
f36905e
fea07c1
4524ab1
e179c20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # ChittyBooks ↔ ChittyFinance Contract | ||
|
|
||
| > Engine / UI boundary. Not canon yet — proposal for the pentad. | ||
|
|
||
| ## Status (verified 2026-05-27) | ||
|
|
||
| | Surface | Repo | Deploy | Health | | ||
| |---|---|---|---| | ||
| | ChittyFinance (engine) | `CHITTYAPPS/chittyfinance` | Hono on CF Workers at `finance.chitty.cc` | `200 ok` | | ||
| | ChittyBooks (UI/app) | `CHITTYAPPS/chittybooks` | Python Flask, `main.py`, Dockerfile, `.replit` → `cloudrun` | **Not deployed**. `books.chitty.cc` does not resolve. | | ||
| | ChittyLedger (substrate) | `CHITTYFOUNDATION/chittyledger` | Worker at `ledger.chitty.cc` | `200 ok` | | ||
| | ChittyLedger (legacy fork) | `CHITTYOS/chittyledger` | Express + React, in-memory, not deployed | n/a — DUPLICATE, candidate for retirement or repurpose as ChittyLedger-Evidence seed (see `docs/proposals/chittyledger-naming-plan.md`) | | ||
|
|
||
| `CHITTYAPPS/chittybooks/CHARTER.md` claims a Cloudflare Worker at `books.chitty.cc`. The repository is a Python Flask application with `deploymentTarget = "cloudrun"`. Charter and code disagree. | ||
|
|
||
| ## Boundary | ||
|
|
||
| - **ChittyFinance is the finance engine.** It owns: tenants, properties, accounts, transactions, allocations, classification (COA L0–L4), reports, valuation, AI summarization, OAuth connectors (Wave, Stripe, Google, Mercury via proxy), and webhooks. It writes evidence-grade entries to ChittyLedger. | ||
| - **ChittyBooks is a bookkeeping UI/app.** It does **not** own bookkeeping records. It is a thin surface that reads ChittyFinance and (where applicable) ChittyLedger-Finance projections. | ||
| - **ChittyLedger is the substrate.** ChittyLedger-Finance and ChittyLedger-Evidence are projections of it. See `docs/chittyledger-finance-design.md` for the canonical projection design. | ||
|
|
||
| ## Source of Truth (no competing writers) | ||
|
|
||
| | Resource | Writer | Reader | | ||
| |---|---|---| | ||
| | `tenants`, `properties`, `accounts`, `transactions`, `allocations`, `classifications` | ChittyFinance | ChittyBooks (read-only), ChittyCommand, exports | | ||
| | `financial_documents`, `financial_facts`, `reconciliation_conflicts` | ChittyFinance writes into the ChittyLedger-Finance projection via `POST https://ledger.chitty.cc/api/entries` (matches `CHITTY.md` and `docs/proposals/chittyledger-naming-plan.md`). ChittyTrace ingest is an upstream feeder that hands material to ChittyFinance — it is not a second writer to the projection. | ChittyFinance, ChittyBooks | | ||
| | Mercury/Wave/Stripe/Plaid raw events | external | ChittyFinance webhooks | | ||
|
|
||
| ChittyBooks MUST NOT write transactions, allocations, or COA classifications directly. All mutations route to ChittyFinance. | ||
|
|
||
| ## API Surface ChittyBooks consumes | ||
|
|
||
| All paths are under `https://finance.chitty.cc`. Auth (per `server/middleware/auth.ts` hybridAuth): either a service `Authorization: Bearer <CHITTY_AUTH_SERVICE_TOKEN>` header (with `X-Chitty-User-Id`) **or** a ChittyAuth-issued JWT delivered as the session cookie (`jwt:` prefix) — Bearer is service-to-service only; user/browser callers use the cookie path. Tenant scoping is resolved server-side from session/JWT claims (`c.var.tenantId`); path params are not trusted. | ||
|
|
||
| | Path (verified mounted) | Purpose for ChittyBooks | | ||
| |---|---| | ||
| | `/api/tenants` | List tenants the caller can read | | ||
| | `/api/properties` | Property list | | ||
| | `/api/accounts` | Account list + balances | | ||
| | `/api/transactions` | Transaction feed for caller's tenant. Today the mounted handler accepts only `?limit=`; `?accountId=` and `?since=` are accepted on `/api/transactions/export`. Any source/date filter parity is a follow-up, not a current guarantee. | | ||
| | `/api/allocations/rules`, `/api/allocations/preview`, `/api/allocations/execute`, `/api/allocations/run` | Allocation rule CRUD + dry-run + execution (per `server/accounting/allocations.ts`). There is no umbrella `/api/allocations` index route. | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence after the prior thread: Useful? React with 👍 / 👎. |
||
| | `/api/classification` | COA assignments + audit trail | | ||
| | `/api/reports/consolidated`, `/api/workflows/close-tax-automation` | Pre-aggregated bookkeeping/close views currently mounted under `/api/reports/*` and `/api/workflows/*` (per `server/accounting/reports.ts`). A `/api/reports/reconciliation` endpoint is **proposed**, not yet mounted. | | ||
| | `/api/integrations/status` | Per-provider `configured` boolean derived from env vars (see `server/routes/integrations.ts`). Last-sync timestamps are a proposed extension, not part of the current response. | | ||
| | `/api/v1/documentation` | OpenAPI 3.0 spec (note: only the docs route is under `/api/v1`; data routes are under `/api`. See `docs/proposals/api-v1-prefix-fix.md`.) | | ||
|
|
||
| ## ChittyLedger projection paths (read-only) | ||
|
|
||
| ChittyBooks reads ChittyLedger-Finance projection tables via `ChittyFinance` aggregator endpoints — it does not query ChittyLedger directly. This preserves the substrate boundary: ChittyLedger does not know about ChittyBooks. The aggregator endpoints (e.g. `/api/ledger/finance/*`) are **proposed** — none are mounted yet. They must be defined and shipped in ChittyFinance before ChittyBooks relies on them. Until then, ChittyBooks reads projection data only through the existing mounted routes above. | ||
|
|
||
| ## Deploy decision (2026-05-27) | ||
|
|
||
| **Retired: the assumption that `books.chitty.cc` is a live API.** The domain does not resolve and no Worker exists. Code paths that target it MUST be disabled or guarded (see `docs/proposals/ch1tty-connector-revision.md`). | ||
|
|
||
| **Not yet decided: the actual ChittyBooks deploy path.** That decision is an explicit operator gate. Until the operator chooses container / worker-port / merged-into-finance, ChittyBooks stays as repo-only — a candidate UI/workflow surface, not a runtime. | ||
|
|
||
| Proof from repo files for the fake-domain retirement (no inference): | ||
|
|
||
| | Evidence | Source | What it proves | | ||
| |---|---|---| | ||
| | Repo is Python Flask, not Worker | `CHITTYAPPS/chittybooks/main.py` (43 KB), `Dockerfile`, `pyproject.toml`, `wsgi.py` | Charter ↔ code mismatch | | ||
| | Replit config targets Cloud Run, not CF | `CHITTYAPPS/chittybooks/.replit` lines `deploymentTarget = "cloudrun"` and `[[ports]] localPort = 5000 externalPort = 80` | Never built as a Worker | | ||
| | No `wrangler.toml`/`wrangler.jsonc` in repo | `find CHITTYAPPS/chittybooks -maxdepth 2 -name 'wrangler*'` returns empty | No CF deploy path exists | | ||
| | No DNS for the claimed domain | `dig books.chitty.cc` → NXDOMAIN (verified via `curl: Could not resolve host`) | Charter URL is aspirational | | ||
| | Bookkeeping engine already complete in ChittyFinance | `CHITTYAPPS/chittyfinance/server/routes/{allocations,classification,reports,tax,portfolio,charges}.ts` | No engine gap requires a second service | | ||
| | Per-tenant `tenantId NOT NULL` at schema | `database/system.schema.ts:103` | Multi-tenant boundary is in ChittyFinance, not in ChittyBooks | | ||
|
|
||
| Options preserved for the operator (deploy gate — not decided here): | ||
| - **Option A — Cloud Run container.** Matches existing `.replit` config. Justification gap: ChittyFinance already owns bookkeeping engine; risk of competing source of truth. | ||
| - **Option B — Worker rewrite.** Same competition risk + significant rewrite cost. | ||
| - **Option C — Merged UI surface served by ChittyFinance.** Lowest cost, no new runtime. Default if no operator decision is made. | ||
|
|
||
| ## Followup actions (operator-gated, not auto-merged) | ||
|
|
||
| 1. Pick A/B/C above. Each requires explicit approval. | ||
| 2. Reconcile `CHITTYAPPS/chittybooks/CHARTER.md` and `CHITTY.md` once the choice is made (currently both claim a Worker at `books.chitty.cc`, which is false). | ||
| 3. Patch `CHITTYOS/chittycommand` so its `booksClient` does not call the dead `CHITTYBOOKS_URL` (see `docs/proposals/ch1tty-connector-revision.md`). This is the only auto-mergeable action in this branch. | ||
|
|
||
| ## Deploy gates | ||
|
|
||
| - [ ] PR approval before any `books.chitty.cc` DNS or Worker creation. | ||
| - [ ] CHARTER.md in `CHITTYAPPS/chittybooks` must be reconciled with whichever option is chosen (currently aspirational). | ||
| - [ ] `CHITTYOS/chittybooks` legacy fork is renamed, archived, or repurposed as ChittyLedger-Evidence — do not let it shadow the canonical surface. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Mercury Multitenant Model | ||
|
|
||
| > How Mercury bank data flows through ChittyFinance with tenant + Legal Person isolation. | ||
|
|
||
| ## Requirements (non-negotiable) | ||
|
|
||
| 1. **`tenant_id` required on every Mercury-derived record.** No row, webhook event, or queue message lacks it. Cross-tenant reads are server-side blocked, not client-trusted. | ||
| 2. **Legal Person ChittyID binding required.** Every Mercury account maps to exactly one Legal Person (`P-Legal` entity), recorded as `legal_person_chittyid` on the account row. Account ↔ Legal Person is many-to-one (one LLC can have many accounts). | ||
| 3. **Wave business mapping required.** Each `(tenant_id, legal_person_chittyid)` pair has at most one Wave business. The intended persistence point is a `integration_account_links` table (source = `wave`, target = `mercury`) — **this table does not yet exist in `database/system.schema.ts`**, so the mapping is contract-only today. Interim implementations may store the link under `integrations.metadata` until the table lands via a coordinated schema cutover (see `CLAUDE.md` → "Schema Changes"). Unmapped Mercury accounts produce reconciliation conflicts once the surface is live, not silent omission. | ||
| 4. **ChittyBooks reconciles over Mercury + Wave + Stripe** by reading ChittyFinance's reconciliation views. ChittyBooks never reaches into Mercury directly. | ||
|
|
||
| ## Identity model | ||
|
|
||
| ```text | ||
| Person (P-Legal, e.g. "IT CAN BE LLC") | ||
| │ legal_person_chittyid | ||
| └── Account (Mercury account #1, #2, ...) | ||
| │ tenant_id | ||
| └── Transaction (mercury txn rows) | ||
| │ metadata.mercury_kind, mercury_id | ||
| └── Allocation → Property/Lease (Business surface) | ||
| ``` | ||
|
|
||
| - **Business / Legalink separation**: business operations (property, lease, allocation) live on the Business surface; the Legal Person binding lives on the Authority/Person surface. They join through `legal_person_chittyid`, not by sharing schemas. | ||
|
|
||
| ## Data sources | ||
|
|
||
| | Source | Path | Tenant binding | | ||
| |---|---|---| | ||
| | Mercury read API | `https://api.mercury.com/api/v1` (direct, OAuth tokens scoped per tenant) | Token issued per `(tenant_id, legal_person_chittyid)` via ChittyConnect | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This multitenant contract still names direct Useful? React with 👍 / 👎. |
||
| | Mercury write API | `mercury-proxy` on `chittyserv-dev` (IP-allowlisted by Mercury) | `X-Mercury-Token` per request; proxy is stateless re tenancy | | ||
| | Mercury webhooks | `POST /api/webhooks/mercury/:tenantId` on `finance.chitty.cc` (native Mercury receiver, per `server/books/webhooks.ts`; PR #113). Per-tenant HMAC secret keyed at `webhook:mercury:secret:<tenantId>` in KV. The legacy `POST /api/webhooks/mercury` path was the ChittyConnect-normalized shim and is not the native receiver. | `tenant_id` is the path param and is verified by HMAC; `legal_person_chittyid` resolution is **not** performed inside the webhook handler today — it joins downstream via the local account row. | | ||
|
|
||
| The write proxy at `CHITTYOS/mercury-proxy` is **not** a tenant boundary — it is a network egress shim. Tenancy is enforced by the caller (ChittyFinance) before reaching it. | ||
|
|
||
| ## Reconciliation surface | ||
|
|
||
| ChittyBooks consumes these ChittyFinance endpoints, not raw Mercury. **Endpoints marked _(proposed)_ are not yet mounted** — they are the target surface for the reconciliation contract and must ship before ChittyBooks relies on them: | ||
|
|
||
| - `GET /api/transactions` — tenant-scoped transaction feed. Today the handler accepts only `?limit=`; `?source=mercury|wave|stripe` filtering is _(proposed)_ and must be added to `server/books/transactions.ts` before this contract is satisfied. | ||
| - `GET /api/reports/reconciliation?tenant_id=...&period=...` _(proposed)_ — three-way diff (Mercury ↔ Wave ↔ Stripe). Not mounted; only `/api/reports/consolidated` exists today. | ||
| - `GET /api/integrations/status` — currently returns per-provider `configured` booleans from env vars (see `server/routes/integrations.ts`). Last-sync timestamps and per-source health _(proposed)_ require extending the handler to read sync state. | ||
|
|
||
| Conflicts surface as `reconciliation_conflicts` in ChittyLedger-Finance (see `docs/chittyledger-finance-design.md`). | ||
|
|
||
| ## What is explicitly out of scope | ||
|
|
||
| - ChittyBooks may **not** call Mercury directly. | ||
| - ChittyBooks may **not** create Wave invoices/sales directly — those route through ChittyFinance. | ||
| - Credential rotation is owned by ChittyConnect concierge. ChittyBooks never sees a Mercury or Wave token. | ||
|
|
||
| ## Adversarial findings (2026-05-27 verification) | ||
|
|
||
| **CRITICAL — schema gap.** `legal_person_chittyid` column is **NOT present** on `accounts` in `database/system.schema.ts` (verified at lines 101-103; only `id`, `tenantId`, and downstream columns exist). The Legal Person binding is therefore not enforceable at the database level today. Two remediation paths: | ||
|
|
||
| - **Path A (schema migration)** — add `legal_person_chittyid TEXT NOT NULL` to `accounts` with a backfill plan. Coordinated cutover required (no migrations in this repo per CLAUDE.md "Schema Changes"; `drizzle-kit push` is destructive). | ||
| - **Path B (interim metadata)** — store the binding in `accounts.metadata->>'legal_person_chittyid'` until Path A is ready. Adds a runtime check at the storage layer. | ||
|
|
||
| Until one of these lands, the Mercury multitenant contract is **partially enforced** (tenant_id yes, legal_person no). Reconciliation reports MUST flag this in their output until the gap closes. | ||
|
|
||
| **Verified OK.** `tenant_id` is `NOT NULL` on `accounts`, `transactions`, `properties`, `integrations` (`database/system.schema.ts`), so any insert missing `tenantId` fails at the database. The 18 inserts in `server/storage/system.ts` rely on the caller's `data` containing `tenantId`; the schema constraint provides defense-in-depth. No write path can bypass it. | ||
|
|
||
| ## Deploy gates | ||
|
|
||
| - [ ] **BLOCKER**: `legal_person_chittyid` column or metadata interim must exist before reconciliation reports reference it. Until then, the column is contract-only. | ||
| - [ ] `legal_person_chittyid` column present on `accounts` (verify in `database/system.schema.ts` before reconciliation reports go live). | ||
| - [ ] Per-business Mercury webhook secret in place (already shipped — PR #113). | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The native webhook handler skips HMAC verification entirely when Useful? React with 👍 / 👎. |
||
| - [ ] No code path can write a Mercury-derived row with `tenant_id = NULL`. Enforce at the SystemStorage layer, not at the route layer. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # API path prefix — RESOLVED as non-issue | ||
|
|
||
| ## Original report | ||
|
|
||
| > `https://finance.chitty.cc/api/v1/entities` → `404`. Docs imply `/api/v1/*` everywhere. | ||
|
|
||
| ## Investigation (2026-05-27) | ||
|
|
||
| Two route families exist by design: | ||
|
|
||
| | Family | Mount | Examples | Verified | | ||
| |---|---|---|---| | ||
| | **Operational/meta** | `/api/v1/*` | `/api/v1/status`, `/api/v1/metrics`, `/api/v1/documentation` | `server/routes/health.ts:21,38`; `server/routes/docs.ts:7`. All return `200`. | | ||
| | **Resource data** | `/api/*` | `/api/accounts`, `/api/transactions`, `/api/properties`, `/api/tenants`, `/api/integrations/*`, `/api/reports/*`, etc. | `server/app.ts:74-118`; each route module under `server/routes/`. All return `401` (auth) — routes exist. | | ||
|
|
||
| `/api/v1/entities` returns `404` because **there is no `entities` resource**, not because of a path mismatch. The original concern came from assuming the `/api/v1` prefix applied to data routes; it does not. | ||
|
|
||
| ## Decision: no code change | ||
|
|
||
| - The split (meta under `/v1`, resources unprefixed) is intentional and consistent with the OpenAPI spec at `/api/v1/documentation` which lists `paths` correctly. | ||
| - All 5 in-repo references to `/api/v1` (`.github/workflows/register.yml`, `deploy/registration/chittyfinance.registration.json`, `client/src/pages/Landing.tsx:342`, `.claude/commands/quick-deploy.md:28`, plans) point at real endpoints. **Nothing to fix.** | ||
| - All 2 cross-org consumer references (`chittycommand/src/lib/integrations.ts:249`, `chittyregistry/src/routes/notion-webhooks.ts`) target `/api/<resource>` — correct. | ||
|
|
||
| ## Documentation patch (the only action) | ||
|
|
||
| Add a single sentence to `CLAUDE.md` under "Where Things Live" → routes section: | ||
|
|
||
| > Resource routes are mounted at `/api/<resource>` (no `/v1` prefix). Only operational/meta routes (`status`, `metrics`, `documentation`) live under `/api/v1/`. The OpenAPI spec at `/api/v1/documentation` is authoritative. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence beyond the earlier CLAUDE.md finding: this proposal still instructs maintainers to add a sentence saying Useful? React with 👍 / 👎. |
||
|
|
||
| ## Deploy gates | ||
|
|
||
| - None. No routing or worker change. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For protected resource routes,
server/app.tsrunstenantMiddlewareafterhybridAuth, andtenantMiddlewarereturns400unless the request includesX-Tenant-IDortenantIdquery; the JWT/session path only resolvesuserId, notc.var.tenantId. A ChittyBooks caller following this auth paragraph with only the service token +X-Chitty-User-Idor a session cookie will still fail every/api/accounts,/api/transactions, etc. request until it supplies the tenant selector the middleware actually requires.Useful? React with 👍 / 👎.