• Timmy review build 2026-11-02 — privacy-safe operator health counters (#19)
    Quality gates / quality (push) Waiting to run
    Pre-Release

    rockachopa released this 2026-11-02 13:29:24 +00:00 | 0 commits to main since this release

    Prerelease for review — tag daily-2026-11-02 on commit 664a9099571fbbf185ab1e94c45d11502458f490 (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: main moved from 9067ce5 (what daily-2026-11-01 shipped) to 664a909.

    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/metrics is an operator surface, not a public one. With TIMMY_METRICS_TOKEN unset 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.mjs reads that snapshot without jq. 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 counters
    

    Those two ingress_refused failures 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 commit 48979bf, Closes #19 (auto-closed 2026-11-02T12:07Z). Reviewed before merge: focused 18-file diff, no secrets, no model weights, no medical imagery; npm test 298/298, npm run test:metrics-report 8/8, check:syntax, check:diff, and test:ui in 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 printed failure ingress_refused0: the longest failure label exactly filled the 22-character column, so label and count rendered as one broken token. LABEL_WIDTH is 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 REQUESTS and all eight FAILURE … 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 wrapped failures line), 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 test 299/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 → both OK; 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 — sha256 038e2340380541e8f6707d438c1cb988c42271645bd3f06322f212462bb15e8a
    • 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 — sha256 14aa6a89d6d6ef858b6afd33e98df11814c4ab19edef8cff66bba82f8f87e1dc

    Model / profile

    Built and recorded by Hermes (profile default, model deepseek-flash). The demo runs the real app from the built commit with TIMMY_VISION_ENABLED=false; the configured vision profile remains self-hosted SmolVLM2-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 shows readiness disabled and 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.com routes to 127.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 from 664a909 and the public URL answers 200 (/api/healthz too). 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 → 200 snapshot.

    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 model should match the two hostile uploads the video showed earlier, and READINESS: disabled should 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 BLOCKED because no candidate model clears every gate. queue.inFlight is the app's own admission depth for POST /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
  • Timmy review release 2026-11-01 (verified rebuild; no source change)
    Quality gates / quality (push) Canceled after 0s
    Pre-Release

    rockachopa released this 2026-11-01 12:52:42 +00:00 | 5 commits to main since this release

    Prerelease for review — tag daily-2026-11-01 on commit 9067ce53067473467d7cc6a0cc681e8ff2054af6 (America/New_York release date 2026-11-01, built 2026-11-01T12:34Z).

    This is a verified rebuild; no source change. main is at the same commit daily-2026-10-31 shipped, 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 main with git merge-tree; all 7 conflict. Gitea's own mergeable field agreed (false on 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 a scope row 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.js reported "/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 1 as 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 test 277/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 3e2253a on branch timmy/19-privacy-safe-metrics, plus a patch/tarball copy under /root/timmy-releases/_preserved-local-wip/) and main was built clean.

    Gates — 29/29 passed

    npm test 277/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.mjs held 127.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 (the timmy-staging.service unit has been inactive since August) that host Caddy routes timmy.alexanderwhitestone.com to. It was stopped for the build and restarted afterwards; the public preview answers 200 again.

    Artifacts

    • source archive timmy-talking-turd-2026-11-01-9067ce530674.tar.gz — 371,253 bytes — sha256 cb5c868359b80e8a462f1fac5c31b7b96225251714a47f8c6cbbffe2144fc4ca
    • 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 — sha256 691deae333c3511766f98d710d089b7b84014b51d13ca8d0914649dfaac9dfb9

    Both hashes were re-verified with sha256sum -c SHA256SUMS after the build, and the demo was probed for codec, pixel format, dimensions, and duration.

    Model / profile

    Built and recorded by Hermes (profile default, model deepseek-flash). The demo runs the real app from the built commit with TIMMY_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-hosted SmolVLM2-2.2B-Instruct (pinned GGUF) with the hosted profile as fallback. The IMAGE INGRESS, REFUSED BY THE SERVER card 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 scope row — 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 BLOCKED because 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
  • Timmy review build 2026-10-31 — image ingress hardening (#16)
    Quality gates / quality (push) Canceled after 0s
    Pre-Release

    rockachopa released this 2026-10-31 12:44:27 +00:00 | 5 commits to main since this release

    Prerelease for review — tag daily-2026-10-31 on commit 9067ce53067473467d7cc6a0cc681e8ff2054af6 (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/analyze used 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.js 400
    Container walked to its own end marker; trailing payload refused (polyglot) src/image-ingress.js 400
    JPEG APP1/APP13/COM and PNG eXIf/tEXt/zTXt/iTXt/tIME refused src/image-ingress.js 400
    Dimensions read from the header; >4096px edge or >16MP refused src/image-ingress.js 400
    Per-caller sliding-window budget, 12 per 60s, with retry-after src/rate-limit.js 429
    6MB ceiling and 15s body deadline; a half-read socket is dropped src/http-body.js 413 / 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:

    1. PR #82 could not pass the gate it added. The builder runs its browser gates with TIMMY_VISION_ENABLED=false, and the 503 AI analysis is disabled branch ran before the request budget, body limits, and byte-level guard. Every hostile upload therefore answered 503, the refusal boundary was never exercised, and test:ingress-capture failed with expected 400, saw 503. Fixed by putting refusals in cost order regardless of model availability (commit 8ce5bc1 on the branch). Verified with vision disabled: hostile bytes → 400 not_an_image, a valid synthetic photo → 503 with the existing copy.
    2. 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/analyze interceptor 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 (commit 2019d51). Both new captions are required gates.
    3. 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: BLOCKED on two P0 ingress holds — ingress-polyglot-html and ingress-garbage-bytes both 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 8
    

    Scope 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 head 8ce5bc1. 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 (labels area/backend, area/privacy, priority/P0, type/security, type/test).
    • Pre-merge verification on the branch head: npm test 277/277; ingress-capture, ui, photo, mobile-capture, sleek, manual-fallback, provisional, followup, vision-refusal, pattern-summary, grounded-chat, photo-lifecycle all green under the builder's own environment (vision disabled), with served-byte hashes checked against the checkout.

    Gates — 29/29 passed

    npm test 277/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 — sha256 e84bd1ed3cfb3df7cf51d464d070ab84d4ccce9f8b3977401109a7813d4ce295
    • 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 — sha256 2c01253110c2633af549e600ab478035444dacf82f81bdbc7b6c570df457b564

    Model / profile

    Built and recorded by Hermes (profile default, model deepseek-flash). The demo runs the real app from the built commit with TIMMY_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-hosted SmolVLM2-2.2B-Instruct (pinned GGUF) with the hosted profile as fallback. The new IMAGE INGRESS, REFUSED BY THE SERVER card 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 as photo.jpg, refused before any upload, with manual logging one tap away
    • The device guard is not the only one: the same bytes, sent straight to the running server — the demo's own interceptor lifted for one probe
    • IMAGE INGRESS, REFUSED BY THE SERVER card — 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 a scope row naming which cases this probe does not cover
    • Refused 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 BLOCKED because 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.md rather 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
  • Timmy review release 2026-10-30-1bbcc10a356f
    Quality gates / quality (push) Canceled after 0s
    Pre-Release

    timmy released this 2026-10-30 12:38:15 +00:00 | 13 commits to main since this release

    Prerelease for review — tag daily-2026-10-30 on commit 1bbcc10a356f62d3ab2118081874cbb8ab5e7ea6 (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.mjs starts the real server.mjs on 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 as image/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 while tests/training-contribution.test.js exists). It writes a JSON report, prints the verdict, and exits 1 when blocked.
    • src/release-security-gate.js owns 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.md states 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 SUITE card 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 held

    • ingress-polyglot-html P0 fail — an HTML file dressed as a JPEG returns 503 and reaches the worker (worker 1); the expectation is 400 with the worker never called.
    • ingress-garbage-bytes P0 fail — undecodable bytes reach the worker the same way.
    • flood-concurrency P1 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 as 0bf8230 after 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 (labels area/privacy, area/release, priority/P0, type/security — no human-gate label).
    • Follow-up commit 1bbcc10 — the first gated build's card rendered http undefined · worker 12 on 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 of 0bf8230 was discarded and the whole gated build was re-run end to end from 1bbcc10.
    • Pre-merge verification on the PR head f0a9a73: npm test 244/244, test:security-attack 8/8 — green before the merge was issued.

    Gates — 28/28 passed

    npm test 244/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 — sha256 04552e814171e1da6fe5101369dca5189db334d2fe2ed4de72d368209c240d29
    • 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 — sha256 aa6383c3632aa699a46eb42f5381df03aa06bcfe198a9ea4fab32597c277e1cb

    Model / profile

    Built and recorded by Hermes (profile default, model deepseek-flash). The demo runs the real app from the built commit with TIMMY_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-hosted SmolVLM2-2.2B-Instruct (pinned GGUF) with the hosted profile as fallback. The SECURITY ATTACK SUITE card is a real suite run against a real server.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 — subtitle command line, not app UI — real server, counted worker calls
    • COMMAND node scripts/security_attack_suite.mjs
    • TARGET real server · counting stub worker · synthetic bytes
    • OVERSIZED P0 pass · http 413 · worker 0 — refused before any provider work
    • POLYGLOT P0 fail · http 503 · worker 1 — the hostile file reached the worker
    • TRUNCATED P0 fail · http 503 · worker 1
    • CONSENT P0 pass · http 400 · worker 0
    • MALFORMED P0 pass · http 503 · text leaked false
    • REPLAY P2 pass · http 200 · worker 1 · identical true
    • FLOOD P1 fail · refused false · worker 12
    • LOG P0 pass · image bytes false
    • DELETION P0 covered-elsewhere
    • LOAD 8 req · p50 15ms · p95 19ms · abstain 8
    • VERDICT BLOCKED · 2 blocking · 1 held, with BLOCKS ingress-polyglot-html and BLOCKS ingress-garbage-bytes

    Known accuracy limitation

    The release candidate is not proven safe by this release. The suite's own verdict is BLOCKED on 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 reports BLOCKED because 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
    Quality gates / quality (push) Canceled after 0s
    Pre-Release

    timmy released this 2026-10-29 12:46:43 +00:00 | 16 commits to main since this release

    Prerelease for review — tag daily-2026-10-29 on commit 65a0258bba891a99b0d827fa67b79b36bb70dd11 (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 — backup archives 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, gzip mtime 0 — so two backups of unchanged records are byte-identical.
    • restore fails closed. It verifies every digest, rejects absolute, traversal, link, and unlisted members, refuses a non-empty target, and only then writes. Afterwards it recounts manifest.jsonl and 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 own HOST default is 0.0.0.0; the deployment sets HOST=127.0.0.1 from 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 documented python3 scripts/... command and checks each flag against the real CLI help, so the runbook cannot drift from the tools.
    • Release gates — scripts/build_release.py now 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 BACKUP card 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 real backup, verify, and restore run 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.py against 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 as 65a0258 after 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 — no human-gate label).
    • Pre-merge verification on the PR head 48f0f56: npm test 230/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 test 230/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_DELTA set, 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-delta and 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.service occupies 127.0.0.1:4173, the port the gated build binds. Production was stopped for the build and restored by a trap-guarded script on both paths; it reported active and /api/healthz 200 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 — sha256 560f0fb46d499d26eb5ef29edd463fedd244c497f896323ad28336cadec71b2a
    • 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 — sha256 0ef579708a7b59d828798f80750f72a519d2ff4eabd16e2c458572c07bfa205c

    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 OPERATOR BACKUP card is a real scripts/operator_backup.py run 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 — subtitle command line, not app UI — real run, synthetic corpus
    • COMMAND python3 scripts/operator_backup.py backup --source srv-timmy-corpus
    • STORED 2 members · …jpg + manifest.jsonl
    • RECORDS samples 2 · tombstones 1 · active 1 — the withdrawal is carried, not dropped
    • REFUSED model-weights 1 · transient-inference 1 / REFUSED secret 1 · log 1
    • REASON every skipped path is named in the manifest, not silently dropped
    • DIGEST sha256 e646e7a8066d… · reproducible (sorted, mtime 0)
    • VERIFY ok · 2 members re-hashed against the manifest
    • RESTORE ok · samples 2 · tombstones 1 · active 1 / MANIFEST returned byte-for-byte, tombstone included
    • SERVER 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 BLOCKED because 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
    Quality gates / quality (push) Canceled after 0s
    Pre-Release

    rockachopa released this 2026-10-28 13:03:54 +00:00 | 18 commits to main since this release

    Prerelease for review — tag daily-2026-10-28 on commit 7fe1901703590f59b68f6f394038c628297bd9c4 (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 from manifest.jsonl; every fact the pipeline does not collect (devices, contexts, licenses) prints as pending — not collected by ingest instead 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 (<205 train, <230 validation, 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. --check fails 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.py runs the 14-case dataset-card suite before a release may exist, requires four new on-camera captions, and records dataset_card_generated in the manifest.
    • Demo — a DATASET CARD card 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, and caption() measures the last line box with a Range and fails the run when bottom padding is under 3px. Every caption in this recording now reports padBottom ≥ 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 as d7026c0 after diff review: 11 files, +1014/−3, text only (no binaries, weights, medical imagery, or secrets), Closes #27 linked to a non-human-gate issue (type/docs, area/data, priority/P1, state:review — no human gate).
    • Commit 7fe1901 — demo caption legibility fix above, on main after the merge.

    Gates — 26/26 passed

    npm test 229/229, tests/dataset-card.test.py 14/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 — sha256 b9320551a61ba2cdbcb243bae01cca7b4b120be0d4f807a13fea0435cc24ed28
    • 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 — sha256 67c248e06bca5f18b226d03e91c0fee8aed69ab1d44e67f51710679c16e1f45c

    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 real scripts/dataset_card.py run 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.md
    • CORPUS 2 active · 2 contributors · 1 withdrawn / SPLITS train 2c/2s · validation 0c/0s · test 0c/0s
    • CLASS Type 1 1 · Type 3 0 · Type 4 1 · withdrawn label excluded — the withdrawn label is not counted
    • PENDING devices · contexts · licenses — not collected, not estimated
    • REFUSED refusing to render a dataset card: contributor 516990fea008961e leaks across test, train → CARD not written
    • COMMITTED 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 BLOCKED because 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
    Quality gates / quality (push) Canceled after 0s
    Pre-Release

    timmy released this 2026-10-27 12:39:09 +00:00 | 21 commits to main since this release

    Prerelease for review — tag daily-2026-10-27 on commit b191d0427ddbb19f982458eeaa093aaaee31a68e (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, and reveal() 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, and load_predictions crashed reading a pretty-printed answer file line by line.
    • Release-level gate — scripts/build_release.py now 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 → revoke sequence against the committed synthetic sample and draws a REVIEW LEDGER card from that actual output, labelled synthetic 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:diff gate on a trailing blank line in scripts/review_corpus.py. Fixed with a whitespace-only commit (d54665d) before merge, then check:diff and 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 as b191d04 after diff review: 10 files, +1296/−109, text only (no binaries, weights, medical imagery, or secrets), Closes #26 linked to a non-human-gate issue.

    Gates — 25/25 passed

    npm test 227/227, tests/corpus-review.test.py 21/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 test once at malformed 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 — sha256 d40aa477b0aa799099d28291e264594d51f1ef68efdca6338137473dcb6e6ffa
    • 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 — sha256 4c3601c830dfb81261a0ffcba75778e753777807212d9c61ed50dd5384899470

    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 real scripts/review_corpus.py run over the committed synthetic sample plus a synthetic answer file (no model was measured on screen). No live model inference appears in the video, no real photo is used anywhere, and the fixtures are CC0 synthetic images generated by scripts/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 0 and 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 BLOCKED because 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
    Quality gates / quality (push) Failing after 5m13s
    Pre-Release

    rockachopa released this 2026-10-26 12:57:38 +00:00 | 24 commits to main since this release

    Prerelease for review — tag daily-2026-10-26 on commit 2533f83af3504a8aeeb2a56e1d0df1756e6b6a31.

    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 floor 0.55, accepted quality good|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 with low_quality however 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: GO plus a recommended candidate whose threshold was selected on validation and scored on the held-out test split, or prefill is blocked with every reason named. A missing report is a block, never a default allow. scripts/abstention_policy_receipt.mjs exposes 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 with low_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-calibration
    

    Completed 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 BLOCKED instead 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 as 2533f83 after full diff review: 13 files, +428/−14, no secrets, no weights, no real imagery.

    Gates — all 24 passed

    npm test 226/226, tests/calibration-harness.test.py 19/19, tests/corpus-manifest.test.py 8/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 — sha256 7af46dc5840da8751809f6e307a4175e97dd9e0150fe34c78eff4841b637a81e
    • demo video timmy-talking-turd-2026-10-26-2533f83af350-demo.mp4 — 22,232,190 bytes — H.264 720×1280 yuv420p, 350.3 s — sha256 9cd0b7374a7f88e5c1950de7d2d1254c3eac184fb70a24dba3e86220a45e9a6b

    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 by scripts/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 BLOCKED because 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
    Quality gates / quality (push) Successful in 12m51s
    Pre-Release

    rockachopa released this 2026-10-25 12:42:05 +00:00 | 26 commits to main since this release

    Prerelease for review — tag daily-2026-10-25 on commit 2a10a20518603325a85740d0259d4b20d788862b.

    What changed

    Contributor-held-out evaluation harness (issue #29, PR #86). scripts/calibration_report.py is the comparison and release-gate tool the ROADMAP gates were written for:

    • Reports macro-F1, per-class precision/recall, a truth->predicted confusion matrix, expected calibration error over ten equal-width confidence bins, coverage, and precision-at-coverage — abstentions split into no-suggestion and low-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 excludedClasses instead 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, so specialist-candidate is no longer broken at its hyphen. No app, server, or harness behaviour changes in that commit.

    Gates — all 23 passed

    npm test 219/219, tests/calibration-harness.test.py 19/19, tests/corpus-manifest.test.py 8/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.py runs tests/calibration-harness.test.py before anything is built.

    Artifacts

    • source archive timmy-talking-turd-2026-10-25-2a10a2051860.tar.gz — 291,771 bytes — sha256 6ad4329d363df65e37a5bd2b4bb8a8660039ad3e516da9402bef62b4d170a920
    • demo video timmy-talking-turd-2026-10-25-2a10a2051860-demo.mp4 — 22,983,816 bytes — H.264 720x1280 yuv420p, 349.7 s — sha256 524d02edc87db89dbc5a0f09934af6586d0526c3fd1645ba660f152304dcf02a

    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 validation and test → 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
  • Timmy daily-2026-10-24 — auditable corpus manifest
    Quality gates / quality (push) Successful in 12m45s
    Pre-Release

    rockachopa released this 2026-10-24 13:03:55 +00:00 | 29 commits to main since this release

    Prerelease for review — tag daily-2026-10-24 on commit 9f697307805903e3074cd44e7d9d4b9430278c5c.

    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 immutable manifest.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 dropping labels, subjectKey, and relativePath. 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 --input and --revoke commands against the committed synthetic fixture and shows their actual output.

    Gates — all 22 passed

    npm test 217/217, tests/corpus-manifest.test.py 8/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.py runs it before anything is built.

    Artifacts

    • source archive timmy-talking-turd-2026-10-24-9f6973078059.tar.gz — 276,132 bytes — SHA-256 e0f289e0e271b1639151d0984a5b5e026d011bedd2495194e65f833b6dc22390
    • 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-256 5e0cd5010ffd6a3d7191e371d490e28bc86c6454b3d3aec51bec3cc6ebf5d5ff

    Model / profile

    Self-hosted profile selfhost, model SmolVLM2-2.2B-Instruct. The recording runs with TIMMY_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:

    • ingest writes one active sample record: consent version, source digest, both hashes, derivation, review, lifecycle — path never stored;
    • --revoke deletes images/<digest>.jpg from 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 --revoke of 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