diff --git a/src/nilscript/dataplane/intent.py b/src/nilscript/dataplane/intent.py index 41a02f7..3a8b956 100644 --- a/src/nilscript/dataplane/intent.py +++ b/src/nilscript/dataplane/intent.py @@ -36,12 +36,14 @@ class Binding: value: Any -# op → the universal generic-CRUD verb every adapter supports (no adapter-specific verb map, no -# keywords): a change intent executes through resource.* — the deterministic write spine. +# op → the universal generic verb every adapter supports (no adapter-specific verb map, no keywords): +# a change intent executes through resource.* — the deterministic write spine. `method` covers workflow +# actions that are NOT CRUD (post an invoice, validate a picking) via the adapter's generic op=method. OP_TO_RESOURCE: dict[str, str] = { "create": "resource.create", "update": "resource.update", "remove": "resource.delete", + "method": "resource.method", } @@ -49,8 +51,12 @@ class Binding: class Change: """The write shape of an intent: what to make true. Resolved → propose→commit→tier (governed).""" - op: str # create | update | remove + op: str # create | update | remove | method set: dict[str, Any] = field(default_factory=dict) + # for op="method": the workflow method to invoke (action_post / button_validate / …) and its kwargs. + # The adapter's allow-list (default-deny) still governs which (model, method) pairs are committable. + method: str | None = None + params: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) diff --git a/tests/test_intent_reads.py b/tests/test_intent_reads.py index 22362c8..14876d5 100644 --- a/tests/test_intent_reads.py +++ b/tests/test_intent_reads.py @@ -112,3 +112,15 @@ def test_seek_summary_groups_via_aggregate() -> None: def test_seek_summary_without_a_dimension_is_a_refusal() -> None: out = _resolver().resolve(Intent(about="res.partner", where=(), seek="summary")) assert out.kind == "refusal" and out.code == "MISSING_DIMENSION" + + +# ── op=method contract readiness (Odoo full-coverage gap 3) ─────────────────────────────────────── +def test_change_contract_supports_method_op() -> None: + """The intent write contract carries `method`/`params` and maps op=method → the adapter's generic + resource.method spine, so workflow actions (post/validate/confirm) are expressible through the one + nil_intent payload. The execution provider that consumes this is the intent-unification phase.""" + from nilscript.dataplane import OP_TO_RESOURCE, Change + + assert OP_TO_RESOURCE["method"] == "resource.method" + change = Change(op="method", method="button_validate", params={"foo": "bar"}) + assert change.op == "method" and change.method == "button_validate" and change.params == {"foo": "bar"}