Skip to content

A subgraph is just a Graph — SubgraphBinding → GraphImplementation (0.4.0) #9

Description

@borisdev

From ChatGPT's nested-graph handoff, reconciled against the code.

⚠️ That handoff describes a local draft that exists nowhere. GraphImplementation is 0 hits in this repo and its sandbox is gone, so its "62 focused tests passed" is unreproducible. This is a spec to implement, not a draft to review.

Why the rename is right, and the reason is stronger than preference

There is no subgraph type today — SubgraphBinding(graph: GraphSpec, strategy: StrategySpec) is already a graph paired with its strategy. And "subgraph" is wrong, not merely unfashionable: a subgraph of G is a subset of G's vertices and edges. A child here is a separate graph whose result substitutes for one node. Nested is accurate.

Decided

  • Clean break at 0.4.0. No compat alias. check() returns CoherenceFinding, so an agent can branch instead of regex (0.3.0) #5's own commit message is "one concept had two names" — an alias re-creates that. Both downstream consumers pin a sha and neither imports it.
  • GraphImplementation, not GraphStepImplementation: (Graph Step)(Implementation) misreads as the special composed-step node type we are deliberately not building, and every key in bindings is already a step, so the position says it.
  • No Implementation union alias. The handoff proposes one; nothing needs it.
  • Rename the payload subgraph flag and check_subgraphs too — a half-renamed vocabulary is worse than either end.

In scope, carried over from Copilot on #8

about on a propagated child finding is relative to the child, so after parent.coherence_check(s) it names nothing the caller holds and collides with the same name in a second child. Precision and usability are both real; reconciling them means about carrying a path across the boundary. The invariant is currently narrowed in prose at both sites and test_about_names_something_the_caller_can_look_up only covers a flat design.

Must not change

_port_type still refuses a multi-port boundary; parent and child still declare identical state_type/deps_type and share the same runtime objects; recursion is still rejected (now checks.check_recursion). All verified. A rename must not quietly relax any of them.

Activity

  1. borisdev commented on Oct 7, 2026

    @borisdev
    OwnerAuthor

    ✅ GraphImplementation CONFIRMED — Boris, 2026-10-07: "YES GraphImplementation. Decided. already."

    So the naming half is settled and needs no further argument. Recording it here because the body
    said "Decided" on my reasoning and now says it on yours.

    And the answer to "what is about?"

    It is one of three attributes on a CoherenceFinding, which is a str subclass — so findings
    print, compare, sort and join exactly as the plain strings they used to be:

    f = CoherenceFinding("orphan is unreachable", check="check_reachable", about="orphan")
    
    str(f)      # 'orphan is unreachable'   <- byte-identical to the old plain string
    f.check     # 'check_reachable'         <- which rule produced it
    f.about     # 'orphan'                  <- WHAT the finding is about
    f.blocking  # True                      <- a defect, vs a `NOT CHECKED` stated gap

    about exists so a caller can branch on structure instead of regexing the sentence — a UI
    highlighting the offending node does nodes[f.about] rather than parsing English.

    Its value space is exactly four kinds, and test_every_finding_names_something_the_caller_can_look_up
    enforces that every non-empty one resolves to something the caller holds:

    "normalize"       a node / join / decision name
    "propose->cite"   source->target, for a finding about an edge
    "trim_only"       a strategy name
    ""                the whole design — e.g. "no edge reaches END"
    

    ⭐ Which is exactly the bug this issue has to fix

    A child's findings propagate to the parent unchanged, so their about is relative to the
    CHILD:

    parent.coherence_check(strategy)
    #  -> [... about="orphan" ...]
    #
    #     `orphan` is a node in the CHILD; the caller holds the PARENT.
    #     nodes["orphan"] -> KeyError.
    #     And two children each having an `orphan` produce findings spelled IDENTICALLY.

    So about has to carry a path across a nesting boundary — "extract/orphan" — which changes
    what the field means. That is why it rides along with this rename instead of landing on its own,
    and why test_about_names_something_the_caller_can_look_up currently covers only a flat design.

    Fuller writeup: docs/open-questions.md §3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions