-
Timmy review build 2026-11-02 — privacy-safe operator health counters (#19)
Pre-ReleaseQuality gates / quality (push) Waiting to runreleased this
2026-11-02 13:29:24 +00:00 | 0 commits to main since this releasePrerelease for review — tag
daily-2026-11-02on commit664a9099571fbbf185ab1e94c45d11502458f490(America/New_York release date 2026-11-02, built 2026-11-02T13:19Z).Delta: privacy-safe operator health counters — disclosed on the privacy screen, and readable as an operator report from the command line. This is a real source change, not a rebuild:
mainmoved from9067ce5(whatdaily-2026-11-01shipped) to664a909.What changed, and what the recording proves
1. The privacy screen now discloses the counters (user-visible). A fourth row,
[data-health-disclosure], sits with the other privacy facts and says what is counted, what is never counted (notes, photos, image fingerprints, identity), and that the counters reset when the server restarts. The recording scrolls that card into frame and reads the app's own sentence first; the scene fails if the copy changes.2.
GET /api/metricsis an operator surface, not a public one. WithTIMMY_METRICS_TOKENunset the route returns 404 (absent, not guarded); with it set, a missing or wrong bearer gets 403 with no counters in the body; a match returns the snapshot. Token comparison is a SHA-256 digest compare (timingSafeEqual), so a wrong-length guess does not leak a prefix.3.
scripts/model_metrics_report.mjsreads that snapshot withoutjq. The recording runs the real CLI against its own server (loopback only, demo token set by the build) and prints what came back — the card on screen is that output, not a fixture:SECTION APP uptime 9m 49s · requests 2 analyzed · 2 refused before the model SECTION QUEUE in flight 0 / 4 · accepted 0 · at capacity no SECTION MODEL readiness disabled · outcomes suggestion 0 · abstained 0 · failure 2 abstention no answers yet · latency mean n/a · max 0ms · samples 0 failures ingress_refused 2, every other class 0 wrong token refused · exit 1 · no countersThose two
ingress_refusedfailures are the hostile-bytes requests this recording made earlier in the same take (device-guard refusal, then the same bytes posted straight at the running server). The scene asserts the count is ≥ 1 before it draws the card, so a zero would fail the recording rather than be captioned as evidence.The privacy property is structural: the formatter imports the three frozen vocabularies and prints only those, so a failure class outside the enum is not printed rather than echoed. No note text, image hash, base64, or caller identity has a path to the terminal — asserted by test, not by convention.
Completed ticket / PR
- PR #92 merged —
feat(metrics): read the privacy-safe health snapshot without jq (#19), merge commit48979bf, Closes #19 (auto-closed 2026-11-02T12:07Z). Reviewed before merge: focused 18-file diff, no secrets, no model weights, no medical imagery;npm test298/298,npm run test:metrics-report8/8,check:syntax,check:diff, andtest:uiin real Chromium all green on the branch in a clean worktree before the merge. - Two corrections on top of the merge, both stated because they were caught by running the thing:
Correction 1 —
ef83ad9. Writing the on-camera scene meant running the new CLI. Its first real output printedfailure ingress_refused0: the longest failure label exactly filled the 22-character column, so label and count rendered as one broken token.LABEL_WIDTHis now 30 and a format test fails if any row loses the gap between its label and its value. (The disclosure-only demo in the PR would never have exercised this — it never ran the CLI.)Correction 2 —
664a909. The first recording was made and inspected at full resolution before publishing, and the card was wrong in a different way:ANALYZE REQUESTSand all eightFAILURE …labels wrapped onto two lines, detaching each label from its count on a 390 px viewport. The card now uses short keys with detail folded into values (one wrappedfailuresline), and was geometry-probed at the recording's own phone viewport before the rebuild: 706 px of content in a 706 px panel, no clipped key. The rebuild was recorded from scratch; no frame from the discarded take is shipped.Gates — 30/30 passed
npm test299/299 (298 merged + the new label-gap regression); mobile green path; photo-first acceptance; sleek Hermes chat; manual fallback + draft recovery; provisional suggestion state; hard-stool follow-up; vision corpus refusal; abstention quality gate; worker container receipts; worker post-load readiness; longitudinal pattern summary; grounded chat citations; training contribution consent; corpus manifest provenance; reviewer adjudication blind; dataset card; calibration harness; operator backup restore; production runbook commands; portable backup restore; image ingress refusal; privacy-safe health metrics; dependency audit (0 high); syntax (node/bash/python); secret scan; forbidden artifacts; demo H.264 720×1280 probe; complete MP4 decode; release delta verified painted on camera (103/103 captions, delta caption on screen, top 14 px, 73 px band, padBottom 14 px).Post-build, independently of the builder:
sha256sum -c SHA256SUMS→ bothOK;ffprobe→ H.264 720×1280 yuv420p, 25 fps, 12,889 frames, 515.56 s, video-only (no audio track, by design);ffmpeg -f null -full decode → exit 0, no errors.The build refused to start while anything held
127.0.0.1:4173, and hash-compares two served files against the clone under test before the first gate — the demo cannot be recorded against a different tree.Artifacts
- source archive
timmy-talking-turd-2026-11-02-664a9099571f.tar.gz— 388,064 bytes — sha256038e2340380541e8f6707d438c1cb988c42271645bd3f06322f212462bb15e8a - demo video
timmy-talking-turd-2026-11-02-664a9099571f-demo.mp4— 31,718,265 bytes — H.264 720×1280 yuv420p, 25 fps, 515.56 s — sha25614aa6a89d6d6ef858b6afd33e98df11814c4ab19edef8cff66bba82f8f87e1dc
Model / profile
Built and recorded by Hermes (profile
default, modeldeepseek-flash). The demo runs the real app from the built commit withTIMMY_VISION_ENABLED=false; the configured vision profile remains self-hostedSmolVLM2-2.2B-Instruct(pinned GGUF) with the hosted profile as fallback. The health report card is a real CLI run against a real server, but that server is in its disabled-AI state, so the report showsreadiness disabledand only the failure classes that state can produce. No live inference appears in the video. No model weights,.env, provider logs, raw corpora, or real medical images are packaged; every log, note, and photo placeholder on screen is synthetic.Operational note
timmy.alexanderwhitestone.comroutes to127.0.0.1:4173. Nothing was listening on that port when this run started, so the public URL had no backend before the build began (this release did not take it down; the gate only requires the port be free while it runs). It is running again from664a909and the public URL answers 200 (/api/healthztoo). The next release build will refuse to start while it runs — that is the gate working as intended, so stop the listener before the build and restart it after.Verified on this run, both auth branches: token unset →
404 {"error":"Not found"}(route absent); token set + wrong bearer →403; token set + match →200snapshot.Review focus
Watch from the privacy screen onward (~7:00–7:30): the disclosure sentence, then the report card. The thing to check is the honesty of the card, specifically two rows —
REQUESTS: 2 analyzed · 2 refused before the modelshould match the two hostile uploads the video showed earlier, andREADINESS: disabledshould make it obvious that this snapshot is from a server with AI switched off, not a claim about model health. If that reads as a health dashboard for a live model, it over-claims and needs rewording.Known accuracy limitation
Unchanged by this release, and not made better by it. Bristol accuracy is not clinically validated; the calibration receipt still reports
BLOCKEDbecause no candidate model clears every gate.queue.inFlightis the app's own admission depth forPOST /api/analyze, not the full bounded queue with cancellation and overload fallback — that is #17, still unmerged (PR #64). User confirmation remains mandatory everywhere: photo analysis may suggest visible Bristol form, broad color, and image quality only, and never symptoms or a diagnosis. Visible color is not blood.Downloads
- PR #92 merged —
-
Timmy review release 2026-11-01 (verified rebuild; no source change)
Pre-ReleaseQuality gates / quality (push) Canceled after 0sreleased this
2026-11-01 12:52:42 +00:00 | 5 commits to main since this releasePrerelease for review — tag
daily-2026-11-01on commit9067ce53067473467d7cc6a0cc681e8ff2054af6(America/New_York release date 2026-11-01, built 2026-11-01T12:34Z).This is a verified rebuild; no source change.
mainis at the same commitdaily-2026-10-31shipped, and this run re-proved it rather than asserting it: the full gate list re-ran from a fresh clone of that commit, a new recording was captured from the app running out of that clone, and both new artifacts carry new hashes. Nothing in the app changed, so nothing is claimed to have changed.Why there is no new feature today
The nightly job looked for shippable work and found none it could honestly merge:
- 7 open PRs were inspected (#79, #65, #64, #63, #62, #61, #60). Each was merge-tested against
mainwithgit merge-tree; all 7 conflict. Gitea's ownmergeablefield agreed (falseon all 7). None can be merged without a rebase and re-review, so none was merged and none was force-merged. - No branch carried a clean, reviewed, non-conflicting diff against this commit.
So the release ships the same tree as yesterday and says so on camera, in the recording's own caption bar.
What the recording proves about the current feature path
The demo replays the boundary the current tree actually ships — the image-ingress refusal from issue #16 — and shows it at both layers:
- a file that only claims to be a photo is refused on the device, nothing uploaded;
- the same bytes are then posted straight to the running server, bypassing the device guard, and the server refuses them too.
The card on screen is not a fixture. It is one real HTTP request to the app running in this recording, and it prints what that response returned:
status 400 refused,code not_an_image,copy That file is not a JPEG, PNG, or WebP photo.,provider never called,budget counted by the per-device limiter,after the journal, the draft, and manual logging are untouched, plus ascoperow naming which cases this single probe does not cover (polyglot tails, embedded metadata, dimensions, body size, rate limit — all asserted in the suite instead of implied by the card).The rest of the path is unchanged and still on camera: provisional AI suggestions with user confirmation, non-diagnostic hard-stool follow-up, abstention and fail-closed vision replies, explainable pattern summaries that cite the logs they counted, grounded chat, opt-in training contribution, versioned backup/restore, the corpus audit card, and the blind review ledger.
What the release job had to correct before it could ship
Stated plainly, because it was a real gate failure and not a formality:
The unit gate failed once, and the failure was host contention — proven, not assumed. The first full build stopped at
npm test(276/277):tests/staging-health.test.jsreported"/timmy/./nested" was accepted. That test spawns 19 servers and gives each 800 ms to exit with a refusal, on a 4-core box that was at load average 6–7 from unrelated agent workloads.- Proven to be contention, not a regression: running the server directly with that value exits 1 immediately with
TIMMY_BASE_PATH must not contain traversal segments— the value is rejected. - Proven to be load-sensitive: the file passes alone repeatedly at lower load and fails at the same assertion when load climbs (
# pass 6 / # fail 0, twice, then# fail 1as load hit 5.5). - The gate was re-run whole, not loosened, skipped, or edited. No deadline was raised, no test was excluded, no source was changed. The entire 12-minute build was re-run from the top and came back green:
npm test277/277.
Completed ticket / PR
- None. No PR was merged in this run, by design. All 7 open PRs conflict with
main(see above) and nothing was force-merged to manufacture a delta. - Local work in progress toward #19 (privacy-safe model metrics) was found uncommitted in the canonical checkout. It was not shipped — it is unreviewed, has no PR, and merging it would have bypassed review. It was preserved intact (snapshot commit
3e2253aon branchtimmy/19-privacy-safe-metrics, plus a patch/tarball copy under/root/timmy-releases/_preserved-local-wip/) andmainwas built clean.
Gates — 29/29 passed
npm test277/277; image ingress refusal; mobile green path; photo-first acceptance; sleek Hermes chat; manual fallback + draft recovery; provisional suggestion state; hard-stool follow-up; vision corpus refusal; abstention quality gate; worker container receipts; worker post-load readiness; longitudinal pattern summary; grounded chat citations; training contribution consent; corpus manifest provenance; reviewer adjudication blind; dataset card; calibration harness; operator backup restore; production runbook commands; portable backup restore; dependency audit (0 high); syntax (node/bash/python); secret scan; forbidden artifacts; demo H.264 720×1280 probe; complete MP4 decode; release delta verified painted on camera (99/99 captions, delta caption 1 line, top 14 px, 42 px band, padBottom 14 px).The build refused to start while a leftover
node server.mjsheld127.0.0.1:4173— the port check that stops a demo from being recorded against a different tree. That listener was an orphaned ad-hoc run (thetimmy-staging.serviceunit has been inactive since August) that host Caddy routestimmy.alexanderwhitestone.comto. It was stopped for the build and restarted afterwards; the public preview answers200again.Artifacts
- source archive
timmy-talking-turd-2026-11-01-9067ce530674.tar.gz— 371,253 bytes — sha256cb5c868359b80e8a462f1fac5c31b7b96225251714a47f8c6cbbffe2144fc4ca - demo video
timmy-talking-turd-2026-11-01-9067ce530674-demo.mp4— 29,482,868 bytes — H.264 720×1280 yuv420p, 25 fps, 477.60 s, no audio track — sha256691deae333c3511766f98d710d089b7b84014b51d13ca8d0914649dfaac9dfb9
Both hashes were re-verified with
sha256sum -c SHA256SUMSafter the build, and the demo was probed for codec, pixel format, dimensions, and duration.Model / profile
Built and recorded by Hermes (profile
default, modeldeepseek-flash). The demo runs the real app from the built commit withTIMMY_VISION_ENABLED=false; vision replies replay the committed CC0 synthetic corpus through the app's real analysis path, and the configured vision profile remains self-hostedSmolVLM2-2.2B-Instruct(pinned GGUF) with the hosted profile as fallback. TheIMAGE INGRESS, REFUSED BY THE SERVERcard is a real HTTP response, not a fixture — but it measures the refusal boundary, not model quality. No live inference appears in the video and no real photo or medical image is used anywhere; every log, note, and photo placeholder on screen is synthetic.Review focus
Watch the first minute: the delta caption, then the device guard, then the server card. The specific thing to check is the honesty of the
scoperow — it should say the probe covers a non-image container while the other ingress controls are asserted in the suite. If that reads as over-claiming, it needs rewording before this path goes public.Known accuracy limitation
Unchanged by this release, and not made better by it. Bristol accuracy is not clinically validated; the calibration receipt still reports
BLOCKEDbecause no candidate model clears every gate. User confirmation remains mandatory everywhere: photo analysis may suggest visible Bristol form, broad color, and image quality only, and never symptoms or a diagnosis. Visible color is not blood. The release packages no model weights and no medical imagery.Downloads
- 7 open PRs were inspected (#79, #65, #64, #63, #62, #61, #60). Each was merge-tested against
-
Timmy review build 2026-10-31 — image ingress hardening (#16)
Pre-ReleaseQuality gates / quality (push) Canceled after 0sreleased this
2026-10-31 12:44:27 +00:00 | 5 commits to main since this releasePrerelease for review — tag
daily-2026-10-31on commit9067ce53067473467d7cc6a0cc681e8ff2054af6(America/New_York release date 2026-10-31, built 2026-10-31T12:36Z).What changed
The upload boundary stops trusting the caller (issue #16, PR #82).
/api/analyzeused to decide whether an upload was a photo from the data-URL MIME type the caller supplied. That claim is now ignored: every decision comes from the bytes, and every limit fails before the provider is asked.Control Module Refusal Container sniffed from magic bytes; a mismatch is refused src/image-ingress.js400 Container walked to its own end marker; trailing payload refused (polyglot) src/image-ingress.js400 JPEG APP1/APP13/COM and PNG eXIf/tEXt/zTXt/iTXt/tIME refused src/image-ingress.js400 Dimensions read from the header; >4096px edge or >16MP refused src/image-ingress.js400 Per-caller sliding-window budget, 12 per 60s, with retry-aftersrc/rate-limit.js429 6MB ceiling and 15s body deadline; a half-read socket is dropped src/http-body.js413 / 408 No bytes, hash, or address in any log line or error body server.mjs+ tests— What a user sees when a file is not a photo is now its own state — “That file isn’t a photo Timmy can read” and “Nothing was uploaded” — instead of the failed-analysis copy. Manual logging stays one tap away. File handling is self-contained enough that this release adds no new dependency.
What the release job had to correct before it could ship
Stated plainly, because each one was a real defect found by a gate rather than by reading:
- PR #82 could not pass the gate it added. The builder runs its browser gates with
TIMMY_VISION_ENABLED=false, and the503 AI analysis is disabledbranch ran before the request budget, body limits, and byte-level guard. Every hostile upload therefore answered 503, the refusal boundary was never exercised, andtest:ingress-capturefailed withexpected 400, saw 503. Fixed by putting refusals in cost order regardless of model availability (commit8ce5bc1on the branch). Verified with vision disabled: hostile bytes →400 not_an_image, a valid synthetic photo →503with the existing copy. - The demo only showed the device guard. The boundary the ticket is actually about — the server deciding from bytes — was never on camera. The recording now lifts its own
/api/analyzeinterceptor for one probe, posts non-image bytes to the running app, and reads the status and copy back out of that response; a 200 fails the recording (commit2019d51). Both new captions are required gates. - That probe then broke the demo's error budget. The recording allows no browser console errors except the ones a scene produces on purpose, and it enumerated only two fail-closed 503s. The budget is now two 503s, one 400, and nothing else (commit
9067ce5). The first full build stopped here instead of shipping a recording that had never exercised the boundary.
The RC attack suite now says GO
Yesterday's release shipped
VERDICT: BLOCKEDon two P0 ingress holds —ingress-polyglot-htmlandingress-garbage-bytesboth reached the model worker where they should have been refused. Re-run against this commit:VERDICT: GO ingress-oversized-body P0 pass ingress-polyglot-html P0 pass ingress-garbage-bytes P0 pass consent-required P0 pass model-output-malformed P0 pass replay-duplicate-request P2 pass flood-concurrency P1 pass log-leak-scan P0 pass deletion-withdrawal P0 covered-elsewhere load: 8 requests · p50 17ms · p95 68ms · errors 0 · abstentions 8Scope note, unchanged from yesterday: the suite is a command line, not a screen in the app, and it is not yet wired into
scripts/build_release.py's mandatory gate list. It is evidence the release job read, and it now reads GO instead of BLOCKED. Wiring it in as a blocking gate belongs with #40, which stays open.Completed ticket / PR
- Issue #16 — Harden image ingress: magic bytes, re-encode, limits, metadata stripping, and rate control — closed by this merge.
- PR #82 merged as
c31d0a5, branch head8ce5bc1. Diff review: 21 files, +1091/−30, code, tests, and docs only — no secrets, model weights, or real medical imagery (the only image-path fixture is a committed text file named.jpg); links a non-human-gate issue (labelsarea/backend,area/privacy,priority/P0,type/security,type/test). - Pre-merge verification on the branch head:
npm test277/277;ingress-capture,ui,photo,mobile-capture,sleek,manual-fallback,provisional,followup,vision-refusal,pattern-summary,grounded-chat,photo-lifecycleall green under the builder's own environment (vision disabled), with served-byte hashes checked against the checkout.
Gates — 29/29 passed
npm test277/277 (up from 244 at the previous release); image ingress refusal; mobile green path; photo-first acceptance; sleek Hermes chat; manual fallback + draft recovery; provisional suggestion state; hard-stool follow-up; vision corpus refusal; abstention quality gate; worker container receipts; worker post-load readiness; longitudinal pattern summary; grounded chat citations; training contribution consent; corpus manifest provenance; reviewer adjudication blind; dataset card; calibration harness; operator backup restore; production runbook commands; portable backup restore; dependency audit (0 high); syntax (node/bash/python); secret scan; forbidden artifacts; demo H.264 720×1280 probe; complete MP4 decode; release delta verified painted on camera (99/99 captions, delta caption 4 lines / padBottom 14 px).Artifacts
- source archive
timmy-talking-turd-2026-10-31-9067ce530674.tar.gz— 371,298 bytes — sha256e84bd1ed3cfb3df7cf51d464d070ab84d4ccce9f8b3977401109a7813d4ce295 - demo video
timmy-talking-turd-2026-10-31-9067ce530674-demo.mp4— 29,041,842 bytes — H.264 720×1280 yuv420p, 25 fps, 457.72 s — sha2562c01253110c2633af549e600ab478035444dacf82f81bdbc7b6c570df457b564
Model / profile
Built and recorded by Hermes (profile
default, modeldeepseek-flash). The demo runs the real app from the built commit withTIMMY_VISION_ENABLED=false; vision replies replay the committed CC0 synthetic corpus through the app's real analysis path, and the configured vision profile remains self-hostedSmolVLM2-2.2B-Instruct(pinned GGUF) with the hosted profile as fallback. The newIMAGE INGRESS, REFUSED BY THE SERVERcard is not a fixture: it is one real HTTP request to the running app, and the card prints the status and copy that response returned. It measures the refusal boundary, not model quality — no live inference appears in the video and no real photo is used anywhere.Demonstrated on camera
A file that only claims to be a photo is refused on the device — nothing is uploaded— the committed text fixture chosen asphoto.jpg, refused before any upload, with manual logging one tap awayThe device guard is not the only one: the same bytes, sent straight to the running server— the demo's own interceptor lifted for one probeIMAGE INGRESS, REFUSED BY THE SERVERcard —status 400 refused,code not_an_image,copy That file is not a JPEG, PNG, or WebP photo.,provider never called,budget counted by the per-device limiter,after the journal, the draft, and manual logging are untouched, and ascoperow naming which cases this probe does not coverRefused at the server too: 400 not_an_image, before any model work
Known accuracy limitation
Unchanged by this release, and not made better by it. Bristol accuracy is not clinically validated; the calibration receipt still reports
BLOCKEDbecause no candidate model clears every gate. User confirmation remains mandatory everywhere: photo analysis may suggest visible Bristol form, broad color, and image quality only, and never symptoms or a diagnosis. Visible color is not blood.On the ingress boundary specifically, the claims are bounded by what was tested: the suite's synthetic attacks and 33 new tests, not every conceivable container or decoder exploit. Residual risks are stated in
docs/PHOTO-DATAFLOW-THREAT-MODEL.mdrather than glossed — no server-side transcode, so unusual-but-valid containers are refused, not repaired; PNG CRCs are not validated; the rate budget is per process, so it does not survive a restart and is not shared across workers.Downloads
- PR #82 could not pass the gate it added. The builder runs its browser gates with
-
Timmy review release 2026-10-30-1bbcc10a356f
Pre-ReleaseQuality gates / quality (push) Canceled after 0sreleased this
2026-10-30 12:38:15 +00:00 | 13 commits to main since this releasePrerelease for review — tag
daily-2026-10-30on commit1bbcc10a356f62d3ab2118081874cbb8ab5e7ea6(America/New_York release date 2026-10-30, built 2026-10-30T12:36Z).What changed
The release candidate is now attacked before anyone is asked to trust it (issue #40, PR #91). Timmy could describe what it does; nothing drove hostile input at the real boundary and recorded what happened. This release adds an executable abuse suite and the rule that turns its observations into a release decision.
scripts/security_attack_suite.mjsstarts the realserver.mjson a free loopback port, points it at a counting stub worker, and drives it with synthetic hostile bytes. Nine cases: oversized body, HTML polyglot dressed asimage/jpeg, undecodable bytes, request without consent, worker answer that breaks the schema, byte-identical replay, concurrent burst, captured-log scan, and deletion (out of surface — counted only whiletests/training-contribution.test.jsexists). It writes a JSON report, prints the verdict, and exits1when blocked.src/release-security-gate.jsowns the verdict so it is testable instead of asserted: nearest-rank percentiles, per-case pass/fail derived from the recorded observation (a partial match fails and names the broken field), and — any failing P0 case blocks RC approval; a required class with no verified evidence blocks it too. A report that still holds image bytes refuses to be written.docs/RC-SECURITY-EVIDENCE.mdstates the procedure, the rule, and the current finding plainly, rather than storing an "all clear" that goes stale on the next commit.- CI regenerates the same evidence on every change (
npm run test:security-attack). - Demo — a
SECURITY ATTACK SUITEcard drawn from a real run of the suite against the real server, printing the verdict it derived instead of a flattering one.
What this run actually found — the headline is a block, not a pass:
VERDICT: BLOCKED · 2 blocking · 1 heldingress-polyglot-htmlP0 fail — an HTML file dressed as a JPEG returns503and reaches the worker (worker 1); the expectation is400with the worker never called.ingress-garbage-bytesP0 fail — undecodable bytes reach the worker the same way.flood-concurrencyP1 held — the concurrent burst is not refused (refused false, 12 worker calls).- Passing as expected: oversized body (
413, worker never called), missing consent (400, worker never called), malformed worker answer (503, provider text not leaked), byte-identical replay, and the captured-log scan (image bytes false). - Load phase: 8 requests, p50 15 ms, p95 19 ms against the stub on loopback.
Bytes are still trusted from the data-URL label, so a hostile file gets through and only fails afterwards on schema. That is a finding against #16 (image ingress hardening, open as PR #82 and PR #63), not a claim that the boundary is now safe.
Scope note, stated plainly: the suite is a command line, not a screen in the app, and the card is a CLI card — this release does not imply UI. It also does not yet run as a gate inside
scripts/build_release.py: the verdict is evidence the release job must read, and it currently reads BLOCKED. Wiring it into the mandatory gate list belongs with #16 landing, not with publishing this harness.Not closed by this release. PR #91 deliberately does not close #40: the ticket's acceptance criteria are "P0 findings block RC approval" (delivered — the gate decides) together with a boundary that survives attack (not delivered — the two ingress holds above). The suite is honest that it is blocked.
Completed ticket / PR
- Issue #40 — Execute security, abuse, load, and medical-image leakage tests (open; suite and blocking rule delivered, ingress holds tracked in #16).
- PR #91
Execute security, abuse, load, and image-leakage tests against the RC (#40)— merged as0bf8230after diff review: 12 files, +782/−3, text and code only (no binaries, model weights, real medical imagery, or secrets; the only data-URI in the diff is the suite's own synthetic 1×1 JPEG used as hostile input), linked to a non-human-gate issue (labelsarea/privacy,area/release,priority/P0,type/security— nohuman-gatelabel). - Follow-up commit
1bbcc10— the first gated build's card renderedhttp undefined · worker 12on the flood case and repeated the bare status on the malformed case; the card now prints only the fields each case recorded (refused false,text leaked false), with a test that fails if the old formatter returns. The unpublished build of0bf8230was discarded and the whole gated build was re-run end to end from1bbcc10. - Pre-merge verification on the PR head
f0a9a73:npm test244/244,test:security-attack8/8 — green before the merge was issued.
Gates — 28/28 passed
npm test244/244; security-attack acceptance 8/8; corpus manifest 8/8; corpus review 21/21; dataset card 14/14; calibration harness 19/19; backup-restore 11/11; production-runbook 9/9; mobile green path; photo-first acceptance; sleek Hermes chat; manual fallback + draft recovery; provisional suggestion state; hard-stool follow-up; vision corpus refusal; abstention quality gate; worker container receipts; worker post-load readiness; longitudinal pattern summary; grounded chat citations; training contribution consent; production backup restore; production runbook commands; portable backup restore; dependency audit (0 high); syntax (node/bash/python); secret scan; forbidden artifacts; demo H.264 720×1280 probe; complete MP4 decode; release delta verified painted on camera (95/95 captions, delta caption 6 lines / padBottom 15 px).Artifacts
- source archive
timmy-talking-turd-2026-10-30-1bbcc10a356f.tar.gz— 357,094 bytes — sha25604552e814171e1da6fe5101369dca5189db334d2fe2ed4de72d368209c240d29 - demo video
timmy-talking-turd-2026-10-30-1bbcc10a356f-demo.mp4— 26,654,756 bytes — H.264 720×1280 yuv420p, 25 fps, 432.16 s — sha256aa6383c3632aa699a46eb42f5381df03aa06bcfe198a9ea4fab32597c277e1cb
Model / profile
Built and recorded by Hermes (profile
default, modeldeepseek-flash). The demo runs the real app from the built commit withTIMMY_VISION_ENABLED=false; vision replies replay the committed CC0 synthetic corpus through the app's real analysis path, and the app's configured vision profile remains self-hostedSmolVLM2-2.2B-Instruct(pinned GGUF) with the hosted profile as fallback. TheSECURITY ATTACK SUITEcard is a real suite run against a realserver.mjs— but the worker behind that server is a counting stub, so the card proves what the HTTP boundary did and whether the model boundary was reached. It measures no model quality. No live model inference appears in the video, no real photo is used anywhere, and every upload shown is synthetic hostile bytes.Demonstrated on camera
SECURITY ATTACK SUITE— subtitlecommand line, not app UI — real server, counted worker callsCOMMAND node scripts/security_attack_suite.mjsTARGET real server · counting stub worker · synthetic bytesOVERSIZED P0 pass · http 413 · worker 0— refused before any provider workPOLYGLOT P0 fail · http 503 · worker 1— the hostile file reached the workerTRUNCATED P0 fail · http 503 · worker 1CONSENT P0 pass · http 400 · worker 0MALFORMED P0 pass · http 503 · text leaked falseREPLAY P2 pass · http 200 · worker 1 · identical trueFLOOD P1 fail · refused false · worker 12LOG P0 pass · image bytes falseDELETION P0 covered-elsewhereLOAD 8 req · p50 15ms · p95 19ms · abstain 8VERDICT BLOCKED · 2 blocking · 1 held, withBLOCKS ingress-polyglot-htmlandBLOCKS ingress-garbage-bytes
Known accuracy limitation
The release candidate is not proven safe by this release. The suite's own verdict is
BLOCKEDon two P0 ingress holds, and the fix is in review at #16 (PR #82 / PR #63), not shipped here. Separately and unchanged: open-weight VLM acceptance is proven, Bristol accuracy is not clinically validated; the calibration receipt still reportsBLOCKEDbecause no candidate model clears every gate. User confirmation remains mandatory everywhere: photo analysis may suggest visible Bristol form, broad color, and image quality only, and never symptoms or a diagnosis. Visible color is not blood.Downloads
-
Timmy daily-2026-10-29 — operator backup, restore, and runbook
Pre-ReleaseQuality gates / quality (push) Canceled after 0sreleased this
2026-10-29 12:46:43 +00:00 | 16 commits to main since this releasePrerelease for review — tag
daily-2026-10-29on commit65a0258bba891a99b0d827fa67b79b36bb70dd11(America/New_York release date 2026-10-29, built 2026-10-29T12:35:27Z).What changed
Operator backup, restore, and production runbook (issue #39, PR #90). The corpus could ingest a consented sample, record its provenance, survive a withdrawal, and publish a card about itself — but nobody could take a copy of the records away, and no operator could bring them back. This release adds both, plus the page that says how.
scripts/operator_backup.py—backuparchives only records:manifest.jsonl(provenance and withdrawal tombstones) and the derived images it points at. Model weights, transient inference images, secrets, logs, symlinks, and oversize files are refused by name, and every skipped path is written into the manifest with its reason. An omission is an auditable decision, not silence.- Every stored member carries a SHA-256, and the archive is reproducible — sorted members, zeroed mtime/uid/gid,
gzipmtime 0 — so two backups of unchanged records are byte-identical. restorefails closed. It verifies every digest, rejects absolute, traversal, link, and unlisted members, refuses a non-empty target, and only then writes. Afterwards it recountsmanifest.jsonland refuses if the counts differ, so a lost tombstone cannot restore quietly.docs/PRODUCTION-RUNBOOK.md— the real topology, the network boundaries (the app's ownHOSTdefault is0.0.0.0; the deployment setsHOST=127.0.0.1from the mode-600 env template; the worker refuses a non-loopback publish), the four secrets and their rotation, retention, and both rollback paths. Its own test parses every documentedpython3 scripts/...command and checks each flag against the real CLI help, so the runbook cannot drift from the tools.- Release gates —
scripts/build_release.pynow runs the 11-case backup suite and the 9-case runbook suite before a release may exist, and requires four new on-camera captions. - Demo — an
OPERATOR BACKUPcard drawn from a real run: the real ingest and revoke commands build a throwaway corpus, one contribution is withdrawn, four decoy files are planted (weight shard, transient upload, env file, log), then the realbackup,verify, andrestorerun and every value on the card is read back from them.
Scope note, stated plainly: the operator backup is a command line, not a screen in the app. This release does not imply a UI. The app holds no server-side journal of its own, so there is no server copy to back up — the recording says so on screen rather than inventing one.
Not closed by this release. PR #90 deliberately does not close #39. Acceptance criterion 3 is "rollback procedure is exercised on staging": the rollback code path is exercised by
tests/staging-deploy.test.pyagainst a rootless fixture, but the live drill on the private staging host is a human gate under #53 and was not run. The runbook states exactly that.Completed ticket / PR
- Issue #39 — Define production topology, secrets, backup, and rollback runbook (open; two of three acceptance criteria delivered, the third is the #53 human gate).
- PR #90
Operator backup, restore, and production runbook (#39)— merged as65a0258after diff review: 9 files, +1170/−4, text and code only (no binaries, model weights, real medical imagery, or secrets; the env file is a credential-free example template), linked to a non-human-gate issue (area/privacy,area/release,priority/P0,type/ops— nohuman-gatelabel). - Pre-merge verification on the PR head
48f0f56:npm test230/230, backup 11/11, runbook 9/9, corpus 8/8, review 21/21, dataset-card 14/14,check:syntax,check:diff— all green before the merge was issued.
Gates — 28/28 passed
npm test230/230; backup-restore 11/11; production-runbook 9/9; corpus manifest 8/8; corpus review 21/21; dataset card 14/14; calibration harness 19/19; mobile green path; photo-first acceptance; sleek Hermes chat; manual fallback + draft recovery; provisional suggestion state; hard-stool follow-up; vision corpus refusal; abstention quality gate; worker container receipts; worker post-load readiness; longitudinal pattern summary; grounded chat citations; training contribution consent; production backup restore; production runbook commands; portable backup restore; dependency audit (0 high); syntax (node/bash/python); secret scan; forbidden artifacts; demo H.264 720×1280 probe; complete MP4 decode; release delta verified painted on camera (91/91 captions, minimum bottom padding 14px, delta caption 9 lines / 139px / padBottom 15).Build note: the first gated build of this release ran without
TIMMY_RELEASE_DELTAset, so the manifest carried the builder's fallback string "Verified rebuild of the current user-visible feature path — no new feature is claimed." That claim is false for this commit, so the build was moved to_superseded-2026-10-29-65a0258bba89-false-no-new-feature-deltaand never published; the whole gated build was re-run end to end with the real delta and passed 28/28. No gate was skipped, loosened, or interpreted away.Operating note:
timmy-app.serviceoccupies127.0.0.1:4173, the port the gated build binds. Production was stopped for the build and restored by atrap-guarded script on both paths; it reportedactiveand/api/healthz200 within two polls after each run. The build refuses to start over a live listener and hash-compares served files against the tree under test.Artifacts
- source archive
timmy-talking-turd-2026-10-29-65a0258bba89.tar.gz— 343,940 bytes — sha256560f0fb46d499d26eb5ef29edd463fedd244c497f896323ad28336cadec71b2a - demo video
timmy-talking-turd-2026-10-29-65a0258bba89-demo.mp4— 27,064,343 bytes — H.264 720×1280 yuv420p, 25 fps, 425.24 s — sha2560ef579708a7b59d828798f80750f72a519d2ff4eabd16e2c458572c07bfa205c
Model / profile
Recorded against the real app running from the built commit with
TIMMY_VISION_ENABLED=false; vision replies replay the committed synthetic corpus through the app's real analysis path. TheOPERATOR BACKUPcard is a realscripts/operator_backup.pyrun over a corpus built on camera by the real ingest and revoke commands from CC0 synthetic fixtures — no model backed anything up or verified anything here, and the card says so on screen. No live model inference appears in the video and no real photo is used anywhere.Demonstrated on camera
OPERATOR BACKUP— subtitlecommand line, not app UI — real run, synthetic corpusCOMMAND python3 scripts/operator_backup.py backup --source srv-timmy-corpusSTORED 2 members · …jpg + manifest.jsonlRECORDS samples 2 · tombstones 1 · active 1— the withdrawal is carried, not droppedREFUSED model-weights 1 · transient-inference 1/REFUSED secret 1 · log 1REASON every skipped path is named in the manifest, not silently droppedDIGEST sha256 e646e7a8066d… · reproducible (sorted, mtime 0)VERIFY ok · 2 members re-hashed against the manifestRESTORE ok · samples 2 · tombstones 1 · active 1/MANIFEST returned byte-for-byte, tombstone includedSERVER the app holds no journal of its own — there is no server copy to back up
Known accuracy limitation
Open-weight VLM acceptance is proven; Bristol accuracy is not clinically validated. The calibration receipt still reports
BLOCKEDbecause no candidate model clears every gate (issue #31). This release publishes a survivable copy of the record, not a measured classifier. User confirmation remains mandatory everywhere: photo analysis may suggest visible Bristol form, broad color, and image quality only, and never symptoms or a diagnosis. Visible color is not blood.Downloads
-
Timmy daily 2026-10-28 — dataset card and labeling guide
Pre-ReleaseQuality gates / quality (push) Canceled after 0sreleased this
2026-10-28 13:03:54 +00:00 | 18 commits to main since this releasePrerelease for review — tag
daily-2026-10-28on commit7fe1901703590f59b68f6f394038c628297bd9c4(America/New_York release date 2026-10-28, built 2026-10-28T12:59:25Z).What changed
Published dataset card and labeling guide (issue #27, PR #89). The corpus could ingest a consented sample, record its provenance, and survive a withdrawal, but no stranger could read what the corpus is. This release publishes the page and the rulebook, both generated from real records:
scripts/dataset_card.py→docs/DATASET_CARD.md— every count is read back frommanifest.jsonl; every fact the pipeline does not collect (devices, contexts, licenses) prints aspending — not collected by ingestinstead of an estimate.- Leakage is refused, not documented. The generator refuses to render a card when one contributor appears in two splits, or when a recorded split is not the one the salted subject key derives (
<205train,<230validation, else test). A withdrawn sample contributes no label to any balance table, and no subject key ever reaches the page. - The published page cannot drift.
--checkfails when the committed card is not the generator's own output, so the card cannot quietly become a hand-written document. docs/LABELING_GUIDE.md— the reviewer's three visible fields (Bristol form, broad visible color, photo quality), stated where it is enforced: visible color is not blood, nothing is diagnosed, and a model suggestion is provisional input to a human decision rather than the label.- Release gate —
scripts/build_release.pyruns the 14-case dataset-card suite before a release may exist, requires four new on-camera captions, and recordsdataset_card_generatedin the manifest. - Demo — a
DATASET CARDcard drawn from a real run: the real ingest command builds a throwaway corpus from synthetic fixtures, one contribution is withdrawn, the card is published, a planted contributor is refused, and the committed page is re-checked.
Demo-harness legibility fix (
7fe1901, follows the release day's own full-resolution QC). The release-delta caption is paragraph length; rendered at 94% opacity over the running app, app text bled through it and its last line sat flush against the bar's edge — the one caption a reviewer most needs was the hardest to read. The bar is now opaque, long copy steps down to 10.5/9.5px, andcaption()measures the last line box with aRangeand fails the run when bottom padding is under 3px. Every caption in this recording now reportspadBottom ≥ 14(the delta caption: 11 lines, 164px tall, padBottom 15) in the caption receipt.Scope note, stated plainly: the dataset card is a generated document, not a screen in the app. This release does not imply one. The recording runs the real generator against synthetic fixtures and shows its actual output, including the refusal.
Completed ticket / PR
- Issue #27 — Publish dataset card, labeling guide, and contributor-level split policy (closed by this release).
- PR #89
Publish the dataset card and labeling guide from the manifest (#27)— merged asd7026c0after diff review: 11 files, +1014/−3, text only (no binaries, weights, medical imagery, or secrets),Closes #27linked to a non-human-gate issue (type/docs,area/data,priority/P1,state:review— no human gate). - Commit
7fe1901— demo caption legibility fix above, onmainafter the merge.
Gates — 26/26 passed
npm test229/229,tests/dataset-card.test.py14/14, corpus manifest 8/8, corpus review 21/21, calibration harness 19/19, mobile green path, photo-first acceptance, sleek Hermes chat, manual fallback + draft recovery, provisional suggestion state, hard-stool follow-up, vision corpus refusal, abstention quality gate, worker container receipts, worker post-load readiness, longitudinal pattern summary, grounded chat citations, training contribution consent, corpus manifest provenance, reviewer/adjudication blindness, dataset card generation, calibration harness gates, portable backup restore, dependency audit (0 high), syntax (node/bash/python), secret scan, forbidden artifacts, demo H.264 720×1280 probe, complete MP4 decode, release delta verified painted on camera (87/87 captions).Build note: the first gated build of this release carried the fallback "no source change" delta, so it was discarded rather than published; the second exposed the cramped delta caption in full-resolution QC, which produced
7fe1901; the whole gated build was then re-run end to end and passed 26/26. No gate was skipped, loosened, or interpreted away.Artifacts
- source archive
timmy-talking-turd-2026-10-28-7fe190170359.tar.gz— 326,257 bytes — sha256b9320551a61ba2cdbcb243bae01cca7b4b120be0d4f807a13fea0435cc24ed28 - demo video
timmy-talking-turd-2026-10-28-7fe190170359-demo.mp4— 25,131,882 bytes — H.264 720×1280 yuv420p, 25 fps, 391.6 s — sha25667c248e06bca5f18b226d03e91c0fee8aed69ab1d44e67f51710679c16e1f45c
Model / profile
Recorded against the real app running from the built commit with
TIMMY_VISION_ENABLED=false; vision replies replay the committed synthetic corpus through the app's real analysis path. The dataset card is a realscripts/dataset_card.pyrun over a corpus built on camera by the real ingest command from CC0 synthetic fixtures — no model labelled anything in it, and the card says so on screen. No live model inference appears in the video and no real photo is used anywhere.Demonstrated on camera
DATASET CARD—COMMAND python3 scripts/dataset_card.py --corpus <tmp> --out DATASET_CARD.mdCORPUS 2 active · 2 contributors · 1 withdrawn/SPLITS train 2c/2s · validation 0c/0s · test 0c/0sCLASS Type 1 1 · Type 3 0 · Type 4 1 · withdrawn label excluded— the withdrawn label is not countedPENDING devices · contexts · licenses — not collected, not estimatedREFUSED refusing to render a dataset card: contributor 516990fea008961e leaks across test, train→CARD not writtenCOMMITTED the published page is this generator's own output(--check)DATA synthetic fixtures · no model labelled anything here
Known accuracy limitation
Open-weight VLM acceptance is proven; Bristol accuracy is not clinically validated. The calibration receipt still reports
BLOCKEDbecause there is no candidate model yet (issue #31). This release publishes a self-describing corpus, not a measured classifier. User confirmation remains mandatory everywhere: photo analysis may suggest visible Bristol form, broad color, and image quality only, and never symptoms or a diagnosis. Visible color is not blood, in the app and in the labeling guide alike.Downloads
-
Timmy daily-2026-10-27 — blind reviewer and adjudication ledger
Pre-ReleaseQuality gates / quality (push) Canceled after 0sreleased this
2026-10-27 12:39:09 +00:00 | 21 commits to main since this releasePrerelease for review — tag
daily-2026-10-27on commitb191d0427ddbb19f982458eeaa093aaaee31a68e(America/New_York release date 2026-10-27, built 2026-10-27T12:23:57Z).What changed
Blind reviewer and adjudication tool for corpus labels (issue #26, PR #88). The corpus manifest already said what was ingested and whether the person still allows it. This release adds the labelling control the photo data-flow threat model names, as a command line with no app screen:
scripts/review_corpus.py— a review queue that shows the proposal (Type 4 · brown · good) and withholds every model field. Blindness is structural:build_queue()takes no predictions at all, andreveal()refuses with "stays hidden until a review is committed" while appending nothing.- Disagreement is preserved, not smoothed — a review is one append-only line carrying the proposal it judged beside the human decision; a correction must actually change the proposal. An adjudication is a second reviewer's line that names the record it resolves, the overruled review stays in the ledger byte for byte, nobody adjudicates their own review, and a contribution adjudicates once.
- Withdrawal outranks everything —
active_records()gates the queue, reveal, adjudicate, and trainable paths, so a revoked contribution refuses with "was withdrawn and cannot be reviewed, revealed, or trained on".trainable_records()also drops unreviewed and excluded samples, so an unreviewed proposal is never training data. tests/corpus-review.test.py— 21 cases written first and watched fail before the module existed, then one behaviour slice at a time. Two real defects were found by tests, not review: a withdrawn contribution reported "already reviewed" because the duplicate check ran before the lifecycle check, andload_predictionscrashed reading a pretty-printed answer file line by line.- Release-level gate —
scripts/build_release.pynow runs the review suite as a release gate (like the corpus and calibration CLIs) and requires four new on-camera captions, so a future release cannot drop blindness or the withdrawal refusal silently. - Demo — the recording runs the real
queue → reveal (refused) → review → reveal → adjudicate → trainable → revokesequence against the committed synthetic sample and draws aREVIEW LEDGERcard from that actual output, labelledsynthetic answer file · no model was measured.
Scope note, stated plainly: the review tool is a command line, not a screen in the app. This release does not imply a reviewer screen; the recording runs the real commands and shows their real output.
Merge hygiene: PR #88's head failed the repo's own
check:diffgate on a trailing blank line inscripts/review_corpus.py. Fixed with a whitespace-only commit (d54665d) before merge, thencheck:diffand all suites re-verified — the gate was satisfied, not skipped.Completed ticket / PR
- Issue #26 — Build reviewer/adjudication tool for Bristol, color, quality, and exclusions (closed by this release).
- PR #88
Blind reviewer and adjudication tool for corpus labels (#26)— merged asb191d04after diff review: 10 files, +1296/−109, text only (no binaries, weights, medical imagery, or secrets),Closes #26linked to a non-human-gate issue.
Gates — 25/25 passed
npm test227/227,tests/corpus-review.test.py21/21, corpus manifest 8/8, calibration harness 19/19, mobile green path, photo-first acceptance, sleek Hermes chat, manual fallback + draft recovery, provisional suggestion state, hard-stool follow-up, vision corpus refusal, abstention quality gate, worker container receipts, worker post-load readiness, longitudinal pattern summary, grounded chat citations, training contribution consent, corpus manifest provenance, reviewer/adjudication blindness, calibration harness gates, portable backup restore, dependency audit (0 high), syntax (node/bash/python), secret scan, forbidden artifacts, demo H.264 720×1280 probe, complete MP4 decode, release delta verified painted on camera (83/83 captions).Gate note: the first gated build failed
npm testonce atmalformed and escaping base paths are rejected at startup— that test spawns 19 servers with a fixed 800 ms deadline on a busy 4-core host (load ~11). Proven host contention rather than a regression: the file passed 5/5 isolated runs, and the whole gated build was re-run end to end and passed with 227/227 and all 25 gates. The gate was re-run, not loosened or bypassed.Artifacts
- source archive
timmy-talking-turd-2026-10-27-b191d0427ddb.tar.gz— 311,861 bytes — sha256d40aa477b0aa799099d28291e264594d51f1ef68efdca6338137473dcb6e6ffa - demo video
timmy-talking-turd-2026-10-27-b191d0427ddb-demo.mp4— 25,456,205 bytes — H.264 720×1280 yuv420p, 25 fps, 408.2 s — sha2564c3601c830dfb81261a0ffcba75778e753777807212d9c61ed50dd5384899470
Model / profile
Recorded against the real app running from the built commit with
TIMMY_VISION_ENABLED=false; vision replies replay the committed synthetic corpus through the app's real analysis path. The review card is a realscripts/review_corpus.pyrun over the committed synthetic sample plus a synthetic answer file (no model was measuredon screen). No live model inference appears in the video, no real photo is used anywhere, and the fixtures are CC0 synthetic images generated byscripts/build_vision_corpus.py.Demonstrated on camera
- queue:
1 pending · proposal Type 4 · brown · good,model answer withheld from the reviewer - reveal before any review: refused, answer stays hidden
- correction committed (
5 / green / fair) with the original proposal kept beside it - model answer revealed (
Type 6 · brown · good · confidence 0.71) →DISAGREE bristolType · color · quality - second reviewer adjudicates:
2 lines · resolves record 1 · reviewer-a's review untouched trainable samples: 1→ after consent withdrawal:queue 0 · trainable 0and review/reveal/train refused
Known accuracy limitation
Open-weight VLM acceptance is proven; Bristol accuracy is not clinically validated. The calibration/abstention release receipt still reports
BLOCKEDbecause there is no candidate model until issue #31 — this release adds the reviewer control, not a measured classifier. User confirmation remains mandatory everywhere: photo analysis may suggest visible Bristol form, broad color, and image quality only, and never symptoms or a diagnosis.Downloads
-
Timmy the Talking Turd — daily-2026-10-26 — quality-gated abstention
Pre-ReleaseQuality gates / quality (push) Failing after 5m13sreleased this
2026-10-26 12:57:38 +00:00 | 24 commits to main since this releasePrerelease for review — tag
daily-2026-10-26on commit2533f83af3504a8aeeb2a56e1d0df1756e6b6a31.What changed
Quality-gated abstention policy (issue #32, PR #87). Confidence used to be the only thing between a model answer and a prefilled journal entry, so a confident reading of a blurry photo could still be offered as fact. Photo quality is now part of the policy:
src/abstention-policy.js— one versioned policy (version: 1, confidence floor0.55, accepted qualitygood|fair) plus the four fixed refusal strings, so the parser, the corpus, and the browser cannot drift into three explanations of the same refusal.imageQuality: "poor"abstains withlow_qualityhowever confident the reply looked.src/analysis.js—parseVisionResponse(raw, policy)reads its limits and copy from that module; a caller can tighten the policy without editing the parser, which is how the tests prove the parser carries no hidden limits of its own.- Release-level gate —
evaluatePrefillGate(report)turns the #29 calibration report into one decision:GOplus a recommended candidate whose threshold was selected onvalidationand scored on the held-outtestsplit, or prefill is blocked with every reason named. A missing report is a block, never a default allow.scripts/abstention_policy_receipt.mjsexposes that as a machine receipt (exit 0 authorised / 3 blocked / 2 unreadable). - Corpus — new synthetic case
poor-quality-confident: a 0.94-confidence answer about the committed motion-blur fixture, expected to abstain withlow_quality. Existing CC0 fixture reused; no camera, no human subject, no new imagery.
Scope note, stated plainly: the per-photo quality gate is live in the app and is what the recording shows. The release-level receipt is a command-line gate and is not yet wired as a hard gate in
scripts/build_release.py— it prints the honest current state,BLOCKED, and today that is correct: there is no candidate model until #31.Run against the committed synthetic predictions, the receipt says:
policy: version 1 · confidence ≥ 0.55 · quality good|fair calibration: verdict BLOCKED · model none prefill: BLOCKED — calibration-verdict-not-go, no-recommended-calibrationCompleted ticket / PR
- Issue #32 — Calibrate confidence and define the production abstention policy (closed by this release). Criterion 2 ("held-out precision reaches the agreed gate or release is blocked") cannot be satisfied yet — there is no real candidate until #31 — so the gate reports
BLOCKEDinstead of passing on synthetic numbers, which is the honest half of that criterion. - PR #87
Gate visual prefill on a versioned abstention policy (#32)— merged as2533f83after full diff review: 13 files, +428/−14, no secrets, no weights, no real imagery.
Gates — all 24 passed
npm test226/226,tests/calibration-harness.test.py19/19,tests/corpus-manifest.test.py8/8, mobile green path, photo-first acceptance, sleek Hermes chat, manual fallback + draft recovery, provisional suggestion state, hard-stool follow-up, vision corpus refusal, abstention quality gate, worker container receipts, worker post-load readiness, longitudinal pattern summary, grounded chat citations, training contribution consent, corpus manifest provenance, calibration harness gates, portable backup restore, dependency audit (0 high), syntax (node/bash/python), secret scan, forbidden artifacts, demo H.264 720×1280 probe, complete MP4 decode, release delta verified painted on camera (78/78 captions painted).The new caption is also a builder-required caption now, so a future release cannot drop the quality gate silently.
Artifacts
- source archive
timmy-talking-turd-2026-10-26-2533f83af350.tar.gz— 297,679 bytes — sha2567af46dc5840da8751809f6e307a4175e97dd9e0150fe34c78eff4841b637a81e - demo video
timmy-talking-turd-2026-10-26-2533f83af350-demo.mp4— 22,232,190 bytes — H.264 720×1280 yuv420p, 350.3 s — sha2569cd0b7374a7f88e5c1950de7d2d1254c3eac184fb70a24dba3e86220a45e9a6b
Model / profile
Recorded against the real app running from the built commit with
TIMMY_VISION_ENABLED=false; the demo replays the committed corpus provider responses through the app's real analysis path, which is why a 0.94-confidence "poor quality" reply can be exercised deterministically. No live model inference appears in the video, no real photo is used anywhere, and the visible fixtures are synthetic (CC0, generated byscripts/build_vision_corpus.py).Demonstrated on camera
The whole existing user path (photo log with provisional suggestion, hard-stool follow-up and red-flag escalation, vision-offline fallback, corpus refusals and abstentions, explainable pattern summary with citations, grounded chat with source refs, private photo lifecycle, opt-in training contribution with withdrawal, versioned backup/restore/refusal, calibration card, corpus audit card) plus this release's new scene at ~1:59:
- the synthetic blur fixture is chosen, consent is ticked, analysis runs;
- the suggestion sheet says "No confident match." and "The photo is too blurry or poorly lit to suggest a form safely." — the provider answer was confident and is discarded on quality;
- the only ways forward are "Choose the form yourself" and "Try another photo": nothing is prefilled, no Bristol form appears, and the demo asserts that absence in the browser rather than only claiming it in a caption.
Known accuracy limitation
Form, broad colour, and image-quality suggestions remain observable-field assistance, never diagnosis; user confirmation stays mandatory and a red flag still escalates in the app's own fixed words. Bristol accuracy is not clinically validated, the calibration fixtures are synthetic, and the release-level prefill gate reports
BLOCKEDbecause no candidate has cleared the #29 gates yet. Open-weight VLM acceptance is proven; clinical accuracy is not.The source package excludes model weights, medical-image corpora, raw research pages, generated screenshots, and video production assets.
Downloads
-
Timmy the Talking Turd — daily-2026-10-25
Pre-ReleaseQuality gates / quality (push) Successful in 12m51sreleased this
2026-10-25 12:42:05 +00:00 | 26 commits to main since this releasePrerelease for review — tag
daily-2026-10-25on commit2a10a20518603325a85740d0259d4b20d788862b.What changed
Contributor-held-out evaluation harness (issue #29, PR #86).
scripts/calibration_report.pyis the comparison and release-gate tool the ROADMAP gates were written for:- Reports macro-F1, per-class precision/recall, a
truth->predictedconfusion matrix, expected calibration error over ten equal-width confidence bins, coverage, and precision-at-coverage — abstentions split intono-suggestionandlow-confidence. - Splits are contributor-held-out and reproducible: a subject key must derive from the existing ingestion rule (first byte of the pseudonymous digest: <205 train, <230 validation, else test), and one contributor appearing in two splits is refused before anything is scored.
- Compares candidate fixtures under three ids: one threshold per candidate is chosen on validation only, then the candidate is scored on test.
- Exit codes: 0 =
GO, 3 =BLOCKED, 2 = refused (malformed, leaky, or unreproducible input — nothing scored, no report written). - Classes whose every photo abstained are named in
excludedClassesinstead of counting as silent zeros, so abstention cannot inflate or deflate the score unnoticed.
Gates are the ROADMAP values, inclusive:
macroF1 >= 0.80,precision@coverage >= 0.90,ECE <= 0.05.Scope note, stated plainly: this is a command-line release gate, not a screen in the app. No app UI changed and this release does not imply a new app feature. The recorded demo runs the real CLI against the committed synthetic fixtures and shows the receipt it wrote.
A follow-on commit (
2a10a20) fixes the demo card layout only — each candidate's name now gets its own row, sospecialist-candidateis no longer broken at its hyphen. No app, server, or harness behaviour changes in that commit.Gates — all 23 passed
npm test219/219,tests/calibration-harness.test.py19/19,tests/corpus-manifest.test.py8/8, mobile green path, photo-first acceptance, sleek Hermes chat, manual fallback + draft recovery, provisional suggestion state, hard-stool follow-up, vision corpus refusal, worker container receipts, worker post-load readiness, longitudinal pattern summary, grounded chat citations, training contribution consent, corpus manifest provenance, calibration harness gates, portable backup restore, dependency audit (0 high), syntax (node/bash/python), secret scan, forbidden artifacts, demo H.264 720x1280 probe, complete MP4 decode, release delta verified painted on camera (77/77 captions painted).The harness suite is a release gate, not just CI:
scripts/build_release.pyrunstests/calibration-harness.test.pybefore anything is built.Artifacts
- source archive
timmy-talking-turd-2026-10-25-2a10a2051860.tar.gz— 291,771 bytes — sha2566ad4329d363df65e37a5bd2b4bb8a8660039ad3e516da9402bef62b4d170a920 - demo video
timmy-talking-turd-2026-10-25-2a10a2051860-demo.mp4— 22,983,816 bytes — H.264 720x1280 yuv420p, 349.7 s — sha256524d02edc87db89dbc5a0f09934af6586d0526c3fd1645ba660f152304dcf02a
Model / profile
Recorded against the real app running from the built commit with
TIMMY_VISION_ENABLED=false; the demo uses the app's deterministic local profile for vision and chat. No live model inference appears in the video and no real photo is used anywhere.Demonstrated on camera
The whole existing user path (photo log with provisional suggestion, hard-stool follow-up and red-flag escalation, vision-offline fallback, corpus refusals and abstentions, explainable pattern summary with citations, grounded chat with source refs, private photo lifecycle, opt-in training contribution with withdrawal, versioned backup/restore/refusal) plus this release's new card:
- three synthetic candidates, each failing a different gate →
BLOCKED, nobody recommended; - a fixture engineered to clear every gate →
GO, labelled "fixture, not a result"; - one contributor spread across
validationandtest→ refused, exit 2, "leaks across test, validation: splits must be contributor-held-out".
Card values read back from the run: frozen-encoder thr 0.57 / F1 1.000 / P@C 1.000 / cov 0.71 / ECE 0.376 (fails ece); specialist-candidate thr 0.00 / F1 0.810 / P@C 0.857 / cov 1.00 / ECE 0.036 (fails precisionAtCoverage); vlm-bootstrap thr 0.58 / F1 0.100 / P@C 0.250 / cov 0.57 / ECE 0.535 (fails macroF1, precisionAtCoverage, ece).
Known accuracy limitation
The fixture scores are invented to exercise the arithmetic: no model was measured against these gates, and the harness is not evidence about classifier accuracy. Form, broad colour, and image-quality suggestions remain observable-field assistance, never diagnosis; user confirmation stays mandatory; a red flag escalates in the app's own fixed words. Bristol accuracy is not clinically validated on this corpus.
The source package excludes model weights, medical-image corpora, raw research pages, generated screenshots, and video production assets.
Downloads
- Reports macro-F1, per-class precision/recall, a
-
Timmy daily-2026-10-24 — auditable corpus manifest
Pre-ReleaseQuality gates / quality (push) Successful in 12m45sreleased this
2026-10-24 13:03:55 +00:00 | 29 commits to main since this releasePrerelease for review — tag
daily-2026-10-24on commit9f697307805903e3074cd44e7d9d4b9430278c5c.What changed
Auditable corpus pipeline (issue #25, PR #85). The training-ingest script is now a corpus pipeline backed by
scripts/corpus_manifest.py:- Every ingested sample records consent (version + recorded-at), source kind and the raw-input digest — the caller's file path is never stored — both the raw and derived hashes, the derivation block (
timmy-ingest/2, jpeg, ≤1200 px, metadata stripped), the human review status, the split, and its lifecycle state, appended to an immutablemanifest.jsonl. --revoke <contribution-id> --reason …deletes the derived image and any raw pre-derivation copy (matched by digest, never by a path), keeps the original record as its own history, and appends a tombstone naming every removed artifact by contribution id + digest while droppinglabels,subjectKey, andrelativePath.active_records()filters anything tombstoned, so a trainer reading through the manifest cannot train on withdrawn data. Deletion is path-guarded against escaping the dataset root.- Illegal lifecycle moves are refused before any byte is written: a duplicate contribution id, a second label set for the same derived image, revoking twice, revoking an unknown contribution, reviving a revoked one, and revocation without a reason.
Scope note, stated plainly: this is a command-line / data-pipeline feature. There is no corpus screen in the app and this release does not imply one. The recorded demo runs the real
--inputand--revokecommands against the committed synthetic fixture and shows their actual output.Gates — all 22 passed
npm test217/217,tests/corpus-manifest.test.py8/8, mobile green path, photo-first acceptance, sleek Hermes chat, manual fallback + draft recovery, provisional suggestion state, hard-stool follow-up, vision corpus refusal, worker container receipts, worker post-load readiness, longitudinal pattern summary, grounded chat citations, training contribution consent, corpus manifest provenance, portable backup restore, dependency audit (0 high), syntax (node/bash/python), secret scan, forbidden artifacts, demo H.264 720×1280 probe, complete MP4 decode, release delta verified painted on camera.The corpus suite is now a release gate, not just CI:
scripts/build_release.pyruns it before anything is built.Artifacts
- source archive
timmy-talking-turd-2026-10-24-9f6973078059.tar.gz— 276,132 bytes — SHA-256e0f289e0e271b1639151d0984a5b5e026d011bedd2495194e65f833b6dc22390 - demo video
timmy-talking-turd-2026-10-24-9f6973078059-demo.mp4— 20,828,543 bytes — 333 s (332.72 s measured) — 720×1280 H.264 yuv420p — SHA-2565e0cd5010ffd6a3d7191e371d490e28bc86c6454b3d3aec51bec3cc6ebf5d5ff
Model / profile
Self-hosted profile
selfhost, model SmolVLM2-2.2B-Instruct. The recording runs withTIMMY_VISION_ENABLED=false: vision suggestions and refusals are exercised through the app's real client rendering, and the refusal cases replay the committed CC0 synthetic corpus through the real analysis module with the server response stubbed deterministically. No live model inference appears in the video, and no real photo is used anywhere.Demonstrated on camera
The whole existing user path (photo log with provisional suggestion, hard-stool follow-up and red-flag escalation, vision-offline fallback, corpus refusals and abstentions, explainable pattern summary with citations, grounded chat with source refs, private photo lifecycle, opt-in training contribution with withdrawal, versioned backup/restore/refusal) plus this release's new card:
ingestwrites one active sample record: consent version, source digest, both hashes, derivation, review, lifecycle — path never stored;--revokedeletesimages/<digest>.jpgfrom disk, keeps the original record, appends a tombstone that drops labels and the subject key, and leaves a trainer with 0 active samples;- a second
--revokeof the same contribution is refused — "contribution demo-contrib-0001-abcd is already revoked" — and the manifest stays at 2 lines.
Known accuracy limitation
Form, broad colour, and image-quality suggestions are observable-field assistance, never diagnosis: user confirmation stays mandatory, symptom answers and urgency are never inferred from the photo, and a red flag escalates in the app's own fixed words. Open-weight VLM acceptance is proven; Bristol accuracy is not clinically validated on this corpus.
The source package excludes model weights, medical-image corpora, raw research pages, generated screenshots, and video production assets.
Downloads
- Every ingested sample records consent (version + recorded-at), source kind and the raw-input digest — the caller's file path is never stored — both the raw and derived hashes, the derivation block (