@agentsfleet/orly 0.10.13 → 0.11.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 (155) hide show
  1. package/.githooks/pre-push +2 -1
  2. package/AGENTS.md +21 -31
  3. package/README.md +65 -29
  4. package/SOUL.md +13 -23
  5. package/audits/agents-md.md +67 -57
  6. package/audits/deinit-pairs.sh +13 -18
  7. package/audits/design-tokens.sh +9 -23
  8. package/audits/doc-read.sh +10 -33
  9. package/audits/doc-reads.ts +41 -0
  10. package/audits/error-codes.sh +18 -25
  11. package/audits/logging-rust.awk +145 -0
  12. package/audits/logging.sh +10 -166
  13. package/audits/msid-ui.sh +45 -18
  14. package/audits/rule-ledger-lib.sh +9 -6
  15. package/audits/rust-error.sh +20 -15
  16. package/audits/scope.sh +88 -0
  17. package/audits/spec-template.sh +2 -2
  18. package/audits/spec-template.ts +1 -1
  19. package/audits/sql-mod.sh +11 -14
  20. package/audits/ufs-strings.awk +164 -0
  21. package/audits/ufs.sh +10 -188
  22. package/core/operating-model.md +8 -8
  23. package/dispatch/edit_rules.md +8 -6
  24. package/dispatch/lib.sh +41 -26
  25. package/dispatch/lifecycle.md +18 -6
  26. package/dispatch/name_architecture.md +8 -3
  27. package/dispatch/verify.md +1 -1
  28. package/dispatch/write_any.md +10 -3
  29. package/dispatch/write_changelog.md +7 -4
  30. package/dispatch/write_go.md +1 -1
  31. package/dispatch/write_http.md +5 -7
  32. package/dispatch/write_python.md +1 -1
  33. package/dispatch/write_rust.md +17 -1
  34. package/dispatch/write_shell.md +1 -1
  35. package/dispatch/write_spec.md +3 -1
  36. package/dispatch/write_sql.md +28 -6
  37. package/dispatch/write_ts_adhere_bun.md +11 -0
  38. package/dispatch/write_zig.md +53 -21
  39. package/docs/CHANGELOG_VOICE.md +11 -2
  40. package/docs/EXECUTE_DOC_READS.md +10 -10
  41. package/docs/HARNESS_VERIFY_OUTPUT.md +6 -3
  42. package/docs/JUDGMENTS.md +153 -0
  43. package/docs/LIFECYCLE_PATTERNS.md +26 -16
  44. package/docs/LOGGING_STANDARD.md +39 -19
  45. package/docs/ORLY_ARCHITECTURE.md +77 -6
  46. package/docs/RELEASE_TEMPLATE.md +21 -5
  47. package/docs/REST_API_DESIGN_GUIDELINES.md +86 -21
  48. package/docs/RULE_ENFORCEMENT.md +6 -6
  49. package/docs/SCHEMA_CONVENTIONS.md +24 -3
  50. package/docs/TEMPLATE.md +4 -4
  51. package/docs/VERIFY_TIERS.md +2 -2
  52. package/docs/greptile-learnings/RULES.md +81 -26
  53. package/llms.txt +37 -34
  54. package/package.json +6 -1
  55. package/packs/language/go/rules.md +1 -1
  56. package/packs/language/python/rules.md +1 -1
  57. package/packs/language/rust/rules.md +17 -1
  58. package/packs/language/shell/rules.md +1 -1
  59. package/registry.json +22 -7
  60. package/schemas/profile.schema.json +48 -5
  61. package/skills/orly-babysit-prs/SKILL.md +87 -34
  62. package/skills/orly-spec-new/SKILL.md +19 -6
  63. package/skills/orly-write-integration-test/SKILL.md +4 -4
  64. package/skills/orly-write-unit-test/SKILL.md +10 -1
  65. package/src/cli.test.ts +2 -2
  66. package/src/cli.ts +19 -75
  67. package/src/cli_help.ts +69 -0
  68. package/src/command_limits.ts +20 -0
  69. package/src/command_process.test.ts +109 -0
  70. package/src/command_process.ts +88 -0
  71. package/src/command_runner.test.ts +88 -0
  72. package/src/command_runner.ts +33 -0
  73. package/src/comprehension_cache.test.ts +123 -0
  74. package/src/config.test.ts +5 -4
  75. package/src/config.ts +26 -164
  76. package/src/config_discovery.ts +159 -0
  77. package/src/config_validation.test.ts +56 -0
  78. package/src/config_validation.ts +25 -0
  79. package/src/criteria.test.ts +1 -1
  80. package/src/criteria.ts +10 -26
  81. package/src/criteria_spec.ts +12 -20
  82. package/src/criteria_support.ts +2 -19
  83. package/src/doc_reads.test.ts +42 -0
  84. package/src/doc_rules.ts +2 -2
  85. package/src/evaluation_resources.test.ts +64 -0
  86. package/src/execution/config.ts +22 -0
  87. package/src/execution/plan.test.ts +65 -0
  88. package/src/execution/plan.ts +51 -0
  89. package/src/gates.ts +39 -24
  90. package/src/gates_spec.test.ts +110 -4
  91. package/src/gates_test_support.ts +9 -7
  92. package/src/install.test.ts +38 -222
  93. package/src/install.ts +117 -308
  94. package/src/install_loaders.test.ts +203 -0
  95. package/src/install_nested.test.ts +2 -2
  96. package/src/install_packs.test.ts +66 -6
  97. package/src/installation/hooks.ts +63 -0
  98. package/src/installation/lock.test.ts +97 -0
  99. package/src/installation/lock.ts +56 -0
  100. package/src/installation/migration.test.ts +212 -0
  101. package/src/installation/migration.ts +65 -0
  102. package/src/installation/plan.ts +96 -0
  103. package/src/installation/transaction.ts +126 -0
  104. package/src/installation_preservation.test.ts +48 -0
  105. package/src/judgments/advice.test.ts +38 -0
  106. package/src/judgments/advice.ts +32 -0
  107. package/src/judgments/command.integration.test.ts +47 -0
  108. package/src/judgments/command.ts +90 -0
  109. package/src/judgments/constants.ts +55 -0
  110. package/src/judgments/evidence.test.ts +48 -0
  111. package/src/judgments/evidence.ts +34 -0
  112. package/src/judgments/files.test.ts +62 -0
  113. package/src/judgments/files.ts +73 -0
  114. package/src/judgments/questions.test.ts +19 -0
  115. package/src/judgments/questions.ts +85 -0
  116. package/src/judgments/replay.test.ts +43 -0
  117. package/src/judgments/replay.ts +66 -0
  118. package/src/judgments/runner.integration.test.ts +165 -0
  119. package/src/judgments/runner.ts +97 -0
  120. package/src/judgments/scan.test.ts +19 -0
  121. package/src/judgments/scan.ts +40 -0
  122. package/src/judgments/syntax.test.ts +115 -0
  123. package/src/judgments/syntax.ts +71 -0
  124. package/src/judgments/syntax_worker.ts +70 -0
  125. package/src/judgments/test_support.ts +60 -0
  126. package/src/judgments/transport.integration.test.ts +63 -0
  127. package/src/judgments/transport.ts +35 -0
  128. package/src/judgments/types.test.ts +43 -0
  129. package/src/judgments/types.ts +66 -0
  130. package/src/judgments/wire.test.ts +48 -0
  131. package/src/judgments/wire.ts +40 -0
  132. package/src/loaders.test.ts +13 -16
  133. package/src/loaders.ts +28 -10
  134. package/src/model.test.ts +20 -3
  135. package/src/model.ts +7 -18
  136. package/src/pack_hygiene.test.ts +42 -1
  137. package/src/pack_hygiene.ts +18 -8
  138. package/src/references.ts +15 -7
  139. package/src/release_inventory.test.ts +80 -0
  140. package/src/render.test.ts +1 -1
  141. package/src/render.ts +3 -8
  142. package/src/required_scanner.test.ts +36 -0
  143. package/src/review_poll.test.ts +158 -0
  144. package/src/scanner_scope.test.ts +202 -0
  145. package/src/setup.test.ts +9 -6
  146. package/src/spec_baseline.test.ts +1 -1
  147. package/src/spec_completion.test.ts +74 -0
  148. package/src/spec_obligations.ts +64 -0
  149. package/src/spec_rehearsal.test.ts +182 -0
  150. package/src/spec_template_io.test.ts +11 -9
  151. package/src/surfaces.test.ts +2 -0
  152. package/src/surfaces.ts +1 -1
  153. package/src/validation.ts +7 -4
  154. package/src/verify.test.ts +18 -2
  155. package/src/verify.ts +7 -3
@@ -280,6 +280,16 @@ immediately preceding the edit. **User-invokable only.** Generic "scope creep" /
280
280
  - Length caps live in `dispatch/write_any.md` (LENGTH GATE — file ≤ 350, fn ≤ 50, method ≤ 70 — same as every other source language).
281
281
  - This file is the **TypeScript / Bun-specific** layer that those universal rules cannot express. Scan its section headers at the first TS/JS touch (`grep -nE "^##+ "` — two-plus hashes, so nested cards surface) and read the sections the diff shape touches; on sub-task shape change, re-scan and read only newly-touched sections — never the full file.
282
282
 
283
+ ## Repository design system
284
+
285
+ > [JUDGMENT → ARCH]
286
+
287
+ Use the consuming repository's declared component library and design tokens.
288
+ Locate its exports and styles before substituting primitives; do not import
289
+ another product's package or invent its directory layout.
290
+ Run the repository's declared checks and review substitutions against that source.
291
+
292
+ <!-- oracle-packs:start product.agentsfleet -->
283
293
  ## Merged from dissolved gate cards
284
294
 
285
295
  > [container]
@@ -491,3 +501,4 @@ If the arbitrary you want is a legitimate design value that simply has no token
491
501
  4. Add the utility to the table above in this gate body (same dotfiles commit — Invariance Suite Gate applies)
492
502
 
493
503
  This is the path to a system that grows without dilution.
504
+ <!-- oracle-packs:end -->
@@ -31,13 +31,15 @@ Status: Canonical Zig source of truth for agents and commits
31
31
 
32
32
  > [JUDGMENT → ARCH]
33
33
 
34
- For every commit that touches `*.zig`, the agent runs the workflow below — no human approvals required mid-loop. This façade is the human-discipline complement to `make lint`; rules already enforced by lint are not duplicated here. The agent owns the rules that lint cannot mechanically catch.
34
+ For every commit touching Zig, run the workflow below alongside the repository's
35
+ declared conformance and verification commands. The reviewer owns the rules
36
+ those commands cannot mechanically check.
35
37
 
36
38
  1. **Before write (trigger: about to edit / create `*.zig`):** scan this façade's section headers (`grep -n "^## " dispatch/write_zig.md`). Re-read any section whose topic the diff touches: concurrency for atomics / threads, allocator-ownership for new structs, doc-comments for new `pub` types, comptime assertions for new invariants, single-type-module pattern for new file structure, etc.
37
39
 
38
40
  2. **During write:** for each surfaced uncertainty (atomic ordering choice, allocator pattern, naming, structural choice), state the rule and the choice in chat — don't decide silently.
39
41
 
40
- 3. **Before commit (post-`make lint`):** run a self-audit grep against the staged diff for the rules `make lint` does not enforce. Don't ask for approval; do the audit and either comply or state explicitly why an exception applies.
42
+ 3. **Before commit (after declared conformance):** inspect the staged diff for rules the repository's checks do not enforce. Comply or record an explicitly authorized exception.
41
43
  - Weak atomic orderings (`\.\(acquire\|release\|monotonic\|unordered\)\b`) — every match needs a `// safe because: ...` comment within 3 lines.
42
44
  - New `pub` symbols — confirm at least one external import via `grep -rn "<symbol>"`. Remove `pub` if unreferenced.
43
45
  - New structs that own heap memory — confirm `alloc:` field OR doc-comment naming the caller-owned-allocator pattern.
@@ -55,15 +57,15 @@ For every commit that touches `*.zig`, the agent runs the workflow below — no
55
57
 
56
58
  > [DETERMINISTIC → TODO-CHECK]
57
59
 
58
- - Run `make lint`, `make test`, and `gitleaks detect` before any commit that includes Zig changes.
59
- - Run the repository's database-backed integration lane, with `TEST_DATABASE_URL` pointed at its test database, when touching DB-backed handlers, proposal flows, or temp-table-based Zig tests.
60
+ - Run the repository's declared conformance and verification commands and required secret scanner before committing Zig changes.
61
+ - Run its database-backed integration lane with its declared test configuration when touching database behavior.
60
62
  - Read this file before creating any new `*.zig` file.
61
63
  - Use `conn.exec()` for INSERT / UPDATE / DDL whenever possible.
62
64
  - Drain early-exit `conn.query()` results before `deinit()`.
63
65
  - Copy row-backed slices before `q.drain()` or `q.deinit()`.
64
66
  - Materialize rows into owned memory before issuing writes on the same `pg.Conn`.
65
67
  - Keep temp-table fixtures aligned with the real production write contract.
66
- - Use `var rows: std.ArrayList(T) = .empty;` for ArrayList init (Zig 0.16; the `= .{}` form was 0.15). Pass alloc per-operation: `append(alloc, ...)`, `toOwnedSlice(alloc)`, `deinit(alloc)`.
68
+ - Match container initialization and allocator calls to the repository's pinned Zig version; read that version's implementation before changing the shape.
67
69
  - Use `q.*.next()` and `q.*.drain()` when the query result is passed through `anytype` as a pointer (`&q`). Direct local vars use `q.next()`.
68
70
  - Reference nested struct types with the full path: `Module.Struct.NestedType`, not `Module.NestedType`.
69
71
  - Add new test files to test discovery. Either `_ = @import("path/to/new_file.zig");` in `main.zig`'s test block, or — preferred when a façade already exists — in a `test {}` block inside the façade (`test { _ = @import("foo_test.zig"); }`). Zig strips `test {}` blocks in release builds, so this adds zero bytes to the production binary. The façade pattern keeps `main.zig` at a flat one-line-per-module list and lets each module own its own test discovery.
@@ -99,12 +101,18 @@ For every commit that touches `*.zig`, the agent runs the workflow below — no
99
101
 
100
102
  > [DETERMINISTIC → PUB]
101
103
 
104
+ Use the repository's selected linter and preserve its unused-public-symbol
105
+ checks. If it has no such checker, review public consumers explicitly;
106
+ installing this language pack does not select a linter version.
107
+
108
+ <!-- oracle-packs:start product.agentsfleet -->
102
109
  - This repo uses `zlint` as part of `make lint`.
103
110
  - Pinned version: `v0.9.0`.
104
111
  - **`unused-decls: error` is load-bearing.** PUB GATE (the pub-surface section of this façade) delegates mechanical consumer-grep to this rule — a `pub` without an in-tree consumer fails `make lint`. Disabling or downgrading it silently bypasses half the gate; if you must, amend PUB GATE in the same diff so the design call is captured elsewhere.
105
112
  - `suppressed-errors` stays off because this repo intentionally uses narrow `pg` cleanup patterns that a generic rule cannot classify correctly.
106
113
  - `unsafe-undefined` is a good future tightening target once current low-level uses are cleaned up or annotated.
107
114
  - A disabled ZLint is not useful; prefer a scoped ruleset that passes today and tightens over time.
115
+ <!-- oracle-packs:end -->
108
116
 
109
117
  ## Memory Safety Rules
110
118
 
@@ -262,10 +270,15 @@ fix it in the same commit. No spec needed — these are incremental improvements
262
270
  - Decide ownership before writing helpers: allocator, free/deinit path, and whether data is owned or borrowed.
263
271
  - If the file touches `pg`, apply the query lifecycle rules above before writing the first helper.
264
272
 
265
- ## HTTP Integration Tests — Use TestHarness
273
+ ## HTTP Integration Tests
266
274
 
267
275
  > [JUDGMENT → ARCH]
268
276
 
277
+ Use the repository's shared HTTP harness and real schema fixtures.
278
+ Register new tests in its discovery graph and keep setup and cleanup owned by
279
+ each test. Do not assume another product's harness or environment variable.
280
+
281
+ <!-- oracle-packs:start product.agentsfleet -->
269
282
  Canonical source: `src/http/test_harness.zig`. Every `*_http_integration_test.zig` under `src/http/` MUST consume it.
270
283
 
271
284
  - **Do not** define a local `TestServer` / `RunningServer` struct, local `startTestServer()` / `startServer()`, local `sendReq()` / `sendRequest()`, or local `waitForServer()`. These are provided by `TestHarness` with a fluent Request/Response API.
@@ -274,17 +287,17 @@ Canonical source: `src/http/test_harness.zig`. Every `*_http_integration_test.zi
274
287
  - **Test fixtures live in a sibling `*_test_fixtures.zig`** beside the integration file, or in a shared fixture module — not inlined. See `src/http/webhook_test_fixtures.zig` for the pattern.
275
288
  - **Skip gracefully when DB is absent** — `TestHarness.start` returns `error.SkipZigTest` when `TEST_DATABASE_URL` is unset; tests just propagate it.
276
289
  - New file registration: add `_ = @import("..._test.zig")` to the `test {}` block at the bottom of `src/http/server.zig` (not `src/main.zig`). Integration tests discover from there.
290
+ <!-- oracle-packs:end -->
277
291
 
278
292
  ## Commands
279
293
 
280
294
  > [DETERMINISTIC → TODO-CHECK]
281
295
 
282
- - `make lint`
283
- - `make test`
284
- - The database-backed integration lane, with `TEST_DATABASE_URL` pointed at the test database
285
- - `gitleaks detect`
286
- - Connection-drain audit — a static check that every `conn.query()` has `.drain()` in the same function. Run the repository's lane for it when touching any file that calls `conn.query()`.
296
+ - Declared `conform` and applicable `verify.*` commands from `.orly/orly.json`.
297
+ - The repository's database integration configuration and required secret scanner.
298
+ - Query cleanup — drain every result before releasing its connection, including early exits. Use wrapper-owned draining where the repository provides it. Run any declared cleanup check and review the owner's cleanup path; no generic static check proves wrapper behavior.
287
299
 
300
+ <!-- oracle-packs:start product.agentsfleet -->
288
301
  ## Zig 0.15.2 API Gotchas (M3_001)
289
302
 
290
303
  > [DETERMINISTIC → XCOMPILE]
@@ -293,17 +306,24 @@ Canonical source: `src/http/test_harness.zig`. Every `*_http_integration_test.zi
293
306
  - `std.fmt.parseHex` does not exist. Use `std.fmt.hexToBytes(&out, hex_str)` to decode hex to bytes.
294
307
  - `std.fmt.fmtSliceHexLower` does not exist. Use `std.fmt.bytesToHex(bytes, .lower)` to encode bytes to hex.
295
308
  - When unsure about an API, check the codebase first: `grep -rn "ArrayList\|hexToBytes" src/ --include="*.zig"` to see how existing code uses it.
309
+ <!-- oracle-packs:end -->
296
310
 
297
311
  ## Cross-Compile Verification (M22_001)
298
312
 
299
313
  > [DETERMINISTIC → XCOMPILE]
300
314
 
315
+ Compile the repository's declared platform matrix, including applicable test
316
+ graphs. Execute platform-specific tests on their declared supported runtime;
317
+ compilation alone does not prove runtime behavior.
318
+
319
+ <!-- oracle-packs:start product.agentsfleet -->
301
320
  - Run `zig build -Dtarget=x86_64-linux && zig build -Dtarget=aarch64-linux` before every commit that touches Zig files. Do not rely on macOS-only compilation.
302
321
  - **Production-binary targets alone do NOT catch Linux-gated drift.** Code inside `if (builtin.os.tag == .linux)` branches and test-only helpers is comptime-dead on a macOS target and never analysed in the production graph (M82 shipped green locally; three Linux-gated `std.fs.accessAbsolute` sites failed CI). For cross-platform changes, also compile the linux **test graphs**: `zig build test -Dtarget=x86_64-linux` + `zig build test-lib -Dtarget=x86_64-linux` + `zig build --build-file build_runner.zig test -Dtarget=x86_64-linux`. A clean compile ending only in `unable to execute binaries from the target` is the PASS signal.
303
322
  - **Linux-gated tests compile but never RUN on macOS** (`SkipZigTest` off-linux) — a wrong runtime assertion slips through to CI (M84_007). To execute locally: cross-compile the test graph `-Dtarget=aarch64-linux`, take the static ELF from `.zig-cache/o/*/`, and run it in a **native arm64** container: `docker run --rm --platform linux/arm64 -v "$PWD:/w" -w /w debian:stable-slim /w/<binary>`. qemu-emulated x86_64 is a false oracle — its fork/clone emulation breaks tests that pass on real Linux. Note: prod children run under bwrap (closes non-passed fds), so bwrap-less tests must assert the *relative* fd property (child fds ⊆ parent), never absolute fd counts.
304
323
  - `std.http.Client.open()` does not exist on Linux targets in Zig 0.15.2. Use `client.request()` + `response.reader()` + `readVec()` for cross-platform HTTP streaming.
305
324
  - `std.Io.Reader` on Linux has `readVec()`, not `read()`. Use `readVec(&[_][]u8{&buf})` for single-buffer reads.
306
325
  - Verify stdlib API existence by grepping: `grep -n "pub fn" ~/.local/share/mise/installs/zig/*/lib/std/http/Client.zig`
326
+ <!-- oracle-packs:end -->
307
327
 
308
328
  ## TLS Transport (M22_001)
309
329
 
@@ -496,7 +516,7 @@ Rules:
496
516
 
497
517
  > [DETERMINISTIC → XCOMPILE]
498
518
 
499
- - Verification runs the repository's DECLARED commands (`.oracle/orly.json`), never `zig build test` standalone. `zig build test` runs only the Zig unit set and silently skips every other language's tests and the cross-language gates the declared commands cover. Use `zig build` for compilation, never for "did my change pass tests".
519
+ - Verification runs the repository's DECLARED commands (`.orly/orly.json`), never `zig build test` standalone. `zig build test` runs only the Zig unit set and silently skips every other language's tests and the cross-language gates the declared commands cover. Use `zig build` for compilation, never for "did my change pass tests".
500
520
  - A memory-leak lane, where the repository declares one, is required when the diff touches server lifecycle, allocator wiring, or cross-thread heap ownership. The macOS `leaks` tool prints a "not debuggable" line under System Integrity Protection — that is expected; the authoritative signal is the allocator-leak phase across `std.testing.allocator`-wrapped tests.
501
521
 
502
522
  ## Doc-Comments and Inline Comments
@@ -530,7 +550,8 @@ Extends "Type Design Rules". Two patterns, both legitimate; pick deliberately an
530
550
 
531
551
  > [DETERMINISTIC → TODO-CHECK]
532
552
 
533
- - snake_case throughout: file names, fields, functions, locals, constants. agentsfleet has no JS-interop boundary — there is no carve-out for camelCase fields. (Bun mixes the two for JS-mapped fields; we do not.)
553
+ - Use snake_case for Zig names. Match externally defined field spellings only at an explicit interoperability boundary.
554
+ - `agentsfleet` has no JavaScript interoperability boundary; its Zig fields stay snake_case. <!-- oracle-packs:product.agentsfleet -->
534
555
 
535
556
  ## RULE UFS — Named constants for repeated and semantic literals
536
557
 
@@ -641,8 +662,8 @@ paths never reach a global to allocate. Debug/test builds pick the leak-checking
641
662
  Purpose Allocator (GPA); release picks the C allocator. Global state never holds the allocator
642
663
  for a Zig path.
643
664
  **Exemplar:** ghostty `global.zig:74-96` (allocator selection at `main`, Valgrind detected at
644
- runtime). In-repo: `agentsfleetd/main.zig` and `runner/daemon/worker_pool.zig` own the GPA;
645
- everything below takes `alloc` as an argument.
665
+ runtime). Everything below the owner takes `alloc` as an argument.
666
+ In `agentsfleet`, `agentsfleetd/main.zig` and `runner/daemon/worker_pool.zig` own the GPA. <!-- oracle-packs:product.agentsfleet -->
646
667
 
647
668
  ### A2 — errdefer ladder: one errdefer immediately after each acquisition
648
669
 
@@ -831,20 +852,31 @@ PUB GATE and LIFECYCLE GATE answer different questions and neither defers to the
831
852
 
832
853
  The init/deinit pairing audit treats all of `deinit`, `close`, `release`, `destroy`, `shutdown`, `dispose`, `free` as lifecycle (cleanup) methods. Renaming `deinit` to `close` or `release` does NOT bypass the pairing requirement — a struct owning heap memory or an opaque handle still needs an `init`-paired cleanup method under any of those names.
833
854
 
834
- ### Empty-pair and arena-leakage informational flags
855
+ ### Empty-pair and arena-lifetime review
835
856
 
836
- > [DETERMINISTIC → DEINIT]
857
+ > [JUDGMENT → ARCH]
837
858
 
838
- Two non-blocking informational flags the pairing audit surfaces: (1) `init` body empty AND `deinit` body empty — likely a pair-for-shape that isn't actually needed; (2) an arena-allocated slice stored in a long-lived struct — the reviewer must either restructure the ownership or explicitly acknowledge the arena-lifetime mismatch. Neither blocks mechanically; both demand a reviewer decision rather than silent acceptance.
859
+ The reviewer checks empty lifecycle pairs and arena-backed slices retained
860
+ beyond the arena's lifetime. `deinit-pairs.sh` emits neither finding.
861
+ Remove a redundant pair or explain its owned state; fix any lifetime mismatch
862
+ before the owning object can outlive the arena.
839
863
 
840
- ### Scope: full-tree audit, staged content satisfies the same hook run
864
+ ### Scope: full tree, explicit files and indexed content
841
865
 
842
866
  > [DETERMINISTIC → DEINIT]
843
867
 
844
- `deinit-pairs.sh` walks the full `src/` working tree via `git ls-files`. The index includes staged-but-not-yet-committed content, so a fix staged in pre-commit satisfies the check on the same hook run. `--staged` is preserved as an opt-in narrowing mode for iterative dev.
868
+ `deinit-pairs.sh` defaults to tracked Zig files throughout the working tree.
869
+ `--staged` checks staged paths from an owned snapshot of their indexed bytes and
870
+ configuration; `-- <files>` checks only the named working-tree files.
871
+ An unstaged fix cannot clear a staged violation.
845
872
 
846
873
  ### Deinit idempotency assertion (reviewer-owned)
847
874
 
848
875
  > [JUDGMENT → DIDEM]
849
876
 
850
- Every type with a cleanup contract should carry a test proving its cleanup method is idempotent (or its single-shot ownership is asserted) — the mechanical audit reports idempotency-test:<present|missing> but does not block on it; presence/absence is a reviewer responsibility, not a machine pass/fail. A struct whose `deinit` frees fields must have a test that exercises the success-cleanup path so the leak detector fires on a missed or double free.
877
+ Every owning type needs a test of its cleanup lifetime.
878
+ For a value that survives cleanup, prove repeated cleanup is safe; for a heap
879
+ object that destroys itself, prove the owner releases it exactly once and
880
+ clears its handle. Never call a method through a freed pointer.
881
+ The leaf emits no `idempotency-test` result; the reviewer inspects the tests and
882
+ allocation paths, including success and failed initialization.
@@ -4,7 +4,8 @@
4
4
  > Prerequisite: read [`DOCUMENTATION_RULES.md`](./DOCUMENTATION_RULES.md) first.
5
5
  > Changelog history keeps its archive exception; this file adds the narrower voice.
6
6
 
7
- Editing `~/Projects/docs/changelog.mdx` or any other Mintlify `<Update>` block.
7
+ Use these voice rules in the repository's declared changelog.
8
+ Apply Mintlify block rules only where that format is already selected.
8
9
 
9
10
  - **One headline per entry, no marketing words.** Apply Documentation rule 07
10
11
  (`DOC-07`) from `DOCUMENTATION_RULES.md`. Also ban "magical", "we are pleased
@@ -14,5 +15,13 @@ Editing `~/Projects/docs/changelog.mdx` or any other Mintlify `<Update>` block.
14
15
  - **Internal cleanup / refactor entries get the most aggressive trimming.** One lead paragraph + one bullet list. Skip "Test coverage" sections unless the test count is the headline. Indy's exact direction: *"Keep internal code cleanup, refactor to a minimal."*
15
16
  - **Never drop load-bearing facts.** Error codes (`UZ-AUTH-003`), endpoint paths + method + body shape + status code, env var names + defaults, schema column / table names, CLI subcommand + flag names, migration steps, money amounts. Tighten prose, not meaning.
16
17
  - **Historical entries are archives.** Brevity-pass them; never rewrite the past. A typo correction (e.g. `$0.001` → `$0.01` when it was never true) is allowed and must be called out in the commit message.
17
- - **Rate constants are declared once, so a money claim has one thing to match.** `rustd/crates/afd_billing/src/nanos.rs` holds the rates and `afd_core::money` holds the nanos denominator; the daemon charges from them and every client renders the figure it was served. A changelog rate or money claim is checked against those constants, never against a client. Public docs are the surface nothing reaches: when a rate changes, update `~/Projects/docs/snippets/rates.mdx` in a paired Pull Request (PR). In `*.mdx`, import its named values rather than hand-typing money amounts.
18
+ - **Rate constants are declared once, so a money claim has one thing to match.**
19
+ Check claims against the repository's canonical billing constants and update
20
+ its published rate tables in the same change.
21
+ <!-- oracle-packs:start product.agentsfleet -->
22
+ - For `agentsfleet`, the rate sources are `rustd/crates/afd_billing/src/nanos.rs`
23
+ and the denominator in `afd_core::money`. Update
24
+ `~/Projects/docs/snippets/rates.mdx` through its authorized branch flow;
25
+ import its named values in Markdown JSX (MDX) instead of copying amounts.
26
+ <!-- oracle-packs:end -->
18
27
  - **The Mintlify reference Indy pasted (May 1 / May 8 entries) is canonical voice.** Mirror its rhythm, not its product nouns.
@@ -12,22 +12,22 @@ Every triggered document requires one `📖 DOC READ: <path>` proof-line **per t
12
12
  | Any source file (cross-cutting authoring) | `dispatch/write_any.md` — length, logging, milestone-id, error-registry, UFS, greptile read, legacy-workaround family. |
13
13
  | Porting a codebase, module, or subsystem from another language | `dispatch/write_any.md` §Porting a codebase between languages — a port is a rewrite with a conformance test: port the guarantee, delete the workaround, conform on observable behaviour, name the debt not carried over. |
14
14
  | Spec's "Applicable Rules" | Each rule (canonical). Missing → the cross-cutting codes of `dispatch/write_any.md` are the floor; surface omission. |
15
- | `*.zig` | `dispatch/write_zig.md`. ZIG GATE per edit. |
16
- | `*.ts`/`*.tsx`/`*.js`/`*.jsx` | `dispatch/write_ts_adhere_bun.md` — TS FILE SHAPE DECISION (§1) at PLAN, const/import/Bun-primitive discipline, anti-patterns. |
15
+ | `*.zig` | `dispatch/write_zig.md`. ZIG GATE per edit. | <!-- oracle-packs:language.zig -->
16
+ | `*.ts`/`*.tsx`/`*.js`/`*.jsx` | `dispatch/write_ts_adhere_bun.md` — TS FILE SHAPE DECISION (§1) at PLAN, const/import/Bun-primitive discipline, anti-patterns. | <!-- oracle-packs:language.typescript,language.javascript -->
17
17
  | `*.rs` | `dispatch/write_rust.md` — ownership, error variants, feature combinations, and contention tests. | <!-- oracle-packs:language.rust -->
18
18
  | Rust error type, `Result` alias, or any fallible signature | `dispatch/write_rust.md` §Error discipline — one error per crate, one `Result` alias, `#[from]` composition, `source()` never returns self. ERR-RS fires per edit: `audits/rust-error.sh` (via `dispatch/write_rust.sh`) blocks a `map_err` that stringifies its own cause and an error type with no alias in its crate; the remaining clauses stay judgment. | <!-- oracle-packs:language.rust -->
19
19
  | Designing a Rust function, type, or module; writing `match` on a `Result`/`Option`; any production `unwrap()` | `dispatch/write_rust.md` §Functional design — Result pipelines over control-flow trees, illegal states unrepresentable, bind once, `unwrap` needs a proved invariant. | <!-- oracle-packs:language.rust -->
20
- | `*.py` | `dispatch/write_python.md` — parsing boundaries, resource ownership, and specific exceptions. |
21
- | `*.sh` | `dispatch/write_shell.md` — quoting, array arguments, cleanup, input safety, and shell compatibility. |
20
+ | `*.py` | `dispatch/write_python.md` — parsing boundaries, resource ownership, and specific exceptions. | <!-- oracle-packs:language.python -->
21
+ | `*.sh` | `dispatch/write_shell.md` — quoting, array arguments, cleanup, input safety, and shell compatibility. | <!-- oracle-packs:language.shell -->
22
22
  | `*.mdx` | `dispatch/write_mdx.md` — structure, front matter, links, code fences, accessibility, and Mintlify isolation. | <!-- oracle-packs:language.mdx -->
23
23
  | Log emit (any language; see LOGGING GATE triggers) | `docs/LOGGING_STANDARD.md` — wire format (logfmt), severity ladder, error-code embedding, scope/event discipline, PII redaction, §10A tightenings. LOGGING GATE per edit. |
24
- | Lifecycle method in `*.zig` (`init|deinit|close|release|destroy|shutdown|dispose|free`) | `docs/LIFECYCLE_PATTERNS.md` — init/deinit pairing, errdefer placement, allocator ownership, defer/errdefer mutual exclusion, §10A tightenings. LIFECYCLE GATE per edit. |
25
- | `rustd/crates/afd_api*/src/handler/**` or `public/openapi/**` | `docs/REST_API_DESIGN_GUIDELINES.md` — Quick Checklist; §1–§5 (URL/method/body/response/error), §6 (OpenAPI), §7 (6-place route registration), §8 (`Hx` handler interface), §10 (pre-PR gates). |
24
+ | Lifecycle method in Zig | `docs/LIFECYCLE_PATTERNS.md` — ownership, acquisition cleanup, repeated cleanup of surviving values and exactly-once release of self-destroying objects. | <!-- oracle-packs:language.zig -->
25
+ | HTTP handlers, route registration or OpenAPI | `docs/REST_API_DESIGN_GUIDELINES.md` — endpoint checklist and the repository's mounted authorization and handler shape. | <!-- oracle-packs:domain.http -->
26
26
  | `ui/packages/**/*.{tsx,jsx,css}`, `app/**/*.{tsx,jsx,css}`, `components/**/*.{tsx,jsx,css}`, repo-root `globals.css`, or any file changing visual tokens / motion / typography | `DESIGN.md` (repo root) or `docs/DESIGN_SYSTEM.md` — whichever the repo carries. Design system source of truth: typography stack, color tokens, the single accent and its currency rule, motion signature, spacing/density, component principles, CLI palette mapping. DOC READ proof-line per turn. |
27
- | `*.tsx` / `*.jsx` under `ui/packages/{app,website}/` | `dispatch/write_ts_adhere_bun.md` (Design Tokens) — token-utility table (text/tracking/leading/max-w/min-w/spacing/motion/radius/color). DESIGN TOKEN GATE fires per edit; audit via project-side `audits/design-tokens.sh`. |
27
+ | `*.tsx` / `*.jsx` under `ui/packages/{app,website}/` | `dispatch/write_ts_adhere_bun.md` (Design Tokens) — token-utility table (text/tracking/leading/max-w/min-w/spacing/motion/radius/color). DESIGN TOKEN GATE fires per edit; audit via project-side `audits/design-tokens.sh`. | <!-- oracle-packs:language.typescript,language.javascript -->
28
28
  | Auth-flow | `docs/AUTH.md`. |
29
- | Published `*.mdx`, reusable Markdown JSX (MDX) fragment, customer readme, or public OpenAPI prose | `dispatch/write_documentation.md` → `docs/DOCUMENTATION_RULES.md` — establish page, fragment, API, or changelog scope before narrower guides. |
30
- | Changelog `<Update>` / release note (`changelog.mdx`) | First `dispatch/write_documentation.md`, then `dispatch/write_changelog.md` → `docs/CHANGELOG_VOICE.md`; internal-only ⇒ no entry. |
29
+ | Published `*.mdx`, reusable Markdown JSX (MDX) fragment, customer readme, or public OpenAPI prose | `dispatch/write_documentation.md` → `docs/DOCUMENTATION_RULES.md` — establish page, fragment, API, or changelog scope before narrower guides. | <!-- oracle-packs:domain.documentation -->
30
+ | Changelog `<Update>` / release note (`changelog.mdx`) | First `dispatch/write_documentation.md`, then `dispatch/write_changelog.md` → `docs/CHANGELOG_VOICE.md`; internal-only ⇒ no entry. | <!-- oracle-packs:domain.changelog -->
31
31
  | Write or update a PR/MR body, including session notes (`gh pr create/edit --body`, `glab mr create/update --description`, forge API, helper, or skill) | `dispatch/write_pr_description.md` — append-only numbered session unit, measured diagram, review findings, and command output. |
32
- | `schema/*.sql` / migration | `dispatch/write_sql.md` + `docs/SCHEMA_CONVENTIONS.md` — naming/type conventions, schema/migration rules + Schema Table Removal Guard. Re-print Schema Guard output. |
32
+ | SQL / migration registration | `dispatch/write_sql.md` + `docs/SCHEMA_CONVENTIONS.md` — the repository's live-data policy and selected product guard. | <!-- oracle-packs:domain.sql -->
33
33
  | Any spec under `docs/v*/{pending,active,done}/` or `docs/TEMPLATE.md` | `docs/TEMPLATE.md` "Prohibited" section — no time/effort estimates, no complexity ratings, no percentage-complete, no owners/dates. SPEC TEMPLATE GATE per edit. |
@@ -7,13 +7,16 @@ Runs after EXECUTE, before VERIFY. Aggregates every gate verdict; lifecycle cann
7
7
 
8
8
  ## Combined end-of-turn audit
9
9
 
10
- Single awk over `git diff -U0 HEAD`, replaces 4 separate self-audits. For every `+` line:
10
+ `audits/msid-ui.sh` makes one awk pass over added lines. Its default
11
+ `--staged` mode reads `git diff --cached -U0`; `--diff` reads the comparison
12
+ revision through checkout HEAD (`audits/msid-ui.sh:76–85`). It emits:
11
13
 
12
14
  - Emit `MS-ID:` hits when file is source/config and matches `M[0-9]+_[0-9]+|§[0-9]+(\.[0-9]+)+|\bT[0-9]+\b|\bdim [0-9]+\.[0-9]+\b`.
13
- - Emit `PUB:` hits when `*.zig` and line matches `^\+(pub | *pub fn | *[A-Z][a-zA-Z]+,$)`.
14
15
  - Emit `UI:` hits when under `ui/packages/app/**.{tsx,jsx}` and line contains `<(section|button|input|dialog|article|nav|header|form)\b`.
15
16
 
16
- Non-empty = address before CONFORM passes.
17
+ Non-empty = address before CONFORM passes. Public declarations remain subject
18
+ to the separate Public (PUB) Gate: zlint checks unused declarations, and the
19
+ agent records the shape decision (`audits/msid-ui.sh:16–33`).
17
20
 
18
21
  ## Required output
19
22
 
@@ -0,0 +1,153 @@
1
+ ---
2
+ type: reference
3
+ audience: contributor
4
+ verified: 2026-10-04
5
+ product_version: 0.11.0
6
+ executable: false
7
+ ---
8
+
9
+ # Jev judgment advice
10
+
11
+ ## Synopsis
12
+
13
+ `orly judge` asks one bounded question about evidence you select.
14
+ It accompanies planning, verification, review and documentation.
15
+ Its answer suggests an inspection or a fixed next action.
16
+
17
+ ```text
18
+ orly judge <plan|verify|review|document> --input <manifest>
19
+ [--project <root>] [--refresh] [--json]
20
+ ```
21
+
22
+ Jev is TypeSafe's System One model.
23
+ The command pins `jev-1.13.0` and runs TypeScript source with Bun.
24
+ Use the same `bunx` installation as 0.10.x.
25
+ TypeScript 7 is development tooling; evidence selection uses a JavaScript parser.
26
+
27
+ Advice cannot approve work, change a gate result or run a suggested command.
28
+ Existing deterministic checks and owner decisions still apply.
29
+ Hooks do not request advice or upload source.
30
+
31
+ ## Example with output
32
+
33
+ Save a manifest describing the required result and its evidence:
34
+
35
+ ```json
36
+ {
37
+ "stage": "verify",
38
+ "items": [{
39
+ "id": "locale",
40
+ "question": "verify.assertion",
41
+ "requirement": "The label matches the requested locale's formatted date, including year, month and day.",
42
+ "evidence": [
43
+ { "role": "implementation", "path": "src/label.ts", "selector": { "kind": "file" } },
44
+ { "role": "test", "path": "src/label.test.ts", "selector": { "kind": "test", "name": "label respects locale" } }
45
+ ]
46
+ }]
47
+ }
48
+ ```
49
+
50
+ Run without refresh to read an existing exact replay:
51
+
52
+ ```sh
53
+ orly judge verify --input judgments.json
54
+ ```
55
+
56
+ For inputs without a replay, the command reports:
57
+
58
+ ```text
59
+ Jev advice — deterministic checks and owner decisions still apply.
60
+ locale: incomplete — No exact replay for these inputs. Use --refresh to permit scanned source upload.
61
+ ```
62
+
63
+ This result has exit status `2` and makes no provider request.
64
+ To request live advice, supply `TYPESAFE_API_KEY` through your environment and install `gitleaks`.
65
+ Then explicitly permit the selected source upload:
66
+
67
+ ```sh
68
+ bunx @agentsfleet/orly judge verify --input judgments.json --refresh --json
69
+ ```
70
+
71
+ The report includes typed answers, selected source references, request counts, elapsed time and token usage.
72
+ The report excludes selected source text and credentials.
73
+
74
+ ## Options
75
+
76
+ | Option | Meaning |
77
+ |---|---|
78
+ | Stage | Must match the manifest and each selected question |
79
+ | `--input` | Required JSON manifest path; maximum 64 KiB |
80
+ | `--project` | Evidence root; defaults to the current directory |
81
+ | `--refresh` | Permits secret-scanned evidence upload and one provider request per item |
82
+ | `--json` | Prints a structured report |
83
+ | `--help` | Prints usage, selectors and the question catalog |
84
+
85
+ Kibibyte (KiB) means 1,024 bytes.
86
+ A manifest contains at most 12 uniquely named items.
87
+ Each item has a question, requirement and at most eight evidence references.
88
+
89
+ Each reference names a role, relative path and selector.
90
+ Roles are `spec`, `implementation`, `test`, `rule`, `claim`, `result` and `context`.
91
+ Name necessary unchanged context explicitly; the command does not discover dependencies.
92
+
93
+ | Selector | Required fields | Selected evidence |
94
+ |---|---|---|
95
+ | `file` | `kind` | Whole bounded file |
96
+ | `function` | `kind`, `name` | One complete TypeScript or JavaScript function |
97
+ | `test` | `kind`, `name` | One named executable `test` or `it` call |
98
+ | `section` | `kind`, `heading` | One plain Markdown heading through its peer or ancestor boundary |
99
+
100
+ Duplicate matches, skipped tests, malformed syntax and escaping paths are refused.
101
+ Individual files have a 256 KiB cap; complete selected state has a 24 KiB cap.
102
+ Oversized evidence is refused rather than truncated.
103
+
104
+ ### Atomic questions and timing
105
+
106
+ | Question | Run when | Required input | Jev criterion | Decision to take |
107
+ |---|---|---|---|---|
108
+ | `plan.prerequisites` | Before accepting a plan | One requirement and its `spec` evidence | Necessary inputs, credentials, fetch locations, permissions and ordering are explicit | Clarify a missing prerequisite |
109
+ | `plan.observable_result` | Before implementation | One requirement and its `spec` evidence | Success and refusal have concrete observable results | Add the missing expected result |
110
+ | `verify.assertion` | Before accepting a test as proof | Required behavior plus complete `implementation` and `test` evidence | The assertion checks that behavior, distinguishes a supplied wrong result, and targets the named operation | Keep the exact assertion or strengthen a weak, missing or misplaced assertion |
111
+ | `review.failure_path` | While disposing of a failure finding | One named failure requirement and `implementation` evidence | The path preserves the failure and handles acquired resources | Trace the path and add a failure test |
112
+ | `review.rule_applicability` | Before deciding a rule finding | One `rule`, its condition and `implementation` evidence | The selected code meets that rule's trigger | Apply the rule or inspect applicability; the owner decides exceptions |
113
+ | `document.claim` | Before publishing a claim | One `claim` plus `implementation` or measured `result` evidence | Evidence supports the exact scope, quantities and conditions | Narrow the claim or collect missing proof |
114
+
115
+ Choice answers classify assertions as `exact`, `weak`, `wrong_target`, `missing` or `insufficient`.
116
+ Noul answers provide a probability for the question's stated property.
117
+ The question catalog supplies fixed actions; the model cannot supply commands.
118
+
119
+ The experiment marks strength below `0.8` as uncertain.
120
+ This threshold has no independent calibration.
121
+ Uncertainty requires inspection and cannot waive a rule or satisfy verification.
122
+
123
+ ### Replay and request limits
124
+
125
+ Default mode reads `.orly/judgments/` without contacting the provider.
126
+ Identity binds the project root, model, question definition, requirement, selectors and selected source bytes.
127
+ Changed inputs require an explicit refresh.
128
+
129
+ Live preflight checks every item before uploading any item.
130
+ Each request has a 15-second deadline, including response reading, and a 64 KiB response cap.
131
+ Requests are sequential and receive no automatic retries.
132
+
133
+ Replay files use private permissions and omit source text.
134
+ Their checksum detects accidental reply changes; a local owner can edit and reseal them.
135
+ Replay remains advisory and cannot prove independent model accuracy.
136
+
137
+ ## Errors
138
+
139
+ | Result | Meaning and next step |
140
+ |---|---|
141
+ | Exit `0` | Every item has valid advice; concerns and uncertainty still require inspection |
142
+ | Exit `2` | An item or manifest is incomplete; inspect the reported reason |
143
+ | No exact replay | Use unchanged inputs or explicitly request refresh |
144
+ | Missing credential | Supply the provider key through the environment |
145
+ | Failed secret scan | Remove credentials from selected evidence before retrying |
146
+ | Missing or ambiguous evidence | Correct the reference or include necessary context |
147
+ | Invalid provider reply | No valid replay was saved; inspect the provider failure before retrying |
148
+
149
+ ## Related pages
150
+
151
+ - [Architecture](ORLY_ARCHITECTURE.md)
152
+ - [Project overview](../README.md)
153
+ - [Unit test skill](../skills/orly-write-unit-test/SKILL.md)
@@ -149,7 +149,10 @@ pub const Watcher = struct {
149
149
  **Mandatory rules:**
150
150
 
151
151
  1. **`deinit` must free everything `init` allocated.** Lints catch the inverse (`init` allocates, `deinit` doesn't free) but not silently-correct cases (`init` allocates A and B, `deinit` frees only A — silent leak). Reviewer responsibility: walk the alloc list against the free list.
152
- 2. **`deinit` must be idempotent.** Calling it twice must not crash. Either set freed pointers to undefined (`self.events = &.{}` after free) or guard with `if (self.events.len > 0)`.
152
+ 2. **Cleanup follows the object's lifetime.** A surviving value clears its
153
+ owned fields and permits repeated cleanup. A heap object that destroys
154
+ itself is released exactly once by its owner, which clears its handle;
155
+ calling `deinit` through the freed pointer is forbidden.
153
156
  3. **`deinit` does not return errors.** Cleanup that *can* fail (closing a file, flushing a buffer) must be split into a separate explicit method (`pub fn close(self: *Self) !void`) called before `deinit`, or the failure must be logged and swallowed inside deinit.
154
157
  4. **No partial deinit.** Once `deinit` runs, the struct is dead. Don't leave half-freed state for "the caller will free the rest" — that's a leak waiting to happen.
155
158
  5. **Deinit order is reverse of init order.** Free children before freeing the parent that owns them. `errdefer` chains in init naturally produce the right order; mirror it in deinit.
@@ -254,17 +257,21 @@ Anything else is a smell:
254
257
 
255
258
  The struct's `deinit` method is the single source of truth for cleanup. Call sites use `defer self.deinit()` (or `errdefer` if ownership might transfer mid-function) and never reach inside the struct.
256
259
 
257
- ## §10 · Anti-patterns flagged by `deinit-pairs.sh`
260
+ ## §10 · Machine checks and mandatory ownership review
258
261
 
259
- | Pattern | Severity | Fix |
262
+ `deinit-pairs.sh` checks refined public initializer/cleanup pairing and nearby
263
+ `defer`/`errdefer` conflicts on the same target. It does not prove struct-level
264
+ ownership, cleanup placement, allocator lifetime or failure-path coverage.
265
+
266
+ | Pattern | Detection | Fix |
260
267
  |---|---|---|
261
- | `pub fn init(` with no matching `pub fn deinit(` in the same struct | **blocking** | Add `deinit`, or remove `init` if the struct is value-type without resources. |
262
- | `pub fn deinit(` with no matching `pub fn init(` | **blocking** | Likely tail of a refactor — restore init, or rename deinit to a non-lifecycle name (e.g. `close`, `release`). |
263
- | `errdefer` at end of init scope (batched) | informational | Move adjacent to the allocation it protects. |
264
- | `defer` and `errdefer` on the same allocation | **blocking** | Pick one. |
265
- | `init` returning `*Self` without `errdefer alloc.destroy(self)` | **blocking** | The most common leak class on init failure. |
266
- | `deinit` that calls allocator-storing struct's `allocator` field after some other field's `.deinit()` (use-after-free risk if children depend on parent's allocator) | informational | Reorder cleanup to free children first. |
267
- | Stored allocator field never used in deinit | informational | Either remove the field or wire it. |
268
+ | Allocating or heap-returning public `init` without a same-file cleanup method | **blocking machine check**; reviewer verifies the matching owner | Add the owner's cleanup method; resource-free value initialization needs no empty pair. |
269
+ | Cleanup with no corresponding ownership acquisition | **mandatory review** | Check all acquisition paths before removing or renaming the method. |
270
+ | `errdefer` at end of init scope (batched) | **mandatory review** | Move adjacent to the allocation it protects. |
271
+ | `defer` and `errdefer` on the same allocation | **blocking machine check** within four lines; mandatory review elsewhere | Pick the cleanup matching the ownership path. |
272
+ | `init` returning `*Self` without failure-path destruction | **mandatory review** | Release the heap owner on every failed initialization path. |
273
+ | Cleanup order invalidates an allocator still needed by children | **mandatory review** | Release children before invalidating their allocator. |
274
+ | Stored allocator field never used in cleanup | **mandatory review** | Remove the field or release the owned resources through it. |
268
275
 
269
276
  ## §10A · Tightening clauses (closures of common skip rationalizations)
270
277
 
@@ -272,18 +279,21 @@ Failure modes the audit script and reviewer must close. These are **not aspirati
272
279
 
273
280
  | # | Rationalization | Closure |
274
281
  |---|---|---|
275
- | LC1 | "Struct is small / one allocation, skip `deinit`" | Any heap allocation OR opaque handle (file descriptor, mutex, socket, GPU resource, lock) requires `deinit`. **No size threshold**. Audit script flags struct definitions with `allocator.alloc`/`alloc.create`/`std.fs.File.open` whose enclosing struct lacks `pub fn deinit`. |
282
+ | LC1 | "Struct is small / one allocation, skip `deinit`" | Every owned allocation or opaque handle needs cleanup. The leaf checks allocating or heap-returning public initializers for a same-file cleanup method; the reviewer checks ownership acquired later and matches every acquisition to release. |
276
283
  | LC2 | "Renamed `deinit` to `close` / `release` to dodge pair audit" | Audit script treats `deinit`, `close`, `release`, `destroy`, `shutdown`, `free`, `dispose` as **lifecycle methods**. All require a paired `init` and trigger LIFECYCLE GATE. Rename does not bypass; intent does. |
277
- | LC3 | "I'll batch `errdefer` at the bottom of init, easier to read" | The LAST `errdefer` line in init must lexically precede the LAST allocation it protects. Audit script enforces line-position. Batched-at-bottom errdefer is **blocking**. |
284
+ | LC3 | "I'll batch `errdefer` at the bottom of init, easier to read" | Place each `errdefer` immediately after its acquisition, before the next fallible operation. The reviewer checks every early return; the leaf does not enforce placement. |
278
285
  | LC4 | "Both `defer` and `errdefer` on the same allocation, belt-and-suspenders" | Audit greps for `defer X.free(Y)` + `errdefer X.free(Y)` (or the reverse) in the same scope. **Blocking violation** — pick one per §6. |
279
- | LC5 | "`deinit` is idempotent in practice; no test needed" | Every struct with `deinit` must have a `*_test.zig` test that calls `s.deinit(); s.deinit();` and asserts no crash (sentinel-on-second-call is fine). Reviewer responsibility. Audit cannot fully grep for this; reviewer must verify. |
280
- | LC6 | "Storing arena slice in long-lived struct works in my test" | Heuristic flag: struct fields of slice type AND a stored `*ArenaAllocator` field → audit raises informational warning. Reviewer must acknowledge or restructure. |
281
- | LC7 | "Empty no-op `init` paired with empty no-op `deinit` to satisfy the gate" | Audit informational-flags when both `init` and `deinit` bodies are empty or single `_ = self;`. Pair exists for shape, not for state — likely should be a `const` value or removed. |
286
+ | LC5 | "Cleanup works in practice; no test needed" | Reviewer verifies repeated cleanup of surviving values or exactly-once release and cleared owner handles for self-destroying heap objects. Never invoke a freed object's method. The leaf reports no test-presence verdict. |
287
+ | LC6 | "Storing arena slice in long-lived struct works in my test" | Reviewer checks that the arena outlives every retained slice and fixes any mismatch; the leaf emits no arena-lifetime warning. |
288
+ | LC7 | "Empty no-op `init` paired with empty no-op `deinit` to satisfy the gate" | Reviewer removes redundant pairs or explains their owned state; the leaf emits no empty-pair warning. |
282
289
  | LC8 | "Auto-mode is on" | **Auto-mode does NOT cover gate skips.** Skip without explicit user-given override = automatic violation. |
283
290
  | LC9 | "Renamed-only file (no content change), gate doesn't fire" | Rename without content change does **not** fire LIFECYCLE GATE. Rename + content change does. Pure rename is a no-op for the gate. |
284
291
  | LC10 | "Two docs disagree (e.g. write_zig.md says X, LIFECYCLE_PATTERNS says Y)" | Precedence: dispatch façade > standards doc > spec. The façade (`dispatch/write_zig.md`, lifecycle) is the canonical enforcement layer. |
285
292
 
286
- These are enforced by `deinit-pairs.sh` (mechanical) and the dispatch façade (`dispatch/write_zig.md`, lifecycle — output discipline). When in conflict, the façade wins.
293
+ `deinit-pairs.sh` checks allocating initializer pairs and adjacent conflicting
294
+ `defer`/`errdefer` releases. The reviewer handles the other obligations above;
295
+ a green leaf result cannot replace them. The dispatch façade names the same
296
+ ownership rules.
287
297
 
288
298
  ## §11 · Carve-out: PUB GATE vs LIFECYCLE GATE
289
299