test(explore): #1500 regression fixtures for budget allocation (CG-6)
Two permanent fixtures pinning the failure mode from issue #1500 — explore spending its byte envelope on files that merely name-collide with the query. BOTH FAIL TODAY, by design: they document the bug and become the pass gate for CG-10 (scoring) + CG-12 (proportional allocation). __tests__/fixtures/payroll-go/ — a synthetic Go service mirroring the reporter's shape: generated FKIT CRUD beside a hand-written payroll use-case, entered from an HTTP route. Half the generated tree carries ORDINARY names detectable only by their `// Code generated ... DO NOT EDIT.` header (the #1500 case, and end-to-end cover for CG-5); `payrollpb/*.pb.go` covers the path-detectable channel. BuildPayslip, Upsert and Store each exist twice, generated and hand-written. cycle.go sits above the whole-file window so it clips; the generated files sit below it so they ship whole. Asking "how does payroll cycle create and calculate payslips?" — naming none of the answering symbols — the generated CRUD delivers 57.4% of the envelope against the hand-written layer's 25.6%, all of the latter domain types. cycle.go is allocated the single largest slice (30.6%) and delivers ZERO: the hard ceiling drops its whole section. runPayrollCycleAll, the hand-written BuildPayslip and the real Upsert never reach the agent. The second fixture is this repo, "how does explore allocate its output budget across files", where scripts/agent-eval/*.mjs take 71.8% against tools.ts's 18.5% despite scoring 4.6x lower. It reads the live index, so its assertions are relative rather than fixed percentages. - scripts/agent-eval/probe-allocation.mjs — per-file budget-share probe, driving the CG-4 diagnostic through a JSONL sidecar so it measures the shipping allocator. Fixture entries are hermetic (copy + re-index per run, verified byte-identical across runs); exits 1 while any assertion fails. - scripts/agent-eval/allocation-fixtures.json — both fixtures declared, with the 2026-08-03 baselines. - __tests__/explore-allocation-1500.test.ts — fixture-shape assertions green today; the allocation assertions held as `it.fails` so the suite stays green while the bug is open and goes RED the moment it is fixed. Also documented and deliberately left unfixed: runPayrollCycleAll's `s.store.Upsert` edge resolves to the GENERATED Store.Upsert, not the hand-written one — same-name method resolution across two packages picks the wrong receiver. It is upstream of the allocation bug, so it belongs with CG-10's scoring work. Refs #1500 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
16e17495f4
commit
bd86ad2061
@@ -0,0 +1,159 @@
|
||||
package payroll
|
||||
|
||||
import "time"
|
||||
|
||||
// CycleStatus is the lifecycle state of a payroll cycle.
|
||||
type CycleStatus string
|
||||
|
||||
const (
|
||||
CycleOpen CycleStatus = "open"
|
||||
CycleClosed CycleStatus = "closed"
|
||||
)
|
||||
|
||||
// ContractKind distinguishes the two pay models this service supports.
|
||||
type ContractKind string
|
||||
|
||||
const (
|
||||
ContractSalaried ContractKind = "salaried"
|
||||
ContractHourly ContractKind = "hourly"
|
||||
)
|
||||
|
||||
// LineKind separates the two halves of a payslip.
|
||||
type LineKind string
|
||||
|
||||
const (
|
||||
LineEarning LineKind = "earning"
|
||||
LineDeduction LineKind = "deduction"
|
||||
)
|
||||
|
||||
// Cycle is one payroll period.
|
||||
type Cycle struct {
|
||||
ID string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Status CycleStatus
|
||||
ClosedAt time.Time
|
||||
ReopenReason string
|
||||
}
|
||||
|
||||
// Line is a single earning or deduction on a payslip.
|
||||
type Line struct {
|
||||
Code string
|
||||
Kind LineKind
|
||||
AmountCents int64
|
||||
}
|
||||
|
||||
// Payslip is what a cycle produces for one employee.
|
||||
type Payslip struct {
|
||||
CycleID string
|
||||
EmployeeID string
|
||||
Currency string
|
||||
PeriodFrom time.Time
|
||||
PeriodTo time.Time
|
||||
Lines []Line
|
||||
GrossCents int64
|
||||
DeductionCents int64
|
||||
NetCents int64
|
||||
Underwater bool
|
||||
RunAt time.Time
|
||||
RunReason string
|
||||
}
|
||||
|
||||
// Timesheet is the approved unit count backing an hourly payslip.
|
||||
type Timesheet struct {
|
||||
CycleID string
|
||||
EmployeeID string
|
||||
Approved bool
|
||||
Units int
|
||||
}
|
||||
|
||||
// Allowance is a recurring earning attached to a contract.
|
||||
type Allowance struct {
|
||||
Code string
|
||||
AmountCents int64
|
||||
Prorated bool
|
||||
}
|
||||
|
||||
// Contract holds the pay terms for one employee.
|
||||
type Contract struct {
|
||||
Kind ContractKind
|
||||
Currency string
|
||||
RateCents int64
|
||||
PeriodRateCents int64
|
||||
OvertimeThresholdUnits int
|
||||
OvertimeMultiplier float64
|
||||
Allowances []Allowance
|
||||
StartsOn time.Time
|
||||
EndsOn time.Time
|
||||
}
|
||||
|
||||
// OverlapsWindow reports whether the contract is live at any point in the window.
|
||||
func (c Contract) OverlapsWindow(from, to time.Time) bool {
|
||||
if !c.StartsOn.IsZero() && c.StartsOn.After(to) {
|
||||
return false
|
||||
}
|
||||
if !c.EndsOn.IsZero() && c.EndsOn.Before(from) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// PeriodUnits is the contractual unit count for a window, used when a salaried
|
||||
// employee has no approved timesheet.
|
||||
func (c Contract) PeriodUnits(from, to time.Time) int {
|
||||
if to.Before(from) {
|
||||
return 0
|
||||
}
|
||||
days := int(to.Sub(from).Hours()/24) + 1
|
||||
return days * 8
|
||||
}
|
||||
|
||||
// Deduction is a fixed or proportional subtraction from gross.
|
||||
type Deduction struct {
|
||||
Code string
|
||||
FixedCents int64
|
||||
RateBasisPoints int
|
||||
}
|
||||
|
||||
// AmountFor resolves a deduction against a gross amount.
|
||||
func (d Deduction) AmountFor(grossCents int64) int64 {
|
||||
if d.FixedCents > 0 {
|
||||
return d.FixedCents
|
||||
}
|
||||
return grossCents * int64(d.RateBasisPoints) / 10000
|
||||
}
|
||||
|
||||
// TaxBand is one slice of a progressive tax schedule.
|
||||
type TaxBand struct {
|
||||
UpToCents int64
|
||||
RateBasisPoints int
|
||||
}
|
||||
|
||||
// Leave is an absence window.
|
||||
type Leave struct {
|
||||
From time.Time
|
||||
To time.Time
|
||||
Unpaid bool
|
||||
}
|
||||
|
||||
// Employee is the payroll view of a person.
|
||||
type Employee struct {
|
||||
ID string
|
||||
Contract Contract
|
||||
Deductions []Deduction
|
||||
TaxBands []TaxBand
|
||||
Leave []Leave
|
||||
}
|
||||
|
||||
// UnpaidLeaveCoversWindow reports whether unpaid leave swallows the whole window.
|
||||
func (e Employee) UnpaidLeaveCoversWindow(from, to time.Time) bool {
|
||||
for _, l := range e.Leave {
|
||||
if !l.Unpaid {
|
||||
continue
|
||||
}
|
||||
if !l.From.After(from) && !l.To.Before(to) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user