From 0240c519b1173da33fea6252ca2901e3716d55a6 Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Sat, 11 Jul 2026 17:38:30 +0200 Subject: [PATCH] fix(provision): grant user lire (251) to ai_agent_sandbox so the armed probe passes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The checkpoint status/relink-env armed probe calls GET /users/info, which requires Dolibarr right 251 (user->user->lire). WRITE_IDS didn't include it, so a freshly provisioned agent answered 403 on the probe — reported NOT armed — while its key actually authenticates (GET /thirdparties -> 200). Right 251 was granted live in SQL on the sandbox (fk_user=4) today; this persists it in WRITE_IDS so every future provision grants it. Also teach both probes to tell the failure modes apart instead of one opaque message: 401 = key rejected (stale/instance-encrypted -> re-provision), 403 = key OK but right 251 missing (-> grant it / re-provision), 200 = armed. Docs updated accordingly (checkpoint SKILL.md probe outcomes, sandbox-write SKILL.md gotcha, test/README.md rights table synced to WRITE_IDS incl. 262/111). Co-Authored-By: Claude Fable 5 --- .claude/skills/dolibarr-sandbox-checkpoint/SKILL.md | 10 ++++++++-- .../scripts/checkpoint-relink-env.sh | 12 +++++++++--- .../scripts/checkpoint-status.sh | 11 ++++++++--- .claude/skills/dolibarr-sandbox-write/SKILL.md | 3 +++ test/README.md | 7 +++++-- test/provisionSandbox.ts | 4 ++++ 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.claude/skills/dolibarr-sandbox-checkpoint/SKILL.md b/.claude/skills/dolibarr-sandbox-checkpoint/SKILL.md index efb1a7a..e5a59d1 100644 --- a/.claude/skills/dolibarr-sandbox-checkpoint/SKILL.md +++ b/.claude/skills/dolibarr-sandbox-checkpoint/SKILL.md @@ -29,7 +29,12 @@ arcodange sandbox checkpoint relink-env ``` 1. **`status`** — HTTP liveness + whether the write agent (`ai_agent_sandbox`) is - *armed* (its key authenticates `GET /users/info`). Read-only, no cluster access. + *armed*. The armed probe calls `GET /users/info` with the linked key; three + outcomes: **200** → `ARMED`; **401** → the key itself is rejected (stale, e.g. + an iso-prod refresh re-encrypted it) → re-run `provision`; **403** → the key + authenticates but lacks right **251** (`user → lire`, which `/users/info` + requires) → re-provision (a fresh run grants it via `WRITE_IDS` in + `test/provisionSandbox.ts`) or grant 251 by hand. Read-only, no cluster access. 2. **`refresh --yes`** — re-seed the sandbox iso-prod from prod, wrapping `ops/sandbox/sandbox-lifecycle.sh` (read-only `pg_dump` of prod → `DROP OWNED` → `pg_restore`, then documents/logo sync). **Destructive**: requires `--yes`, and @@ -40,7 +45,8 @@ arcodange sandbox checkpoint relink-env (`test/provisionSandbox.ts`). It opens a browser; **you complete the admin login** — with the **PROD** admin credentials, since the sandbox is iso-prod (they come from `test/.env.sandbox`). The POC re-grants the agent's rights - (including `banque lire`) and writes the key to `test/.ai_agent_sandbox.key`, + (including `banque lire` and `user lire` = right 251, which the armed probe + needs) and writes the key to `test/.ai_agent_sandbox.key`, then this command auto-runs `relink-env`. Needs `deno`. 4. **`relink-env`** — (re)write `dolibarr-sandbox-write/.env` from `test/.ai_agent_sandbox.key` (mode 600) and verify it authenticates. Run it diff --git a/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-relink-env.sh b/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-relink-env.sh index 73bf73e..3fa4607 100755 --- a/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-relink-env.sh +++ b/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-relink-env.sh @@ -24,7 +24,13 @@ printf 'verify: ' "${DOLW}" GET /users/info | python3 -c "import json,sys d = json.load(sys.stdin) if isinstance(d, dict) and d.get('login'): - print('OK — armed as %s (id %s)' % (d['login'], d.get('id'))) + print('OK — armed as %s (id %s)' % (d['login'], d.get('id'))); sys.exit(0) +err = d.get('error', {}) if isinstance(d, dict) else {} +code = str(err.get('code', '')) +if code == '401': + print('FAILED — 401 key rejected (stale/instance-encrypted) → re-run checkpoint provision') +elif code == '403': + print('FAILED — 403 key authenticates but right 251 (user lire) is missing → re-provision or grant 251 (WRITE_IDS, test/provisionSandbox.ts)') else: - msg = d.get('error', {}).get('message', '?') if isinstance(d, dict) else str(d) - print('FAILED — %s' % msg); sys.exit(1)" + print('FAILED — %s' % (err.get('message', '?') if isinstance(d, dict) else str(d))) +sys.exit(1)" diff --git a/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-status.sh b/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-status.sh index 1e9e7c0..1bdeb49 100755 --- a/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-status.sh +++ b/.claude/skills/dolibarr-sandbox-checkpoint/scripts/checkpoint-status.sh @@ -22,10 +22,15 @@ try: except Exception: print('NOT armed — no/invalid response'); sys.exit(0) if isinstance(d, dict) and d.get('login'): - print('ARMED — login=%s id=%s' % (d['login'], d.get('id'))) + print('ARMED — login=%s id=%s' % (d['login'], d.get('id'))); sys.exit(0) +err = d.get('error', {}) if isinstance(d, dict) else {} +code = str(err.get('code', '')) +if code == '401': + print('NOT armed — 401 key rejected (stale after a refresh?) → run checkpoint provision') +elif code == '403': + print('NOT armed — 403 key OK but right 251 (user lire) missing → re-provision or grant 251 (WRITE_IDS, test/provisionSandbox.ts)') else: - msg = d.get('error', {}).get('message', '?') if isinstance(d, dict) else 'unexpected' - print('NOT armed — %s' % msg[:80])" + print('NOT armed — %s' % str(err.get('message', 'unexpected'))[:80])" else echo " write .env : ABSENT" echo " write agent : not linked — run 'arcodange sandbox checkpoint relink-env' after provisioning" diff --git a/.claude/skills/dolibarr-sandbox-write/SKILL.md b/.claude/skills/dolibarr-sandbox-write/SKILL.md index e8eb64a..26c31a9 100644 --- a/.claude/skills/dolibarr-sandbox-write/SKILL.md +++ b/.claude/skills/dolibarr-sandbox-write/SKILL.md @@ -183,6 +183,9 @@ sandbox KissMetrics on `--target sandbox` and the prod one on `--target prod`. - **`banque lire`** (rights id 111) is granted → `scripts/bank-accounts.sh` lists accounts (id/label/bank) so a payment can pick its `account_id`. It's in the provisioner's `WRITE_IDS`, so a fresh `provisionSandbox.ts` run includes it. +- **`user lire`** (rights id 251) is granted → the checkpoint *armed* probe + (`GET /users/info` in `status`/`relink-env`) answers 200 instead of 403. Also in + the provisioner's `WRITE_IDS`. - **Avoirs (credit notes)** → `creditnote-create.sh` (customer invoice `type=2` referencing `source_invoice`; amounts negative, ref `AVC…`). Supplier avoirs are a follow-up. diff --git a/test/README.md b/test/README.md index b35d417..cb3e00c 100644 --- a/test/README.md +++ b/test/README.md @@ -95,12 +95,15 @@ Vault at `kvv2/erp-sandbox/ai_agent`. ### Write rights granted -The `ai_agent_sandbox` user is created non-admin and granted read + create on: +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 | +| 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`) | diff --git a/test/provisionSandbox.ts b/test/provisionSandbox.ts index bd3819a..2b4375e 100644 --- a/test/provisionSandbox.ts +++ b/test/provisionSandbox.ts @@ -39,6 +39,7 @@ import userSetup from "./scripts/admin/userSetup.ts"; fournisseur: lire=1181, facture lire=1231, facture creer=1232 produit: lire=31, creer=32 banque: lire=111 + user: lire=251 */ const WRITE_IDS = [ 11, // facture lire @@ -56,6 +57,9 @@ const WRITE_IDS = [ 31, // produit lire 32, // produit creer 111, // banque lire — list bank accounts (GET /bankaccounts) to pick account_id + 251, // user lire — requis par le probe armé GET /users/info (checkpoint status/relink) + // Sans lui l'endpoint répond 403 alors que la clé authentifie, et le + // checkpoint affiche NOT armed sur un agent pourtant fonctionnel. ]; const KEY_FILE = ".ai_agent_sandbox.key";