Skip to content

fix(property-model): forced-output cells can never be planner sources#34

Merged
sean-parent merged 12 commits into
mainfrom
worktree-single-method-forced
Jul 9, 2026
Merged

fix(property-model): forced-output cells can never be planner sources#34
sean-parent merged 12 commits into
mainfrom
worktree-single-method-forced

Conversation

@sean-parent

@sean-parent sean-parent commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

  • The planner previously let cell strength override a relationship's structurally forced direction: a single-method relationship's output cell could wrongly be promoted to "source" status, silently orphaning its input or reporting a spurious Error::Conflict.
  • Adds a fixpoint pre-pass (forced_output_cells in property-model/src/planner.rs) that computes, per plan() call, the set of cells no active relationship can ever treat as a source — including cells only transitively forced through a cascade across relationships — and excludes them from the strength-ordered source-selection loop. The existing flood-fill / eligibility logic is unchanged.
  • Exposes the result via new Sheet::is_forced / Sheet::forced_cells public API, so UI code binding a form to a Sheet can disable fields that can never accept user input.
  • Design spec: docs/superpowers/specs/2026-07-09-planner-forced-outputs-design.md. Implementation plan: docs/superpowers/plans/2026-07-09-planner-forced-outputs.md.

Test plan

  • cargo test --workspace — all green
  • cargo test --doc --workspace — all green
  • cargo clippy --workspace --exclude begin -- -D warnings — clean
  • cargo clippy -p begin --no-default-features -- -D warnings — clean
  • cargo fmt --all — no diffs
  • Two isolated task-level reviews (spec compliance + code quality) plus one whole-branch review, all approved
  • Whole-branch reviewer independently proved the fixpoint cannot over-force (induction on method elimination) and hand-traced 4 topologies (single-method, cascade, 3-chain, diamond) with no under/over-forcing found

Note

Medium Risk
Core constraint propagation logic in property-model changes, but behavior is heavily tested; remaining risk is subtle planner edge cases on large graphs.

Overview
Fixes a property-model planner bug where high cell strength could treat structurally forced outputs as sources (e.g. single-method a → b), breaking propagation or yielding spurious conflicts.

Adds a forced_output_cells fixpoint before strength-ordered source selection: cells that every viable method of some active relationship must write (pure outputs, with cross-relationship method elimination) are skipped as sources. Plan carries forced_outputs; Sheet::propagate() caches them as last_forced and exposes is_forced / forced_cells for UI (e.g. disabling read-only fields). Integration and planner unit tests cover single-method, cascade, multi-method choice, and conditional branches.

Also documents --all-targets for workspace clippy in CLAUDE.md, passes --path to tokensave branch add in the worktree VS Code task, adds design/plan docs under docs/superpowers/, and applies clippy-driven test cleanups (boolean asserts, explicit transmute types, eprintln! in a debug proc-macro) without changing runtime behavior.

Reviewed by Cursor Bugbot for commit 8e76120. Bugbot is set up for automated code reviews on this repo. Configure here.

sean-parent and others added 7 commits July 9, 2026 12:46
Documents the fixpoint algorithm for identifying cells that can never
be sources under the currently active relationships, fixing the bug
where single-method relationships could have their forced direction
overridden by unrelated cell strength.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Task-by-task plan implementing the fixpoint design from
2026-07-09-planner-forced-outputs-design.md: the planner fixpoint
itself, then the public is_forced/forced_cells API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A relationship's method structure can guarantee a cell is always
produced by a method (e.g. the sole output of a single-method
relationship), independent of cell strength. The planner previously
let strength alone decide source candidacy, so such a cell could be
wrongly promoted to a source, orphaning its real input or reporting a
spurious Conflict. forced_output_cells computes this set as a
fixpoint over active relationships (eliminating a method whose output
collides with another relationship's forced cell can force further
cells) and excludes it from the strength-ordered source loop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pure_outputs scans inputs once per output cell, matching the O(K^2)
complexity already documented on plan() for the same kind of
linear-scan-heavy operation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
UI code binding a form to a Sheet needs to know which fields can never
accept user input (they are always overwritten by an active
relationship, regardless of priority) so it can disable them.
is_forced/forced_cells expose the forced-output set computed by the
planner, cached from the last full propagate() the same way
is_source/last_plan already are.

Removes the temporary #[allow(dead_code)] on Plan::forced_outputs now
that it is read outside #[cfg(test)] via Sheet::propagate().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ew term

pure_outputs is O(K^2) (inputs.contains scans linearly per output) but
had no Complexity bullet. plan()'s aggregate complexity omitted the
forced_output_cells term added by the forced-outputs fixpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes property-model planning so structurally forced output cells are never selected as planner “sources” due to strength ordering, and exposes that forced-ness through Sheet for UI consumers.

Changes:

  • Add a fixpoint pre-pass in planner::plan() to compute forced output cells and exclude them from source selection.
  • Cache forced cells from the last successful propagate() and expose them via Sheet::is_forced / Sheet::forced_cells.
  • Add regression + API tests, plus design/implementation docs; update a VS Code task to pass --path to tokensave.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
property-model/src/planner.rs Adds forced_output_cells fixpoint analysis, threads forced_outputs through Plan, and skips forced cells as source candidates.
property-model/src/sheet.rs Caches forced cells from the last propagate() and exposes is_forced / forced_cells APIs (with staleness note for propagate_without_replan).
property-model/tests/integration.rs Adds integration coverage for forced-direction correctness, cascades, conditional activation, and the new Sheet API.
docs/superpowers/specs/2026-07-09-planner-forced-outputs-design.md Documents the forced-output problem, fixpoint approach, and intended semantics.
docs/superpowers/plans/2026-07-09-planner-forced-outputs.md Provides a detailed implementation plan and verification checklist.
.vscode/tasks.json Updates worktree task to pass --path to tokensave branch add.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread property-model/src/sheet.rs Outdated
Comment thread property-model/src/planner.rs Outdated
sean-parent and others added 4 commits July 9, 2026 14:25
…ed_output_cells

is_forced's doc claimed to reflect "currently active relationships" but
actually serves the cached result from the last propagate(); reworded to
match forced_cells's phrasing. forced_output_cells allocated a HashSet on
every fixpoint iteration for every relationship purely for membership
checks; replaced with direct lookups against global_forced/own forced set.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CLAUDE.md's documented clippy commands never passed --all-targets, so
#[cfg(test)] unit tests, integration tests, and doctests were never
linted by the official gate. That's how an unused nested unsafe block
and several bool_assert_comparison violations slipped into raw_stack.rs
and property-model's PR. Added --all-targets to the clippy commands and
fixed everything it surfaces workspace-wide: redundant unsafe blocks,
assert_eq!(x, true/false) -> assert!, an unannotated transmute, a
redundant import, a needless borrow, and swapped 3.14 test literals for
42.14 to avoid clippy reading them as an approximation of PI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8e76120. Configure here.

Comment thread property-model/src/planner.rs
forced_output_cells identifies methods that must never run because they'd
double-write a cell a different relationship structurally owns, but plan()
only used the resulting forced-cell set to exclude sources -- the
flood-fill's method-eligibility check never consulted method-level
aliveness. A dead method's own pure output can still be undetermined at
the moment it's considered, so a higher-strength cell reachable only
through that dead method could get flood-filled first, permanently
claiming a cell the real relationship was meant to produce and turning an
otherwise solvable sheet into a spurious Error::Conflict.

forced_output_cells now returns the per-method alive map alongside the
forced-cell set, and plan()'s eligibility and pre-claim-feasibility checks
both consult it so dead methods are never selected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sean-parent sean-parent merged commit e214034 into main Jul 9, 2026
2 checks passed
@sean-parent sean-parent deleted the worktree-single-method-forced branch July 9, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants