test: prove schema conformance and refusal with a committed vision corpus (#18) #72
Closed
rockachopa
wants to merge 0 commits from
timmy/18-vision-corpus into main
pull from: timmy/18-vision-corpus
merge into: stackchain:main
stackchain:main
stackchain:timmy/19-metrics-operator-report
stackchain:timmy/16-image-ingress-hardening
stackchain:timmy/40-security-rc-evidence
stackchain:timmy/27-dataset-card
stackchain:timmy/26-review-adjudication
stackchain:timmy/32-abstention-policy
stackchain:timmy/29-evaluation-harness
stackchain:timmy/35-portable-backup-versions
stackchain:timmy/24-training-contribution-consent
stackchain:fix/demo-caption-pixels
stackchain:timmy/36-photo-lifecycle-controls
stackchain:timmy/release-notes-grounded-chat
stackchain:timmy/37-grounded-chat-citations
stackchain:timmy/21-escalation-regression
stackchain:timmy/34-explainable-pattern-summaries
stackchain:timmy/15-container-worker-receipts
stackchain:timmy/73-blank-first-ordering
stackchain:timmy/20-photo-dataflow-threat-model
stackchain:timmy/67-hard-stool-followup
stackchain:timmy/13-provisional-suggestion-state
stackchain:timmy/12-manual-fallback-draft-recovery
stackchain:timmy/11-mobile-capture-recovery
stackchain:free/hy3-timmy-16
stackchain:timmy/35-version-ledger-migrations
stackchain:timmy/41-release-observability-dashboard
stackchain:free/step-timmy-14
stackchain:timmy/21-escalation-regression-suite
stackchain:timmy/17-bounded-inference-queue
stackchain:timmy/58-strict-csp-runtime-config
stackchain:timmy/55-staging-deployment-tooling
stackchain:timmy/53-private-subpage-staging
stackchain:timmy/10-reproducible-smolvlm-bootstrap
stackchain:timmy/48-vomiting-phrase-hotfix
stackchain:timmy/48-server-safety-override
stackchain:timmy/47-sleek-hermes-agent
stackchain:timmy/9-product-boundary-decisions
stackchain:timmy/8-ci-gates
No Reviewers
Labels
Clear labels
area/backend
area/data
area/frontend
area/model
area/privacy
area/release
area/safety
blocked
human-gate
priority/P0
priority/P1
priority/P2
state:review
type/docs
type/epic
type/feature
type/ops
type/research
type/security
type/test
Application API and storage
Dataset and provenance
PWA and interaction design
Vision model and inference
Privacy and consent
Release engineering and distribution
Clinical safety boundary
Blocked on an explicit dependency or decision
Requires explicit human approval
Release-critical
High priority
Planned
Documentation and evidence
Roadmap epic
Product feature
Infrastructure and operations
Research or evaluation
Security hardening
Verification and quality gate
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: stackchain/timmy-talking-turd#72
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #18
What changed
The worker contract was only proven by one happy-path fixture plus a few inline assertions, so nothing continuously checked that a reply Timmy cannot use is refused rather than guessed at.
tests/fixtures/vision-corpus/, CC0-1.0): stool, non-stool, blur, glare, empty bowl, and malformed-response cases, each a synthetic fixture drawn deterministically byscripts/build_vision_corpus.py. Every entry records itssha256, provenance, licence, case class, the provider reply it replays, and the outcome that reply must produce.loadVisionCorpus()refuses a fixture whose bytes no longer match the manifest, so the corpus cannot be quietly edited into a different test. No camera, no human subject, no real medical imagery, no third-party content.Invalid AI response: visual fields are out of rangeand surfaced to the user as a worker failure. The declared response schema permits those nulls, so it now abstains (abstainedFor: unusable_visual_fields, "No confident match") while still prefilling nothing. This is the visible delta for this release.invalid_json,invalid_fields) instead of prose matching, andvisionFailureResponse()maps them to plain user-safe copy — "No suggestion was applied. Continue manually, or try a clearer photo." No provider text, image bytes, or credentials are echoed.server.mjsuses it, so unparsable and out-of-range replies return 503 with that copy.Evidence
npm test→ 122 tests, 122 pass, 0 fail (was 112; +10 covering the corpus, hash integrity, conformance, the abstention, and the boundary).tests/vision-corpus.test.js— corpus contract, licence/provenance, hash + byte-length integrity, coverage of every required case class, and per-case outcome/reason conformance.tests/vision-boundary.test.js— the corpus replayed through the realanalyzePhotopath, then through a runningserver.mjswith a stub worker on a real socket: 200 for the only schema-valid suggestion, 200 abstention for unreadable fields, 503 with plain copy for unparsable and out-of-range replies, 400 without consent, and nobase64/data:image/credential in any body.npm run test:vision-refusal→ browser acceptance against a real server: non-stool refusal, unreadable-fields abstention (this is the regression test for fix 1 — it fails before the fix), out-of-range fail-closed, plus a positive control that a usable reply still prefill. Screenshots inartifacts/vision-refusal-*.pngandartifacts/vision-corpus-suggestion-mobile.png.test:ui,test:photo,test:mobile-capture,test:sleek,test:manual-fallback,test:provisional,test:followup;python3 tests/staging-deploy.test.py→ 20/20 OK;npm audit --audit-level=high→ 0 vulnerabilities;npm run check:syntax;npm run check:diff.python3 scripts/build_vision_corpus.py --verify→ verified 7 fixtures againstmanifest.json.Demonstration path
scripts/record_release_demo.mjsnow replays three corpus cases through the real analysis code path (the recorded response is the status and body the server would actually send, not copy written for the video) and shows the refusal, the new abstention, and the fail-closed reply on camera, with honest captions and no real photos. The demo was run end to end againstserver.mjsand its on-camera caption receipt lists all 36 captions.Gate wiring:
package.json(test,test:vision-refusal,check:syntax),.gitea/workflows/quality.yml,scripts/build_release.py(corpus--verify, the refusal suite, a new required on-camera caption,vision_corpus_refusal: passed), with meta-tests intests/ci-workflow.test.jsandtests/release-demo.test.jsso the wiring cannot silently drop.Safety boundary
Unchanged and deliberately more conservative: the corpus adds no new claims, a refusal never carries a form or colour, and nothing diagnoses. Only visible form, colour, and image quality remain suggestible.
Not merged: awaiting review.
Pull request closed