0031 — krites-models: a signed, licence-clean model-provisioning repo¶
- Status: IN PROGRESS (2026-07-18) — phase 1 IMPLEMENTED (2026-07-21):
krites-modelsv0.1.0 published (signed, provenance-bearing) and krites' consumer side (pkg/face/models) migrated to verify signature + provenance for the MediaPipe FaceMesh/Blendshapes models. Phases 2–4 pending. - Why now: krites depends on six downloaded ONNX models (plus the ORT
shared library), each pinned ad-hoc to a third-party host (GitHub raw,
Hugging Face re-hosts) with SHA-256 only — no signature, no provenance, no
artefact we control. Three carry live licence caveats (§3). LaMa, MI-GAN, the
ORT lib and now the MediaPipe models all already carry a "Hardening TODO:
mirror to an artefact we control and re-pin" in-code.
0029shipped the MediaPipe expression models with their registry URL pinned but unpublished, which is the immediate forcing function. This spec resolves all of them at once. - Decision (2026-07-18): stand up a dedicated
krites-modelsrepo that fetches → converts → parity-verifies → signs → publishes each model as a release asset we control, and migrate krites' consumer side to verify the signature + provenance, not just a checksum. Model directly on the provenphpboyscout/ffmpeg-wasi(producer) +afmpeg(consumer) chain rather than inventing one — same signing backend, OIDC role pattern, package-registry layout, andgitlab.com/phpboyscout/go/signing/verifylibrary. - Realises: the
0029§7 hosting follow-up; the LaMa/MI-GAN ("remove/onnx/models.go") and ORT (pkg/onnxruntime) hardening TODOs; the0001§5 local-first / provenance posture. - Depends on:
0013(ORT provisioning),0004/0029(the models), the phpboyscout signing chain (go-tool-baseaws-kmsbackend,phpboyscout/go/ signing,phpboyscout/cicdcomponents). Cross-references: the Places2 licence risk tracked for0008.
1. The problem¶
pkg/face/models, pkg/remove/onnx/models, and pkg/onnxruntime each pin an
artefact by {URL, SHA-256} and download-on-first-use. That gives integrity
(a mismatched download is refused) but not:
- Provenance / authenticity — nothing proves the artefact is the one we produced from a known source; we trust GitHub/HF to serve the bytes the hash expects, and if the upstream URL 404s or is replaced, provisioning breaks.
- Control — several pins are someone else's re-host (the Carve LaMa export, the licence-less MI-GAN re-host), which can vanish or change.
- A publishable MediaPipe pin —
0029's models are converted by us and hosted nowhere yet; the strategy only works from a warm cache. - A single place to state each model's licence + provenance — today it is scattered across three Go files' doc comments.
2. Shape — mirror ffmpeg-wasi / afmpeg¶
ffmpeg-wasi already solves this exact class (disparate licences, an artefact we
build + sign, a consumer that verifies): tag-driven releases, a KMS-held OpenPGP
key assumed via GitLab OIDC (reproducible fingerprint), assets + checksums.txt +
checksums.txt.sig + provenance.json + release.asc published to the GitLab
generic package registry, and a consumer (afmpeg) that pins the URL + a
vendored public key and verifies the detached signature, the per-asset SHA-256,
and the provenance via gitlab.com/phpboyscout/go/signing/verify. We adopt that
chain wholesale; the only krites-specific differences are in §5.
krites-models repo (new)
pipeline (tag-gated): fetch upstream ─▶ convert/curate ─▶ parity-verify ─▶ sign ─▶ release
│ │ │ │ │
versioned source tf2onnx / passthrough golden values KMS+OIDC generic
recorded in reproduced OpenPGP package
provenance.json (not a hash!) detached registry
sig
krites (consumer): pkg/face/models + pkg/remove/onnx ─▶ verify sig + SHA-256 + provenance
(adopt phpboyscout/go/signing/verify; vendor the krites-models pubkey)
3. Model inventory & licence audit (the reason to centralise)¶
| Model | Role | Source & how | Licence | Commercial track |
|---|---|---|---|---|
| MediaPipe FaceMesh | expression landmarks (0029) |
Google face_landmarker.task → convert (tf2onnx) |
Apache-2.0, Google-trained | ✅ clean |
| MediaPipe Blendshapes | expression coeffs (0029) |
same bundle → convert | Apache-2.0, Google-trained | ✅ clean |
| UltraFace RFB-320 | face detect (0004) |
ONNX Model Zoo → mirror | MIT (Linzaer) | ✅ clean |
| InsightFace 2d106det | eye/facing landmarks (0004) |
InsightFace buffalo_l → mirror |
⚠️ non-commercial / research only | ❌ blocker |
| LaMa (big-lama fp32) | inpaint (0008) |
Carve HF export → mirror | Apache code, Places2 non-commercial training data | ⚠️ unsettled |
| MI-GAN pipeline v2 | inpaint (0008) |
licence-less HF re-host → mirror | MIT code, Places2 training data | ⚠️ unsettled |
| (ORT shared library) | inference runtime (0013) |
Microsoft GH release → mirror (binary, no convert) | MIT | ✅ clean (in scope as mirror-only) |
Centralising makes this table enforced, not folklore: each release's
provenance.json records the model's upstream source, version, and licence, and
the consumer surfaces it. Two clean-up paths this exposes:
- Drop the non-commercial 2d106det by moving eyes/facing onto the MediaPipe
FaceMesh we already ship —
0029phase 4 (shared backbone). DECIDED (2026-07-18): do it — makemediapipethe primary/default face strategy and keepear+ 2d106det as an opt-in fallback, leaving the entire default face stack Apache-2.0/MIT. Tracked in0029(promoted); gated on this repo hosting the models first (a fresh default install must have something to download). - Replace the Places2-trained inpainters with a permissively-trained model — the standing Places2-licence-risk item (still future).
Until those land, the tainted models stay fine for private, self-hosted use and are published with an explicit licence record + disclosure — the same good-faith posture we already hold, now in one auditable place.
4. The krites-models repo¶
Per-model pipeline, tag-gated (a release is cut by tagging), following
ffmpeg-wasi's validate → build → sign → release stages:
- Fetch the pinned upstream (URL + its own SHA recorded in provenance).
- Convert or curate. MediaPipe:
tf2onnxopset-17 (the existingscripts/convert-mediapipe-models.py, moved here). Mirror-only models (UltraFace, 2d106det, LaMa, MI-GAN, ORT lib): pass through unchanged. - Parity-verify — the load-bearing gate.
tf2onnxoutput is not byte-reproducible (0029: functionally identical, different SHA every run), so we cannot pin on a rebuild hash. Instead the pipeline runs the converted model against frozen golden reference values (e.g. the bride smile 0.8141) and fails the release if it drifts. Mirror-only models verify the fetched bytes against their recorded upstream hash. This gate is what makes an unattended re-convert safe. - Sign — with a dedicated
krites-modelskey (domain-separated). Achecksums.txtover all assets + a detached OpenPGP signature (checksums.txt.sig) viagtb sign --backend aws-kms, the KMS key assumed through GitLab OIDC (no static creds). Provision a new dedicated key + signer role via the reusablesigning-kms/awsmodule —krites-models-release- signing-v1, OIDC-filter-locked toproject_path:phpboyscout/krites-models: ref_type:tag:ref:v*— mirroring howffmpeg-wasihas its own key. This preserves the D-0010-D domain-separation decision (each project's tag pipeline can mint only its own project's signatures — a compromise of one cannot forge another's). It reuses the shared signing infrastructure (the GitLab OIDC IDP, the<project>-automationapply role, break-glass admins), only the key + signer role are new (a single module block ininfra/src/ main.signing-kms.tf). Emitprovenance.json(per-model upstream source/version/licence). Sign all assets — converted, mirror-only, and the ORT binary mirror alike (uniform trust chain). - Release to the GitLab generic package registry
(
…/packages/generic/krites-models/<tag>/…) — one release, many assets, under a singlechecksums.txt/signature withprovenance.jsoncarrying the per-model records.
Versioning / upstream tracking. Tag-driven and deliberately human-initiated
(ffmpeg-wasi's model): a tag encodes the release, provenance.json records each
model's exact upstream version. Renovate on the repo (renovate-self + the shared
gitlab>phpboyscout/cicd preset) keeps the repo's own CI/deps current; adopting
a new upstream model is a chosen tag, not an auto-merge — a model swap deserves
review. (Upstream model feeds are not clean semver sources, so auto-datasource
tracking is out; a tag is simpler and safer.)
5. Consumer side (krites)¶
Migrate pkg/face/models (the shared fetcher) to the ffmpeg-wasi/afmpeg trust bar:
- Repoint every
Model.URLat thekrites-modelsgeneric-package release. - Add signature + provenance verification on top of the existing SHA-256:
verify the detached sig over
checksums.txt, then the asset SHA against the manifest, then assertprovenance.json(surfacing the licence for the disclosure UI/logs). A failure at any step refuses the artefact — never a silently-used file (R-MLR-4). - Trust anchor — vendor the
krites-modelspublic key (its own.ascin the krites tree) and pin its fingerprint, exactly asafmpegvendors ffmpeg-wasi's key. Verify withgitlab.com/phpboyscout/go/signing/verify(or gtb'spkg/signing). The key is dedicated (D-0010-D), so it is not gtb's binary-signing anchor — a distinct trust root for the models, exported from KMS once at provisioning time viagtb keys export/kms:GetPublicKey. pkg/remove/onnx/modelsandpkg/onnxruntimemove onto the same fetch + verify path, retiring their in-code hardening TODOs.- Golden fixtures stay the drift guard. krites' own
pkg/face/onnx/testdataparity test still runs; a model release that changed behaviour would break it and force human review of the Renovate pin-bump MR — the deliberate guardrail.
The pin-bump flow mirrors the go-tool-base bumps already seen: krites-models
cuts a release → Renovate opens an MR in krites bumping the pinned URL + SHA
(+ fingerprint only if the key rotated).
6. Phasing¶
- Repo + MediaPipe + signing chain. ✅ DONE (2026-07-21). Stood up
krites-models, ported the conversion script, wired the KMS/OIDC OpenPGP sign - generic-package release, published the two
0029models (v0.1.0). Migratedpkg/face/modelsto verify sig + provenance and repointed FaceMesh/Blendshapes at the release — an embedded, fingerprint-pinned (5B648A6B…) trust key verifies the detached signature overchecksums.txt, the in-code SHA pins bind to that signed manifest, andprovenance.jsonis asserted (Apache-2.0/clean) before any model byte is trusted. Third-party pins (UltraFace, 2d106det, LaMa, MI-GAN) keep the plain checksum fetch until phase 2. Closes the0029hosting gap. Note:provenance.jsonis not itself listed inchecksums.txtin v0.1.0, so the consumer cross-checks its per-asset SHA against the signed manifest; a producer-side follow-up should add it tochecksums.txtfor a direct signed-checksum verification. - Fold in the mirror-only models. UltraFace, 2d106det, LaMa, MI-GAN — fetch,
verify-vs-upstream, sign, publish; repoint krites; per-model
provenance.jsonlicence records + disclosure. Retire the in-code hardening TODOs. - ORT shared library. Mirror the Microsoft archive(s) the same way
(
pkg/onnxruntime), retiring its TODO. Binary mirror, no conversion. - (Later, tracked elsewhere) licence clean-up. Drop 2d106det via the
0029phase-4 FaceMesh backbone; replace the Places2 inpainters. Not this spec — this spec makes the debt visible and centrally managed.
7. Open questions¶
- Q1 — repo scope/name.
RESOLVED (2026-07-18):krites-modelsvs a genericphpboyscout/onnx-models?krites-models. Generalise only if a second tool needs it. - Q2 — one release with all models, or per-model releases?
one vs per-modelRESOLVED (2026-07-18): one release, many assets — singlechecksums.txt/ signature,provenance.jsoncarrying per-model records. - Q3 — sign the ORT binary mirror too, or checksum-only?
sign vs checksumRESOLVED (2026-07-18): sign everything, the ORT binary mirror included. - Q4 — signing key. RESOLVED (2026-07-18): a dedicated
krites-modelskey. Investigating../infrashowed there is no group-scoped key — the gtb and ffmpeg-wasi keys are each OIDC-filter-locked to one project, by the deliberate D-0010-D domain-separation decision. So krites-models gets its ownkrites-models-release-signing-v1key + signer role via thesigning-kms/awsmodule (one new block ininfra/src/main.signing-kms.tf), locked tophpboyscout/krites-modelstag pipelines; krites vendors + pins that key. This preserves D-0010-D rather than widening the gtb key to the group. (Superseded earlier drafts that proposed reusing the gtb key — that would reverse D-0010-D.)
8. Non-goals¶
- Not re-training or fine-tuning any model (that is the Phase-4 learner / future permissive-training work).
- Not resolving the InsightFace non-commercial or Places2 training-data licence questions — this spec records and centralises them and points at the replacement paths; the legal calls stay separate.
- Not bundling models into the krites binary — download-on-demand + verify stays the model (size, updatability, consistency with the ORT lib), even though the Apache-2.0/MIT models could legally be bundled.
- Not changing any inference/behaviour — pure provisioning/provenance.