dodil/erp-method

node_modules/.cache/erp-method/v0.1.15/modules/waivers.yaml

# Known violations of the package contract, counted.
#
# A conformance gate that is red on day one gets ignored by day three, and a codebase this size
# has debt. So every violation is either FIXED or WAIVED HERE with a reason — and the waiver is
# a COUNT, not a blanket. Add a 35th unprefixed table to the CRM and the build fails; fix one
# and the build tells you to lower the number. The debt is visible, counted, and can only shrink.
#
# What a waiver is not: a place to put something because fixing it is annoying. Each of these
# costs a data migration or a test suite, and each names what it would take.

waivers:
  - check: key-type
    package: ar-core
    allow: 5
    what: ar_invoices.bp_id, ar_payments.bp_id, ar_credit_notes.bp_id, ar_dunning.bp_id, ar_customers.bp_id
    reason: >-
      NOT a type mismatch, which is what it was first recorded as. Invoicing's column named
      bp_id holds what the party master calls a bp_KEY — the customer's own identifier, which is
      a VARCHAR on purpose because plenty of businesses number their customers ACME-01. The
      party master derives a BIGINT bp_id from that key. Both meanings now live in one column,
      told apart only by whether they parse as a number, so the cross-module join needs a CAST
      (pinned by archive/code/crm-suite-app/v1/tests/test_suite.py). Retyping the column to BIGINT would
      make the party master a hard dependency of invoicing and break every business using its
      own customer numbers — a worse outcome than the bug.
    costs: >-
      four releases, because two of the five columns are PRIMARY KEYs and a PK type cannot be
      altered: expand, backfill and dual-write, switch reads, then drop a release later once the
      rollback window has closed. Full impact summary, with row counts:
      design/ar-bp-id-change-request.md
    owner: unassigned

  - check: prefix
    package: gl-suite-app
    allow: 12
    what: journals, journal_lines, ledger, periods, trial_balance, subledger_items, reconciliation, fx_rates, …
    reason: the rule postdates the package (as it did for the retired crm-suite-app)
    costs: 12 table renames plus queries, and a migration for any bucket with books in it
    owner: unassigned

  - check: runnable
    package: erp-shell
    allow: 1
    what: tests/
    reason: >-
      the shell holds no data and no rules: every path is a proxy to a module. A test worth
      writing needs the modules running, which makes it a suite test rather than a unit one —
      and the thing most worth asserting (that the user's own token is forwarded, so the module
      sees the person rather than the shell) can only be observed through a real gateway.
    costs: a deployed smoke test against erp-demo, of the shape archive/code/crm-suite-app/v1/tests/test_suite.py already has
    owner: unassigned

# ── platform follow-ups, not package violations ────────────────────────────────────────────
# Recorded here because this file is read when someone asks "what do we know is wrong?".

  - check: money
    package: brick-hr
    allow: 2
    what: hr_pay_grade.min_amount, hr_pay_grade.max_amount
    reason: >-
      found when check:packages began cloning reference.repos: the pay-grade band is VARCHAR, not
      NUMERIC(18,2). The spec calls for a 4-decimal rate type, which NUMERIC(18,2) cannot hold, so
      this may be deliberate (exact decimal as text) rather than drift. Not verified with the
      brick's owner; waived by count until it is.
    costs: a column retype in brick-hr (an ALTER, disruptive) or a money rule that admits rates
    owner: unassigned

follow_ups:
  - what: re-scope every service account holding org-wide k3.editor
    why: >-
      No role in k3-authorization-service is conditionable (verified 2026-09-18 against
      auth service-account list-roles: k3.admin, k3.editor and k3.viewer all lack
      conditionable_on, while ignite.app-developer and the scriptum roles carry it). So a
      credential that can write one bucket can write EVERY bucket in the org — which is why
      db.py defaulting to a live bucket was as dangerous as it was: there was no backstop
      underneath the env var.
    blocked_on: per-resource k3.editor, expected by 2026-09-20
    then: >-
      scope each app's SA to its own bucket DRN — cli-erp-suite-sa, cli-erp-bp-sa, cli-crm-sa,
      cli-crm-suite-sa, cli-gl-sa, cli-itsm-sa, cli-casting-sa, cli-retail-vision-app,
      cli-dodil-growth-sa, cli-dodil-crm-public-sa. Until then the app-side bucket assert is
      the only control, and it stays afterwards: scoping narrows the blast radius, but only
      the app can refuse to start against a bucket it was not told to use.