devlyn-cli 2.7.0 → 2.8.0
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 +9 -1
- package/CLAUDE.md +28 -11
- package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/task.ko.txt +8 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/NOTES.md +28 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/RETIRED.md +15 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/expected.json +57 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/setup.sh +50 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/spec.md +59 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/ordered-settlement.js +88 -0
- package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/reject-invalid.js +45 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/NOTES.md +30 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/RETIRED.md +16 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/expected.json +64 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/setup.sh +21 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/spec.md +58 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/dup-op.js +54 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/priority-apply.js +82 -0
- package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/rollback.js +77 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/NOTES.md +99 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/RETIRED.md +18 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/expected.json +64 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/setup.sh +4 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/spec.md +61 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/correctness-small.js +135 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/scale.js +100 -0
- package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/validation.js +75 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/NOTES.md +93 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/RETIRED.md +16 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/expected.json +65 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/metadata.json +10 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/setup.sh +18 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/spec.md +66 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/task.txt +9 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/correctness-small.js +84 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/scale.js +184 -0
- package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/validation.js +74 -0
- package/benchmark/auto-resolve/scripts/pair-rejected-fixtures.sh +12 -0
- package/benchmark/auto-resolve/scripts/run-fixture.sh +39 -1
- package/benchmark/auto-resolve/scripts/test-lint-fixtures.sh +1 -1
- package/bin/devlyn.js +42 -1
- package/config/skills/_shared/adapters/README.md +7 -0
- package/config/skills/_shared/adapters/claude.md +47 -0
- package/config/skills/_shared/adapters/omp.md +17 -0
- package/config/skills/_shared/archive_run.py +8 -2
- package/config/skills/_shared/codex-config.md +22 -6
- package/config/skills/_shared/engine-doctor.sh +107 -0
- package/config/skills/_shared/engine-preflight.md +18 -11
- package/config/skills/_shared/expected.schema.json +26 -0
- package/config/skills/_shared/runtime-principles.md +1 -1
- package/config/skills/_shared/spec-verify-check.py +773 -486
- package/config/skills/_shared/state-phase-write.py +374 -0
- package/config/skills/_shared/verify-merge-findings.py +177 -61
- package/config/skills/devlyn:engines/SKILL.md +47 -0
- package/config/skills/devlyn:ideate/SKILL.md +22 -3
- package/config/skills/devlyn:ideate/references/elicitation.md +2 -2
- package/config/skills/devlyn:ideate/references/from-spec-mode.md +2 -2
- package/config/skills/devlyn:ideate/references/project-mode.md +1 -1
- package/config/skills/devlyn:ideate/references/spec-template.md +15 -4
- package/config/skills/devlyn:queue/SKILL.md +26 -0
- package/config/skills/devlyn:resolve/SKILL.md +54 -24
- package/config/skills/devlyn:resolve/references/free-form-mode.md +1 -1
- package/config/skills/devlyn:resolve/references/phases/build-gate.md +2 -2
- package/config/skills/devlyn:resolve/references/phases/cleanup.md +1 -1
- package/config/skills/devlyn:resolve/references/phases/implement.md +1 -1
- package/config/skills/devlyn:resolve/references/phases/plan.md +3 -2
- package/config/skills/devlyn:resolve/references/phases/probe-derive.md +8 -0
- package/config/skills/devlyn:resolve/references/phases/verify.md +18 -9
- package/config/skills/devlyn:resolve/references/state-schema.md +10 -6
- package/optional-skills/devlyn:pencil-pull/SKILL.md +5 -0
- package/optional-skills/devlyn:pencil-push/SKILL.md +5 -0
- package/package.json +1 -1
- package/scripts/lint-skills.sh +48 -41
package/AGENTS.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
devlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. Codex CLI reads this file when invoked inside a project that has it. The principles are non-negotiable on every change.
|
|
4
4
|
|
|
5
|
+
## North Star
|
|
6
|
+
|
|
7
|
+
This contract serves one goal: any capable engine — Claude, GPT/Codex, or a future adapter-equipped model — takes a user's intent (prompt, spec, or queue entry) end-to-end to shipped, engineer-quality software, hands-free, with consistent quality across engines. The harness must measurably out-earn bare prompting, and every added layer (pair mode, probes, gates) must out-earn the layer below it. When rules below conflict or feel ambiguous in context, resolve toward this goal.
|
|
8
|
+
|
|
5
9
|
## Core principles
|
|
6
10
|
|
|
7
11
|
Seven rules govern every change. Cite them by name when a decision touches one.
|
|
@@ -32,6 +36,10 @@ ideate (optional) -> resolve -> ship
|
|
|
32
36
|
|
|
33
37
|
Each skill's `SKILL.md` is the source of truth for flags and workflow. Do not duplicate.
|
|
34
38
|
|
|
39
|
+
Engine roles: orchestrator = whichever CLI the user opened (this contract is symmetric with CLAUDE.md; measured status 2026-07-05: Claude Code + omp run the full phase-gated pipeline; in the iter-0061 minimal-repo trivial-add shape on this machine, Codex CLI also ran it when this AGENTS.md was present in the project (ordinary invocation, 4/4); without project AGENTS.md, the same shape silently skipped the pipeline (iter-0040 F6, 4/4). If you are Codex orchestrating `/devlyn:resolve`, the phase machinery is mandatory regardless of task size; if you will not run it, say so explicitly and stop — do not silently degrade to ad-hoc execution); executor (PLAN/IMPLEMENT/CLEANUP) defaults to `claude`, overridable per run with `--engine <name>` or durably via machine-local `.devlyn/engines.json` `{"executor": "<name>", "pair_judge_priority": ["<name>", ...]}`; pair judge = first available OTHER engine. Pins fail closed (`BLOCKED:<engine>-unavailable` / `BLOCKED:invalid-engine-config`); new engines plug in by shipping `_shared/adapters/<name>.md`. `/devlyn:engines` (no args) shows the role table + detected engines; `executor <name>` / `pair <name>,...` / `clear` manage the pins.
|
|
40
|
+
|
|
41
|
+
Conversational handoff + loop engineering (default entry): the orchestrating model — not the user — invokes the skills. `/devlyn:queue` fronts the intent queue (`docs/specs/queue.md`): no args = status, `add <intent>`, `drain` = serial unattended drain per the contract below. Large agreed work is written to `docs/specs/<id>/spec.md` (always a spec file; `--goal-file` is trivial/medium-only), summarized once for user review, then run hands-free via `--spec`. Outer loop per task: findings-backed verdicts (NEEDS_WORK, verify/build-gate exhaustion) get spec amendment + re-run, max 3 iterations; infrastructure / invalid-input / engine-availability / implement-empty BLOCKED verdicts surface immediately. Unattended queue drain (`docs/specs/queue.md`, strictly serial): assumptions may only take scope-narrowing, reversible, non-user-visible defaults — material ambiguity marks the item `[F] needs-review` and the drain continues.
|
|
42
|
+
|
|
35
43
|
## Subtractive-First Editing
|
|
36
44
|
|
|
37
45
|
Before writing any change, answer in this order:
|
|
@@ -56,7 +64,7 @@ Default mode is execution toward the user's stated goal. Do not drift.
|
|
|
56
64
|
Refuse these patterns:
|
|
57
65
|
|
|
58
66
|
- Unrequested work ("while here, also fix..."). Pre-existing dead code → mention only, do NOT delete. Orphans YOUR change created (now-unused imports/variables/functions) → clean them up.
|
|
59
|
-
- Tangential cleanup in files the task does not require. Match existing style even if you'd write it differently;
|
|
67
|
+
- Tangential cleanup in files the task does not require. Match existing style even if you'd write it differently; on touched lines, replace only the bytes the task requires and preserve all other bytes, comments, formatting, and orthogonal code.
|
|
60
68
|
- Speculative robustness for cases not observed in production, tests, findings, or the spec.
|
|
61
69
|
- Mid-flight re-scoping without user approval.
|
|
62
70
|
- Curiosity exploration that is not on the critical path.
|
package/CLAUDE.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
devlyn-cli installs `/devlyn:ideate` (optional) and `/devlyn:resolve` (required) into Claude Code, plus the contract below. These principles are non-negotiable on every change — yours and any sub-agent's.
|
|
4
4
|
|
|
5
|
+
## North Star
|
|
6
|
+
|
|
7
|
+
This contract serves one goal: any capable engine — Claude, GPT/Codex, or a future adapter-equipped model — takes a user's intent (prompt, spec, or queue entry) end-to-end to shipped, engineer-quality software, hands-free, with consistent quality across engines. The harness must measurably out-earn bare prompting, and every added layer (pair mode, probes, gates) must out-earn the layer below it. When rules below conflict or feel ambiguous in context, resolve toward this goal.
|
|
8
|
+
|
|
5
9
|
## Core principles
|
|
6
10
|
|
|
7
11
|
Seven rules govern every change. Cite them by name when a decision touches one.
|
|
@@ -24,22 +28,35 @@ The runtime sub-agent contract below (Subtractive-first / Goal-locked / No-worka
|
|
|
24
28
|
|
|
25
29
|
## Quick Start
|
|
26
30
|
|
|
27
|
-
Two skills cover the full cycle. `/devlyn:ideate` is OPTIONAL; `/devlyn:resolve` is REQUIRED; `/devlyn:design-ui` is also REQUIRED as the creative UI exploration surface.
|
|
31
|
+
Two skills cover the full cycle. `/devlyn:ideate` is OPTIONAL; `/devlyn:resolve` is REQUIRED; `/devlyn:design-ui` is also REQUIRED as the creative UI exploration surface. Engine selection follows the role map below.
|
|
28
32
|
|
|
29
33
|
1. `/devlyn:ideate` (optional) — unstructured idea → `docs/specs/<id>/spec.md` + `spec.expected.json`. Modes: default Q&A, `--quick` (autonomous-pipeline-safe), `--from-spec <path>`, `--project`.
|
|
30
34
|
2. `/devlyn:resolve` — hands-free pipeline for any coding task. Free-form goal, `--spec <path>`, or `--verify-only <diff> --spec <path>`. Phases: PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY (fresh subagent, findings-only).
|
|
31
35
|
|
|
32
36
|
Each skill's `SKILL.md` is the source of truth for its flags and workflow — don't duplicate them here.
|
|
33
37
|
|
|
34
|
-
###
|
|
38
|
+
### Engine roles — auto-detected, manually pinnable
|
|
39
|
+
|
|
40
|
+
| Role | Default | Manual override |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Orchestrator — conversation, handoff, loop driving | whichever CLI you open (contract is symmetric: CLAUDE.md ↔ AGENTS.md). Measured status 2026-07-05: Claude Code + omp run the full phase-gated pipeline; in the iter-0061 minimal-repo trivial-add shape on this machine, Codex CLI also ran it when the project carried the devlyn AGENTS.md (ordinary invocation, 4/4); without project AGENTS.md, the same shape silently skipped the pipeline (iter-0040 F6, 4/4) | switch CLIs; the file artifacts (spec/queue/state) carry over |
|
|
43
|
+
| Executor — PLAN/IMPLEMENT/CLEANUP + primary VERIFY judge | `claude` | `--engine <name>` per run, or `/devlyn:engines executor <name>` (durable pin) |
|
|
44
|
+
| Pair judge — VERIFY pair-JUDGE, risk probes | first available OTHER engine (claude↔codex) | `/devlyn:engines pair <name>,...`; `--no-pair` opts out |
|
|
45
|
+
|
|
46
|
+
`/devlyn:engines` with no args shows the current role table, detected engines, and how to pin or clear — the pins live in `.devlyn/engines.json`.
|
|
47
|
+
|
|
48
|
+
`.devlyn/engines.json` is machine-local — not committed, not archived. Pins are promises: a pinned unavailable engine stops with `BLOCKED:<engine>-unavailable`; a name without a `_shared/adapters/<name>.md` adapter stops with `BLOCKED:invalid-engine-config`. New engines (GLM, pi-agent backends) plug in by shipping an adapter file — no skill changes. Codex BUILD/IMPLEMENT and PLAN-pair remain research-only paths behind explicit `--engine codex`.
|
|
49
|
+
|
|
50
|
+
### Conversational handoff + loop engineering — the default entry for all work
|
|
51
|
+
|
|
52
|
+
The user does not invoke skills manually; the orchestrating model does. Small tasks: invoke `/devlyn:resolve "<goal>"` directly. Large tasks agreed in conversation:
|
|
53
|
+
|
|
54
|
+
1. Write the agreed contract to `docs/specs/<id>/spec.md` (+ `spec.expected.json` when mechanical verifications exist). Always a spec file for large work — never `--goal-file`, which routes into `BLOCKED:large-needs-ideation`.
|
|
55
|
+
2. Present a one-screen plan-contract summary — the user's single review checkpoint, BEFORE the pipeline starts.
|
|
56
|
+
3. On go-ahead, invoke `/devlyn:resolve --spec <path>` hands-free to completion.
|
|
57
|
+
4. **Per-task outer loop**: read the terminal verdict. PASS → done. Verdicts backed by spec/verification findings (NEEDS_WORK, verify/build-gate exhaustion) → adjudicate the findings, amend the spec (recorded in the spec file; spec stays read-only inside a run), re-invoke — at most 3 outer iterations, then surface with the findings trail. Infrastructure, invalid-input, engine-availability, and implement-empty BLOCKED verdicts are not spec-amendable: surface immediately. Every iteration re-enters through durable artifacts (spec, findings, run archive), never conversation memory.
|
|
35
58
|
|
|
36
|
-
|
|
37
|
-
|-----------|---------|
|
|
38
|
-
| New project / multi-feature plan / external spec to normalize | `/devlyn:ideate` (`--project`, default, or `--from-spec`) |
|
|
39
|
-
| One-line goal you want turned into a spec autonomously | `/devlyn:ideate --quick` |
|
|
40
|
-
| Spec already in hand | `/devlyn:resolve --spec <path>` |
|
|
41
|
-
| Free-form fix / feature / refactor / debug / PR review | `/devlyn:resolve "<describe the work>"` |
|
|
42
|
-
| Verify a diff or PR against a spec | `/devlyn:resolve --verify-only <ref> --spec <path>` |
|
|
59
|
+
**Intent queue (unattended drain)**: the user stacks intents in `docs/specs/queue.md` (ordered checklist; `/devlyn:queue` is the front-end — no args shows status, `add <intent>` appends, `drain` starts the serial drain). Drain strictly serially — per item: spec it, run the outer loop, mark `[x]` done or `[F]` blocked with reason, continue; a blocked item never halts the queue. The queue entry is the user's go-ahead, so assume-and-log replaces the interactive checkpoint — but unattended assumptions may only take scope-narrowing, reversible, non-user-visible defaults. Material ambiguity (user-visible behavior, data/state semantics, new files/scripts/flags, implementation surface) → mark `[F] needs-review` and move on. End the drain with a per-item verdict + assumptions report.
|
|
43
60
|
|
|
44
61
|
### Subtractive-first editing — perfection = nothing left to remove
|
|
45
62
|
<!-- runtime-principles:section=subtractive-first:begin -->
|
|
@@ -87,7 +104,7 @@ This rule exists because LLMs (including you) are trained to be helpful, compreh
|
|
|
87
104
|
**The five drift patterns you must refuse to execute on:**
|
|
88
105
|
|
|
89
106
|
1. **Unrequested work.** "While I'm here, I noticed X is broken/ugly/inefficient" → **stop**. The user did not ask for X. If X is a real defect, surface it as a finding, a follow-up suggestion, or an entry in a TODO list — do NOT fix it inside the current change. Mixing unrequested work with requested work is what makes diffs unreviewable and PRs eternal. **Pre-existing dead code → mention only, do NOT delete; orphans YOUR change created (now-unused imports, variables, functions) → clean them up.**
|
|
90
|
-
2. **Tangential cleanup.** "This file looks messy, let me also tidy..." → **stop**. The current task is the only task. Unrelated cleanup is a separate change requiring its own justification, scope, and pre-flight 0 check. **Match existing style even if you'd write it differently;
|
|
107
|
+
2. **Tangential cleanup.** "This file looks messy, let me also tidy..." → **stop**. The current task is the only task. Unrelated cleanup is a separate change requiring its own justification, scope, and pre-flight 0 check. **Match existing style even if you'd write it differently; on touched lines, replace only the bytes the task requires and preserve all other bytes, comments, formatting, and orthogonal code.**
|
|
91
108
|
3. **Speculative robustness.** "Just adding a check / fallback / handler for the case where..." → **stop**. If the case has not been observed (in production, in tests, in a finding), it does not belong in this change. Defensive code added for unobserved cases is the most common form of accretion debt — it never gets removed because nobody can prove the case never happens.
|
|
92
109
|
4. **Re-scoping mid-flight.** "Actually, the better way to do this is to also restructure / rename / migrate..." → **stop**. If you discover the requested approach is wrong, surface that to the user with evidence and let them adjudicate. Do NOT silently expand scope. The user's explicit redirect is the only authorization to enlarge a task.
|
|
93
110
|
5. **Curiosity detours.** "Let me also explore how Y works to understand this better..." → **stop**, unless Y is provably on the goal's critical path. Curiosity-driven exploration is creative-mode; default is execution-mode.
|
|
@@ -146,7 +163,7 @@ When `/devlyn:resolve` or `/devlyn:ideate` route a phase to Codex (`--engine cod
|
|
|
146
163
|
|
|
147
164
|
## Skill Boundary Policy
|
|
148
165
|
|
|
149
|
-
The runtime pipeline surface is two skills — `/devlyn:resolve` and `/devlyn:ideate` — plus `/devlyn:design-ui` for creative UI exploration. `/devlyn:resolve` runs PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY inline; verification, cleanup, and security review (delegated to the native `security-review` Claude Code skill from BUILD_GATE) all live inside the pipeline. There are no standalone `/devlyn:review`, `/devlyn:evaluate`, or `/devlyn:team-resolve` surfaces. `/devlyn:design-ui` spawns a 5-specialist design team (Creative Director, Product Designer, Visual Designer, Interaction Designer, Accessibility Designer). `/devlyn:reap` is an optional user-invoked skill in `optional-skills/`; resolve never delegates to it.
|
|
166
|
+
The runtime pipeline surface is two skills — `/devlyn:resolve` and `/devlyn:ideate` — plus `/devlyn:design-ui` for creative UI exploration and two utilities added on explicit user direction: `/devlyn:engines` (engine-role config, iter-0038) and `/devlyn:queue` (intent-queue status/add/drain, iter-0039). `/devlyn:resolve` runs PLAN → IMPLEMENT → BUILD_GATE → CLEANUP → VERIFY inline; verification, cleanup, and security review (delegated to the native `security-review` Claude Code skill from BUILD_GATE) all live inside the pipeline. There are no standalone `/devlyn:review`, `/devlyn:evaluate`, or `/devlyn:team-resolve` surfaces. `/devlyn:design-ui` spawns a 5-specialist design team (Creative Director, Product Designer, Visual Designer, Interaction Designer, Accessibility Designer). `/devlyn:reap` is an optional user-invoked skill in `optional-skills/`; resolve never delegates to it.
|
|
150
167
|
|
|
151
168
|
Browser validation runs directly from BUILD_GATE using whichever toolchain is available (Chrome MCP, Playwright, or curl-tier fallback) — there is no separate `/devlyn:browser-validate` skill.
|
|
152
169
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
bench-test-repo의 CLI(bin/cli.js)에 있는 `hello` 서브커맨드에 `--loud` 플래그를 추가해주세요. `--loud`가 전달되면 인사말이 대문자로 바뀌고 느낌표 두 개로 끝나야 합니다.
|
|
2
|
+
|
|
3
|
+
예시:
|
|
4
|
+
- `node bin/cli.js hello --loud` → `HELLO, WORLD!!`
|
|
5
|
+
- `node bin/cli.js hello --loud --name alice` → `HELLO, ALICE!!`
|
|
6
|
+
- `node bin/cli.js hello` → `Hello, world!` (기본값 변경 없음)
|
|
7
|
+
|
|
8
|
+
기존 테스트가 계속 통과하는지 확인하고, `--loud` 경로에 대한 테스트를 최소 하나 추가해주세요. 관련 없는 파일은 건드리지 마세요 — `bin/cli.js`와 `tests/cli.test.js`만 수정하세요. 새로운 npm 의존성은 추가하지 마세요.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# F34 CLI settle in-flight
|
|
2
|
+
|
|
3
|
+
## Failure mode
|
|
4
|
+
|
|
5
|
+
Detects implementations that couple authorization completion order to
|
|
6
|
+
settlement order. The two cheap-but-wrong shapes: (a) sequential
|
|
7
|
+
await-per-transfer — correct ledger, fails the visible overlap requirement
|
|
8
|
+
(`high_water >= 3`); (b) apply-as-authorizations-resolve — meets overlap,
|
|
9
|
+
corrupts file-order settlement (flips `t-60` accept and `t-40` reject in the
|
|
10
|
+
verifier input). The correct shape starts authorizations concurrently and
|
|
11
|
+
settles strictly in file order afterward — the decoupling is the measured
|
|
12
|
+
skill (Tier-1 "H1" from memory `project_pair_fixture_tier1_2026_05_05`;
|
|
13
|
+
keyword-hidden: spec never says race/lock/mutex/concurrency primitive).
|
|
14
|
+
|
|
15
|
+
## Fairness
|
|
16
|
+
|
|
17
|
+
Every verifier assertion quotes a visible spec bullet via `contract_refs`
|
|
18
|
+
(lint-fixtures enforced). Gateway latency is deterministic (trailing digits
|
|
19
|
+
of the id = ms), high-water threshold is stated in the spec with the exact
|
|
20
|
+
sample size, and the flip case is derivable from the visible settlement rule
|
|
21
|
+
alone. No hidden shape or tie-break exists outside spec.md.
|
|
22
|
+
|
|
23
|
+
## Headroom prediction (pre-registered, unmeasured)
|
|
24
|
+
|
|
25
|
+
bare ≤ 60 (expected to fail ordering-vs-completion or overlap), solo ≤ 80
|
|
26
|
+
(hypothesis: misses the decoupling — settles in completion order). To be
|
|
27
|
+
measured with `headroom-gate.py` before any pair-evidence claims; this
|
|
28
|
+
fixture carries no measurement claim until then.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# F34 retired
|
|
2
|
+
|
|
3
|
+
Retired from the active golden suite after headroom run
|
|
4
|
+
`iter-0039-headroom-f34-f35`.
|
|
5
|
+
|
|
6
|
+
Reason: bare scored 92, exceeding the headroom ceiling of 60, and
|
|
7
|
+
`solo_claude` scored 88, exceeding the headroom ceiling of 80. Both arms
|
|
8
|
+
solved the settle-inflight task competently (judge: bare 92 vs solo_claude 88,
|
|
9
|
+
margin -4) and share the same minor scope slip (touching
|
|
10
|
+
`data/gateway-stats.json` outside the expected diff); neither arm hit a hard
|
|
11
|
+
disqualifier. The task was not hard enough for bare, let alone solo — there is
|
|
12
|
+
no pair-lift headroom to measure.
|
|
13
|
+
|
|
14
|
+
Future use: rework the visible contract or hidden verifiers so the task
|
|
15
|
+
creates a fair pair-risk-probe gap without hiding requirements from the spec.
|
|
@@ -0,0 +1,57 @@
|
|
|
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/ordered-settlement.js\"",
|
|
11
|
+
"exit_code": 0,
|
|
12
|
+
"stdout_contains": ["\"ok\":true"],
|
|
13
|
+
"stdout_not_contains": [],
|
|
14
|
+
"contract_refs": [
|
|
15
|
+
"Every transfer is authorized by calling `authorize(transfer)` from `lib/gateway.js` exactly once per transfer.",
|
|
16
|
+
"Regardless of the order in which authorizations complete, transfers settle in file order, each exactly once.",
|
|
17
|
+
"Each rejected transfer produces exact row shape `{ \"id\": string, \"error\": \"insufficient_funds\", \"available\": number, \"requested\": number }`, where `available` is the sender's balance at that transfer's turn in file order.",
|
|
18
|
+
"The seeded gateway records the maximum number of authorizations in flight at once in `data/gateway-stats.json`; settling the six-transfer sample in `## Verification` must record `high_water` of at least `3`.",
|
|
19
|
+
"The command must not modify `data/accounts.json`."
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/reject-invalid.js\"",
|
|
24
|
+
"exit_code": 0,
|
|
25
|
+
"stdout_contains": ["\"ok\":true"],
|
|
26
|
+
"stdout_not_contains": [],
|
|
27
|
+
"contract_refs": [
|
|
28
|
+
"Validation happens before any authorization starts.",
|
|
29
|
+
"unknown account, or non-positive or non-integer `amount` exits `2` and writes exactly one JSON error object to stderr with shape `{ \"error\": \"invalid_input\", \"reason\": string }`, printing nothing to stdout."
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"forbidden_patterns": [
|
|
34
|
+
{
|
|
35
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{[^}]*return\\s+(null|undefined|''|\\{\\})",
|
|
36
|
+
"description": "silent catch returning fallback in settle path",
|
|
37
|
+
"files": ["bin/cli.js"],
|
|
38
|
+
"severity": "disqualifier"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}",
|
|
42
|
+
"description": "empty catch block",
|
|
43
|
+
"files": ["bin/cli.js"],
|
|
44
|
+
"severity": "disqualifier"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"pattern": "/\\*\\s*eslint-disable",
|
|
48
|
+
"description": "eslint-disable without scoped justification",
|
|
49
|
+
"files": ["bin/cli.js"],
|
|
50
|
+
"severity": "disqualifier"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"required_files": ["bin/cli.js", "tests/cli.test.js", "data/accounts.json", "lib/gateway.js"],
|
|
54
|
+
"forbidden_files": [],
|
|
55
|
+
"max_deps_added": 0,
|
|
56
|
+
"spec_output_files": ["bin/cli.js", "tests/cli.test.js"]
|
|
57
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "F34-cli-settle-inflight",
|
|
3
|
+
"category": "high-risk",
|
|
4
|
+
"difficulty": "high",
|
|
5
|
+
"timeout_seconds": 1500,
|
|
6
|
+
"required_tools": ["node"],
|
|
7
|
+
"browser": false,
|
|
8
|
+
"deps_change_expected": false,
|
|
9
|
+
"intent": "Add a bench-cli settle command that authorizes transfers through a seeded async gateway with overlapping in-flight authorizations while settling balances strictly in file order, with exact accepted/rejected/balances JSON output and validation before any authorization."
|
|
10
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# F34 setup — seed accounts and the concurrency-tracking gateway.
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
mkdir -p data lib
|
|
6
|
+
|
|
7
|
+
cat > data/accounts.json <<'JSON'
|
|
8
|
+
{
|
|
9
|
+
"acct-a": 100,
|
|
10
|
+
"acct-b": 0,
|
|
11
|
+
"acct-c": 50,
|
|
12
|
+
"acct-d": 10
|
|
13
|
+
}
|
|
14
|
+
JSON
|
|
15
|
+
|
|
16
|
+
cat > lib/gateway.js <<'JS'
|
|
17
|
+
'use strict';
|
|
18
|
+
// Seeded authorization gateway. DO NOT MODIFY (fixture contract).
|
|
19
|
+
// Latency is deterministic per transfer id (trailing digits = milliseconds),
|
|
20
|
+
// so authorization completion order can differ from file order. Tracks how
|
|
21
|
+
// many authorizations are in flight at once and how many calls were made.
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
const STATS_PATH = path.join(__dirname, '..', 'data', 'gateway-stats.json');
|
|
26
|
+
let inFlight = 0;
|
|
27
|
+
let highWater = 0;
|
|
28
|
+
let calls = 0;
|
|
29
|
+
|
|
30
|
+
function writeStats() {
|
|
31
|
+
fs.writeFileSync(STATS_PATH, JSON.stringify({ calls, high_water: highWater }) + '\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function authorize(transfer) {
|
|
35
|
+
calls += 1;
|
|
36
|
+
inFlight += 1;
|
|
37
|
+
if (inFlight > highWater) highWater = inFlight;
|
|
38
|
+
writeStats();
|
|
39
|
+
const digits = String(transfer && transfer.id ? transfer.id : '').replace(/\D/g, '');
|
|
40
|
+
const ms = digits ? (Number(digits) % 100) || 5 : 5;
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
|
+
inFlight -= 1;
|
|
43
|
+
writeStats();
|
|
44
|
+
return { authorized: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { authorize };
|
|
48
|
+
JS
|
|
49
|
+
|
|
50
|
+
exit 0
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "F34-cli-settle-inflight"
|
|
3
|
+
title: "Settle command with in-flight authorizations"
|
|
4
|
+
status: planned
|
|
5
|
+
complexity: high
|
|
6
|
+
depends-on: []
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# F34 Settle command with in-flight authorizations
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
`bench-cli` gets a `settle` command that previews a batch of credit
|
|
14
|
+
transfers between accounts. Every transfer must be authorized through the
|
|
15
|
+
seeded gateway module before settlement. The gateway is slow, so
|
|
16
|
+
authorizations for different transfers must be able to overlap — but the
|
|
17
|
+
ledger math must stay exact and order-stable.
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- [ ] `bench-cli settle --input <path>` reads JSON shaped as `{ "transfers": [{ "id": string, "from": string, "to": string, "amount": number }] }`.
|
|
22
|
+
- [ ] Account starting balances come from `data/accounts.json`. The command must not modify `data/accounts.json`.
|
|
23
|
+
- [ ] Every transfer is authorized by calling `authorize(transfer)` from `lib/gateway.js` exactly once per transfer. Do not modify, reimplement, or bypass `lib/gateway.js`.
|
|
24
|
+
- [ ] Authorizations must be able to overlap: while one transfer's authorization is awaited, later transfers' authorizations must be able to start. The seeded gateway records the maximum number of authorizations in flight at once in `data/gateway-stats.json`; settling the six-transfer sample in `## Verification` must record `high_water` of at least `3`.
|
|
25
|
+
- [ ] Regardless of the order in which authorizations complete, transfers settle in file order, each exactly once. A transfer is accepted when the sender's balance — after all earlier-in-file accepted transfers — covers the amount; otherwise it is rejected.
|
|
26
|
+
- [ ] Each rejected transfer produces exact row shape `{ "id": string, "error": "insufficient_funds", "available": number, "requested": number }`, where `available` is the sender's balance at that transfer's turn in file order.
|
|
27
|
+
- [ ] Validation happens before any authorization starts. Invalid JSON, missing or non-string `id`/`from`/`to`, unknown account, or non-positive or non-integer `amount` exits `2` and writes exactly one JSON error object to stderr with shape `{ "error": "invalid_input", "reason": string }`, printing nothing to stdout.
|
|
28
|
+
- [ ] On success, write exactly one JSON object to stdout and no stderr. Keys: `accepted` (array of transfer ids in file order), `rejected` (array of rejected rows in file order), `balances` (object with the final balance of every account in `data/accounts.json`).
|
|
29
|
+
- [ ] `tests/cli.test.js` is updated. Existing tests still pass AND at least two new tests cover `settle`: one where a later-in-file transfer depends on an earlier-in-file transfer's settlement, and one rejection.
|
|
30
|
+
|
|
31
|
+
## Constraints
|
|
32
|
+
|
|
33
|
+
- **No new npm dependencies.**
|
|
34
|
+
- **No silent catches.** If parsing or file reading fails, emit a visible JSON error to stderr and exit `2`.
|
|
35
|
+
- **No extra stdout/stderr text** on the success path; downstream tooling parses stdout as JSON.
|
|
36
|
+
- Balances are integer credits; no floating-point money.
|
|
37
|
+
|
|
38
|
+
## Out of Scope
|
|
39
|
+
|
|
40
|
+
- Persisting settlement results (settle is a preview; `data/accounts.json` stays untouched).
|
|
41
|
+
- Changing server routes, web UI, or `lib/gateway.js`.
|
|
42
|
+
- Retry, timeout, or failure handling for the gateway beyond what it returns.
|
|
43
|
+
- Touching `server/`, `web/`, or `tests/server.test.js`.
|
|
44
|
+
|
|
45
|
+
## Verification
|
|
46
|
+
|
|
47
|
+
- `node --test tests/cli.test.js` exits 0.
|
|
48
|
+
- Settling a six-transfer file where authorizations complete in reverse file order still yields file-order settlement: exact `accepted`, `rejected`, and `balances` values.
|
|
49
|
+
- `data/gateway-stats.json` shows `calls` equal to the number of transfers and `high_water` of at least `3`.
|
|
50
|
+
- An unknown account exits `2`, prints one JSON error to stderr, and prints no stdout.
|
|
51
|
+
- `git diff --stat` shows only `bin/cli.js` and `tests/cli.test.js` touched (accounts and gateway are seeded by setup, not the arm).
|
|
52
|
+
|
|
53
|
+
## Solo-headroom hypothesis
|
|
54
|
+
|
|
55
|
+
A capable solo_claude baseline is expected to miss the decoupling of
|
|
56
|
+
authorization completion order from file-order settlement — applying
|
|
57
|
+
balance changes as authorizations resolve instead of at each transfer's
|
|
58
|
+
file-order turn; observable command
|
|
59
|
+
`node "$BENCH_FIXTURE_DIR/verifiers/ordered-settlement.js"` exposes the miss.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Add a `settle` command to `bench-cli` so users can run `bench-cli settle --input <path>` with a transfers JSON file. Starting balances come from `data/accounts.json` (read-only — settle is a preview and must not modify it). Every transfer must be authorized by calling `authorize(transfer)` from the seeded `lib/gateway.js` exactly once; do not modify or bypass that module.
|
|
2
|
+
|
|
3
|
+
The gateway is slow, so authorizations for different transfers must be able to overlap — while one is awaited, later ones must be able to start. The seeded gateway records `calls` and the in-flight `high_water` mark in `data/gateway-stats.json`; a six-transfer batch must reach a high-water mark of at least 3.
|
|
4
|
+
|
|
5
|
+
No matter which order authorizations finish in, transfers settle in file order, each exactly once: a transfer is accepted when the sender's balance after all earlier-in-file accepted transfers covers the amount, otherwise it is rejected with the exact row `{ "id", "error": "insufficient_funds", "available", "requested" }` where `available` is the balance at that transfer's file-order turn.
|
|
6
|
+
|
|
7
|
+
Validate the whole file before any authorization starts: bad JSON, missing fields, unknown accounts, or non-positive/non-integer amounts exit `2` with exactly one JSON error object `{ "error": "invalid_input", "reason": string }` on stderr and nothing on stdout. On success print exactly one JSON object with `accepted` (ids in file order), `rejected` (rows in file order), and `balances` (every account) — and nothing on stderr.
|
|
8
|
+
|
|
9
|
+
Update `tests/cli.test.js`: existing tests still pass, plus at least two new settle tests (one where a later transfer depends on an earlier transfer's settlement, one rejection). No new npm dependencies. Only touch `bin/cli.js` and `tests/cli.test.js`.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// F34 hidden verifier — file-order settlement under inverted completion order.
|
|
3
|
+
// Transfer ids encode gateway latency (trailing digits = ms), so the FIRST
|
|
4
|
+
// transfer in the file finishes authorization LAST. Applying balances in
|
|
5
|
+
// completion order (or rejecting against pre-batch balances) flips t-60 and
|
|
6
|
+
// t-40; only strict file-order settlement produces this exact output.
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const os = require('node:os');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const { spawnSync } = require('node:child_process');
|
|
12
|
+
|
|
13
|
+
// Structural equality: object key order is NOT part of the visible contract;
|
|
14
|
+
// array order IS. Codex fairness-audit fix (stringify equality made JSON
|
|
15
|
+
// property order a hidden oracle narrower than spec.md).
|
|
16
|
+
function deepEqual(a, b) {
|
|
17
|
+
if (a === b) return true;
|
|
18
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
19
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
20
|
+
}
|
|
21
|
+
if (a && b && typeof a === 'object' && typeof b === 'object' && !Array.isArray(a) && !Array.isArray(b)) {
|
|
22
|
+
const ka = Object.keys(a).sort();
|
|
23
|
+
const kb = Object.keys(b).sort();
|
|
24
|
+
return deepEqual(ka, kb) && ka.every((k) => deepEqual(a[k], b[k]));
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const inputPath = path.join(os.tmpdir(), `settle-input-${process.pid}.json`);
|
|
30
|
+
fs.writeFileSync(inputPath, JSON.stringify({
|
|
31
|
+
transfers: [
|
|
32
|
+
{ id: 't-80', from: 'acct-a', to: 'acct-b', amount: 70 },
|
|
33
|
+
{ id: 't-60', from: 'acct-b', to: 'acct-c', amount: 50 },
|
|
34
|
+
{ id: 't-40', from: 'acct-a', to: 'acct-d', amount: 40 },
|
|
35
|
+
{ id: 't-20', from: 'acct-c', to: 'acct-a', amount: 100 },
|
|
36
|
+
{ id: 't-10', from: 'acct-d', to: 'acct-b', amount: 5 },
|
|
37
|
+
{ id: 't-5', from: 'acct-b', to: 'acct-a', amount: 25 }
|
|
38
|
+
]
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const work = process.env.BENCH_WORKDIR;
|
|
42
|
+
const cli = path.join(work, 'bin', 'cli.js');
|
|
43
|
+
const result = spawnSync('node', [cli, 'settle', '--input', inputPath], {
|
|
44
|
+
cwd: work,
|
|
45
|
+
encoding: 'utf8'
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
let out;
|
|
49
|
+
try {
|
|
50
|
+
out = JSON.parse(result.stdout);
|
|
51
|
+
} catch {
|
|
52
|
+
out = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const expected = {
|
|
56
|
+
accepted: ['t-80', 't-60', 't-20', 't-10', 't-5'],
|
|
57
|
+
rejected: [
|
|
58
|
+
{ id: 't-40', error: 'insufficient_funds', available: 30, requested: 40 }
|
|
59
|
+
],
|
|
60
|
+
balances: { 'acct-a': 155, 'acct-b': 0, 'acct-c': 0, 'acct-d': 5 }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let stats;
|
|
64
|
+
try {
|
|
65
|
+
stats = JSON.parse(fs.readFileSync(path.join(work, 'data', 'gateway-stats.json'), 'utf8'));
|
|
66
|
+
} catch {
|
|
67
|
+
stats = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const accountsUntouched = (() => {
|
|
71
|
+
try {
|
|
72
|
+
const seeded = JSON.parse(fs.readFileSync(path.join(work, 'data', 'accounts.json'), 'utf8'));
|
|
73
|
+
return deepEqual(seeded, { 'acct-a': 100, 'acct-b': 0, 'acct-c': 50, 'acct-d': 10 });
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
|
|
79
|
+
const ok = result.status === 0
|
|
80
|
+
&& result.stderr === ''
|
|
81
|
+
&& deepEqual(out, expected)
|
|
82
|
+
&& stats !== null
|
|
83
|
+
&& stats.calls === 6
|
|
84
|
+
&& stats.high_water >= 3
|
|
85
|
+
&& accountsUntouched;
|
|
86
|
+
|
|
87
|
+
console.log(JSON.stringify({ ok, status: result.status, stderr: result.stderr, out, stats, accountsUntouched }));
|
|
88
|
+
process.exit(ok ? 0 : 1);
|
package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/reject-invalid.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// F34 hidden verifier — validation happens before any authorization: an
|
|
3
|
+
// unknown account exits 2 with one JSON error object on stderr, no stdout,
|
|
4
|
+
// and the gateway records zero calls for the run.
|
|
5
|
+
|
|
6
|
+
const fs = require('node:fs');
|
|
7
|
+
const os = require('node:os');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { spawnSync } = require('node:child_process');
|
|
10
|
+
|
|
11
|
+
const work = process.env.BENCH_WORKDIR;
|
|
12
|
+
const statsPath = path.join(work, 'data', 'gateway-stats.json');
|
|
13
|
+
fs.rmSync(statsPath, { force: true });
|
|
14
|
+
|
|
15
|
+
const inputPath = path.join(os.tmpdir(), `settle-bad-${process.pid}.json`);
|
|
16
|
+
fs.writeFileSync(inputPath, JSON.stringify({
|
|
17
|
+
transfers: [
|
|
18
|
+
{ id: 't-30', from: 'acct-a', to: 'acct-zzz', amount: 10 }
|
|
19
|
+
]
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
const cli = path.join(work, 'bin', 'cli.js');
|
|
23
|
+
const result = spawnSync('node', [cli, 'settle', '--input', inputPath], {
|
|
24
|
+
cwd: work,
|
|
25
|
+
encoding: 'utf8'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let err;
|
|
29
|
+
try {
|
|
30
|
+
err = JSON.parse(result.stderr);
|
|
31
|
+
} catch {
|
|
32
|
+
err = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const gatewayNeverCalled = !fs.existsSync(statsPath);
|
|
36
|
+
|
|
37
|
+
const ok = result.status === 2
|
|
38
|
+
&& result.stdout === ''
|
|
39
|
+
&& err !== null
|
|
40
|
+
&& err.error === 'invalid_input'
|
|
41
|
+
&& typeof err.reason === 'string'
|
|
42
|
+
&& gatewayNeverCalled;
|
|
43
|
+
|
|
44
|
+
console.log(JSON.stringify({ ok, status: result.status, stdout: result.stdout, err, gatewayNeverCalled }));
|
|
45
|
+
process.exit(ok ? 0 : 1);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# F35 CLI apply journal
|
|
2
|
+
|
|
3
|
+
## Failure mode
|
|
4
|
+
|
|
5
|
+
Detects implementations that flatten the composite ordering/replay/atomicity
|
|
6
|
+
contract into the easy shape: apply-in-file-order against pre-batch stock
|
|
7
|
+
with per-row persistence. Three discriminators: (a) `applied` order and
|
|
8
|
+
journal append order must follow priority-desc with file-order ties (final
|
|
9
|
+
quantities alone cannot catch this — addition commutes); (b) the failing
|
|
10
|
+
op's `available` is measured at its turn AFTER higher-priority ops changed
|
|
11
|
+
stock (rollback verifier: 15, not the pre-batch 10); (c) any failure leaves
|
|
12
|
+
both files byte-for-byte unchanged. In-file duplicate `op_id` is a
|
|
13
|
+
validation error while a journaled `op_id` is a per-op skip — the
|
|
14
|
+
distinction is the replay axis (Tier-1 "A1" from memory
|
|
15
|
+
`project_pair_fixture_tier1_2026_05_05`; keyword-hidden: spec never says
|
|
16
|
+
atomic/transaction/idempotent/rollback).
|
|
17
|
+
|
|
18
|
+
## Fairness
|
|
19
|
+
|
|
20
|
+
Every verifier assertion quotes a visible spec bullet via `contract_refs`
|
|
21
|
+
(lint-fixtures enforced). Verifiers reset the seeded files before running so
|
|
22
|
+
command order cannot leak state between checks. No hidden shape, tie-break,
|
|
23
|
+
or availability rule exists outside spec.md.
|
|
24
|
+
|
|
25
|
+
## Headroom prediction (pre-registered, unmeasured)
|
|
26
|
+
|
|
27
|
+
bare ≤ 60 (expected to miss at-its-turn availability or journal append
|
|
28
|
+
order), solo ≤ 80 (hypothesis: evaluates availability against pre-batch or
|
|
29
|
+
file-order state). To be measured with `headroom-gate.py` before any
|
|
30
|
+
pair-evidence claims; this fixture carries no measurement claim until then.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# F35 retired
|
|
2
|
+
|
|
3
|
+
Retired from the active golden suite after headroom run
|
|
4
|
+
`iter-0039-headroom-f34-f35`.
|
|
5
|
+
|
|
6
|
+
Reason: `solo_claude` scored 97, exceeding the headroom ceiling of 80, while
|
|
7
|
+
bare scored 50. `solo_claude` correctly implemented the `{ "ops": [...] }`
|
|
8
|
+
input contract, priority ordering, rollback, and duplicate-op handling; bare
|
|
9
|
+
failed only because it assumed a bare top-level array instead of the required
|
|
10
|
+
object shape — an input-shape slip, not evidence the task is hard. The
|
|
11
|
+
solo_claude arm also ran to the full 1500s timeout before finishing
|
|
12
|
+
(elapsed_seconds 1501, INVOKE_EXIT=124), but its diff still verified clean, so
|
|
13
|
+
the fixture is not pair-lift evidence.
|
|
14
|
+
|
|
15
|
+
Future use: rework the visible contract or hidden verifiers so the task
|
|
16
|
+
creates a fair pair-risk-probe gap without hiding requirements from the spec.
|
|
@@ -0,0 +1,64 @@
|
|
|
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-apply.js\"",
|
|
11
|
+
"exit_code": 0,
|
|
12
|
+
"stdout_contains": ["\"ok\":true"],
|
|
13
|
+
"stdout_not_contains": [],
|
|
14
|
+
"contract_refs": [
|
|
15
|
+
"An op whose `op_id` is already listed in the journal's `applied` array is recorded as skipped and changes nothing, regardless of its other fields.",
|
|
16
|
+
"Non-skipped ops apply in descending `priority`; ties apply in file order.",
|
|
17
|
+
"On success, persist the final quantities to `data/inventory.json`, append the applied `op_id`s to the journal's `applied` array in application order, and write exactly one JSON object to stdout with keys `applied` (op_ids in application order), `skipped` (op_ids in file order), and `inventory` (final quantities for every sku)."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/rollback.js\"",
|
|
22
|
+
"exit_code": 0,
|
|
23
|
+
"stdout_contains": ["\"ok\":true"],
|
|
24
|
+
"stdout_not_contains": [],
|
|
25
|
+
"contract_refs": [
|
|
26
|
+
"An op fails when applying its `delta` would take the sku's quantity below zero, evaluated at its turn in that order, with exact error shape `{ \"error\": \"insufficient_stock\", \"op_id\": string, \"sku\": string, \"available\": number, \"requested\": number }` where `available` is the quantity at that op's turn and `requested` is the absolute value of its `delta`.",
|
|
27
|
+
"If any op fails, nothing persists: `data/inventory.json` and `data/journal.json` are byte-for-byte unchanged, the command exits `2`, and exactly one JSON error object is written to stderr with nothing on stdout."
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/dup-op.js\"",
|
|
32
|
+
"exit_code": 0,
|
|
33
|
+
"stdout_contains": ["\"ok\":true"],
|
|
34
|
+
"stdout_not_contains": [],
|
|
35
|
+
"contract_refs": [
|
|
36
|
+
"or an `op_id` that appears more than once in the input file exits `2` and writes exactly one JSON error object to stderr with shape `{ \"error\": \"invalid_input\", \"reason\": string }`, printing nothing to stdout and changing neither file."
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"forbidden_patterns": [
|
|
41
|
+
{
|
|
42
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{[^}]*return\\s+(null|undefined|''|\\{\\})",
|
|
43
|
+
"description": "silent catch returning fallback in apply path",
|
|
44
|
+
"files": ["bin/cli.js"],
|
|
45
|
+
"severity": "disqualifier"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}",
|
|
49
|
+
"description": "empty catch block",
|
|
50
|
+
"files": ["bin/cli.js"],
|
|
51
|
+
"severity": "disqualifier"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"pattern": "/\\*\\s*eslint-disable",
|
|
55
|
+
"description": "eslint-disable without scoped justification",
|
|
56
|
+
"files": ["bin/cli.js"],
|
|
57
|
+
"severity": "disqualifier"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"required_files": ["bin/cli.js", "tests/cli.test.js", "data/inventory.json", "data/journal.json"],
|
|
61
|
+
"forbidden_files": [],
|
|
62
|
+
"max_deps_added": 0,
|
|
63
|
+
"spec_output_files": ["bin/cli.js", "tests/cli.test.js"]
|
|
64
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "F35-cli-apply-journal",
|
|
3
|
+
"category": "high-risk",
|
|
4
|
+
"difficulty": "high",
|
|
5
|
+
"timeout_seconds": 1500,
|
|
6
|
+
"required_tools": ["node"],
|
|
7
|
+
"browser": false,
|
|
8
|
+
"deps_change_expected": false,
|
|
9
|
+
"intent": "Add a bench-cli apply command that applies inventory adjustments in priority order with journal-based replay skipping, at-its-turn stock checks, exact JSON output, and all-or-nothing persistence of inventory and journal."
|
|
10
|
+
}
|