Skip to content

Commit 8b2ac11

Browse files
sean-parentclaude
andcommitted
fix(planner): enforce source-only rule strictly for self-referencing eligibility
Remove `|| pre_claimed.contains(i)` from the self-referencing eligibility check: pre-claimed cells are never self-referencing in valid networks (the pre-claiming code only pre-claims pure outputs), so the condition was dead code that contradicted the source-only rule. Clear self-referencing outputs from `source_cells` when a method is selected so that a method-derived value cannot satisfy a later self-referencing eligibility check. Update the design spec to document both invariants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent dbc6d26 commit 8b2ac11

2 files changed

Lines changed: 10 additions & 3 deletions

File tree

docs/superpowers/specs/2026-06-25-self-reference-design.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ Add a `source_cells: HashSet<CellId>` set alongside the existing `determined` se
6969

7070
- When the outer loop promotes a cell as a source: add to both `determined` and `source_cells`.
7171
- When a method is selected and its outputs are marked determined: add to `determined` only.
72+
For self-referencing outputs (cells in both `inputs` and `outputs` of the selected method),
73+
also **remove** from `source_cells`: the method has overwritten the source value, so the cell
74+
must no longer qualify as a source for subsequent self-referencing eligibility checks.
7275

7376
`source_cells` records which cells were determined via `write()` rather than by a method. This
7477
distinguishes "may be read as self-referencing input" from "was derived; cannot be used as
@@ -81,7 +84,7 @@ For each method M, classify cells into three disjoint groups:
8184
| Group | Definition | Eligibility condition |
8285
|---|---|---|
8386
| **pure inputs** | `inputs(M) ∖ outputs(M)` | all in `determined` |
84-
| **self-referencing** | `inputs(M) ∩ outputs(M)` | all in `source_cells` or `pre_claimed` |
87+
| **self-referencing** | `inputs(M) ∩ outputs(M)` | all in `source_cells` |
8588
| **pure outputs** | `outputs(M) ∖ inputs(M)` | none in `determined` |
8689

8790
The existing rule is the special case where every cell is either a pure input or a pure output.

property-model/src/planner.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub(crate) fn plan(
9393

9494
// A method is eligible when:
9595
// pure inputs (inputs ∖ outputs): all in `determined`
96-
// self-ref (inputs ∩ outputs): all in `source_cells` or `pre_claimed`
96+
// self-ref (inputs ∩ outputs): all in `source_cells`
9797
// pure outputs (outputs ∖ inputs): none in `determined`
9898
let is_eligible = |m: &Method| {
9999
m.inputs
@@ -103,7 +103,7 @@ pub(crate) fn plan(
103103
&& m.inputs
104104
.iter()
105105
.filter(|i| m.outputs.contains(i))
106-
.all(|i| source_cells.contains(i) || pre_claimed.contains(i))
106+
.all(|i| source_cells.contains(i))
107107
&& m.outputs
108108
.iter()
109109
.filter(|o| !m.inputs.contains(o))
@@ -130,6 +130,10 @@ pub(crate) fn plan(
130130
// (as sources); only re-queue cells that are newly determined.
131131
let newly_determined = determined.insert(output);
132132
pre_claimed.remove(&output);
133+
// A method's output is no longer a source: remove it so that
134+
// subsequent self-referencing eligibility checks cannot treat
135+
// a method-derived value as a source value.
136+
source_cells.remove(&output);
133137
if newly_determined {
134138
queue.push_back(output);
135139
}

0 commit comments

Comments
 (0)