Two guards, both from real incidents in the same session.
1. invoice-create.sh — chronology (CGI art. 289). Dolibarr assigns the number at
validation, in creation order, so issuing a document dated BEFORE the last one
already issued gives a higher number to an earlier date. The July plan walked
straight into it: the M3 deferred part is due 2026-10-23 and must be issued at
D-60 (24/08) to stay under the L.441-10 I ceiling, while the M4 fixed part is
dated 23/08 — issue them in the wrong order and the numbering breaks. The
guard reads the last issued document of the same kind and refuses an earlier
date, with ARCO_ALLOW_BACKDATE as a loud, documented override.
Verified: refuses a 01/07 invoice against FAC008 (23/07), accepts 23/08.
2. test/scripts/guard.ts — production opt-in. The sandbox-only guard had no way
to express a deliberate production run, so any prod work meant bypassing it
entirely (which is how guards die). Production now requires BOTH
ARCO_ALLOW_PRODUCTION=<exact host> and
ARCO_PROD_CONFIRM=I-UNDERSTAND-THIS-WRITES-PROD, and prints a banner. Nothing
reaches prod by inheriting an ambient variable.
Also fixes a misleading "(sandbox verified)" log that printed even on prod.
grantAgentRight.ts joins the repo (it was never committed) and gains --revoke,
so a temporarily elevated right can be handed back — used today to attach a
payment in production and revoked immediately after.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh
test/.env ships DOLIBARR_ADDRESS pointing at PRODUCTION and test/main.ts
defaults to it, so any Playwright admin script run with the ambient
environment drives the real ERP. The REST path has been structurally safe
since ADR-0003 (dol-write.sh refuses non-sandbox hosts); the UI path had no
equivalent. scripts/guard.ts closes that gap — assertSandbox() resolves the
target and refuses anything that is not erp-sandbox.*, with the override
spelled out in the error. Verified: an unqualified run now dies instead of
reaching prod.
Two settings the write agent cannot reach (non-admin by design, 403 on
/setup/conf), rehearsed on the sandbox:
- sandboxLegalSetup.ts — capital social + multi-currency module. Finding:
the capital was NOT missing, it was stored as "1000€"; the symbol made the
value unusable by the PDF template, which is why "Capital de 1 000 €" was
absent from every invoice since January (C. com. R.123-238). Normalised to
"1000" → the mention now renders.
- sandboxCurrencySetup.ts — registers the USD reference rate. Enabling the
module is not enough: a currency absent from the rate table makes Dolibarr
silently fall back to EUR (observed on a probe invoice). With the rate
registered, an invoice carries USD 3,000.00 with its EUR counter-value,
i.e. the contractual obligation itself rather than a drifting equivalent.
Both scripts are report-only when they cannot recognise a form, and screenshot
what they did.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh
After an iso-prod refresh the instance unique-id changes, so an api_key encrypted
with the OLD id can't be decrypted — Dolibarr renders non-UTF-8 bytes in the field.
The POC's generateApiKey reused any non-empty value, so it copied that garbage into
test/.ai_agent_sandbox.key (corrupt key, 401s). Now it reuses ONLY a clean key
(^[A-Za-z0-9_-]{24,}$); otherwise it clears the field and regenerates. So
`checkpoint provision` after a refresh yields a fresh, working key.
Also documents the open PLATFORM follow-ups in ops/backup/README.md (easy to find
when revisiting ERP backups): the orphaned Longhorn `default` recurring-job group
(other cluster volumes have no offsite backup), and verifying the factory
pg_dumpall host cron.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
First real run against the sandbox revealed three issues in userSetup.ts:
1. generateApiKey generated the key client-side and read it into the file but
never submitted the edit form, so Dolibarr never persisted api_key (DB stayed
NULL → the key could not authenticate). Now it clicks Save after generating.
2. assignRights matched `rights=<id>` as an href substring, so a short id like
12 (facture creer) also matched rights=121 / rights=1232 and .first() clicked
the wrong link — facture creer was never granted. Anchored with a trailing
"&" (rights=<id>&) for an exact match.
3. createUser was not idempotent: a re-run hit the existing login and failed to
parse a new id. Added findUserId (look up by login via the user list) and
return the existing id instead of creating a duplicate.
Verified the symptoms live: ai_agent_sandbox (rowid 4) had api_key NULL and was
missing only facture/creer among the 11 intended rights.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Extend the Deno + Playwright UI-automation POC to provision the erp-sandbox
Dolibarr for the AI agent:
- moduleSetup.ts: add enableApiModule(ctx) — toggles the REST API / Web services
module on /admin/modules.php (kanban). Resilient: tries the fr_FR card label
"API/Web services REST (serveur)" first, falls back to a /API.*REST|REST.*API/i
title match if the exact label is absent.
- userSetup.ts (new): createUser (returns the new numeric id), assignRights
(clicks each addrights link on /user/perms.php, idempotent), generateApiKey
(triggers Dolibarr's generate control on the user card and reads the value back).
- provisionSandbox.ts (new entrypoint, main.ts untouched): login → enable API →
create ai_agent_sandbox (non-admin) → grant write rights → generate API key,
then write the key to test/.ai_agent_sandbox.key (gitignored) instead of
printing it.
- .gitignore (new), .env.example + README.md: sandbox vars, the
deno run --allow-all provisionSandbox.ts command, and kubectl one-liners to
pull DOLI_ADMIN_PASSWORD (secretkv) / DOLI_DB_PASSWORD (vso-db-credentials)
from the erp-sandbox namespace.
Why UI not SQL: API keys are encrypted with the instance's DOLI_INSTANCE_UNIQUE_ID,
so the key must be generated by the sandbox itself, not INSERTed raw.
deno check passes for provisionSandbox.ts and scripts/admin/userSetup.ts.
NOT run end-to-end: the sandbox Dolibarr is not installed yet (empty DB / fresh
install wizard), so the selectors are best-effort Dolibarr 22 conventions and
must be confirmed on the first real run.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>