Attribution: why delta has no owner yet

Measured 2026-09-03 Status blocking — every per-trader number depends on this

The seam

The whole design rests on three lines:

MECHANICAL   the issued plan, resolved against the tape   -> a property of the SETUP
REALISED     what the account actually did                -> a property of the ACCOUNT
TRADER DELTA realised - mechanical                        -> a property of a PERSON

Delta is a property of a person. No row in the ledger names the person.

What is actually there

ride_context.decided_by is free text:

rule:lab-fast      1,742        hand:evancooney@gmail.com    41
rule:runner          182        hand:api                     31
rule:lab-ticks        51        hand:evan                    26
rule:tick-scalper     46        unknown (reconstructed)      22
rule:junk-gate        24        ...

Roughly 2,050 machine to 67 human — and hand:evan and hand:evancooney@gmail.com are the same person under two users rows:

id 1  evan                      owner    (no email)
id 2  evancooney@gmail.com      owner    evancooney@gmail.com
id 3  143804e8-…                member   evancooney+55@gmail.com
id 6  74689488-…                member   mark@slowdesk.io

All four share the account agentic-907533723. No ride table carries a user_id at all.

Why it is fatal rather than untidy

With one trader the ambiguity is invisible, because every delta is the same person's. With a desk it becomes an average over people, which describes nobody: one trader who over-manages and one who under-manages cancel to a clean zero, and the report says the desk is fine.

parseDecidedBy() in src/ledger.ts currently returns { kind, userId: null } — it can tell human from rule, but not which human. Every ride scored today produces a delta with no owner.

The other half: no way to record a No

position_recommendations, 5,087 rows:

~50    accepted
 0     rejected          <- the state does not exist
3,375  hold, still "proposed" forever
1,199  expired as "superseded by a newer verdict"

There is no seen_at either. So when a trader holds through an exit-early that would have saved a point, the ledger cannot say whether they overruled the desk or never opened the page. Those are opposite diagnoses — a judgment to examine versus a workflow bug — and coaching someone on the wrong one costs their trust in the tool permanently.

Findings have no owner either

learning_findings is well shaped and working — category, headline, metric_json, status, and live rows already carry low_power to hold back thin claims. But its subjects are grade:B- and source:catalyst-scan: properties of the system. With no user_id and no scope it cannot hold "you cut winners early" at all.

What has to change, desk-side

  1. Merge the two Evan users rows, then add user_id to positions, manual_orders and every ride_context writer. Backfill from the decided_by prefix, which already encodes hand: vs rule:.
  2. Add seen_at and a terminal rejected (with a reason) to position_recommendations, and stamp seen_at when one renders. This is the cheapest of the changes and the most urgent — every day without it is a day of data that cannot be recovered afterwards.
  3. Add user_id and a scope to learning_findings, so a finding can belong to a person as well as to the desk.

Recommendation on account structure: keep one account and separate by user_id. Splitting accounts also splits caps, rails and reconciliation — a much larger job, undertaken to solve what is really a reporting problem.