@agentsfleet/orly 0.5.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.
Files changed (112) hide show
  1. package/.githooks/pre-commit +42 -0
  2. package/.githooks/pre-push +67 -0
  3. package/AGENTS.md +348 -0
  4. package/LICENSE +21 -0
  5. package/README.md +249 -0
  6. package/SOUL.md +90 -0
  7. package/audits/agents-md.md +497 -0
  8. package/audits/agents-md.sh +353 -0
  9. package/audits/cross-tier-rates.sh +105 -0
  10. package/audits/data.sh +185 -0
  11. package/audits/deinit-pairs.sh +224 -0
  12. package/audits/design-tokens.sh +143 -0
  13. package/audits/doc-read.sh +183 -0
  14. package/audits/error-codes.sh +221 -0
  15. package/audits/fixtures/clean.diff +15 -0
  16. package/audits/fixtures/dirty.diff +16 -0
  17. package/audits/lifecycle-anchors.sh +22 -0
  18. package/audits/logging.sh +217 -0
  19. package/audits/msid-ui.sh +144 -0
  20. package/audits/parity-dispatch.sh +51 -0
  21. package/audits/rule-ledger-lib.sh +264 -0
  22. package/audits/rule-ledger.sh +225 -0
  23. package/audits/rule-paths.sh +108 -0
  24. package/audits/spec-template.sh +258 -0
  25. package/audits/sql-mod.sh +102 -0
  26. package/audits/ufs.sh +303 -0
  27. package/bin/orly +26 -0
  28. package/core/operating-model.md +256 -0
  29. package/dispatch/edit_rules.md +69 -0
  30. package/dispatch/lib.sh +213 -0
  31. package/dispatch/lifecycle.md +187 -0
  32. package/dispatch/name_architecture.md +93 -0
  33. package/dispatch/verify.md +111 -0
  34. package/dispatch/write_any.md +740 -0
  35. package/dispatch/write_any.sh +49 -0
  36. package/dispatch/write_auth.md +36 -0
  37. package/dispatch/write_changelog.md +64 -0
  38. package/dispatch/write_documentation.md +45 -0
  39. package/dispatch/write_go.md +24 -0
  40. package/dispatch/write_http.md +45 -0
  41. package/dispatch/write_mdx.md +7 -0
  42. package/dispatch/write_python.md +8 -0
  43. package/dispatch/write_rust.md +38 -0
  44. package/dispatch/write_shell.md +8 -0
  45. package/dispatch/write_spec.md +105 -0
  46. package/dispatch/write_sql.md +112 -0
  47. package/dispatch/write_sql.sh +36 -0
  48. package/dispatch/write_ts_adhere_bun.md +493 -0
  49. package/dispatch/write_ts_adhere_bun.sh +37 -0
  50. package/dispatch/write_zig.md +852 -0
  51. package/dispatch/write_zig.sh +40 -0
  52. package/docs/CHANGELOG_VOICE.md +18 -0
  53. package/docs/DISPATCH_ARCHITECTURE.md +664 -0
  54. package/docs/DISPATCH_REVIEW_DISPOSITION.md +65 -0
  55. package/docs/DOCUMENTATION_RULES.md +209 -0
  56. package/docs/EXECUTE_DOC_READS.md +29 -0
  57. package/docs/HARNESS_VERIFY_OUTPUT.md +40 -0
  58. package/docs/LIFECYCLE_PATTERNS.md +316 -0
  59. package/docs/LOGGING_STANDARD.md +397 -0
  60. package/docs/ORLY_ARCHITECTURE.md +129 -0
  61. package/docs/REST_API_DESIGN_GUIDELINES.md +783 -0
  62. package/docs/RULE_ENFORCEMENT.md +41 -0
  63. package/docs/SCHEMA_CONVENTIONS.md +93 -0
  64. package/docs/TEMPLATE.md +411 -0
  65. package/docs/VERIFY_TIERS.md +57 -0
  66. package/docs/greptile-learnings/README.md +51 -0
  67. package/docs/greptile-learnings/RULES.md +1045 -0
  68. package/fixtures/registry-invalid.json +1 -0
  69. package/fixtures/registry-valid.json +18 -0
  70. package/fixtures/unclosed-pack-block.md +8 -0
  71. package/package.json +63 -0
  72. package/packs/language/go/rules.md +24 -0
  73. package/packs/language/mdx/rules.md +7 -0
  74. package/packs/language/python/rules.md +8 -0
  75. package/packs/language/rust/rules.md +38 -0
  76. package/packs/language/shell/rules.md +8 -0
  77. package/registry.json +405 -0
  78. package/schemas/evidence.schema.json +43 -0
  79. package/schemas/profile.schema.json +12 -0
  80. package/schemas/registry.schema.json +12 -0
  81. package/skills/orly-babysit-prs/SKILL.md +326 -0
  82. package/skills/orly-spec-new/SKILL.md +157 -0
  83. package/skills/orly-write-integration-test/SKILL.md +470 -0
  84. package/skills/orly-write-unit-test/SKILL.md +520 -0
  85. package/src/cli.test.ts +70 -0
  86. package/src/cli.ts +287 -0
  87. package/src/config.test.ts +226 -0
  88. package/src/config.ts +288 -0
  89. package/src/criteria.test.ts +137 -0
  90. package/src/criteria.ts +166 -0
  91. package/src/criteria_spec.ts +127 -0
  92. package/src/criteria_support.ts +53 -0
  93. package/src/gates.test.ts +183 -0
  94. package/src/gates.ts +169 -0
  95. package/src/gates_spec.test.ts +98 -0
  96. package/src/gates_test_support.ts +194 -0
  97. package/src/git_env.ts +20 -0
  98. package/src/install.test.ts +514 -0
  99. package/src/install.ts +447 -0
  100. package/src/install_nested.test.ts +32 -0
  101. package/src/model.test.ts +31 -0
  102. package/src/model.ts +245 -0
  103. package/src/references.test.ts +47 -0
  104. package/src/references.ts +147 -0
  105. package/src/render.test.ts +60 -0
  106. package/src/render.ts +94 -0
  107. package/src/surfaces.test.ts +43 -0
  108. package/src/surfaces.ts +89 -0
  109. package/src/validation.test.ts +11 -0
  110. package/src/validation.ts +87 -0
  111. package/src/verify.test.ts +13 -0
  112. package/src/verify.ts +59 -0
@@ -0,0 +1,520 @@
1
+ ---
2
+ name: orly-write-unit-test
3
+ description: >
4
+ Risk-weighted, failure-injecting test generation across 8 stacks (Python,
5
+ Python SDK, OpenAPI, JS/TS CLI, React/TS, Zig, Rust, Shell) enforcing behaviour +
6
+ failure + invariant + integration + regression coverage with a
7
+ Definition-of-Done gate; deterministic production-safety proofs (Zig
8
+ zero-leak, 100-or-more-connection concurrency, complexity/latency budgets) live
9
+ in the skill body. Use during implementation, not after.
10
+ ---
11
+
12
+ # Write Unit Test
13
+
14
+ Generate tests that catch bugs before they ship. Tests run *during* implementation, not after.
15
+
16
+ > **What this guarantees / what it doesn't.** No test suite proves "zero bugs in
17
+ > production" — nothing does. This skill guarantees something *checkable*: **no
18
+ > changed behaviour, branch, or named failure-mode ships untested, and the tests
19
+ > provably *assert* rather than merely *execute*.** Three production-safety proofs
20
+ > (Zig zero-leak · concurrency-at-scale · performance/complexity budget) are gated
21
+ > on **deterministic** evidence — counters, exhaustive injection, pinned baselines —
22
+ > not wall-clock vibes. Coverage proves a line *ran*; these proofs prove a test
23
+ > would *scream* when the behaviour breaks.
24
+
25
+ ## Test quality bar
26
+
27
+ Every test answers three questions. If it answers none, delete it.
28
+
29
+ 1. **What bug would this catch?**
30
+ 2. **What behaviour does this prove?**
31
+ 3. **What invariant does this enforce?**
32
+
33
+ A green test that answers none of these is worse than no test — it's false confidence.
34
+
35
+ ## Truth hierarchy
36
+
37
+ When sources disagree, trust in this order:
38
+
39
+ 1. **Runtime behaviour** — prod logs, incidents, observed traces
40
+ 2. **Spec** — `docs/v*/active/M*.md`, PR description, milestone doc
41
+ 3. **Code** — current implementation
42
+
43
+ Spec contradicts runtime → surface the conflict before testing. Code contradicts spec → spec wins; test the spec, flag the code as a defect.
44
+
45
+ ## First steps
46
+
47
+ 1. **Read the spec, then the code** — spec defines what to test; code is what gets tested. Don't let code shape expectations.
48
+ 2. **Read `docs/greptile-learnings/RULES.md`** — every rule is a regression test.
49
+ 3. **Detect the stack** from project files (`pyproject.toml`, `Cargo.toml`, `build.zig`, `package.json` + `react`/`bin`, `*.sh`, `openapi.yaml`).
50
+ 4. **Run the existing suite first** — establish the green baseline before adding tests.
51
+ 5. **Follow existing naming and fixture conventions.**
52
+
53
+ ## Step 0 — the diff is the spec: build the required-test ledger first
54
+
55
+ Before writing a single test, compute what *must* be tested **from the diff** — not from
56
+ re-reading whole files. This is the optimised review: bounded by the change, not the
57
+ codebase, and it's the artifact that makes "is the skill applied?" auditable.
58
+
59
+ 1. `git diff origin/main...HEAD` (or the PR base). Enumerate, per changed file:
60
+ - every changed/added **public function or method**
61
+ - every new or modified **branch** (`if`/`switch`/`match`/`?`/ternary)
62
+ - every new **error construction** (`catch`/`orelse`/`try`/`Err`/`raise`/`throw`)
63
+ - every new **public symbol** (export, `pub`, `__all__`, header signature)
64
+ - every ordered boundary interaction when one operation touches multiple systems or acquires the same resource more than once; repeated acquisitions are separate rows
65
+ 2. Each row gets a required test type and a status:
66
+
67
+ ```
68
+ | Changed unit | Test type | Exists? | Note |
69
+ |---------------------------|---------------|---------|----------------------------|
70
+ | parseLease() new branch | Failure | ❌ | empty-input path |
71
+ | revokeKey() orelse | Failure (inj) | ❌ | DB-down → 503 |
72
+ | computeTotals() n² scan | Performance | ❌ | counter ladder + refactor? |
73
+ ```
74
+
75
+ 3. The ledger is **done only when every row is ✅ or carries an explicit
76
+ `won't-test: <reason>` / `needs-infra`**. A bare gap is a defect, not a deferral.
77
+ 4. **Paste the resolved ledger into PR Session Notes** so `/review`, `orly-babysit-prs`,
78
+ and a human can audit it — the check becomes reviewable, not merely claimed.
79
+
80
+ ## Three execution modes
81
+
82
+ Pick one. Don't run all categories on every file.
83
+
84
+ | Mode | When | Required categories |
85
+ |---|---|---|
86
+ | **Change-set** | Standard PR, isolated fix | Behaviour, Failure, Integration, Regression |
87
+ | **Hardening** | Critical paths: auth, money, data integrity, migrations, distributed ops | + Invariant, Concurrency, Fuzz |
88
+ | **Deep audit** | Infra/core, dependency upgrade, full release QA, security review | All categories + Performance + Contract |
89
+
90
+ Auto-detect from diff: auth/payment/migration/concurrency/streaming files → Hardening. Otherwise → Change-set.
91
+
92
+ ## Risk-weighted priority
93
+
94
+ Within each mode, write tests in this order. Stop when you've covered the surface.
95
+
96
+ 1. **State transitions** — PENDING→RUNNING, idempotent vs not, monotonic
97
+ 2. **External boundaries** — I/O, network, DB, FS, IPC; test release-then-reacquire windows first
98
+ 3. **Concurrency paths** — races, ordering, double-fire
99
+ 4. **Error handling branches** — every `catch`, `orelse`, `?`, `Result::Err`, `try/except`
100
+ 5. **Pure functions** — last; usually low bug density
101
+
102
+ A string helper does not get the same treatment as a distributed retry loop. Allocate effort by blast radius.
103
+
104
+ ---
105
+
106
+ ## Five test categories
107
+
108
+ ### 1. Behaviour
109
+ Input → exact output shape AND side effects. Assert *both*. Asserting only the return value misses DB writes, event emits, log lines, state changes.
110
+
111
+ ### 2. Failure (≥50% of all tests)
112
+ - **Specific error contracts** — type + message + structured fields (status code, error code, hint). Bare `expect error` / `assert.throws` is a smell.
113
+ - **Boundaries** — empty, null/None/`Option::None`, max (`i64::MAX`, `Number.MAX_SAFE_INTEGER`), overflow, NaN, malformed, truncated, unicode (CJK, emoji, RTL, ZWJ), CRLF/LF, leading/trailing whitespace, embedded newlines.
114
+ - **Expected vs fatal distinction** — a function returning null for both timeout (expected) and connection-reset (fatal) causes busy-loops. Test that they take different paths.
115
+ - **Auth failures** — no token, expired, wrong role, revoked, rate limited.
116
+ - **Downstream errors** — 4xx, 5xx, timeout, DNS, TLS, partial UTF-8, broken pipe.
117
+ - **Partial completion** — when one operation touches multiple systems or acquires the same resource more than once, enumerate every ordered connection, transaction, and external call; select each workflow ordinal and boundary-call ordinal for failure, record failure timing / remote outcome, then assert the residual durable state, external state, user-visible state, and retry outcome.
118
+
119
+ Write the matrix before the tests:
120
+
121
+ | Workflow N | Boundary call N | Dependency / resource | Interaction | Prior completed actions | Injection seam | Operation / request scope | Response | Failure timing / remote outcome | Durable residual state | External residual state | User-visible state | Retry outcome | Design finding |
122
+ |---|---|---|---|---|---|---|---|---|---|---|---|---|---|
123
+ | 1..N | 1..M | named boundary | exact call | side effects already committed | ordinal-selecting mechanism | test plus operation ID | exact surface | before effect / remote committed, acknowledgement lost / after durable write | rows and transactions | remote effects | next user read | heals or stays broken | ordering defect or none |
124
+
125
+ The workflow ordinal locates the interaction in the whole operation. The
126
+ boundary-call ordinal selects a repeated call at one dependency. Scope the
127
+ failpoint to the test and operation or request identifier, reset it per test,
128
+ make its counter concurrency-safe, and assert it fired exactly once.
129
+
130
+ Every external mutation gets rows for failure before the effect, remote commit with
131
+ acknowledgement-loss, and failure after the following durable write. For
132
+ acknowledgement-loss, inspect remote state or a provider status query, assert the
133
+ idempotency or reconciliation record, and prove retry reaches exactly one effect.
134
+ Durable intent or an outbox makes repair observable; it does not by itself prove a
135
+ retry cannot reissue the remote mutation.
136
+
137
+ ### 3. Invariant
138
+ Properties that must hold across all valid inputs. Use property-based testing (`hypothesis`/`proptest`/`fast-check`) when input domain is large.
139
+
140
+ - **Idempotency** — N calls = 1 effect. Required for webhooks, retries, distributed ops, PUT semantics.
141
+ - **Monotonic state** — RUNNING never goes back to PENDING.
142
+ - **No duplicate side effects** — same event ID never processes twice.
143
+ - **Eventual consistency** — final state converges regardless of order.
144
+ - **Conservation** — sum of debits = sum of credits; total events in = total out + dropped.
145
+ - **Schema lock** — no silent field add/remove on stored or wire format.
146
+
147
+ ### 4. Integration
148
+ End-to-end through real stack. **Mock only at system boundaries** (network, disk, external APIs, clock, randomness). Never mock internal modules — that validates fake behaviour and integration bugs slip through.
149
+
150
+ - Full middleware chain executes (auth, rate-limit, logging, CORS).
151
+ - Real DB, real serializer, real router. Use containers, not in-memory fakes when behaviour differs.
152
+ - For spec claims involving "real-time" / "streaming" / "incremental": assert bytes arrive incrementally — not just that the parser handles pre-buffered data.
153
+ - Failure modes named in code → integration test with deterministic injection.
154
+
155
+ ### 5. Regression
156
+ Pin behaviour that must not silently change.
157
+
158
+ - Golden output files — diff on change. Agent must explain *why* a snapshot changed; never silently re-bless.
159
+ - Public API surface (`__all__`, exports, header signatures, `@sizeOf`, ABI stability).
160
+ - Breaking-change detectors (`oasdiff breaking`, `buf breaking`, `cargo public-api`).
161
+ - Old test suite passes against new code on dependency upgrades.
162
+
163
+ ---
164
+
165
+ ## Hard rules
166
+
167
+ - **Mock only at system boundaries.** No mocking internal modules. No asserting on mocks (`expect(mockFn).toHaveBeenCalled()`) without also asserting outcome state.
168
+ - **State isolation per test.** Reset globals, DB rows, fixtures, env vars, caches. Tests must pass under randomised execution order. Run `pytest -p randomly` / `cargo test -- --shuffle` / equivalent at least once per branch.
169
+ - **Specific error contracts.** Assert error type AND message AND structured fields.
170
+ - **≥50% negative-path tests** on changed surface. If success tests outnumber failure tests, you're not done.
171
+ - **Test names read as documentation.** `should_reject_empty_user_id`, `should_retry_on_502_from_gateway`, `should_not_duplicate_event_on_retry`. No `test_1`, `test_basic`, `it works`.
172
+ - **No testing private functions directly.** Test through the public surface. If a private function needs direct testing, it's actually public — promote it.
173
+ - **No duplicating implementation logic in tests.** A test that re-implements the function under test proves nothing.
174
+ - **Tests favour clarity over DRY.** Independence and explicitness beat reuse. Extract a helper only when ≥3 tests share non-trivial setup AND the helper has no logic worth hiding.
175
+ - **Untestable claims are flagged**, not silently skipped. Mark `needs infra` in the claim-tracing table.
176
+ - **Inject at the selected ordinals, not only the first interaction.** A boundary fake must select both the workflow ordinal and boundary-call ordinal. Draining a whole resource pool proves only the first acquisition and cannot cover a later one.
177
+ - **Residual state is the failure assertion.** For each injected interaction, assert what survived, what external state changed, what the user sees next, and whether retry heals it. The response alone is incomplete.
178
+ - **Unsafe external mutation ordering is a design defect.** Read-only prerequisites are valid. If a mutation can complete before any durable intent or repair record, report it; prefer durable intent first, or require idempotency, compensation, and reconciliation when sequencing cannot change.
179
+ - **Acknowledgement loss needs a remote-state proof.** For every external mutation, make the remote effect succeed while its acknowledgement is lost; inspect remote state, assert the idempotency or reconciliation record, and prove retry reaches exactly one effect. A durable record alone is not an exactly-once proof.
180
+ - **Record the partial-completion matrix.** Paste the completed rows into PR Session Notes. A checked box without the rows is indistinguishable from a skipped proof.
181
+
182
+ ## Anti-patterns (reject in review)
183
+
184
+ - ❌ Snapshot-only tests with no behavioural assertion
185
+ - ❌ Asserting on mocks instead of outcomes
186
+ - ❌ Testing private functions directly
187
+ - ❌ Duplicating implementation logic in tests
188
+ - ❌ Bare `expect error` / `assert.throws` with no type/message contract
189
+ - ❌ Tests that pass only when run in a specific order
190
+ - ❌ "Minimum N tests per file" filler that asserts the same invariant N times
191
+ - ❌ Mocking internal modules
192
+ - ❌ Coverage padding — line coverage with no behavioural claim
193
+ - ❌ Hand-wavy performance assertions ("not too slow")
194
+ - ❌ Silent golden-file re-blessing on snapshot drift
195
+ - ❌ Draining an entire pool and calling it coverage of every acquisition
196
+ - ❌ Asserting a failure response without asserting the operation's residual state and retry outcome
197
+ - ❌ Treating a durable intent row as proof that acknowledgement-loss cannot reissue a remote effect
198
+
199
+ ---
200
+
201
+ ## Failure injection toolkit
202
+
203
+ Vague claim: "handles connection reset". Real test: deterministic injection. If you can't reproduce a failure deterministically, the test is fiction.
204
+
205
+ | Failure | Tool / technique |
206
+ |---|---|
207
+ | TCP reset / half-open | `toxiproxy`, `tc netem`, raw socket close from peer |
208
+ | Slow / delayed response | `toxiproxy` latency, `respx` delay, fake clock |
209
+ | Partial read | `io.LimitedReader`, custom `Read` returning short |
210
+ | DB connection drop | `pg_terminate_backend()` mid-transaction |
211
+ | Timeout | shrink `SO_RCVTIMEO`, `tokio::time::pause()` |
212
+ | Clock skew | freeze/advance fake clock; `chrono` mock; `mock-time` |
213
+ | Disk full / EIO | `tmpfs` size limit; mock filesystem |
214
+ | OOM | `FixedBufferAllocator` (Zig), small heap caps |
215
+ | Concurrency races | `loom` (Rust), `std.Thread` + barrier (Zig) |
216
+ | Network partition | `iptables`/`pfctl` block, `toxiproxy` down |
217
+ | Selected workflow and boundary-call ordinals | Counter-controlled boundary fake fails the selected interaction |
218
+
219
+ Every failure mode named in the spec or code (`catch`, `orelse`, retry loop) needs at least one injection-driven test.
220
+
221
+ ---
222
+
223
+ ## Coverage that matters
224
+
225
+ Line coverage is noise. Track and report:
226
+
227
+ - **Branch coverage** — every `if`/`match`/`switch` arm hit
228
+ - **Error-path coverage** — every `catch`/`orelse`/`Err` constructed in code is exercised by a test
229
+ - **Input-class coverage** — empty / single / many / unicode / malformed each appear at least once per public input
230
+
231
+ Targets on touched code: branch ≥80%, error-path 100%, negative-path ratio ≥50%.
232
+
233
+ ## Mutation testing — coverage proves *ran*, mutation proves *caught*
234
+
235
+ Branch coverage says a line executed; it can't tell a real assertion from
236
+ `expect(true).toBe(true)`. Mutation testing flips operators and deletes statements in the
237
+ **changed** code and checks your tests **fail** (kill the mutant). A *surviving* mutant on
238
+ a changed line = a test that asserts nothing meaningful. Required in **Hardening** mode;
239
+ recommended on any changed logic with branching.
240
+
241
+ - **Scope to the diff** — mutate only files changed vs the PR base; full-repo mutation is
242
+ too slow to gate on.
243
+ - **Determinism:** prefer **rule-based** mutators (same mutants every run) over LLM-based
244
+ ones (nondeterministic — fights the invariance spine).
245
+ - **Gate:** **0 surviving mutants on changed lines** (or each survivor justified as an
246
+ equivalent mutant). Paste the kill ratio.
247
+
248
+ | Stack | Tool | Diff-scoping |
249
+ |---|---|---|
250
+ | TypeScript / Next.js / React | **Stryker** (`@stryker-mutator/core` + `vitest-runner`/`jest-runner` + `typescript-checker`) | `--since=<base>` / `--incremental`; scope to `lib/`, server actions, reducers, validators — **not** render-heavy `.tsx` |
251
+ | Python | `mutmut` or `cosmic-ray` | mutate changed modules only |
252
+ | Rust | `cargo-mutants` | `--in-diff <patch>` (native git-diff scoping) |
253
+ | Zig | no dedicated tool — **`universalmutator`** (regex/Comby, language-agnostic; offloads non-compiling mutants to the strict compiler), or a homegrown operator-swap harness (`==`↔`!=`, `<`↔`<=`, `+`↔`-`, `and`↔`or`, drop `orelse`/`errdefer`) driving `zig build test` | restrict the mutate target to changed files |
254
+
255
+ ## Red-green proof (every bug-fix / regression PR)
256
+
257
+ A regression test that passes on the *buggy* code proves nothing. Prove the red:
258
+
259
+ 1. Check out the parent commit (`git worktree add ../pre <parent>`) or stash the fix.
260
+ 2. Run the new test there — it **must FAIL** (the bug reproduces).
261
+ 3. Restore HEAD — the test **must PASS**.
262
+ 4. Paste both results. A test green on the pre-fix code is a false test — fix it until it
263
+ reproduces the bug, or it doesn't ship.
264
+
265
+ This is the only proof the test actually pins the bug. Mandatory whenever the PR references
266
+ a bug, incident, or "fix"; recommended for any behaviour-correcting change.
267
+
268
+ ## Spec integration
269
+
270
+ When a spec exists with these tables, treat them as authoritative test sources:
271
+
272
+ | Spec table | Generates |
273
+ |---|---|
274
+ | Test Specification | One test per row, name = `dim X.Y: <name>` |
275
+ | Error Contracts | One negative test per row asserting exact error |
276
+ | Failure Modes | One integration test per row with deterministic injection |
277
+ | Partial Completion | One row per ordered failure point with workflow-ordinal and boundary-call-ordinal injection plus residual-state assertions |
278
+ | Implementation Constraints | Verification command run + result reported |
279
+
280
+ If the spec has none of these, build a claim-tracing table from spec prose / PR description:
281
+
282
+ ```
283
+ | Claim | Test type | Injection mechanism | Test exists? |
284
+ |---|---|---|---|
285
+ | "gate results print in real time" | integration (streaming) | partial-read reader | ❌ |
286
+ | "heartbeat fires within 30s" | integration (timer) | fake clock | ❌ |
287
+ | "no busy-loop on Redis reset" | failure (fatal vs timeout) | `pg_terminate_backend` | ❌ |
288
+ ```
289
+
290
+ Every claim → ≥1 test. Untestable claims → flag `needs infra`.
291
+
292
+ ---
293
+
294
+ ## Definition of Done
295
+
296
+ Reject the change if any are missing on touched code:
297
+
298
+ - [ ] ≥50% negative-path tests for changed surface
299
+ - [ ] Boundary tests: empty, null/None, max, malformed
300
+ - [ ] Error contract tests: type + message + structured fields
301
+ - [ ] Idempotency check where retries / webhooks / distributed ops apply
302
+ - [ ] State isolation: tests pass under random execution order
303
+ - [ ] Integration test through real stack for any spec claim involving transport/streaming/real-time
304
+ - [ ] Failure-injection test for every failure mode named in code
305
+ - [ ] **Partial completion:** every operation touching multiple systems or acquiring the same resource more than once has one matrix row per ordered failure point; each row selects workflow and boundary-call ordinals, failure timing / remote outcome, scopes a concurrency-safe failpoint to one operation, and asserts durable, external, user-visible, and retry residual state; every external mutation proves acknowledgement-loss remote state plus exactly one effect on retry; the completed matrix is pasted into PR Session Notes
306
+ - [ ] Test names read as documentation
307
+ - [ ] No mocking of internal modules
308
+ - [ ] Golden-file drift, if any, has a written explanation
309
+ - [ ] Branch coverage ≥80%, error-path 100% on touched code
310
+ - [ ] **Performance:** counter-based complexity proof on the n-ladder (O-bound asserted) + median-of-K latency vs pinned baseline; N+1 round-trips ruled out
311
+ - [ ] **Zig zero-leak:** `make memleak` 0 leaks; every alloc-with-error-path proven by `std.testing.checkAllAllocationFailures`; no cross-request high-water growth over N iters
312
+ - [ ] **Concurrency:** ≥100-connection barrier-started test proves exactly-once + parallelism (peak-concurrency/lock-wait counter, or wall-time < R×); passes K runs + race detector clean
313
+ - [ ] **Refactor escalation:** any super-linear or serialization finding on touched code carries a refactor proposal (root cause + target design + patch alternative + recommendation) surfaced for decision — not silently band-aided
314
+ - [ ] **Diff ledger** resolved 100% — every changed unit has a test or an explicit `won't-test`/`needs-infra`; ledger pasted into PR Session Notes
315
+ - [ ] **Mutation** (Hardening / changed logic): 0 surviving mutants on changed lines (diff-scoped), or each survivor justified; kill ratio pasted
316
+ - [ ] **Red-green** (fix PRs): the new test fails on the parent commit and passes on HEAD — both pasted
317
+
318
+ 100% required before declaring done.
319
+
320
+ ## CI execution strategy
321
+
322
+ | Stage | What runs |
323
+ |---|---|
324
+ | **PR** | Behaviour + Failure + Regression unit; fast Integration; lint; type-check; randomised order |
325
+ | **Pre-merge** | Full Integration; Contract (`oasdiff` / `buf breaking`); coverage delta |
326
+ | **Nightly** | Concurrency stress; Fuzz (`schemathesis` / `proptest` / `cargo-fuzz`); Performance benchmarks |
327
+ | **Pre-release** | Golden full-suite; cross-platform; memory-leak (`std.testing.allocator`); ABI stability |
328
+
329
+ A test in the wrong stage either delays PRs or never runs. Place deliberately.
330
+
331
+ ## Production-safety proofs (deterministic & invariant)
332
+
333
+ These three catch bugs that reach production silently and cost the most to remove.
334
+ Each is written to be **deterministic** (same verdict every run) and **invariant**
335
+ (independent of machine, load noise, and execution order).
336
+
337
+ **The rule that governs all three: prefer counters over clocks.** An allocation
338
+ count, a query/round-trip count, a lock-wait tally, a peak-concurrency count is
339
+ noise-free and reproducible; wall-clock time is not. Where wall-clock is unavoidable
340
+ (latency budgets), use **median-of-K runs + a margin** against a **pinned baseline
341
+ file**, never a single sample. Where an input space must be covered, prefer
342
+ **exhaustive injection** (every allocation site, every interleaving the tool can
343
+ enumerate) over sampling.
344
+
345
+ ### Proof 1 — Zig: zero leaks in production, error paths included
346
+
347
+ "0 leaks" means *every* path frees, including the ones that only run on failure — not
348
+ "the happy path didn't leak."
349
+
350
+ - **Every test allocates via `std.testing.allocator`.** It fails the test on any leak
351
+ or double-free, deterministically. Gate: no test uses `std.heap.page_allocator` or a
352
+ long-lived General Purpose Allocator (GPA); `grep -rn "page_allocator" '**/test*.zig'`
353
+ in test code is empty (or justified per line).
354
+ - **Exhaustive error-path proof — `std.testing.checkAllAllocationFailures`.** For every
355
+ function that allocates *and* can return an error, run it under
356
+ `checkAllAllocationFailures` (or a `FailingAllocator` loop): it fails each allocation
357
+ site in turn and asserts the function leaks nothing on the resulting error return.
358
+ This is the *only* acceptable proof that every `errdefer` is correct — "looks right"
359
+ is not. Exhaustive over allocation sites ⇒ deterministic.
360
+ - **No cross-request growth.** Drive the full lifecycle (init → handle → deinit, or
361
+ arena acquire → use → reset) for N iterations and assert outstanding-bytes /
362
+ high-water does **not** grow monotonically. A slow leak the single-shot test misses
363
+ shows up here as linear growth.
364
+ - **Gate:** `make memleak` → **0 leaks**, output pasted into PR Session Notes; cross-compile
365
+ both targets first (`x86_64-linux`, `aarch64-linux`) — stdlib alloc paths differ from macOS.
366
+
367
+ ### Proof 2 — Concurrency proven at ≥100 connections (no hidden global lock)
368
+
369
+ The code must be correct **and actually parallel** under real contention. A single
370
+ global `Thread.Mutex` makes correctness tests pass while quietly serialising every
371
+ request — exactly the regression this proof exists to catch.
372
+
373
+ - **Real contention, deterministic start.** ≥100 concurrent clients/connections,
374
+ released together off a **barrier** so contention is real and reproducible (not
375
+ staggered by spawn latency).
376
+ - **Correctness invariant (the hard gate, fully deterministic):** same row / same
377
+ idempotency key / same lease under the 100-way race → **exactly one effect**, no lost
378
+ update, no corruption. Assert the invariant, not "no crash."
379
+ - **Parallelism assertion (catches the global-lock regression):** prove work runs in
380
+ parallel. Cheapest deterministic form is a **counter** — assert peak simultaneous
381
+ in-flight ≥ a threshold, or lock-wait ≈ 0. Where only wall-clock exists, assert 100
382
+ concurrent ops finish in **< R×** a single op (R with margin: a global lock blows past
383
+ ~100×, so R≈10 catches it without flaking), median-of-K.
384
+ - **Flush ordering bugs:** run K× under randomised order. `loom` (Rust) enumerates
385
+ interleavings exhaustively — use it where the stack supports it; for Zig, barrier +
386
+ repeat + invariant assertions.
387
+ - **Gate:** the ≥100-connection test passes K consecutive runs; race detector clean where
388
+ available; counters/timing pasted.
389
+ - **A serialization bottleneck is a *structural* finding, not a patch target.** If the
390
+ parallelism counter shows one global lock serialising requests, propose the concurrent
391
+ redesign (shard the lock · per-key/per-shard locks · lock-free CAS · read-copy-update),
392
+ not a hack around it — and let the ≥100-connection parallelism counter be the acceptance
393
+ proof of the redesign (Proof 3 optimisation loop, step 3).
394
+
395
+ ### Proof 3 — Performance & complexity budget (and how I optimise to meet it)
396
+
397
+ A changed hot path declares a latency budget **and** a proven complexity bound — and
398
+ when it misses, I optimise deterministically rather than hand-wave "no O(n²)".
399
+
400
+ - **Complexity by counters (deterministic).** Measure a **work counter** (allocations,
401
+ DB/Redis round-trips, comparisons, bytes scanned) at n, 2n, 4n, 8n and assert the
402
+ growth matches the claim: O(n) → ~doubles per doubling; O(1) → flat; O(n²) → ~4×.
403
+ Counters are noise-free, so the verdict is invariant across machines.
404
+ - **N+1 detector (the most common super-linear surprise):** assert round-trip count is
405
+ constant or linear in n, never multiplicative — count at n and 10n.
406
+ - **Latency budget (when wall-clock is required):** fixed input + warmup + **median-of-K**
407
+ + p95/p99 threshold + regression threshold vs a **pinned baseline file** (fail at 1.5×
408
+ baseline). Single samples banned — they flake.
409
+ - **Conservation:** allocation count bounded; connection / file-descriptor (FD) open-count
410
+ before == after.
411
+
412
+ **The optimisation loop I run when a budget is missed** (deterministic, repeatable).
413
+ Default bias for the perf/concurrency class: **propose the structural refactor, not the
414
+ band-aid** — a patched O(n²) or a hacked-around global lock still ships a fragile design.
415
+
416
+ 1. **Measure with counters first**, not a profiler guess — find the term that grows
417
+ super-linearly (the N+1, the O(n²) nested scan, the per-iteration allocation) or the
418
+ **serialization point** (one lock every request waits on).
419
+ 2. **Classify the fix — local vs structural.** *Local* = a genuine one-liner: add an
420
+ index, hoist one allocation, collapse one N+1 into a join. *Structural* = the data
421
+ structure, locking model, or call graph is wrong: an O(n²) algorithm, a global
422
+ `Thread.Mutex` serialising all requests, per-request work that belongs in a shared
423
+ cache, a layer doing I/O in a loop.
424
+ 3. **Structural ⇒ analyse and propose the large refactor — surface it, don't silently
425
+ band-aid.** Write: the **root cause**, the **target design** that is deterministic +
426
+ performant + concurrent (e.g. shard the lock / per-key or per-shard locks / lock-free
427
+ CAS instead of one mutex; one batched query instead of N+1 scattered across layers;
428
+ an index or precompute instead of repeated scans; streaming instead of full-buffer),
429
+ the **throwaway-patch alternative and why it's worse**, and the **blast radius**.
430
+ This follows the patch-vs-refactor discipline (`AGENTS.md` Decomposition) — the call
431
+ is surfaced to Indy, not made unilaterally. The test then pins the **target** bound,
432
+ so the refactor is provably met and can't regress.
433
+ 4. **Local ⇒ apply**, then re-measure the same counter on the same n-ladder and assert
434
+ the growth ratio dropped to target. The before/after counter delta *is* the proof —
435
+ pin it in the baseline file so a future regression fails the gate.
436
+ 5. If the budget still can't be met after the agreed change, that's a **design finding** —
437
+ surface it; never loosen the threshold silently.
438
+
439
+ ---
440
+
441
+ ## Stack appendix
442
+
443
+ Tooling per stack:
444
+
445
+ | Stack | Runner | Branch coverage | Property | Fuzz | Concurrency |
446
+ |---|---|---|---|---|---|
447
+ | Python | `pytest` | `pytest-cov --branch` | `hypothesis` | `atheris` | `ThreadPoolExecutor`, `asyncio` |
448
+ | Python SDK | `pytest` + `responses`/`respx` | same | `hypothesis` | `schemathesis` | `asyncio.gather` |
449
+ | OpenAPI | `schemathesis` | n/a | n/a | `schemathesis run --checks all` | n/a |
450
+ | JS/TS CLI | `vitest` + `execa` | `c8` branch | `fast-check` | `fast-check` | `Promise.all` |
451
+ | React | `vitest` + `testing-library` | `c8` branch | `fast-check` | n/a | `userEvent` rapid |
452
+ | Zig | `zig build test` | manual | manual | manual mutation | `std.Thread.spawn` |
453
+ | Rust | `cargo test` | `cargo-llvm-cov` | `proptest` | `cargo-fuzz` | `loom`, `tokio::spawn` |
454
+ | Shell | `bats` | `bashcov` | n/a | n/a | `&` + `wait`, `flock` |
455
+
456
+ Naming convention by stack:
457
+
458
+ - Python: `test_should_<behaviour>_when_<condition>`
459
+ - Rust: `fn should_<behaviour>_when_<condition>()` in `mod tests`
460
+ - Zig: `test "should <behaviour> when <condition>"`
461
+ - React/TS: `it("should <behaviour> when <condition>")`
462
+ - Shell/bats: `@test "should <behaviour> when <condition>"`
463
+
464
+ Stack-specific must-knows:
465
+
466
+ - **Zig** — `std.testing.allocator` auto-detects leaks; verify cross-compile (`x86_64-linux`, `aarch64-linux`) since stdlib API may differ from macOS; `conn.query()` requires `.drain()`.
467
+ - **Python SDK** — assert API key not in debug output; HTTPS enforced; pagination doesn't load all pages in memory; rate-limit retry honours `Retry-After`.
468
+ - **Rust gRPC** — protobuf round-trip; streaming doesn't buffer full response; `tonic::Status` codes assert specific variant.
469
+ - **Rust HTTP** — `axum::extract::rejection` paths; payload too large (413); missing `Content-Type`.
470
+ - **React** — `axe` accessibility audit; no `dangerouslySetInnerHTML` with unsanitized input; controlled vs uncontrolled input switching.
471
+ - **Shell** — all variables quoted (`"$var"`); `set -euo pipefail`; no `eval` with user input; `mktemp` for temp files.
472
+ - **OpenAPI** — `schemathesis run --checks all`; `oasdiff breaking` in CI; `spectral lint` for style.
473
+
474
+ ## OWASP for agent-bound data
475
+
476
+ Code paths sending data to LLMs / tool-calling agents (directly or via queue/DB/API):
477
+
478
+ - [ ] User-controlled input never concatenated raw into prompts; structured roles only
479
+ - [ ] Length-bounded, validated, sanitised against instruction-injection markers (`ignore previous`, role-confusion XML/JSON)
480
+ - [ ] Tool permissions scoped per-invocation, not globally granted
481
+ - [ ] Secrets/credentials/PII stripped before reaching the agent; opaque references only
482
+ - [ ] Agent output validated against allowlist/schema before any side effect; never `eval()` raw output
483
+ - [ ] Authorization re-checked at every hop; agent inherits caller's permissions, not service-level
484
+ - [ ] Audit log: who, input, tools called, output, side effects — tamper-resistant
485
+ - [ ] Fail-closed on any trust check failure — never silently fall back
486
+
487
+ ---
488
+
489
+ ## Output format
490
+
491
+ For each test write:
492
+
493
+ 1. Name (per stack convention)
494
+ 2. Category (Behaviour / Failure / Invariant / Integration / Regression)
495
+ 3. The bug it catches in one line
496
+ 4. The test code
497
+ 5. Failure-injection mechanism if applicable
498
+
499
+ After the suite produce:
500
+
501
+ ```
502
+ ## Coverage report: <module>
503
+ | Metric | Before | After | Target |
504
+ | Branch | 41% | 87% | ≥80% |
505
+ | Error-path | 60% | 100% | 100% |
506
+ | Negative-path ratio | 20% | 55% | ≥50% |
507
+ Categories: Behaviour ✅ · Failure ✅ · Invariant ✅ · Integration ✅ · Regression ✅
508
+ Production-safety: Zig-leak ✅ 0 (incl. checkAllAllocationFailures) · Concurrency ✅ 100-conn exactly-once + parallel · Perf ✅ O(n) counter + p95 vs baseline
509
+ Refactor proposals surfaced: <n> (or "none — no structural finding")
510
+ Partial completion: <rows complete>/<ordered failure points> · residual state + retry asserted per row
511
+ Diff ledger: 7/7 rows resolved (5 tested · 2 won't-test w/ reason)
512
+ Mutation (changed lines): 18/19 killed · 1 justified equivalent
513
+ Red-green (fix PRs): ✅ fails on parent, passes on HEAD
514
+ DoD checklist: 19/19
515
+ Mode: Change-set | Hardening | Deep audit
516
+ ```
517
+
518
+ ## Validation gate
519
+
520
+ Single command runs all tests in the chosen mode. Report exact command, pass/fail/skip counts, coverage delta, and DoD checklist status. Do not declare done until DoD is 100%.
@@ -0,0 +1,70 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join, resolve } from "node:path";
5
+
6
+ const ROOT = resolve(import.meta.dir, "..");
7
+ const COMMAND = resolve(ROOT, "bin/orly");
8
+
9
+ describe("orly command", () => {
10
+ test("uses the concise public command surface", () => {
11
+ const result = Bun.spawnSync([COMMAND, "--help"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
12
+ const output = result.stdout.toString();
13
+
14
+ expect(result.exitCode).toBe(0);
15
+ expect(output).toContain("orly gate");
16
+ expect(output).toContain("orly override <CRITERION> --reason <REASON>");
17
+ expect(output).not.toContain("orly adopt");
18
+ expect(output).not.toContain("oracle-rules");
19
+ });
20
+
21
+ // Adversarial review found recordOptIn running before the --dry-run branch,
22
+ // so a preview created .oracle/orly.json — and on an installed repository
23
+ // permanently added the pack. A dry run changes nothing, opt-in included.
24
+ test("--dry-run records no pack", () => {
25
+ const repo = mkdtempSync(join(tmpdir(), "orly-dry-"));
26
+ try {
27
+ Bun.spawnSync(["git", "init", "-q", repo], { stdout: "pipe", stderr: "pipe" });
28
+ writeFileSync(join(repo, "m.go"), "package main\n");
29
+
30
+ const result = Bun.spawnSync([COMMAND, "init", "--with", "persona.indy", "--dry-run"], { cwd: repo, stdout: "pipe", stderr: "pipe" });
31
+
32
+ expect(result.exitCode).toBe(0);
33
+ expect(result.stdout.toString()).toContain("Generated by");
34
+ expect(existsSync(join(repo, ".oracle/orly.json"))).toBe(false);
35
+ expect(existsSync(join(repo, "AGENTS.orly.md"))).toBe(false);
36
+ } finally {
37
+ rmSync(repo, { recursive: true, force: true });
38
+ }
39
+ });
40
+
41
+ test("reports the version carried by the package manifest", async () => {
42
+ const result = Bun.spawnSync([COMMAND, "--version"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
43
+ const manifest = (await Bun.file(resolve(ROOT, "package.json")).json()) as { version: string };
44
+
45
+ expect(result.exitCode).toBe(0);
46
+ expect(result.stdout.toString().trim()).toBe(manifest.version);
47
+ });
48
+
49
+ // `validate` is gone: verifyRenders calls model.validate() before its own
50
+ // checks, so the verb was a strict subset of `verify` with a second name.
51
+ test("verify validates the registry on its way to the render checks", () => {
52
+ const result = Bun.spawnSync([COMMAND, "verify"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
53
+
54
+ expect(result.exitCode).toBe(0);
55
+ expect(result.stdout.toString()).toContain("render.local.idempotent");
56
+ });
57
+
58
+ test("validate is not a verb", () => {
59
+ const result = Bun.spawnSync([COMMAND, "validate"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
60
+
61
+ expect(result.exitCode).toBe(1);
62
+ expect(result.stderr.toString()).toContain("unknown command: validate");
63
+ });
64
+
65
+ test("verify needs no --all, because there was never a not-all", () => {
66
+ const result = Bun.spawnSync([COMMAND, "verify"], { cwd: ROOT, stdout: "pipe", stderr: "pipe" });
67
+
68
+ expect(result.exitCode).toBe(0);
69
+ });
70
+ });