fix: fail closed when a filled-in visual field is out of range (#73) #74
Closed
rockachopa
wants to merge 0 commits from
timmy/73-blank-first-ordering into main
pull from: timmy/73-blank-first-ordering
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/18-vision-corpus
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#74
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 #73
What changed
parseVisionResponseused to check for blank fields before validating them, so a single blank field short-circuited the whole reply. A response that filled in one field and left the other blank therefore abstained asunusable_visual_fieldseven when the filled-in field was out of range — telling the caller the photo was unreadable when the model had actually returned a value Timmy must never apply.Decision taken: option 1 from the control-plane call recorded on the issue (2026-10-11T13:45Z). Blank-first abstention is kept; the range clause now fails closed only for non-blank fields.
unusable_visual_fields.Corpus earns its label
The issue found that no corpus fixture failed at the range check:
out-of-range-fieldshadconfidence: 1.4, so it died at the confidence gate (line 33) and never reached the field-range guard, while carrying theout_of_range_or_missing_fieldslabel. Confidence is now0.9, so it reaches the range check it is named for.blank-plus-out-of-range:bristolType: 9with anullcolour, confidence0.9→fail_closed/out_of_range_or_missing_fields. This is the exact reply shape the old ordering let through. Fixture is synthetic, drawn deterministically byscripts/build_vision_corpus.py; manifest regenerated (--verifypasses over 8 fixtures, and every pre-existing fixture hash is byte-identical).Evidence (real runs, this working tree at
5c75907)node --test tests/analysis.test.jsnot ok 7 - fails closed when a field the model did fill in is out of range while another field is blank→Missing expected exception. Then 9/9 passnode --test tests/vision-corpus.test.js tests/vision-boundary.test.js tests/release-demo.test.jscorpus case blank-plus-out-of-range is missing, andat least one out-of-range case must clear the confidence gate. Then 30/30 passnpm testnpm run test:vision-refusalNo confident matchcount is 0, and requires exactly two 503snpm run test:ui | test:photo | test:mobile-capture | test:sleek | test:manual-fallback | test:provisional | test:followupnode scripts/record_release_demo.mjspython3 tests/staging-deploy.test.py -vnpm run check:syntax/check:diff/build_vision_corpus.py --verifyDemonstration path
scripts/record_release_demo.mjsnow replaysblank-plus-out-of-rangethrough the real analysis code path and asserts on camera that the app shows the fail-closed copy and not the abstention copy. Two captions:A filled-in field that is out of range still fails closed, even when the other field came back blankYou are no longer told the photo was unreadable when the model returned an impossible valueThe fail-closed 503 budget is raised from one to two so the added scene cannot be dropped silently, and the new caption is a required
DEMO_REQUIRED_CAPTIONSgate inscripts/build_release.py. Release notes andfeature_demo.fixture_datamention the new case. New visual evidence:artifacts/vision-refusal-blank-plus-out-of-range-mobile.png(follows the existing committed-screenshot convention).Boundary
Unchanged: no diagnosis, no symptom inference from images, no provider detail or image bytes in a fail-closed body, no real medical imagery (all fixtures synthetic, CC0), no model weights, no infrastructure or production-service changes. A schema-valid suggestion is still the only 200-with-fields outcome.
Not merged — opened for review.
🤖 Generated with Hermes Agent (deepseek-flash)
Pull request closed