fix: preserve resolved bindings through recoverable preflight faults #371
Closed
nsaspy
wants to merge 2 commits from
fix/316-original-batch-effect-isolation into main
pull from: fix/316-original-batch-effect-isolation
merge into: nsaspy:main
nsaspy:main
nsaspy:feat/prolog-rlm-language
nsaspy:codex/claude-api-provider
nsaspy:codex/symbolic-experts
nsaspy:codex/openai-api-provider
nsaspy:feat/zai-provider-protocols
nsaspy:hardening/project-semantic-20260911
nsaspy:hardening/ws7-nix-ci-pinning
nsaspy:chore/track-prolog
nsaspy:rage/98-semantic-project-knowledge
nsaspy:fix/339-agent-zero-skill-graph
nsaspy:ci/tree-sitter-runner-labels
nsaspy:issue-293
nsaspy:research/text-streaming
nsaspy:docs/agent-editor-handoff
nsaspy:icon-add
nsaspy:hydra/fleet-jobs-20260904
nsaspy:rage/355-d6-11-plan-native-dispatch
nsaspy:cd/nix-installer-action-url
nsaspy:issue-355
nsaspy:rage/97-query-capture-apis
nsaspy:research/expert-direct-tool-projection
nsaspy:rage/96-versioned-syntax-facts
nsaspy:fix/direct-deadline-typing
nsaspy:dogfood/auto-dig-native-tools
nsaspy:feature/rage-feature-freeze
nsaspy:fix/323-native-batch-cardinality
nsaspy:fix/328-direct-user-namespace-text-string
nsaspy:fix/transient-provider-retry-2026-09-01
nsaspy:fix/openrouter-dotted-tool-result-name
nsaspy:rage/325-native-call-isolation
nsaspy:fix/313-per-call-preflight
nsaspy:fix/312-peek-schema-contract
nsaspy:docs/agents-worktree-rule
nsaspy:fix/child-native-capability-narrowing
nsaspy:test/paid-lane-glm-53-flash
nsaspy:docs/spec-seeded-symbolic-plans
nsaspy:rage/290-spec-plan-authority
nsaspy:fix/304-context-peek-selector-contract
nsaspy:fix/298-native-any-json-schema
nsaspy:rage/288-spec-plan-graph-executor
nsaspy:feat/spec-plan-flow-api
nsaspy:prolog-rlm-v1
nsaspy:gpt-5-6-sol-high/questions-for-rlm-prolog-and-lambda-rlm
nsaspy:rage/277-planner-protocol-context
nsaspy:research-approval/rlm-research-026-task-deadlines-20260827135838
nsaspy:research-approval/rlm-research-025-lem-ui-20260827135751
nsaspy:rage/183-live-operator-behavior
nsaspy:agent/127-agentprolog-config
nsaspy:recovery/132-agentprolog-monorepo
nsaspy:rage/223-durable-context-mount-recovery
nsaspy:rage/223-constraint-benchmark-recovery
nsaspy:research-approval/rlm-research-011-managed-context-tool-discovery-20260827054019
nsaspy:feat/research-approval-schema
nsaspy:agent/rrlm-control-plane-research
nsaspy:docs/219-adrrd-review
nsaspy:salvage/231-runtime-status
nsaspy:salvage/231-runtime-status-run
nsaspy:tmp
nsaspy:tmp2
nsaspy:tmp3
nsaspy:rage/245-planner-structural-retry
nsaspy:rage/168-skill-selection-eval
nsaspy:rage/250-skill-catalog-graph
nsaspy:rage/56-result-acceptance
nsaspy:rage/172-parent-resume-replan
nsaspy:rage/175-deadline-policy-recovery
nsaspy:rage/257-provider-tool-choice-normalization
nsaspy:agent/tool-result-projection-presets
nsaspy:fix/234-numeric-schema-bounds
nsaspy:feature/231-rlm-cli-reference-harness
nsaspy:feature/223-durable-context-mounts
nsaspy:feature/223-real-constraint-benchmark
nsaspy:feature/223-real-constraint-benchmark-clean
nsaspy:feature/223-real-constraint-benchmark-final
nsaspy:feature/223-real-constraint-benchmark-impl
nsaspy:feature/223-real-constraint-benchmark-now
nsaspy:feature/223-real-constraint-benchmark-tdd
nsaspy:feature/223-real-constraint-benchmark-work
nsaspy:rage/176-root-planner-tool-projection
nsaspy:rage/172-typed-delegation-policy
nsaspy:rage/175-subagent-deadline-policy
nsaspy:rage/206-prompt-command-runtime
nsaspy:rage/203-subagent-skill-role-provenance
nsaspy:rage/200-permanent-rlm-context
nsaspy:fix/190-cli-help-success
nsaspy:archive/pr-132-agentprolog-config-20260827
nsaspy:feature/117-prolog-skill-activation-linear
nsaspy:feature/117-prolog-skill-activation
nsaspy:fix/194-completion-budget-usage
nsaspy:fix/191-capability-filtered-tool-schemas
nsaspy:fix/185-reasoning-effort
nsaspy:ci/report-workflow-failures-20260825
nsaspy:cleanup/186-remove-legacy-harnesses
nsaspy:fix/185-reasoning-routing
nsaspy:agent/evolution-async-evaluator
nsaspy:rage/181-binding-replay-race
nsaspy:agent/124-deepseek-harness-prolog
nsaspy:rage/144-fallback-closure
nsaspy:rage/164-conversation-metadata
nsaspy:agent/subagent-supervised-call-conformance
nsaspy:fix/160-context-adapter-closed-data
nsaspy:codex/move-agent-zero-adaptor
nsaspy:codex/sol-high-integration
nsaspy:fix/151-plunit-gate
nsaspy:fix/165-evolution-closed-data
nsaspy:fix/162-async-control-exceptions
nsaspy:fix/158-prompt-compiler-closed-dicts
nsaspy:fix/151-plunit-main-ownership
nsaspy:fix/156-registry-destroy-hooks
nsaspy:fix/154-anonymous-dict-canonicalization
nsaspy:fix/flake-lock-reproducibility
nsaspy:agent/issue-142-evolution-kernel
nsaspy:agent/141-flake-runtime-package
nsaspy:agent/rlm-subagent-runtime
nsaspy:agent/144-subagent-fallback-a
nsaspy:agent/107-bound-adapter-metadata
nsaspy:validation/clean-pack-install
nsaspy:fix/45-authoritative-nested-model-events
nsaspy:fix/46-router-safe-live-streaming
nsaspy:agent/prompt-context-compiler
nsaspy:agent/42-canonical-recursive-fingerprints
nsaspy:agent/136-static-load-errors-fail-ci
nsaspy:agent/44-completion-error-usage
nsaspy:fix/67-loader-registry-cleanup
nsaspy:agent/opentui-solid-reference-client-current
nsaspy:agent/95-project-source-registry
nsaspy:feature/issue-117-prolog-skill-compiler
nsaspy:backlog/roadmap-86-merged
nsaspy:agent/opentui-solid-reference-client
nsaspy:79-tool-effect-boundary
nsaspy:agent/prolog-agent-ui-research
nsaspy:agent/conversation-cold-context
nsaspy:agent/94-tree-sitter-ffi
nsaspy:agent/conversation-warm-context
nsaspy:111-opentui-solid-reference-client
nsaspy:109-prolog-agent-ui-v1
nsaspy:agent/conversation-runtime
nsaspy:agent/spec-mode-language
nsaspy:agent/spec-verify-foundation
nsaspy:agent/prompt-compiler-research
nsaspy:reconcile-backlog-postmerge
nsaspy:reconcile-backlog-issues
nsaspy:agent/prolog-agent-roadmap
nsaspy:feature/issue-84-effect-store-migration
nsaspy:fix/issue-80-effect-substrate-adversarial-hardening
nsaspy:feature/issue-57-effect-identity
nsaspy:agent/mcp-declaration-security
nsaspy:agent/external-tool-category-boundary
nsaspy:feature/issue-53-authority-pending-async
nsaspy:feature/issue-54-agent-graph-canonical-async
nsaspy:feature/issue-54-tools-mcp-canonical-async
nsaspy:feature/issue-54-async-canonical-runtime
nsaspy:agent/dual-sync-async-runtime
nsaspy:agent/reconcile-todo-status
nsaspy:feature/issue-20-deep-recursion-experiments
nsaspy:feature/issue-19-cli-demo-trace
nsaspy:feature/issue-18-benchmark-conformance
nsaspy:feature/issue-17-adaptive-recursion
nsaspy:feature/issue-16-durable-artifacts
nsaspy:feature/issue-15-mcp-2026-dual-version
nsaspy:feature/issue-14-mcp-2025-11-25
nsaspy:hotfix/live-tool-native-openrouter
nsaspy:feature/issue-13-chain-runtime
nsaspy:fix/stable-live-repair-gate
nsaspy:fix/live-repair-strategy-parser
nsaspy:feature/issue-12-durable-graph
nsaspy:feature/issue-11-agent-supervision
nsaspy:feature/issue-10-structured-outcomes-repair
nsaspy:feature/issue-9-rlm-completion
nsaspy:feature/issue-8-capability-tools
nsaspy:feature/issue-7-typed-plan-runtime
nsaspy:feature/issue-6-context-store
nsaspy:fix/openrouter-reasoning-response
nsaspy:fix/live-openrouter-smoke-stability
nsaspy:feature/issue-5-openrouter-provider
nsaspy:feature/issue-4-swi-bootstrap
nsaspy:agent/agentic-harness-research
nsaspy:agent/prolog-rlm-foundation
No reviewers
Labels
Clear labels
bug
Something isn't working
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
wontfix
This will not be worked on
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
nsaspy/prolog-rlm!371
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/316-original-batch-effect-isolation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #313 / #316. Tightens original-request effect isolation for native call batches.
1. Exact bug, and why #316's tests missed it
#316 classified per-call preflight faults as
fault(Call, Cause)— the successfully resolved trusted binding was discarded, andeffectful_status/1only inspected resolved statuses. An effectful call whose arguments failed recoverable validation therefore stopped counting as a requested effectful operation:Result: the batch was no longer recognized as containing a requested effectful operation; the write handler still never executed (no mutation escape), but a valid sibling executed beside it — violating #316's documented invariant that any effectful call in a multi-call ORIGINAL request makes the whole batch fatal before any operation executes.
#316's tests missed it because every effectful-batch case used a valid effectful call (
_{value:7}): the effectful call resolved AND validated, so it stayed a resolved status and the check fired. No test exercised "resolves effectfully, then fails argument validation" — the one path where resolution metadata and fault status diverge.2. New classification/status contract
classify_calls/4still performs one side-effect-free classification pass (nothing re-run, nothing executed). Per call it now runs two explicit steps, each exactly once:classify_resolution/3— resolves against the trusted catalog and remembers the binding; a faulting resolution is remembered as a fault resolution with no binding (unknown/unavailable call →binding:none, recoverablecatalog/unavailable_tool_schemawhen policy allows).classify_validation/4— validates arguments for calls that resolved; a recoverable fault becomes:retaining the resolved binding; successful calls remain
resolved_call{call, binding}.validate_requested_effect_batch/2reads effect metadata throughclassified_effect/2for both status shapes, so effect identity survives recoverable faults:Recoverable never means executable: the faulted effectful call has no execution path, and the provider-visible repair observation carries no binding, capability, authority, or handler data.
3. Recoverability policy hardened
The whitelist is now keyed by fault phase + kind (
recoverable_fault/2):schema/malformed_argumentscatalog/unavailable_tool_schemaA same-kind fault emitted by an unrelated runtime phase (e.g.
normalize/malformed_arguments) escapes classification and is batch-fatal; future kinds/phases remain fatal by default. Pinned by whitebox closure and wiring tests.4. RED-before-fix evidence
test/rlm_direct_partial_batch_test.plgrew from 29 to 36 tests. Against the pre-fix branch (commit5ff492b, tests-only), 6 failed as intended:malformed_effectful_call_with_valid_read_sibling_is_batch_fatal— batch proceeded, valid sibling executedvalid_read_tool_sibling_does_not_execute_for_malformed_effectful_call— read probe counter reached 1malformed_effectful_call_with_malformed_read_sibling_is_batch_fatal— batch continued into repair loopmalformed_read_call_with_malformed_effectful_sibling_is_batch_fatal— same, opposite orderonly_phase_kind_whitelisted_faults_are_recoverable—recoverable_fault/2did not existsame_kind_from_unrelated_phase_escapes_classification— kind-only policy had no phase keyingThe singleton malformed-effectful case passed pre- and post-fix (pin: recoverable per-call semantics preserved).
5. Exact deterministic results after the fix
All commands run locally in the worktree on the exact pushed head (
c4ad710), all green:swipl -q -s test/check_runtime.pl— supported runtimeswipl -q -s test/load_all.pl— clean loadswipl -q -s test/run_tests.pl— 1128/1128 passed, 0 failed / timeout / blocked / fixme (main: 1121)rlm_direct_partial_batch: 36/36 pass, no choicepoint warningsswipl -q -s scripts/validate_research_approval.pl— PASS (19 files)swipl -q -s scripts/design_gate.pl— all checks passedswipl -q -s benchmark/run.pl -- deterministic— 16/16, quality 1.00swipl -q -s benchmark/run.pl -- deep-experiment— 15/15bin/prolog-rlm.pl -- demo --json+ graph trace smoke + trace-view — passgit diff --check— clean5. Effectful handler never executes in malformed effectful multi-call cases
Confirmed: in all four malformed-effectful multi-call tests the mutation counter stays 0,
tool_calls =:= 0, and there is no second provider turn (no repair path). The faulted effectful call has no execution path at all —resolved_status/1excludes it fromresolved_batch_budgetandexecute_callsdispatches it to the observation-only clause.6. Valid siblings do not execute when original-request effect isolation is fatal
Pinned by
valid_read_tool_sibling_does_not_execute_for_malformed_effectful_call: an externally observable read-probe execution counter is 0, pluscontext_calls =:= 0,tool_calls =:= 0, and no second provider request in the fatal cases. Batch-fatal classification completes before any execution.7. Docs updated
docs/direct-runtime.mdreplaces the overly broad fail-closed paragraph with the actual contract: complete batch normalized → one side-effect-free classification pass → batch-fatal invariants against the original request → whitelisted per-call repair observations → valid read siblings may execute → effectful multi-call requests remain entirely batch-fatal even with malformed effectful arguments. It states explicitly: recoverable != executable, visible != authorized, malformed effectful calls never execute, and per-call recovery cannot shrink an unsafe original request into a safe executable batch.8. Follow-up for native batch cardinality
#323 tracks an explicit
max_native_calls_per_batch-style admission limit. Today there is no cardinality cap on one provider response's tool-call list (only implicit providermax_tokens/max_output_bytes/ cross-turn budget bounds); faulted calls intentionally consume nomax_tool_calls/max_context_ops, and this slice does not silently repurpose those counters.Adversarial boundary review
Re-inspected normalization → classify → preserve metadata → original-request invariants → assistant validation → execute/observe → repair continuation: no duplicate execution, no preflight re-run, no double charging, no effect laundering, no internal-structure leaks in observations (pinned
NO_TERM/NO_EFFECTassertions), call-ID reuse still fatal across turns (faulted IDs marked seen), cancellation still propagates ascancelled(never a repair observation), no valid-sibling execution before batch-fatal checks, classification leaves no pending choicepoints.Non-goals
#296 model metadata, MCP imported-tool projection, #297 research tool packs, #288 plan graph execution, retrieval/embeddings, planner/scheduler redesign, effect-journal redesign, provider redesign, direct-mode skill architecture — untouched.
Pull request closed