@agentsfleet/orly 0.6.4 → 0.6.6

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.
@@ -222,7 +222,7 @@ The questionnaire is organised by scenario. Each scenario corresponds to a momen
222
222
 
223
223
  | # | Question | Expected |
224
224
  |---|---|---|
225
- | 17.1 | Does `conn.query()` require `.drain()` in the same function before `deinit()`, with `make lint-governance` verifying (it wraps `_lint_zig_pg_drain`, the drain check that actually exists)? | YES |
225
+ | 17.1 | Does `conn.query()` require `.drain()` in the same function before `deinit()`, with the repository's own convention gate verifying it? | YES |
226
226
  | 17.2 | Is `conn.exec()` the prescribed alternative when no rows are needed? | YES |
227
227
 
228
228
  ### Scenario 18 — Commit/push hygiene & worktree isolation
package/audits/ufs.sh CHANGED
@@ -4,6 +4,12 @@
4
4
  # Dispatch façade: dispatch/write_any.md (UFS Gate)
5
5
  # Fires in: make lint, CONFORM.
6
6
  #
7
+ # Languages read: Zig, TypeScript, JavaScript, Rust, Go (see `is_source`, which
8
+ # carries the pack that owns each extension and the reasons `.py` / `.sh` are
9
+ # held out). The set is pinned against the dispatch facade by the engine
10
+ # dispatch-coverage audit — a facade that fires on a language its leaf cannot
11
+ # read is a gate that prints green over an unscanned file.
12
+ #
7
13
  # Generic detection — no manifest of known literals, so the audit scales
8
14
  # as the codebase grows. Three classes of violation:
9
15
  #
@@ -15,9 +21,11 @@
15
21
  #
16
22
  # Carve-out: any `// pin test: literal is the contract` comment on or
17
23
  # above the offending line excludes that line from numeric-suspect.
18
- # A `const` / `pub const` / `export const` declaration line is likewise
19
- # exempt for both per-file checks (is_const_decl + its string-dup mirror
20
- # below) — binding the literal to a name on a const line clears the hit.
24
+ # A `const` / `pub const` / `export const` / `static` declaration line is
25
+ # likewise exempt for both per-file checks (is_const_decl + its string-dup
26
+ # mirror below) — binding the literal to a name on that line clears the hit.
27
+ # Go states the keyword once for a whole `const ( ... )` group; the string-dup
28
+ # lane tracks the group so its members are read as the declarations they are.
21
29
  #
22
30
  # Staged-scope semantics: --staged narrows WHICH FILES are scanned, not
23
31
  # how much of each — a staged file is audited in FULL, so staging a file
@@ -69,14 +77,34 @@ ok() { printf "OK: %s\n" "$*"; }
69
77
 
70
78
  # ── File scope ──────────────────────────────────────────────────────────────
71
79
 
80
+ # The extension set is the CONTRACT the dispatch façade already advertises:
81
+ # dispatch/write_any.sh's `dispatch_init "ANY" ...` list is what fires the gate,
82
+ # and this list is what the gate then reads. When the two disagree the façade
83
+ # runs, prints green, and has scanned nothing — which is exactly how a Rust
84
+ # crate carried `const S_PING = "PING"` past a gate that claimed to cover it.
85
+ # The engine fails its own dispatch-coverage audit on that drift now, so a
86
+ # language added to the façade must land here, or be named out of scope with a
87
+ # reason, before that audit goes green again.
88
+ #
89
+ # .zig language.zig
90
+ # .ts .tsx language.typescript
91
+ # .js .jsx language.javascript
92
+ # .rs language.rust
93
+ # .go language.go
94
+ #
95
+ # Out of scope, deliberately — the engine keeps the machine-readable twin of
96
+ # this list: `.py` (single-quoted literals dominate and the
97
+ # double-quote matcher below would report partial coverage as full), `.sh`
98
+ # (a repeated `"$var"` is interpolation, not a magic string — measured at 59%
99
+ # of hits on a real tree), `.sql` (its own gate, dispatch/write_sql.sh).
72
100
  is_source() {
73
101
  local f="$1"
74
102
  case "$f" in
75
103
  vendor/*|third_party/*|.zig-cache/*|*/node_modules/*|evals/dispatch/fixtures/*|*.tsbuildinfo) return 1 ;;
76
- *_test.zig|*.test.ts|*.test.tsx|*.test.js|*.test.jsx|*.unit.test.js|*.spec.ts) ;; # tests in scope
104
+ *_test.zig|*.test.ts|*.test.tsx|*.test.js|*.test.jsx|*.unit.test.js|*.spec.ts|*_test.go) ;; # tests in scope
77
105
  esac
78
106
  case "$f" in
79
- *.zig|*.ts|*.tsx|*.js|*.jsx) return 0 ;;
107
+ *.zig|*.ts|*.tsx|*.js|*.jsx|*.rs|*.go) return 0 ;;
80
108
  *) return 1 ;;
81
109
  esac
82
110
  }
@@ -112,9 +140,21 @@ while IFS= read -r row; do
112
140
  done < <(awk '
113
141
  FNR == 1 {
114
142
  prev_file = FILENAME
143
+ # Language lane. Each lane below carves out what its syntax makes
144
+ # UNFIXABLE — a literal a named const cannot legally replace. Anything
145
+ # a const CAN replace stays in scope, in every language.
146
+ lang = "other"
147
+ if (FILENAME ~ /\.zig$/) lang = "zig"
148
+ else if (FILENAME ~ /\.rs$/) lang = "rust"
149
+ else if (FILENAME ~ /\.go$/) lang = "go"
115
150
  # Test files: repetition is fixture data, not magic strings.
116
151
  # Skip string-dup-file for tests; other checks still apply.
117
- is_test = (FILENAME ~ /(_test\.zig|\.test\.|\.spec\.|\.unit\.test|\.integration\.test|\/test\/|\/tests\/)/)
152
+ is_test = (FILENAME ~ /(_test\.zig|_test\.go|\.test\.|\.spec\.|\.unit\.test|\.integration\.test|\/test\/|\/tests\/)/)
153
+ # Directory-shaped test and benchmark trees. Rust puts both at the CRATE
154
+ # ROOT (`tests/`, `benches/`) with no leading slash, so the `\/tests\/`
155
+ # form above — which needs a parent directory — walked straight past them
156
+ # and audited 100 fixture hits as production literals.
157
+ if (FILENAME ~ "(^|/)(test|tests|benches|__tests__)/") is_test = 1
118
158
  }
119
159
  is_test { next }
120
160
  # ui/ files: extracting class-strings or short literals to file-local
@@ -126,7 +166,7 @@ done < <(awk '
126
166
  # ("nonterminated character class"); use a dynamic-regex STRING — identical
127
167
  # match on gawk, portable on BWK awk (the macOS default). Do not re-inline.
128
168
  FILENAME ~ "^ui/packages/[^/]+/(src|app|tests|components|lib|hooks)/" { next }
129
- FNR == 1 { in_test_block = 0; test_depth = 0; in_block_comment = 0 }
169
+ FNR == 1 { in_test_block = 0; test_depth = 0; in_block_comment = 0; in_rs_test = 0; rs_open = 0; rs_depth = 0; in_go_const = 0 }
130
170
  {
131
171
  line = $0
132
172
  # Block-comment exclusion (TS/JS/Zig non-applicable): skip lines inside /* ... */
@@ -139,9 +179,9 @@ done < <(awk '
139
179
  next
140
180
  }
141
181
  # Zig multi-line string literal — lines start with `\\` after whitespace.
142
- if (FILENAME ~ /\.zig$/ && line ~ /^[[:space:]]*\\\\/) next
182
+ if (lang == "zig" && line ~ /^[[:space:]]*\\\\/) next
143
183
  # Inline-test exclusion (Zig): track depth across `test "..." {` blocks.
144
- if (FILENAME ~ /\.zig$/) {
184
+ if (lang == "zig") {
145
185
  if (in_test_block) {
146
186
  test_depth += gsub(/\{/, "{", line) - gsub(/\}/, "}", line)
147
187
  if (test_depth <= 0) in_test_block = 0
@@ -156,6 +196,54 @@ done < <(awk '
156
196
  next
157
197
  }
158
198
  }
199
+ # Inline-test exclusion (Rust): Rust keeps its unit tests INSIDE the file
200
+ # they cover, under `#[cfg(test)] mod tests { ... }`, so without this the
201
+ # fixture keys of a well-tested module read as the worst literal debt in
202
+ # the production half of that same file — a real crate reported `"key1"`
203
+ # 15 times from one such block. Same brace-depth shape as the Zig branch.
204
+ if (lang == "rust") {
205
+ if (in_rs_test) {
206
+ tmp = line
207
+ rs_depth += gsub(/\{/, "{", tmp) - gsub(/\}/, "}", tmp)
208
+ if (!rs_open && index(line, "{") > 0) rs_open = 1
209
+ if (rs_open && rs_depth <= 0) in_rs_test = 0
210
+ else if (!rs_open && line ~ /;[[:space:]]*$/) in_rs_test = 0
211
+ next
212
+ }
213
+ # Three ways Rust marks a block as fixture data, not shipped behaviour:
214
+ # `#[cfg(test)]`, a `#[test]`/`#[tokio::test]` function, and a
215
+ # FEATURE-GATED test seam — `#[cfg(feature = "test-util")]`, the
216
+ # convention for a helper that must compile into the crate so an
217
+ # integration test in a sibling file can call it (a `one_of_each_kind`
218
+ # error-surface enumerator is the usual shape). The feature name is
219
+ # matched narrowly, `test` as a whole word or a `-`/`_` segment, so a
220
+ # production gate like `#[cfg(feature = "redis")]` still counts and
221
+ # `"latest"` is not mistaken for a test seam.
222
+ if (line ~ /^[[:space:]]*#\[cfg\(test\)\]/ ||
223
+ line ~ /^[[:space:]]*#\[[A-Za-z_:]*test\]/ ||
224
+ line ~ /^[[:space:]]*#\[cfg\(.*feature[[:space:]]*=[[:space:]]*"([A-Za-z0-9]+[-_])?test(ing)?([-_][A-Za-z0-9_-]+)?"/) {
225
+ in_rs_test = 1; rs_open = 0; rs_depth = 0
226
+ next
227
+ }
228
+ # Attribute literals are UNFIXABLE, not undisciplined. `#[serde(rename =
229
+ # "...")]`, `#[cfg(feature = "...")]` and `#[doc = "..."]` take a literal
230
+ # token by language rule — a const is not accepted there, so naming one
231
+ # cannot resolve the hit and reporting it only teaches people to ignore
232
+ # the gate. Ordered after the cfg(test) branch, which is also a `#[`.
233
+ if (line ~ /^[[:space:]]*#!?\[/) next
234
+ }
235
+ # Grouped-const exclusion (Go): `const ( ... )` states the binding keyword
236
+ # ONCE, on the opening line, so a member like `metaKeyQoS = "conf_..."` is
237
+ # a definition carrying no `const` for the binding carve-out below to see.
238
+ # `var (` groups are deliberately NOT tracked — the carve-out is const-only
239
+ # in every other language and widening it here would be a silent divergence.
240
+ if (lang == "go") {
241
+ if (in_go_const) {
242
+ if (line ~ /^[[:space:]]*\)/) { in_go_const = 0; next }
243
+ if (line ~ /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*([[:space:]]+[A-Za-z0-9_.\[\]*]+)?[[:space:]]*=/) next
244
+ }
245
+ else if (line ~ /^[[:space:]]*const[[:space:]]*\([[:space:]]*$/) { in_go_const = 1; next }
246
+ }
159
247
  sub(/\/\/.*$/, "", line)
160
248
  # Strip single-line /* ... */ inline block comments (jsdoc/etc) so
161
249
  # literals inside them are not counted.
@@ -171,11 +259,23 @@ done < <(awk '
171
259
  # like any other use. (A prior line-level skip exempted those too and silently
172
260
  # gutted the check for most Zig code; the ufs_dup_string eval fixture pins the
173
261
  # binding-level semantic.)
174
- if (line ~ /(^|[[:space:]])(pub[[:space:]]+const|export[[:space:]]+const|const)[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*([[:space:]]*:[^=]*)?[[:space:]]*=[[:space:]]*"((\\.)|[^\\"])+"[[:space:]]*(as[[:space:]]+const)?[[:space:]]*[;,]?[[:space:]]*$/) next
262
+ # `static` joins the keyword set for Rust: `static NAME: &str = "..."` binds
263
+ # a literal to a name exactly as `const` does, and the leading
264
+ # `(^|[[:space:]])` anchor already carries the `pub`/`pub(crate)` prefixes.
265
+ # The second type slot belongs to Go, which spells the type with no colon
266
+ # (`const name string = "..."`), which the colon-only slot could not see.
267
+ if (line ~ /(^|[[:space:]])(pub[[:space:]]+const|export[[:space:]]+const|const|static)[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*([[:space:]]*:[^=]*|[[:space:]]+[A-Za-z_][A-Za-z0-9_.\[\]*]*)?[[:space:]]*=[[:space:]]*"((\\.)|[^\\"])+"[[:space:]]*(as[[:space:]]+const)?[[:space:]]*[;,]?[[:space:]]*$/) next
175
268
  rest = line
176
269
  # Strip Zig identifier-escape syntax @"name" — body is an identifier,
177
270
  # not a string literal, but the regex below would otherwise match it.
178
271
  gsub(/@"[^"]*"/, "", rest)
272
+ # Strip Go backtick runs. They are overwhelmingly struct tags
273
+ # (`json:"id" yaml:"id"`), whose quoted halves are tag SYNTAX addressed by
274
+ # reflection — a const cannot appear inside one, so the repeat is unfixable.
275
+ # This also drops single-line Go raw strings, which is the accepted cost:
276
+ # the alternative reports every `json:"id"` in a wire struct as a violation.
277
+ # A backtick run spanning lines is not tracked (best-effort, as documented).
278
+ if (lang == "go") gsub(/`[^`]*`/, "", rest)
179
279
  # Strip empty string literals so the regex below cannot fuse
180
280
  # two adjacent Zig literals through their inner gap.
181
281
  gsub(/""/, "", rest)
@@ -229,7 +329,7 @@ done < <(awk -v re="$NUMERIC_RE" '
229
329
  line = $0
230
330
  is_pin_now = (line ~ /pin test: literal is the contract/)
231
331
  is_pin_above = (prev ~ /pin test: literal is the contract/)
232
- is_const_decl = (line ~ /(^|[[:space:]])(pub[[:space:]]+const|export[[:space:]]+const|const)[[:space:]]/)
332
+ is_const_decl = (line ~ /(^|[[:space:]])(pub[[:space:]]+const|export[[:space:]]+const|const|static)[[:space:]]/)
233
333
  stripped = line
234
334
  sub(/\/\/.*$/, "", stripped)
235
335
  sub(/#.*$/, "", stripped)
@@ -31,7 +31,9 @@ own Bun project needing install + build. `git worktree add` fires
31
31
  into the tree; on 🟠 run `provision-env-1password`, re-link. Post-merge:
32
32
  `git worktree remove ../agentsfleet-mNN-name`.
33
33
 
34
- **agentsfleet Test Baseline counter:** `make _lint_zig_test_depth`.
34
+ **Test Baseline counter.** A repository whose declared `verify.unit` does not
35
+ print a total names its own counter here. Without one, record the count the
36
+ declared command reports.
35
37
 
36
38
  **agentsfleet CHORE(close) paths:** the `<Update>` lands in
37
39
  `~/Projects/docs/changelog.mdx` (template + version-bump matrix:
@@ -11,8 +11,8 @@ edit. (This is the former Verification gate absorbed into the dispatch model.)
11
11
 
12
12
  - 🤔 DECIDE — judgment-only; the agent must run the targets below and report
13
13
  honestly. No script gates this — it blocks the *turn*, not a commit.
14
- - 🟣 delegated — the `make` targets themselves live in the product repo; dotfiles
15
- carries only the discipline of *which* targets are canonical and when.
14
+ - 🟣 delegated — the `make` targets themselves live in the product repository;
15
+ this pack carries only the discipline of *which* targets are canonical and when.
16
16
 
17
17
  ## Trigger
18
18
 
@@ -27,35 +27,35 @@ pass".
27
27
 
28
28
  ## Why `make` is canonical
29
29
 
30
- Package-scoped runners (`bun run test`, `vitest <file>`, `zig build test` without
31
- the integration tier) are **not** verification — they skip cross-package lint,
32
- cross-compile, pg-drain, and integration. `make` targets are the canonical gates.
30
+ Package-scoped runners (`bun run test`, `vitest <file>`, `cargo test -p <crate>`)
31
+ are **not** verification — they skip every other package's lint and tests and the
32
+ cross-language gates. The declared `make` targets are the canonical gates.
33
33
 
34
34
  ## Required runs
35
35
 
36
36
  | Target | When |
37
37
  |---|---|
38
- | `make lint` | Always. |
39
- | `make test` | Always (tier 1, unit-only by definition). |
40
- | `make test-integration` | Tier 2: diff touches HTTP handlers, schema, DB code, Redis code, or any `_integration_test.zig`. Focused subsets: `make test-integration-db` / `make test-integration-redis`. |
41
- | `make test-integration` | Tier 3: at least once per branch before declaring ship-ready, from a clean state (e.g. after `make down`) when tier 2 is intermittent — fresh DB proves no state carry-over. |
42
- | `make memleak` | Server lifecycle (`src/http/**`, `src/cmd/serve.zig`), allocator wiring, cross-thread heap ownership. |
38
+ | `make lint-all` | Always. The declared `verify.lint`. |
39
+ | `make test-unit-all` | Always. The declared `verify.unit` — the cargo workspace plus every package coverage gate. |
40
+ | `make harness-verify` | Always. The declared `conform` — the deterministic gate audit. |
41
+ | `make check-version` | Always. The declared `verify.version`. |
42
+ | `make wire-fixtures` | The `/v1/runners` wire types changed. Regenerate, then review the diff — a changed fixture IS the wire change. |
43
43
  | `make bench` (local) | Diff touches request-path code, allocator wiring, or startup/shutdown sequencing. |
44
44
  | `API_BENCH_URL=https://api-dev.agentsfleet.net/healthz make bench` | After branch deploys to dev. |
45
- | Cross-compile `x86_64-linux` + `aarch64-linux` | Whenever `*.zig` touched. |
46
- | `make check-pg-drain` | Whenever `*.zig` touched. |
47
45
  | `/orly-write-integration-test` (skill) | With `/orly-write-unit-test` at VERIFY when the diff crosses module boundaries with real I/O; otherwise record `N/A — <reason>`. |
48
- | Acceptance e2e (product repo's live tier) | Diff touches a surface the live/acceptance tier covers — relevant acceptance suites green, or their opt-in skip matrix recorded. |
46
+ | Acceptance e2e (live tier) | Diff touches a surface the live/acceptance tier covers — relevant suites green, or their opt-in skip matrix recorded. |
49
47
 
50
- `make test` never substitutes for tier 2/3. Tier 2 passing but tier 3 failing
51
- means state pollution — fix isolation before shipping.
48
+ There is no slow tier. The Zig integration and memory-leak lanes retired with the
49
+ rest of the Zig gating, so nothing a developer runs needs live Postgres or Redis.
50
+ A package-scoped runner (`cargo test -p afd_wire`, `bun run test` inside a
51
+ package) proves that package and never the repository.
52
52
 
53
- ## Memleak evidence rule
53
+ ## Wire-fixture evidence rule
54
54
 
55
- Before CHORE(close) reports green, paste the final `make memleak` result line into
56
- the PR Session Notes block OR cite the CI memleak job URL. Branches touching
57
- `src/http/**`, `src/cmd/serve.zig`, or allocator wiring MUST include the last 3
58
- lines verbatim. No "I ran it, trust me."
55
+ A diff that regenerates `samples/fixtures/wire-v2/` carries the regenerated
56
+ fixtures in the same commit as the type change, and the PR Session Notes say what
57
+ moved. A fixture diff with no type change beside it means someone hand-edited
58
+ generated output; a type change with no fixture diff means the emitter never ran.
59
59
 
60
60
  ## Coverage discipline
61
61
 
@@ -73,7 +73,7 @@ lines verbatim. No "I ran it, trust me."
73
73
  **Success:**
74
74
 
75
75
  ```
76
- ✅ Verified: 🧪 lint ✓ · 🧪 test ✓ <N>p/<M>s · 🧩 test-integration ✓ (or N/A — no handler/schema/redis) · 🎯 cross-compile ✓ (zig only)
76
+ ✅ Verified: 🧪 lint-all ✓ · 🧪 test-unit-all ✓ <N>p/<M>s · 🔆 harness-verify ✓ · 🔆 check-version ✓
77
77
  ```
78
78
 
79
79
  **Failure (any required target failed):**
@@ -107,5 +107,4 @@ missing from the table counts as ❌.
107
107
  | 🟠 | Skipped per environment constraint — read the reason |
108
108
  | 🧪 | Lint / unit / integration test |
109
109
  | 🧩 | Integration test (cross-process) |
110
- | 🎯 | Cross-compile target |
111
110
  | 🔆 | Informational note (does not affect verdict) |
@@ -343,7 +343,13 @@ ERROR REGISTRY GATE: <file>
343
343
  **Family:** Constant discipline. **Source:** `AGENTS.md` conformance enforcement
344
344
  + the selected language façade's Uniform Free Strings (UFS) clauses.
345
345
 
346
- **Triggers** — every `Edit`/`Write` to source files under `src/`, `ui/packages/*/src/`, `ui/packages/*/app/`, `ui/packages/*/lib/`, `ui/packages/*/components/`, `ui/packages/*/pages/`, `ui/packages/*/tests/`, `agentsfleet/src/`, `agentsfleet/test/` matching `*.zig`, `*.ts`, `*.tsx`, `*.js`, `*.jsx`. Excluded: `vendor/`, `third_party/`, `.zig-cache/`, `node_modules/`, `*.tsbuildinfo`.
346
+ **Triggers** — every `Edit`/`Write` to a source file under the repository's own source surfaces — the `surfaces.user` prefixes it declares in `.oracle/orly.json`, or its source roots where it declares none — matching `*.zig`, `*.ts`, `*.tsx`, `*.js`, `*.jsx`, `*.rs`, `*.go`. Excluded: `vendor/`, `third_party/`, `.zig-cache/`, `node_modules/`, `*.tsbuildinfo`. `*.py` and `*.sh` fire this façade but are held out of the UFS leaf — see Scope. Each repository binds its own prefixes; naming one repository's layout here is what sends every other repository a trigger list it cannot match.
347
+
348
+ <!-- oracle-packs:start product.agentsfleet -->
349
+ In this repository those surfaces are `src/` (Zig), `rustd/crates/*/src/` (Rust), `cli/src/` and `ui/packages/*/{src,app,lib,components,pages,tests}/` (TypeScript).
350
+
351
+ `agentsfleet/src/` and `agentsfleet/test/` were listed here until they stopped existing. A trigger path that resolves to nothing is the same silent green this card's gate exists to prevent — and the identical stale prefix in `ufs.sh`'s cross-runtime parity scan means its JavaScript half reads zero files today.
352
+ <!-- oracle-packs:end -->
347
353
 
348
354
  **Override:** `UFS GATE: SKIPPED per user override (reason: ...)` immediately preceding the edit. **User-only**; auto-mode does not cover.
349
355
 
@@ -376,12 +382,16 @@ Before saving an edit that introduces or relocates a literal:
376
382
 
377
383
  1. **Same-file repeat?** Grep within the file for the literal value. If it appears ≥1 time already, define a const at the top of the file (or in the module's `constants.ts`/`constants.zig`) and replace all occurrences.
378
384
 
379
- 2. **Same-module repeat?** Grep within the module/package (`ui/packages/app/`, `agentsfleet/src/`, `src/`). If repeated, define in the module's canonical types/constants file (`lib/types.ts`, `src/state/<topic>.zig`, `agentsfleet/src/constants/<topic>.js`).
385
+ 2. **Same-module repeat?** Grep within the module or package that owns the file. If repeated, define it in that module's canonical constants file and replace every occurrence.
380
386
 
381
- 3. **Cross-runtime sibling?** When the literal is a wire-format string or unit-conversion numeric, grep all three runtimes for an existing matching name. If found, reuse the name. If not, define in every runtime that uses the same value, same commit.
387
+ 3. **Cross-runtime sibling?** When the literal is a wire-format string or unit-conversion numeric, grep **every runtime this repository ships** for an existing matching name. If found, reuse it verbatim. If not, define it in each runtime that carries the value, same commit. A single-runtime repository answers this in one grep — the check is about the runtimes that exist, not a fixed count.
382
388
 
383
389
  4. **Carve-out check?** If the literal IS the contract a pin test verifies, write the `// pin test: literal is the contract` comment on or above the line. Otherwise, name it.
384
390
 
391
+ <!-- oracle-packs:start product.agentsfleet -->
392
+ Concretely here: modules are `src/` (Zig), `rustd/crates/*/` (Rust), `cli/src/` and `ui/packages/*/` (TypeScript), and their canonical constants files are `src/state/<topic>.zig`, the crate's own `constants.rs`, and `lib/types.ts`. The runtimes to grep in step 3 are Zig, TypeScript and Rust.
393
+ <!-- oracle-packs:end -->
394
+
385
395
  #### Required output (per Edit/Write — one line by default)
386
396
 
387
397
  > [DETERMINISTIC → UFS]
@@ -411,6 +421,19 @@ The previous `--diff` (`BASE...HEAD`) default was retired with M70. The forcing
411
421
 
412
422
  `--all` is accepted as a back-compat alias for the default. `--diff` is rejected with exit 2 + a pointer to this section.
413
423
 
424
+ **Languages the leaf reads.** Zig, TypeScript, JavaScript, Rust and Go. The rule is one rule, but each language hides its literals differently, so each gets a lane in `is_source` + the string-dup pass:
425
+
426
+ | Language | Held out of the count, and why |
427
+ |---|---|
428
+ | Rust | `#[cfg(test)]` / `#[test]` blocks (unit tests live *inside* the file they cover), feature-gated test seams (`#[cfg(feature = "test-util")]` — a helper that must compile into the crate so a sibling integration test can call it), `#[…]` attribute literals (`#[serde(rename = "…")]` takes a literal token by language rule — no const is legal there), `static` bindings alongside `const` |
429
+ | Go | `const ( … )` group members (the keyword is stated once, on the opening line), backtick struct tags (`json:"id"` is read by reflection; a const cannot appear inside one) |
430
+
431
+ The test-seam match is deliberately narrow — `test` as a whole word or a `-`/`_` segment — so a production gate like `#[cfg(feature = "redis")]` still counts and `"latest"` is not mistaken for a seam. `ufs_feature_gate.rs` pins that: widening it turns the fixture green.
432
+
433
+ `*.py` and `*.sh` are **out of scope on purpose**, and the engine pins the reason in its own dispatch-coverage audit: Python writes ~14% of its literals in single quotes the matcher does not read, so it would report partial coverage as full; and a repeated `"$var"` in shell is interpolation, not a magic string — 59% of hits on a real tree, with no `const` to bind to. Adding either means building its lane first, not widening the glob.
434
+
435
+ **A façade that fires on a language its leaf cannot read is a silent green.** `write_any.sh` has declared `*.rs` and `*.go` since those packs existed while `ufs.sh` read neither, so the gate printed a green UFS row over a file it never opened — and a Rust crate shipped a bare, twice-spelled wire verb under it. The engine now fails its dispatch-coverage audit on that divergence: every extension the façade claims is read by the leaf, or carries a written reason it is not.
436
+
414
437
  #### Self-audit (end-of-turn / CONFORM)
415
438
 
416
439
  > [DETERMINISTIC → UFS]
@@ -56,7 +56,7 @@ For every commit that touches `*.zig`, the agent runs the workflow below — no
56
56
  > [DETERMINISTIC → DRAIN]
57
57
 
58
58
  - Run `make lint`, `make test`, and `gitleaks detect` before any commit that includes Zig changes.
59
- - Run `TEST_DATABASE_URL=postgres://agentsfleet:agentsfleet@localhost:5432/agentsfleetdb make test-integration-db` when touching DB-backed handlers, proposal flows, or temp-table-based Zig tests.
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
60
  - Read this file before creating any new `*.zig` file.
61
61
  - Use `conn.exec()` for INSERT / UPDATE / DDL whenever possible.
62
62
  - Drain early-exit `conn.query()` results before `deinit()`.
@@ -270,7 +270,7 @@ Canonical source: `src/http/test_harness.zig`. Every `*_http_integration_test.zi
270
270
 
271
271
  - **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.
272
272
  - **Do** wire middleware via `Config.configureRegistry: fn(*MiddlewareRegistry, *TestHarness) anyerror!void`. The harness is policy-agnostic; each suite supplies only the middleware it exercises.
273
- - **Integration tests run against the LIVE test DB** — never `CREATE TEMP TABLE` in an integration test. Fixtures go through the real schema so the code under test sees production-shaped rows. The `make test-integration` gate resets schemas via `_reset-test-db`; individual tests clean up their own rows explicitly in the test body (not via `defer`) because deferred cleanup leaks pool connections at `pool.deinit()`.
273
+ - **Integration tests run against the LIVE test DB** — never `CREATE TEMP TABLE` in an integration test. Fixtures go through the real schema so the code under test sees production-shaped rows. The integration lane resets schemas before the suite; individual tests clean up their own rows explicitly in the test body (not via `defer`) because deferred cleanup leaks pool connections at `pool.deinit()`.
274
274
  - **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
275
  - **Skip gracefully when DB is absent** — `TestHarness.start` returns `error.SkipZigTest` when `TEST_DATABASE_URL` is unset; tests just propagate it.
276
276
  - 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.
@@ -281,9 +281,9 @@ Canonical source: `src/http/test_harness.zig`. Every `*_http_integration_test.zi
281
281
 
282
282
  - `make lint`
283
283
  - `make test`
284
- - `TEST_DATABASE_URL=postgres://agentsfleet:agentsfleet@localhost:5432/agentsfleetdb make test-integration-db`
284
+ - The database-backed integration lane, with `TEST_DATABASE_URL` pointed at the test database
285
285
  - `gitleaks detect`
286
- - `make check-pg-drain` — static check: every `conn.query()` must have `.drain()` in the same function. Run this when touching any file that calls `conn.query()`. See `lint-zig.py`.
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()`.
287
287
 
288
288
  ## Zig 0.15.2 API Gotchas (M3_001)
289
289
 
@@ -497,8 +497,8 @@ Rules:
497
497
 
498
498
  > [DETERMINISTIC → XCOMPILE]
499
499
 
500
- - Verification commands are defined in `make` targets and CI runs `make`. Use `make test` (tier 1) and `make test-integration` (tier 2) for verification — **not** `zig build test` standalone. `zig build test` runs only the Zig unit set and silently skips the website / app / agentsfleet unit tests + cross-language gates that the verification gate is defined against. Use `zig build` directly only for compilation, never for "did my change pass tests".
501
- - `make memleak` 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.
500
+ - 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".
501
+ - 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.
502
502
 
503
503
  ## Doc-Comments and Inline Comments
504
504
 
@@ -45,7 +45,7 @@ Run this checklist as part of `CHORE(close)` (per `~/.claude/CLAUDE.md` lifecycl
45
45
  - [ ] **Tests** — happy path + one error per `hx.fail` + idempotency double-PATCH + `Idempotency-Key` replay (where applicable) + ETag mismatch (§10)
46
46
  - [ ] **Logging** — sensitive ID values are DEBUG-only or carry `// log-id-allowed:` comment; secret-shaped fields are write-only or one-time-read (§11)
47
47
  - [ ] **`make check-openapi` clean** — bundle in sync, redocly lint, error-schema + URL-shape checks pass (§6)
48
- - [ ] **`make test-unit-agentsfleetd` clean** — `route_scopes.zig`'s exhaustive switch + `route_scopes_test.zig` assertions cover the auth gate matrix (§10)
48
+ - [ ] **The repository's declared `verify.unit` command is clean** — the route-scope table's exhaustive match and its tests cover the auth gate matrix (§10)
49
49
  - [ ] **No file over 350 lines** (§10)
50
50
  - [ ] **`gitleaks detect` clean** (§10)
51
51
 
@@ -577,7 +577,7 @@ When in doubt, mirror an existing handler:
577
577
 
578
578
  ## §8 — Handler signature rule
579
579
 
580
- Every HTTP handler in `agentsfleetd` follows this shape. Enforced by `make lint-zig` and by the dispatcher in `src/agentsfleetd/http/server.zig::dispatchMatchedRoute()`, which builds the per-request arena, runs the middleware chain, constructs `Hx`, and calls the route's invoke shim (§7) — a handler never constructs any of this itself.
580
+ Every HTTP handler in `agentsfleetd` follows this shape. Enforced by the repository's declared `verify.lint` command and by the dispatcher in `src/agentsfleetd/http/server.zig::dispatchMatchedRoute()`, which builds the per-request arena, runs the middleware chain, constructs `Hx`, and calls the route's invoke shim (§7) — a handler never constructs any of this itself.
581
581
 
582
582
  ```zig
583
583
  pub fn innerMyEndpoint(hx: Hx, req: *httpz.Request, ...path_params) void {
@@ -705,10 +705,10 @@ Before opening a PR touching any handler:
705
705
 
706
706
  - [ ] `zig build` clean
707
707
  - [ ] `zig build test` passes
708
- - [ ] `make test-unit-agentsfleetd` passes — `route_scopes.zig`'s exhaustive switch + `route_scopes_test.zig` cover the auth gate matrix
709
- - [ ] `make test-integration` passes (HTTP + DB + Redis E2E)
708
+ - [ ] the declared `verify.unit` command passes — the route-scope match and its tests cover the auth gate matrix
709
+ - [ ] the repository's integration suite passes end to end against real datastores, where it declares one
710
710
  - [ ] Cross-compile: `zig build -Dtarget=x86_64-linux && zig build -Dtarget=aarch64-linux`
711
- - [ ] `make lint-zig` — all Zig gates pass (350-line file cap, `check-pg-drain`, zlint)
711
+ - [ ] the declared `verify.lint` command passes — every language gate the repository declares
712
712
  - [ ] Handler file ≤ 350 lines; split if it grows
713
713
  - [ ] Integration test covers the happy path AND at least one error path per `hx.fail` call in the handler
714
714
  - [ ] PATCH endpoints have an idempotency test: same body issued twice → identical 200 + identical row state. Skip only if the spec's "Failure Modes" explicitly declares non-idempotent PATCH with a reason (§2)
@@ -1,57 +1,67 @@
1
- # VERIFY — Tiers, performance, hygiene
1
+ # Verification tiers — `agentsfleet`
2
2
 
3
- > Parent: [`../AGENTS.md`](../AGENTS.md) §VERIFY. Verification Gate (`dispatch/verify.md`) enforces; `make` targets are canonical.
3
+ The exact commands VERIFY runs, and the block it emits. The generic discipline —
4
+ when the gate fires, what a skip must say — lives in `dispatch/verify.md`; this
5
+ page carries only what is specific to this repository.
4
6
 
5
- **FIRST: `/orly-write-unit-test`** — audits diff coverage vs spec's Test Specification (or changed surface when no spec). Iterate until clean. Skipping = CHORE(close) violation.
6
-
7
- ## Correctness tiers (do not skip)
7
+ ## Tiers
8
8
 
9
9
  | Tier | Command | When |
10
10
  |---|---|---|
11
- | 1 | `make test-unit-all` | Every EXECUTE iteration; start of VERIFY. Unit-only — never substitutes for 2/3. Zig-only fast loop: `make test-unit-agentsfleetd`. |
12
- | 2 | `make test-integration` | Diff touches `src/agentsfleetd/http/**`, `src/agentsfleetd/db/**`, `src/agentsfleetd/fleet/**`, `src/agentsfleetd/observability/**`, `*_integration_test.zig`, schema, migrations. Before COMMIT. |
13
- | 3 | `make test-integration` | ≥1× per branch from clean state (after `make down`) before ship-ready. Mandatory when schema changes pre-v2.0. Tier 2 passing + 3 failing = state pollution; fix isolation. |
11
+ | conform | `make harness-verify` | Always, after EXECUTE and before the rest. Any 🔴 returns to EXECUTE. |
12
+ | lint | `make lint-all` | Always. Rust rides `lint-rustd` (rustfmt + Clippy, warnings are errors); script self-tests ride `lint-scripts`. |
13
+ | unit | `make test-unit-all` | Always. The cargo workspace plus every TypeScript package coverage gate. |
14
+ | version | `make check-version` | Always. `VERSION` against `build.zig.zon`, `cli/package.json` and both `rustd/Cargo.toml` sites. |
14
15
 
15
- ## Test delta — VERIFY ends by reporting coverage growth
16
+ These four are exactly what `.oracle/orly.json` declares and exactly what
17
+ `orly gate` runs, so the rubric and the mechanical gate grade one boundary.
16
18
 
17
- CHORE(open) recorded the branch-point counts in the spec header (`**Test Baseline:** unit=<N> integration=<M>`, from `make _lint_zig_test_depth`, which also writes `.tmp/agentsfleetd-test-depth.txt`). VERIFY ends with the same command and this required row in the verification block:
19
+ There is no slow tier. The Zig integration and memory-leak lanes retired with the
20
+ rest of the Zig gating, so no lane a developer runs needs live Postgres or Redis.
18
21
 
19
- ```
20
- Test Delta: unit <N₀>→<N₁> (+x) · integration <M₀>→<M₁> (+y) vs CHORE(open) baseline
21
- Lacking: <changed surfaces whose tests did not grow, or "none">
22
- ```
22
+ ## Test Baseline
23
23
 
24
- - **Delta is deterministic; Lacking is judgment (🤔)** — walk the diff's changed modules and name every one whose coverage did not move (e.g. "EgressScope Linux paths — integration lane pending"). "none" must be earned, not defaulted.
25
- - **Zero/negative unit delta while the diff adds non-trivial code** → justify in the row (pure refactor, test consolidation) or return to EXECUTE. Deleting tests to go green is a violation, not a justification.
26
- - Spec predates this rule (no `Test Baseline:` line) → reconstruct from the branch point (run the counter at the CHORE(open) commit or on `origin/main`) and add the line to the spec in the same commit as the VERIFY report.
24
+ `make test-unit-all` reports its own counts per target. Record the cargo
25
+ workspace total in the spec header at CHORE(open) as
26
+ `**Test Baseline:** unit=<N>`, and compare against it in VERIFY's Test Delta row.
27
+ Zero or negative growth on a code-adding diff needs justification or a return to
28
+ EXECUTE.
27
29
 
28
- ## Performance / leak (before PR)
30
+ ## Wire fixtures
29
31
 
30
- | Gate | Command | When |
31
- |---|---|---|
32
- | Leak | `make memleak` | Server lifecycle (`src/agentsfleetd/http/**`, `src/agentsfleetd/cmd/serve.zig`), allocator wiring, cross-thread heap ownership. |
33
- | Bench (local) | `make bench` | When the diff touches request-path code, allocator wiring, or startup/shutdown sequencing. |
34
- | Bench (dev) | `API_BENCH_URL=https://api-dev.agentsfleet.net/healthz make bench` | After deploy to dev. |
32
+ `make wire-fixtures` regenerates `samples/fixtures/wire-v2/` from
33
+ `src/lib/contract` — the Zig module that still defines the `/v1/runners` wire.
34
+ Run it whenever a wire type changes, and commit the regenerated fixtures in the
35
+ same commit as the type change.
35
36
 
36
- **Zig coverage.** `make test-coverage-zig` runs the daemon, runner, and three shared-library unit binaries under kcov. Each component must produce a non-empty Cobertura report. The merged report lives under `coverage/zig/merged` and must meet `ZIG_COVERAGE_MIN_LINES`.
37
+ The fixtures are the parity oracle for the Rust port: Zig generates, Rust
38
+ conforms, and the suite compares BYTES. Never hand-edit one. A fixture diff with
39
+ no type change beside it means someone edited generated output; a type change
40
+ with no fixture diff means the emitter never ran.
37
41
 
38
- **Integration roots.** `make test-unit-agentsfleetd` runs `src/agentsfleetd/tests.zig`. `make test-integration` prepares isolated services once and runs `src/agentsfleetd/integration_tests.zig`, which owns the live PostgreSQL, Redis, and QStash imports. The runner's Linux kernel root remains independently selectable.
42
+ ## Coverage
39
43
 
40
- **Memleak lanes.** `make memleak` leak-gates all three test graphs concurrently, each named in its own lane prefix: **`agentsfleetd`** (daemon), **`runner`** (`build_runner.zig` unit tests — integration tests fork real children and are excluded), and **`lib`** (three `src/lib` binaries). Component failures are collected and reported before the final lifecycle proof. On Linux, Valgrind is blocking and propagates its exit code. On macOS, the Zig testing allocator is blocking. The advisory `leaks` rerun happens only when one lightweight preflight proves that the host can inspect child processes. `-Dopenssl=false` is a root-graph option only; the runner graph links no OpenSSL and rejects the flag. A final **`boot-drain`** lane migrates the test database, exports the integration environment, runs the filtered daemon lifecycle test under the blocking gate, and requires its run-proof marker.
44
+ One bar, everywhere: **100%**, project-wide and per flag.
41
45
 
42
- Knobs (`make/test-bench.mk`): `API_BENCH_METHOD`, `_DURATION_SEC`, `_CONCURRENCY`, `_TIMEOUT_MS`, `_MAX_ERROR_RATE`, `_MAX_P95_MS`, `_MAX_RSS_GROWTH_MB`.
46
+ | Flag | Paths | Target |
47
+ |---|---|---|
48
+ | `rust-afd` | `rustd/crates/` | 100% |
49
+ | `typescript` | `app`, `website`, `cli` | 100% |
50
+
51
+ Every threshold is 0%: the target IS the bar. The Rust crates carry no
52
+ input/output, no runtime and no external dependency, so every line is reachable
53
+ from a test; the TypeScript packages are pinned at 100 by their own runners.
43
54
 
44
- **Memleak evidence:** paste final `make memleak` line into PR Session Notes OR cite CI URL. Branches touching `src/agentsfleetd/http/**`/`src/agentsfleetd/cmd/serve.zig`/allocator wiring → last 3 lines verbatim. No "trust me".
55
+ The Zig tree is in Codecov's `ignore` list. It still compiles and the revision
56
+ built from it serves `api-dev`, but nothing measures it.
45
57
 
46
- ## Hygiene (always, before PR)
58
+ ## Required output
47
59
 
48
- `make lint-all` (hard); `make _lint_zig_pg_drain` (also folded into `make lint-all` via `lint-zig`) + cross-compile `x86_64-linux` + `aarch64-linux` (any `*.zig` touched); cross-layer orphan sweep (RULE ORP — every renamed/deleted symbol → 0 hits across schema/Zig/JS/tests/docs in non-historical files); `gitleaks detect` before any Zig-including commit; 350-line / 50-fn-line check via:
60
+ Paste the deciding line, not the exit code. A gate that reports a number — a size
61
+ against a cap, a count against a budget — is owed that number in the done message.
49
62
 
50
- ```bash
51
- git diff --name-only origin/main \
52
- | grep -v -E '\.md$|^vendor/|_test\.|\.test\.|\.spec\.|/tests?/' \
53
- | xargs -I{} sh -c 'wc -l "{}"' \
54
- | awk '$1 > 350'
63
+ ```
64
+ ✅ Verified: 🧪 lint-all ✓ · 🧪 test-unit-all ✓ <N>p/<M>s · 🔆 harness-verify ✓ · 🔆 check-version ✓
55
65
  ```
56
66
 
57
- **Other:** after refactors, list newly dead code before removing — `NEWLY UNREACHABLE: <symbol/file> — <why now dead>. Remove? Confirm.` **Greptile learning capture:** each finding → "Could this recur?" If yes, add compact rule (Rule/Why/Tags/Ref) to RULES.md same commit. Never defer.
67
+ A skipped target is surfaced as 🟠 with its reason, never dressed as a pass.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentsfleet/orly",
3
- "version": "0.6.4",
4
- "description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary — for any coding agent.",
3
+ "version": "0.6.6",
4
+ "description": "Repository-scoped engineering harness: renders agent rules, materialises the gates that enforce them, and proves the PR boundary \u2014 for any coding agent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -20,5 +20,13 @@ a working value.
20
20
  Table-driven tests are the default shape. Concurrency work needs a `-race` run,
21
21
  and a deterministic contention test rather than a sleep.
22
22
 
23
+ A string literal repeated in one file becomes a named constant — `audits/ufs.sh`
24
+ reads `*.go` and enforces it. Two Go shapes are held out, because no rename can
25
+ fix them: members of a `const ( … )` group, which state the binding keyword once
26
+ on the opening line rather than per member, and backtick struct tags, whose
27
+ quoted halves (`json:"id"`) are addressed by reflection and cannot hold a const.
28
+ Both are carve-outs for what the *language* makes unfixable, not permission —
29
+ a flag name or a label spelled inline twice is still the rule.
30
+
23
31
  The repository owns formatting, vet, lint, build, and test commands — it
24
32
  declares them in `.oracle/orly.json`.
@@ -31,6 +31,35 @@ Local convention wins on conflict, but the divergence is named in the review
31
31
  output. Refresh the copy when the user asks or a cited rule looks stale
32
32
  (re-run the fetch; upstream updates continuously).
33
33
 
34
+ ## Constant discipline (RULE UFS)
35
+
36
+ `audits/ufs.sh` reads `*.rs`. It did not until this pack said so, and the cost
37
+ was concrete: a crate spelled a wire verb inline twice — `pong == "PONG" ||
38
+ pong.contains("PONG")` — while `write_any.sh` printed a green UFS row over the
39
+ file, because the façade had always declared `*.rs` and the leaf had never read
40
+ it. Bind the repeated literal to a `const` or `static` and use the name.
41
+
42
+ Four Rust shapes are held out of the count, because no rename can fix them:
43
+
44
+ - **`#[cfg(test)]` and `#[test]` blocks.** Rust keeps unit tests inside the file
45
+ they cover, so fixture keys would otherwise read as that file's worst debt.
46
+ - **Feature-gated test seams** — `#[cfg(feature = "test-util")]`. A helper that
47
+ hands a sibling integration test one of each error kind has to compile into
48
+ the crate, which is the only reason it is not behind `cfg(test)`; its contents
49
+ are fixture data all the same. The match is narrow on purpose: `test` as a
50
+ whole word or a `-`/`_` segment, so `#[cfg(feature = "redis")]` still counts.
51
+ - **Attribute literals** — `#[serde(rename = "…")]`, `#[cfg(feature = "…")]`,
52
+ `#[doc = "…"]`. An attribute takes a literal token by language rule; a const
53
+ is not accepted in that position.
54
+ - **`tests/` and `benches/`** at the crate root, which are fixture trees.
55
+
56
+ Everything else counts, including `examples/` — not because it compiles, but
57
+ because what repeats there is coupling, not demonstration. A cache namespace
58
+ declared at the top of a walkthrough and matched ninety lines below it, or a
59
+ fixture id that is both the fake store's match arm and the key five call sites
60
+ pass, breaks silently when one end moves. That is the drift RULE UFS exists to
61
+ catch, and example code is where a reader copies it from.
62
+
34
63
  ## Evolution
35
64
 
36
65
  This pack is young next to the Zig façade and grows the same way: a Rust
@@ -35,7 +35,7 @@ If a test answers (1) but mocks the dep, demote it to a unit test.
35
35
 
36
36
  ## What belongs here
37
37
 
38
- A test belongs in the integration suite (`make test-integration` / `pytest -m integration` / `cargo test --test 'integration_*'`) if **any** of:
38
+ A test belongs in the integration suite — whatever the repository calls that lane (`pytest -m integration`, `cargo test --test 'integration_*'`, a dedicated make target) — if **any** of:
39
39
 
40
40
  - **Real Postgres** — schema applied, real connection pool, real extensions if used
41
41
  - **Real Redis** — actual pub/sub, streams, KV, lease semantics
@@ -214,7 +214,7 @@ every request; this is the regression that proof exists to catch.
214
214
  ### T6 — Resource lifecycle
215
215
 
216
216
  Proofs the system doesn't bleed:
217
- - **Drain audit** — `make check-pg-drain` (or equivalent) green after suite. Every `conn.query()` paired with `.drain()` before `deinit()`.
217
+ - **Drain audit** — the repository's connection-drain check green after the suite. Every `conn.query()` paired with `.drain()` before `deinit()`.
218
218
  - **Connection leak** — open count before == after across N requests
219
219
  - **Memory leak** — Zig: `std.testing.allocator` over full request lifecycle. Rust: `dhat`/`heaptrack` over N iters. Python: `objgraph`/`gc` over N iters.
220
220
  - **FD leak** — `lsof -p $$ | wc -l` before/after holds
@@ -232,7 +232,7 @@ Proofs the system doesn't bleed:
232
232
  deterministic proof every `errdefer` is correct; exhaustive over alloc sites ⇒ invariant.
233
233
  - **No cross-request growth:** drive N requests and assert outstanding-bytes / high-water
234
234
  does not grow monotonically (a slow leak shows as linear growth).
235
- - **Gate:** `make memleak` → **0 leaks** + `make check-pg-drain` clean, both pasted into PR
235
+ - **Gate:** the memory-leak lane → **0 leaks**, and the drain audit clean; both pasted into the PR
236
236
  Session Notes; cross-compile both targets before declaring done.
237
237
 
238
238
  **Complexity / latency budget under load (counters over clocks).**
@@ -331,9 +331,9 @@ Both make every downstream test a lie — fix them before adding coverage. A gre
331
331
  | Stage | What runs | Why |
332
332
  |---|---|---|
333
333
  | **PR** | Smoke mode on changed surface + randomised order | Fast feedback, catches obvious wiring breaks |
334
- | **Pre-merge** | Standard mode on changed module + 1× full suite from clean (`make down && make up && make test-integration`) | Detects state pollution between tests |
334
+ | **Pre-merge** | Standard mode on changed module + 1× full suite from a CLEAN datastore (tear the services down, bring them up, run the integration lane) | Detects state pollution between tests |
335
335
  | **Nightly** | Hardening mode + chaos pass (random failure injection across deps) | Catches rare-path bugs |
336
- | **Pre-release** | Full Hardening + `make memleak` + `make bench` + cross-platform | Final sign-off |
336
+ | **Pre-release** | Full Hardening + the memory-leak lane + a benchmark run + cross-platform | Final sign-off |
337
337
 
338
338
  Suite passes shared but fails clean = state pollution, not a bug. Fix isolation before re-running.
339
339
 
@@ -367,8 +367,8 @@ Every claim → ≥1 test. Untestable claims → flag `needs infra`, never silen
367
367
 
368
368
  | Stack | Suite runner | Real PG | Real Redis | Drain/leak | Concurrency |
369
369
  |---|---|---|---|---|---|
370
- | Zig (agentsfleet) | `make test-integration` | `make up` container | `make up` container | `std.testing.allocator` + `make check-pg-drain` | `std.Thread.spawn` |
371
- | Zig runner (client daemon) | `make test-integration` (protocol loop vs a harness/real control plane) | via the control plane (runner holds none) | via the control plane | `std.testing.allocator` over parent + forked child | `fork` + `kill -9` / fake-clock past lease TTL for reclaim |
370
+ | Zig | the integration lane | containerized datastore | containerized datastore | `std.testing.allocator` + the drain audit | `std.Thread.spawn` |
371
+ | Zig client daemon | the integration lane (protocol loop against a harness or a real control plane) | via the control plane (the daemon holds none) | via the control plane | `std.testing.allocator` over parent + forked child | `fork` + `kill -9` / fake-clock past lease TTL for reclaim |
372
372
  | Python | `pytest -m integration` | `testcontainers-python` | `testcontainers-python` | `gc` + `objgraph` over N iters | `asyncio.gather` / `ThreadPoolExecutor` |
373
373
  | Rust | `cargo test --test 'integration_*'` | `testcontainers-rs` / `sqlx::test` | `testcontainers-rs` | `dhat-rs`; `Drop` checks | `tokio::spawn` + `loom` for races |
374
374
  | Node/Bun | `bun test --integration` | `testcontainers-node` | `testcontainers-node` | manual handle counting | `Promise.all` |
@@ -425,7 +425,7 @@ Mode: Smoke | Standard | Hardening
425
425
  Scenario axis: positive ✅ · negative ✅ · invalid 🟡 · flaky-conn ✅ · kill/restart ✅ · concurrency ✅
426
426
  Harness: ✅ port bind-and-hold · ✅ teardown catalog-derived (survives a new schema)
427
427
  DoD: 9/12 — return to EXECUTE before declaring done
428
- Drain audit: ✅ make check-pg-drain clean
428
+ Drain audit: ✅ clean
429
429
  Leak audit: 🔴 std.testing.allocator reported 2 leaks across 100 requests
430
430
  Shuffle: ✅ pass under --shuffle
431
431
  Clean run: ✅ pass after make down && make up
@@ -444,17 +444,17 @@ Block the change if any are missing on touched surface:
444
444
  - [ ] Every changed handler/service/repo has ≥1 T1+T2+T3 happy-path test
445
445
  - [ ] Every `catch`/`orelse`/`except`/`Err` in the request path has a T4 injection test
446
446
  - [ ] Every concurrent-write or idempotency claim has a T5 test
447
- - [ ] Drain audit (`make check-pg-drain`) clean post-suite
447
+ - [ ] Drain audit clean post-suite
448
448
  - [ ] Leak audit clean post-suite (Zig: `std.testing.allocator`; Rust: `dhat`; Python: `objgraph`)
449
449
  - [ ] Suite passes under randomised execution order
450
- - [ ] Suite passes from clean state (`make down && make up && make test-integration`)
450
+ - [ ] Suite passes from a clean datastore — services down, up, then the integration lane
451
451
  - [ ] No mocking of DB, Redis, or internal modules
452
452
  - [ ] Test names read as documentation
453
453
  - [ ] OpenAPI / protobuf breaking-change check (`oasdiff` / `buf breaking`) clean
454
454
  - [ ] If streaming touched: T7 incremental-delivery test present
455
455
  - [ ] Failure-injection mechanism named for every T4/T7 test (no fiction)
456
456
  - [ ] **Partial completion:** every operation touching multiple systems or acquiring the same resource more than once carries a matrix with one 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
457
- - [ ] PR Session Notes paste final `make test-integration` + `make memleak` lines
457
+ - [ ] PR Session Notes paste the final result line from the integration lane and the memory-leak lane
458
458
  - [ ] **≥100-connection** test proves exactly-once + parallelism (peak-concurrency/lock-wait counter, or wall-time < R×), passes K runs
459
459
  - [ ] **Zig error-path leak:** allocating handlers/repos proven by `std.testing.checkAllAllocationFailures`; no cross-request high-water growth over N requests
460
460
  - [ ] **Complexity under load:** N+1 round-trips ruled out (count at n vs 10n); work-counter ladder asserts the O-bound; p95 within budget vs pinned baseline
@@ -463,8 +463,8 @@ Block the change if any are missing on touched surface:
463
463
  - [ ] **Harness hygiene:** server binds-and-holds its port (no rebind race, no `listen()` panic); teardown/reset is catalog-derived (proven by adding a throwaway schema and re-running teardown — it must still reach zero)
464
464
  - [ ] **Client daemon (if present, T9):** sudden SIGKILL mid-lease → reclaim + re-fence + exactly-once + stale report fenced; restart → re-register + resume, no duplicate, no orphan lease; flaky control-plane link → backoff without crash + un-acked redelivery + idempotent report retry; forked child always reaped + scope always destroyed on every exit path
465
465
 
466
- 100% required before declaring `make test-integration` done.
466
+ 100% required before declaring the integration suite done.
467
467
 
468
468
  ## Validation gate
469
469
 
470
- Run `make test-integration` (or stack equivalent) twice — once shared, once from clean (`make down && make up`). Run once more under randomised order. Report pass/fail/skip counts, drain audit, leak audit, the coverage table above, and the DoD checklist. Do not declare done until DoD is 100%.
470
+ Run the integration lane twice — once against shared services, once from a clean datastore. Run once more under randomised order. Report pass/fail/skip counts, drain audit, leak audit, the coverage table above, and the DoD checklist. Do not declare done until DoD is 100%.
@@ -308,7 +308,7 @@ Reject the change if any are missing on touched code:
308
308
  - [ ] Golden-file drift, if any, has a written explanation
309
309
  - [ ] Branch coverage ≥80%, error-path 100% on touched code
310
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
311
+ - [ ] **Zig zero-leak:** the memory-leak lane reports 0 leaks; every alloc-with-error-path proven by `std.testing.checkAllAllocationFailures`; no cross-request high-water growth over N iters
312
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
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
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
@@ -361,7 +361,7 @@ enumerate) over sampling.
361
361
  arena acquire → use → reset) for N iterations and assert outstanding-bytes /
362
362
  high-water does **not** grow monotonically. A slow leak the single-shot test misses
363
363
  shows up here as linear growth.
364
- - **Gate:** `make memleak` → **0 leaks**, output pasted into PR Session Notes; cross-compile
364
+ - **Gate:** the memory-leak lane → **0 leaks**, output pasted into PR Session Notes; cross-compile
365
365
  both targets first (`x86_64-linux`, `aarch64-linux`) — stdlib alloc paths differ from macOS.
366
366
 
367
367
  ### Proof 2 — Concurrency proven at ≥100 connections (no hidden global lock)
@@ -0,0 +1,104 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ import { PRODUCT_ONLY_TARGETS, productLeakErrors, shippedFiles } from "./pack_hygiene";
7
+
8
+ const registryFor = (pack: string, source: string) => ({
9
+ packs: { [pack]: { documents: [{ source, target: source }] } },
10
+ });
11
+
12
+ describe("productLeakErrors", () => {
13
+ test("flags a product-only target named by a generic pack", async () => {
14
+ const output = mkdtempSync(join(tmpdir(), "orly-hygiene-"));
15
+ try {
16
+ mkdirSync(join(output, "docs"));
17
+ await Bun.write(join(output, "docs/GUIDE.md"), "Run `make test-integration` before you ship.\n");
18
+
19
+ const errors = await productLeakErrors(output, registryFor("domain.http", "docs/GUIDE.md"));
20
+
21
+ expect(errors).toHaveLength(1);
22
+ expect(errors[0]).toContain("docs/GUIDE.md:1");
23
+ expect(errors[0]).toContain("domain.http");
24
+ expect(errors[0]).toContain("make test-integration");
25
+ } finally {
26
+ rmSync(output, { recursive: true, force: true });
27
+ }
28
+ });
29
+
30
+ test("allows the same target inside a product-scoped pack", async () => {
31
+ const output = mkdtempSync(join(tmpdir(), "orly-hygiene-"));
32
+ try {
33
+ mkdirSync(join(output, "docs"));
34
+ await Bun.write(join(output, "docs/GUIDE.md"), "Run `make test-integration` before you ship.\n");
35
+
36
+ const errors = await productLeakErrors(output, registryFor("product.agentsfleet", "docs/GUIDE.md"));
37
+
38
+ expect(errors).toBeEmpty();
39
+ } finally {
40
+ rmSync(output, { recursive: true, force: true });
41
+ }
42
+ });
43
+
44
+ test("does not fire on English that begins with 'make'", async () => {
45
+ const output = mkdtempSync(join(tmpdir(), "orly-hygiene-"));
46
+ try {
47
+ mkdirSync(join(output, "docs"));
48
+ await Bun.write(
49
+ join(output, "docs/GUIDE.md"),
50
+ "Make it obvious. This will make the reader stop.\nDo not make an exception here.\n",
51
+ );
52
+
53
+ const errors = await productLeakErrors(output, registryFor("universal.authoring", "docs/GUIDE.md"));
54
+
55
+ expect(errors).toBeEmpty();
56
+ } finally {
57
+ rmSync(output, { recursive: true, force: true });
58
+ }
59
+ });
60
+
61
+ test("reports the most specific target, not its prefix", async () => {
62
+ const output = mkdtempSync(join(tmpdir(), "orly-hygiene-"));
63
+ try {
64
+ mkdirSync(join(output, "docs"));
65
+ await Bun.write(join(output, "docs/GUIDE.md"), "Run `make test-integration-db` for the DB half.\n");
66
+
67
+ const errors = await productLeakErrors(output, registryFor("domain.sql", "docs/GUIDE.md"));
68
+
69
+ expect(errors).toHaveLength(1);
70
+ expect(errors[0]).toContain("make test-integration-db");
71
+ } finally {
72
+ rmSync(output, { recursive: true, force: true });
73
+ }
74
+ });
75
+ });
76
+
77
+ describe("shippedFiles", () => {
78
+ test("tags each source with the pack that ships it", () => {
79
+ const files = shippedFiles({
80
+ packs: {
81
+ "domain.http": { documents: [{ source: "docs/A.md" }] },
82
+ "product.agentsfleet": { documents: [{ source: "docs/B.md" }] },
83
+ },
84
+ core_documents: [{ source: "docs/C.md" }],
85
+ });
86
+
87
+ expect(files).toContainEqual({ source: "docs/A.md", pack: "domain.http" });
88
+ expect(files).toContainEqual({ source: "docs/B.md", pack: "product.agentsfleet" });
89
+ expect(files).toContainEqual({ source: "docs/C.md", pack: "core" });
90
+ });
91
+ });
92
+
93
+ describe("the real corpus", () => {
94
+ test("no generic pack names a product-only make target", async () => {
95
+ const registry = await Bun.file("registry.json").json();
96
+ const errors = await productLeakErrors(".", registry);
97
+
98
+ expect(errors).toEqual([]);
99
+ });
100
+
101
+ test("every denied target is spelled once", () => {
102
+ expect(new Set(PRODUCT_ONLY_TARGETS).size).toBe(PRODUCT_ONLY_TARGETS.length);
103
+ });
104
+ });
@@ -0,0 +1,109 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ /**
5
+ * Make targets that belong to ONE consuming repository.
6
+ *
7
+ * A generic pack — anything not `product.*` — must never name these. A repository
8
+ * that renames or retires one gets a managed file citing a target it does not
9
+ * have, and if it gates on cited targets resolving, the only way to go green is
10
+ * to edit a managed file, which the next `orly update` reverts. That loop is the
11
+ * bug this list exists to prevent.
12
+ *
13
+ * Generic prose says what the target IS — "the declared `verify.unit` command",
14
+ * "the repository's integration lane" — and lets each repository bind the name.
15
+ *
16
+ * Deliberately a denylist, not "no `make` citations at all": English says "make
17
+ * it", "make the", "make an", so a blanket pattern fires on prose and gets
18
+ * carved out until it means nothing.
19
+ */
20
+ export const PRODUCT_ONLY_TARGETS: readonly string[] = [
21
+ "lint-zig",
22
+ "lint-governance",
23
+ "check-pg-drain",
24
+ "memleak",
25
+ "test-integration",
26
+ "test-integration-db",
27
+ "test-integration-redis",
28
+ "test-integration-kernel",
29
+ "test-unit-agentsfleetd",
30
+ "test-unit-agentsfleet-runner",
31
+ "test-unit-agentsfleet-lib",
32
+ "test-coverage-zig",
33
+ "test-coverage-grade",
34
+ "_lint_zig_test_depth",
35
+ ];
36
+
37
+ /**
38
+ * Pack label for files the registry ships outside any pack — core documents and
39
+ * rules. Not a pack id, so it never matches the `product.` prefix and is held to
40
+ * the generic standard, which is what core documents are.
41
+ */
42
+ const CORE_PACK = "core";
43
+
44
+ /** A file shipped by a pack, with the pack that ships it. */
45
+ export interface ShippedFile {
46
+ readonly source: string;
47
+ readonly pack: string;
48
+ }
49
+
50
+ /** Collects every `source` a registry node ships, tagged with its pack. */
51
+ export function shippedFiles(registry: unknown): ShippedFile[] {
52
+ const out: ShippedFile[] = [];
53
+ const walk = (node: unknown, pack: string): void => {
54
+ if (Array.isArray(node)) {
55
+ for (const item of node) walk(item, pack);
56
+ return;
57
+ }
58
+ if (node === null || typeof node !== "object") return;
59
+ const record = node as Record<string, unknown>;
60
+ if (typeof record.source === "string") out.push({ source: record.source, pack });
61
+ for (const value of Object.values(record)) walk(value, pack);
62
+ };
63
+
64
+ const root = registry as Record<string, unknown>;
65
+ const packs = (root.packs ?? {}) as Record<string, unknown>;
66
+ for (const [name, pack] of Object.entries(packs)) walk(pack, name);
67
+ walk(root.core_documents, CORE_PACK);
68
+ walk(root.rules, CORE_PACK);
69
+ return out;
70
+ }
71
+
72
+ /**
73
+ * Reports every product-only make target named by a file a GENERIC pack ships.
74
+ *
75
+ * Returns one line per offending citation, empty when clean.
76
+ */
77
+ export async function productLeakErrors(root: string, registry: unknown): Promise<string[]> {
78
+ const errors: string[] = [];
79
+ const seen = new Set<string>();
80
+
81
+ for (const { source, pack } of shippedFiles(registry)) {
82
+ if (pack.startsWith("product.")) continue;
83
+ if (!source.endsWith(".md")) continue;
84
+ if (seen.has(source)) continue;
85
+ seen.add(source);
86
+
87
+ let text: string;
88
+ try {
89
+ text = await readFile(join(root, source), "utf8");
90
+ } catch {
91
+ continue; // absent sources are the reference gate's business, not ours
92
+ }
93
+
94
+ text.split("\n").forEach((line, index) => {
95
+ for (const target of PRODUCT_ONLY_TARGETS) {
96
+ // Word-boundary on the tail so `test-integration` does not swallow
97
+ // `test-integration-db`'s own, more specific, report.
98
+ const pattern = new RegExp(`make ${target}(?![a-z0-9_-])`);
99
+ if (pattern.test(line)) {
100
+ errors.push(
101
+ `${source}:${index + 1}: pack '${pack}' is not product-scoped but names \`make ${target}\`, ` +
102
+ `which only one repository defines — say what the command IS instead`,
103
+ );
104
+ }
105
+ }
106
+ });
107
+ }
108
+ return errors;
109
+ }