Project
Changelog
Notable changes to the kernel, the adapter toolkit, and the reference adapters. Newest first.
2026-06-22 · Earned read-after-write verification
A COMMIT’s success envelope (verified, changed, ssot.read_after_write) was emitted as a constant by the scaffold template, so every generated shim asserted a read-after-write it never performed. A field the backend silently dropped (for example an unresolved country_id) still reported verified: true.
The edge now earns the claim: after a write it re-reads the record from the system of record and compares every field it wrote. Any field that did not land flips the envelope to verified: false, claim: "partial", and names the offenders in unverified_fields. Comparison tolerates backend normalization (HTML wrapping, formatting) but treats empty-where-a-value-was-intended as a hard miss.
// COMMIT now re-reads the system of record and compares every written field.
"result": {
"claim": "partial", // "success" only when nothing is unverified
"changed": true,
"verified": false, // EARNED, not asserted
"ssot": { "system": "odoo_crm", "read_after_write": true,
"unverified_fields": ["country_id"] },
"unverified_fields": ["country_id"] // the field the backend silently dropped
}PROPOSE was hardened in the same pass: an argument a verb cannot write is now surfaced in an ignored list instead of being echoed back as accepted. The fix landed at its root — the kernel scaffold template — and was propagated to every already-generated edge: the PocketBase reference adapter, the Odoo CRM adapter, the adapter template, and the documentation example.
verified: true only when a read-back from the system of record confirms every written field. Asserting it is the one thing an “agent that can’t lie” must never do.2026-06-22 · Odoo CRM adapter — Phase 0 verbs
The Odoo CRM adapter gained the minimum loop for a phone-first agent, built on an explicit execution-strategy (op) the edge dispatches on instead of inferring CRUD from the verb name:
| Verb | Effect | Reversibility |
|---|---|---|
crm.get_contact_by_phone | Indexed lookup; unmatched is an empty read, not an error | — |
crm.update_contact | Whitelisted partial update (no blind writes) | COMPENSABLE (before-image) |
crm.create_contact | Upsert: dedup on email/phone so retries don’t fork the identity; ambiguous matches are refused | Conditional |
crm.log_note | Appends a chatter note | IRREVERSIBLE (honestly) |