bc5e0603d1a726c53b7742baa18327029e708861
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4d1e3ecb23 |
fix(email-ingest): extraction testable et pinnée au golden set + adc-008
L'extraction de champs vivait dans un heredoc à l'intérieur d'email-inspect.sh : impossible à exécuter isolément, donc jamais mesurée, donc fausse sans que personne puisse le voir. Sur la facture Darnis F1048 elle renvoyait le numéro de TVA d'Arcodange comme référence de facture, et aucune date. - extract_fields.py : l'extraction sort du shell et devient un module. - test_extract.py : régression contre les 16 factures hand-vérifiées de fleet/golden/invoice-extract/. Score par champ, et une valeur FAUSSE pèse plus qu'une valeur absente — un humain recopie ce qui s'affiche. Valeurs fausses : 4 → 0. Exactitude ref 62,5 → 75 %, date 62,5 → 75 %, HT 68,8 → 75 %, TTC 81,2 → 93,8 %. Cinq bugs réels, dont trois invisibles sans test : - « Nº » sur les factures françaises est U+00BA (ordinal masculin), pas le signe degré. La classe [°o] le rate, le motif principal échoue, et le repli attrape le premier jeton ref-shaped du document — très souvent un numéro de TVA. - Le filtre anti-TVA rejetait « FR73261832 », qui est la vraie référence OVH : un numéro FR fait exactement 11 caractères après le préfixe. - « Montant total (HT) » était lu comme un TTC. - Une référence coupée par la colonne (« 06-01-26- » / « payment-366753 ») était renvoyée amputée : le recollage doit précéder le scan, sinon la queue seule est trouvée en premier. - Un `\b` après `€` ne peut jamais matcher en fin de ligne (€ n'est pas un caractère de mot) — la TVA n'était jamais extraite. adc-008 : une facture fournisseur s'enregistre à SA date, même future, tant que l'exercice (année civile) ne bascule pas. Le document fait foi ; altérer sa date ferait diverger l'écriture de sa pièce justificative (CGI art. 289 VII). Registre validé : 8 règles, 8 ADC, 0 erreur. scopes.ts : 1232 (factures fournisseur) ajouté à prod-write — oubli initial, révélé par un 403 en production sur F1048. Le pipeline s'est arrêté sans écrire. Appliqué en production via le pipeline gated : FAF2026014 (Darnis F1048), 218,50 HT + 43,70 TVA = 262,20 TTC, validée, non réglée. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh |
||
|
|
2e699e1fc6 |
feat(sandbox): scoped agents provisioned by the checkpoint cycle
Completes the role model on the sandbox side, and closes the third failure of the 2026-07/08 sessions: a refresh wiped hand-granted rights and nothing recorded them, so the sandbox silently lost capabilities nobody had written down. - Provisioned `ai_agent_sandbox_read` (36 rights) and `ai_agent_sandbox_sandbox_write` (44 rights) from test/scopes.ts. Verified functionally on the live sandbox: the reader reads and gets 403 on invoice creation; the writer creates a draft and gets 403 on DELETE. - checkpoint-provision.sh now re-creates both scoped agents after every refresh, so their rights come from code rather than from someone's memory. Failure to provision a scope warns instead of aborting the whole checkpoint. - checkpoint-relink-env.sh points the write skill at the scoped writer key, falling back to the legacy single-user key so an older checkout still works. The write skill now operates as ai_agent_sandbox_sandbox_write (id 6). Smoke-tested end to end after the credential swap: the promote pipeline rehearses on the sandbox under the scoped writer, and `apply` still refuses without a recorded human gate. The redundant `ai_agent_prod_prod_write` login is documented as deliberate: renaming a provisioned production credential means creating a second privileged user and repointing the promote flow — churn for cosmetics. Left behind in the sandbox: draft invoice id=19, a scope probe. It cannot be deleted (no scope grants DELETE, which is the point) and the next refresh reclaims it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh |
||
|
|
1cba032512 |
fix(security): production read agent is now actually read-only
Migration applied to production, in the only order that does not break the promote flow: 1. Created `ai_agent_prod_prod_write` (id=5) with the narrow `prod-write` scope — invoices, payments, and document submission (needed by builddoc to regenerate a modified invoice's PDF). Verified functionally: reads pass, DELETE on an invoice returns 403. 2. Repointed the promote pipeline at that user's key. It no longer borrows the read skills' credential; if the key is absent it dies with the provisioning command rather than silently falling back. 3. Revoked 12 write/delete rights from `ai_agent` (id=3), the credential every read skill holds: create/modify on customer AND supplier invoices, thirdparties, contacts, thirdparty payment details, proposals, exports, accounting links — and delete on proposals, events, and GED documents. Verified after: invoices, thirdparties, contacts, products, proposals, supplier invoices and bank accounts all still read; creating an invoice returns `403 Forbidden: Insuffisant rights`. The documented posture and the real one finally agree. scopes.ts corrected against the live instance: 262 is NOT "créer/modifier les produits" as the first catalogue guessed but the `voir_tous` ACL extension — a READ right the skills depend on (without it, list endpoints return empty arrays instead of 403). Revoking it would have silently blinded every read skill. This is why the audit reads labels off /user/perms.php rather than trusting ids in code. The READ_ONLY baseline is now the audited read surface (35 rights), not a guess. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh |
||
|
|
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 |