You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Coherence: the rename, the generated reference tables, and the glossary - #7
Stacked on #6 (which is stacked on #5). Merge in order.
The README has to stay short, and this change keeps introducing words that carry real meaning a reader can't guess — coherence, well-formedness, stated gap, noise floor, declaration layer. Explaining each inline wears the reader down; dropping them loses the knowledge.
So: define them once, link them on first use.
16 terms, three lines each
The third line is the one worth reading — what the word does not mean here:
term
the correction it carries
coherence
the parts connect. Not that they support a conclusion — that's only measurable, which is what a battle is for
consistency
weaker than coherence, and the gap is the whole problem. An inconsistency usually crashes; an incoherence runs
design rule check
chip design's name for this activity. ERC's floating net, dangling output and two drivers on one net map onto three of our rules almost exactly. LVS has no equivalent here, deliberately
soundness
van der Aalst's workflow nets. Ours are structural approximations, not a proof — a smaller claim, stated
deep embedding
why the design is data, and why every capability follows from that one choice
well-formedness rule
UML's own section heading. Not an "invariant" in the strict sense
⛔ The test is the point
A glossary with dead links is worse than none: a reader clicks a term they don't know, lands nowhere, and learns not to click the next one. The mechanism dies quietly — and nothing else here would notice, because a markdown link is not code.
Five tests. All six ways this rots were introduced one at a time, each went red:
mutation
caught
a term is renamed, README links rot
✅
an internal cross-reference breaks
✅
the README quietly stops linking a load-bearing term
✅
the glossary is truncated to a stub
✅
two headings collapse to one anchor
✅
the file is deleted outright
✅
The anchor test replicates GitHub's slug algorithm rather than assuming headings and links happen to agree.
⚠️test_the_readme_actually_links_the_terms_it_leans_on is the unusual one, and it guards the failure this idea invites: the glossary exists, nobody links to it, and the knowledge is parked where no reader will find it. Five terms are required by name — dropping one is allowed, but only on purpose, by editing that list.
…egex (0.3.0)
Closes#4.
`check()` returned `list[str]`. The findings are sentences, so the structure a
caller needs was encoded in the prose:
hard = [f for f in findings if not f.startswith("NOT CHECKED")]
That prefix match was load-bearing control flow in three call sites here and in
both downstream repos — two kinds of finding wearing one type, told apart by
text. `.claude/rules/checks.md`: NOT CHECKED and 0 FOUND must never render the
same.
`CoherenceFinding` is a `str` SUBCLASS, which is the load-bearing decision:
check the producing function's name, e.g. "check_variables"
about a node/join/decision name; "source->target" for an edge; the
strategy name; "" for a finding about the whole design
blocking derived — False only for a `NOT CHECKED — …` stated gap
A bool, not a severity enum: two states, no third observed. A frozen dataclass
is tidier and costs a second breaking migration one release after StepSpec;
that is why it loses.
ADDITIVE, and measured rather than asserted:
· all 221 existing tests pass untouched
· every finding the test designs produce — 64 of them — is byte-for-byte
identical to origin/main. `__repr__` is deliberately NOT overridden,
because the examples print whole lists of findings
· the five downstream lines #4's comment names as the acceptance bar were
RUN, unmodified, against this build: nobsmed-v2's
scripts/prove_workflow_workbench.py (ALL CHECKS PASSED) and
libs/case_build/tests/test_battle.py (15 passed)
27 findings.append sites in checks.py tagged; message text unchanged at every
one. `GraphSpec._check`'s recursion finding is the 28th and the only producer
outside checks.py — it carries that qualified name because cycle detection
needs the `ancestry` only that method holds.
New tests, because the 221 are an oracle for the MESSAGES and say nothing about
`check` and `about`:
· a structural oracle — every `about` is "" or a name resolvable against the
design the caller handed in. Not a hand-written table of 27 expected
strings, which is as likely to be wrong as the code it checks
· `about` follows the SUBJECT, not the loop variable: an undeclared node
cannot be looked up, so that finding is about the EDGE; a branch missing
`when=` is about the branch, not the decision
· the compatibility oracle — the five string operations that must not move
· a source-scan ratchet, so an append site on a branch nothing provokes
cannot ship returning a plain `str`
All nine were mutation-tested: eight deliberate defects introduced one at a
time, each caught by exactly the assertion meant to catch it.
⚠️ One correction to #4's premise, found by running it. "The 221 tests assert
finding message substrings, so a wrong message fails loudly" is true of some
messages and not others — rewording the tail of check_reachable's "unreachable
from START" finding keeps the whole suite green, because the test asserts only
`"orphan" in f and "unreachable" in f`. The byte-for-byte diff above is the
check that actually covers the claim; the suite alone does not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
did not say what it checks, and the type it returns says — a
word that appeared NOWHERE else in the API. One concept wearing two names is the
thing the naming rule exists to prevent, and the orphan was the half I added.
spec.check(strategy) 0.2.0
spec.coherence_check(strategy) 0.3.0
No alias: a missed call site is an AttributeError at the call. Same choice 0.2.0
made when NodeSpec(...) became a TypeError.
was considered and rejected — IS the design, so
restates its own receiver.
UNCHANGED, deliberately: the eleven functions and .
Both name an individual check, which is what they still are.
103 replacements across 25 files, applied by counted exact-match rather than a blind
sed — every in the repo was first enumerated and confirmed to be this method.
⚠️ was deliberately REVERTED after the sweep touched it. It
documents what the API was AT 0.2.0, where the method genuinely was .
Renaming it there would make a historical upgrade guide assert something false — a
doc that lies is worse than no doc.
Done inside this PR rather than after it, so 0.3.0 carries ONE migration instead of
0.3.0 and 0.4.0 carrying one each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to 0863362, whose commit message is truncated: it was passed through a
double-quoted shell string and every `backticked` term was eaten as command
substitution. The rationale it should have carried is below. No code in that commit
was affected — only its message.
WHY THE RENAME. `check()` did not say what it checks, and the type it returns says
`Coherence` — a word that appeared NOWHERE else in the API. One concept wearing two
names is what the naming rule exists to prevent, and the orphan half was the one I
added in this PR. `design_check()` was considered and rejected: `spec` IS the design,
so `spec.design_check()` restates its own receiver. There is no alias — a missed call
site is an `AttributeError` at the call, the same choice 0.2.0 made when
`NodeSpec(...)` became a `TypeError`.
Unchanged, deliberately: the eleven `check_*` functions and `CoherenceFinding.check`.
Both name an individual check, which is what they still are.
THE THREE LOOSE ENDS, and none was found by reading the diff:
1. The README's four `spec.coherence_check(...)` samples were MISSED by the sweep.
Its regex required no `.` before `check()`, which excluded exactly the form the
README uses. Caught by `test_the_docs_use_the_current_api` — the lint that exists
because the README's own opening example stopped running once before and nothing
said so. It went red on the first full run after the rename.
2. `docs/migration-0.3.md` cites the 0.2.0 precedent by name, so it contains
`NodeSpec(` and that same lint flags it. Added to `_NAMES_THE_OLD_API` rather than
reworded around: a migration doc necessarily names the API it migrates FROM, and
`test_the_retirement_exemptions_all_exist` keeps the exemption from going stale.
3. `docs/migration-0.2.md` was REVERTED after the sweep touched it (in 0863362). It
documents what the API was AT 0.2.0, where the method genuinely was `check()`.
Renaming it there would make a historical upgrade guide assert something false.
`CHANGELOG.md` keeps three bare `check()` mentions on purpose — they are the breaking
change narrating itself, and one sample is explicitly labelled `# 0.2.0`.
235 tests pass. `parity --check` green, `examples.greeting` and
`examples.ladder.stage9_decision` both run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… docstrings
The README said what the library is FOR and never what it checks. A reader could not
tell whether `coherence_check()` was three trivial rules or eleven real ones, and the
data language — the whole reason the design is inspectable — was never written down.
TWO GENERATED TABLES, in the README where they were asked for:
the data language every word you declare a design with, grouped
well-formedness every rule coherence_check() enforces, split by whether it
needs a strategy
python3 -m workflow_workbench.reference --write
python3 -m workflow_workbench.reference --check
NO DESCRIPTION IS AUTHORED. Every cell is the first line of the thing's own docstring,
or — for the two unions, which have none of their own — the names of their members.
A table cannot claim what the code does not say.
What IS authored is the GROUPING: which word sits under "boxes" and which under
"wires". That is curation, it is source, and it is a literal in `reference.py`. The
source/derived split is the point, so there is a test asserting GROUPS carries names
only.
THE README OPENING NOW NAMES THE FAILURE, NOT THE AUDIENCE. Boris's framing: an agent
left to run free produces work that RUNS AND IS INCOHERENT — each piece locally fine,
the whole not adding up. That is a reason to care; "for developing workflows with an
AI coding agent" was only a statement of who it is for. Four problems, one declaration
answering all four: incoherence, strategies that can only be compared and not asserted,
reuse, and visibility.
⚠️ The visualization claim is narrowed to what is true. pydantic-graph DOES emit
mermaid — `build_mermaid_graph` in `graph_builder.py` — so "they have none" would have
been false. Measured: it is not exported from their `__init__`, and it takes a BUILT
graph's internals, so every implementation must exist first. Two honest differences
then, not a feature list: ours reads the declaration, and ours can draw two strategies
at once.
Rebased onto the coherence-finding branch, because these tables name the method and
that branch renames it. #6 is stacked on #5.
ALSO FIXED AT SOURCE, not worked around:
· `_Start`'s first docstring line ended mid-sentence on "so `mypy` can narrow a", and
`_End`'s was "See `_Start`." Both are now standalone sentences, because the table
lifts that line verbatim. `test_every_first_docstring_line_stands_alone` makes a
wrapping summary a test failure rather than a fragment in the README.
· the union rows needed their pipes ESCAPED — a bare `|` is a column separator, so
`StepSpec | JoinSpec | DecisionSpec` rendered as three empty cells. Invisible in
the source string; the test asserts the rendered row.
· the generated prose said "`NodeSpec(...)` is a `TypeError`", which
`test_the_docs_use_the_current_api` greps for and flags. Reworded at the source
rather than exempting the README — that lint exists because the README's own
example once stopped running and nothing said so, and the README is the last file
that should be exempt from it. It has now caught me twice in this change, both
times correctly.
`docs/well-formedness.md` is deleted: the tables live in the README now, and two homes
for one generated table is the duplication this repo keeps a drift test to avoid.
Six ways this can rot were introduced one at a time, and each went red: hand-editing a
README row, a docstring changing without regeneration, a new export left out of the
table, a stale exclusion naming something no longer exported, GROUPS growing a
hand-written description, and the unions losing their pipe escaping.
245 tests pass. `parity --check` and `reference --check` both green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README has to stay short, and this change keeps adding words that carry real
meaning a reader cannot guess — coherence, well-formedness, stated gap, noise floor,
declaration layer. Explaining each inline wears the reader down; dropping them loses
the knowledge. So: define them once, link them on first use.
16 terms. Each is three lines at most, and the third is the one worth reading — what
the word does NOT mean here:
coherence the parts CONNECT. Not that they support a conclusion — that is
only measurable, which is what a battle is for
consistency weaker than coherence, and the gap is the whole problem. An
inconsistency usually crashes; an incoherence RUNS
design rule chk chip design's name for this activity. ERC's floating net,
dangling output and two-drivers-on-one-net map onto three of our
rules almost exactly. LVS has no equivalent here, deliberately
soundness van der Aalst's workflow nets. Our checks are STRUCTURAL
approximations of it, not a proof — a smaller claim, stated
deep embedding why the design is data, and why every capability follows from it
well-formedness UML's own section heading. NOT an "invariant" in the strict sense
⛔ THE TEST IS THE POINT. A glossary with dead links is worse than none: a reader
clicks a term they do not know, lands nowhere, and learns not to click the next one.
The mechanism dies quietly, and nothing else in this repo would notice — a markdown
link is not code.
Five tests, and all six ways this rots were introduced one at a time and each went
red: renaming a term so README links break, breaking an internal cross-reference,
the README quietly dropping a load-bearing link, the glossary truncated to a stub,
two headings collapsing to one anchor, and the file deleted outright.
The anchor test replicates GitHub's slug algorithm rather than trusting that headings
and links happen to agree.
⚠️ `test_the_readme_actually_links_the_terms_it_leans_on` is the unusual one, and it
guards the failure this whole idea invites: the glossary exists, nobody links to it,
and the knowledge is parked where no reader will find it. Five terms are required by
name. Dropping one is allowed — dropping it on purpose, by editing that list.
250 tests pass. Both generators green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Audited by diffing README.md against origin/main and reading every REMOVED line,
rather than trusting that a rewrite kept what mattered. Two were real losses, not
rewordings:
1. "Specify the workflow and its data contracts, inspect its diagram, then have the
agent implement the steps." The development-sequence table still covers this, but
the one-line version is what tells a reader the VERB ORDER before they commit to
reading a table. Back, as "What you do:".
2. "The specification keeps the workflow understandable." Dropped entirely, and it is
the half of Boris's framing about agent output being hard to READ — a disservice to
holding the thing in your memory, separate from it being wrong.
Restored as a claim with a mechanism instead of an adjective: the declaration's size
is set by the SHAPE of the workflow, not by the complexity of the steps. A step body
can grow to 500 lines; its StepSpec stays one. Measured across this repo's examples:
11 to 43 declaration lines, whatever is bound into them.
⚠️ Stated as absolute lines, not as a ratio. The ratio over these files is 13-32%,
but the files are mostly `__main__` demo printing, so the percentage measures the
demos rather than the claim. The bound is the real property.
250 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
borisdev
changed the title
A glossary, linked from the prose, with a test that kills dead links
Coherence: the rename, the generated reference tables, and the glossary
Oct 1, 2026
blocking is described as derived from the message, but this public slot is writable. After f.blocking = False, the field disagrees with both the message and blocking([f]), despite _is_blocking being documented as preventing drift. Make it a computed read-only property instead of stored mutable state.
This issue also appears on line 86 of the same file.
Attribute extra-binding findings to the supplied strategy
workflow_workbench/checks.py:290
For an extra binding, n is explicitly not declared by this design, so using its name as about violates the new guarantee that this field is a resolvable handle in the design supplied by the caller. Point this finding at the supplied strategy, which owns the invalid binding, rather than at the foreign node.
This issue also appears on line 306 of the same file.
Scope nested finding handles to the parent design
workflow_workbench/checks.py:441
Propagating child findings unchanged breaks the documented about invariant for the public root call. If a child has an incoherent node named inner_orphan, parent.coherence_check(...) returns about="inner_orphan", although that name cannot be resolved in the parent design handed to the method; this is the exact ambiguity the new structural handle is intended to remove (compare tests/test_workflow_spec.py:331-349). Nested findings need parent/path qualification or another explicit scope mechanism.
Only report successful table rewrites when rc is zero
workflow_workbench/reference.py:221
When either marker is missing, the loop sets rc = 1 and skips that block, but this unconditional success message still claims both tables were rewritten. Gate the message on rc == 0; the earlier marker-specific error is sufficient on failure.
Scope upgrade guidance to the return-type change
CHANGELOG.md:65
This upgrade instruction contradicts the breaking rename documented immediately above: 0.2 callers must replace spec.check(...) before moving to 0.3, otherwise they get the stated AttributeError. Keep the “nothing to do” wording scoped only to the finding return-type change, not to the release upgrade as a whole.
Update example label to use coherence_check
examples/parallel.py:77
The displayed label still uses the removed check API even though the expression calls coherence_check. Running this example therefore prints outdated migration guidance.
Rename stale GraphSpec._check test documentation
tests/test_subgraph.py:605
This newly added test documentation still names GraphSpec._check, but the method was renamed to GraphSpec._coherence_check in this change. The stale name sends readers to a method that no longer exists.
…— and five rounds of review (#5#6#7#8)
Contains #5, #6 and #7 — the stack is linear, so this one merge brings all four.
- #5 `coherence_check()` returns `CoherenceFinding`, so an agent can branch instead of regex
- #6 both reference tables in the README, generated from the package's own docstrings
- #7 the glossary, linked from the prose, with a test that kills dead links
- #8 the thesis, `examples/contestable.py`, `StepSpec.problem`, the implement-a-workflow skill
Merged as a MERGE COMMIT, not a squash, so the eight commits stay in history.
## Five rounds of Copilot review, worked
20 inline comments were on record unread when consolidation started. Across five rounds:
14 live / 2 stale on round one, 5 on round two (three of them my own regression), pickle on
round three, a stale docstring on round four, and two previously-missed on round five.
⚠️ **Every single finding was a CHECK narrower than its claim, not code that was wrong.** The
dead-link regex skipped malformed links; the "every rule" table enumerated 11 of 12; the retired
-token map had no `.check(`; the compatibility oracle's five string operations omitted pickle;
the rule-count guard scanned one phrasing in one file; a UI test drove one of three render
surfaces. The code was mostly right. The things asserting it was right were the defects.
266 -> 278 tests. Both generators green. Bundle reproduces byte-for-byte from `npm run build`.
## One finding deliberately open
Nested findings propagate an `about` relative to the child design, so it is not resolvable by a
caller holding only the parent. Both readings are right about different things, and reconciling
them means `about` carrying a path across a boundary — a semantic change to the field. Tracked in
#9 with the limit documented in-code at both sites rather than left overstating.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #6 (which is stacked on #5). Merge in order.
The README has to stay short, and this change keeps introducing words that carry real meaning a reader can't guess — coherence, well-formedness, stated gap, noise floor, declaration layer. Explaining each inline wears the reader down; dropping them loses the knowledge.
So: define them once, link them on first use.
16 terms, three lines each
The third line is the one worth reading — what the word does not mean here:
⛔ The test is the point
A glossary with dead links is worse than none: a reader clicks a term they don't know, lands nowhere, and learns not to click the next one. The mechanism dies quietly — and nothing else here would notice, because a markdown link is not code.
Five tests. All six ways this rots were introduced one at a time, each went red:
The anchor test replicates GitHub's slug algorithm rather than assuming headings and links happen to agree.
test_the_readme_actually_links_the_terms_it_leans_onis the unusual one, and it guards the failure this idea invites: the glossary exists, nobody links to it, and the knowledge is parked where no reader will find it. Five terms are required by name — dropping one is allowed, but only on purpose, by editing that list.250 tests pass. Both generators green.
🤖 Generated with Claude Code