Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
926b409
feat(D-80): Net-negative revert scores silently void item credit
jurby Jul 21, 2026
a675da3
chore(evidence): D-80 from 926b409
jurby Jul 21, 2026
301480a
fix(D-80): clamp net-negative contribution scores in aggregate_signals
jurby Jul 21, 2026
d8107d7
chore(evidence): D-80 from 301480a
jurby Jul 21, 2026
538f7fe
chore(D-80): transition to Done
jurby Jul 21, 2026
15dd640
chore(evidence): D-80 from 538f7fe
jurby Jul 21, 2026
52a9198
feat(D-81): Gate bounce double-credits ladder edges — per-item gate c…
jurby Jul 21, 2026
28c4d3d
chore(evidence): D-81 from 52a9198
jurby Jul 21, 2026
75cc42a
feat(D-82): Gate events inherit all-time contributors — cross-iterati…
jurby Jul 21, 2026
cf8f008
chore(evidence): D-82 from 75cc42a
jurby Jul 21, 2026
e701798
fix(D-81): gate credit is a per-lifecycle budget, not per-transition …
jurby Jul 21, 2026
b914f67
chore(evidence): D-81 from e701798
jurby Jul 21, 2026
5b34ea4
fix(D-82): gate events prefer in-window evidence shares over all-time…
jurby Jul 21, 2026
6191eee
chore(evidence): D-82,D-73 from 5b34ea4
jurby Jul 21, 2026
dfb296d
chore(D-81,D-82): transition to Done
jurby Jul 21, 2026
f4e5bf0
chore(evidence): D-81,D-82 from dfb296d
jurby Jul 21, 2026
14e04b6
chore(D-82): re-vendor cw_heuristics template (gate budget + attribut…
jurby Jul 21, 2026
f04d8c4
chore(evidence): D-82 from 14e04b6
jurby Jul 21, 2026
4ea5b4c
chore(release): v2.22.0
jurby Jul 21, 2026
4e1b5a3
chore(evidence): D-80,D-81,D-82 from 4ea5b4c
jurby Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .edpa/backlog/defects/D-73.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ tc: 0
rr_oe: 0
wsjf: 0.0
evidence:
- type: commit_author
person: urbanek
weight: 0
raw_weight: 4.0
ref: commit/5b34ea4
at: '2026-07-21T02:56:45+02:00'
tags:
- referenced
- type: commit_author
person: urbanek
weight: 4.0
Expand Down
156 changes: 156 additions & 0 deletions .edpa/backlog/defects/D-80.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
id: D-80
type: Defect
title: Net-negative revert scores silently void item credit
status: Done
created_at: '2026-07-21T00:02:28Z'
js: 2
bv: 0
tc: 0
rr_oe: 0
wsjf: 0.0
evidence:
- type: commit_author
person: urbanek
weight: 4.0
raw_weight: 4.0
ref: commit/301480a
at: '2026-07-21T02:04:25+02:00'
- type: yaml_edit
person: urbanek
weight: 2.77
raw_weight: 2.77
discount: 1.0
ref: commit/301480a/D-80.md
at: '2026-07-21T00:04:25+00:00'
tags:
- list_grow×3
- scalar×2
- vol+0.8
delta:
blocks_added: 0
blocks_removed: 0
list_items_added: 3
list_items_removed: 0
scalars_changed: 2
lines_added: 32
lines_removed: 1
- type: agent_contribution
agent: claude-opus-4-8-(1m-context)
person: _claude
weight: 1.0
ref: commit/301480a/agent/claude-opus-4-8-(1m-context)
at: '2026-07-21T02:04:25+02:00'
- type: commit_author
person: urbanek
weight: 0
raw_weight: 4.0
ref: commit/4ea5b4c
at: '2026-07-21T03:40:08+02:00'
tags:
- referenced
- type: commit_author
person: urbanek
weight: 4.0
raw_weight: 4.0
ref: commit/538f7fe
at: '2026-07-21T02:04:50+02:00'
- type: yaml_edit
person: urbanek
weight: 0.25
raw_weight: 0.25
discount: 1.0
ref: commit/538f7fe/D-80.md
at: '2026-07-21T00:04:50+00:00'
tags:
- scalar×1
delta:
blocks_added: 0
blocks_removed: 0
list_items_added: 0
list_items_removed: 0
scalars_changed: 1
lines_added: 2
lines_removed: 1
- type: state_transition
person: urbanek
weight: 0
ref: commit/538f7fe/D-80/Funnel->Done
at: '2026-07-21T02:04:50+02:00'
from_status: Funnel
to_status: Done
- type: commit_author
person: urbanek
weight: 4.0
raw_weight: 4.0
ref: commit/926b409
at: '2026-07-21T02:02:28+02:00'
- type: yaml_edit
person: urbanek
weight: 3.8
raw_weight: 3.8
discount: 1.0
ref: commit/926b409/D-80.md
at: '2026-07-21T00:02:28+00:00'
tags:
- create
- scalar×6
- vol+0.3
delta:
blocks_added: 0
blocks_removed: 0
list_items_added: 0
list_items_removed: 0
scalars_changed: 6
lines_added: 12
lines_removed: 0
- type: state_transition
person: urbanek
weight: 0
ref: commit/926b409/D-80/init->Funnel
at: '2026-07-21T02:02:28+02:00'
from_status:
to_status: Funnel
closed_at: '2026-07-21T00:04:34Z'
---

## Symptom

An item whose signals net out negative for one contributor (e.g. a
`yaml_edit:revert` from grooming cleanup on someone else's Story) silently
lost ALL credit — for everyone, not just the reverter:

| Signals on item | total | cw (Alice) | engine does |
|---|---|---|---|
| Alice commit 4.0, Bob revert −0.5 | 3.5 | **1.14** | hard-drops Alice (`0 <= cw <= 1` filter), drops Bob → item credited to nobody |

No WARN fired anywhere: the Σcw≠1.0 drift check never sees the dropped
entries, so a Done Story with real evidence could vanish from the
timesheet without a trace.

## Cause

`aggregate_signals` let net-negative person scores into the cw math. A
negative share is incoherent in per-item normalization — it deflates the
denominator and pushes the other contributors above cw 1.0, which
`engine.extract_contributors` then hard-drops as out of range. The drop
was silent on top of that.

Negative weights exist by design (`yaml_edit:revert` = −0.5/block,
anti-work semantics), but they are only meaningful as a reduction of the
SAME person's score, never as a cross-person share transfer.

## Fix

- `detect_contributors.aggregate_signals`: clamp per-person net scores at
0 before normalization; the raw (negative) score stays in
`contribution_score` for audit and the clamp fires a stderr WARNING.
All-negative items return None (existing warn-and-skip path).
- `engine.extract_contributors`: the out-of-range cw drop now prints
`WARN: <item>: contributors[] entry for <person> has cw=X outside [0,1]`
(covers files written before the clamp and hand-written frontmatter).
- A revert from the SAME person still reduces their own score — clamping
only kicks in at the net-negative boundary, not per signal.

Tests: 3 aggregate cases (clamp-to-zero, all-negative → None, own-revert)
+ 1 engine WARN case. Full suite 1219 passed.
168 changes: 168 additions & 0 deletions .edpa/backlog/defects/D-81.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
---
id: D-81
type: Defect
title: Gate bounce double-credits ladder edges — per-item gate credit must budget to <= 1.0 x js
status: Done
created_at: '2026-07-21T00:39:32Z'
js: 3
bv: 0
tc: 0
rr_oe: 0
wsjf: 0.0
evidence:
- type: commit_author
person: urbanek
weight: 0
raw_weight: 4.0
ref: commit/4ea5b4c
at: '2026-07-21T03:40:08+02:00'
tags:
- referenced
- type: commit_author
person: urbanek
weight: 4.0
raw_weight: 4.0
ref: commit/52a9198
at: '2026-07-21T02:39:32+02:00'
- type: yaml_edit
person: urbanek
weight: 3.8
raw_weight: 3.8
discount: 1.0
ref: commit/52a9198/D-81.md
at: '2026-07-21T00:39:32+00:00'
tags:
- create
- scalar×6
- vol+0.3
delta:
blocks_added: 0
blocks_removed: 0
list_items_added: 0
list_items_removed: 0
scalars_changed: 6
lines_added: 12
lines_removed: 0
- type: state_transition
person: urbanek
weight: 0
ref: commit/52a9198/D-81/init->Funnel
at: '2026-07-21T02:39:32+02:00'
from_status:
to_status: Funnel
- type: commit_author
person: urbanek
weight: 4.0
raw_weight: 4.0
ref: commit/dfb296d
at: '2026-07-21T02:57:01+02:00'
- type: yaml_edit
person: urbanek
weight: 0.25
raw_weight: 0.25
discount: 1.0
ref: commit/dfb296d/D-81.md
at: '2026-07-21T00:57:01+00:00'
tags:
- scalar×1
delta:
blocks_added: 0
blocks_removed: 0
list_items_added: 0
list_items_removed: 0
scalars_changed: 1
lines_added: 2
lines_removed: 1
- type: state_transition
person: urbanek
weight: 0
ref: commit/dfb296d/D-81/Funnel->Done
at: '2026-07-21T02:57:01+02:00'
from_status: Funnel
to_status: Done
- type: commit_author
person: urbanek
weight: 4.0
raw_weight: 4.0
ref: commit/e701798
at: '2026-07-21T02:48:58+02:00'
- type: yaml_edit
person: urbanek
weight: 3.5
raw_weight: 3.5
discount: 1.0
ref: commit/e701798/D-81.md
at: '2026-07-21T00:48:58+00:00'
tags:
- list_grow×4
- scalar×2
- vol+1.0
delta:
blocks_added: 0
blocks_removed: 0
list_items_added: 4
list_items_removed: 0
scalars_changed: 2
lines_added: 43
lines_removed: 1
- type: agent_contribution
agent: claude-opus-4-8-(1m-context)
person: _claude
weight: 1.0
ref: commit/e701798/agent/claude-opus-4-8-(1m-context)
at: '2026-07-21T02:48:58+02:00'
closed_at: '2026-07-21T00:52:00Z'
---

## Symptom

A Feature that bounced through QA (Validating → Implementing → Validating)
accumulated gate credit beyond its Job Size — each crossing of
`Implementing→Validating` credited another `0.50 × js`:

```
Funnel→Analyzing 0.10
Analyzing→Backlog 0.05
Backlog→Implementing 0.05
Implementing→Validating 0.50
Validating→Implementing 0.143 ← not in gate_weights → equal-split fallback
Implementing→Validating 0.50 ← SECOND crossing, credited again
... ----
total 1.64 × js (ladder budget is 1.0)
```

Two side-effects of the same root cause: the equal-split fallback credited
a REOPEN (~0.143) more than `Releasing→Done` (0.10), and a forward jump
(Funnel→Done) got an arbitrary 1/N instead of the work it compressed.

## Cause

Gate credit was computed per TRANSITION (`weight = gate_weights[key]` per
occurrence), while the config contract is per LIFECYCLE ("weights split the
parent's Job Size across its lifecycle, MUST sum to 1.0"). Reaching a
milestone twice doesn't extend the milestone ladder. There was no per-edge
dedup and no budget replay across iterations.

## Fix

Gate credit is now a **budget replayed over the item's full transition
history** (`load_gate_events`):

- the gate_weights table is parsed into a status ladder
(`_build_gate_ladder`); each ladder edge is credited at most once per
item, the first time any recorded transition crosses it — re-crossings
earn nothing ("already credited" note);
- backward moves (reopens) earn nothing — the rework they represent is
credited through the Story / commit_author / yaml_edit channels;
- a forward JUMP over skipped statuses credits the sum of the bypassed
edges (Funnel→Backlog = 0.10 + 0.05);
- off-ladder transitions earn nothing + WARN (equal-split fallback
removed); creation (init→first status) stays analytics-only.

Budget replays over ALL materialized transitions chronologically, so an
edge credited in iteration N stays spent in iteration N+k. Total gate
credit per item is bounded at 1.0 × js, as the config contract states.

Tests: bounce-credits-each-edge-once, forward-jump-sums-bypassed-edges,
budget-replays-across-iterations, unknown-gate-no-credit, revert-does-not-
subtract (rewritten). Full suite green.
Loading
Loading