Files
erp/test
arcodangeandClaude Opus 5 11c8c65d45 feat(test): scoped AI users — one user per (environment × scope), provisioned by script
provisionSandbox.ts granted `[12, 122, 262, 32, 111, 251]`: opaque numeric ids,
one fixed scope, one agent per environment. Three failures in the 2026-07/08
sessions came straight from that:

1. Production `ai_agent` — documented as READ-ONLY in AGENTS.md, ADR-0003 and
   every SKILL.md — actually holds 46 rights against 9 declared, including
   create/modify on customer AND supplier invoices, thirdparties, contacts,
   thirdparty payment details, proposals, plus THREE delete rights (proposals,
   events, and submit/delete documents in the GED).
2. Writing a payment, then a product, to production each required granting a
   right by hand and revoking it after. A privilege granted ad hoc under time
   pressure is worse than one nobody holds.
3. A sandbox refresh wiped the agent's proposal rights, because they had been
   granted manually and lived nowhere in code.

- scopes.ts declares three scopes with their purpose and allowed environments:
  `read` (both envs), `sandbox-write` (sandbox only), `prod-write` (production
  only, narrow: invoices + payments, what the gated promote apply actually
  does). resolveScope() refuses a scope on an environment it does not belong to.
  No scope grants DELETE — the ledger is append-only, deletion stays human.
- provisionAiUser.ts creates or aligns one user per (environment × scope), emits
  its API key to a gitignored 600 file, and has an --audit mode that diffs what a
  user HOLDS against what its scope DECLARES. Production writes require the
  guard.ts opt-in.
- The audit reads permission labels LIVE off /user/perms.php rather than trusting
  a catalogue in code: ids are stable per Dolibarr version, not across them, and
  an audit that cannot name what it found is not actionable.

findUserId is implemented locally rather than imported: the trunk's userSetup.ts
has one, but it is uncommitted WIP and a provisioning script must not depend on
someone's working tree.

Tooling only — no production rights were changed. The migration (create the
scoped users, repoint the promote pipeline, then strip the over-grants from
`ai_agent`) rotates credentials used by every read skill and is the operator's
call.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh
2026-08-09 18:13:21 +02:00
..
2025-08-08 17:57:56 +02:00
2025-08-08 17:57:56 +02:00

test — Dolibarr UI automation (Deno + Playwright)

A small Deno + Playwright POC that drives the Dolibarr admin UI in the fr-FR locale. Playwright fills the same forms a human admin would, so the automation works even where the REST API can't (e.g. generating an API key, which is encrypted with the instance's own DOLI_INSTANCE_UNIQUE_ID).

Layout

  • main.ts — original entrypoint (first install, company/display/module setup).
  • provisionSandbox.ts — entrypoint that provisions the erp-sandbox instance for the AI agent (enable REST API, create a write-scoped user, generate its API key).
  • scripts/login.ts — admin login / logout / whoami helpers.
  • scripts/forms.tsfillForm, toggleOnOff, CKEditor/ACE helpers.
  • scripts/admin/moduleSetup.tsconfigureModule, enableApiModule.
  • scripts/admin/userSetup.tscreateUser, assignRights, generateApiKey.

Configure

Copy .env.example to .env and fill it in. .env, *.key, and .ai_agent_sandbox.key are gitignored — never commit secrets.

cp .env.example .env

Lock the installer (after a fresh install via main.ts)

Dolibarr keeps its web installer reachable until an install.lock file exists. After a fresh install (the main.ts flow), create it in the target pod — for the sandbox:

kubectl -n erp-sandbox exec \
  "$(kubectl get pod -n erp-sandbox -l app.kubernetes.io/instance=erp-sandbox -o name)" -- \
  /bin/sh -c 'touch /var/www/documents/install.lock && chown www-data:www-data /var/www/documents/install.lock'

The path is the Dolibarr data root (/var/www/documents, a PVC) — that's where Dolibarr checks, and being on the PVC the lock persists across pod restarts. For prod, swap to -n erp -l app.kubernetes.io/instance=erp. A sandbox seeded from prod still needs this: the seed (see ../ops/sandbox/) copies the DB + documents/mycompany, not install.lock.

Provision the sandbox

Provisions erp-sandbox.arcodange.lab: enables the REST API module, creates the write-scoped ai_agent_sandbox user, grants it its write rights, and has Dolibarr generate the user's API key. The key is written to test/.ai_agent_sandbox.key (gitignored) — it is never printed.

cd test
deno run --allow-all provisionSandbox.ts

Populate .env from the erp-sandbox namespace secrets first. secretkv carries the app env (including DOLI_ADMIN_PASSWORD); vso-db-credentials carries the database password:

# Admin password (key DOLI_ADMIN_PASSWORD inside the secretkv secret)
kubectl get secret secretkv -n erp-sandbox \
  -o jsonpath='{.data.DOLI_ADMIN_PASSWORD}' | base64 -d

# Database password (key `password` inside vso-db-credentials)
kubectl get secret vso-db-credentials -n erp-sandbox \
  -o jsonpath='{.data.password}' | base64 -d

Set in .env:

DOLIBARR_ADDRESS=https://erp-sandbox.arcodange.lab
DOLI_ADMIN_LOGIN=admin
DOLI_ADMIN_PASSWORD="<from secretkv above>"
DOLI_DB_PASSWORD="<from vso-db-credentials above>"
# Optional — otherwise a random password is generated and only the API key emitted:
# AI_AGENT_SANDBOX_PASSWORD="<choose one>"

After it runs

The generated API key lands in test/.ai_agent_sandbox.key. Next step (not automated by this POC): load it into the dolibarr skill's sandbox config / Vault at kvv2/erp-sandbox/ai_agent.

Important

The sandbox Dolibarr is not installed/provisioned yet (empty DB, fresh install wizard). Until the install wizard has been completed against the sandbox, provisionSandbox.ts will not have a UI to drive, and the selectors in moduleSetup.ts / userSetup.ts are best-effort (Dolibarr 22 conventions, not verified live). Confirm them on the first real run.

Write rights granted

The ai_agent_sandbox user is created non-admin and granted (the authoritative list is WRITE_IDS in provisionSandbox.ts):

Module rights ids
facture lire=11, creer=12
societe lire=121, creer=122, client voir=262
societe contact lire=281, creer=282
fournisseur lire=1181, facture lire=1231, facture creer=1232
produit lire=31, creer=32
banque lire=111
user lire=251 — requis par le probe armé GET /users/info (checkpoint status/relink-env)