Found while assessing #346.
What happens
recordEngineUsage takes an optional authResolved and maps it to the ledger's payer
(migration 0092). It has four call sites, and only one passes the observation:
| Call site |
Passes authResolved? |
Has it available? |
routes/coding.ts:698 (console capture poll) |
✅ yes |
yes |
workflows/coding-session.ts:379 (the Pilot's own capture drain) |
❌ no |
yes — in snap |
workflows/coding-session.ts:663 (acts-final-drain) |
❌ no |
yes — in snap |
routes/coding.ts:1473 (session end) |
❌ no |
no — /coding/end doesn't return it |
The runner puts authResolved on every /coding/capture snapshot, unconditionally and
independent of drainUsage (packages/browser-runner/src/coding/runtime.ts:190, with the comment
"Reported even when the process is not alive"). Both workflow drains call that exact endpoint with
drainUsage: true and then destructure the response — const { usage, acts, ...pane } = snap —
so the field is in hand and discarded one line before the ledger write.
lib/usage.ts:164 states the opposite as the justification for the parameter being optional:
several drain paths (session end, the Pilot's own capture) have no observation to pass
Session end is correct. The Pilot's own capture is not: it holds the same snapshot
routes/coding.ts:697 reads the value off.
Why it matters
The workflow drain's own comment says it is "the only path collecting the Engine's own spend for
the longest and most expensive sessions the platform has". So the sessions whose payer attribution
matters most are exactly the ones recording payer = NULL, while a short session watched in the
console records it correctly. The Usage page then reports "Payer not established" for autonomous
work whose credential the platform did observe.
It does not re-open #343 — NULL is excluded from CHARGED_SQL, so nothing is blocked over
money nobody owes. The damage is that the column added to answer "who paid?" is unpopulated on the
majority of engine rows, for no reason in the data.
Fix
Pass it, the same way routes/coding.ts:697 does — the wire object carries it even though
CodingPaneSnapshot (lib/coding-loop.ts:38) does not declare it. Adding authResolved?: EngineAuthResolved to that interface would make the field visible to both drains and remove the
cast at the third site.
Optionally, and separately: /coding/end could return authResolved alongside {usage, acts}
(packages/browser-runner/src/coding/runtime.ts:224) so the closing turn is attributed too. That
one needs a runner release, so it is worth doing only if the first fix leaves a visible gap.
A test worth more than the fix: assert every recordEngineUsage call whose records come from a
/coding/capture response also passes authResolved. The failure mode here is a fifth drain
path added later without it.
Found while assessing #346.
What happens
recordEngineUsagetakes an optionalauthResolvedand maps it to the ledger'spayer(migration 0092). It has four call sites, and only one passes the observation:
authResolved?routes/coding.ts:698(console capture poll)workflows/coding-session.ts:379(the Pilot's own capture drain)snapworkflows/coding-session.ts:663(acts-final-drain)snaproutes/coding.ts:1473(session end)/coding/enddoesn't return itThe runner puts
authResolvedon every/coding/capturesnapshot, unconditionally andindependent of
drainUsage(packages/browser-runner/src/coding/runtime.ts:190, with the comment"Reported even when the process is not alive"). Both workflow drains call that exact endpoint with
drainUsage: trueand then destructure the response —const { usage, acts, ...pane } = snap—so the field is in hand and discarded one line before the ledger write.
lib/usage.ts:164states the opposite as the justification for the parameter being optional:Session end is correct. The Pilot's own capture is not: it holds the same snapshot
routes/coding.ts:697reads the value off.Why it matters
The workflow drain's own comment says it is "the only path collecting the Engine's own spend for
the longest and most expensive sessions the platform has". So the sessions whose payer attribution
matters most are exactly the ones recording
payer = NULL, while a short session watched in theconsole records it correctly. The Usage page then reports "Payer not established" for autonomous
work whose credential the platform did observe.
It does not re-open #343 — NULL is excluded from
CHARGED_SQL, so nothing is blocked overmoney nobody owes. The damage is that the column added to answer "who paid?" is unpopulated on the
majority of engine rows, for no reason in the data.
Fix
Pass it, the same way
routes/coding.ts:697does — the wire object carries it even thoughCodingPaneSnapshot(lib/coding-loop.ts:38) does not declare it. AddingauthResolved?: EngineAuthResolvedto that interface would make the field visible to both drains and remove thecast at the third site.
Optionally, and separately:
/coding/endcould returnauthResolvedalongside{usage, acts}(
packages/browser-runner/src/coding/runtime.ts:224) so the closing turn is attributed too. Thatone needs a runner release, so it is worth doing only if the first fix leaves a visible gap.
A test worth more than the fix: assert every
recordEngineUsagecall whose records come from a/coding/captureresponse also passesauthResolved. The failure mode here is a fifth drainpath added later without it.