devlyn-cli 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CLAUDE.md +2 -2
- package/README.md +80 -29
- package/benchmark/auto-resolve/BENCHMARK-DESIGN.md +61 -44
- package/benchmark/auto-resolve/BENCHMARK-RESULTS.md +341 -0
- package/benchmark/auto-resolve/README.md +307 -44
- package/benchmark/auto-resolve/RUBRIC.md +23 -14
- package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/NOTES.md +7 -3
- package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/NOTES.md +8 -3
- package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/NOTES.md +8 -3
- package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/NOTES.md +10 -4
- package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/NOTES.md +10 -4
- package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/NOTES.md +12 -0
- package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/spec.md +6 -0
- package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/NOTES.md +7 -4
- package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/NOTES.md +12 -0
- package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/spec.md +6 -0
- package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/NOTES.md +8 -0
- package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/NOTES.md +12 -0
- package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/spec.md +6 -0
- package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/NOTES.md +16 -4
- package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/spec.md +7 -0
- package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/NOTES.md +11 -5
- package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/NOTES.md +8 -1
- package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/expected.json +4 -2
- package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/spec.md +1 -1
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/NOTES.md +34 -0
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/expected.json +57 -0
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/setup.sh +2 -0
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/spec.md +67 -0
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/task.txt +7 -0
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/verifiers/duplicate-event-error.js +35 -0
- package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/verifiers/priority-transfer-rollback.js +53 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/NOTES.md +38 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/expected.json +57 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/setup.sh +2 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/spec.md +70 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/task.txt +3 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/verifiers/duplicate-renewal-error.js +42 -0
- package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/verifiers/priority-credit-rollback.js +70 -0
- package/benchmark/auto-resolve/fixtures/F4-web-browser-design/NOTES.md +10 -3
- package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/NOTES.md +7 -0
- package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/NOTES.md +5 -0
- package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/NOTES.md +7 -0
- package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/NOTES.md +3 -0
- package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/spec.md +1 -1
- package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/NOTES.md +15 -3
- package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/spec.md +1 -1
- package/benchmark/auto-resolve/fixtures/SCHEMA.md +53 -7
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/NOTES.md +37 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/RETIRED.md +13 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/expected.json +56 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/setup.sh +18 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/spec.md +69 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/task.txt +7 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/verifiers/exact-proration.js +48 -0
- package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/verifiers/rules-source-and-conflict.js +79 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/NOTES.md +54 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/RETIRED.md +7 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/expected.json +67 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/setup.sh +2 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/spec.md +67 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/task.txt +5 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/verifiers/policy-precedence.js +72 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/verifiers/validation-and-immutability.js +43 -0
- package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/verifiers/validation-boundary.js +116 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/NOTES.md +35 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/RETIRED.md +12 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/expected.json +58 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/setup.sh +2 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/spec.md +73 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/task.txt +17 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/verifiers/mixed-idempotent-settlement.js +53 -0
- package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/verifiers/rejection-boundaries.js +74 -0
- package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/NOTES.md +60 -0
- package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/RETIRED.md +29 -0
- package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/expected.json +73 -0
- package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/setup.sh +28 -0
- package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/spec.md +58 -0
- package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/task.txt +5 -0
- package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/full-pipeline-pair-gate.json +82 -0
- package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/full-pipeline-pair-gate.md +18 -0
- package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/headroom-gate.json +46 -0
- package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/headroom-gate.md +17 -0
- package/benchmark/auto-resolve/run-real-benchmark.md +303 -0
- package/benchmark/auto-resolve/scripts/audit-headroom-rejections.py +441 -0
- package/benchmark/auto-resolve/scripts/audit-pair-evidence.py +1256 -0
- package/benchmark/auto-resolve/scripts/build-pair-eligible-manifest.py +147 -15
- package/benchmark/auto-resolve/scripts/check-f9-artifacts.py +28 -16
- package/benchmark/auto-resolve/scripts/collect-swebench-predictions.py +11 -1
- package/benchmark/auto-resolve/scripts/compile-report.py +208 -46
- package/benchmark/auto-resolve/scripts/fetch-swebench-instances.py +22 -4
- package/benchmark/auto-resolve/scripts/frozen-verify-gate.py +175 -30
- package/benchmark/auto-resolve/scripts/full-pipeline-pair-gate.py +408 -46
- package/benchmark/auto-resolve/scripts/headroom-gate.py +270 -39
- package/benchmark/auto-resolve/scripts/iter-0033c-compare.py +164 -33
- package/benchmark/auto-resolve/scripts/iter-0033c-l1-summary.py +97 -0
- package/benchmark/auto-resolve/scripts/judge-opus-pass.sh +150 -38
- package/benchmark/auto-resolve/scripts/judge.sh +153 -26
- package/benchmark/auto-resolve/scripts/oracle-scope-tier-a.py +12 -5
- package/benchmark/auto-resolve/scripts/oracle-scope-tier-b.py +25 -2
- package/benchmark/auto-resolve/scripts/pair-candidate-frontier.py +469 -0
- package/benchmark/auto-resolve/scripts/pair-plan-idgen.py +5 -5
- package/benchmark/auto-resolve/scripts/pair-plan-lint.py +9 -2
- package/benchmark/auto-resolve/scripts/pair-rejected-fixtures.sh +91 -0
- package/benchmark/auto-resolve/scripts/pair_evidence_contract.py +269 -0
- package/benchmark/auto-resolve/scripts/prepare-swebench-frozen-case.py +39 -10
- package/benchmark/auto-resolve/scripts/prepare-swebench-frozen-corpus.py +34 -4
- package/benchmark/auto-resolve/scripts/prepare-swebench-solver-worktree.py +23 -5
- package/benchmark/auto-resolve/scripts/recent-benchmark-summary.py +232 -0
- package/benchmark/auto-resolve/scripts/run-fixture.sh +118 -51
- package/benchmark/auto-resolve/scripts/run-frozen-verify-pair.sh +211 -39
- package/benchmark/auto-resolve/scripts/run-full-pipeline-pair-candidate.sh +335 -39
- package/benchmark/auto-resolve/scripts/run-headroom-candidate.sh +249 -6
- package/benchmark/auto-resolve/scripts/run-iter-0033c.sh +22 -48
- package/benchmark/auto-resolve/scripts/run-suite.sh +44 -7
- package/benchmark/auto-resolve/scripts/run-swebench-frozen-corpus.sh +120 -19
- package/benchmark/auto-resolve/scripts/run-swebench-solver-batch.sh +32 -14
- package/benchmark/auto-resolve/scripts/ship-gate.py +219 -50
- package/benchmark/auto-resolve/scripts/solo-ceiling-avoidance.py +53 -0
- package/benchmark/auto-resolve/scripts/solo-headroom-hypothesis.py +77 -0
- package/benchmark/auto-resolve/scripts/swebench-frozen-matrix.py +239 -26
- package/benchmark/auto-resolve/scripts/test-audit-headroom-rejections.sh +288 -0
- package/benchmark/auto-resolve/scripts/test-audit-pair-evidence.sh +1672 -0
- package/benchmark/auto-resolve/scripts/test-benchmark-arg-parsing.sh +933 -0
- package/benchmark/auto-resolve/scripts/test-build-pair-eligible-manifest.sh +491 -0
- package/benchmark/auto-resolve/scripts/test-check-f9-artifacts.sh +91 -0
- package/benchmark/auto-resolve/scripts/test-frozen-verify-gate.sh +328 -3
- package/benchmark/auto-resolve/scripts/test-full-pipeline-pair-gate.sh +497 -18
- package/benchmark/auto-resolve/scripts/test-headroom-gate.sh +331 -14
- package/benchmark/auto-resolve/scripts/test-iter-0033c-compare.sh +525 -0
- package/benchmark/auto-resolve/scripts/test-iter-0033c-l1-summary.sh +254 -0
- package/benchmark/auto-resolve/scripts/test-lint-fixtures.sh +580 -0
- package/benchmark/auto-resolve/scripts/test-pair-candidate-frontier.sh +591 -0
- package/benchmark/auto-resolve/scripts/test-run-full-pipeline-pair-candidate.sh +497 -0
- package/benchmark/auto-resolve/scripts/test-run-headroom-candidate.sh +401 -0
- package/benchmark/auto-resolve/scripts/test-run-swebench-solver-batch.sh +111 -0
- package/benchmark/auto-resolve/scripts/test-ship-gate.sh +1189 -0
- package/benchmark/auto-resolve/scripts/test-swebench-frozen-case.sh +924 -5
- package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/NOTES.md +28 -0
- package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/expected.json +63 -0
- package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/metadata.json +10 -0
- package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/setup.sh +3 -0
- package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/spec.md +47 -0
- package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/task.txt +1 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/NOTES.md +34 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/expected.json +53 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/metadata.json +10 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/setup.sh +3 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/spec.md +50 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/task.txt +1 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/verifiers/duplicate-order-error.js +27 -0
- package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/verifiers/priority-stock-reservation.js +44 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/NOTES.md +34 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/expected.json +55 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/metadata.json +10 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/setup.sh +3 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/spec.md +52 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/task.txt +1 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/verifiers/duplicate-ticket-error.js +29 -0
- package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/verifiers/priority-agent-assignment.js +48 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/NOTES.md +34 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/expected.json +55 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/metadata.json +10 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/setup.sh +3 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/spec.md +55 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/task.txt +1 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/verifiers/duplicate-return-error.js +43 -0
- package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/verifiers/priority-return-routing.js +70 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/NOTES.md +37 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/expected.json +54 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/metadata.json +10 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/setup.sh +3 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/spec.md +59 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/task.txt +1 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/verifiers/credit-ledger-priority.js +98 -0
- package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/verifiers/duplicate-charge-error.js +38 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/NOTES.md +36 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/expected.json +56 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/metadata.json +10 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/setup.sh +3 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/spec.md +59 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/task.txt +1 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/verifiers/duplicate-refund-error.js +41 -0
- package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/verifiers/priority-refund-ledger.js +65 -0
- package/bin/devlyn.js +210 -17
- package/config/skills/_shared/adapters/README.md +3 -0
- package/config/skills/_shared/adapters/gpt-5-5.md +5 -1
- package/config/skills/_shared/adapters/opus-4-7.md +9 -1
- package/config/skills/_shared/archive_run.py +78 -6
- package/config/skills/_shared/codex-config.md +3 -2
- package/config/skills/_shared/codex-monitored.sh +46 -1
- package/config/skills/_shared/collect-codex-findings.py +20 -5
- package/config/skills/_shared/engine-preflight.md +1 -1
- package/config/skills/_shared/runtime-principles.md +5 -8
- package/config/skills/_shared/spec-verify-check.py +2664 -107
- package/config/skills/_shared/verify-merge-findings.py +1369 -19
- package/config/skills/devlyn:ideate/SKILL.md +7 -4
- package/config/skills/devlyn:ideate/references/elicitation.md +50 -4
- package/config/skills/devlyn:ideate/references/from-spec-mode.md +26 -4
- package/config/skills/devlyn:ideate/references/project-mode.md +20 -1
- package/config/skills/devlyn:ideate/references/spec-template.md +10 -1
- package/config/skills/devlyn:resolve/SKILL.md +49 -18
- package/config/skills/devlyn:resolve/references/free-form-mode.md +15 -0
- package/config/skills/devlyn:resolve/references/phases/build-gate.md +2 -2
- package/config/skills/devlyn:resolve/references/phases/probe-derive.md +74 -2
- package/config/skills/devlyn:resolve/references/phases/verify.md +62 -28
- package/config/skills/devlyn:resolve/references/state-schema.md +7 -4
- package/package.json +47 -2
- package/scripts/lint-fixtures.sh +349 -0
- package/scripts/lint-shadow-fixtures.sh +58 -0
- package/scripts/lint-skills.sh +3642 -92
- /package/{optional-skills → config/skills}/devlyn:design-ui/SKILL.md +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# S4-cli-return-routing NOTES
|
|
2
|
+
|
|
3
|
+
## What failure mode does this fixture detect?
|
|
4
|
+
|
|
5
|
+
Priority-sensitive return routing where policy decisions and mutable destination
|
|
6
|
+
capacity interact. Bare implementations commonly route in input order, decrement
|
|
7
|
+
capacity for rejected rows, apply the dispose/window rule after condition
|
|
8
|
+
branches, or emit rejected rows in processing order.
|
|
9
|
+
|
|
10
|
+
## What pipeline phase(s) is this testing?
|
|
11
|
+
|
|
12
|
+
- **PLAN / RISK_PROBES**: must notice priority ordering, condition/window rule
|
|
13
|
+
order, capacity mutation, duplicate id handling, and output-shape contracts.
|
|
14
|
+
- **IMPLEMENT**: must add a CLI command without broadening scope or adding deps.
|
|
15
|
+
- **VERIFY**: hidden oracles exercise compound behavior that simple unit tests
|
|
16
|
+
often miss.
|
|
17
|
+
|
|
18
|
+
## Why can't another fixture cover this?
|
|
19
|
+
|
|
20
|
+
S2 uses single-SKU inventory and S3 uses skill/capacity assignment. S4 adds a
|
|
21
|
+
policy-derived destination before capacity mutation, so it catches rule-order
|
|
22
|
+
and output-order failures that those fixtures do not.
|
|
23
|
+
|
|
24
|
+
## When should this fixture be retired?
|
|
25
|
+
|
|
26
|
+
Retire or replace it if two consecutive measured runs show both bare and
|
|
27
|
+
solo_claude consistently satisfy priority ordering, policy rule order, capacity
|
|
28
|
+
mutation, duplicate id handling, and exact output shape without pair assistance.
|
|
29
|
+
|
|
30
|
+
## Calibration status
|
|
31
|
+
|
|
32
|
+
- `20260513-s4-return-headroom`: bare `33`, solo_claude `98`, headroom gate
|
|
33
|
+
`FAIL` because solo exceeded the `80` ceiling and timed out. Treat S4 as a
|
|
34
|
+
shadow control unless it is reworked to preserve solo_claude headroom.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"verification_commands": [
|
|
3
|
+
{
|
|
4
|
+
"cmd": "node --test tests/cli.test.js",
|
|
5
|
+
"exit_code": 0,
|
|
6
|
+
"stdout_contains": [],
|
|
7
|
+
"stdout_not_contains": ["not ok "]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/priority-return-routing.js\"",
|
|
11
|
+
"exit_code": 0,
|
|
12
|
+
"stdout_contains": ["\"ok\":true"],
|
|
13
|
+
"stdout_not_contains": [],
|
|
14
|
+
"contract_refs": [
|
|
15
|
+
"Process returns globally by `priority` descending, then original input order ascending.",
|
|
16
|
+
"A return with an unknown category rejects with reason `unknown_category` and does not change capacity.",
|
|
17
|
+
"For a known category, choose the target destination by this rule order: `damaged` condition routes to `dispose`; otherwise, if `days_since_purchase` is greater than `restock_window_days`, route to `dispose`; otherwise `sealed` routes to `restock`; otherwise `opened` routes to `refurbish`.",
|
|
18
|
+
"A return accepts only when the chosen destination exists in `capacity` and has positive remaining capacity. On accept, decrement that destination by `1`.",
|
|
19
|
+
"A return rejects with reason `destination_full` when the chosen destination is absent from `capacity` or has zero remaining capacity.",
|
|
20
|
+
"`routed` is ordered by processing order. Each row has keys `id`, `destination`.",
|
|
21
|
+
"`rejected` is ordered in the original input order. Each row has keys `id`, `reason`.",
|
|
22
|
+
"`capacity` is an object whose keys are sorted alphabetically and whose values are remaining capacities.",
|
|
23
|
+
"On success, write exactly one JSON object to stdout and no stderr. Keys: `routed`, `rejected`, `capacity`."
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/duplicate-return-error.js\"",
|
|
28
|
+
"exit_code": 0,
|
|
29
|
+
"stdout_contains": ["\"ok\":true"],
|
|
30
|
+
"stdout_not_contains": [],
|
|
31
|
+
"contract_refs": [
|
|
32
|
+
"Before routing any return, duplicate return ids are invalid input: exit `2`, write exactly one JSON error object `{ \"error\": \"duplicate_return_id\", \"id\": string }` to stderr, and write no stdout."
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"forbidden_patterns": [
|
|
37
|
+
{
|
|
38
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{[^}]*return\\s+(null|undefined|''|\\{\\}|\\[\\])",
|
|
39
|
+
"description": "silent catch returning fallback in route-returns path",
|
|
40
|
+
"files": ["bin/cli.js", "tests/cli.test.js"],
|
|
41
|
+
"severity": "disqualifier"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}",
|
|
45
|
+
"description": "empty catch block",
|
|
46
|
+
"files": ["bin/cli.js", "tests/cli.test.js"],
|
|
47
|
+
"severity": "disqualifier"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"required_files": ["bin/cli.js", "tests/cli.test.js"],
|
|
51
|
+
"forbidden_files": [],
|
|
52
|
+
"tier_a_waivers": [],
|
|
53
|
+
"spec_output_files": ["bin/cli.js", "tests/cli.test.js"],
|
|
54
|
+
"max_deps_added": 0
|
|
55
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S4-cli-return-routing",
|
|
3
|
+
"category": "high-risk",
|
|
4
|
+
"difficulty": "high",
|
|
5
|
+
"timeout_seconds": 900,
|
|
6
|
+
"required_tools": ["node"],
|
|
7
|
+
"browser": false,
|
|
8
|
+
"deps_change_expected": false,
|
|
9
|
+
"intent": "Add a return routing CLI command that applies category policy, priority ordering, destination capacity mutation, duplicate id rejection, and exact JSON output shape."
|
|
10
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "S4-cli-return-routing"
|
|
3
|
+
title: "Add return routing command"
|
|
4
|
+
status: planned
|
|
5
|
+
complexity: high
|
|
6
|
+
depends-on: []
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# S4 Add Return Routing Command
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
Operations needs a deterministic CLI command that routes product returns to
|
|
14
|
+
restock, refurbish, or dispose destinations. The command must combine category
|
|
15
|
+
policy, condition/window rules, priority ordering, destination capacity mutation,
|
|
16
|
+
and exact machine-readable output.
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- [ ] Add `route-returns` to `bin/cli.js`.
|
|
21
|
+
- [ ] Accept `--policies <json>` as a JSON array of policy objects. Each policy has keys `category`, `restock_window_days`, and `destinations`.
|
|
22
|
+
- [ ] Each `destinations` object has keys `restock`, `refurbish`, and `dispose`, whose values are destination ids.
|
|
23
|
+
- [ ] Accept `--capacity <json>` as a JSON object mapping destination ids to non-negative integer remaining capacity.
|
|
24
|
+
- [ ] Accept `--returns <json>` as a JSON array of return objects. Each return has keys `id`, `category`, `condition`, `days_since_purchase`, and `priority`.
|
|
25
|
+
- [ ] Before routing any return, duplicate return ids are invalid input: exit `2`, write exactly one JSON error object `{ "error": "duplicate_return_id", "id": string }` to stderr, and write no stdout.
|
|
26
|
+
- [ ] Process returns globally by `priority` descending, then original input order ascending.
|
|
27
|
+
- [ ] A return with an unknown category rejects with reason `unknown_category` and does not change capacity.
|
|
28
|
+
- [ ] For a known category, choose the target destination by this rule order: `damaged` condition routes to `dispose`; otherwise, if `days_since_purchase` is greater than `restock_window_days`, route to `dispose`; otherwise `sealed` routes to `restock`; otherwise `opened` routes to `refurbish`.
|
|
29
|
+
- [ ] A return rejects with reason `unsupported_condition` when condition is not `sealed`, `opened`, or `damaged`, and does not change capacity.
|
|
30
|
+
- [ ] A return accepts only when the chosen destination exists in `capacity` and has positive remaining capacity. On accept, decrement that destination by `1`.
|
|
31
|
+
- [ ] A return rejects with reason `destination_full` when the chosen destination is absent from `capacity` or has zero remaining capacity.
|
|
32
|
+
- [ ] `routed` is ordered by processing order. Each row has keys `id`, `destination`.
|
|
33
|
+
- [ ] `rejected` is ordered in the original input order. Each row has keys `id`, `reason`.
|
|
34
|
+
- [ ] `capacity` is an object whose keys are sorted alphabetically and whose values are remaining capacities.
|
|
35
|
+
- [ ] On success, write exactly one JSON object to stdout and no stderr. Keys: `routed`, `rejected`, `capacity`.
|
|
36
|
+
|
|
37
|
+
## Constraints
|
|
38
|
+
|
|
39
|
+
- Use only Node.js built-ins; add no npm dependencies.
|
|
40
|
+
- Touch only `bin/cli.js` and `tests/cli.test.js`.
|
|
41
|
+
- Do not silently catch JSON parse or validation errors. Surface invalid input as a user-visible error with nonzero exit.
|
|
42
|
+
- Do not persist destination capacity between command invocations.
|
|
43
|
+
|
|
44
|
+
## Out of Scope
|
|
45
|
+
|
|
46
|
+
- Reading input from files.
|
|
47
|
+
- SKU catalogs, refund amounts, shipping labels, or warehouse zones.
|
|
48
|
+
- Changing `hello`, `version`, server routes, or package metadata.
|
|
49
|
+
|
|
50
|
+
## Verification
|
|
51
|
+
|
|
52
|
+
- `node --test tests/cli.test.js` passes.
|
|
53
|
+
- `node "$BENCH_FIXTURE_DIR/verifiers/priority-return-routing.js"` prints `{"ok":true}`.
|
|
54
|
+
- `node "$BENCH_FIXTURE_DIR/verifiers/duplicate-return-error.js"` prints `{"ok":true}`.
|
|
55
|
+
- Solo-headroom hypothesis: solo_claude is expected to miss destination policy precedence or capacity mutation under priority routing; observable command `node "$BENCH_FIXTURE_DIR/verifiers/priority-return-routing.js"` exposes the miss.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Add a `route-returns` CLI command. It should accept `--policies <json>`, `--capacity <json>`, and `--returns <json>`, route known-category returns by condition/window policy, process higher priority returns first, decrement destination capacity only for accepted returns, reject duplicate return ids with exit 2 and a JSON error, and print exactly one JSON object with `routed`, `rejected`, and `capacity`. Use no new dependencies and update CLI tests.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const work = process.env.BENCH_WORKDIR || process.cwd();
|
|
7
|
+
const cli = path.join(work, 'bin', 'cli.js');
|
|
8
|
+
|
|
9
|
+
const policies = JSON.stringify([
|
|
10
|
+
{
|
|
11
|
+
category: 'electronics',
|
|
12
|
+
restock_window_days: 30,
|
|
13
|
+
destinations: { restock: 'restock-a', refurbish: 'refurb-a', dispose: 'dispose-a' }
|
|
14
|
+
}
|
|
15
|
+
]);
|
|
16
|
+
const capacity = JSON.stringify({ 'restock-a': 1 });
|
|
17
|
+
const returns = JSON.stringify([
|
|
18
|
+
{ id: 'dup', category: 'electronics', condition: 'sealed', days_since_purchase: 1, priority: 2 },
|
|
19
|
+
{ id: 'dup', category: 'electronics', condition: 'opened', days_since_purchase: 1, priority: 1 }
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const result = spawnSync('node', [
|
|
23
|
+
cli,
|
|
24
|
+
'route-returns',
|
|
25
|
+
'--policies',
|
|
26
|
+
policies,
|
|
27
|
+
'--capacity',
|
|
28
|
+
capacity,
|
|
29
|
+
'--returns',
|
|
30
|
+
returns
|
|
31
|
+
], {
|
|
32
|
+
cwd: work,
|
|
33
|
+
encoding: 'utf8'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
assert.strictEqual(result.status, 2);
|
|
37
|
+
assert.strictEqual(result.stdout, '');
|
|
38
|
+
assert.deepStrictEqual(JSON.parse(result.stderr), {
|
|
39
|
+
error: 'duplicate_return_id',
|
|
40
|
+
id: 'dup'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(JSON.stringify({ ok: true }));
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const work = process.env.BENCH_WORKDIR || process.cwd();
|
|
7
|
+
const cli = path.join(work, 'bin', 'cli.js');
|
|
8
|
+
|
|
9
|
+
const policies = JSON.stringify([
|
|
10
|
+
{
|
|
11
|
+
category: 'electronics',
|
|
12
|
+
restock_window_days: 30,
|
|
13
|
+
destinations: {
|
|
14
|
+
restock: 'restock-a',
|
|
15
|
+
refurbish: 'refurb-a',
|
|
16
|
+
dispose: 'dispose-a'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]);
|
|
20
|
+
const capacity = JSON.stringify({
|
|
21
|
+
'dispose-a': 1,
|
|
22
|
+
'refurb-a': 1,
|
|
23
|
+
'restock-a': 1
|
|
24
|
+
});
|
|
25
|
+
const returns = JSON.stringify([
|
|
26
|
+
{ id: 'low-sealed', category: 'electronics', condition: 'sealed', days_since_purchase: 10, priority: 1 },
|
|
27
|
+
{ id: 'vip-opened', category: 'electronics', condition: 'opened', days_since_purchase: 20, priority: 10 },
|
|
28
|
+
{ id: 'vip-damaged', category: 'electronics', condition: 'damaged', days_since_purchase: 5, priority: 9 },
|
|
29
|
+
{ id: 'std-sealed', category: 'electronics', condition: 'sealed', days_since_purchase: 10, priority: 5 },
|
|
30
|
+
{ id: 'late-sealed', category: 'electronics', condition: 'sealed', days_since_purchase: 10, priority: 4 },
|
|
31
|
+
{ id: 'unknown-cat', category: 'furniture', condition: 'sealed', days_since_purchase: 1, priority: 3 }
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const result = spawnSync('node', [
|
|
35
|
+
cli,
|
|
36
|
+
'route-returns',
|
|
37
|
+
'--policies',
|
|
38
|
+
policies,
|
|
39
|
+
'--capacity',
|
|
40
|
+
capacity,
|
|
41
|
+
'--returns',
|
|
42
|
+
returns
|
|
43
|
+
], {
|
|
44
|
+
cwd: work,
|
|
45
|
+
encoding: 'utf8'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
49
|
+
assert.strictEqual(result.stderr, '');
|
|
50
|
+
const parsed = JSON.parse(result.stdout);
|
|
51
|
+
|
|
52
|
+
assert.deepStrictEqual(parsed, {
|
|
53
|
+
routed: [
|
|
54
|
+
{ id: 'vip-opened', destination: 'refurb-a' },
|
|
55
|
+
{ id: 'vip-damaged', destination: 'dispose-a' },
|
|
56
|
+
{ id: 'std-sealed', destination: 'restock-a' }
|
|
57
|
+
],
|
|
58
|
+
rejected: [
|
|
59
|
+
{ id: 'low-sealed', reason: 'destination_full' },
|
|
60
|
+
{ id: 'late-sealed', reason: 'destination_full' },
|
|
61
|
+
{ id: 'unknown-cat', reason: 'unknown_category' }
|
|
62
|
+
],
|
|
63
|
+
capacity: {
|
|
64
|
+
'dispose-a': 0,
|
|
65
|
+
'refurb-a': 0,
|
|
66
|
+
'restock-a': 0
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log(JSON.stringify({ ok: true }));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# S5-cli-credit-grant-ledger NOTES
|
|
2
|
+
|
|
3
|
+
## What failure mode does this fixture detect?
|
|
4
|
+
|
|
5
|
+
Billing credit settlement where date ordering, same-day priority, grant
|
|
6
|
+
expiration, account isolation, and mutable grant balances interact. Bare or
|
|
7
|
+
single-pass implementations commonly process input order, use expired grants,
|
|
8
|
+
let one account consume another account's grant, or compute balances without
|
|
9
|
+
respecting earlier charge consumption.
|
|
10
|
+
|
|
11
|
+
## What pipeline phase(s) is this testing?
|
|
12
|
+
|
|
13
|
+
- **PLAN / RISK_PROBES**: must notice charge ordering, expiration boundaries,
|
|
14
|
+
grant consumption order, account scoping, mutation, duplicate id handling, and
|
|
15
|
+
exact output-shape contracts.
|
|
16
|
+
- **IMPLEMENT**: must add a CLI command without broadening scope or adding deps.
|
|
17
|
+
- **VERIFY**: hidden oracles exercise compound ledger behavior that simple unit
|
|
18
|
+
tests often miss.
|
|
19
|
+
|
|
20
|
+
## Why can't another fixture cover this?
|
|
21
|
+
|
|
22
|
+
S2 covers inventory reservation, S3 covers agent assignment, and S4 covers return
|
|
23
|
+
routing. S5 adds money-like credit balances with date-bounded grant eligibility,
|
|
24
|
+
which catches a different class of ledger mutation and ordering failures.
|
|
25
|
+
|
|
26
|
+
## When should this fixture be retired?
|
|
27
|
+
|
|
28
|
+
Retire or replace it if two consecutive measured runs show both bare and
|
|
29
|
+
solo_claude consistently satisfy charge ordering, account scoping, expiration
|
|
30
|
+
boundaries, mutable grant balances, duplicate id handling, and exact output
|
|
31
|
+
shape without pair assistance.
|
|
32
|
+
|
|
33
|
+
## Calibration status
|
|
34
|
+
|
|
35
|
+
- `20260513-s5-credit-headroom`: bare `33`, solo_claude `98`, headroom gate
|
|
36
|
+
`FAIL` because solo exceeded the `80` ceiling and timed out. Treat S5 as a
|
|
37
|
+
shadow control unless it is reworked to preserve solo_claude headroom.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"verification_commands": [
|
|
3
|
+
{
|
|
4
|
+
"cmd": "node --test tests/cli.test.js",
|
|
5
|
+
"exit_code": 0,
|
|
6
|
+
"stdout_contains": [],
|
|
7
|
+
"stdout_not_contains": ["not ok "]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/credit-ledger-priority.js\"",
|
|
11
|
+
"exit_code": 0,
|
|
12
|
+
"stdout_contains": ["\"ok\":true"],
|
|
13
|
+
"stdout_not_contains": [],
|
|
14
|
+
"contract_refs": [
|
|
15
|
+
"Process charges globally by `occurred_on` ascending, then `priority` descending, then original input order ascending.",
|
|
16
|
+
"A grant is usable for a charge only when it belongs to the same `account`, has positive remaining `cents`, and `expires_on` is greater than or equal to the charge's `occurred_on`.",
|
|
17
|
+
"For each charge, consume usable grants by `expires_on` ascending, then grant `id` ascending, until the charge is fully covered or no usable grant remains.",
|
|
18
|
+
"Consuming a grant decrements that grant's remaining `cents` immediately; later charges see the reduced balance.",
|
|
19
|
+
"Each `settled` row is ordered by processing order and has keys `id`, `covered_cents`, `uncovered_cents`, and `grants`.",
|
|
20
|
+
"`balances` is ordered by grant `id` ascending. Each row has keys `id` and `remaining_cents`.",
|
|
21
|
+
"`expired` is ordered by grant `id` ascending and includes grants with positive remaining `cents` whose `expires_on` is less than `--as-of`.",
|
|
22
|
+
"On success, write exactly one JSON object to stdout and no stderr. Keys: `settled`, `balances`, `expired`."
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/duplicate-charge-error.js\"",
|
|
27
|
+
"exit_code": 0,
|
|
28
|
+
"stdout_contains": ["\"ok\":true"],
|
|
29
|
+
"stdout_not_contains": [],
|
|
30
|
+
"contract_refs": [
|
|
31
|
+
"Before settling any charge, duplicate charge ids are invalid input: exit `2`, write exactly one JSON error object `{ \"error\": \"duplicate_charge_id\", \"id\": string }` to stderr, and write no stdout."
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"forbidden_patterns": [
|
|
36
|
+
{
|
|
37
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{[^}]*return\\s+(null|undefined|''|\\{\\}|\\[\\])",
|
|
38
|
+
"description": "silent catch returning fallback in settle-credits path",
|
|
39
|
+
"files": ["bin/cli.js", "tests/cli.test.js"],
|
|
40
|
+
"severity": "disqualifier"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}",
|
|
44
|
+
"description": "empty catch block",
|
|
45
|
+
"files": ["bin/cli.js", "tests/cli.test.js"],
|
|
46
|
+
"severity": "disqualifier"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"required_files": ["bin/cli.js", "tests/cli.test.js"],
|
|
50
|
+
"forbidden_files": [],
|
|
51
|
+
"tier_a_waivers": [],
|
|
52
|
+
"spec_output_files": ["bin/cli.js", "tests/cli.test.js"],
|
|
53
|
+
"max_deps_added": 0
|
|
54
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S5-cli-credit-grant-ledger",
|
|
3
|
+
"category": "high-risk",
|
|
4
|
+
"difficulty": "high",
|
|
5
|
+
"timeout_seconds": 900,
|
|
6
|
+
"required_tools": ["node"],
|
|
7
|
+
"browser": false,
|
|
8
|
+
"deps_change_expected": false,
|
|
9
|
+
"intent": "Add a credit grant ledger CLI command that applies account-scoped grant expiration, priority-ordered charges, mutable balances, duplicate charge rejection, and exact JSON output shape."
|
|
10
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "S5-cli-credit-grant-ledger"
|
|
3
|
+
title: "Add credit grant ledger command"
|
|
4
|
+
status: planned
|
|
5
|
+
complexity: high
|
|
6
|
+
depends-on: []
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# S5 Add Credit Grant Ledger Command
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
Billing operations needs a deterministic CLI command that settles account
|
|
14
|
+
charges against expiring promotional credit grants. The command must combine
|
|
15
|
+
account isolation, date ordering, same-day priority, grant expiration, mutable
|
|
16
|
+
balances, duplicate id rejection, and exact machine-readable output.
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- [ ] Add `settle-credits` to `bin/cli.js`.
|
|
21
|
+
- [ ] Accept `--grants <json>` as a JSON array of grant objects. Each grant has
|
|
22
|
+
keys `id`, `account`, `cents`, and `expires_on`.
|
|
23
|
+
- [ ] Accept `--charges <json>` as a JSON array of charge objects. Each charge
|
|
24
|
+
has keys `id`, `account`, `cents`, `occurred_on`, and `priority`.
|
|
25
|
+
- [ ] Accept `--as-of <YYYY-MM-DD>` as the ledger close date.
|
|
26
|
+
- [ ] Before settling any charge, duplicate charge ids are invalid input: exit `2`, write exactly one JSON error object `{ "error": "duplicate_charge_id", "id": string }` to stderr, and write no stdout.
|
|
27
|
+
- [ ] Process charges globally by `occurred_on` ascending, then `priority` descending, then original input order ascending.
|
|
28
|
+
- [ ] A grant is usable for a charge only when it belongs to the same `account`, has positive remaining `cents`, and `expires_on` is greater than or equal to the charge's `occurred_on`.
|
|
29
|
+
- [ ] For each charge, consume usable grants by `expires_on` ascending, then grant `id` ascending, until the charge is fully covered or no usable grant remains.
|
|
30
|
+
- [ ] Consuming a grant decrements that grant's remaining `cents` immediately; later charges see the reduced balance.
|
|
31
|
+
- [ ] Each `settled` row is ordered by processing order and has keys `id`, `covered_cents`, `uncovered_cents`, and `grants`.
|
|
32
|
+
- [ ] Each row's `grants` array is ordered by actual consumption order. Each
|
|
33
|
+
item has keys `id` and `cents`.
|
|
34
|
+
- [ ] `balances` is ordered by grant `id` ascending. Each row has keys `id` and `remaining_cents`.
|
|
35
|
+
- [ ] `expired` is ordered by grant `id` ascending and includes grants with positive remaining `cents` whose `expires_on` is less than `--as-of`. Each row has keys `id` and `remaining_cents`.
|
|
36
|
+
- [ ] On success, write exactly one JSON object to stdout and no stderr. Keys: `settled`, `balances`, `expired`.
|
|
37
|
+
|
|
38
|
+
## Constraints
|
|
39
|
+
|
|
40
|
+
- Use only Node.js built-ins; add no npm dependencies.
|
|
41
|
+
- Touch only `bin/cli.js` and `tests/cli.test.js`.
|
|
42
|
+
- Do not silently catch JSON parse or validation errors. Surface invalid input
|
|
43
|
+
as a user-visible error with nonzero exit.
|
|
44
|
+
- Do not persist grant balances between command invocations.
|
|
45
|
+
|
|
46
|
+
## Out of Scope
|
|
47
|
+
|
|
48
|
+
- Reading input from files.
|
|
49
|
+
- Taxes, invoices, refunds, currency conversion, or account creation.
|
|
50
|
+
- Changing `hello`, `version`, server routes, or package metadata.
|
|
51
|
+
|
|
52
|
+
## Verification
|
|
53
|
+
|
|
54
|
+
- `node --test tests/cli.test.js` passes.
|
|
55
|
+
- `node "$BENCH_FIXTURE_DIR/verifiers/credit-ledger-priority.js"` prints
|
|
56
|
+
`{"ok":true}`.
|
|
57
|
+
- `node "$BENCH_FIXTURE_DIR/verifiers/duplicate-charge-error.js"` prints
|
|
58
|
+
`{"ok":true}`.
|
|
59
|
+
- Solo-headroom hypothesis: solo_claude is expected to miss mutable grant balances across priority/date-ordered charges; observable command `node "$BENCH_FIXTURE_DIR/verifiers/credit-ledger-priority.js"` exposes the miss.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Add a `settle-credits` CLI command. It should accept `--grants <json>`, `--charges <json>`, and `--as-of <YYYY-MM-DD>`, settle account-scoped charges against unexpired credit grants, process earlier charge dates first with higher priority first on the same date, consume grants by earliest expiration then grant id, reject duplicate charge ids with exit 2 and a JSON error, and print exactly one JSON object with `settled`, `balances`, and `expired`. Use no new dependencies and update CLI tests.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const work = process.env.BENCH_WORKDIR || process.cwd();
|
|
7
|
+
const cli = path.join(work, 'bin', 'cli.js');
|
|
8
|
+
|
|
9
|
+
const grants = JSON.stringify([
|
|
10
|
+
{ id: 'g-late', account: 'acct-1', cents: 700, expires_on: '2026-03-31' },
|
|
11
|
+
{ id: 'g-early-b', account: 'acct-1', cents: 300, expires_on: '2026-01-31' },
|
|
12
|
+
{ id: 'g-early-a', account: 'acct-1', cents: 500, expires_on: '2026-01-31' },
|
|
13
|
+
{ id: 'g-other', account: 'acct-2', cents: 400, expires_on: '2026-01-31' },
|
|
14
|
+
{ id: 'g-expired-unused', account: 'acct-1', cents: 250, expires_on: '2026-01-05' }
|
|
15
|
+
]);
|
|
16
|
+
const charges = JSON.stringify([
|
|
17
|
+
{ id: 'late-low', account: 'acct-1', cents: 500, occurred_on: '2026-01-20', priority: 1 },
|
|
18
|
+
{ id: 'early', account: 'acct-1', cents: 450, occurred_on: '2026-01-10', priority: 1 },
|
|
19
|
+
{ id: 'same-day-high', account: 'acct-1', cents: 600, occurred_on: '2026-01-20', priority: 9 },
|
|
20
|
+
{ id: 'other-account', account: 'acct-2', cents: 350, occurred_on: '2026-01-20', priority: 10 },
|
|
21
|
+
{ id: 'after-expiry', account: 'acct-1', cents: 500, occurred_on: '2026-02-02', priority: 10 }
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const result = spawnSync('node', [
|
|
25
|
+
cli,
|
|
26
|
+
'settle-credits',
|
|
27
|
+
'--grants',
|
|
28
|
+
grants,
|
|
29
|
+
'--charges',
|
|
30
|
+
charges,
|
|
31
|
+
'--as-of',
|
|
32
|
+
'2026-04-01'
|
|
33
|
+
], {
|
|
34
|
+
cwd: work,
|
|
35
|
+
encoding: 'utf8'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
39
|
+
assert.strictEqual(result.stderr, '');
|
|
40
|
+
const parsed = JSON.parse(result.stdout);
|
|
41
|
+
|
|
42
|
+
assert.deepStrictEqual(parsed, {
|
|
43
|
+
settled: [
|
|
44
|
+
{
|
|
45
|
+
id: 'early',
|
|
46
|
+
covered_cents: 450,
|
|
47
|
+
uncovered_cents: 0,
|
|
48
|
+
grants: [
|
|
49
|
+
{ id: 'g-early-a', cents: 450 }
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'other-account',
|
|
54
|
+
covered_cents: 350,
|
|
55
|
+
uncovered_cents: 0,
|
|
56
|
+
grants: [
|
|
57
|
+
{ id: 'g-other', cents: 350 }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'same-day-high',
|
|
62
|
+
covered_cents: 600,
|
|
63
|
+
uncovered_cents: 0,
|
|
64
|
+
grants: [
|
|
65
|
+
{ id: 'g-early-a', cents: 50 },
|
|
66
|
+
{ id: 'g-early-b', cents: 300 },
|
|
67
|
+
{ id: 'g-late', cents: 250 }
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'late-low',
|
|
72
|
+
covered_cents: 450,
|
|
73
|
+
uncovered_cents: 50,
|
|
74
|
+
grants: [
|
|
75
|
+
{ id: 'g-late', cents: 450 }
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'after-expiry',
|
|
80
|
+
covered_cents: 0,
|
|
81
|
+
uncovered_cents: 500,
|
|
82
|
+
grants: []
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
balances: [
|
|
86
|
+
{ id: 'g-early-a', remaining_cents: 0 },
|
|
87
|
+
{ id: 'g-early-b', remaining_cents: 0 },
|
|
88
|
+
{ id: 'g-expired-unused', remaining_cents: 250 },
|
|
89
|
+
{ id: 'g-late', remaining_cents: 0 },
|
|
90
|
+
{ id: 'g-other', remaining_cents: 50 }
|
|
91
|
+
],
|
|
92
|
+
expired: [
|
|
93
|
+
{ id: 'g-expired-unused', remaining_cents: 250 },
|
|
94
|
+
{ id: 'g-other', remaining_cents: 50 }
|
|
95
|
+
]
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
console.log(JSON.stringify({ ok: true }));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const work = process.env.BENCH_WORKDIR || process.cwd();
|
|
7
|
+
const cli = path.join(work, 'bin', 'cli.js');
|
|
8
|
+
|
|
9
|
+
const grants = JSON.stringify([
|
|
10
|
+
{ id: 'g1', account: 'acct-1', cents: 100, expires_on: '2026-01-31' }
|
|
11
|
+
]);
|
|
12
|
+
const charges = JSON.stringify([
|
|
13
|
+
{ id: 'dup', account: 'acct-1', cents: 50, occurred_on: '2026-01-10', priority: 1 },
|
|
14
|
+
{ id: 'dup', account: 'acct-1', cents: 50, occurred_on: '2026-01-11', priority: 2 }
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const result = spawnSync('node', [
|
|
18
|
+
cli,
|
|
19
|
+
'settle-credits',
|
|
20
|
+
'--grants',
|
|
21
|
+
grants,
|
|
22
|
+
'--charges',
|
|
23
|
+
charges,
|
|
24
|
+
'--as-of',
|
|
25
|
+
'2026-02-01'
|
|
26
|
+
], {
|
|
27
|
+
cwd: work,
|
|
28
|
+
encoding: 'utf8'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
assert.strictEqual(result.status, 2);
|
|
32
|
+
assert.strictEqual(result.stdout, '');
|
|
33
|
+
assert.deepStrictEqual(JSON.parse(result.stderr), {
|
|
34
|
+
error: 'duplicate_charge_id',
|
|
35
|
+
id: 'dup'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log(JSON.stringify({ ok: true }));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# S6-cli-refund-window-ledger NOTES
|
|
2
|
+
|
|
3
|
+
## What failure mode does this fixture detect?
|
|
4
|
+
|
|
5
|
+
Refund settlement where category windows, priority ordering, mutable per-order
|
|
6
|
+
remaining refundable cents, fee calculation, and exact output-shape obligations
|
|
7
|
+
interact. Bare or single-pass implementations commonly process input order,
|
|
8
|
+
forget that an earlier high-priority refund consumes later refundable balance,
|
|
9
|
+
or report rejected rows in processing order.
|
|
10
|
+
|
|
11
|
+
## What pipeline phase(s) is this testing?
|
|
12
|
+
|
|
13
|
+
- **PLAN / RISK_PROBES**: must notice priority ordering, date-window policy,
|
|
14
|
+
mutable per-order balances, duplicate id handling, and output-shape contracts.
|
|
15
|
+
- **IMPLEMENT**: must add a CLI command without broadening scope or adding deps.
|
|
16
|
+
- **VERIFY**: hidden oracles exercise compound refund ledger behavior that
|
|
17
|
+
simple unit tests often miss.
|
|
18
|
+
|
|
19
|
+
## Why can't another fixture cover this?
|
|
20
|
+
|
|
21
|
+
S5 covers credit grant consumption by charge date. S6 flips the money-like
|
|
22
|
+
mutation to customer refunds, where priority ordering and refund-window policy
|
|
23
|
+
jointly decide whether a later input row consumes the balance first.
|
|
24
|
+
|
|
25
|
+
## When should this fixture be retired?
|
|
26
|
+
|
|
27
|
+
Retire or replace it if two consecutive measured runs show both bare and
|
|
28
|
+
solo_claude consistently satisfy priority ordering, refund-window policy,
|
|
29
|
+
cumulative refundable balances, duplicate id handling, and exact output shape
|
|
30
|
+
without pair assistance.
|
|
31
|
+
|
|
32
|
+
## Calibration status
|
|
33
|
+
|
|
34
|
+
- `20260514-s6-refund-headroom-v1`: bare `33`, solo_claude `98`, headroom
|
|
35
|
+
gate `FAIL` because solo exceeded the `80` ceiling and timed out. Treat S6 as
|
|
36
|
+
a shadow control unless it is reworked to preserve solo_claude headroom.
|