All notable changes to the ERPClaw foundation skill.
- Consolidation translation previews, benefit-liability calculations and contract-balance reporting.
- Balanced reciprocal fund-transfer drafts and commitment worksheets.
- Company-scoped supplier quotation drafts with bounded snapshots and opaque audit references.
- Item barcodes for inventory records.
- Ordinary audit-log reads are scoped to the selected company and legacy or non-company rows fail closed outside the administrator-only system path.
- Party-ledger behavior is corrected for the accepted accounting flows.
- Action discovery keeps exact all-word matches and adds stable ranking plus a bounded multi-word fallback.
- Foundation migrations 059 and 060 add item barcodes and the company-scoped supplier quotation draft store. They are idempotent and preserve existing business data.
- Legal three-way trust reconciliation: transfers between trust accounts and account-level interest now count correctly on both the transaction and client totals, so a valid transfer no longer makes an account look unreconciled. Deposits and disbursements recorded without a matter stay unassigned, so a client shortfall is still flagged. A transaction or matter from another company is refused.
- Construction WIP schedule: earned revenue uses the exact cost-to-cost ratio (rounded once, at the end) and is capped at 100 percent of the contract. Approved and executed change orders are included in the revised contract, and only submitted and approved progress bills count as billed. The response adds the change-order and revised-contract amounts.
- A read-only continuous close readiness preview for one company and date: draft journals, unbalanced posted vouchers, unapplied payments and fiscal-year coverage.
- A 24-month business simulation report. It is a projection only and never touches the books.
- A read-only business-rule preview, and exact percent, duration, rating and clock custom-field types.
- An audit-log checkpoint: a fingerprint of the stored audit log that can be compared with a trusted earlier value.
- Schema drift and planning tools can inspect PostgreSQL schemas.
First versions (basic tests) of many planned capabilities. Each is a starting point; deeper coverage follows in later releases.
- Nonprofit: classified financial statements, release of donor restrictions, classification of conditional contributions, endowment appropriation, and split-receipt donor substantiation.
- Financial statements export to CSV with exact amounts and a company filter.
- A plain-language explanation of an account's budget-to-actual (flux) variance, with exact amounts and no model call.
- Draft employer tax return amounts (Forms 941 and 940) prepared from posted payroll.
- A weekly business digest report.
- Nonprofit: a Form 990 preparation worksheet.
- Selling: a read-only follow-up list of customers whose oldest overdue invoice has passed a set number of days, with exact outstanding totals.
- Buying: draft vendor bills from reviewed email fields.
- Inventory: a first demand forecast and a standard-cost variance report. Logistics: an inventory traceability report. POS: a measure of recorded retail volume.
- Construction: a cost-to-cost work-in-progress schedule.
- Legal: a three-way trust account reconciliation.
- Health: patient revenue posted to the ledger, approved charity care applied, a claim (X12 837) preview, and a 340B accumulator.
- Higher education: student receivables posted to the ledger and an IPEDS completions preview.
- Compliance: exclusion screening and an attestation readiness report.
- QuickBooks import staging and offline bank-feed file import.
- A local CRM account brief and insights over imported commerce data.
- The module generator validates generated modules safely before use.
- The ERPClaw MCP server works with the current Python MCP SDK.
- Foundation migration 058 adds the follow-up threshold store; nonprofit migration 002 adds donor substantiation columns. Neither changes existing data.
- A credit note now reduces the invoice it was issued against. Submitting a credit note allocates it to its original sales invoice; any excess stays as open customer credit. The original invoice cannot be cancelled while a live credit note is applied to it, and a credit note copies its invoice's currency fields. Foundation migration 052 brings existing books into line: it allocates already-submitted credit notes to their originals and records every change in the audit log.
- Stripe MRR is exact. MRR counts active subscriptions only; trialing ones are shown as a count, never as revenue. Annual plans divide exactly by 12, while day and week plans keep their existing approximation. Rounding happens once, after grouping. Each currency gets its own total; with more than one currency there is no single combined total. An unknown interval, a blank currency or a negative or non-finite amount is refused rather than guessed.
- The Stripe fee report keeps unitemised fees. An account with a mix of itemised and unitemised fees now reports its full fee total, and each entry names its source.
- India Form 16 and Form 24Q read the real salary slips: submitted and paid slips over the April to March fiscal year, TDS per quarter, gross salary and taxable income after the standard deduction.
- Loan approve, reject, update, disbursement, repayment schedule, restructure and close; stock revaluation; and work-order cancellation now take the company ledger lock first, then re-read the record and re-check its status before they write. A request that read a record which another request then changed rolls back and asks you to re-read it, instead of writing from stale data. Work-order cancellation never leaves a partial reversal.
add-carrier-invoice(logistics) now requires--total-amount, a positive amount with at most two decimals, and refuses a carrier invoice from another company. It no longer stores a silent zero.
- Migration 052: credit-note allocation for existing books (changes data; audited).
- Migration 053: adds the authority session and credential tables. They are groundwork for operator sign-in and are not active yet; existing approvals work as before.
- Migration 054: repairs PostgreSQL installs on which foundation migrations 001 and 002 had been recorded as applied without creating their objects.
- Lists and reports that used to total every company now need one. Selling, buying, inventory, payments, journals, HR, payroll and advanced-accounting lists and reports use the installation's sole company automatically, or refuse "Multiple companies found. Please specify the company by name." when more than one exists; a new
--companyflag resolves a company by exact id or case-insensitive name. - Cross-company reads and writes are refused.
get-account,get-account-balance,get-journal-entry,get-payment,get-customer,get-sales-invoice,get-supplier,get-purchase-invoiceandget-stock-entryrefuse a record of another company than the one given.add-quotation,add-sales-order,create-sales-invoice,add-purchase-order,create-purchase-invoiceand the recurring templates refuse a customer or supplier of another company. A posting now needs an open fiscal year on the posting company itself. Full list:../../BREAKING_CHANGES.mdBC17-BC22.
- Principals, company memberships and single-use authorization envelopes bound to one document, company and amount are in the schema and the administration actions (
issue-authorization,revoke-authorization,get-authorization,grant-/deny-/revoke-company-membership,list-company-memberships). STAGED is the supported install phase, where nothing changes for the operator; this release has no authenticated production issuer, so the ACTIVE issuance, revocation and envelope-reading actions answerAUTHORIZATION_ISSUER_UNAVAILABLE. Payments, journals, selling and buying submit/cancel paths pass through the shared authority gate. See../../BREAKING_CHANGES.mdBC18. - Read-only storage and read-only MCP sessions.
ERPCLAW_DB_READONLY=1opens the database read-only, refuses a path other than the install's own, and skips module, onboarding, dangerous and credential actions withread_only_session.ERPCLAW_MCP_READONLY=1lists, describes and runs only a pinned set of reads proven to write nothing. - Business audit entries name the record they describe (skill, action, table, record id), not the company, across selling, buying, inventory, HR, payroll, retail, hospitality, healthclaw and more; they also record the operating-system account and, where available, a claimed actor context and its status. That context is a claim, not an authenticated identity. Migration-written audit rows keep their earlier shape.
update-recurring-bill-templateedits a recurring bill template's schedule and items and moves it from draft to active; invalid input is refused before any write.list-landed-cost-voucher-anomaliesis a read-only report of posted landed-cost vouchers that priced another company's receipt or counted a receipt line twice, from before those were refused; it repairs nothing (reverse withcancel-landed-cost-voucher).delete-sales-invoiceremoves a draft sales invoice with no ledger rows or references and needs confirmation, alongside the existingpos-abandon-posting.
- Model-supplied
erpclaw_actionarguments cannot carry a routing, confirmation or database-target key (action,action-name,user-confirmed,db-path,db-url,actor,actor-id,session-token). A duplicate or abbreviated--action/--forceoption on the underlying router call is refused before anything dispatches. - Identity-mutating actions are not dispatchable over MCP at all (
add-user,update-user,add-role,assign-role,revoke-role,set-password,seed-permissions,link-telegram-user,unlink-telegram-user, both spellings ofinitialize-database); they stay available on the direct operator CLI under their existing gate. - A model-facing call can carry an operator-issued authorization in one explicit
authorization_idfield. A malformed id, or one shorter than 16 characters, is refused before anything runs, and an authorization id never appears in an MCP reply. See../../BREAKING_CHANGES.mdBC30.
- Six actions that pass a confirmation on to a gated child in another module now need
--user-confirmedthemselves:transfer-materials-to-subcontractor,cancel-subcontract-transfer,pos-submit-transaction,construction-approve-progress-bill,legal-generate-invoice,legal-send-invoice. The flag records the user's own confirmation; it is not a substitute for an authorization envelope. See../../BREAKING_CHANGES.mdBC29.
- Payment submit/cancel/allocate/reconcile, sales- and purchase-invoice cancel, journal-entry submit/cancel/amend, expense-claim approval, purchase-invoice submit, payroll-run submit/cancel, intercompany-invoice cancel, delivery-note submit/cancel, and stock-entry/stock-reconciliation actions now take their company's ledger lock and re-check their document under it. A concurrent or stale change is refused instead of posting twice, losing an update, or reversing twice; on a refusal, read the returned error and the document's current state before deciding whether to retry. A per-company ledger chain head records posting order (migration 038 reports any legacy legs it cannot sequence and rewrites none). See
../../BREAKING_CHANGES.mdBC43.
- Payments, journal entries, and the selling, buying and inventory documents accept
--dimensionsor repeated--dimension-key/--dimension-valueand carry them from draft to every ledger leg they post; an unknown or inactive dimension is refused before any write. - A ledger leg that needs a default cost center now uses the company's own default, or the oldest non-group cost center of that company; an approved expense claim posts on the employee's payroll cost center, else their department's, else the company default, and refuses when that account is a group cost center or belongs to another company.
allocate-paymentrefuses a currency mismatch between the payment and the invoice ("currency mismatch: invoice in<INV>, payment in<PAY>; invoice currency must equal payment currency");reconcile-paymentsonly pairs same-currency documents.add-paymentrefuses a party that does not exist, is of the wrong type, or belongs to another company; every allocation path refuses an unregistered voucher type.- A customer refund clears credit notes with the receivable sign and never touches invoices or advances; migration 032 heals existing installs and now skips customer refunds when it does (matters only when upgrading from a release before 032).
add-expense-claimrefuses a line on an account that is not an operating expense;approve-expense-claimchecks every account it would debit before the first ledger leg. An approved claim with no payable row of its own cannot be paid through an allocation ("has no payable in the payment ledger").- HR writers record their audit rows in the same transaction as the change they describe, so a failure after the change can no longer leave a change with no audit row.
add-landed-cost-voucherrefuses a purchase receipt that belongs to another company, and a receipt listed more than once in the same voucher, before any write; a charge account must be an operating expense account (type expense or untyped) or an untyped accrual liability.- A purchase-order line takes
--discount-amountor--discount-percentage, carried through receipt, stock valuation, bill and landed cost.
add-leave-allocationrefuses a fiscal year that belongs to another company or is closed.submit-payroll-runrefuses with a named list of missing statutory liability accounts, the amount each needs, and the name to give it, rather than folding the gap into net pay or posting an unbalanced total.- Bulk attendance refuses a future date.
- Revenue-contract, performance-obligation and variable-consideration amounts must be finite, non-negative and at most two decimal places; probability must be within 0-100.
generate-revenue-entriestakes--as-of-date(default today in UTC) and refuses a malformed or future date, posting only schedule rows due on or before it.- Inventory list and report actions refuse non-finite or overflowing numbers and invalid paging parameters.
create-delivery-noterefuses "Nothing left to deliver: invoice<name>already moved these goods out of stock" when a stock-moving invoice already shipped what the order has left; a sales order's status follows both its deliveries and its invoices.
get-userno longer returnspassword_hashin any mode.- The setup
--effectflag shadows the--effe/--effecabbreviations of--effective-date; use the full flag name.
- reconcile-payments offers each invoice at what it still owes. A partly paid invoice used to be offered at its full amount, so the reconcile failed and matched nothing for that customer or supplier; a paid invoice or another company's invoice could also be picked. Candidates now come from each invoice's own outstanding balance, in the right company, oldest first.
- update-invoice-outstanding and update-purchase-outstanding are retired. They moved an invoice's balance with no ledger posting, so the books and the customer or supplier ledger could disagree. Calling either now returns a steer: record cash with add-payment and submit-payment (or allocate-payment), reduce an invoice with a credit or debit note, or write off with write-off-invoice.
- Foundation migrations 037-051 ship in this release. Only 037 (cancellation-marking symmetry) and 051 (expense-claim payment-ledger backfill) change existing data; both audit each document they change. A PostgreSQL install that already recorded migrations 001 and 002 as applied does not re-run them under this release; the repair migration for that case is not part of this release. See
../../BREAKING_CHANGES.md, "Upgrading from v4.15.0 to release candidate 1". - Today's installer uses AvanSaber's signed registry. Accepting a customer's own publisher key is planned, not shipped in this release.
Release gate: nine end-to-end business scenarios, driven in plain business language and judged by a deterministic oracle against the resulting books (short-pay write-offs, recurring billing honesty, outside-invoice linking, order-to-cash with corrections, deposit forfeiture, and more), all pass at this tree. The books-integrity invariants held in every scenario.
- Cancelling an invoice now releases the payment allocations it voided. Cash no longer stays "applied" to a document that left the books; allocations are marked delinked (kept for audit), the payment's unallocated balance is restored, all in the cancel's single transaction. Migration 031 back-heals existing installs.
- What a customer owes is no longer double-counted. Payment submission wrote the same cash against the party twice in the ledger; readers now agree with the truth (a 1,000.00 invoice paid 300.00 reads 700.00). Corrected at the source with compensating ledger rows; migration 032 heals existing installs, refusing (and reporting) any payment whose stored residual disagrees with its detail rows.
- One canonical reading of the party ledger.
ar-aging,ap-agingandget-outstandingshare a single definition of which ledger rows count and which document each row belongs to;get-outstandingattributes each amount to the invoice it belongs to. New always-on invariant INV-27: every party's payment-ledger net must equal open invoices minus unapplied cash, exact to the cent. - Loan money reaches the ledger. Loan disbursement and repayment posted no GL under an error swallow; both now post balanced entries or fail loudly.
- Asset disposal posts to the right accounts. Sale proceeds and gain/loss stopped going to depreciation expense; a disposal gain can no longer be credited to plain Sales Revenue, and disposal account choices are gated by account type.
- Intercompany eliminations are idempotent, and old damage is correctable. Re-running elimination generation eliminates only new activity (migration 036 links existing entries); the consolidated trial balance names any unlinked elimination rows an earlier duplicate run left behind,
list-elimination-surplusshows exactly those rows, andremove-elimination-surplusremoves them only with--confirm, auditing every removal. - A billing period reads
invoicedonly when a real, GL-posted invoice backs it. Draft creation no longer flips the status;sync-billing-period-statusmaterializes the truth, a cancelled invoice auto-reverts the period, and migration 033 re-derives existing installs.generate-invoicesnow produces a real invoice end-to-end (company derived, a generic billing service item resolved through inventory), and flags — never blocks, never silently skips — a possible covering invoice. - An invoice raised outside the billing flow gets attached, not duplicated.
link-billing-period-invoiceattaches an already-posted invoice to its rated period (one link only; a second is rejected);unlink-billing-period-invoiceresets deliberately, with--reasonwhile the invoice is live. - Write-offs are real accounting. New
write-off-invoiceposts balanced bad-debt entries under the invoice's own voucher, dated by the decision (today) rather than the sale; short-pays can be written off in the same payment via thewrite_offdeduction type (migration 034); the legal edition's invoice write-off now moves the actual books instead of flipping a status. Both write-off actions ask for confirmation. - Property security deposits post GL (receipt, deduction, return, and the new
prop-forfeit-security-deposit), with the liability netting to zero across the lifecycle. - Education edition: notifications accept everything core writes regardless of module install order (educlaw migration 003); LMS document numbering carries the year (
LMS-2026-00001) with a counter migration for live installs; course-prerequisite inserts are dialect-portable on PostgreSQL.
- Intercompany elimination is done in one place. The legacy four-action elimination pair — which posted "eliminations" into operating companies' own books and left them unbalanced — is retired. The actions still answer: each refuses with one message naming the consolidation flow that does the job correctly, which keeps group adjustments at group level. Existing posted entries are left in the books (immutable; removing them is a bookkeeping decision); their two tables are archived and dropped by a migration.
- The two one-sided stock-ledger actions are retired the same way. They could move quantity with no general-ledger leg; both now refuse and name the document flows (stock entries, receipts, deliveries) that tie quantity to the books, and a constitutional gate pins that every stock movement path reaches the books.
- Every schema, connection and catalog access flows through one seam (
erpclaw_lib.seam, SQLAlchemy Core vendored, dialect-correct DDL). 38 of 39 module installers now provision through it, the module install path is verified against live PostgreSQL, and a ratcheted gate prevents new bypasses. MySQL is explicitly unsupported (the TEXT-keyed schema cannot be indexed there) rather than silently falling through. - Migrations declare whether they change data, and the ones that do write an audit-trail row per changed document inside the migration's own transaction.
- Three guidance steers close the gaps the release-gate scenarios exposed, each re-verified end-to-end: the one-payment short-pay write-off route; the billing flow opener (plan → rate → real invoice, in plain words); and the record-once-then-link rule for outside invoices (an invoice exists in the books exactly once — never generate a second one for a period already covered).
- An invariant check that examined nothing may no longer report a pass; the smoke tier verifies through the product-library checker on both backends; test lanes bind the tree under test rather than the deployed install, with a poisoned-install companion proving each pin does its job.
- Rating engine complete: all 7 rate plan types rate to exact Decimal —
time_of_use,demand,prepaid_credit,hybridjoinflat/tiered/volume_discount. TOU tiers validated for 24h coverage; demand plans price peak + optional energy component; prepaid deducts FIFO inside the billing transaction with explicitover_limiton insufficient balance; hybrid composes non-hybrid components. - Money write gates: garbage, non-finite (NaN/Infinity), and finite-but-unrepresentable magnitudes are rejected at every billing write entry point with a named validation error; any value that passes a gate survives currency rounding. Legacy stored garbage is contained as a per-meter/per-row named data error — one bad meter never hides, lies, or aborts a run.
- No silent non-billing:
run-billingreports per-meter rating failures loud;generate-invoicesmarks a periodinvoicedonly with a real invoice id (failures stayratedfor retry with the reason reported). - Crash-safe billing runs: new
billing_run+billing_run_targetregistry (migration 030, both dialects).run-billing,generate-recurring-invoices, andprocess-recurringprocess one target per transaction; a crash mid-run resumes via the newresume-billing-runwith zero duplicate documents; failed targets isolate, healthy targets proceed. New actions:list-billing-runs,get-billing-run,resume-billing-run. Delegated resumes carry the caller's database context. - Truthful period rows:
run-billingnow stampsbilling_period.rate_plan_idwith the plan that actually priced a re-rated open period. - INV-25 always-on invariant: for every open AR/AP invoice,
outstanding_amountmust equal its payment-ledger detail net — checked continuously, not only at paid.update-invoice-outstanding/update-purchase-outstandingco-post the matching ledger detail row in the same transaction; auto-submitted recurring bills post their ledger row. - Riders: pre-master-key-load environment sanity check in the crypto layer (warn by default,
ERPCLAW_STRICT_ENV=1to refuse);cleanup-backupsnow requires confirmation (dangerous-action gate); phantom table declarations removed from the dependency map; billing SKILL section counts and homes corrected. - erpclaw-growth 2.10.0 (separate module): Wave-F usage-anomaly detector —
consumption_spikefires on default sweeps via a meter-local recency split;rate_plan_mismatchjudges each billing period against the plan that priced it with impossible-charge attribution; garbage data skips rows, never company-wide sweeps (migration 006).
Every known books-integrity gap surfaced by the live-agent testing program, closed and machine-guarded. All items shipped behind independent adversarial QA.
- Landed costs now reprice inventory valuation (and post GL at all).
add-landed-cost-voucherhad been silently broken since the 2026-05-31 voucher-type registry displacement (migration 004): itsvoucher_typewas never registered, so every landed-cost GL post failed the registry gate — masked because the feature had no tests. Migration 029 registers the type, and the action now writes zero-quantity stock-ledger valuation rows and updates FIFO layer rates so the goods carry their true landed cost, not just the base receipt rate.list/get/cancel-landed-cost-voucheradded (cancel = reverse, both GL and valuation). - New constitutional invariant INV-24: stock-account GL ≡ stock-ledger valuation. For each company, the sum over stock-type GL accounts must equal the sum of stock-ledger valuation movements (reversal-inclusive, exact TEXT-decimal). Any posting path that moves one side without the other now reddens the gate. Fills the long-standing stock-subledger blind spot (the AR/AP subledgers already had this check).
revalue-stocknow updates FIFO layer rates. Previously it repriced only the moving-average valuation, leaving FIFO items to consume at stale layer rates after a revaluation.- Rate-less invoice/order lines no longer silently price at $0. Line rate now resolves in order: explicit rate, then the customer's default price list, then any enabled selling price list, then the item's standard rate.
--default-price-list-idadded toadd-customer/update-customer(the previously-unused customer price-list link is now live).
- Open-order receiving/issuing guards. A standalone
material_receiptfor an item with an open purchase order is refused with guidance to receive against the order (prevents the double-count where the later bill re-receives the goods); the symmetric guard blocks a standalonematerial_issuewhen an open sales order covers the item. - Material request → purchase order.
create-po-from-material-requestcopies requested lines into a PO, updates ordered quantities, and rolls the request status;get-material-requestreturns the request with its lines. - Payment deductions.
add-payment --deductionsrecords withholding / early-payment-discount / commission legs;submit-paymentposts them to their GL accounts under full validation; cancel reverses them. Previously the table and its UI existed but no action could ever write a row. - Retroactive pay flows into salary slips.
calculate-retro-payis now idempotent, and generated salary slips consume pending retro adjustments as an earning line (applied on submit, reverted on cancel) instead of leaving them permanently stuck. - HR government-ID protection. Free-text HR fields are scanned and a non-blocking caution is returned when a value looks like a government ID; those fields are masked to last-4 on read;
add-employee/update-employeenow accept--ssn(encrypted at rest, last-4 on read).
install-moduleno longer writes into the OpenClaw workspace when installed under a non-defaultERPCLAW_HOME(a Hermes-home install had been touching the other runtime's skill directory), and its response now reports the real created-table count instead of zero.- Credit-hold refusal message no longer claims a
--user-confirmedoverride that the module layer never honored; it now names the real release path.
- Receiving guidance tightened. The procure-to-pay receiving note previously permitted a standalone
material_receiptfor purchased goods "unless you restate the unit cost" — but a cost-stated standalone receipt still leaves the purchase order un-received, so a later bill/receipt flow receives the goods again and double-counts stock (reproduced end-to-end via live agent testing: one 100-unit delivery became 200 units on hand). The steer now says NEVER for purchased goods, and instructs checkinglist-purchase-ordersbefore any bare "receive stock" request. The stock-entries catalog row carries the same warning at the point of use. Guidance-only; no action, schema, or GL change. A product-level guard (warn/link when receiving an item with an open PO line) is tracked separately as a build decision. - Catalog-pricing guidance added. The pricing catalog row now states that an item's default selling price is
add-item/update-item --standard-rate, and thatadd-item-pricerows (named price lists) are NOT automatically consulted at invoice time — previously nothing steered between the two overlapping surfaces, and live agent testing showed "set the selling price to X" landing in the unconsulteditem_pricetable. Guidance-only.
- Internal design-document references in migration docstrings, the table provenance manifest, and repository prose were replaced with stable opaque provenance keys and neutral descriptions. No functional change to any migration, action, or schema (verified by AST comparison); the provenance governance gate keeps full strength through a maintainer-side reference map.
update-foundationnow heals theerpclaw_module.versionbookkeeping row. Since 4.12.0, a confirmedupdate-foundationreconciled files and ran pending migrations but never updated the module-version row thatlist-modulesreads, so an upgraded ClawHub install under-reported its version indefinitely (fresh installs were unaffected —install-modulewrites the current version). The confirmed-success paths — both the applied path and the in-sync early return — now bump the row to the registry's foundation version, extending the 4.12.0 rule "a run reportingokmeans files AND schema converged" to include the observable version row. The bump is idempotent, so the in-sync path also HEALS rows left stale by pre-rider upgrades on the next reconcile. It never fires on a dry-run/preview (previews stay read-only) or a failed migration (the DB stays at the last good migration and the run exits 1 — never a half-convergedok), and is a clean no-op on the DB-less skip. No schema change, no migration, no new action.- erpclaw-meta
SKILL_TABLESstale CRM entry corrected. Theerpclaw-crmpresence-check row referenced pre-rename table names (crm_lead/crm_opportunity/crm_campaign); the live foundation tables arelead/opportunity/campaign, so thecheck-skillstables_okflag for CRM could never read true. The ratified hygiene rider namedcrm_lead→lead; the two adjacent names on the same line were the same staleness and are corrected together so the presence check is actually functional (map-only change; no action, schema, or GL impact).
- 33 shipped actions are now actually reachable through the router. The meta-router
ACTION_MAPgained every action defined in the per-domain sub-scripts that it previously could not dispatch — most importantlymigrate(the documented upgrade path), the whole custom-fields cluster (add-custom-field/list-custom-fields/remove-custom-field/set-custom-field-value/get-custom-field-values), and credit control (place-customer-on-holdand the dunning cycle). Previously these returned "Unknown action" via the router despite being fully built and tested.hr-status/payroll-statusnow route via ALIASES to each domain'sstatus. A new L0 dispatchability gate (defined ⇒ routable, all modules) makes this bug class structurally unable to ship again. update-foundationnow converges files AND schema. A confirmed apply-path reconcile applies pending foundation migrations after the file sync (idempotent; loud, per-migration-ledgered failure; explicit skip on uninitialized DBs) — an upgrade can no longer silently skip schema migrations, including on the in-sync retry path.rollback-foundationrestores files only, never schema (migrations are forward-only).migratejoinedDANGEROUS_ACTIONSbeside itsschema-*siblings. One-time bootstrap note (box-verified): the very firstupdate-foundationFROM a pre-4.12.0 version still runs the old in-memory code, so schema converges on the second call (or a follow-upmigrate); every upgrade from 4.12.0 onward converges in one call.
- 3 dead tables dropped (migration
028_drop_dead_orphan_tables_2.py):communication,erpclaw_module_validation,erpclaw_table_ownership— all verified zero-writer/zero-reader/zero-FK (necessity audit, adversarially QA-confirmed); dialect-aware + idempotent; PostgreSQL-rehearsed at close. Fresh-install table count 776. - Table provenance manifest (
scripts/table_provenance.json): every table now declares why it exists (doc ref / parent concept / explicit accepted-risk); a diff-aware L0 gate blocks any NEW provenance-less table — adding a table is now a governance event.
- Shared integration lib modules:
erpclaw_lib/action_validators.py(the validators previously duplicated across integration addons),erpclaw_lib/integration_secrets.py(AES-256-GCM field crypto with transparent legacy-format read-back),erpclaw_lib/integration_actions.py(shared sync-job/GL-rule/reconciliation action bodies; table writes stay with owning modules).
- Putaway rules + pick lists + hard, persisted stock reservations. Stock promised to one order can no longer be silently drained by another. Reservations are now first-class, persisted, and binding:
add-reservation/release-reservationhold a quantity against an (item, warehouse), and a stock issue (e.g.material_issue) that would breach the active reserved quantity is refused with the available-minus-reserved figure and rolls back in one transaction (no partial SLE).get-projected-qtynets active reservations against on-hand and is byte-for-byte back-compatible when there are zero reservations. Putaway rules (add-putaway-rule/list-putaway-rules/delete-putaway-rule,apply-putaway-on-receipt) route received goods to a target warehouse by item or item group. Pick lists run end to end:create-pick-listfrom a sales order ->add-pick-list-item->mark-picked->complete-pick-list(into a delivery note) ->cancel-pick-list(releases the reservation). Warehouse-level for V1; bin-level is deliberately deferred. New tablesputaway_rule,pick_list,pick_list_item,stock_reservation_entryvia foundation migration025_putaway_pick_reservation.py(net-new, dialect-aware, idempotent; all quantities Decimal-as-text). PostgreSQL-rehearsed before release. - Item-global alternatives / substitutes (Wave 2 S7). Define substitute items once at the item level instead of repeating them on every bill of materials:
add-item-alternative/list-item-alternatives/remove-item-alternative, andget-best-alternative-for-itemwhich returns the highest-priority substitute that actually has enough stock at a given warehouse (ties broken by on-hand quantity). Each alternative is directional (A can substitute for B without B substituting for A), ranked bypriority, and carries a Decimalconversion_factor. Manufacturing's BOM substitutes now fall back to these item-global alternatives when a BOM line defines none of its own, so the same substitute list is not maintained twice (cross-module read only; manufacturing gains no new writes). New tableitem_alternativevia foundation migration027_item_alternative_table.py(net-new, dialect-aware, idempotent, UNIQUE + directional CHECK). PostgreSQL-rehearsed on the box at wave close. (Wave 2 S7) - Stock-entry typed dispatch — 3 new entry types (Wave 2 S6).
add-stock-entry --entry-typenow handlesrepack,subcontract(send_to_subcontractor), andmaterial_consumption(it previously errored on these three, even though thestock_entry_typeCHECK already permitted all seven).repackconsumes input lines and produces output lines within one warehouse and enforces a cost-balance invariant (total input value == total output value within $0.01, Decimal) — an unbalanced repack is refused and rolls back.send_to_subcontractortransfers stock out to a--supplier-warehouse-idthat must be a transit or production warehouse (this is the dispatch path Wave 2 S5's subcontracting transfer emits).material_consumptionissues raw materials against an active--work-order-id(not_started/in_process), recording the work-order link on the stock entry. Two convenience wrappers:add-repack-stock-entry(one-in/one-out repack) andadd-material-consumption(single raw-material issue). SLE remains immutable and balanced; submit stays a single transaction. No schema change, no migration (the CHECK already had all seven values). (Wave 2 S6) - Inventory anomaly hooks AI1 (Wave 2).
detect-anomalies(erpclaw-ai-engine) gains two internal heuristics, bringingVALID_ANOMALY_TYPESfrom 18 to 20:reservation_over_availableflags an (item, warehouse) whose activestock_reservation_entryreserved qty exceeds the on-hand SLE balance (a stock-out is predicted — critical when nothing is on hand, otherwise warning), andsubcontract_receipt_mismatchflags asubcontracting_orderwhose finished-goodsreceived_qtydiverges frommaterials_transferredbeyond a 5% tolerance (over-receipt = more FG than the transferred materials can yield, critical; a completed order that returned materially less than was transferred = yield loss, warning). Both are internal detectors run by the existingdetect-anomaliessweep (no new public action) and emit through the shared_insert_anomalyhelper; they READ M5'sstock_reservation_entry/ the SLE and S5'ssubcontracting_order(cross-module reads) and WRITE only growth'sanomalytable. All quantities are Decimal-as-text. Theanomaly.anomaly_typeCHECK enum is extended in lockstep (init_db for fresh installs; growth migration005_wave2_anomaly_types.pyfor existing ones — dialect-aware, idempotent, no rebuild dance sinceanomalyhas no FK edges). Additive; no new action or table. (Wave 2 AI1) - Subcontracting receipt lifecycle (Wave 2 S5). Completes the outsourced-production workflow on top of
add-subcontracting-order(erpclaw-manufacturingaddon):submit-subcontracting-order(validates the BOM lists the service item, draft -> submitted),transfer-materials-to-subcontractor(ships only the stock raw materials to the supplier sub-store via S6'ssend_to_subcontractorstock entry — the service line is never shipped; partial transfers accumulate),receive-subcontracted-items(receives finished goods: the FG cost rolls up raw-material cost +--subcontract-charge-ratex received qty, posts exactly ONE balanced FG stock-ledger-entry + GL, generates a draft subcontract-charge purchase invoice, advances partially_received -> completed and stampsfinal_received_at), pluscancel-subcontracting-order(only pre-transfer),cancel-subcontract-transfer(reverses the transfer SLE — cancel = reverse, allowed only pre-receipt),get-subcontracting-order, andlist-subcontracting-orders. Buying'screate-purchase-receipt --subcontracting-order-idDEFERS the finished-goods bookkeeping entirely toreceive-subcontracted-itemsand posts nothing itself, so a subcontract receipt posts the FG SLE/GL exactly once (no double-post). All money is Decimal-as-text (ROUND_HALF_UP); GL is immutable (cancel = reverse); the FG receipt is a single transaction running the 12-step GL validation. (Wave 2 S5)
- 4 net-new inventory tables (Wave 2 M5):
putaway_rule,pick_list,pick_list_item,stock_reservation_entryvia foundation migration025_putaway_pick_reservation.py. Quantities (reserved_qty/expected_qty/picked_qty) are Decimal-as-text (no float); FKs toitem/warehouse/sales_order/companyintact. Net-new, no rebuild; SQLite + PostgreSQL (box-rehearsed) green. item_alternativenet-new table (Wave 2 S7): foundation migration027_item_alternative_table.py;conversion_factorDecimal-as-text,priorityinteger, both FKs ->item, UNIQUE + directional CHECK. Net-new, no rebuild; SQLite + PostgreSQL (box-rehearsed) green.anomaly.anomaly_typeCHECK extended +2 values (Wave 2 AI1):reservation_over_available+subcontract_receipt_mismatchadded to the growth-ownedanomalytable CHECK. Fresh installs get it frominit_db.py; existing installs from growth migration005_wave2_anomaly_types.py(dialect-aware, idempotent — SQLite rebuild guarded by a stored-SQL probe, PostgresDROP CONSTRAINT IF EXISTS+ADD CONSTRAINT). No FK edges onanomaly, so no rebuild dance.subcontracting_ordergains 2 nullable TEXT columns (Wave 2 S5):subcontract_charge_rate(Decimal-as-text per-unit subcontracting fee) andfinal_received_at(completion timestamp). AdditiveADD COLUMNvia foundation migration026_subcontracting_charge_columns.py(dialect-aware, idempotent — SQLite guarded by_sqlite_has_column, PostgresADD COLUMN IF NOT EXISTS); also ininit_schema.pyfor fresh installs. No rebuild.
- Hermes runtime port v1 (experimental). ERPClaw is now runtime-portable from a single source tree via the
ERPCLAW_HOMEdefaulting env var (unset =~/.openclaw/erpclaw, byte-identical to today — OpenClaw users unaffected). Runs on the Hermes Agent runtime as an experimental hedge via a GitHub tap (hermes skills tap add avansaber/erpclaw). OpenClaw stays the primary, supported, ClawHub-distributed runtime; Hermes is best-effort, no SLA, no feature-parity promise. No encrypted-credential actions on Hermes in v1. v1 acceptance: two business-user NL scenarios (company setup + order-to-cash customer payment) graded GREEN on Hermes by the same deterministic oracle as the OpenClaw baseline. Registry re-signed to registry_version 61. (Also in this window: a thin stdio MCP server over the db_query routers, built + QA-green but shipped as optional runtime-reach only — not a feature dependency.)
- Wave 1B — built-in Sales/CRM depth. Contacts + Companies, first-class Tasks, customizable Pipelines, Saved views (filterable), global CRM search, and CSV import/export — a "lead → opportunity → quote → order" Sales module (built in
erpclaw-growth). Foundation gains nullable FK links to the addon's contact/pipeline entities via foundation migrations023_crm_contact_fks.py+024_displace_opportunity_stage_check.py; the sharedtable_existsis now PostgreSQL-correct via the CLI. All six pieces were validated on the live gateway + PostgreSQL.
- Bank statement import + matching (erpclaw-integrations v2.1.0). Import OFX / CAMT.053 / MT940 / BAI2 statement files with pure-stdlib parsers (no new dependencies), idempotent re-import (
external_idunique), a rule-based auto-match engine against open invoices/payments, manual match/clear, andbank-reconciliation-summary. Tablesbank_statement/bank_statement_line/bank_match_rulevia foundation migration 020; writes routed through the foundationerpclaw_lib/bank_import.py(module write-ownership preserved).--bank-account-nameresolves accounts by name. NL-validated on the live gateway (fin-m2 PASS). - P&L by dimension — natural routing (M6 follow-up).
profit-and-lossnow accepts an optional--group-by <dimension>that returns the statement broken down per accounting-dimension value —revenue/expenses/netper value, income/expense accounts only, with entries that lack the key folded into an explicit(untagged)bucket (never dropped). The grouped totals reconcile to the flat statement (income_total/expense_total/net_incomeretained). Without the flag, output is byte-identical to before. The grouping composes the shared dialect-awarejson_getGROUP BY (no SQL duplicated frommulti-dim-trial-balance, which keeps owning whole-trial-balance grouping); an unregistered or deactivated--group-bykey errors cleanly pointing atlist-dimensions, and a--dimension-key/--dimension-valuefilter composes as filter-then-group. This makes the agent's natural reach (profit-and-lossfor "P&L by department") correct instead of routing it to hand-rolled cost-center SQL. Read-only report — no schema, migration, registry, or new-action change. (Wave 1 M6 routing fix) - Anomaly types AI1.
detect-anomalies(erpclaw-ai-engine) gains two heuristics, bringingVALID_ANOMALY_TYPESfrom 16 to 18:asset_book_value_driftflags assets whosecurrent_book_valuedeviates >5% from thegross_value − accumulated_depreciationinvariant (critical >25%), anddimension_tag_driftflags anaccount_typewhosegl_entry.dimensions_jsontagging is partial for a given key (some entries carry the key, others omit it — read & grouped in Python, dialect-safe, no JSONGROUP BY). Both names verbatim for corpus traceability; theanomaly.anomaly_typeCHECK enum is extended in lockstep.dimension_tag_driftconsumes M6'sdimensions_json. Additive; no new action, table, or migration.
Wave 1 Financials depth (partial bundle: P0 + M6 + M7 + S3). Additive only — no breaking changes. M2 and AI1 ship in a later bundle. No ClawHub upload this release (bundled into the next functional release; ClawHub foundation stays v4.1.6, propagated to the OpenClaw box via signed reconciliation).
- Multi-dimensional GL (M6). A
dimension_registry(seededproject/department/cost_center) now drives optional per-entry GL dimensions.insert_gl_entriesserializes adimensionsdict into the existingdimensions_jsoncolumn (default'{}', so every existing GL caller is byte-unchanged), GL validation gains step 13 (required-dimension enforcement peraccount_type+uuid_fkreferential-integrity check), andreverse_gl_entriespreserves the original dimensions on the mirror entry. Four CRUD actions inerpclaw-gl(add-dimension,list-dimensions,update-dimension,deactivate-dimension; deactivation is blocked while recent live GL references the key) and two new reports inerpclaw-reports(multi-dim-trial-balance --group-by "project,department",dimension-balance-report --dimension K).general-ledger/profit-and-loss/balance-sheet/cash-flowaccept repeated--dimension-key/--dimension-valuefilters. All dimension SQL routes througherpclaw_lib.query.json_get()for dialect-safe extraction (no rawjson_extractliterals). Migration017. - Asset depth (M7). Full fixed-asset lifecycle beyond depreciation:
impair-asset/reverse-impairment(write-down to recoverable amount with balanced GL; reverse mirrors the GL and restores book value — cancel-by-reverse, impairment rows are immutable),capitalize-asset(initial recognition from purchase cost), andrevalue-asset(upward/downward revaluation against a revaluation reserve, with depreciation recompute).complete-maintenancegains an--is-capexbranch that capitalizes the cost into the asset (DR Asset / CR Cash) and recomputes the depreciation schedule instead of expensing it. New voucher typesasset_impairment/asset_capitalization/asset_repair_capex;is_capexcolumn onasset_maintenance. Migrations018/019. (erpclaw-ops/erpclaw-assets) - Construction-work-in-progress (S3). A
cwip_cost_accumulationledger plus five actions in erpclaw-assets:add-cwip(start anunder_constructionasset),accumulate-cwip-cost(DR thecapital_work_in_progressaccount, per-project via M6'sdimensions_json),transfer-cwip-to-asset(capitalize toin_use+ start depreciation from the transfer date),cancel-cwip(reverse all accumulations; blocked if any cost arrived from a submitted document), andlist-cwip-projects.create-purchase-invoice(erpclaw-buying) andadd-journal-entry(erpclaw-journals) accept an optional--cwip-asset-idthat routes the GL leg to the CWIP account and records acwip_cost_accumulationrow in the same transaction. The existinggl_posting.pyguard that rejects a direct JE to a CWIP account is now reachable. New voucher typecwip_capitalization. Migration021.
erpclaw_lib.query.json_get()now emits dialect-correct SQL on PostgreSQL. The Postgres branch previously emitted the SQLite JSONPath formcol->>'$.key', which is invalid: Postgres->>takes a plain object key, and the JSON columns (dimensions_jsonet al.) are provisioned astext, so the operator needs a::jsonbcast first. It now emitscol::jsonb->>'key'(verified on PostgreSQL 16). All three branches also escape the key as a proper SQL string literal (doubling embedded single quotes) instead of raw f-string interpolation, so a key containing a quote can no longer break or inject the emitted SQL. Latent until Wave 1 M6's multi-dimensional reporting, which is the first production consumer.
close-fiscal-yearnow refuses to close when the chosen closing (retained-earnings) account belongs to a different company than the fiscal year being closed: it hard-errors with an actionable message and rolls back before any GL is posted. Previously, in a multi-company database, a mismatched closing account would have posted one company's net income into another company's equity (a silent cross-tenant contamination). Pure pre-write validation, no schema or API change.
- NL company-by-name resolution: business users can address a company by name ("for Acme, invoice Bruce") instead of a UUID. The
resolve_company_idchokepoint now accepts a--company "<name>"flag (exact, case-insensitive, dialect-neutralLOWER(name), never.ilike()) alongside the unchanged--company-id; the sole-company auto-detect path is byte-unchanged. Wired across the GL, payments, inventory, tax, journals, and reports actions plus the educlaw-k12 router (~70 call sites). A named-but-missing company fails loudly listing the available company names and never falls through to a different company, so one entity's books can never post to another. SKILL.md instructs the agent to pass the user's exact wording and never fuzzy-substitute or autocorrect a company name. Verified end-to-end on the live gateway (mc01 right-company invoice; mc02 missing-name hard-error with zero posting).
resolve-item(erpclaw-inventory): cascade item resolver that maps a loose/plural user phrase ("20 Brake Pad Sets") to the stored item via a deterministic 4-tier cascade (exact → singularized → substring → token-AND, stop at first non-empty, shortest-name-first). Read-only, cross-DB (dialect-neutralLOWER(...) LIKE, never.ilike()), stdlib-only singularizer. Returnssingle_match/multiple_matches/matched:falseso callers can branch deterministically. (Inventory action count 42 → 43.)
- Gateway replies no longer leak accounting internals to business users: SKILL.md
## Speaking to the usernow instructs the agent to translate or omit double-entry GL narration, account names, internal status/field labels (status, posting date, outstanding, valuation rate, naming series, gl/sle counts) and raw UUIDs, surfacing the business outcome instead. Guidance-only — no response-shape or schema change. - Stock receipts now always post the perpetual-inventory GL leg (DR Stock-in-Hand / CR Stock Received Not Billed); a missing Stock-in-Hand or Stock Received Not Billed account now fails loudly with an actionable message instead of silently skipping the GL while still moving the subledger. The
create_perpetual_inventory_gllib helper raises a structured error (caught at all 7 caller sites across inventory/buying/selling/manufacturing, surfaced as a clean JSON error with full transaction rollback — no SLE-without-GL).tutorial(demo company) now seeds a Stock Adjustment account so demo-company reconciliations post correct GL. - Purchased stock is now valued from its source document end to end: receiving against a purchase order (GRN) or recording a bill with stock update carries the unit cost into the stock ledger and posts the inventory GL. A standalone, rate-less external stock receipt with no item standard cost now fails loudly with an actionable message instead of silently booking inventory at $0 (internal transfers and manufacturing legs are unaffected — they keep inheriting existing valuation). SKILL.md now guides the procure-to-pay receipt flow.
--email/--phoneonadd-customer/update-customer/add-supplier/update-supplier; returned by the correspondingget-*.
customer.email TEXT,customer.phone TEXT,supplier.email TEXT,supplier.phone TEXT(nullable); foundation migration016(dialect-aware SQLite + PostgreSQL).import-customers/import-suppliersINSERTs now resolve against real columns.
- AR/AP payment application now clears the invoice/bill: the document's
outstanding_amount/statusis synced and a per-allocation payment-ledger entry withagainst_voucheris posted, so a recorded payment marks the invoice/bill paid (previously it stayed unpaid). - Gateway
voucher_typelabels are canonicalized system-wide (e.g. "Sales Invoice" ->sales_invoice) so the NL gateway and downstream GL/inventory/tax/reports/payments consumers agree on the voucher vocabulary.
erpclaw-selling/db_query.pymain(): re-add--descriptionargparse flag that was incorrectly removed as a dupe during v4.3.0 (it's used byadd-dunning-level). Server smoke caught:Unknown flags: --descriptionon first invocation.
Customer credit limit + dunning levels (ROADMAP S1). B2B AR depth: enforce credit limits at invoice submit and run automated dunning escalation cycles.
- 5 new actions in
erpclaw-selling:check-credit-limit --customer-id X— returns credit_limit, outstanding_ar, available_credit, credit_status (read-only).place-customer-on-hold --customer-id X --credit-status on_hold|suspended|active [--reason ...]— flip credit status. Audited.add-dunning-level --company-id X --level N --days-overdue D --dunning-action email|hold|call|suspend— configure escalation policy.run-dunning-cycle --company-id X [--run-date Y-M-D]— match overdue invoices to highest-applicable dunning level, take action (hold/suspend update credit_status; email/call record-only until M8 ships SMTP).list-dunning-runs [--customer-id X] [--company-id Y]— history.
- Invoice-submit credit policy hook in
submit-sales-invoice: blocks suspended customers, blocks on-hold customers, enforces credit_limit when configured. Outstanding AR computed fromsales_invoice.outstanding_amountof submitted, non-return invoices.
customer.credit_status TEXT NOT NULL DEFAULT 'active' CHECK(credit_status IN ('active','on_hold','suspended')).dunning_leveltable: id, company_id, level (1-10), days_overdue, action, template_id, description, UNIQUE(company_id, level).dunning_runtable: id, company_id, run_date, customer_id, level, invoice_ids_json, action_taken, status, generated_email_id, notes.- Migration
002_credit_dunning.pybrings existing installs up to date. Idempotent. customer.credit_limitalready existed (since pre-v4.0); no change there.
source/erpclaw/scripts/erpclaw-selling/tests/test_credit_dunning.py— 17 tests covering check-credit-limit, place-customer-on-hold, add-dunning-level, and the invoice-submit policy hook. All pass.
- The email dunning action is record-only at this version; actual SMTP send ships with ROADMAP M8 (Email marketing real sender). When M8 lands, run-dunning-cycle will trigger real emails for level=email rows.
- The
callaction is also record-only by design — it logs that a human-call escalation is due; no auto-dialer. - credit_status is a separate concept from customer.status: status governs "is this customer still ours" (active / inactive / blocked); credit_status governs "are we currently extending credit" (active / on_hold / suspended). Both must be active for a new invoice to submit.
Operational follow-up to the v4.2.x publish saga. Three small Tier-1 fixes from the pending-work backlog.
- Shared skip filters. Extracted
SKIP_DIRS,SKIP_SUFFIXES,SKIP_FILE_EXACTintosource/erpclaw/scripts/erpclaw-setup/lib/erpclaw_lib/skip_filters.py. Bothrelease/regen_module_manifests.py(manifest hashing) andsource/erpclaw/scripts/module_manager.py(install-time integrity walk) now import from the canonical module. Prevents the v4.2.1-class "extra files" failure where the two walks drifted out of sync.
- Registry cache auto-refresh on foundation upgrade. Previously
~/.openclaw/erpclaw/registry_cache.jsonwas not invalidated whenclawhub installupgraded the foundation, so the firstinstall-moduleafter an upgrade ran integrity checks against the OLD manifest and surfaced false-positive "N mismatched" warnings. Now_load_registry()compares the bundled foundation version against the cached one and treats the cache as stale when the bundled version is newer, forcing a refresh. - Snapshot publish-surface alignment.
release/scripts/regen_clawhub.pywas carrying test files (scripts/<sub>/tests/),.gitkeepmarkers, and.github/configs that ClawHub strips at upload time — dead weight in the snapshot (~50% of file count). Fixed the snapshot walk to actively remove these fromclawhub/erpclaw/so the local snapshot matches what users actually receive. Snapshot file count dropped from 187 to 90.
- No new actions, no schema changes, no behavioural changes for end users; this release is internal hygiene only.
- Verified end-to-end on the OpenClaw test server via the post-republish smoke plan after publish.
Companion fix to v4.2.1. The v4.2.1 manifest correctly excluded tests/, .github/, bin/, etc. — but module_manager.py's install-time integrity check kept its old skip set, so when install-module <vertical> git-cloned a public repo (which ships the full tree including those dirs), the walk treated tests/ files as "delivered" while the manifest didn't list them. Result: every install-module after a v4.2.1 install failed with "0 mismatched, 0 missing, N extra files".
source/erpclaw/scripts/module_manager.py: aligned the install-time integrity walk's skip set withrelease/regen_module_manifests.py.SKIP_DIRSaddstests,.github,bin;SKIP_SUFFIXESadds.sig; newSKIP_FILE_EXACTcovers.DS_Store,.gitkeep,.clawhubignore. The two skip sets must stay in lockstep — drift between them produces "extra"/"missing" false positives. A code comment inmodule_manager.pyflags this invariant for future maintainers.
- Foundation must be re-installed (
clawhub update erpclaw --forceor fresh install) to pick up the fixedmodule_manager.py. The bug only manifests wheninstall-moduleis called against a v4.2.0/v4.2.1-installed foundation. - Smoke plan re-run on 2026-05-10 confirmed the cold-install path now passes end-to-end with this fix.
Fix-up release for v4.2.0. Two install-blocking issues caught during cold-install smoke testing on the OpenClaw server:
- Manifest / shipped-package alignment.
release/regen_module_manifests.pywas hashing files (tests/,.github/,bin/,*.sig,.gitkeep) that ClawHub strips during publish/install. Result: everyinstall-modulecall against a v4.2.0 install failed integrity check with "100 mismatched/missing files" because the manifest claimed files that don't ship. Fixed by aligning the walk's exclude set to ClawHub's actual ship contract:SKIP_DIRSnow includestests,.github,bin;SKIP_FILE_SUFFIXESadds.sig;SKIP_FILE_EXACTadds.gitkeep,.clawhubignore. Foundation manifest dropped from 183 file entries to 84 entries that exactly match what users see post-install. Registry re-signed; signer fingerprint unchanged:d471:335b:0e4d:75ce. - Vertical parser fixes propagated to public
avansaber/*repos. The four argparse duplicate-flag fixes that shipped in v4.2.0's foundation manifest were not pushed to the public repos thatinstall-moduleclones from (avansaber/healthclaw,avansaber/constructclaw,avansaber/legalclaw,avansaber/retailclaw). Result:install-module healthclawcloned the broken parser, hash-verified successfully (we'd hashed the broken file), and crashed at first invocation. Fixed by re-running the public-repo publish across allavansaber/*targets.
- v4.2.0 was published to ClawHub Marketplace 2026-05-10 with these latent bugs. v4.2.1 supersedes; users should
clawhub update erpclaw --forceto receive the fixed package. - Cold-install smoke plan run on 2026-05-10 caught both bugs before any external user encountered them.
clawhub install erpclawstill requires--forcedue to a VirusTotal Code Insight flag that fires on our crypto/external-API patterns. This is a marketplace-side scanner over which we have no control; the flag does not indicate actual malware. Out-of-scope for v4.2.1.
License change plus four parser bug fixes in vertical modules. ERPClaw moves from MIT to GNU General Public License v3, retroactively, across the entire codebase (foundation + 48 modules + addons + integrations + website + tooling). Existing copies that were downloaded under MIT retain their MIT rights for that downloaded copy per US copyright law on implied license; new clones, forks, and downloads receive GPL v3 terms. Patent (filed under USPTO provisional) is unchanged and continues to apply.
LICENSE.txtat every level (48 files) replaced with the canonical GPL v3 text from FSF, prefixed by a short notice and license history.package.jsonlicensefield in JS sub-projects updated from"MIT"to"GPL-3.0-only".- README, STATUS, ARCHITECTURE, ROADMAP, PROJECT_RULES, CLAUDE.md, and module READMEs across
source/updated to describe ERPClaw as GPL v3 with a license-history line for v4.1.x and earlier. - Marketing prose on
erpclaw.aiupdated to say "open source" generically, with explicit "GPL v3" only on license-specific pages. - Comparison pages drop "MIT vs. GPL" wedge framing; positioning shifts to architecture (AI-native vs. AI-decorated).
release/scripts/security_audit.pycheck_licencenow accepts either GPL v3 or MIT (the latter retained as legacy-compatible for v4.1.x).- Publish/packaging tooling
LICENSE_TEMPLATEre-templated with a short GPL v3 notice + license-history line. - Internal open-source sourcing guidance rewritten: GPL v3 is now license-compatible with ERPNext, Odoo Community, and other GPL/LGPL upstreams for direct inclusion; AGPL v3 is the new forbidden inbound boundary.
- Removed duplicate argparse flag registrations that crashed the parser at load time across four vertical modules. None of the affected verticals were callable for any action until the dup was removed.
healthclaw/scripts/db_query.py: duplicate--verified-by(kept LAB-domain copy at L453, removed Provider-Credentialing duplicate at L530).constructclaw/scripts/db_query.py: duplicate--spec-section(kept SUBMITTAL copy at L234, removed Drawing duplicate at L369).legalclaw/scripts/db_query.py: duplicate--reminder-days(kept CALENDAR copy at L161, removed SOL-Calculator duplicate at L235).retailclaw/scripts/db_query.py: duplicate--category-id(kept MERCHANDISING copy at L136, removed Procurement/Shrinkage duplicate at L215).
- Cleanup sweep across all
source/*/scripts/db_query.pyconfirms zero remaining duplicate argparse registrations.
- License re-anchor was driven by long-term enterprise positioning (Linux/Ubuntu/Red Hat playbook): copyleft prevents proprietary fork-and-close by Microsoft / Salesforce / Oracle, while GPL v3 Section 11 patent grant + defensive termination preserves enterprise adoption optics. Trade-off accepted: SaaS resellers must contribute back; some hyperscaler co-selling motions become harder. Net: better foundation for paid-services + commercial-license dual-track at scale.
- Registry signing key unchanged. Fingerprint remains
d471:335b:0e4d:75ce. - Manifests regenerated and re-signed (registry_version 8 → 18) to capture the new LICENSE.txt hash and the four fixed
db_query.pyfiles.
- Tested on OpenClaw 2026.5.7 with ClawHub CLI v0.12.3. Foundation passes the full 6-gate pipeline (270 L0 + 3,088 L2 + 248 L3 tests + invariants + smoke).
- NL routing validated end-to-end on the OpenClaw test server: foundation 5/5 scenarios (companies, customers, invoices, employees, items), three verticals 3/3 (healthclaw patient, educlaw student, constructclaw job).
- The license re-anchor was backed by a lawyer-level internal analysis.
Adds an ed25519 signature on the foundation registry. Reconciliation verifies the signature against an embedded public key before trusting any file hash, refuses tampered or downgraded registries, and refuses unsigned registries entirely. Closes the v4.1.5 supply-chain finding by giving the integrity check a cryptographic trust root rather than a hash-only one.
- ed25519 signature on
module_registry.json. Public key embedded inerpclaw_lib/signing.py::TRUSTED_KEYSwith a NamedTuple-based key list that supports rotation. Initial signer fingerprint:d471:335b:0e4d:75ce(labelerpclaw-foundation-signer-2026-05-04). Verify locally witherpclaw verify-trust-root. - New foundation action
verify-trust-rootthat prints the embedded key fingerprint(s). Use this to compare against the published fingerprint onerpclaw.aibefore trusting a reconciliation. - Strict-mode registry loader.
update-foundationrefuses to proceed unless the registry is freshly signed by a trusted key. The lenient loader used for read-only listings (available-modules,search-modules) emits a stderr warning when the signature cannot be verified, but does not block. - Monotonic
registry_versionfield inside the signed payload. The verifier rejects any registry whoseregistry_versionis lower than the locally tracked value, defending against replay/downgrade of older legitimately-signed registries. - Append-only signing log at
scripts/signing_log.txtrecording each signing event (timestamp, hash prefix, version, signer fingerprint). Allows after-the-fact detection of unauthorized signing. - Recovery path
--unsafe-trust-bundledforupdate-foundation. When a published key has been revoked and no rotated key has yet reached an offline install, an operator can reconcile against the locally-bundled hashes only. The flag emits a stderr warning, writes an entry to the audit log, and is documented as a recovery-only operator action; ordinary reconciliation always requires a verified signature. - Atomic publish pipeline: new entry-point
release/regen_and_sign.pyruns manifest regeneration + signing in one invocation and verifies registry mtime ≤ signature mtime before completion.
- Foundation action count: 477 → 478 (added
verify-trust-root). _load_registrynow returns the registry with_signed_by(fingerprint) on successful verification, or_signature_warning(string) when the lenient path falls back to unsigned content. The strict variant_load_registry_strictraises on any verification failure with no fallback to unsigned content.
Rotation is one of the few legitimate triggers for a future ClawHub re-publish, because every install ships with the embedded key list. Rotation procedure: ship the new key alongside the existing one with a valid_until for the old key, allow a grace period, then ship the new key alone. Stale installs that never reconcile remain locked to their original key list. Out-of-band fingerprint verification via erpclaw verify-trust-root and the published fingerprint on erpclaw.ai is recommended before trusting any rotation.
- v4.1.5 → v4.1.6 transition: the first reconcile that crosses this boundary establishes signing on the install. Subsequent reconciles verify.
- Long-running processes hold imported modules in memory; foundation file changes take effect on next launch.
- Backed by an internal fix plan and audit (8 BLOCK + 7 SHOULD items adopted from external + internal audits).
Foundation manifest reconciliation. Bundles the v4.1.4 runtime gate extension with two new gated actions that let an administrator align installed foundation files with the published module_registry.json manifest. Future foundation updates apply via these actions on explicit user invocation.
- Foundation actions
update-foundationandrollback-foundation(gated, require--user-confirmed). The first compares each installed file against the manifest'sfiles_sha256map, and for drifting files re-fetches from the published source and re-verifies the declared SHA256 before atomic replacement. A pre-flight verifies all replacements before any rename, so a hash failure leaves the install unchanged. Replaced files are preserved as.bakfor one cycle. - A non-blocking convenience check in the foundation router that surfaces a reminder on stderr when manifest-version drift is present, no more than once per 24-hour window per install. The check does not modify files; the user invokes
update-foundationto apply. Suppressed by the marker~/.openclaw/erpclaw/.skip_reconcileor the per-invocation flag--no-reconcile-check. Recursion-guarded for foundation-touching actions (update-foundation,rollback-foundation,install-module,remove-module,update-modules,schema-apply,schema-rollback). fcntl.flockon~/.openclaw/erpclaw/.sync.lockserializes reconciliation so the one-cycle.bakis never corrupted by concurrent invocation.- A safety guard refuses reconciliation when running inside a git-tracked source tree (developer checkout); the mechanism targets ClawHub-installed deployments only.
- Foundation action count: 475 → 477 (added the two reconciliation actions).
_strip_router_flagscontinues to strip--user-confirmedbefore forwarding to domain scripts; the foundation router gate is the single source of truth for confirmation.
Reconciliation verifies each file against the SHA256 declared in the published manifest before atomic replacement. Cryptographic signing of the manifest itself is roadmap for v4.2.0. Operators preferring not to use the reconciliation path can place the opt-out marker ~/.openclaw/erpclaw/.skip_reconcile.
- Long-running processes (MCP servers, daemons) hold imported modules in memory; foundation file changes take effect on next launch.
Closes the v4.1.3 OpenClaw Tool Misuse Concern by extending the runtime gate to administrative actions beyond financial postings.
DANGEROUS_ACTIONSfrozenset extended with 11 entries spanning RBAC + identity changes (add-role,assign-role,revoke-role,seed-permissions,update-user,set-password), credential lifecycle (set-credential,delete-credential,migrate-credentials,import-master-key-from-backup), and account-state (unfreeze-account). All require--user-confirmedon every invocation.- Foundation SKILL.md
## Runtime gateparagraph reworded to describe high-impact actions broadly without enumerating action names. Catalog and frozenset are the source of truth. - Gate-rejection error message generalized: now says "is a high-impact action" instead of "materially changes financial or system state".
- Stale comment in
db_query.pyreferenced a removed environment-variable bypass; cleaned up.
Cross-machine backup restore + Tier A regression fix-ups discovered during v4.1.x test-plan execution.
- New foundation action
import-master-key-from-backup. Required for cross-machine restore: a backup taken on Machine A is now restorable on Machine B with full encrypted-column readability. The backup's ECRYPT02 header carries a passphrase-wrapped copy of the column-encryption master key; this action unwraps it and installs at~/.config/erpclaw/master.key. Refuses to overwrite an existing master key without--force. Passphrase via--passphrase,--passphrase-from-stdin, or--passphrase-from-env.
backup-database --encrypt: now wraps the current machine's column-encryption master key with the backup passphrase and embeds it in the ECRYPT02 header. Backups taken without a master key (no encrypted columns yet) work as before. Response now includescarries_master_key: boolto indicate whether cross-machine restore is supported for this backup.- Foundation action count: 474 → 475 (added
import-master-key-from-backup).
- Foundation SKILL.md catalog now lists the 5 credential-management actions (
set-credential,get-credential,list-credentials,delete-credential,migrate-credentials) added in v4.1.0, plus 2 module-discovery actions (list-articles,build-table-registry) that were previously implemented but undocumented. L0test_skillmd_action_completenesswas failing on this drift; now passes. test_nacha_ach.py::TestAddEmployeeBankAccount::test_basic_addupdated to decrypt encrypted columns before asserting plaintext (regression caused by v4.1.0 column encryption).
- No code logic changes; only documentation alignment + one test fixture update.
- 3 pre-existing
erpclaw-os-engineconstitution failures (Article 5 cross-module write violations + addon SKILL.md drift) deferred to the vertical addon cross-test tier.
Made the v4.1.0 runtime gate's enforcement visible in SKILL.md so static-analysis review correctly attributes write actions to a gated context.
- New
## Runtime gatesection in foundation SKILL.md (8 lines, immediately before the action catalog) describing the per-invocation flag requirement and the router's pre-dispatch rejection of unflagged calls.
- No code logic changes. The v4.1.0+ runtime gate is unchanged.
- Phase 2 audit reviewed the proposed text; revised to drop verb-enumeration and env-var-bypass wording that would have re-summoned previous trigger phrases.
Tightened v4.1.0 security posture in response to OpenClaw rescan feedback.
- Hardened the runtime confirmation gate to require explicit per-invocation flag; removed an environment-variable form that could globalize confirmation across processes.
- Module install now verifies the full file tree against the foundation registry, not only the manifest entry-point.
- Trimmed user-facing security claims to neutral wording matching implementation. Mechanism specifics live in code, not in marketing prose.
- Environment-variable bypass for the runtime confirmation gate. Per-invocation flag is the only path.
- Cron / CI users that relied on the removed env var: switch to per-invocation flag. The gate's error message indicates the required flag.
- v4.2.0 will add cryptographic signature verification (sigstore/cosign) on top of the file-tree integrity manifest, plus an approve-pending queue for sanctioned automation.
Comprehensive security modernization. Real architectural changes: audited crypto, file-based credential management, column-level encryption, runtime confirmation gate for high-impact actions, supply-chain integrity verification, lib bootstrap removal. No legacy --api-key flag.
- Column-level encryption for selected sensitive fields (employee SSN, bank routing/account numbers). New helper
erpclaw_lib.encrypted_columns. - Encrypted credential store with per-machine master key, accessed via foundation actions:
set-credential,get-credential(returns redacted),list-credentials,delete-credential,migrate-credentials. Library:erpclaw_lib.credentials. - Runtime confirmation gate for high-impact financial-mutation actions. High-impact actions require explicit per-invocation confirmation; routed through the foundation gate before dispatch.
- Module integrity verification in
module_registry.json.module_manager.py install-moduleverifies content integrity against the foundation registry. - AES-256-GCM streaming backup format
ECRYPT02with 1 MiB plaintext frames, per-frame nonces, and an embedded passphrase-wrapped master key for cross-machine restore. Files of any size supported (no in-memory load).
erpclaw_lib/crypto.pyrewritten to use thecryptographylibrary (OpenSSL via cffi). PBKDF2-HMAC-SHA256 at 600,000 iterations (OWASP 2024). Field-level encryption uses raw AES-256-GCM withenc:v2:prefix. LegacyECRYPT01decrypt path retained for v4.0.x backups.- Stripe addon: hard-removed
--api-keyflag.erpclaw-integrations-stripev2.0.1 reads credentials from the foundation credential store. Users migrate viaerpclaw migrate-credentials(one-time read-from-DB → write-to-encrypted-store) or set fresh viaerpclaw set-credential --integration stripe --from-stdin. - Lib bootstrap removed.
_bootstrap.pydeleted.~/.openclaw/erpclaw/libis now a symlink (created atinitialize-database) to the skill-bundled location, not a self-healing copy. Eliminates the "self-modifying code at runtime" finding. - Foundation
SKILL.mddisclosure cleanup. Removed 4 v4.0.1 paragraphs (Credential handling, Data protection, Module installation safety, Library self-heal) that were reading as scanner trigger surfaces while describing addon behavior or implementation details. Kept only the factual one-line Security summary.
--api-keyflag (Stripe addon). Useset-credentialinstead._bootstrap.py(erpclaw_lib._bootstrap). Lib symlink replaces self-heal.install_shared_libraryfoundation function. Symlink replaces.- Homemade HMAC-SHA256-CTR cipher in
crypto.py. Replaced withcryptographylibrary AES-256-GCM.
- Registry foundation entry was stale at
version: 3.5.0/has_init_db: false/action_count: 438. Now reflects current state:version: 4.1.0/has_init_db: true/action_count: 467.
- Existing v4.0.x backups remain decryptable (legacy
ECRYPT01path retained). - Existing plaintext column rows pass through
decrypt_for_columnunchanged; encrypted-on-write applies to new rows only. No mandatory data migration. - Stripe users who previously stored API keys via
--api-key: runerpclaw migrate-credentialsonce after upgrade to move keys from DB plaintext to the encrypted credential store. - Cron/agent/CI users: append
--user-confirmedto high-impact action invocations.
Eliminate the Rogue Agents (cron) concern from the ClawHub OpenClaw review by removing decorative cron: blocks from foundation and grouped-addons SKILL.md files.
Phase 2 audit verification (B1) discovered that OpenClaw's runtime cron daemon does NOT auto-discover SKILL.md cron: blocks. Active scheduling requires explicit openclaw cron add CLI commands. The cron: block in foundation SKILL.md was therefore decorative metadata, not active scheduling — but the ClawHub static analyzer was reading it as scheduled financial mutation and flagging it as HIGH/Concern.
Removing the decorative blocks eliminates the trigger at the source without changing operational behavior (no user has ever had ERPClaw crons running automatically from clawhub install; users wanting daily jobs always had to run openclaw cron add manually).
- Foundation
SKILL.md: removed the entirecron:block (4 entries: process-recurring, generate-recurring-invoices, check-reorder, check-overdue). Replaced "Background automation" prose section with "Optional scheduling" pointer toopenclaw cron addfor users who want daily jobs. erpclaw-growthSKILL.md (grouped addon): removed 1 cron entry (weekly anomaly detection sweep).erpclaw-opsSKILL.md (grouped addon): removed 3 cron entries (monthly depreciation, daily overdue issues, weekly SLA compliance).- Library self-heal disclosure kept as standalone subsection (was bundled with cron prose in v4.0.1).
- Foundation
version: 4.0.1→4.0.2. erpclaw_lib/__version__ = "4.0.2".module_registry.jsontop-levelversion: "4.0.2".
- No code paths read SKILL.md cron blocks. Verified via grep across the source tree and internal tooling — zero hits for cron-block consumption.
- All 4 daily action targets (
process-recurring,generate-recurring-invoices,check-reorder,check-overdue) remain in foundation as on-demand callable actions. No capability removed. - Users who want automatic daily runs use
openclaw cron add --name <id> --cron "<expr>" --message "Using erpclaw, run the <action> action."— the same path that was always required for actual scheduling.
| Version | Cron in foundation SKILL.md | Cron actually running on install |
|---|---|---|
| v3.5.1 | 4 entries, announce: false |
NO (decorative only) |
| v4.0.0 | 4 entries, announce: false |
NO |
| v4.0.1 | 4 entries, announce: true |
NO |
| v4.0.2 | none | NO (unchanged — same as all prior) |
The "migration" is operationally a no-op. Only the SKILL.md text changed.
Security patch responding to ClawHub OpenClaw v4.0.0 review findings. Documentation, defaults, and disclosure changes; no schema changes, no new actions.
- Cron jobs now announce. All four daily background jobs (
process-recurring,generate-recurring-invoices,check-reorder,check-overdue) flipped fromannounce: falsetoannounce: true. Each run is now visible in the OpenClaw activity feed. (Resolves OpenClaw v4.0.0 finding "Rogue Agents" HIGH/Concern.) - README scrub. Removed "Self-Extending ERP" section from foundation README. Module-generation prose lives in the optional
erpclaw-os-engineaddon README only. (Resolves OpenClaw v4.0.0 finding "Unexpected Code Execution" MEDIUM/Concern.) - Foundation SKILL.md actions table no longer advertises the ~30 module-authoring / variant-analysis / heartbeat / semantic-check actions that moved to
erpclaw-os-enginein v4.0.0. Replaced with a single pointer to the optional addon. Removedgenerate-moduleanddeploy-modulefrom the "always confirm" list (now addon-only). - Addon SKILL.md description reworded to lead with developer-tooling framing and explicit sandbox-first / user-approval-before-deploy disclosure.
- Credential handling, Data protection, Module installation safety paragraphs in foundation SKILL.md security section. Mirror the OpenClaw recommendations for its Note-level findings.
- Background automation section in foundation SKILL.md documenting the four cron jobs and the lib bootstrap self-heal behavior.
- chmod 600 on
data.sqlite,data.sqlite-wal,data.sqlite-shm— applied atinitialize-database, afterrestore-database, afterbackup-database, AND on every foundation action invocation. Backup outputs are also chmod 600. New helperchmod_db_files()inerpclaw-setup/db_query.py. - Lib bootstrap self-heal (
erpclaw_lib/_bootstrap.py). On every foundation action invocation, the router compares the bundlederpclaw_lib.__version__to a marker file at~/.openclaw/erpclaw/lib/.erpclaw_lib_version. On mismatch, it re-syncs the deployederpclaw_lib/from the bundled source, writes a new marker, and appends an entry to~/.openclaw/erpclaw/logs/bootstrap.log. Eliminates the v3.5.1 → v4.0.0 upgrade gotcha whereclawhub updateskipped the foundation post-install hook and addonsandbox.pycouldn't find the newgl_invariants.py. HonorsERPCLAW_DISABLE_BOOTSTRAP=1env var as an explicit opt-out. __version__ = "4.0.1"declared inerpclaw_lib/__init__.pyas the canonical lib version.
restore-databasenow explicitly chmods the restored DB file to 0o600 (previously inherited mode from the backup source viashutil.copy2).
- The 3 Note-level OpenClaw findings (Agentic Supply Chain, Identity & Privilege Abuse, Memory and Context Poisoning) remain at Note status by design — they describe disclosed-and-accepted ERP behavior, and the recommendations are user-side practices (use scope-limited keys, rotate credentials, restrict file permissions, test imports against a separate DB). v4.0.1 mirrors those recommendations in the SKILL.md security section.
- Keychain integration, SQLite encryption-at-rest, and module signature verification remain on the v4.1+ roadmap.
Architectural split. ClawHub static-analysis CRITs eliminated. clawhub install erpclaw works without --force.
- Foundation / addon split. 21 dev-time files (
generate_module.py,in_module_generator.py,sandbox.py, etc.) moved out of foundation to a new optional addon,erpclaw-os-enginev1.0.0, distributed via GitHub-only atavansaber/erpclaw-addonssubdirerpclaw-os-engine. The addon trips the same scanner the foundation used to trip; isolating it keeps foundation users scan-clean. os-prefix on 28 user-facing actions that moved to the addon (os-generate-module,os-deploy-module, etc.). Foundation router emits a structured migration error JSON for legacy bare-name calls.gl_invariant_checkerextracted toerpclaw_lib.gl_invariantsfor cross-package import. Foundation runtime keeps the GL invariant validation; addon's sandbox imports fromerpclaw_lib.gl_invariants.- Foundation
scripts/erpclaw-os/retains 7 runtime actions only:validate-module,list-articles,build-table-registry,schema-{plan,apply,rollback,drift}.
erpclaw-os-engineSKILL.md, db_query.py, web_dashboard.py, sandbox.py — addon entry points.- Foundation-locator pattern in addon's
db_query.py(3-candidate sys.path resolution: env, prod, repo-relative).
- ClawHub release id:
k974yxfap664grmdnxfsstnhy5863wfa.