Review Context Inventory

Review context inventory #

Classification: Restricted (describes source code structure).

Every input that changes the review UI. Companion to the Review Kit ADR; the review core’s comments cite it as “inventory §n”.

Purpose: before porting the review to ui-v2/review-kit, know every input that decides what the review shows, allows or computes, so each one has a home in the kit architecture (context field → rule → modifier → model) and nothing is discovered after the port. Built 2026-10-23 from seven code scans of the sources listed under Sources; a bracketed number like [4] names the scan’s area, and its sources are the files to open.

Reading order: §1 is the shape of ReviewContext (what the container must gather). §2 is the catalogue of inputs by area, with the kit mechanism each one maps to. §3 lists the defects and divergences the scans found, which must be decided (fix before port, fix as you port, or carry over) rather than silently ported. §4 is what changes in ENG-9191.

Legend for the “kit” column: ctx = field on ReviewContext; rule = a Specification over ctx (field/column visibility); mod = pipeline modifier that sets flags on the model; adapter = per-type adapter logic; container = stays in the app (fetch, commands, persistence); labels = string the container resolves.


1. ReviewContext: what the container gathers #

One object, built once per document by the app’s container, passed to buildReviewModel(raw, ctx). The kit never reads any of these sources itself.

interface ReviewContext {
  type: ReviewDocumentType;                     // 6 types; credit notes are their OWN type here (see §3.4)
  status: ExtractionStatus;                     // 18 values [6 §3]
  phase: ReviewPhase;                           // loading | extracting | reprocessing | ready | failed | not_extracted

  integration: {
    state: 'none' | 'active' | 'disconnected';  // disconnected = exists but not ACTIVE; today treated as none [4]
    provider: Provider | 'NON_INTEGRATED';      // 14 accounting providers; unnamed ones resolve to NON_INTEGRATED in rules [4]
    capabilities: ReadonlySet<Capability>;      // from bonsai-model capabilities(): can_publish*, can_create_*, has_header_level_tags, supports_double_entry_bank_statement, can_publish_attachments … [4 T1]
    metadata: IntegrationMetadataFlags;         // discounts_enabled, address_for_sale_enabled, deposits_enabled, allow_service_date, use_account_numbers, pays_tax, multi_currency_enabled, country_code, invoice_auto_number_disabled, FreeAgent company fields, Xero short_code + bank_feed_connections, base_currency, default_item_id, book_close_date [4 T2b]
    accountingConfig: AccountingConfig | null;  // per type: tax_calculation, line_level_tax_type, extraction_level_tax_type, tax_calculation_mode, rounding_method, tax_rounding_method, *_decimal_places, supports_tax_group_override, calculate_groups_by_tax_components, supports_line_amount_types, empty_is_auto_tax [4 T2a]
    features: IntegrationFeatures;              // the derived "information" flags (isSupportsLineDiscount, requiresContactCode, ignoresContactRole, tax treatments, supportsInvoiceDueDate, …) — tofu-web already has this as domain/integrations/features.ts [4 T3]
  };

  entity: {
    settings: EntitySettingsForReview;          // §2.1: double entry per type, tax amount override per type, validate_total_amount, validate_tax_identification_number, is_contact_tin_sync_enabled, hidden_extraction_fields per type, csv_export_target, auto_publish_* , account_type_column_enabled, calculation_decimals, extraction_config, multi_currency_enabled, BS: enable_multi_account, enable_child_transactions, validate_* ×4, bank_statements_enable_double_entry, CN/AR accounting status on publish
    typesEnabled: ReadonlySet<ReviewDocumentType>;
    tagRoots: TagRoot[];                        // header + line roots with level/scope/mandatory/archived [5 §3]
    internalAccounting: boolean;                // non-integrated entity has internal accounts/tax/items/contacts [4]
  };

  options: {                                    // picker sources, already filtered by the container (compatibility, archived, date, class) [5 §4]
    accounts: PickerOption[]; taxRates: PickerOption[]; salesTaxRates: PickerOption[]; items: PickerOption[]; contacts: PickerOption[]; locations: PickerOption[]; currencies: PickerOption[]; bankAccounts: PickerOption[];
    canCreate: { contact; account; taxRate; currency; item; bankAccount };   // permission + capability + pays_tax already combined
  };

  numbers: {                                    // resolved once, see §2.2
    decimals: { line; tax; total; unitPrice; quantity; taxRate };
    minDecimals: number;                        // 2 today, ISO minor units when decided
    rounding: RoundingMethod; taxRounding: RoundingMethod; taxMode: 'LINE_BY_LINE' | 'ACCRUED';
    locale: string;                             // display only; parsing stays '.'-decimal until decided (§3.2)
  };

  permissions: {                                // §2.6
    canRead; canUpdate; canVerify; canDelete; canWriteIntegration; isImpersonating; isAdmin; trialBlocked;
  };

  document: {
    isDoubleEntry: boolean;                     // per-DOCUMENT flag, not the entity setting [5 §7]
    isPublished: boolean; lastExportedAt; externalId; errorPhase;
    duplicates: DuplicateMatch[]; pages: { total; read: number[]; held: number[] }; version: 1 | 2 (BS)
  };

  onboarding?: {                                // only in webapp onboarding and tofu-web onboarding
    lock: null | 'ready' | 'final'; limitReached; unitsLeft; discount: { earned; cap }; demo: boolean;
  };

  locale: { ui: string; dateFormat: (d) => string; rtl: boolean };
}

Everything else the scans found is either derived inside the pipeline from these (e.g. “Publish hidden because double entry is on and provider is not Money Forward”) or stays in the container (commands, polling, analytics, persistence).


2. Catalogue of inputs by area #

2.1 Entity settings [1] #

No organization setting is read by any review code. Entity inputs that matter:

Input Effect today Types Kit
{ap_bills,ar_invoices,direct_expenses}_enable_double_entry Publish hidden when provider can’t publish DE; export gains journal formats; knowledge-panel field lists switch. Form columns follow the document’s is_double_entry, not this setting. AP AR DE ctx.entity.settings + ctx.document.isDoubleEntry → mod applyEntitySettings, rule for debit/credit columns
bank_statements_enable_double_entry BS editor double-entry mode, contra-defaults panel, publish gate; clamped by canBankStatementUseDoubleEntry BS ctx + adapter(BS)
line_item_tax_amount_override_enabled (+ AR, DE variants) Line tax amount editable; off → tooltip; Xero DE always off; no integration → always on AP AR DE mod → column.editableToggle
account_type_column_enabled Initial state of Account Type column AP AR CN DE mod → column visible
auto_publish_*_enabled ×6 Mark Ready reads “Ready & Publish”; sync progress includes publish all mod → footer.primaryLabelVariant
ap/ar_credit_notes_accounting_status_on_publish, ar_invoices_accounting_invoice_status_on_publish/_on_verify Status passed to WASM validator CN, AR (FreeAgent) container (validator input)
{type}_enabled ×6 (+ credit_note feature flag) Which types exist in pickers/filters all ctx.entity.typesEnabled (container)
snippet_type, ai_auto_split_type, ai_page_classification_type Document-level snippet/reprocess actions, snippet hold document container (document panel actions)
calculation_decimals, extraction_config (per type: tax mode, rounding ×2, decimals ×4) Decimals/rounding for non-integrated entities (provider config overrides) AP AR CN DE ctx.numbers (resolver, §2.2)
multi_currency_enabled Effectively no effect (bug) AP AR DE drop or fix (§3)
{type}.hidden_extraction_fields Hides header fields, line columns, BS columns; required fields never hidden; never applies to credit notes AP AR DE BS rule notHidden(field)
{type}.validate_total_amount (default true) Mark Ready blocked on total mismatch; onboarding verdict AP AR DE (CN→parent) ctx → mod applyValidationPolicy → check.blocking
{type}.validate_tax_identification_number THE tax-ID mismatch block AP AR DE CN ctx → mod → counterparty.taxIdWarning.blocking
{type}.is_contact_tax_identification_number_sync_enabled Writes TIN to contact on verify AP AR DE CN container (command side effect)
{type}.csv_export_target Locks export format list (non-integrated) AP AR DE BS container (export command)
BS enable_multi_account, enable_child_transactions, validate_opening_closing_balance / credit_debit_total / subtotal / per_line_balance Add/Move account, add child, which Summary rows + per-row status BS ctx → adapter(BS) + mod
auto_ready_*, auto_done_* Only edited by the onboarding automation panel all out of kit

Not read by review (safe to ignore): auto_import_*, total_rounding, duplicate_detection_enabled, extract_duplicates_enabled, knowledge toggles, country_id, multi_type_extraction_enabled, expense_claim_enabled, target_accounting_software, all org settings.

2.2 Numbers: decimals, rounding, tax calculation [2] #

Input Resolution today Kit
Decimal places One resolver getExtractionDecimalPlaces: (a) currency tax_decimal_places only when no integration; (b) entity extraction_config when integration has no config; (c) integration accounting_config[type]; (d) entity calculation_decimals; (e) 2. Total = line decimals (total_decimal_places never read). Unit price: provider (QBO 7, Xero/Bukku/FreeAgent/Jaz 4, Zoho 5, freee 3, else 2) or max(2, line). Quantity: config or 4 (input fallback 5). Tax rate: 6 (invoice) / 2 (DE). ctx.numbers.decimals, computed by one shared resolver moved to a pure lib; kit only formats
Minimum shown decimals CURRENCY_MIN_DECIMAL_PLACES = 2 applied in onboarding sheet + total check, dropped in main-review line inputs, absent in footers/text ctx.numbers.minDecimals; presenters always honour it
Rounding + tax mode Integration accounting_config else entity extraction_config; all providers NEAREST, Japan preset ROUND_DOWN; calculator tofu-calculator-core (WASM) ctx.numbers; calculator stays in container (adapter changeRows recomputes)
Total reconciliation (per-line vs once on 10-dp sum) Only in _calculateInvoiceTotal and Mark Ready; not initial load/single-cell/DE adapter: one path for every recompute
External tax (QBO US AR) tax_calculation = External + external_total_tax; two different formulas exist adapter(AR) with the backend formula
Line amount type Stored else EXCLUSIVE; forced NO_TAX (AP/DE, no-tax provider); EXCLUSIVE under reverse charge; selector hidden when supports_line_amount_types false; onboarding ignores all of this rule priceBasisAllowed(ctx) → lines.taxMode.options
Discounts / deposit / balance due Line discount AR only (none/Xero/Sage/MYOB, not Sage AP); header discount QBO or none (calc also Xero/Sage/MYOB); deposit QBO; balance due from calculator rules on ctx.integration.features/metadata
Display locale Hard-coded en-US everywhere ctx.numbers.locale (decide: keep en-US or follow UI locale)
Typed number parsing . only; 1.234,50 cut / rejected / dropped depending on surface one parser in the kit’s cell editor, behaviour decided once (§3.2)
Date parsing Stored MMM dd, yyyy; ambiguous numeric = MM/DD unless first > 12; invalid → today (9215 cancelled) one parser; decision pending (§3.2)
BS numbers Hard-coded 2 dp + NEAREST; Number() loses precision; calcOptions exists but never passed adapter(BS) receives ctx.numbers like every other type

2.3 Validation [3] #

Four points: live form (zod + WASM tofu-validator-core, shared by main review and onboarding), Mark ready (client TS + backend validate_ready, not in WASM), Mark done (onboarding only: total check + blocked dialog), Publish (Rust preflight + connector + bank-feed preflights).

Input Kit
WASM provider rules (QBO, Xero, Zoho, FreeAgent, AutoCount; Other = common only). Inputs: provider settings from metadata (pays_tax, base currency, multi-currency, QBO default item + book close date, FreeAgent company), cached catalogues, invoice status, today (UK). Skipped when no contact. container runs it; results enter the model as field.error, cell.error, notices[validation]
TS required fields per provider (Xero AR account/tax, QBO no-tax, Zoho AR number) rule set requiredBy(ctx) → field.required; one source, not three
Contra required (double entry) adapter/mod → cell.error
Archived tag refusal on save container command guard
Mark ready client gates: ≥1 line/transaction, total vs recalculated (full calculator), tax ID vs contact (3 variants, Zoho fetch), new contact place of supply, contact creation permission, BS publish confirmation mod applyValidationPolicy sets footer.primary.blockedBy[]; container shows the dialog variant
Onboarding-only: total check blocked dialog (Fix it / Turn off check / Ask admin), validation banner, per-cell errors + prev/next problem, unknown account/tax (non-blocking) kit presenters (ReviewNotice, LinesCard problem nav) fed by model; the “turn off” action is a command
Non-blocking warnings: tax ID 3 variants, tax rate not applicable, unknown currency, mandatory tag roots (no rule exists in VC), date parse (silent), duplicates, YAML required (= cannot hide, not a value check) model flags; mandatory tag root needs a real rule (§3.3)
BS: backend enforces opening/closing + per-line only; publish ignores the four toggles and runs ALL; UI shows credit/debit/subtotal too adapter(BS) check rows mirror the toggles; note the backend gap
Publish failures: 400 reasons, connector validate_bonsai first English error saved on the document, Zoho own checks model notices[publish_failed]; container maps keys to labels

2.4 Integration capabilities, config, metadata, provider branches [4] #

  • Capabilities read by review: can_publish (via canPublishExtraction, but 3 surfaces use the raw flag and skip CN rules), can_publish_credit_note, can_publish_ap_credit_note, can_publish_bank_statement (only for BS double entry; publish list is hard-coded), supports_double_entry_bank_statement (missing from tofu-web schema), can_publish_attachments, can_create_contacts/currencies/accounts/tax_rates/bank_account, has_header_level_tags (knowledge panel only; form uses a provider list), default_invoice_status_on_publish.
  • Not read: can_update_contacts, can_sync_contact_tax_identification_number (list used instead), can_create_items/tags, can_sync, is_auto_assign_tags, can_import, can_attach_ar_invoice_files, can_self_prompt_*.
  • AccountingConfig per type and metadata flags: see §1. Per-provider decimals/tax mode table in [2].
  • Provider branches not expressed as flags (must become features or rules before the port): Zoho AR number required; Xero AR account/tax required; QBO ACCPAY no-tax; QBO/UNKNOWN discount section; QBO waits for locations; FreeAgent republish status; Xero drops payment_type; Xero tax override unsupported; Xero bank-feed UI; per-provider export defaults; canPublishDoubleEntry (Money Forward only); supportsPublishForType (BS list); Xero+DE override off; AutoCount+AP no due date; QBO ARCN credit-note discount; AutoCount payment method; Sage AP no line discount; Xero ignores contact role; Zoho place of supply; validator provider list; getIntegrationInformation derived fields (line discount, items, service header, sales taxes, contact type, contact code (Bukku), DE paid-from (not FreeAgent), Sage addresses, Zoho/FreeAgent tax treatments, TIN sync list); header-tag provider policy; tag level/scope policy (Zoho/Sage, Sage DE none); IFV token list (only 5 providers named).
  • tofu-web already has domain/integrations/features.ts as the port of getIntegrationInformation: this becomes the shared IntegrationFeatures for both apps.

2.5 Field catalog, pickers, tags, priority [5] #

  • YAML attributes honoured: name, display_name (header labels only), extraction_type, required (= cannot hide), supported/unsupported_providers (where the form calls the catalog), requires_metadata_flag (addresses only). Ignored by forms: type, group_name, requires_capability (header tags), requires/excluded_by_entity_settings (forms use per-document flag), requires_accounting_config (read directly), requires_accounting_config_non_null, supported_version (server v2.version decides). No priority/hint/aliases/options exist. Line column headers come from i18n review.shared.columns, not YAML.
  • Fields decided outside YAML: location (shown when list non-empty), header tags (provider policy), AR header discount, due date (AutoCount), tax treatment, deposit, service date, DE paid-from account, AR line discount (MYOB not in YAML).
  • Onboarding today: SECTION_FIELDS has no integration section → location, tax treatment, header tags never render; line sheet has no item/tag/contra/service-date/account-type columns; DE gets a column subset; BS renders the full slot.
  • Priority fields: star in knowledge panel only; drive hinoki re-extraction. No ordering in forms.
  • Tag roots: four different computations (invoice/DE header: integration-only + provider policy + level/scope + mandatory + archived rules; invoice/DE lines: getVisibleTagColumns + LINE_ITEM + mandatory; BS2: parentless roots, no level/scope/provider/mandatory filter, deleted_at only; onboarding: none). → one tagColumnsFor(ctx, level, scope) in the shared lib.
  • Pickers: account compatibility filter copied ~10×; tax-rate filtering differs per form (invoice: date + class + selectable; DE: none; BS2: purchase+sales merged); Create New gated differently per form (invoice: permission + capability; DE: non-integrated only; BS2: readOnly only; items/locations: none). → container builds ctx.options with one filter set; canCreate computed once.
  • Persisted prefs: items per page (localStorage ui-state per key), review split widths, review mode; BS2 column toggle in memory only; ColumnVisibilityMenu has zero usages. → container.
  • Double entry: per-document is_double_entry (set at extraction); AR contra = debit, AP contra = credit; BS contra defaults derived from first transaction, not persisted.

2.6 Flags, permissions, status, trial, locale, layout, concurrency, analytics [6] #

  • Feature flags read in review: credit_note, preprocess_docs only. Env: NEXT_PUBLIC_BANK_STATEMENT_HELP_URL; ?zoom=auto|lens.
  • Permissions: resource per type (INVOICE / DIRECT_EXPENSE / BANK_STATEMENT / CREDIT_NOTE), canUpdate / canVerify (INVOICE_VERIFICATION:update) / canDelete, INTEGRATION:write for inline create, read gates, impersonation denies writes, trial cap denies create/extract, tutorial allows all. tofu-web has canInEntity instead; webapp has use-permission. → ctx.permissions, computed by each app.
  • Status machine: 18 extraction statuses; per-status primary action/editable (extraction-action-map); static statuses lock; page lock; type-change and re-extract preconditions; edit warning for published/exported; onboarding lock kinds ready | final with 8 final notices; form freeze during Mark done; publish not offered for ERROR/LIMIT_REACHED/VERIFIED. → mod applyDocumentLock + footer derivation from one status table shared by both apps.
  • Trial/onboarding: LIMIT_REACHED read-only notice; units left; discount loop (1 %/doc, BS 1 %/page, cap 20 %); publish facts (bankFeed, autoPublishes, exportTypes); demo beats; tofie rail (36 line ids); persisted progress. → ctx.onboarding + container.
  • Locale: 49 locales, RTL for ar/he/ur (used in two onboarding components only), useDateFormat, calendar locale. Label volume: review 1,485 leaves (89 namespaces), onboarding 679 (63 namespaces), plus statuses 103, extractionFields 197, apiToasts 66, common 66 → ReviewKitLabels will be large; group it by presenter.
  • Layout: container breakpoints (560/440/400/450/…), viewport mobile 768 / onboarding lg 64rem; review split persisted (webapp) vs in-memory (onboarding); hotkeys (bulk mod+shift+*, viewer q/w/e/r/a/s/d/f/c/v/j, onboarding mod+Enter, sheet keys); zoom lens geometry; page image sources (signed URLs, size map, refetch on focus, tutorial r2Key); rotate debounced 500 ms; snippet drafts in localStorage. → ReviewWorkspace props + container.
  • Concurrency: 120 s list polling (off in tutorial); staleness detector invalidates detail (no banner in main review); client-only save confirmation (no server optimistic lock); 409 retry on verification; unsaved-changes guard; onboarding save queue (settle 60 ms, timeout 10 s); no websockets. → container.
  • Analytics: business events (Datadog RUM + Amplitude) and 147 onboarding_* events with server mirror; the review-emitted ones are listed in [6 §8]. → container, triggered from callbacks; the kit emits nothing.

2.7 Bank statement specifics [7] #

★ items with no invoice analogue (kit decisions in M3.5.6): multi-account panels + Add/Restore + identity/currency buckets + filters; per-account published lock (host currently locks the whole statement once any group is published; backend guard differs); bank-feed Connected badges (Xero-only concept); new-account Accept/Edit (dialog lacks the account number the backend requires); Summary with editable extracted values + extBalance/calcBalance/status columns + 4 validation toggles; per-account contra defaults; Move to Account / Change Currency / child rows; reorderTransactions exists with no UI; no keyboard navigation between cells (move never called); pagination local, virtualised > 100; bulk publish per-account result panel + error banners (map knows only Xero keys); partial read / page billing shown only in onboarding; export runs from the review header (host dialog never opens); 84-key label contract with hard-coded English in 10 places; tofu-web schema lacks supports_double_entry_bank_statement.


3. Defects and divergences to decide before/while porting #

Each of these is a place where porting “as is” would freeze a bug or a fork. Mark each: F fix before port (in webapp, small), P fix as you port (kit does it once), C carry over and ticket later.

Decision (Estu, 2026-10-23): everything is P. Nothing is fixed in webapp first and no separate tickets are opened now. The kit resolves each item once, during the port ticket that touches it; the webapp keeps its current behaviour until it switches to the kit. Items that only the backend can change (marked backend below) are recorded here as known gaps the kit must tolerate, not as work. Header tags follow the Rust capability has_header_level_tags, not the TS provider list.

Where each P lands: §3.1 duplicates and the number helpers → ENG-9191 (rules.ts, numbers.ts) and ENG-9192/9193; §3.2 surface differences → ENG-9191 contract decides the single behaviour (min decimals 2, typed-number input rejected with a message, price basis gated, tax rate 6 dp, ACTIVE-filtered integration source, header tags by capability, TIN sync by capability, canPublishExtraction everywhere); §3.3 gaps → the kit follows the backend as it is today and the gap is listed in the parity map with status backend; §3.4 modelling → ENG-9191.

3.1 Two or more copies of the same logic (P: the kit keeps exactly one) #

  • Total-vs-lines check: Rust, TS mark-ready, zod, onboarding model, ui-v2 schema (ignores toggle).
  • Tax ID match: Rust (1 variant), TS (3 variants), ui-v2 copy; two different block dialogs; CN differ client (blocks) vs backend (never checks).
  • Required fields: TS utils vs WASM vs form-state; ui-v2 copy lacks Zoho/CN/i18n.
  • Tax rate vs account class: 5 copies. Unknown account/tax: 2. Archived tags: 2. ≥1 line: 3.
  • WASM adapter, schema refinement, error-tree walkers, BS host, BS calc: webapp vs ui-v2 copies.
  • Line column builders ×5, account compatibility filter ×~10, tag roots ×4, tax-rate filters ×4, Create New gating ×4.
  • Number helpers in ui-v2: decimal-places.ts, formatCurrency ×2, round ×3, line amount type ×6, date suggestion parser, BS calc byte-identical.

3.2 Behaviour that differs by surface (decide once) #

  • Min decimals: main review shows 849.6, onboarding 849.60, footers none.
  • Typed numbers: 1.234,50 cut (main), rejected (onboarding), dropped (BS). Masks cut instead of round.
  • Dates: MM/DD assumption, invalid → today (9215 cancelled; ui-v2 has the same parser).
  • Price basis: onboarding not gated by supports_line_amount_types, no-tax forcing, reverse charge, hidden fields.
  • Tax rate 6 dp (invoice) vs 2 dp (DE). Discount rate rounded to money decimals (12.5 % → 13 % at 0 dp).
  • Integration source: total check reads integrations[0], form reads ACTIVE-filtered hook.
  • Header tags: Rust capability (Zoho, Bukku, DynamicsBc) vs TS policy (Zoho, Bukku, FreeAgent).
  • TIN sync: capability never read; provider list used.
  • Credit notes offered Publish via raw can_publish on 3 surfaces.

3.3 Gaps (C unless cheap) #

  • total_decimal_places never read; freee total 2 vs lines 0.
  • JS rounding half-away vs backend bankers on the external-tax swap; JS ignores rounding_method.
  • Reconciliation not applied on initial load / single-cell edits / DE (backend applies to DE).
  • Initial external-tax total formula differs from recalculation and backend.
  • AR not forced NO_TAX under no-tax config (footer hides rows, calculator still taxes).
  • Header discount for Xero/Sage/MYOB AR in the calc but input hidden.
  • Mandatory tag roots: no blocking rule anywhere; only Zoho sets the flag.
  • Hidden fields: never for CN; BS2 mapping lacks debit/credit fields; knowledge panel ignores type.
  • EC (expense claim) falls to the decimals fallback; backend computes as AP.
  • BS: publish ignores validation toggles; backend doesn’t check credit/debit/subtotal; new-account dialog vs backend account-number requirement; host locks whole statement; supports_double_entry_bank_statement missing from tofu-web schema; FreeAgent allows DE BS but Publish hidden.
  • Onboarding: location/tax treatment/header tags never render; DE and BS get no validation banner (sync mounted only in card stack); FreeAgent DE skips WASM (no contact).
  • Disconnected integration behaves as non-integrated everywhere except one banner.
  • Likely bug: invoice-validation-utils.ts:186 checks external_account_item_id (not in schema).
  • Dead code: _isInvoiceReferenceEnabled, _isSupportsAccountField, unreachable branch LC:655-665, DIRECT_EXPENSE_QUANTITY_*, ColumnVisibilityMenu (0 usages), reorderTransactions (no caller).

3.4 Modelling decisions the scans force #

  • Credit notes are a real type in ctx (config and entity overrides differ per CN), even though the form collapses them to AP/AR in two places.
  • Double entry comes from the document, not the entity setting, for columns; the setting only gates publish/export/knowledge.
  • integration.state = 'disconnected' must exist so the kit can show the banner instead of silently falling back to internal accounting.
  • numbers is resolved once by a shared pure resolver (moved out of apps/webapp/src/shared/utils/currency.ts), used by webapp, tofu-web and the BS adapter.
  • IntegrationFeatures (tofu-web domain/integrations/features.ts) becomes the single home for every provider branch in §2.4; rules read it, never provider ===.
  • Labels are grouped per presenter; the full set is ~1,500 review + ~700 onboarding leaves, so ReviewKitLabels must be split (header, verdict, fields, lines, footer, notices, bankStatement, viewer).

Sources #

The seven scans read these files; every claim above traces to one of them. When a row here and the code disagree, the code wins and this page is corrected in the same PR.

Scan Area Sources
[1] Entity settings libs/rust/bonsai-model/src/entity_setting.rs (EntitySetting), libs/rust/bonsai-model/src/entity.rs (extraction_config), webapp shared/lib/api/entities/read.ts
[2] Numbers webapp shared/utils/currency.ts (getExtractionDecimalPlaces), libs/rust/bonsai-model/src/integration.rs (AccountingConfig, per-provider defaults), libs/rust/tofu-calculator-core
[3] Validation libs/rust/tofu-validator-core/src, libs/rust/tofu-validator-wasm/src, webapp features/review/components/extracted-data/form/__tests__/wasm-validation-mapping.test.ts, apps/bonsapi/src/service (validate_ready), libs/rust/bonsai-integration/src/accounting (validate_bonsai)
[4] Integration capabilities, config, metadata, provider branches libs/rust/bonsai-model/src/integration.rs (IntegrationProvider::capabilities(), AccountingConfig, the *PublicMetadata structs), webapp shared/utils/integration.ts, shared/utils/integration-extraction-types.ts, shared/utils/integration-publish.ts, shared/lib/fields/integration-field-visibility.ts
[5] Field catalog, pickers, tags config/extraction_fields/*.yaml, webapp shared/lib/fields/integration-field-visibility.ts, shared/sync/accounting.ts, features/review/components/extracted-data/form/invoice/invoice-basic-information.tsx, .../invoice-lines/line-columns.tsx, .../line-table.tsx, .../double-entry/sections.tsx, the direct-expense form and transaction table
[6] Flags, permissions, status, trial, locale, layout config/features.yaml, webapp features/review/utils/extraction-action-map.ts, features/review/utils/status.ts, features/onboarding/publish.ts, features/onboarding/review-lock.ts, shared/lib/features
[7] Bank statement libs/typescript/ui-v2/src/components/bank-statement*, webapp features/review/components/extracted-data/bank-statement-v2, features/review/hooks/bank-statement-v2, config/extraction_fields/bank_statement.yaml, libs/rust/bonsai-model/src/integration.rs (supports_double_entry_bank_statement)

4. What this changes in ENG-9191 (contract ticket) #

  1. model.ts gains ReviewContext (shape in §1) and IntegrationFeatures is lifted to a shared package; the brief §3.2 is updated accordingly.
  2. rules.ts: the five YAML rule kinds + notHidden + priceBasisAllowed + requiredBy + tagColumnsFor, each a pure (ctx) => …, with the provider-branch list in §2.4 as the backlog of rules to express.
  3. numbers.ts: the decimals/rounding resolver, one function, tested against the per-provider table in [2].
  4. The file-by-file UI inventory (attached to ENG-9191) adds a column “ctx field” for every file it lists, so each webapp file is mapped to the context input it reads.
  5. Parity map: rows 95+ for the inputs in §2 that are not features (settings, numbers, validation points), so approval covers them too.
  6. §3 above is the agenda for one decision session (Estu + Koki + Ryota): F / P / C per line, written back into this file.