Skip to content

feat(worker): persist trigger attribution on the run record (issue #54) - #165

Merged
edgehero merged 1 commit into
mainfrom
feat/run-record-trigger-attribution
Aug 11, 2026
Merged

feat(worker): persist trigger attribution on the run record (issue #54)#165
edgehero merged 1 commit into
mainfrom
feat/run-record-trigger-attribution

Conversation

@edgehero

Copy link
Copy Markdown
Owner

First slice of issue #54 (Gap 2: github runs can't be attributed to their trigger).

What

buildRecord persists two new additive, nullable fields, triggerIndex and triggerType, read from the job's own data.trigger.matched as explicit no-spread literals beside the replica fields whose admissibility argument they reuse.

  • triggerIndex is the raw triggers.json array position of the entry that fired (cron entries counted, the file index is the rule's identity). Index 0 persists as 0, never null, and a test pins that.
  • triggerType is that entry's on.type, a fixed enum (label | comment | pull_request).
  • The third matched key (label/phrase/action) is deliberately NOT persisted: a label that satisfied an any predicate is collaborator-applied payload text, and the record's PII-free property rests on holding no attacker-chosen string. The end-to-end wiring test asserts the serialized bytes carry index and type but never the label.
  • Cron records hold null for both on purpose: cron attribution is already exact via the repeat:<id>:<millis> jobId join, which also reaches records written before these fields existed, where a new field cannot.

matched has been minted by the receiver since issue #49 and survives on job.data.trigger all the way into recordRun's argument; persisting it was deferred there by that issue's no-new-record-fields scope, not by the record's posture. Issue #54's graph is the consumer that makes it earn its place.

Specs (same PR)

  • INT-RUN-HISTORY-FILE-CONTRACT AMENDED: schema block, Why prose, Acceptance.
  • INT-CONTAINER-JOB-INPUTS AMENDED: one cross-reference sentence; matched stays event.json-only inside the container and never enters the prompt.
  • INT-WEBHOOK-PAYLOAD-SUBSET UNCHANGED, checked (matched is harness-computed, not a payload field).
  • INT-OUTBOX-CONTRACT UNCHANGED, checked (chained children carry no trigger and record null/null, as manual runs do).

Tests

Suite run in the CI posture (PI_DISPATCH_REQUIRE_{LOADER,WORKER,RECEIVER}_TESTS=1, live Valkey): 2073 pass, 0 skipped. New: present case, 0-not-null pin, closed-route-set iteration, cron-shaped null case, matched-object-never-stored, PII sweeps, and the end-to-end serialized-bytes assertions.

A forge run's record could join to the triggers.json entry that fired
it only by the flow name heuristic, which two triggers naming one flow
defeat. The receiver has computed matched: { index, type, ... } since
issue #49, and it survives on job.data.trigger all the way into
recordRun's argument; buildRecord simply never read it.

buildRecord now persists triggerIndex and triggerType as explicit
nullable literals beside the replica fields whose admissibility
argument they reuse: an integer and a fixed enum, nothing
attacker-chosen. The third matched key (label/phrase/action) stays out
on purpose, because a label that satisfied an any predicate is
collaborator-applied payload text. Cron records keep null for both:
their attribution is already exact via the repeat:<id>:<millis> jobId
join, which also reaches records written before these fields existed,
where a new field cannot.

Specs: INT-RUN-HISTORY-FILE-CONTRACT AMENDED (schema, Why, Acceptance:
index 0 persists as 0, never null; the matched label never persists).
INT-CONTAINER-JOB-INPUTS AMENDED (cross-reference only; matched stays
event.json-only inside the container). INT-WEBHOOK-PAYLOAD-SUBSET and
INT-OUTBOX-CONTRACT UNCHANGED, checked.

Suite run in the CI posture (REQUIRE gates, live Valkey): 2073 pass,
0 skipped.

Signed-off-by: Rob Boerman <robboerman@live.nl>
@edgehero
edgehero merged commit f61fd18 into main Aug 11, 2026
12 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