feat: pin the worker container and gate readiness on the loaded model (#15) #75
Closed
rockachopa
wants to merge 0 commits from
timmy/15-container-worker-receipts into main
pull from: timmy/15-container-worker-receipts
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/73-blank-first-ordering
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#75
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.
Refs #15 — deliberately not
Closes: the target-GPU half of criterion 4 (cold/warm latency and VRAM recorded on the target GPU) is instrumented but not measured. This run had no GPU host and buying compute is out of bounds, so no receipt is bundled. Criterion 3 is enforced by contract tests rather than a running container: the image targets a CUDA host, and this host has no CUDA to build or run it on. Said plainly here so nobody reads the green CI as more than it is.What changed
A worker that answers
/v1/modelshas not necessarily loaded its weights. Real evidence from the pinned build (llama.cpp 6d054983,SmolVLM2-2.2B-Instruct-Q4_K_M+ mmproj):The app called that first state ready and offered consent + Analyze visible form + color, which could only fail after the upload. Readiness now requires the loaded pinned model, and that state lands on manual Bristol entry in one tap.
User-visible (
src/vision-readiness.js,app.js,styles.css): newloadingphase — "Vision worker up · the model is not loaded yet · manual logging is available" — with its own amber treatment, no consent card, no analyze control, and the manual path still one action.analyzeenablement now followsvisionReadiness(...).readyinstead ofproviderReady.Container recipe (
deploy/worker/): digest-pinned CUDA build/runtime bases (nvidia/cuda:12.4.1-*@sha256:…), pinned llama.cpp commit, weights never baked in but mounted read-only at/modelsand sha256-verified by the entrypoint before the server starts, numeric non-rootUSER, no credential of any kind, and a run recipe that refuses any publish host outside loopback (--read-only --cap-drop=ALL --security-opt no-new-privileges).deploy/worker/receipts.jsonis the machine-readable receipt, drift-guarded againstscripts/bootstrap_selfhost_smolvlm.sh.Post-load gate (
scripts/worker_ready.sh): one implementation shared by the host bootstrap (bootstrap_selfhost_smolvlm.sh ready), the imageHEALTHCHECK, and the benchmark — exit 2 unreachable, 3 reachable-but-not-loaded, 0 ready.Target-GPU benchmark (
scripts/record_worker_benchmark.mjs): records cold start, warm latency percentiles, and VRAM against the target device, and refuses to write anything it did not measure. Exercised against the real running worker with no GPU:Acceptance criteria
receipts.json, and the host bootstrap must agree ortests/worker-container.test.jsfails; the pins were confirmed against the real weight files (sha256summatches the recorded values).HEALTHCHECKcalls the same gate with--start-period=600s; the app contract fails closed on an unconfirmed model.--privileged, read-only mounts, no credential defaults,.dockerignoreexcludes weights/user images/releases/secrets. Runtime behaviour of the unbuilt CUDA image is not verified here.Demonstration path
scripts/record_release_demo.mjsruns a new asserted scene (issue #15): the worker answers while the model loads — the recording proves no "Self-hosted model ready" claim, no#analyze-photo, no.consent-card, the distinct amber not-loaded status, and one-tap manual entry; then the model loads and the AI path returns. The recorded captions were replayed for real (42 captions rendered,TIMMY_DEMO_CAPTIONS_OUTreceipt), and the caption is added toDEMO_REQUIRED_CAPTIONSso the release build fails if the scene disappears.Tests
npm test), including the newtests/worker-container.test.js,tests/worker-ready.test.js,tests/worker-benchmark.test.js.test:ui,test:photo,test:mobile-capture,test:sleek,test:manual-fallback,test:provisional,test:followup,test:vision-refusal, and the newtest:worker-readiness).npm run check:syntax,npm run check:diff,python3 tests/staging-deploy.test.pypass; secret scan over the diff is clean.Contract widening worth a reviewer's eye
visionReadinesspreviously treatedproviderReady: trueas ready. It now requiresmodelSeen: true, and a missingmodelSeenfails closed.server.mjsalways sends it, so the self-hosted app path is unaffected (verified against the real worker, which returns bothdataandmodels). Three test fixtures that mocked a ready provider withoutmodelSeenwere completed to match the real contract (mobile-capture,sleek-chat), and the pre-existing phase assertion intests/vision-readiness.test.jswas updated to the widened contract.Pull request closed