# payroll-go — the #1500 regression fixture

A synthetic Go service reproducing the repo shape from [issue #1500](https://github.com/colbymchenry/codegraph/issues/1500):
**generated CRUD sitting beside the hand-written use-case that does the real work.**

This tree is a fixture, not a program. It never compiles or runs — it exists to be
indexed. Keep it valid, idiomatic Go anyway: the extractor's output is the whole point.

## The shape

```
cmd/payrolld/main.go                        wires the service
internal/transport/httpapi/                 HTTP entry point → use-case
internal/usecase/payroll/                   ← THE ANSWER. Hand-written workflow:
  cycle.go                                    runPayrollCycleAll (227 lines)
  payslip_builder.go                          BuildPayslip — the actual pay calculation
  prorate.go
internal/domain/payroll/payslip.go          hand-written domain types
internal/store/payslipstore/store.go        the real Upsert
internal/platform/clock/clock.go

internal/gen/fkit/payroll/                  ← THE NOISE. Generated CRUD, ORDINARY names:
  payslip.go        CreatePayslip, GetPayslip, UpdatePayslip, a second BuildPayslip
  payroll_cycle.go  CreatePayrollCycle, PayrollCycleCreateRequest, …
  store.go          a second Upsert
  calculate.go      CalculatePayrollCycleTotals, CalculatePayslipNet, …
  dto.go
internal/gen/fkit/employee/, timesheet/     more generated CRUD
internal/gen/payrollpb/*.pb.go              generated, detectable by PATH
```

The chain the fixture is built around is `runPayrollCycleAll` → `BuildPayslip` → `Upsert`,
entered from `POST /v1/payroll/cycles/{cycleID}/run`.

## The three properties that make it a regression fixture

1. **Generated files that only a CONTENT header betrays.** The `internal/gen/fkit/**`
   files have ordinary names (`payslip.go`, `store.go`) and carry
   `// Code generated by fkit v3.11.0. DO NOT EDIT.`. Path-only detection misses every
   one of them — that is the #1500 case, and why CG-5 added the content check. The
   `payrollpb/*.pb.go` files cover the path-detectable channel beside them.

2. **Deliberate name collisions.** `BuildPayslip`, `Upsert` and `Store` each exist twice,
   once generated and once hand-written. The generated layer also name-collides on every
   term of the question below — `CreatePayslip`, `PayrollCycleCreateRequest`,
   `CalculatePayrollCycleTotals` — so a scorer that rewards incidental name matches
   surfaces the CRUD path.

3. **A size split that drives the render mode.** `cycle.go` is deliberately over the
   whole-file window (227 lines) so it falls through to clipped clusters; the generated
   files are deliberately under it so they ship whole. Allocation follows file size, not
   relevance. `__tests__/explore-allocation-1500.test.ts` pins both sides of that split —
   if you edit these files, keep it.

## The assertion

Query: **"how does payroll cycle create and calculate payslips?"** — an architecture
question that names none of the symbols that answer it. The budget should concentrate on
the hand-written workflow. As of 2026-08-03 it does not:

| | allocated | delivered |
|---|---|---|
| hand-written | 48.4% | **25.6%** (all of it domain types) |
| generated CRUD | 39.9% | **57.4%** |

`cycle.go` is allocated the single largest slice (7,052 chars, 30.6%) and delivers
**zero** — the hard ceiling drops its whole section. `payslip_builder.go` (rank #8) never
renders at all. `runPayrollCycleAll`, the hand-written `BuildPayslip` and the real
`Upsert` never reach the agent.

## Running it

```bash
npm run build
node scripts/agent-eval/probe-allocation.mjs payroll-go   # exits 1 today, by design
npx vitest run __tests__/explore-allocation-1500.test.ts  # green today, by design
```

The probe reports per-file budget share against `scripts/agent-eval/allocation-fixtures.json`.
The vitest suite pins the fixture's shape and holds the allocation assertion as `it.fails` —
green while the bug is open, red the moment it is fixed. See
`docs/design/explore-budget-allocation.md`.

## Known finding: the chain's `Upsert` edge resolves to the generated store

`runPayrollCycleAll` calls `s.store.Upsert(ctx, slip)`, where `s.store` is a
`*payslipstore.Store`. The graph resolves that edge to `internal/gen/fkit/payroll/store.go`
— the **generated** `Store.Upsert` — not the hand-written one. Same-name method resolution
across two packages that both define `Store.Upsert` picks the wrong receiver.

This is a resolution defect, not a budget one, and it is left unfixed on purpose: it is
upstream of the allocation bug (a wrong edge pulls the generated store into the subgraph
and inflates its score), so it belongs with the scoring work in CG-10 rather than here.
The test asserts only that the workflow reaches *an* `Upsert`, so tightening the resolver
later will not break the fixture.
