Prove the gates ran, and ship the spec the app serves

Two faults with one cause: docs/ is excluded from publication because the prose
becomes the wiki, and two things under docs/ are not prose.

shopdb/core/api/docs.py reads docs/openapi.json AT RUNTIME to serve /api/docs.
Excluding it meant every site installed from the published repo shipped the
ReDoc page, loaded it, and got {"error":"openapi.json not generated"}. The spec
now publishes - it is a product file that happens to live under docs/. The prose
still goes to the wiki and nowhere else, and docs/api-inventory.json stays out,
because nothing reads it at runtime.

The second fault is the reason this was worth chasing. Three gate modules skip
themselves when the file they read is absent, which is correct in a published
checkout and silently wrong on GitHub, which runs against exactly that checkout.
The modules skipped, CI reported green, and an outside branch added nine
undocumented routes and passed. A gate that stops running is worse than no gate,
because nobody looks at it again.

scripts/check-gates-ran.sh asserts each gate ran, with two lists: the ones that
must run everywhere, and the ones needing files publication deliberately leaves
behind - required here, allowed to be absent there. A `gates` job runs it in CI.
Proven in both directions: green in each checkout, and red when the file a gate
reads is removed.

Also purge instance/plugins.json from the publication tree. It was never
published - it is untracked there - but an old copy had sat in that tree for
months, protected from --delete by its own exclude, and it made the published
repo fail its own suite locally: 13 plugins enabled where the code expects 16.
A publication tree that cannot pass its own tests cannot be verified before
shipping. That is the exact trap the comment at the top of the script warns
about, which had claimed a second victim.

Also: concurrency on the workflow, so a second push supersedes the first rather
than queueing behind it holding a self-hosted runner.
This commit is contained in:
cproudlock
2026-08-28 14:15:58 -04:00
parent dd3db50460
commit f4b17d0fa9
4 changed files with 175 additions and 3 deletions

View File

@@ -41,7 +41,31 @@ done
# the publication tree before its exclude existed stays there forever, invisible
# to the sync and caught only by the scrub gate. Purge the generated paths first
# so adding an exclude is enough on its own.
for stale in .pytest_cache .ruff_cache htmlcov .coverage \
#
# docs/ does NOT publish - the prose becomes the WIKI instead. One exception,
# and it is not a documentation decision:
#
# docs/openapi.json is read AT RUNTIME by shopdb/core/api/docs.py, which
# serves the /api/docs page every published site ships. Excluding it meant
# that page loaded and then answered {"error":"openapi.json not generated"}
# on every site installed from the published repo. It is a product file that
# happens to live under docs/, not documentation.
#
# It also un-skips tests/test_openapi_spec.py, which had been reporting green
# by not running - see scripts/check-gates-ran.sh.
#
# docs/api-inventory.json stays OUT: nothing reads it at runtime, so it is a
# development gate and belongs with the wiki. That does mean
# test_api_inventory_parity cannot run on GitHub, which is why the gate guard
# separates the checks that must run everywhere from the ones that only run
# where the repo is complete.
# instance/plugins.json is one site's plugin CHOICES. It is excluded, so it was
# never published - but an old copy sat in the publication tree for months,
# protected from --delete by that same exclude, and it made the published repo's
# own test suite fail locally: 13 plugins enabled where the code expects 16.
# A publication tree that cannot pass its own tests is not one anybody can
# verify before shipping.
for stale in .pytest_cache .ruff_cache htmlcov .coverage instance/plugins.json \
deploy/windows/installer/bundle deploy/windows/installer/Output \
deploy/windows/installer/plugins.iss deploy/windows/installer/version.iss; do
rm -rf "${PUB:?}/$stale"
@@ -50,7 +74,9 @@ done
rsync -a --delete \
--exclude '.git' \
--exclude '.gitea' \
--exclude '/docs' \
--include '/docs/' \
--include '/docs/openapi.json' \
--exclude '/docs/*' \
--exclude '/tools' \
--exclude '/mcp' \
--exclude 'start-api.sh' \