Skip to content

docs(#802): existence gate, exhaustive snippet check, five behavior pages corrected - #804

Merged
gsdali merged 2 commits into
refactor/381-pass1bfrom
docs/802-reference-audit
Aug 9, 2026
Merged

gsdali merged 2 commits into
refactor/381-pass1bfrom
docs/802-reference-audit

Conversation

@gsdali

@gsdali gsdali commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

What & why

#802: a merge-to-main condition for v2.0.0. The release removes 62 deprecated symbols (#784/PR
#798), makes 97 functions visible to tooling for the first time (PR #797's parser fix), and
changes behavior under five unchanged signatures. Nothing in the repo's existing gate scripts
checks that a documented symbol still exists, and during #798 that gap let two reference pages
show live-looking signatures for deleted symbols, caught only by a reviewer reading closely. This
PR builds the check, runs it, fixes every stale reference it (and a snippet-level extension of it)
found, verifies the five behavior-changed pages, confirms the operation-count gate, and records
the decision on the 97 newly-visible functions.

Closes #802

Item 1: existence, the highest-value half

New Scripts/check-docs-existence.py, wired into ci.yml's gate-scripts job and the optional
pre-commit hook alongside the other five gates. Every symbol docs/**/*.md and README.md
document (excluding docs/CHANGELOG.md/docs/SEMVER.md, append-only historical ledgers) is
checked against a live index built from Sources/OCCTSwift, keyed on the exact (owning type,
member name) pair so a rename can never collide with a same-named live symbol elsewhere: the
ShapeContentsExtended.nbEdges vs. removed Shape.nbEdges trap the issue calls out is a
dedicated self-test case, proven both directions.

11 self-test cases, each a real removed/renamed symbol from PR #798's own migration table or one
of the two named collision traps, run once with the relevant guard disabled per
okf/policies/prove-the-test-fails.md (documented in the self-test's own comments; the
protocol-extension case's removal drops the battery from 11/11 to 10/11, isolated to exactly that
case).

Measured against the real tree, then iterated three times before converging clean:

  1. A naive per-heading type guess (nearest section heading, then filename) produced 946
    candidates; reading a sample showed the guess wrong nearly every time, because the real pages
    mix a genuine type-name heading (## AAG) with a // MARK:-style group label (## Properties,
    reused verbatim across many files) under the identical un-backticked convention, and several
    pages are grab-bag completions pages whose filename names none of the types they document.
    Fixed by asking existence globally for a bare (undotted) reference, while keeping every
    dotted reference (Type.member) strict, since that is the shape the actual trap lives in.
  2. Down to 127: two real parser bugs (indirect enum and extension Shape.History with a dotted
    name were both invisible to the type tracker) and an overly strict access-level gate
    (Face.exactBounds is internal, FeatureRecognition.swift's buildGraph() is private,
    both real and both correctly documented for context) accounted for most of it.
  3. Down to 14 genuine stale references, all fixed:

Final state: 0 stale, 29 acknowledged historical (mentions a removed name and correctly says
so), 0 unresolved.

Item 2: snippets, measured then scoped

7,294 ​```swift fenced blocks in scope. Measured, not sampled first: the check that matters
(does a snippet name a removed symbol) is text matching against an already-built index, not
compilation, so it costs nothing to run against the full population. Scripts/repro/802-snippet- sample/measure-snippet-failures.py reuses check-docs-existence.py's own machinery, now
conformance-aware (see below), against every snippet's code text.

First pass found 16 candidates; two were false positives from this checker's own gaps (.self
metatype references, and WireCurve/EdgeCurve.maximumSampleCount, which is genuinely inherited
from a protocol EXTENSION on ArcLengthCurveAdaptor with no separate declaration near either
conforming type for the checker to find). Fixed the second properly: check-docs-existence.py
now tracks textual conformance declarations and resolves a member through them, transitively, with
a dedicated self-test proving it load-bearing. The other 14 candidates were real (4 new beyond
item 1's list, since a snippet's own code can name something a heading never restates:
Curve3D.helix again, Exporter.writeGLB/exportDXF, Curve2D.rectangle, Mesh.fromShape/
from), all fixed. Final exhaustive run: 0/7294.

Then, separately, a real-compilation sample: 40 random snippets fed through swiftc -typecheck
against the built OCCTSwift module. All 40 failed to typecheck, and all 40 are extraction
artifacts, not docs defects, once classified: this repo's own reference-page convention of
restating just a signature with no body (public var shellCount: Int { get }, public enum ProjectionSymbol with no cases) is invalid Swift at file scope, and a bare fragment
(graph.setCoEdgeUVBox(...)) assumes a graph the surrounding prose bound, not the snippet
itself. 0 failures remained once fragment-shaped errors are excluded. This closes the case for
exhaustive per-snippet compilation: the existing extraction shape makes it measure the extraction
method, not the docs, for the overwhelming majority of the corpus.

Item 3: the five behavior-changed APIs

Read each against its fix PR:

Item 4: confirmation

count-operations.py: README headline 4256, API_REFERENCE total 4256, both match the derived
count. Clean, as expected since this PR touches no Sources/ file.

Item 5: the 97 newly-visible functions, decided

Not release-blocking, per the issue's own framing, confirmed rather than assumed. I could not
reproduce PR #797's exact 97 (that count is intrinsic to its own duplication-detector's tokenizer,
a different, narrower population than "has a nested-paren parameter"), so I built a smaller,
independent sample of 35 public functions with a tuple-typed parameter using the same live index
this PR already has. 13 of 35 (37%) have no reference-page entry anywhere in docs/reference/,
spread across Shape, BRepGraph, Camera, ClipPlane, and LawFunction, every one of which
docs/reference/README.md's own coverage tracker currently marks "done". These are pre-existing
gaps (always public, never independently inventoried by anything until #797's parser fix), not a
regression from this release.

Decision: not release-blocking for v2.0.0. Documenting them is real, boundable follow-up work,
not urgent enough to hold the release for. Recommend a follow-up issue scoped to (a) closing the
13+ measured gaps and (b) correcting docs/reference/README.md's Status table, which currently
overclaims complete coverage for types that have measured gaps.

Tests

This PR touches no Sources/OCCTSwift or Sources/OCCTBridge file, only documentation and new
standalone tooling under Scripts/. Full swift test: 5463 tests, 1436 suites, 0 failures,
one clean run (not three, since nothing in Sources/ changed and a docs/tooling-only diff carries
none of the risk the three-run recommendation in #786 is aimed at).

All ten scripts under Scripts/*.py (the nine pre-existing plus the new
check-docs-existence.py), each run plus its --self-test where it has one:

check-bridge-index.py                        0 stale, 0 misfiled                        exit 0
check-bridge-index.py --self-test            18/18 cases correct                        exit 0
check-null-handle-guards.py                  all functions guard                        exit 0
check-null-handle-guards.py --self-test      24/24 cases correct                        exit 0
check-docs-defaults.py                       0 drifted, 0 unverified                    exit 0
check-docs-defaults.py --self-test           13/13 passed                               exit 0
check-docs-existence.py (NEW)                0 stale, 29 acknowledged historical        exit 0
check-docs-existence.py --self-test (NEW)    11/11 passed                               exit 0
derive-bridge-header-split.py --verify       0 ambiguous, 0 unmapped, 0 misfiled        exit 0
derive-bridge-header-split.py --self-test    8/8 cases correct                          exit 0
count-operations.py                          4256 == 4256 (README/API_REFERENCE match)  exit 0
census-unmeasured-values.py --self-test      36/36 cases correct                        exit 0
check-changelog-transcription.py --self-test 22/22 cases correct                        exit 0
derive-shape-domain-split.py --self-test     10/10 cases correct                        exit 0
derive-swift-file-split.py --self-test       6/6 cases correct, 0 spurious              exit 0

census-unmeasured-values.py's bare run (a census, not a gate) and check-changelog- transcription.py's bare run (a report, not a gate) are both unaffected in character by this
diff, as expected for a docs/tooling-only change.

Prove the test fails, per okf/policies/prove-the-test-fails.md: the new self-test's
protocol-conformance case (proving check-docs-existence.py resolves a member supplied only by a
protocol extension, the WireCurve/ArcLengthCurveAdaptor shape) was run once with
resolve_conformances neutered to a no-op. Result: 10/11 passed, failing exactly and only the new
case (expected clean=0, got stale=1), restored, re-ran clean at 11/11.

CHANGELOG entry

A new gate checks that every symbol docs/ documents as current API still exists in Sources/OCCTSwift (#802)

Scripts/check-docs-existence.py closes a gap none of the existing gates covered: during #798,
two reference pages showed a live-looking signature for a symbol the same PR had deleted, caught
only by a reviewer reading closely. The new gate checks every symbol docs/**/*.md and
README.md document against Sources/OCCTSwift, keyed on the exact (owning type, member name)
pair so a rename cannot collide with a same-named live symbol elsewhere (Shape.nbEdges, removed,
vs. ShapeContentsExtended.nbEdges, live and unrelated). Wired into ci.yml's gate-scripts job
and the optional pre-commit hook alongside the other five gates.

Fixed 18 stale references it found: three sections (Curve3D.ContinuityOrder,
Shape.BSplineContinuity, Surface.Continuity) still described a removed typealias as a live
deprecated alias; Document-OCAF-Attributes.md's whole TDocStd_PathParser section documented a
type #784 removed outright (now points at OSDPath); a cookbook guide still called generation
merely deprecated rather than removed; and 13 pages (headings and code snippets both) named a
method that never existed under that name, unrelated to #784: Curve3D.helix, Shape.fillet,
Shape.pipe, Mesh.fromShape/Mesh.from, Curve2D.rectangle/Curve2D.arc,
Shape.makeFace/makePolygon, and Exporter.writeGLB/exportDXF.

Also corrected docs/reference/FeatureRecognition.md's detectPocketsAAG() and detectHoles()
sections, both of which still described their pre-#762/#747 criteria in full rather than the
current mechanism.

SemVer impact

NONE. This PR changes no public Swift declaration: it adds a new internal gate script and repro
tooling under Scripts/, and corrects documentation prose and code examples to match already-
shipped behavior. Nothing in Sources/OCCTSwift or Sources/OCCTBridge changed.

Checklist

  • New or changed behavior is covered by a unit test in the same PR. The gate script's own
    --self-test (11 cases) is that test, per this repo's convention for gate scripts.
  • Every new test and every new --self-test case was run once with its subject broken, and
    the failure is reported above (see "Prove the test fails").
  • The CHANGELOG entry above is complete, and docs/CHANGELOG.md is not in this diff.
  • The SemVer impact above is stated, and docs/SEMVER.md is not in this diff.

Notes for the reviewer

  • Not merging this PR, per instructions; opening it against refactor/381-pass1b for review
    only.
  • Scope boundary with PR refactor(#795): unify PDF/SVG/DXF drawing-collection onto DrawingDispatch.swift #800: that PR is open against Sources/OCCTSwift/{DXFExporter, DrawingDispatch,PDFExporter,SVGExporter}.swift and Tests/OCCTIOTests/. This PR does not touch
    any of those four source files or that test directory; it does correct two documentation
    mentions of Exporter.writeDXF/writeGLTF (real, existing methods on the Exporter extension
    in DXFExporter.swift, not a change to that file itself).
  • Item 5's decision (the 97 newly-visible functions) is recorded above rather than left implicit,
    per the issue's own instruction; happy to file the recommended follow-up issue if that is
    wanted, left undone here since the task was to decide and record, not to also open new tracking
    issues.
  • docs/occt-upgrades.md is in the existence check's scope (prose about OCCT kernel version
    history, not excluded like CHANGELOG.md/SEMVER.md); it produced no findings.
  • The snippet-measurement script (Scripts/repro/802-snippet-sample/measure-snippet-failures.py)
    is a one-off measurement artifact per the issue's "sample first, decide" framing, not proposed
    as a permanent gate; check-docs-existence.py itself is the permanent artifact.

…behavior pages corrected

Item 1: new Scripts/check-docs-existence.py verifies every symbol docs/ documents as current
API still exists in Sources/OCCTSwift, keyed on (owning type, member name) so a rename cannot
collide with a same-named live symbol (ShapeContentsExtended.nbEdges vs the removed
Shape.nbEdges). Found and fixed 14 real stale references across 8 pages: three sections
(BSplineContinuity, Continuity, ContinuityOrder) still described removed typealiases as live,
Document-OCAF-Attributes.md's whole PathParser section documented an entirely removed type, and
five pages named wrong method signatures unrelated to #784 (Curve3D.helix, Shape.fillet,
Shape.pipe, Mesh.fromShape/from, Curve2D.rectangle/arc). Wired into ci.yml's gate-scripts job
and the optional pre-commit hook alongside the other five gates.

Item 2: measured the existence-check's own machinery against all 7,294 ```swift snippets
(Scripts/repro/802-snippet-sample/), exhaustively rather than by sample since the check is text
matching, not compilation. Found and fixed 4 more stale snippets (Curve3D.helix, Exporter
writeGLB/exportDXF, Curve2D.rectangle, Mesh.fromShape/from). A 40-snippet real-swiftc sample
confirmed 0 defects beyond existence once extraction artifacts (this repo's own body-less
signature restatement convention) are correctly classified, closing the case for exhaustive
per-snippet compilation.

Item 3: read all five behavior-changed APIs against their fix PRs. detectPocketsAAG() and
detectHoles() were badly stale (describing pre-#762/#747 criteria entirely); rewrote both
sections. ShapeAxis.extent, Document.shapeColor and AAGEdge.sharedEdgeCount were already
accurate; added brief historical context to the last.

Item 4: count-operations.py confirms README/API_REFERENCE totals match (4256).

Item 5: measured a sample of the 97 newly-visible functions from #797's parser fix; found real
(pre-existing) reference-page gaps. Decision recorded in the PR body: not release-blocking.

Also fixed two real parser bugs in check-docs-existence.py found against the real corpus:
`indirect enum` and dotted `extension Type.Nested` were invisible to the type tracker, and
protocol-extension-supplied members (ArcLengthCurveAdaptor.maximumSampleCount) were invisible to
conforming types until a conformance-resolution pass was added.
@gsdali

gsdali commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Reviewed. Nothing blocking. I verified the gate against the live tree rather than trusting its
self-test, because a checker reporting zero is exactly what a blind one reports.

The gate discriminates, proven on the real corpus

Injected two references into docs/reference/Edge.md: Shape.nbEdges, genuinely removed, and
ShapeContentsExtended.nbEdges, live and same-named.

stale (no live declaration): 1
  docs/reference/Edge.md:907  Shape.nbEdges  [heading/dotted]

It caught the removed one and did not flag the live collision. That is the exact trap I warned
about, the one that produced false positives for me during #798, and keying on (owning type, member)
rather than on the name is what makes it work. Restored byte-identical, back to 0.

Exit code is 1 with a stale reference and 0 when clean, so it fails CI rather than merely reporting.
Wired into gate-scripts and the pre-commit hook.

The convergence is the part worth reading

946 candidates, then 127, then 14 real. Most reviewers would have shipped the first number as a
finding. Three separate causes were measured and fixed instead: per-heading type-guessing, two
parser gaps (indirect enum, dotted extension Type.Nested), and an over-strict access gate.

A checker that cries wolf at 946 gets ignored, which would have been worse than not having one,
so the tuning was the deliverable and the 14 were the by-product.

The 14, and what they say about the sweep before them

Two of the fixes are the ones that matter: detectPocketsAAG() and detectHoles() were documenting
their full pre-fix criteria, months out of date, after both were rewritten this week in #762 and
#747. Neither the PRs that changed them nor my reviews of those PRs caught it.

Five more were wrong method names unrelated to the deprecation work (Curve3D.helix, Shape.fillet,
Shape.pipe, Mesh.fromShape, Curve2D.rectangle), which is the useful signal: the doc set was
drifting independently of the removals
, so this audit was worth doing on its own terms rather than
only as #784 cleanup.

Snippets: measured rather than sampled, and the right call

7,294 checked exhaustively, because text-matching is cheap and only compilation is not. Four more
stale found. The 40-snippet swiftc -typecheck sample returning 0 real defects, once this repo's
body-less signature-restatement convention was classified as an extraction artifact rather than a
failure, is what justifies not compiling all of them. That classification step is where a sample
usually goes wrong.

Item 5, and one thing I want on the record

13 of a 35-function sample are undocumented across types the README's own Status table calls
done
. Agreed that is not release-blocking, since they have always been public and undocumented and
nothing regressed.

But it means the Status table overclaims, and that is a separate, smaller problem from the one this
issue was about. Please make sure the follow-up says so explicitly, because "these functions lack
pages" and "a table asserts they do not" want different fixes.

Also

Finding and fixing a real conformance-resolution bug, protocol-extension members being invisible to
conforming types, with its own proven self-test, is a good catch: that shape would have produced
silent false positives forever.

And you swept roughly 50 em-dashes out of your own new prose after introducing them. Recording that
because it is now the third artifact this week to need that sweep after the fact.

…re-guessed

The gate walks docs and checks each reference resolves. `--coverage` walks the source and checks each
member is referenced, which is the inverse question and the one item 5 asked. A clean gate run says
nothing about coverage, so they are separate modes rather than one number.

Measured: **1,487 of 6,471 public members, 23.0%, are named nowhere in `docs/`**, across 447 types.
By declaration kind that is 720 properties, 575 enum cases, 159 functions and 33 other, which is a
very different shape from a flat list and changes what completing it means: one table covers
`Shape.CheckStatus`'s 37 cases, where 159 functions want prose and snippets.

**This lives here because deriving it any other way produced a wrong answer.** An ad hoc regex over
`Type.member` and `member(` reported 1,857, roughly 25% high, because a member documented as a bare
heading or in running prose is invisible to that shape. `Shape.isValidSolid` is documented in nine
files and that count called it missing. The number was believed and nearly acted on before a
five-item spot check killed it. This script already parses docs correctly, handles the
`Shape.nbEdges` versus `ShapeContentsExtended.nbEdges` collision, resolves protocol conformances and
recognises historical annotations, so it is the only place the question can be asked honestly.

Never fails. Coverage is a backlog, not a property of a correct tree, and a gate that failed on it
would fail every build until the backlog is empty.

Four self-test cases with a removal matrix, each isolating one mechanism. The first is the one that
matters: `rep.seen` is populated as a side effect of the staleness walk, so a change that stopped
recording it would leave every existing case green while coverage silently reported the entire
surface as undocumented.
@gsdali
gsdali merged commit 3d3765c into refactor/381-pass1b Aug 9, 2026
3 checks passed
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.

1 participant