Skip to content

Complete protocol-resolution and History launch safeguards - #5

Merged
Born2tweak merged 7 commits into
masterfrom
agent/launch-p4-resolution-provenance
Aug 9, 2026
Merged

Complete protocol-resolution and History launch safeguards#5
Born2tweak merged 7 commits into
masterfrom
agent/launch-p4-resolution-provenance

Conversation

@Born2tweak

Copy link
Copy Markdown
Owner

What changed

  • Carry movement-resolution provenance through capture, persisted sessions, reports, History, and exports.
  • Let athletes correct the analyzed movement after recording by replaying the stored pose tape.
  • Preserve rep counts when reanalysis returns to the original protocol.
  • Add per-session History deletion that removes the report, video, and pose tape.
  • Distinguish a deleted or missing report from the never-recorded empty state.
  • Withhold findings and coaching from questionable forward-lunge captures while retaining measured evidence.
  • Use protocol-correct History vocabulary such as “trials” for forward lunges.
  • Add browser regression coverage for the Upload pose-tape flow.

Why

Several post-capture surfaces could present an assumed movement as chosen, lose replay parity during correction, strand stored media, or publish recommendations from a capture explicitly classified as questionable. History also leaked squat-specific vocabulary into transition protocols.

Impact

Reports now retain an honest account of how the movement was selected, corrections remain reproducible, privacy controls remove all session artifacts, questionable captures stay observation-only, and mixed-protocol History reads consistently.

Validation

  • npm test: 120 test files, 838 tests passed.
  • npm run build: TypeScript and Vite production build passed.
  • Chromium Playwright suite: 23 passed, 1 intentionally skipped.
  • Upload, report, missing-record, History, desktop, and mobile states were browser-verified.
  • Real MP4 no-person detection and recovery were browser-verified.

Known CI evidence debt remains unchanged; no evidence was rebound to make CI green.

…ecord and the export

The resolver seam disclosed an assumed movement on the capture screen and
nothing carried it any further. A bare /camera or /upload arrival - which the
nav bar and both landing CTAs produce - analyzed as a squat, and from that
navigation onward the report, the stored session, the history list and the
exported evidence artifact all named "Bodyweight squat" with nothing anywhere
recording that no one had said so.

SessionResult now carries an optional ProtocolResolution, reconciled against
the protocol the analysis actually ran under so a record cannot claim
authority the analysis did not have. Absence reads as unknown, never as a
selection: legacy records predate the field and must not be upgraded into
consent for free.

Surfaced where the claim is made rather than where it originated - beside the
report title, on the history row, in the report JSON, and as a banner plus a
"Movement decided by" row in the exported HTML. Report schema goes to v2: the
addition is additive, but a v1 reader cannot tell "chosen" from "this build
could not record either way", which is the whole distinction.

Also fixes a related overclaim: historyObservation compared two sessions by
protocolId, so an assumed squat and a chosen squat were presented as the same
movement over time. It now discloses instead of dropping the comparison -
silencing history for everyone who never opened the picker would cost more.

Verified: tsc clean, 811 unit tests, 19/20 chromium e2e (1 skipped), and the
full flow driven in-browser through capture, reload, history and both exports.
The disclosure shipped in the previous commit told an athlete their recording
may not have been the movement it was measured against, and then offered no
way to act on it. The only remedy was to record the set again - but the
recording is the expensive part and it is not the part that was wrong.

The stored pose tape is the substrate, so a finished session can be re-read
under any protocol that has a runtime. The resulting resolution is
'user-corrected', a source kept distinct from 'user-selected' on purpose: both
carry the athlete's authority, but a correction is applied to footage framed
by the setup guidance of a different movement, and the report says so.

The correction runs the pipeline the app WOULD have run had the movement been
chosen at capture time. A naive cold replay does not: it feeds the calibration
preroll to a state machine that never saw it and starts that machine from
standing when the live one was seeded mid-descent. Found in the browser - a
19-rep set round-tripped through the lunge and came back reporting 0 reps.
Re-analysis now reproduces the recorded filtering, skips the preroll, and
seeds the entry state, and the same round trip returns 19 reps exactly.
Whole-session runtimes still get raw frames, as the upload path feeds them.

Corrections rewrite the record under its own id and original timestamp: a new
id would orphan the video and pose tape, which are keyed by session id, and
leave two reports of one recording in History.

History fixes found while verifying this, all of the same family - the screen
was written when squat was the only protocol:
  - it printed the raw protocol id, which read acceptably as "squat" and
    became "ForwardLungeStrideReturn" the moment a second protocol shipped
  - it counted a transition movement in "reps"; the repo already has
    attemptVocabulary for exactly this, and this screen was never migrated
  - the longitudinal copy leaked the raw id too
  - rows now mark a corrected movement, as they already marked an assumed one
Both lookups tolerate an unregistered protocol, because History is where a
record gets deleted and stranding one there is the worst place to fail.

Verified: tsc clean, 837 unit tests, 20/21 chromium e2e (1 skipped), and the
full correction round trip driven in-browser including History and the report.
The report footer has always told the athlete "delete it any time from
History", and History could only delete everything. Someone who wanted one
recording gone had to choose between keeping it and losing every other session
they had - a promise the shipped copy made and the screen could not keep.

Reports and capture media live in separate databases, so the per-row delete
removes from both. Deleting only the report would leave the actual footage on
the device: the more sensitive half, and the opposite of what the control
promises. Proven in-browser against both IndexedDB stores - the deleted row's
pose tape is gone and the other session's survives.

The control is a sibling of the row link rather than nested inside it: an
interactive element inside an anchor is invalid, and a keyboard user would
otherwise land on a destructive button while trying to open a report. Its
accessible name says which session, because a screen-reader user moving
through the list hears the button out of context.

Two style fixes found while verifying:
  - the row carried text-transform: capitalize to dress up the raw protocol id
    it used to print, and that rewrote the authored label "Bodyweight squat"
    into "Bodyweight Squat"
  - --color-danger is now a real token instead of a literal hiding behind an
    undefined var() fallback, and the history rows point at --color-text-muted
    rather than the undefined --text-muted they were silently falling back
    from

Also fixes two races in the e2e specs added earlier in this branch: they read
or reloaded the /results hand-off URL before it had been replaced by the
record's own address, which passed in isolation and failed in a full run.

Verified: tsc clean, 837 unit tests, 21/22 chromium e2e (1 skipped) twice in a
row, and the delete flow driven in-browser at desktop and 375px with no
overlap or horizontal overflow.
…never recorded

/results/<id> for a session this device does not have rendered the same "No
session yet - record a set on the camera page" card as the bare hand-off
address. Those are different situations and only one of them is "you haven't
recorded anything yet". A specific report that isn't here was deleted, or was
recorded in another browser - sessions are local-only and never sync, so a
link that works on one device legitimately fails on another.

The per-row delete shipped in the previous commit makes this a realistic path
rather than a corner case: a bookmark, the back button, or a History link
followed after the record is gone all land here.

The hook now distinguishes `missing` (nothing was asked for) from `not-found`
(an id was asked for and this device cannot produce it), including the read
that fails outright - that is still a specific session the athlete asked for
and did not get.

Verified: tsc clean, 837 unit tests, 22/23 chromium e2e (1 skipped), both
states rendered in-browser, and an e2e that records, deletes, and returns to
the dead link.
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
kinematiciq Ready Ready Preview Aug 9, 2026 12:22pm

@Born2tweak
Born2tweak merged commit f76dd25 into master Aug 9, 2026
2 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant