Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/reference/recogniser-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,34 @@ pass.
`bosses` is the fully consumed reference family. `repeating-radial-profiles` is the opposite
reference: it remains geometry-only critique evidence for a separately authored gear declaration,
with no inferred gear feature added to fill the table.

## Passage compatibility boundary

The installed `b123d-recognisers==0.3.1` release already contains the `passages` family introduced
in 0.2.6. Draftwright declares its schema-v1 `Passage` output exhaustively but deliberately keeps
the family `unsupported`, with the drafting decision tracked by issue #1245. This is a truthful
consumer disposition: the geometry remains visible in the aggregate inventory, while Draftwright
does not invent an IR feature, DSL declaration, generated code, drawing annotation, or completeness
requirement for it.

Draftwright's exact dependency pin must remain on a reviewed released version in the interval
`>=0.2.6,<0.4.0` until the separately reviewed F4b transition is ready. The lower boundary ensures
the installed manifest really contains `passages`; the upper boundary prevents accidental adoption
of the planned 0.4 compatibility change.

The reviewed future 0.4 model is a distinct migration, not latent behavior in this declaration:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The paper model omits the one consumer-visible consequence of the 0.4 migration, and the crossing condition doesn't mention #1245. Under the revised #184, a principal split-junction void whose walls contain a Slot group flips ownership at 0.4: today the legacy finder never sees it, so the Slot is the accepted, counted, rendered claim (slots is fully supported); post-0.4 the rich passage wins (SLOT_SUPERSEDED_BY_PASSAGE) and the claim moves to a family this page declares Draftwright does not draw. As specified, crossing 0.4 would silently replace a rendered slot callout with nothing. Since this section is the consumer-side boundary statement, it should say so: add a bullet that adoption changes which family claims Slot-superseding split-junction voids, and condition the pin crossing 0.4 not only on the capability-contract representation but on #1245 resolving what a passage draws (or an explicit decision to accept the callout loss).


- `SectionPassage` will be the authoritative physical output and aggregate census source;
- legacy `Passage` values will be an accepted-only compatibility projection;
- `recognise_passages(..., ledger=...)` will be a fail-loud unavailable compatibility operation;
- the writer-free `recognise_passages` name will remain public but non-authoritative; and
- rich split-junction passages can supersede a currently rendered Slot claim, moving ownership to
the Passage family through `SLOT_SUPERSEDED_BY_PASSAGE`; and
- the capability contract must represent those API and per-output roles exhaustively before the
dependency pin can cross 0.4.

That migration must update the package release, Draftwright adapter and declaration model,
validator, tests, exact pin, and lockfile together. The current validator remains unchanged and
fail closed; there is no forward declaration or stale-manifest exception. Crossing 0.4 also
requires #1245 to decide what those newly Passage-owned occurrences draw, or an explicit reviewed
decision to accept the resulting loss of the existing Slot callout.
40 changes: 40 additions & 0 deletions tests/test_recogniser_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,46 @@ def test_installed_package_contract_validates_without_a_sibling_checkout() -> No
assert len(package["families"]) == len(declaration["families"]) == 25


def test_existing_passage_contract_is_truthfully_declared_before_f4b() -> None:
"""Pin the current-family declaration separately from the future 0.4 migration."""

assert INSTALLED_PACKAGE_VERSION == "0.3.1"

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The adoption interval is not actually executable — only the point version is. The PR body says it "freezes the reviewed released interval >=0.2.6,<0.4.0 as the adoption boundary" and "pins … in an executable test", but this asserts == "0.3.1". The exact-literal ratchet is fine as a both-sides-changed-together convention (like the 25-family literal above), but the upper bound — the part the doc says "prevents accidental adoption of the planned 0.4 compatibility change" — has no executable form: when someone bumps the pin and dutifully updates this literal to "0.4.0", nothing fails and nothing routes them to the F4b gate. Add an interval assertion alongside the literal, e.g. parse INSTALLED_PACKAGE_VERSION and assert (0, 2, 6) <= v < (0, 4, 0) with a comment naming b123d-recognisers#184 / #1337 as the gate that must be ACCEPTed before the upper bound moves.

installed = tuple(int(component) for component in INSTALLED_PACKAGE_VERSION.split("."))
# b123d-recognisers#184 and Draftwright #1337/#1245 must be accepted before this
# upper bound moves: 0.4 changes both the capability model and rendered ownership.
assert (0, 2, 6) <= installed < (0, 4, 0)
package = _families(recognition.capability_manifest())
declaration = _families(consumer_capability_declaration())
passage_package = package["passages"]
passage_consumer = declaration["passages"]

assert passage_package["introduced_in"] == "0.2.6"
assert len(passage_package["records"]) == 1
passage_record = passage_package["records"][0]
assert passage_record["name"] == "Passage"
assert passage_record["role"] == "output"
assert passage_record["schema_version"] == 1
assert passage_record["aggregate_membership"] == ["RecognitionResult.passages"]
assert passage_consumer["record_schemas"] == {"Passage": [1]}
assert passage_consumer["disposition"] == "unsupported"
assert passage_consumer["tracking"] == "https://github.com/pzfreo/draftwright/issues/1245"
assert {
passage_consumer[name]["state"]
for name in (
"ir_adapter",
"dsl_declaration",
"generated_code",
"drawing_consumer",
)
} == {"unsupported"}
assert passage_consumer["completeness"]["state"] == "deferred"

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Minor: the unsupported lowering emits a sixth boundary this test doesn't pin — documentation: {"state": "supported", "evidence": ["docs/reference/recogniser-capabilities.md"]} — and this PR is precisely that evidence. Worth one more assertion (passage_consumer["documentation"]["state"] == "supported" plus the evidence path) so the boundary set checked here is exhaustive rather than four-of-six, and so deleting the new doc section can't leave the declaration claiming evidence that no longer exists.

assert passage_consumer["documentation"] == {
"state": "supported",
"evidence": ["docs/reference/recogniser-capabilities.md"],
}
assert "passages" not in pending_family_declarations()


def test_runtime_adapter_inventory_is_derived_independently_and_exhaustive() -> None:
runtime = _runtime_emitted_records()
tiers = [
Expand Down
Loading