Skip to content

W12: make ontology/kko/PROVENANCE.md a gate instead of a record - #35

Merged
mdheller merged 2 commits into
mainfrom
chore/w12-kko-digest-gate
Jul 30, 2026
Merged

W12: make ontology/kko/PROVENANCE.md a gate instead of a record#35
mdheller merged 2 commits into
mainfrom
chore/w12-kko-digest-gate

Conversation

@mdheller

Copy link
Copy Markdown
Member

Companion to prophet-platform #1056 (W12 inventory hygiene, from the vendor-freshness inventory in sociosphere PR #492).

The finding

ontology/kko/PROVENANCE.md is the best provenance record in the estate — source, licence, version IRI, byte count, sha256. It is also, until this PR, verified by nothing. The digest was a correct line in a Markdown file, connected to no check.

That was worth fixing on its own, but it became load-bearing this week: prophet-platform vendors byte-identical copies of this TBox (apps/owl-reasoner) and now asserts the same constant at import. The engine — the artifact's source of truth — was the one copy where drift would have gone unnoticed.

I went looking for the "sha asserted at import" the engine was believed to have. It does not exist: grep for the digest across the repo returns exactly one hit, PROVENANCE.md:4. Worth knowing, since other work has been reasoning from the assumption that it was already enforced here.

What this adds

scripts/check-kko-provenance.mjsnpm run check:kko, wired into ts-ci on every PR (cheap, no path filter, so a re-vendor can never land unverified). Three assertions:

  1. kko-2.10.n3 still hashes to the SHA-256 the record declares, and is still the declared byte length. The record is the source of the expectation, so record and artifact cannot silently disagree.
  2. The record pins a COMMIT. It cited @ master — a moving reference: the same retrieval later can return different bytes while claiming this provenance. Now pinned to 3f888b397255b69d1439fd95823e97011ed9440b, verified against upstream 2026-07-29. (master has not moved since 2019-04-09, which is exactly why the difference stays invisible until it isn't.)
  3. ts/src/kko-data.ts is exactly what scripts/gen-kko.mjs reproduces from that .n3 — so the ontology the engine actually ships derives from the file whose provenance we publish. A hand-edit, or a re-vendor without regeneration, otherwise leaves the runtime ontology diverged from its stated source with no signal. Same doctrine as this repo's existing ts/dist staleness gate: a generated artifact must be reproducible from its source, or the source is not the source.

Lane safety

ts/src is not modified. Check 3 imports ts/src/kko.ts to reuse the engine's own parser, regenerates into memory, and compares. Nothing is written. git status on this branch touches only .github/workflows/ts-ci.yml, ontology/kko/PROVENANCE.md, package.json, and the new script.

One consequence worth stating plainly for the active ts/src lane: if a parser change alters what gen-kko.mjs would emit, check 3 goes red until kko-data.ts is regenerated and committed. That is the intended signal — it is the same contract check:dist already imposes — but it is a new red that lane could see.

Teeth verified

Each check fails on a real tamper, then passes again on the clean tree:

tamper result
:Monads:Places — a length-preserving re-parent of a real class. Still valid Turtle, still parses, still exactly 327,797 bytes refused (defeats size and parse checks; changes only entailments)
commit pin reverted to master refused
hand-edited label in the generated kko-data.ts refused
clean tree exit 0

One trap worth calling out

The commit-pin check strips the sha256 from the document before searching for a 40-hex commit. A naive /[0-9a-f]{40}/ over the whole file matches a 40-char window inside the 64-char digest — I wrote it that way first, and it passed against a PROVENANCE.md that pinned no commit at all. The check was being satisfied by the very value it is meant to be independent of. A checker that validates itself validates nothing.

Also noticed, deliberately not fixed here

package-lock.json is stale — it records version: 0.4.2 while package.json says 0.4.45, and npm install rewrites it. Reverted to keep this PR surgical and to avoid a conflict with the active ts/src lane; worth its own change.

allow_auto_merge=false on this repo — do not merge, opened for review.

The engine's KKO provenance is the best in the estate — source, licence, version IRI,
byte count, sha256 — and until now it was verified by NOTHING. The digest was a correct
line in a Markdown file, connected to no check.

That matters beyond this repo: prophet-platform vendors byte-identical copies of this
TBox and now asserts the SAME constant at import. The engine, the artifact's source of
truth, was the one copy where drift would have gone unnoticed.

scripts/check-kko-provenance.mjs (npm run check:kko, wired into ts-ci on every PR)
asserts three things:

  1. kko-2.10.n3 still hashes to the SHA-256 the record declares, and is still the
     declared byte length. The record is the source of the expectation, so record and
     artifact cannot silently disagree.
  2. The record pins a COMMIT. It previously cited "@ master" — a moving reference: the
     same retrieval later can return different bytes while claiming this provenance.
     Now pinned to 3f888b397255b69d1439fd95823e97011ed9440b (verified against upstream
     2026-07-29; master has not moved since 2019-04-09, which is exactly why the
     difference stays invisible until it isn't).
  3. ts/src/kko-data.ts is exactly what scripts/gen-kko.mjs reproduces from that .n3 —
     so the ontology the engine SHIPS derives from the file whose provenance we publish.
     A hand-edit, or a re-vendor without regeneration, otherwise leaves the runtime
     ontology diverged from its stated source. Same doctrine as the existing ts/dist
     staleness gate.

Read-only with respect to ts/src: it regenerates into memory and compares, never writes.

Teeth verified — each check fails on a real tamper:
  1. length-preserving :Monads -> :Places re-parent (valid Turtle, still 327,797 bytes) -> refused
  2. commit pin reverted to "master" -> refused
  3. hand-edited label in the generated kko-data.ts -> refused
  clean tree -> exit 0

Note: the commit-pin check deliberately strips the sha256 from the document before
searching for a 40-hex commit — a naive scan matches a 40-char window INSIDE the 64-char
digest and would have been satisfied by the very value it is meant to be independent of.
Comment thread .github/workflows/ts-ci.yml Fixed
…guards

Two findings from review of #35.

1. CodeQL actions/missing-workflow-permissions fired on the new kko-provenance
   job (alert 36, ts-ci.yml:92). Accepted. Fixed at WORKFLOW level rather than
   on the new job, because the same rule was already open against this file on
   `main` for build-and-verify-dist (alert 33, ts-ci.yml:24) — a job-level
   block would have closed the alert this PR introduced and left the one it
   inherited. Declaring it once also means a job added later inherits
   least-privilege instead of silently getting the default write-scoped token.
   Both jobs only read the checkout and install from the public registry, so
   `contents: read` is sufficient; verified both still pass locally.

2. `on: push` was path-filtered to package.json / ts/** / the workflow itself.
   The kko-provenance job guards ontology/kko/** and scripts/**, so on a push
   that touched ONLY those paths the gate did not run at all. It always runs on
   pull_request (no path filter there), which is the merge path, so this was
   not a hole in practice — but the trigger should cover what the job guards.
   Added ontology/** and scripts/**.

Gate-fires proof (all six failure branches, each restored afterwards):
  artifact digest drift          -> rc=1
  commit pin replaced by @master -> rc=1
  kko-data.ts hand-edited        -> rc=1
  declared byte count wrong      -> rc=1
  declared sha256 removed        -> rc=1
  source repo no longer recorded -> rc=1
  untouched tree                 -> rc=0
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.

2 participants