@agentsfleet/orly 0.6.9 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -81,8 +81,8 @@ The questionnaire is organised by scenario. Each scenario corresponds to a momen
81
81
  | 4.9 (Shell) | Must `gitleaks` pass before any commit/push? | YES |
82
82
  | 4.10 (CI) | Are CI/CD edits (`.github/workflows/**`, deploy configs) **forbidden without explicit user approval** even in auto mode? | YES |
83
83
  | 4.11 (Bun) | For `*.ts`/`*.tsx`/`*.js`/`*.jsx` edits, does AGENTS.md route to `dispatch/write_ts_adhere_bun.md` (TS FILE SHAPE DECISION at PLAN, const/import/Bun-primitive discipline)? | YES |
84
- | 4.12 (Logging) | For every Edit/Write that adds/removes/changes a log emit (Zig `std.log.*`/`std.debug.print`/`obs.scoped`, TS/JS `console.*`/custom logger, shell `echo` to `&2`), does LOGGING GATE require reading `docs/LOGGING_STANDARD.md` and printing the per-edit gate block citing §3 (wire format) / §4 (severity) / §5 (error codes) / §6 (PII) / §10A (tightenings)? | YES |
85
- | 4.13 (Logging) | Is `std.debug.print` in non-test source AND `console.log` in `agentsfleet/src/**` an automatic blocking violation, with no "temporary" carve-out? | YES |
84
+ | 4.12 (Logging) | For every Edit/Write that adds/removes/changes a log emit (Zig `std.log.*`/`std.debug.print`/`obs.scoped`, Rust `tracing::*`/print macros under `rustd/`, TS/JS `console.*`/custom logger, shell `echo` to `&2`), does LOGGING GATE require reading `docs/LOGGING_STANDARD.md` and printing the per-edit gate block citing §3 (wire format) / §4 (severity) / §5 (error codes) / §6 (PII) / §7, §8, or §8A (language binding) / §10A (tightenings)? | YES |
85
+ | 4.13 (Logging) | Are `std.debug.print`, Rust `println!`/`eprintln!`/`dbg!`, Rust tracing without `event` or with positional formatting, and `console.log` in their non-test production scopes automatic blocking violations, with no "temporary" carve-out? | YES |
86
86
  | 4.14 (Logging) | Must a new registry-scheme `error_code` reference (agentsfleet: `UZ-XXX-NNN`) land in the same commit as its registry entry (used-but-undeclared = blocking)? | YES |
87
87
  | 4.15 (Lifecycle) | For every Edit/Write that adds/reshapes a lifecycle method in `*.zig` (`pub fn init|deinit|close|release|destroy|shutdown|dispose|free`) or an `errdefer`/`defer` adjacent to allocation, does LIFECYCLE GATE require reading `docs/LIFECYCLE_PATTERNS.md` and printing the per-edit gate block? | YES |
88
88
  | 4.16 (Lifecycle) | Is `defer X.free(Y)` + `errdefer X.free(Y)` on the same allocation in the same scope a blocking violation? | YES |
package/audits/logging.sh CHANGED
@@ -9,7 +9,7 @@
9
9
  # - agentsfleet scope-prefix format from LOGGING_STANDARD §7 (the
10
10
  # `log.scoped(...)` API path under `src/logging/`)
11
11
  # - `UZ-XXX-NNN` as the `error_code=` substring per LOGGING_STANDARD §5
12
- # - `src/` + `agentsfleet/src/` as the scan roots
12
+ # - `src/` + `agentsfleet/src/` as the Zig and TypeScript scan roots
13
13
  # A parameterised version that reads prefix + scope-api path + scan
14
14
  # roots from a per-project config is the right long-term shape; not
15
15
  # done yet. Today, this script lives in dotfiles so the gate body and
@@ -18,6 +18,10 @@
18
18
  # Two severity tiers:
19
19
  # BLOCK — exits 1, must fix:
20
20
  # - `std.debug.print(` in non-test source under src/.
21
+ # - `println!` / `eprintln!` / `dbg!`, missing `event`, or positional
22
+ # tracing formatting in runtime Rust source. Direct stream writes
23
+ # may carry `// logging: <reason>` on or immediately above the
24
+ # emit when stdout or stderr is the intended program interface.
21
25
  # - `console.log/debug/info/warn/error` in agentsfleet/src outside tests.
22
26
  # INFO — surfaced for reviewer/agent attention, doesn't block:
23
27
  # - `std.log.scoped(...)` outside `src/logging/` (LOGGING_STANDARD §7
@@ -29,7 +33,8 @@
29
33
  #
30
34
  # Modes:
31
35
  # --staged diff-scope: only files in `git diff --cached`
32
- # --all (default) full src/ + agentsfleet/src/ scan
36
+ # --all (default) full src/ + every tracked/unignored *.rs +
37
+ # agentsfleet/src/ scan
33
38
  # --strict promote every INFO finding to BLOCK (post-migration use)
34
39
  #
35
40
  # Exits 0 clean, 1 on BLOCK findings.
@@ -79,6 +84,13 @@ is_test_zig() {
79
84
  return 1
80
85
  }
81
86
 
87
+ is_non_runtime_rust_path() {
88
+ case "$1" in
89
+ build.rs|*/build.rs|tests/*|*/tests/*|benches/*|*/benches/*|examples/*|*/examples/*) return 0 ;;
90
+ esac
91
+ return 1
92
+ }
93
+
82
94
  # ---------------------------------------------------------------------------
83
95
  # 1. Gather files in scope.
84
96
  # ---------------------------------------------------------------------------
@@ -86,11 +98,14 @@ gather_paths() {
86
98
  case "$MODE" in
87
99
  --staged)
88
100
  git diff --cached --name-only --diff-filter=ACMRT \
89
- | grep -E '^(src/.*\.zig$|agentsfleet/src/.*\.(js|jsx|ts|tsx)$)' || true
101
+ | grep -E '(^src/.*\.zig$|\.rs$|^agentsfleet/src/.*\.(js|jsx|ts|tsx)$)' || true
90
102
  ;;
91
103
  --all)
92
- find src -type f -name '*.zig' 2>/dev/null
93
- find agentsfleet/src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) 2>/dev/null
104
+ find src -type f -name '*.zig' 2>/dev/null || true
105
+ while IFS= read -r path; do
106
+ [[ -f "$path" ]] && printf '%s\n' "$path"
107
+ done < <(git ls-files --cached --others --exclude-standard -- '*.rs')
108
+ find agentsfleet/src -type f \( -name '*.js' -o -name '*.jsx' -o -name '*.ts' -o -name '*.tsx' \) 2>/dev/null || true
94
109
  ;;
95
110
  esac
96
111
  }
@@ -105,11 +120,16 @@ fi
105
120
  # previously ran 3–4 forks × N files; M70 perf pass batches them into
106
121
  # single awk/grep passes.
107
122
  zig_nontest=()
123
+ rust_nontest=()
124
+ rust_candidates=()
108
125
  js_nontest=()
109
126
  for f in "${FILES[@]}"; do
110
127
  if [[ "$f" == *.zig ]] && ! is_test_zig "$f"; then
111
128
  zig_nontest+=("$f")
112
129
  fi
130
+ if [[ "$f" == *.rs ]] && ! is_non_runtime_rust_path "$f"; then
131
+ rust_nontest+=("$f")
132
+ fi
113
133
  case "$f" in
114
134
  agentsfleet/src/*.test.*|agentsfleet/src/*.spec.*|agentsfleet/src/tests/*) ;;
115
135
  agentsfleet/src/*.js|agentsfleet/src/*.jsx|agentsfleet/src/*.ts|agentsfleet/src/*.tsx)
@@ -118,6 +138,10 @@ for f in "${FILES[@]}"; do
118
138
  esac
119
139
  done
120
140
 
141
+ if [[ ${#rust_nontest[@]} -gt 0 ]]; then
142
+ mapfile -t rust_candidates < <(grep -lE '(^|[^[:alnum:]_])(println|eprintln|dbg)!|tracing::(error|warn|info|debug|trace)!' "${rust_nontest[@]}" 2>/dev/null || true)
143
+ fi
144
+
121
145
  # ---------------------------------------------------------------------------
122
146
  # 2. BLOCKING: std.debug.print in non-test Zig source.
123
147
  # ---------------------------------------------------------------------------
@@ -132,15 +156,217 @@ if [[ ${#zig_nontest[@]} -gt 0 ]]; then
132
156
  fail "$f:$ln — \`std.debug.print\` in non-test source (LOGGING_STANDARD §10A.L1)"
133
157
  debug_print_hits=$((debug_print_hits + 1))
134
158
  done < <(awk '
135
- FNR == 1 { in_test = 0 }
136
- /^test "/ { in_test = 1; next }
137
- /^}/ { in_test = 0; next }
138
- /(^|[^A-Za-z0-9_])std\.debug\.print\(/ { if (!in_test) printf "%s:%d\n", FILENAME, FNR }
159
+ function comment_text(text, output, cursor, char, escaped, quoted) {
160
+ escaped = 0
161
+ quoted = 0
162
+ for (cursor = 1; cursor <= length(text); cursor++) {
163
+ char = substr(text, cursor, 1)
164
+ if (quoted) {
165
+ if (escaped) escaped = 0
166
+ else if (char == "\\") escaped = 1
167
+ else if (char == "\"") quoted = 0
168
+ continue
169
+ }
170
+ if (char == "\"") {
171
+ quoted = 1
172
+ continue
173
+ }
174
+ if (char == "/" && substr(text, cursor + 1, 1) == "/")
175
+ return substr(text, cursor + 2)
176
+ }
177
+ return ""
178
+ }
179
+ function has_logging_reason(text, comment) {
180
+ if (text ~ /^[[:space:]]*\\\\/) return 0
181
+ comment = comment_text(text)
182
+ return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
183
+ }
184
+ FNR == 1 { in_test = 0; previous_annotation = 0 }
185
+ {
186
+ annotated = has_logging_reason($0) || previous_annotation
187
+ if ($0 ~ /^test "/) in_test = 1
188
+ else if ($0 ~ /^}/) in_test = 0
189
+ else if ($0 ~ /(^|[^A-Za-z0-9_])std\.debug\.print\(/ && !in_test && !annotated)
190
+ printf "%s:%d\n", FILENAME, FNR
191
+ previous_annotation = has_logging_reason($0)
192
+ }
139
193
  ' "${zig_nontest[@]}")
140
194
  fi
141
195
 
142
196
  # ---------------------------------------------------------------------------
143
- # 3. BLOCKING: console.log/debug/info/warn/error in agentsfleet/src non-test.
197
+ # 3. BLOCKING: unstructured or event-less tracing in non-test Rust source.
198
+ # The awk tracker excludes complete items annotated with #[cfg(test)].
199
+ # ---------------------------------------------------------------------------
200
+ rust_direct_hits=0
201
+ rust_missing_event_hits=0
202
+ rust_positional_hits=0
203
+ if [[ ${#rust_candidates[@]} -gt 0 ]]; then
204
+ while IFS='|' read -r kind f ln; do
205
+ [[ -z "$kind" ]] && continue
206
+ case "$kind" in
207
+ direct)
208
+ fail "$f:$ln — direct Rust diagnostic macro in non-test source (LOGGING_STANDARD §8A)"
209
+ rust_direct_hits=$((rust_direct_hits + 1))
210
+ ;;
211
+ event)
212
+ fail "$f:$ln — \`tracing\` emit without \`event = ...\` (LOGGING_STANDARD §8A)"
213
+ rust_missing_event_hits=$((rust_missing_event_hits + 1))
214
+ ;;
215
+ positional)
216
+ fail "$f:$ln — positional formatting in \`tracing\` emit (LOGGING_STANDARD §8A)"
217
+ rust_positional_hits=$((rust_positional_hits + 1))
218
+ ;;
219
+ esac
220
+ done < <(awk '
221
+ function count_char(text, char, copy, count) {
222
+ copy = text
223
+ count = gsub(char, "", copy)
224
+ return count
225
+ }
226
+ function code_only(text, output, cursor, char, escaped, quoted) {
227
+ output = ""
228
+ escaped = 0
229
+ quoted = 0
230
+ for (cursor = 1; cursor <= length(text); cursor++) {
231
+ char = substr(text, cursor, 1)
232
+ if (quoted) {
233
+ if (escaped) escaped = 0
234
+ else if (char == "\\") escaped = 1
235
+ else if (char == "\"") quoted = 0
236
+ continue
237
+ }
238
+ if (char == "\"") {
239
+ quoted = 1
240
+ continue
241
+ }
242
+ if (char == "/" && substr(text, cursor + 1, 1) == "/") break
243
+ output = output char
244
+ }
245
+ return output
246
+ }
247
+ function without_comment(text, output, cursor, char, escaped, quoted) {
248
+ output = ""
249
+ escaped = 0
250
+ quoted = 0
251
+ for (cursor = 1; cursor <= length(text); cursor++) {
252
+ char = substr(text, cursor, 1)
253
+ if (quoted) {
254
+ output = output char
255
+ if (escaped) escaped = 0
256
+ else if (char == "\\") escaped = 1
257
+ else if (char == "\"") quoted = 0
258
+ continue
259
+ }
260
+ if (char == "\"") {
261
+ quoted = 1
262
+ output = output char
263
+ continue
264
+ }
265
+ if (char == "/" && substr(text, cursor + 1, 1) == "/") break
266
+ output = output char
267
+ }
268
+ return output
269
+ }
270
+ function comment_text(text, cursor, char, escaped, quoted) {
271
+ escaped = 0
272
+ quoted = 0
273
+ for (cursor = 1; cursor <= length(text); cursor++) {
274
+ char = substr(text, cursor, 1)
275
+ if (quoted) {
276
+ if (escaped) escaped = 0
277
+ else if (char == "\\") escaped = 1
278
+ else if (char == "\"") quoted = 0
279
+ continue
280
+ }
281
+ if (char == "\"") {
282
+ quoted = 1
283
+ continue
284
+ }
285
+ if (char == "/" && substr(text, cursor + 1, 1) == "/")
286
+ return substr(text, cursor + 2)
287
+ }
288
+ return ""
289
+ }
290
+ function has_logging_reason(text, comment) {
291
+ comment = comment_text(text)
292
+ return comment ~ /^[[:space:]]*logging:[[:space:]]*[^[:space:]]/
293
+ }
294
+ function check_emit() {
295
+ if (emit_code !~ /event[[:space:]]*=/ && emit_code !~ /[(,][[:space:]]*event[[:space:]]*[,)]/)
296
+ printf "event|%s|%d\n", FILENAME, emit_line
297
+ if (emit_source ~ /"[^"\n]*\{[^"\n]*\}[^"\n]*"/)
298
+ printf "positional|%s|%d\n", FILENAME, emit_line
299
+ emit_code = ""
300
+ emit_source = ""
301
+ in_emit = 0
302
+ }
303
+ FNR == 1 {
304
+ depth = 0
305
+ test_depth = 0
306
+ cfg_test = 0
307
+ in_emit = 0
308
+ emit_parens = 0
309
+ emit_code = ""
310
+ emit_source = ""
311
+ previous_annotation = 0
312
+ }
313
+ {
314
+ current_annotation = has_logging_reason($0)
315
+ code = code_only($0)
316
+ if (!in_emit && code ~ /^[[:space:]]*$/) {
317
+ previous_annotation = current_annotation
318
+ next
319
+ }
320
+ opens = count_char(code, "\\{")
321
+ closes = count_char(code, "\\}")
322
+ if (test_depth > 0) {
323
+ depth += opens - closes
324
+ if (depth < test_depth) test_depth = 0
325
+ previous_annotation = current_annotation
326
+ next
327
+ }
328
+ if (code ~ /^[[:space:]]*#\[cfg\(test\)\]/) {
329
+ if (opens > 0) {
330
+ test_depth = depth + 1
331
+ depth += opens - closes
332
+ if (depth < test_depth) test_depth = 0
333
+ } else cfg_test = 1
334
+ previous_annotation = current_annotation
335
+ next
336
+ }
337
+ if (cfg_test) {
338
+ if (opens > 0) {
339
+ test_depth = depth + 1
340
+ depth += opens - closes
341
+ if (depth < test_depth) test_depth = 0
342
+ cfg_test = 0
343
+ } else if (code ~ /;/) cfg_test = 0
344
+ previous_annotation = current_annotation
345
+ next
346
+ }
347
+ if (code ~ /(^|[^[:alnum:]_])(println|eprintln|dbg)!/ && !current_annotation && !previous_annotation)
348
+ printf "direct|%s|%d\n", FILENAME, FNR
349
+ if (!in_emit && match(code, /tracing::(error|warn|info|debug|trace)!/)) {
350
+ in_emit = 1
351
+ emit_line = FNR
352
+ emit_code = code
353
+ emit_source = without_comment($0)
354
+ macro = substr(code, RSTART)
355
+ emit_parens = count_char(macro, "\\(") - count_char(macro, "\\)")
356
+ } else if (in_emit) {
357
+ emit_code = emit_code " " code
358
+ emit_source = emit_source " " without_comment($0)
359
+ emit_parens += count_char(code, "\\(") - count_char(code, "\\)")
360
+ }
361
+ if (in_emit && emit_parens <= 0) check_emit()
362
+ depth += opens - closes
363
+ previous_annotation = current_annotation
364
+ }
365
+ ' "${rust_candidates[@]}")
366
+ fi
367
+
368
+ # ---------------------------------------------------------------------------
369
+ # 4. BLOCKING: console.log/debug/info/warn/error in agentsfleet/src non-test.
144
370
  # ---------------------------------------------------------------------------
145
371
  console_hits=0
146
372
  if [[ ${#js_nontest[@]} -gt 0 ]]; then
@@ -155,7 +381,7 @@ if [[ ${#js_nontest[@]} -gt 0 ]]; then
155
381
  fi
156
382
 
157
383
  # ---------------------------------------------------------------------------
158
- # 4. INFO: std.log.scoped outside src/logging/ (pre-migration to the named
384
+ # 5. INFO: std.log.scoped outside src/logging/ (pre-migration to the named
159
385
  # `log` module's scoped API). The audit no longer carves out src/auth/ —
160
386
  # the named module is import-able from layer-isolated trees, so the
161
387
  # portability exception is gone.
@@ -180,7 +406,7 @@ if [[ ${#scoped_eligible[@]} -gt 0 ]]; then
180
406
  fi
181
407
 
182
408
  # ---------------------------------------------------------------------------
183
- # 5. INFO: err/warn logs without `error_code=` substring on the same line.
409
+ # 6. INFO: err/warn logs without `error_code=` substring on the same line.
184
410
  # Heuristic — captures the common case where an err/warn line should
185
411
  # embed UZ-XXX-NNN per LOGGING_STANDARD §5.
186
412
  # ---------------------------------------------------------------------------
@@ -198,18 +424,19 @@ if [[ ${#zig_nontest[@]} -gt 0 ]]; then
198
424
  fi
199
425
 
200
426
  # ---------------------------------------------------------------------------
201
- # 6. Promote INFO to BLOCK in --strict mode.
427
+ # 7. Promote INFO to BLOCK in --strict mode.
202
428
  # ---------------------------------------------------------------------------
203
429
  if [[ $STRICT -eq 1 && $INFO_COUNT -gt 0 ]]; then
204
430
  fail "--strict: $INFO_COUNT informational findings promoted to blocking"
205
431
  fi
206
432
 
207
433
  # ---------------------------------------------------------------------------
208
- # 7. Verdict.
434
+ # 8. Verdict.
209
435
  # ---------------------------------------------------------------------------
210
- ok "scanned ${#FILES[@]} files; std.debug.print=$debug_print_hits console.*=$console_hits std.log.scoped=$scoped_hits missing-error_code=$missing_code_hits"
436
+ ok "scanned ${#FILES[@]} files; std.debug.print=$debug_print_hits rust-direct=$rust_direct_hits rust-missing-event=$rust_missing_event_hits rust-positional=$rust_positional_hits console.*=$console_hits std.log.scoped=$scoped_hits missing-error_code=$missing_code_hits"
211
437
  if [[ $FAIL -ne 0 ]]; then
212
438
  printf "\n🔴 LOGGING GATE: blocking violations. See dispatch/write_any.md (Logging Gate).\n" >&2
439
+ printf "Intentional direct Zig or Rust stream output requires '// logging: <reason>' on or immediately above the emit; the reason must be non-empty.\n" >&2
213
440
  exit 1
214
441
  fi
215
442
  [[ $INFO_COUNT -gt 0 ]] && note "$INFO_COUNT informational findings; not blocking. Use --strict to enforce."
@@ -127,6 +127,7 @@ shebang and is the authoritative check.
127
127
  **Triggers** — every `Edit`/`Write` that adds, removes, or changes a log emit:
128
128
 
129
129
  - `*.zig` outside `vendor/`/`third_party/`/`.zig-cache/`/`*_test.zig` — `std.log.*`, `std.debug.print`, raw stderr writes, calls into the `log` named module (source: `src/logging/mod.zig`).
130
+ - Every tracked or unignored `*.rs` outside `build.rs`, `examples/`, `tests/`, `benches/`, and `#[cfg(test)]` items — `tracing::*`, `println!`, `eprintln!`, and `dbg!`.
130
131
  - `*.ts`/`*.tsx`/`*.js`/`*.jsx` outside `vendor/`/`node_modules/`/`*.test.*`/`*.spec.*` — `console.*`, custom logger calls.
131
132
  - `*.sh` outside generated dirs — `echo`/`printf` to `&2`.
132
133
 
@@ -145,10 +146,12 @@ shebang and is the authoritative check.
145
146
  | Pattern | Rule |
146
147
  |---|---|
147
148
  | New `logging.scoped(.tag)` call | Scope is a Zig enum literal — adding a new tag is freeform. `event` must be snake_case `verb_noun`. |
149
+ | New Rust `tracing::<level>!` call | `event = "verb_noun"` or the equivalent `event` field shorthand is mandatory; values are structured fields hoisted into locals. |
148
150
  | New `err`/`warn` log mapping to a domain failure | registry-scheme `error_code=` field required (agentsfleet: `UZ-XXX-NNN`). Registry entry must land in same commit. |
149
151
  | Per-iteration / hot-loop log | Use `debug` (hidden by default), not `info`. |
150
- | `info` level | No allow-list — `info` is open (§4 rule 2). Two checks instead: a boundary-crossing operation needs its `_started`/`_completed`\|`_failed` pair on every exit path (rule 1), and a per-iteration path is `debug` (rule 3). |
151
- | `console.log`/`std.debug.print` in non-test source | Forbidden. Convert to logger or delete before commit. |
152
+ | `info` level | No allow-list — `info` is open (§4 rule 2). Two checks instead: a boundary-crossing operation needs its `_started`/`_completed`\|`_failed` pair on every exit path (rule 1), and a per-iteration path is `debug` (rule 3). Hot-poll boundary pairs remain complete but use `debug`. |
153
+ | `console.log` in non-test source | Forbidden. Convert to the structured logger or delete before commit. |
154
+ | `std.debug.print` or a Rust print macro in runtime source | Convert to structured logging. When the stream output is the program interface, add `// logging: <reason>` on or immediately above the emit. The reason is mandatory. |
152
155
  | `std.log.scoped` outside `src/logging/` | Migration target. The `log` named module's `scoped` API is the only non-test entry point; flip the file's alias and migrate every call site in the same commit. |
153
156
  | `msg=` field | ≤ 300 chars. Stack traces emit as separate `event=stack_trace` debug record. |
154
157
  | Multi-line values | Newlines must be `\n` literal (two chars), not raw newline byte. |
@@ -173,7 +176,7 @@ Full multi-line block fires when a sub-rule reports a violation:
173
176
 
174
177
  ```
175
178
  LOGGING GATE: <file>
176
- docs/LOGGING_STANDARD.md sections consulted: §3 (wire format), §4 (severity), §5 (error codes), §6 (PII), §7 (zig binding) | §8 (TS binding), §10A (tightenings)
179
+ docs/LOGGING_STANDARD.md sections consulted: §3 (wire format), §4 (severity), §5 (error codes), §6 (PII), §7 (Zig binding) | §8 (TS binding) | §8A (Rust binding), §10A (tightenings)
177
180
  Wire format: <logfmt ✓ | violation: <where>>
178
181
  Required keys: <ts_ms,level,scope,event present ✓ | violation: <missing>>
179
182
  Severity choice: <within rules ✓ | violation: <e.g. info on per-iteration path>>
@@ -188,13 +191,13 @@ LOGGING GATE: <file>
188
191
 
189
192
  > [DETERMINISTIC → LOG]
190
193
 
191
- `logging.sh` walks the **full `src/` + `agentsfleet/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.
194
+ `logging.sh` walks `src/**/*.zig`, every tracked or unignored `*.rs`, and `agentsfleet/src/**/*.{js,jsx,ts,tsx}`. Rust repositories do not need to use a fixed source-root name. The staged mode reads the index, 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 development.
192
195
 
193
196
  #### End-of-turn audit
194
197
 
195
198
  > [DETERMINISTIC → LOG]
196
199
 
197
- `audits/logging.sh` runs as part of `make lint`. It greps `std.log.*`, `console.*` and raw stderr writes — it does NOT see calls through the `log` named module's `scoped()`, which is where every structured emit in `agentsfleet` goes, so a clean run is not evidence that §4/§5 hold. Mechanical today: `std.debug.print`/`console.log` presence, `std.log.scoped` outside `src/lib/logging/`, msg-length. Reviewer-side: severity choice, entry/exit pairing, `error_code` on scoped-logger calls, and PII spot-checks.
200
+ `audits/logging.sh` runs as part of `make lint`. It greps `std.log.*`, Rust `tracing` and direct-print macros, `console.*`, and raw stderr writes. It accepts a direct Zig or Rust stream write only when `// logging: <reason>` appears on or immediately above the emit with a non-empty reason. It does NOT see calls through the Zig `log` named module's `scoped()`, so a clean run is not evidence that §4/§5 hold everywhere. Mechanical today: direct Zig/Rust/TypeScript diagnostics, Rust's required `event` field and positional-format ban, `std.log.scoped` outside `src/lib/logging/`, and legacy Zig error-code hints. Reviewer-side: reason validity, severity choice, entry/exit pairing, registry applicability, inline Rust expression hoisting, and Personally Identifiable Information (PII) spot-checks.
198
201
 
199
202
  #### Family
200
203
 
@@ -1,6 +1,6 @@
1
1
  # LOGGING_STANDARD — wire format and discipline for structured logs
2
2
 
3
- The contract between code that emits log records and the systems (humans, collectors, dashboards) that consume them. Cross-language: applies to every Zig, TypeScript, JavaScript, and shell call site that emits to `stderr`/`stdout`.
3
+ The contract between code that emits log records and the systems (humans, collectors, dashboards) that consume them. Cross-language: applies to every Zig, Rust, TypeScript, JavaScript, and shell call site that emits to `stderr`/`stdout`.
4
4
 
5
5
  Pre-design rules, decisive defaults, and the anti-patterns each rule exists to prevent. Every rule has a one-line "why" so you can judge edge cases.
6
6
 
@@ -9,12 +9,13 @@ Pre-design rules, decisive defaults, and the anti-patterns each rule exists to p
9
9
  Triggers on every `Edit`/`Write` that adds, removes, or changes a log emit:
10
10
 
11
11
  - `*.zig` outside `vendor/`/`third_party/`/`.zig-cache/` — `std.log.*`, `std.debug.print`, `std.io.getStdErr().writer().print`, any helper in `src/lib/logging/`.
12
+ - `*.rs` under `rustd/` outside test and benchmark scopes — `tracing::{error,warn,info,debug,trace}!`, `println!`, `eprintln!`, and `dbg!`.
12
13
  - `*.ts`/`*.tsx`/`*.js`/`*.jsx` outside `vendor/`/`node_modules/` — `console.*`, custom logger calls.
13
14
  - `*.sh` outside generated directories — `echo`, `printf` to `&2`.
14
15
 
15
16
  Out of scope (explicitly):
16
17
 
17
- - Test-only diagnostic prints inside `*_test.zig`, `*.test.ts`, `*.spec.ts`. Tests render to humans, not collectors; gates ignore.
18
+ - Test-only diagnostic prints inside `*_test.zig`, Rust `tests/` and `benches/` trees or `#[cfg(test)]` items, `*.test.ts`, and `*.spec.ts`. Tests render to humans, not collectors; gates ignore.
18
19
  - Build/release scripts — the harness's own `audits/*`, `audits/release-*`, and any governance-repo tooling — toolchain output, not application logs.
19
20
  - Generated framework noise (Next.js startup banners, Bun runtime warnings) — out of our control.
20
21
 
@@ -123,6 +124,12 @@ a per-chunk callback: these flood collectors and drown the records that matter.
123
124
  `debug` is not a lesser level, it is the level with a volume switch. A sweep that
124
125
  reaps 4,000 workspaces logs each one at `debug` and its outcome at `info`.
125
126
 
127
+ Boundary pairing and per-iteration severity both apply to hot polls. A lease poll
128
+ that crosses Redis and Postgres on every iteration still emits its complete
129
+ `_started` and `_completed`/`_failed` pair, but emits both records at `debug`.
130
+ The pair satisfies rule 1; the level satisfies rule 3. A coarser poll-loop start,
131
+ stop, or aggregate outcome may remain `info`.
132
+
126
133
  ### `err` and `warn` are unconditional
127
134
 
128
135
  > [UNENFORCED → "always emitted" is a runtime filter-configuration property; nothing greps for a suppressed err/warn path]
@@ -243,7 +250,7 @@ The `comptime if ... return` makes the entire emit path disappear when the scope
243
250
  | Pattern | Why banned | Fix |
244
251
  |---|---|---|
245
252
  | `std.log.info("..."` outside `logging.scoped(.tag)` | Bypasses scope/event/field discipline. | Convert to `logging.scoped(.tag).info("event", .{...})`. |
246
- | `std.debug.print(` in non-test source | No level, no scope, no field structure. Dev-only debugging that escaped to main. | Convert or delete before commit. |
253
+ | `std.debug.print(` in non-test source | No level, no scope, no field structure. Dev-only debugging that escaped to main. | Convert or delete. If stdout or stderr is the program interface, add `// logging: <reason>` on or immediately above the emit. |
247
254
  | `std.log.err` without `error_code=` field | Drops registry traceability. | Add the registry code, or add a registry entry if missing. |
248
255
  | `std.log.scoped(...)` outside `src/lib/logging/` | Free-form printf-style emit. | Switch the file's alias to `const log = logging.scoped(.tag);` and migrate every call site to the event+fields form. |
249
256
  | Positional `{s} {d}` placeholders | Not logfmt; not greppable. | Convert to struct-of-fields. |
@@ -288,6 +295,58 @@ class AgentError extends Error {
288
295
 
289
296
  Throw-style modules `throw new AgentError({...})`. Result-style modules return `{ ok: false, error: new AgentError({...}) }`. Render path picks human or JSON based on the runtime mode.
290
297
 
298
+ ## §8A · Per-language binding — Rust
299
+
300
+ > [DETERMINISTIC → LOG]
301
+
302
+ Rust uses the existing `tracing` crate and emits the same logical fields as §3.
303
+ Every production emit supplies an `event` field, explicitly or through tracing's
304
+ field shorthand, whose value is a snake_case `verb_noun`. Fields are structured
305
+ arguments, never positional message formatting.
306
+
307
+ > [JUDGMENT → RUST-STYLE]
308
+
309
+ Every `warn` or `error` that maps to a registry code supplies
310
+ `error_code = code` per §5. Hoist field expressions into locals before the macro
311
+ call. The log bridge duplicates field expressions, and LLVM coverage
312
+ instrumentation scores the dead copy; locals preserve evaluation and coverage
313
+ behavior. A trailing string message is §3's optional `msg` and is always the
314
+ last macro argument. Prefer fields alone when they already explain the event.
315
+
316
+ ```rust
317
+ let code = error_code::INTERNAL_DB_QUERY.as_str();
318
+ let id = runner.as_str();
319
+ let reason = error.to_string();
320
+ let event = "runner_lease_failed";
321
+ tracing::warn!(error_code = code, runner_id = id, reason, event);
322
+ ```
323
+
324
+ > [JUDGMENT → EVENT-COMPAT]
325
+
326
+ Ports preserve existing event-name bytes by default. A Rust replacement for a
327
+ Zig operation keeps the Zig event spelling so dashboards and alerts continue to
328
+ match. Renaming is a separate, intentional observability migration.
329
+
330
+ > [DETERMINISTIC → LOG]
331
+
332
+ **Anti-patterns flagged by `logging.sh`:**
333
+
334
+ | Pattern | Why banned | Fix |
335
+ |---|---|---|
336
+ | `println!`, `eprintln!`, or `dbg!` in runtime source | No level, event, or structured fields. | Convert to a `tracing` emit or delete. If the stream is the program interface, add `// logging: <reason>` on or immediately above the emit. |
337
+ | `tracing::<level>!` without `event = ...` | Drops the stable §3 event key. | Add the byte-stable snake_case event field. |
338
+ | Positional formatting in a `tracing` message | Hides values inside text and defeats field queries. | Hoist values and emit named fields. |
339
+
340
+ The annotation reason is mandatory and must be non-empty. A bare `// logging:`
341
+ marker still fails. The annotation is for direct program output, such as a
342
+ command result or a fatal renderer that must work before tracing starts. It does
343
+ not excuse an incomplete `tracing` emit.
344
+
345
+ ```rust
346
+ // logging: stdout is this command's answer, not a log record
347
+ println!("{}", rendered_summary);
348
+ ```
349
+
291
350
  ## §9 · Pretty-printer (dev-only render variant)
292
351
 
293
352
  In production: every record on the wire is logfmt. No exceptions.
@@ -330,10 +389,10 @@ Failure modes the audit script and reviewer must close. These are **not aspirati
330
389
 
331
390
  | # | Rationalization | Closure |
332
391
  |---|---|---|
333
- | L1 | "Temporary debug print, I'll remove later" | `logging.sh` greps `std.debug.print` and `console.log` / `console.debug` / `console.info` in non-test source unconditionally. Found in commit → gate fails. No "temporary" carve-out. |
392
+ | L1 | "Temporary debug print, I'll remove later" | `logging.sh` rejects `std.debug.print`, Rust `println!` / `eprintln!` / `dbg!`, and `console.log` / `console.debug` / `console.info` in runtime source. Direct Zig or Rust stream output needs `// logging: <reason>` on or immediately above the emit. The reason must be non-empty and explain why the stream is the program interface. |
334
393
  | L2 | "`std.log.scoped` is fine, `obs.scoped` is just a wrapper" | `std.log.scoped` is **forbidden** in `src/**/*.zig` outside `src/lib/logging/`. Only `obs.scoped` is callable. Audit flags every `std.log.` call site. |
335
394
  | L3 | "I added `error_code=UZ-NEW-001` — registry entry coming next commit" | The registry entry **must land in the same commit** as the first reference. `error-codes.sh` runs against the staged diff; missing entry = blocking. |
336
- | L4 | "This per-iteration event matters for debugging — `info`-level" | Per-iteration and per-row paths are `debug`, no exception: a loop body is not a boundary, and `debug` is the level with a volume switch. This closes the ONLY `info` prohibition — there is no allow-list of event names and none may be reintroduced (§4). The inverse rationalization is closed too: "this operation is obviously fine, no need to log it" does not survive §4 rule 1, which requires a `_started`/`_completed`\|`_failed` pair on every boundary-crossing operation. Reviewer checks the pair and the loop, never the spelling. |
395
+ | L4 | "This per-iteration event matters for debugging — `info`-level" | Per-iteration and per-row paths are `debug`, including mandatory boundary pairs on hot polls. `debug` is the level with a volume switch. This closes the ONLY `info` prohibition — there is no allow-list of event names and none may be reintroduced (§4). The inverse rationalization is closed too: "this operation is obviously fine, no need to log it" does not survive §4 rule 1, which requires a `_started`/`_completed`\|`_failed` pair on every boundary-crossing operation. Reviewer checks the pair and the loop, never the spelling. |
337
396
  | L5 | "Operator needs the full stack trace in `msg=`" | `msg=` capped at 300 chars; total fields per record capped at 15. Stack traces emit as a separate `event=stack_trace` record at `debug` level, correlated by `correlation_id`, not stuffed into `msg`. |
338
397
  | L6 | "Embedded newlines because I copy-pasted output" | Audit greps for raw newline byte inside quoted logfmt values. Must be `\n` literal (two chars). |
339
398
  | L7 | "Auto-mode is on, the gate block is ceremony" | **Auto-mode does NOT cover gate skips.** Skip without an explicit user-given override = automatic violation. No size threshold lets an edit bypass the gate. |
@@ -348,6 +407,9 @@ These are enforced by `logging.sh` (mechanical) and the dispatch façade (`dispa
348
407
  |---|---|
349
408
  | Free-form English log lines | Not greppable, not parseable, no event tag. |
350
409
  | `std.debug.print` outside tests | No level, no scope, no fields — dev debugging that escaped to main. |
410
+ | Rust `println!`, `eprintln!`, or `dbg!` outside tests | No level, event, or structured fields — dev diagnostics that escaped to main. |
411
+ | Rust `tracing` emit without `event` | Drops the stable event key required by §3. |
412
+ | Positional formatting in a Rust `tracing` emit | Values become opaque message text instead of queryable fields. |
351
413
  | `console.log` in `agentsfleet` source | Bypasses logger, breaks `--json` mode, violates `dispatch/write_ts_adhere_bun.md` §10. |
352
414
  | Logging on hot per-iteration paths at `info` | Floods collectors. Use `debug` (gated off by default). |
353
415
  | `error_code=` missing on `err`/`warn` mapping to registry codes | Breaks traceability; future operator can't link log to docs. |
@@ -371,6 +433,7 @@ immediately preceding the edit. Generic "scope creep" is not a valid reason —
371
433
  - `dispatch/write_ts_adhere_bun.md` §10 — banned `console.log` in TS/JS source. Cross-referenced by `logging.sh`.
372
434
  - `dispatch/write_ts_adhere_bun.md` §9 — module-level error style (throw vs Result) for `agentsfleet`.
373
435
  - `dispatch/write_zig.md` — Zig discipline umbrella; this doc's §7 is the logging-specific layer.
436
+ - `dispatch/write_rust.md` — Rust discipline umbrella; this doc's §8A is the logging-specific layer.
374
437
  - `LIFECYCLE_PATTERNS.md` — orthogonal: ownership/cleanup of structs, including allocator wiring for the thread-local log buffer.
375
438
  - M42_002 redaction harness (`src/runner/engine/runner_progress.zig`) — secret-redaction precondition; this doc's §6 inherits.
376
439
  - Universal rules (RULE UFS, RULE TGU, RULE PRI, RULE FLL, RULE ORP, RULE TST-NAM) live in `docs/greptile-learnings/RULES.md`.
@@ -388,10 +451,12 @@ its line covers the clauses under it until the next heading.
388
451
 
389
452
  | Tag | Meaning | Where the verdict comes from |
390
453
  |---|---|---|
391
- | `[DETERMINISTIC → LOG]` | a script decides; no talking past it | `audits/logging.sh` — `std.debug.print` and `console.*` in non-test source block; missing `error_code=` on `std.log.{err,warn}` reports as INFO |
454
+ | `[DETERMINISTIC → LOG]` | a script decides; no talking past it | `audits/logging.sh` — direct Zig/Rust/TypeScript diagnostics and malformed Rust tracing emits in non-test source block; missing `error_code=` on `std.log.{err,warn}` reports as INFO |
392
455
  | `[JUDGMENT → BOUNDARY]` | is this operation boundary-crossing, and is its `_started`/`_completed`\|`_failed` pair complete | the agent at write time; the reviewer at `/review` |
393
456
  | `[JUDGMENT → REDACT]` | is this value a secret, or the label of one | the agent at write time; the M42_002 harness covers only executor-child output |
394
457
  | `[JUDGMENT → MSG-REVIEW]` | does this `msg=` carry a credential | the reviewer; no length or content check runs today |
395
458
  | `[JUDGMENT → TS-STYLE]` | throw-style or Result-style for this module | `dispatch/write_ts_adhere_bun.md` §9, agent-decided |
459
+ | `[JUDGMENT → RUST-STYLE]` | does a Rust failure map to a registry code, and are field expressions safely hoisted | the agent at write time; the reviewer at `/review` |
460
+ | `[JUDGMENT → EVENT-COMPAT]` | does a language port preserve the event bytes consumed by existing dashboards | the agent compares old and new event constants; the reviewer confirms intentional renames |
396
461
  | `[JUDGMENT → SECTION-SCAN]` | which sections of this file the current sub-task needs | the agent; `audits/doc-read.sh` records façade reads, not delegated-doc reads |
397
462
  | `[UNENFORCED → reason]` | acknowledged prose; the reason states why no check exists | nothing — that is the point of the class |
@@ -122,6 +122,25 @@ surfaces:
122
122
 
123
123
  Orly owns policy and invokes these commands. The repository owns what they do.
124
124
 
125
+ ## Usage telemetry
126
+
127
+ Orly keeps usage telemetry off until a person enables anonymous collection in
128
+ an interactive run. Hooks, Continuous Integration (CI), and other automated
129
+ runs never ask for consent. Off writes no event and makes no network request.
130
+
131
+ Anonymous events enter a bounded outbound spool under the agentsfleet state
132
+ root. After each append, Orly launches a detached sync attempt and lets the
133
+ command exit. The sync sends at most 100 events and exits without a fetch when
134
+ another attempt ran within five minutes. A successful response removes the
135
+ acknowledged prefix. Spool maintenance drops records older than seven days and
136
+ keeps the file at or below 10 MiB, so an unavailable destination cannot create
137
+ an unbounded local log. Orly runs no telemetry daemon.
138
+
139
+ PostHog owns product-event storage and funnel analysis. Orly sends no source,
140
+ path, repository, branch, argument, output, environment, hostname, username, or
141
+ raw error value. A future Insights Fleet may query PostHog with a private key;
142
+ the Fleet is an analysis consumer and never the public ingestion boundary.
143
+
125
144
  ## Evidence
126
145
 
127
146
  `orly verify --all --write-evidence` records the source commit and every check
@@ -6,7 +6,7 @@ regenerates this file and fails on any difference. To update it, run
6
6
 
7
7
  | Rule document | enforced by | judged | acknowledged | classified | trigger |
8
8
  |---|---|--:|--:|--:|---|
9
- | `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 10 | 13 | 26/26 | mechanical |
9
+ | `docs/LOGGING_STANDARD.md` | `LOG` → `logging.sh` | 11 | 13 | 30/30 | mechanical |
10
10
  | `docs/REST_API_DESIGN_GUIDELINES.md` | — | 0 | 0 | 0/74 | mechanical |
11
11
  | `docs/SCHEMA_CONVENTIONS.md` | — | 0 | 0 | 0/16 | mechanical |
12
12
  | `docs/DOCUMENTATION_RULES.md` | — | 0 | 0 | 0/22 | latent |
@@ -14,7 +14,7 @@ regenerates this file and fails on any difference. To update it, run
14
14
  | `docs/CHANGELOG_VOICE.md` | — | 0 | 0 | 0/3 | latent |
15
15
  | `docs/VERIFY_TIERS.md` | — | 0 | 0 | 0/4 | uncited |
16
16
  | `docs/greptile-learnings/RULES.md` | — | 0 | 0 | 0/114 | mechanical |
17
- | **corpus** | | | | **26/281** | |
17
+ | **corpus** | | | | **30/285** | |
18
18
 
19
19
  ## How to read this
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsfleet/orly",
3
- "version": "0.6.9",
3
+ "version": "0.7.1",
4
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",
@@ -13,6 +13,12 @@ description: |
13
13
 
14
14
  # orly-babysit-prs
15
15
 
16
+ At invocation start, record the consent-gated usage signal without blocking the skill:
17
+
18
+ ```bash
19
+ command -v orly >/dev/null 2>&1 && ORLY_INVOCATION=skill orly skill-event orly-babysit-prs >/dev/null 2>&1 || true
20
+ ```
21
+
16
22
  Review bots (greptile on GitHub; greptile or another review bot on GitLab)
17
23
  post auto-reviews **asynchronously**. They land as PR review comments / MR
18
24
  discussion threads, not check runs, so `gh pr checks --watch` (or the glab
@@ -11,6 +11,12 @@ description: |
11
11
 
12
12
  # orly-spec-new
13
13
 
14
+ At invocation start, record the consent-gated usage signal without blocking the skill:
15
+
16
+ ```bash
17
+ command -v orly >/dev/null 2>&1 && ORLY_INVOCATION=skill orly skill-event orly-spec-new >/dev/null 2>&1 || true
18
+ ```
19
+
14
20
  A spec is the **rulebook** the executing agent (Orly) plans and ships from. This
15
21
  skill makes me author one whose Pull Request (PR) lands **deterministic, review-clean, and
16
22
  reported** — without a 20-questions loop. It is written for my reasoning, not as