Skip to content

feat(admin): spend per trigger and per repo, and the COSTS table cycle (issue #175) - #177

Merged
edgehero merged 1 commit into
mainfrom
feat/cost-trigger-repo-folds
Aug 12, 2026
Merged

feat(admin): spend per trigger and per repo, and the COSTS table cycle (issue #175)#177
edgehero merged 1 commit into
mainfrom
feat/cost-trigger-repo-folds

Conversation

@edgehero

Copy link
Copy Markdown
Owner

Second slice of #175, stacked on #176: the joins the graph already owned become the cost breakdowns the fold was missing. The diff collapses to this slice once #176 merges.

What

  • attributeRunsToTriggers (read-model, beside joinRunsToTriggers): the per-jobId join for the cost fold. Cron attributes via the raw repeat:<id>:<millis> grammar cronRunStats already uses (digits-tail disambiguated); forge attributes via the index+type agreement doctrine. A forge record whose persisted pair disagrees with the current file, or predates the field, gets an explicit unattributed entry rather than silence, so the fold cannot misfile it under manual. triggerMatchLabel is exported from graph-model so the label vocabulary keeps one home.
  • foldCosts gains triggerJoinbyTrigger: typed costs per trigger with an outcome split and failedCost (a trigger whose spend is mostly failures is a different problem than an expensive one), and the honesty buckets (chained runs) / (manual/local) / (unattributed) pinned to the table tail in fixed order however the dollars compare. Chained spend is deliberately NOT rolled up to the ancestor trigger: a parent chain walked across the retention boundary attributes partially, and a partial rollup wearing a trigger's name would lie. byTrigger is null (not []) without a join: "not computed" and "nothing attributed" are different sentences.
  • byRepo via the new shared repoOfTarget grammar; forgeRepoTargets now calls the same function, so the graph's repo list and the cost table can never disagree on what a repo is.
  • foldTriggerCosts: the per-trigger spend map keyed by the graph node id trigger:<index>, for the topology surfaces the next slices add.
  • The COSTS view's f cycles four tables (flow / model / trigger / repo); the footer hint becomes [f] table so width 80 still fits whole; w stays flow-table-only. /dispatch costs renders the two new tables; dispatch_costs returns the same fold, so its JSON gains the arrays additively, every dollar still classed.
  • Refresh policies untouched: the join is one file read (readTriggers) plus a pure fold — no spawn — so the COSTS 10s stale-gated poll piggyback stands, and the GRAPH entry-plus-r posture is not involved at all.

Specs

  • REQ-COST-ANALYTICS AMENDED: the rollup list gains per-trigger and per-repository-target; two acceptance rows (the disagreeing-pair bucket; null byTrigger renders as absence).
  • DES-COST-FOLD-BY-SCAN AMENDED: the join is passed IN (the injected-pricing pattern); "the fold re-deriving the join" joins the Rejected list.
  • DES-ADMIN-VIA-PI-EXTENSION AMENDED: the four-table cycle, and why the extra file read is tick-safe.
  • REQ-TOPOLOGY-GRAPH UNCHANGED, checked: its join doctrine gained a second consumer, not a second definition. DES-GRAPH-EDGE-DERIVATION UNCHANGED, checked. No dispatch_costs shape pin exists in interfaces.md (checked), so the additive fold fields need no INT row.

Tests

Suite green in the CI posture (2194 tests, 0 skipped, live Valkey). New pins: the byTrigger attribution ladder (cron jobId, agreeing forge pair, refused pair → unattributed, chained via parentJobId, manual), tail ordering, failedCost typing, byRepo grouping including local: and the no-target bucket, repoOfTarget unit cases, foldTriggerCosts mapping only real triggers, attributeRunsToTriggers mirroring the adversarial join cases (type shift, out-of-range, pre-field), the four-table f cycle with w inert off the flow table, null-byTrigger degrade, the new tables in plain text, and the 80-col SGR invariant re-run per table.

Stacked on #176.

@edgehero
edgehero changed the base branch from fix/cost-fold-window-honesty to main August 12, 2026 07:26
…e (issue #175)

The second insights slice: the joins the graph already owned become the
cost breakdowns the fold was missing.

- attributeRunsToTriggers (read-model, beside joinRunsToTriggers): the
  per-jobId join for the cost fold -- cron via the raw repeat jobId
  grammar cronRunStats already uses, forge via the index+type agreement
  doctrine. A forge record whose pair disagrees with the current file
  (or predates the field) gets an explicit unattributed entry, never
  silence, so the fold cannot misfile it under manual.
- foldCosts gains triggerJoin -> byTrigger (typed costs, outcome split,
  failedCost, honesty buckets chained/manual/unattributed pinned to the
  tail) and byRepo (repoOfTarget, the one stripping grammar, now also
  called by forgeRepoTargets). byTrigger is null without a join: not
  computed and nothing attributed are different sentences.
- foldTriggerCosts: the per-trigger spend map keyed by the graph node
  id trigger:<index>, for the topology surfaces the next slices add.
- The COSTS view's f cycles flow/model/trigger/repo; footer hint
  renamed [f] table so width 80 still fits whole; w stays
  flow-table-only. /dispatch costs renders the two new tables; the
  trigger join is one file read, so the 10s stale-gated poll piggyback
  policy stands untouched.

Specs in the same PR: REQ-COST-ANALYTICS and DES-COST-FOLD-BY-SCAN and
DES-ADMIN-VIA-PI-EXTENSION amended; REQ-TOPOLOGY-GRAPH UNCHANGED,
checked (its join doctrine gained a second consumer, not a second
definition); DES-GRAPH-EDGE-DERIVATION UNCHANGED, checked.

Suite green in the CI posture: 2194 tests, 0 skipped, live Valkey.

Signed-off-by: Rob Boerman <robboerman@live.nl>
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