feat(plan-graph): D6-11 plan-native deterministic mutations (#355) #362

Open
nsaspy wants to merge 1 commit from rage/355-d6-11-plan-native-dispatch into rage/288-spec-plan-graph-executor
Owner

Runtime invariant implemented

Implements the operator decision from Forgejo #293 (comment 3762), recorded verbatim as D6-11 in docs/research/spec-plan-authority.md §6.3 (main, PR #356 amends D6-8):

The closed set sync_remote/1, run/1, index/1, delete/1 executes at the plan layer through the canonical boundary (schema → capability → authority → durable effect admission → dispatch → observe), exactly like a tool/3 step — never ambient shell/git access in plan code. They are excluded from expert mapping and from the future expert registry. Model-payload mutations (edit/2, create/2) remain write-expert-owned per §8.3.

Targets the rage/288 BASE branch because rlm_plan_graph.pl lives there (parent #288 unmerged); this amends the adoption input so the parent lands with the D6-11 semantics already reconciled.

Executor changes (rlm_plan_graph.pl)

  • plan_native_op/1 exported closed set (+ native_op_tool/1); the desugared form is unchanged: plan([tool(Op, literal(Args), Bind), final(var(Bind))]) executed through the rlm_plan validate/execute ABIs — D6-11 "exactly like a tool/3 step".
  • Deterministic host adapter closures move to a separate trusted native_handlers([native_handler(Op, Handler)]) option. The executor performs no effects itself, registers no tools of its own, and gains no ambient shell/git authority; adapters are host-supplied static closures whose obligation is the canonical effect boundary.
  • Fail-closed preflight: a native step without a native handler faults unknown_native_handler; an expert-registry entry for a plan-native op faults expert_mapping_excluded (D6-11 exclusion enforced, not silently ignored); a native entry naming a non-native op faults not_plan_native.
  • Per-op capability enforcement untouched: ungranted tool(Op) still fails closed at validation before any dispatch.

Tests (TDD, red first)

Six new tests in test/rlm_plan_graph_test.pl: plan-native single-op and full-set runs with an empty expert registry; missing-handler preflight fault; expert-mapping exclusion fault; non-native native-table rejection; capability fail-closed unchanged. Existing fixtures converted — index/sync_remote move to the native table; read/search/validate/delegate expert paths unchanged.

SWI 10.0.2 compat fix required by the gate

decode_args/4 re-tags unknown-op JSON args via dict_pairs(Args, _, Pairs): this SWI decodes JSON objects with a tag that does not unify with plan_graph_args, so the invalid_args sentinel path silently failed and the two vocabulary-rejection tests (rejects_unknown_op, vocabulary_validated_before_desugar) failed at the pristine BASE. Fault contract unchanged; both tests restored to green.

Docs

  • docs/plan-graph-runtime.md: D6-11 plan-native dispatch section + updated effect-boundary footnote and authority-chain diagram.
  • Design record rage/288-spec-plan-graph-executor.org: D6-11 amendment section.
  • Contract gate pins the plan_native_op/1 export.

Non-goals

  • No merging of #288 into main (parent slice owns adoption).
  • No canonical sync_remote/run/delete/index adapter implementations — hosts supply them; this slice adds no external-effect path.

Evidence

  • swipl -q -s test/run_tests.pl — 1027/1027 (86 suites, incl. 32 plan-graph tests)
  • scripts/plan_graph_contract_check.pl — ALL REQUIREMENTS DEFINED
  • check_runtime, load_all, benchmark deterministic 16/16, CLI demo, git diff --check, make research-approval — all green
  • prolog-verify check — verification passed at the exact head (observations in local .prolog/, untracked on this branch)

Refs #355 #288

## Runtime invariant implemented Implements the operator decision from Forgejo #293 (comment 3762), recorded verbatim as **D6-11** in `docs/research/spec-plan-authority.md` §6.3 (main, PR #356 amends D6-8): > The closed set `sync_remote/1`, `run/1`, `index/1`, `delete/1` executes at the plan layer through the canonical boundary (schema → capability → authority → durable effect admission → dispatch → observe), exactly like a `tool/3` step — never ambient shell/git access in plan code. They are excluded from expert mapping and from the future expert registry. Model-payload mutations (`edit/2`, `create/2`) remain write-expert-owned per §8.3. Targets the rage/288 BASE branch because `rlm_plan_graph.pl` lives there (parent #288 unmerged); this amends the adoption input so the parent lands with the D6-11 semantics already reconciled. ## Executor changes (`rlm_plan_graph.pl`) - `plan_native_op/1` exported closed set (+ `native_op_tool/1`); the desugared form is unchanged: `plan([tool(Op, literal(Args), Bind), final(var(Bind))])` executed through the `rlm_plan` validate/execute ABIs — D6-11 "exactly like a tool/3 step". - Deterministic host adapter closures move to a separate trusted `native_handlers([native_handler(Op, Handler)])` option. The executor performs no effects itself, registers no tools of its own, and gains no ambient shell/git authority; adapters are host-supplied static closures whose obligation is the canonical effect boundary. - Fail-closed preflight: a native step without a native handler faults `unknown_native_handler`; an expert-registry entry for a plan-native op faults `expert_mapping_excluded` (D6-11 exclusion enforced, not silently ignored); a native entry naming a non-native op faults `not_plan_native`. - Per-op capability enforcement untouched: ungranted `tool(Op)` still fails closed at validation before any dispatch. ## Tests (TDD, red first) Six new tests in `test/rlm_plan_graph_test.pl`: plan-native single-op and full-set runs with an **empty expert registry**; missing-handler preflight fault; expert-mapping exclusion fault; non-native native-table rejection; capability fail-closed unchanged. Existing fixtures converted — `index`/`sync_remote` move to the native table; `read`/`search`/`validate`/`delegate` expert paths unchanged. ## SWI 10.0.2 compat fix required by the gate `decode_args/4` re-tags unknown-op JSON args via `dict_pairs(Args, _, Pairs)`: this SWI decodes JSON objects with a tag that does not unify with `plan_graph_args`, so the `invalid_args` sentinel path silently failed and the two vocabulary-rejection tests (`rejects_unknown_op`, `vocabulary_validated_before_desugar`) failed at the pristine BASE. Fault contract unchanged; both tests restored to green. ## Docs - `docs/plan-graph-runtime.md`: D6-11 plan-native dispatch section + updated effect-boundary footnote and authority-chain diagram. - Design record `rage/288-spec-plan-graph-executor.org`: D6-11 amendment section. - Contract gate pins the `plan_native_op/1` export. ## Non-goals - No merging of #288 into main (parent slice owns adoption). - No canonical `sync_remote`/`run`/`delete`/`index` adapter implementations — hosts supply them; this slice adds no external-effect path. ## Evidence - `swipl -q -s test/run_tests.pl` — 1027/1027 (86 suites, incl. 32 plan-graph tests) - `scripts/plan_graph_contract_check.pl` — ALL REQUIREMENTS DEFINED - check_runtime, load_all, benchmark deterministic 16/16, CLI demo, `git diff --check`, `make research-approval` — all green - `prolog-verify check` — verification passed at the exact head (observations in local `.prolog/`, untracked on this branch) Refs #355 #288
feat(plan-graph): D6-11 plan-native deterministic mutations (#355)
Some checks failed
Nix flake / Flake package and clean runtime load (pull_request) Failing after 54s
CI / Deterministic unit and load checks (pull_request) Successful in 4m46s
Clean SWI pack install / Install and load copied pack (pull_request) Successful in 4m21s
Paid OpenRouter / Pinned paid OpenRouter integration (pull_request) Failing after 4m48s
Tree-sitter FFI / Direct SWI-Prolog Tree-sitter FFI (pull_request) Successful in 8m0s
CI / REAL OpenRouter integration (pull_request) Failing after 20m36s
653fed13ad
Implements the operator decision recorded as D6-11 in
docs/research/spec-plan-authority.md (main): the closed set
sync_remote/1, run/1, index/1, delete/1 executes at the plan layer
through the canonical boundary (schema -> capability -> authority ->
durable effect admission -> dispatch -> observe), exactly like a
tool/3 step — never ambient shell/git access in plan code. They are
excluded from expert mapping and from the expert registry;
edit/2 and create/2 remain write-expert-owned (§8.3).

Executor changes (prolog/rlm_plan_graph.pl):
- plan_native_op/1 exported closed set + native_op_tool/1 mapping.
- Host deterministic adapter closures move to a separate trusted
  native_handlers([native_handler(Op, Handler)]) option; the executor
  performs no effects itself and gains no ambient authority.
- Preflight fail-closed: native steps require a native handler
  (unknown_native_handler), expert entries for plan-native ops are
  rejected (expert_mapping_excluded), and native entries for
  non-native ops are rejected (not_plan_native).
- Desugared form unchanged:
  plan([tool(Op, literal(Args), Bind), final(var(Bind))]) executed
  through the rlm_plan validate/execute ABIs.
- Per-op capability enforcement is untouched: an ungranted
  tool(Op) still fails closed at validation before any dispatch.

Tests (TDD, red first): plan-native single-op and full-set runs with
no expert registry, missing-handler preflight fault, expert-mapping
exclusion fault, non-native native-table rejection, capability
fail-closed unchanged. Existing expert-routed fixtures converted:
index/sync_remote move to the native table; read/search/validate/
delegate/expert paths unchanged.

SWI 10.0.2 compat fix required by the gate: decode_args/4 re-tags
unknown-op JSON args via dict_pairs(Args, _, Pairs) — this SWI
decodes JSON objects with a tag that does not unify with
plan_graph_args, so the invalid_args sentinel silently failed and
the two vocabulary-rejection tests failed. Fault contract unchanged.

Docs: plan-graph-runtime.md D6-11 dispatch section; design record
amendment appended (rage/288-spec-plan-graph-executor.org); contract
gate pins the plan_native_op/1 export.

Gate: check_runtime, load_all, run_tests 1027/1027 (86 suites),
benchmark deterministic 16/16, CLI demo, git diff --check,
research-approval, contract gate ALL DEFINED.
Some checks failed
Nix flake / Flake package and clean runtime load (pull_request) Failing after 54s
CI / Deterministic unit and load checks (pull_request) Successful in 4m46s
Clean SWI pack install / Install and load copied pack (pull_request) Successful in 4m21s
Paid OpenRouter / Pinned paid OpenRouter integration (pull_request) Failing after 4m48s
Tree-sitter FFI / Direct SWI-Prolog Tree-sitter FFI (pull_request) Successful in 8m0s
CI / REAL OpenRouter integration (pull_request) Failing after 20m36s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin rage/355-d6-11-plan-native-dispatch:rage/355-d6-11-plan-native-dispatch
git switch rage/355-d6-11-plan-native-dispatch

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch rage/288-spec-plan-graph-executor
git merge --no-ff rage/355-d6-11-plan-native-dispatch
git switch rage/355-d6-11-plan-native-dispatch
git rebase rage/288-spec-plan-graph-executor
git switch rage/288-spec-plan-graph-executor
git merge --ff-only rage/355-d6-11-plan-native-dispatch
git switch rage/355-d6-11-plan-native-dispatch
git rebase rage/288-spec-plan-graph-executor
git switch rage/288-spec-plan-graph-executor
git merge --no-ff rage/355-d6-11-plan-native-dispatch
git switch rage/288-spec-plan-graph-executor
git merge --squash rage/355-d6-11-plan-native-dispatch
git switch rage/288-spec-plan-graph-executor
git merge --ff-only rage/355-d6-11-plan-native-dispatch
git switch rage/288-spec-plan-graph-executor
git merge rage/355-d6-11-plan-native-dispatch
git push origin rage/288-spec-plan-graph-executor
Sign in to join this conversation.
No description provided.