[P2] Canonicalize recursive-plan fingerprints before cycle detection #42

Closed
opened 2026-08-13 06:38:12 +00:00 by lost-rob0t · 0 comments
lost-rob0t commented 2026-08-13 06:38:12 +00:00 (Migrated from github.com)

Problem

While implementing #20, a depth-2 capability-narrowing test used a term-form plan containing an anonymous-tag SWI dict literal (_{secret:true}). The recursive-plan cycle check in rlm_completion fingerprints nested plans with term_hash/2 directly. Because the anonymous dict tag is representation-level non-ground state, the hash can remain non-ground and unify with an ancestor fingerprint, producing a false recursive_cycle(...) rejection before capability validation.

Reproduction

A nested rlm(...) plan whose grandchild includes an anonymous-tag dict literal can be rejected as a cycle even when the nested plans are structurally distinct. Replacing the dict with a named-tag dict makes the intended child-capability denial observable.

Desired fix

  • canonicalize representation-level anonymous dict tags before recursive fingerprinting, or hash a stable canonical serialization;
  • reject genuinely non-ground executable plan data explicitly rather than allowing fingerprint variables to participate in ancestor membership checks;
  • preserve real recursive-cycle/duplicate-call detection;
  • add regression coverage for anonymous SWI dict tags and genuine duplicate/cycle cases.

Constraints

Do not weaken duplicate/cycle protection. Model-origin JSON plans already normalize to closed structures; this primarily hardens the Prolog term API and prevents false positives.

Discovered by

Issue #20 deep-recursion experiment work.

## Problem While implementing #20, a depth-2 capability-narrowing test used a term-form plan containing an anonymous-tag SWI dict literal (`_{secret:true}`). The recursive-plan cycle check in `rlm_completion` fingerprints nested plans with `term_hash/2` directly. Because the anonymous dict tag is representation-level non-ground state, the hash can remain non-ground and unify with an ancestor fingerprint, producing a false `recursive_cycle(...)` rejection before capability validation. ## Reproduction A nested `rlm(...)` plan whose grandchild includes an anonymous-tag dict literal can be rejected as a cycle even when the nested plans are structurally distinct. Replacing the dict with a named-tag dict makes the intended child-capability denial observable. ## Desired fix - canonicalize representation-level anonymous dict tags before recursive fingerprinting, or hash a stable canonical serialization; - reject genuinely non-ground executable plan data explicitly rather than allowing fingerprint variables to participate in ancestor membership checks; - preserve real recursive-cycle/duplicate-call detection; - add regression coverage for anonymous SWI dict tags and genuine duplicate/cycle cases. ## Constraints Do not weaken duplicate/cycle protection. Model-origin JSON plans already normalize to closed structures; this primarily hardens the Prolog term API and prevents false positives. ## Discovered by Issue #20 deep-recursion experiment work.
Sign in to join this conversation.
No milestone
No project
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#42
No description provided.