feat(write-skill): client-dossier ops — thirdparty update (allowlisted) + idempotent contacts #66

Merged
arcodange merged 1 commits from arcodange/client-dossier-ops into main 2026-07-15 19:51:22 +02:00
Owner

Part of #65 (phase 1 — phase 2 products/contracts remains, so no Closes).

Deliverables

  • scripts/thirdparty-update.sh <socid> <json-file-or-inline> — PUT /thirdparties/{id} through the host-guarded dol-write.sh. Allowlist (identity/address/typology/notes/idprof1-6/tva_intra — the ledger grammar's "thirdparty complete"); any other field is refused pre-PUT, exit non-zero, offender named. Read-back contract: GET before → PUT → GET after, stdout = per-field diff of the requested fields that changed; a requested field that didn't take ⇒ non-zero (no silent partial apply). Also accepts the promote stdin form {"socid":N,"fields":{…}}.
  • scripts/contact-create.sh — POST /contacts, born idempotent (the #44 pattern): dedupes by case-insensitive email, then (lastname, firstname) → {"id": existing, "deduped": true} without POSTing. Tolerates exactly the Dolibarr empty-list HTTP 404; any other listing failure aborts rather than minting duplicates. Refuses unknown fields — including the WIP payloads' soc2 (→ poste).
  • Promote ops thirdparty_update + contact wired into promote-plan.sh (one-line summaries) and promote-apply.sh (both targets; per-op output now surfaces changed=N […] / deduped=true so idempotency is visible in the apply log). Sandbox target honors a pre-set DOL_WRITE for the offline stub hook; prod never inherits — always the gated dol-prod-write.sh.
  • Offline tests (tests/run-tests.sh, stub extended per-endpoint): allowlist rejection pre-PUT, contact dedupe (case-insensitive, no POST) + soc2 refusal, happy path of both ops through promote-apply --target sandbox applied twice (run 2 = changed=0 (no-op) + deduped=true). Zero credentials, zero network.
  • KM manifest pack replay-packs/2026-07-15-km-dossier/ — socid 1 dossier from the operator payloads with the truth fix (contract unsigned: effect-dated 2026-04-23, eIDAS signature in progress; USD invoicing; 4 % window = 6 months post-launch; « US EIN : à collecter (→ idprof1) ») + contact Hendrik Rootering (COO). README documents provenance + the human-gated prod step.
  • SKILL.md — workflows 5 (dossier update) + 6 (contact), promote op-kind list, three new gotchas; removed the stale "SKIP for credit notes" frontmatter clause (workflow 4 shipped earlier).

Offline test results

OK: payment-record normalization tests passed (long→short, wise untouched, >50 refused pre-POST)
OK: thirdparty-update allowlist — code_client refused pre-PUT, offender named
OK: contact-create dedupe — email match returns existing id, no POST; soc2 refused → poste
OK: promote-apply happy path — run 1 applies (changed=2, contact id 88), run 2 is a no-op (changed=0, deduped)
OK: all offline tests passed

Sandbox rehearsal (LIVE, 2026-07-15)

promote-plan renders both ops; then:

Run 1

>>> promote-apply target=sandbox (writes via dol-write.sh)
  [1/2] thirdparty_update @km      -> id=1  changed=1 [note_public]
  [2/2] contact           @hendrik -> id=3
OK — promote complete. ref -> id: {"km": 1, "hendrik": 3}

(The sandbox fiche already carried the WIP payload, so the one-field diff is the truth fix.)

Run 2 (idempotency)

>>> promote-apply target=sandbox (writes via dol-write.sh)
  [1/2] thirdparty_update @km      -> id=1  changed=0 (no-op)
  [2/2] contact           @hendrik -> id=3  deduped=true (already on target — no write)
OK — promote complete. ref -> id: {"km": 1, "hendrik": 3}

Read-back: all 13 dossier fields correct; note_public carries « SIGNATURE eIDAS EN COURS », « US EIN : à collecter », « FACTURÉ EN USD »; contact 3 = Rootering Hendrik / COO / [email protected] (poste set).

Prod promote is NOT part of this PR — orchestrator's human-gated replay (env-only key + ARCO_PROMOTE_CONFIRM), then dolibarr-thirdparty-completeness re-run on socid 1 should show only the EIN gap.

🤖 Generated with Claude Code

Part of #65 (phase 1 — phase 2 products/contracts remains, so no `Closes`). ## Deliverables - **`scripts/thirdparty-update.sh <socid> <json-file-or-inline>`** — PUT `/thirdparties/{id}` through the host-guarded `dol-write.sh`. **Allowlist** (identity/address/typology/notes/idprof1-6/tva_intra — the ledger grammar's "thirdparty complete"); any other field is refused pre-PUT, exit non-zero, offender named. Read-back contract: GET before → PUT → GET after, stdout = per-field diff of the requested fields that changed; a requested field that didn't take ⇒ non-zero (no silent partial apply). Also accepts the promote stdin form `{"socid":N,"fields":{…}}`. - **`scripts/contact-create.sh`** — POST `/contacts`, **born idempotent** (the #44 pattern): dedupes by case-insensitive email, then (lastname, firstname) → `{"id": existing, "deduped": true}` without POSTing. Tolerates exactly the Dolibarr empty-list HTTP 404; any other listing failure aborts rather than minting duplicates. Refuses unknown fields — including the WIP payloads' `soc2` (→ `poste`). - **Promote ops** `thirdparty_update` + `contact` wired into `promote-plan.sh` (one-line summaries) and `promote-apply.sh` (both targets; per-op output now surfaces `changed=N […]` / `deduped=true` so idempotency is visible in the apply log). Sandbox target honors a pre-set `DOL_WRITE` for the offline stub hook; **prod never inherits** — always the gated `dol-prod-write.sh`. - **Offline tests** (`tests/run-tests.sh`, stub extended per-endpoint): allowlist rejection pre-PUT, contact dedupe (case-insensitive, no POST) + `soc2` refusal, happy path of both ops through `promote-apply --target sandbox` applied **twice** (run 2 = `changed=0 (no-op)` + `deduped=true`). Zero credentials, zero network. - **KM manifest pack** `replay-packs/2026-07-15-km-dossier/` — socid 1 dossier from the operator payloads with the **truth fix** (contract *unsigned*: effect-dated 2026-04-23, eIDAS signature in progress; USD invoicing; 4 % window = 6 months post-launch; « US EIN : à collecter (→ idprof1) ») + contact Hendrik Rootering (COO). README documents provenance + the human-gated prod step. - **SKILL.md** — workflows 5 (dossier update) + 6 (contact), promote op-kind list, three new gotchas; removed the stale "SKIP for credit notes" frontmatter clause (workflow 4 shipped earlier). ## Offline test results ``` OK: payment-record normalization tests passed (long→short, wise untouched, >50 refused pre-POST) OK: thirdparty-update allowlist — code_client refused pre-PUT, offender named OK: contact-create dedupe — email match returns existing id, no POST; soc2 refused → poste OK: promote-apply happy path — run 1 applies (changed=2, contact id 88), run 2 is a no-op (changed=0, deduped) OK: all offline tests passed ``` ## Sandbox rehearsal (LIVE, 2026-07-15) `promote-plan` renders both ops; then: **Run 1** ``` >>> promote-apply target=sandbox (writes via dol-write.sh) [1/2] thirdparty_update @km -> id=1 changed=1 [note_public] [2/2] contact @hendrik -> id=3 OK — promote complete. ref -> id: {"km": 1, "hendrik": 3} ``` (The sandbox fiche already carried the WIP payload, so the one-field diff **is** the truth fix.) **Run 2 (idempotency)** ``` >>> promote-apply target=sandbox (writes via dol-write.sh) [1/2] thirdparty_update @km -> id=1 changed=0 (no-op) [2/2] contact @hendrik -> id=3 deduped=true (already on target — no write) OK — promote complete. ref -> id: {"km": 1, "hendrik": 3} ``` Read-back: all 13 dossier fields correct; `note_public` carries « SIGNATURE eIDAS EN COURS », « US EIN : à collecter », « FACTURÉ EN USD »; contact 3 = Rootering Hendrik / COO / [email protected] (`poste` set). **Prod promote is NOT part of this PR** — orchestrator's human-gated replay (env-only key + `ARCO_PROMOTE_CONFIRM`), then `dolibarr-thirdparty-completeness` re-run on socid 1 should show only the EIN gap. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
arcodange added 1 commit 2026-07-15 19:50:05 +02:00
Part of erp#65 (phase 1). Ledger grammar "thirdparty complete" gets its
op: allowlisted non-ledger fields, per-field diff read-back. Contacts
are born idempotent (dedupe by email then name). Promote ops wired both
targets, offline stub tests, SKILL.md workflows, KM dossier manifest
(unsigned-contract truth fix + EIN-to-collect note).

Co-Authored-By: Claude Fable 5 <[email protected]>
arcodange merged commit fe6909a2a6 into main 2026-07-15 19:51:22 +02:00
arcodange deleted branch arcodange/client-dossier-ops 2026-07-15 19:51:22 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: arcodange-org/erp#66