@agentsfleet/orly 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -61,7 +61,7 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
61
61
  - **Skip hooks/signing.** Never `--no-verify`/`--no-gpg-sign`/`-c commit.gpgsign=false`. Hook fails → fix cause.
62
62
  - **Plaintext secrets in entity tables.** Store vault `key_name` ref, resolve via `crypto_store.load()`.
63
63
  - **Static strings in SQL schema.** No `DEFAULT 'value'`/`CHECK (col IN ('a','b'))`. Enforce in app via named constants.
64
- - **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log.
64
+ - **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log. Vault names come from the environment, never from a rules file.
65
65
  - **Force-push default branch** (`main`/`master`).
66
66
  - **Install-process launches in core paths.** Native SDKs. Exception: personal dev tools (`op`/`gh`/`glab`/`oracle`).
67
67
 
@@ -83,14 +83,14 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
83
83
  - **Symlinked edits land in the repository that owns the file.** A file whose `readlink` resolves outside this checkout — into a dotfiles or config repository — is an edit to *that* repository, not this one. Detect via `readlink` BEFORE editing. After: `cd` to the resolved repository, `git add <files> && git commit && git push` on its default branch. Never leave uncommitted.
84
84
  - **Docs-repo edits on own branch.** `~/Projects/docs/` is shared across milestones: check `git status` first; from `main` (checkout or worktree off `main`) commit on `chore/m{N}-{slug}-changelog`; recovery = stash, re-apply on a fresh branch.
85
85
  - Other dotfiles (`.zshrc`/`.gitconfig`/etc.): timestamped backup; minimal edits.
86
+ - **Check a sibling repository for an existing pattern in the same language before inventing one.** A shape shipping next door is prior art; a new one needs a reason.
87
+ - **Read the reference implementation before designing.** Where the language's dispatch page names a canonical external source, that read is mandatory in review and its guideline identifiers are cited — applied or diverged from. "Broken for us" means the delta was missed: diff our call site against theirs before blaming the principle.
86
88
  - Before commit/push: `gitleaks` must pass.
87
89
  - No new `make` targets without a distinct caller (CI job, spec-mandated gate, or a workflow existing targets can't express) — check `make/*.mk` first; extend over near-duplicate wrappers.
88
90
  - `*.zig` → read `dispatch/write_zig.md`; ZIG GATE fires.
89
91
  - Auth-flow (token-minting handlers, credential-typed spec dimensions, the repository's auth directories) → read the repository's `docs/AUTH.md` first, where it exists.
90
92
  - `conn.query()` requires `.drain()` in same fn before `deinit()`. Verify `make lint-governance`. Use `conn.exec()` for no-rows.
91
- - **Vault (1Password `op`).** Resolve secrets via the `op` CLI, never hand-paste/log. Vault names come from the environment, never from a rules file.
92
- - Check a sibling repository under `$HOME/Projects/` for an existing pattern in the same language before inventing one.
93
- - **Reference canon (read before designing):** TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop` + Microsoft's Rust guidelines (`oss/rust-guidelines/all.txt`, fetch if absent; mandatory in review). Missing checkout → ask, then clone into `~/Projects/oss/`. "Broken for us" → call-site diff first.
93
+ - **Indy's reference checkouts** (the machine-local half of the rule above): TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop`. Missing checkout → ask, then clone into `~/Projects/oss/`.
94
94
 
95
95
  **Forge detection:** `github.com` → `gh`; `gitlab.com` → `glab`. Check `git remote -v`.
96
96
 
@@ -180,7 +180,9 @@ Non-trivial (full lifecycle) if it: touches >1 file · new abstraction · data m
180
180
 
181
181
  **With spec:** `CHORE(open) → PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT → CHORE(close)`. **Without spec** (bug fix/config/refactor): `PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT`. CHORE bookends iff work creates/continues a spec under `docs/v*/{active,pending}/`. Stage runbooks (checklists, recipes, formats): `dispatch/lifecycle.md`.
182
182
 
183
- **Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason. `orly gate` runs `work → verify → pr`, stops at the first red group, and only reads; green unlocks CHORE(close) but never performs it. No spec → spec criteria skip, quality gates still run. Slow suites (`verify.integration`, `verify.memory`) run only when the branch carries code. A user-surface change without a docs change is red until docs land or an override says why not.
183
+ **Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason.
184
+
185
+ **One gate per cadence, each tier run once.** `work` = the declared `conform` command, no git state (a commit hook's tree is dirty by construction). `verify` = spec dimensions, docs language, the fast `verify.*` set. `pr` = branch shape, clean tree, pushed branch, every spec criterion, the slow suites. `orly gate pr` is the command CHORE(close) runs; `git.pushed` proves HEAD is exactly what the pre-push `orly gate verify` already graded. A bare `orly gate` chains all three, for a branch whose earlier gates never ran. Stage map: `dispatch/lifecycle.md`. Both stop at the first red group and only read; green unlocks CHORE(close) but never performs it. No spec → spec criteria skip, quality gates still run. Slow suites (`verify.integration`, `verify.memory`) run only when the branch carries code. A user-surface change without a docs change is red until docs land or an override says why not.
184
186
 
185
187
  **LAND (after merge, or when the user confirms it merged):** pull the default branch, prune the merged worktree + branch, `make down` where defined.
186
188
 
@@ -203,13 +205,13 @@ Edit only approved scope; no opportunistic refactors. Stay in active worktree. C
203
205
 
204
206
  ### CONFORM
205
207
 
206
- Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance.
208
+ Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance. This is the `work` gate's tier and the pre-commit hook runs it at every commit: declare a `conform` costing seconds, not minutes.
207
209
  In `agentsfleet` this stage is `make harness-verify`; its output block and end-of-turn audit detail live in `docs/HARNESS_VERIFY_OUTPUT.md`. Required rows: FILE SHAPE, PUB GATE, LENGTH GATE, MILESTONE-ID GATE, ZIG GATE, UI GATE, DESIGN TOKEN GATE, UFS GATE, SCHEMA GUARD, GREPTILE GATE, Architecture consult, Coverage, and `/orly-write-unit-test`.
208
210
 
209
211
  ### VERIFY
210
212
 
211
- Run the repository's declared `verify.*` commands from `.oracle/orly.json`. A package-scoped command never replaces a listed one.
212
- The `agentsfleet` output block and exact tiers live in `docs/VERIFY_TIERS.md`. **FIRST: `/orly-write-unit-test`** audits diff coverage. **LAST: the Test Delta row** compares against the CHORE(open) baseline; zero or negative unit growth on a code-adding diff needs justification or a return to EXECUTE. Paste memory-leak evidence into PR Session Notes or cite its Continuous Integration (CI) URL. After refactors, list newly dead code before removing it.
213
+ **Two cadences, one boundary.** Inside a Section, run CONFORM plus the declared lane covering the surface the Section touched — enough to prove a Section, reported as a Section claim. The repository's full declared `verify.*` set runs ONCE, at the milestone boundary, before the PR. A package-scoped command never replaces a listed one at either cadence, and a Section lane never satisfies a "tests pass" claim about the repository.
214
+ The `agentsfleet` output block and both cadences live in `docs/VERIFY_TIERS.md`. **FIRST: `/orly-write-unit-test`** audits diff coverage. **LAST: the Test Delta row** compares against the CHORE(open) baseline; zero or negative unit growth on a code-adding diff needs justification or a return to EXECUTE. Paste memory-leak evidence into PR Session Notes or cite its Continuous Integration (CI) URL. After refactors, list newly dead code before removing it.
213
215
 
214
216
  ### REVIEW
215
217
 
@@ -235,7 +237,7 @@ Required when spec involved — after last COMMIT, before PR. Also runs when par
235
237
 
236
238
  | # | When | Skill |
237
239
  |---|---|---|
238
- | 1 | VERIFY | `/orly-write-unit-test`, then `/orly-write-integration-test` — both before the PR, never skipped, never deferred |
240
+ | 1 | VERIFY | `/orly-write-unit-test` once per Section over that Section's diff, and again at the boundary — never skipped, never deferred. Then `/orly-write-integration-test` at the boundary **when the diff crosses a module boundary with real input/output**; otherwise record `N/A — <reason>` in Session Notes. |
239
241
  | 2 | REVIEW | gstack `/review` — every runtime, same route; address findings or record a user-acked deferral; reviewer unavailable → Session Notes: *"skipped — <reason> <ts>; rerun before merge"* |
240
242
  | 3 | After every push | `orly-babysit-prs` — CI check runs + greptile inline threads + PR-level summary; stops on two consecutive empty polls with CI green; never `gh pr checks --watch` for greptile |
241
243
 
@@ -294,7 +296,10 @@ Optimise for one thing: he never has to ask twice.
294
296
  justification clauses, importance puffery, em-dash rhythm crutches, and
295
297
  fake-profound kickers. End on the clearest concrete sentence. The banned-word
296
298
  list lives in `docs/DOCUMENTATION_RULES.md` §DOC-05, §DOC-07, §DOC-14b, and
297
- `orly gate verify` enforces it.
299
+ `orly gate verify` REPORTS it — `docs.language` prints its findings and stays
300
+ green, because the corpus predates the rule and a red on day one gets switched
301
+ off by the end of the week. Nobody fails a build over this, so the prose is
302
+ mine to get right.
298
303
 
299
304
  ## Reading Indy
300
305
 
@@ -370,5 +375,5 @@ Optimise for one thing: he never has to ask twice.
370
375
 
371
376
  | Responsibility | Commands |
372
377
  |---|---|
373
- | `conform` | `make audit` |
378
+ | `conform` | `make conform` |
374
379
  | `verify.unit` | `bun test src` |
package/SOUL.md CHANGED
@@ -44,7 +44,10 @@ Optimise for one thing: he never has to ask twice.
44
44
  justification clauses, importance puffery, em-dash rhythm crutches, and
45
45
  fake-profound kickers. End on the clearest concrete sentence. The banned-word
46
46
  list lives in `docs/DOCUMENTATION_RULES.md` §DOC-05, §DOC-07, §DOC-14b, and
47
- `orly gate verify` enforces it.
47
+ `orly gate verify` REPORTS it — `docs.language` prints its findings and stays
48
+ green, because the corpus predates the rule and a red on day one gets switched
49
+ off by the end of the week. Nobody fails a build over this, so the prose is
50
+ mine to get right.
48
51
 
49
52
  ## Reading Indy
50
53
 
@@ -95,6 +95,7 @@ The questionnaire is organised by scenario. Each scenario corresponds to a momen
95
95
  | 4.23 (Documentation) | Before published Markdown JSX (MDX), reusable snippets, customer readme, or public OpenAPI prose, must the agent read `dispatch/write_documentation.md` and `docs/DOCUMENTATION_RULES.md` before any narrower guide? | YES |
96
96
  | 4.24 (Documentation) | Are page, snippet, generated API, and changelog scopes distinct, with repository-owned pre-commit checks enforcing the mechanical rules? | YES |
97
97
  | 4.25 (Rust) | Do `*.rs` edits route to `dispatch/write_rust.md` — ownership, error variants, feature combinations, deterministic contention tests — with Microsoft's Pragmatic Rust Guidelines (`~/Projects/oss/rust-guidelines/all.txt`, fetched when absent) read sectioned and its `M-…` IDs cited in every Rust review? | YES | <!-- oracle-packs:language.rust -->
98
+ | 4.25b (Rust errors) | Is RULE ERR-RS deterministic where a machine can decide it — `dispatch/write_rust.sh` running `audits/rust-error.sh` over staged `*.rs` to block a `map_err` that stringifies its own cause and an error type with no `pub type Result<…>` in its crate — while `source()` shape and whether a context addition is warranted stay the agent's call? | YES | <!-- oracle-packs:language.rust -->
98
99
  | 4.25a (Go) | Do `*.go` edits route to `dispatch/write_go.md`, with wrapped errors, goroutine exit paths, context-first signatures, defer-at-acquire, and `-race` verification? | YES | <!-- oracle-packs:language.go -->
99
100
  | 4.26 (Python) | Do `*.py` edits route to `dispatch/write_python.md`, with parse-boundary validation and context-managed resources? | YES |
100
101
  | 4.27 (Shell) | Do `*.sh` edits route to `dispatch/write_shell.md`, with quoted expansions, array arguments, cleanup, and compatibility requirements? | YES |
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env bash
2
+ # rust-error.sh — the mechanical half of RULE ERR-RS (Rust error discipline).
3
+ #
4
+ # Dispatch façade: dispatch/write_rust.md (Error discipline). Pairs with
5
+ # dispatch/write_rust.sh, which is what runs it.
6
+ #
7
+ # The rule has four clauses. Two of them a machine can decide from the text
8
+ # alone, and only those two live here:
9
+ #
10
+ # 1. LOSSY MAP_ERR — `map_err` whose closure converts its own cause through
11
+ # `to_string()` or `format!`. The rule allows `map_err` to ADD context the
12
+ # call site alone knows; it forbids stringifying the cause on the way into
13
+ # an error type, because `to_string()` destroys the `source()` chain. The
14
+ # closure's BINDING is what makes this decidable: `.map_err(|e| Mine::Db(
15
+ # e.to_string()))` stringifies the cause, while `.map_err(|e| Mine::Read {
16
+ # source: e, path: path.to_string() })` stringifies a path and is correct.
17
+ # Only a `to_string()` or `format!` applied to the closure's own parameter
18
+ # is a finding.
19
+ #
20
+ # 2. MISSING RESULT ALIAS — a crate declaring `pub enum Error` or
21
+ # `pub struct Error` with no `pub type Result<` anywhere beside it. The
22
+ # rule is "one error type per crate, and a `Result` alias beside it": a
23
+ # reader must never have to check WHICH error a signature returns.
24
+ #
25
+ # The other two clauses stay JUDGMENT and are not attempted here. Whether a
26
+ # `source()` implementation returns its own kind needs the Display body read
27
+ # beside it, and whether a context addition is warranted is a call-site
28
+ # question. A gate that guesses at those gets switched off within a week, and a
29
+ # switched-off gate protects nothing — partial mechanisation stated honestly is
30
+ # the whole design (the same call audits/doc-read.sh makes about its record).
31
+ #
32
+ # Carve-outs match audits/logging.sh exactly, so one Rust scope answers to both
33
+ # leaves: build scripts, `tests/`, `benches/`, `examples/`, and `#[cfg(test)]`
34
+ # modules are fixture code, where a stringified error in a test assertion is
35
+ # not a production error path.
36
+ #
37
+ # Modes:
38
+ # --staged diff-scope: only *.rs in `git diff --cached`
39
+ # --all (default) every tracked/unignored *.rs
40
+ #
41
+ # Exits 0 clean, 1 on findings.
42
+
43
+ set -euo pipefail
44
+
45
+ MODE_STAGED="--staged"
46
+ MODE_ALL="--all"
47
+ RULE="ERR-RS"
48
+ FACADE="dispatch/write_rust.md"
49
+ # The closure body window. A `map_err` closure that has not closed its
50
+ # parentheses within this many lines is a block long enough that its conversion
51
+ # is no longer a one-liner, and the rule's subject is the one-liner.
52
+ MAX_CLOSURE_LINES=12
53
+ CARGO_MANIFEST="Cargo.toml"
54
+ RESULT_ALIAS_PATTERN='^[[:space:]]*pub[[:space:]]+type[[:space:]]+Result[[:space:]]*<'
55
+ ERROR_TYPE_PATTERN='^[[:space:]]*pub[[:space:]]+(enum|struct)[[:space:]]+Error[[:space:]]*[<{(;]'
56
+
57
+ MODE="$MODE_ALL"
58
+ for arg in "$@"; do
59
+ case "$arg" in
60
+ --staged|staged) MODE="$MODE_STAGED" ;;
61
+ --all|all) MODE="$MODE_ALL" ;;
62
+ -h|--help)
63
+ printf 'usage: %s [--staged|--all]\n' "$0"
64
+ exit 0
65
+ ;;
66
+ *) printf 'unknown arg: %s\n' "$arg" >&2; exit 64 ;;
67
+ esac
68
+ done
69
+
70
+ ROOT="$(git rev-parse --show-toplevel)"
71
+ cd "$ROOT"
72
+
73
+ FAIL=0
74
+ fail() { printf 'FAIL: %s\n' "$*" >&2; FAIL=1; }
75
+ ok() { printf 'OK: %s\n' "$*"; }
76
+
77
+ # Non-runtime Rust paths, identical to audits/logging.sh's carve-out list.
78
+ is_non_runtime_rust_path() {
79
+ case "$1" in
80
+ build.rs|*/build.rs|tests/*|*/tests/*|benches/*|*/benches/*|examples/*|*/examples/*) return 0 ;;
81
+ esac
82
+ return 1
83
+ }
84
+
85
+ gather_paths() {
86
+ case "$MODE" in
87
+ "$MODE_STAGED")
88
+ git diff --cached --name-only --diff-filter=ACMRT -- '*.rs' || true
89
+ ;;
90
+ "$MODE_ALL")
91
+ git ls-files --cached --others --exclude-standard -- '*.rs' || true
92
+ ;;
93
+ esac
94
+ }
95
+
96
+ FILES=()
97
+ while IFS= read -r path; do
98
+ [[ -n "$path" && -f "$path" ]] || continue
99
+ is_non_runtime_rust_path "$path" && continue
100
+ FILES+=("$path")
101
+ done < <(gather_paths)
102
+
103
+ if [[ ${#FILES[@]} -eq 0 ]]; then
104
+ ok "$RULE: no runtime Rust files in scope ($MODE)"
105
+ exit 0
106
+ fi
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # 1. Scan every in-scope file once, emitting both signals.
110
+ # ---------------------------------------------------------------------------
111
+ # One pass, one carve-out. Both halves need the same `#[cfg(test)]` skip, and
112
+ # implementing it twice is how the two halves would come to disagree about what
113
+ # a test module is — so the scanner emits `LOSSY:` and `ERRTYPE:` records from a
114
+ # single walk and the shell decides what to do with each.
115
+ #
116
+ # The lossy scanner accumulates a map_err closure from its opening parenthesis
117
+ # until the parentheses balance, so a conversion split over several lines reads
118
+ # the same as a one-liner.
119
+ signals="$(awk -v maxlines="$MAX_CLOSURE_LINES" -v errtype="$ERROR_TYPE_PATTERN" '
120
+ FNR == 1 { intest = 0; testdepth = 0; pending = 0; collecting = 0 }
121
+
122
+ # A #[cfg(test)] module is fixture code. The attribute precedes the item, so
123
+ # arm on the attribute and start counting braces at the item that follows.
124
+ !collecting && /#\[cfg\(test\)\]/ { pending = 1; next }
125
+ pending {
126
+ if (index($0, "{") > 0) { pending = 0; intest = 1; testdepth = 0 }
127
+ else next
128
+ }
129
+ intest {
130
+ n = gsub(/\{/, "{"); m = gsub(/\}/, "}")
131
+ testdepth += n - m
132
+ if (testdepth <= 0) intest = 0
133
+ next
134
+ }
135
+
136
+ {
137
+ line = $0
138
+ # Strip a trailing line comment so a commented example is not a finding.
139
+ sub(/\/\/.*$/, "", line)
140
+
141
+ # A public error type outside any test module. Emitted per file; the shell
142
+ # resolves it to a crate and asks that crate for its alias once.
143
+ if (!collecting && line ~ errtype) printf "ERRTYPE:%s\n", FILENAME
144
+
145
+ if (!collecting) {
146
+ where = match(line, /map_err[[:space:]]*\([[:space:]]*\|[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*\|/)
147
+ if (where == 0) next
148
+ rest = substr(line, where)
149
+ # The closure binding: the identifier between the pipes.
150
+ bind = rest
151
+ sub(/^map_err[[:space:]]*\([[:space:]]*\|[[:space:]]*/, "", bind)
152
+ sub(/[[:space:]]*\|.*$/, "", bind)
153
+ if (bind == "_") next
154
+ collecting = 1; buffer = rest; startline = FNR; spanned = 1
155
+ depth = gsub(/\(/, "(", rest) - gsub(/\)/, ")", rest)
156
+ } else {
157
+ buffer = buffer " " line
158
+ spanned++
159
+ depth += gsub(/\(/, "(", line) - gsub(/\)/, ")", line)
160
+ }
161
+
162
+ if (depth > 0 && spanned < maxlines) next
163
+
164
+ # `bind.to_string()` — the cause converted to text on the way in.
165
+ stringified = 0
166
+ if (buffer ~ ("(^|[^A-Za-z0-9_])" bind "[[:space:]]*\\.[[:space:]]*to_string[[:space:]]*\\(")) stringified = 1
167
+ # `format!(... bind ...)` — the same loss wearing a formatter.
168
+ if (buffer ~ /format!/ && buffer ~ ("(^|[^A-Za-z0-9_])" bind "([^A-Za-z0-9_]|$)")) {
169
+ after = buffer; sub(/^.*format!/, "", after)
170
+ if (after ~ ("(^|[^A-Za-z0-9_])" bind "([^A-Za-z0-9_]|$)")) stringified = 1
171
+ }
172
+ if (stringified) printf "LOSSY:%s:%d\n", FILENAME, startline
173
+ collecting = 0
174
+ }
175
+ ' "${FILES[@]}" || true)"
176
+
177
+ lossy_count=0
178
+ error_type_files=()
179
+ while IFS= read -r signal; do
180
+ case "$signal" in
181
+ LOSSY:*)
182
+ lossy_count=$((lossy_count + 1))
183
+ fail "${signal#LOSSY:} $RULE: map_err stringifies its own cause — to_string()/format! on the closure binding destroys the source() chain. Carry the cause with #[from] or a source field; map_err only ADDS context."
184
+ ;;
185
+ ERRTYPE:*)
186
+ error_type_files+=("${signal#ERRTYPE:}")
187
+ ;;
188
+ esac
189
+ done <<< "$signals"
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # 2. An error type with no Result alias beside it.
193
+ # ---------------------------------------------------------------------------
194
+ # Crate-scoped: the alias may live in any file of the crate that declares the
195
+ # error, so the question is asked of the crate, once, not of the file.
196
+ crate_root_of() {
197
+ local dir; dir="$(dirname "$1")"
198
+ while [[ "$dir" != "." && "$dir" != "/" ]]; do
199
+ [[ -f "$dir/$CARGO_MANIFEST" ]] && { printf '%s' "$dir"; return 0; }
200
+ dir="$(dirname "$dir")"
201
+ done
202
+ [[ -f "$CARGO_MANIFEST" ]] && { printf '.'; return 0; }
203
+ return 1
204
+ }
205
+
206
+ alias_count=0
207
+ checked_crates=""
208
+ for path in ${error_type_files[@]+"${error_type_files[@]}"}; do
209
+ crate=""
210
+ crate_root_of "$path" > /dev/null 2>&1 && crate="$(crate_root_of "$path")"
211
+ # A .rs file outside any Cargo crate has no crate to carry the alias; the
212
+ # lossy-map_err half still judged it.
213
+ [[ -n "$crate" ]] || continue
214
+ case " $checked_crates " in *" $crate "*) continue ;; esac
215
+ checked_crates="$checked_crates $crate"
216
+ if ! git ls-files --cached --others --exclude-standard -- "$crate/*.rs" \
217
+ | xargs grep -lE "$RESULT_ALIAS_PATTERN" 2>/dev/null | grep -q .; then
218
+ alias_count=$((alias_count + 1))
219
+ fail "$path $RULE: crate '$crate' declares a public Error type with no \`pub type Result<T, E = Error>\` beside it — a reader must not have to check WHICH error a signature returns."
220
+ fi
221
+ done
222
+
223
+ ok "$RULE: scanned ${#FILES[@]} runtime Rust file(s); lossy-map_err=$lossy_count missing-result-alias=$alias_count"
224
+ if [[ $FAIL -ne 0 ]]; then
225
+ printf '\n🔴 %s: blocking violations. See %s (Error discipline).\n' "$RULE" "$FACADE" >&2
226
+ exit 1
227
+ fi
228
+ exit 0
@@ -61,7 +61,7 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
61
61
  - **Skip hooks/signing.** Never `--no-verify`/`--no-gpg-sign`/`-c commit.gpgsign=false`. Hook fails → fix cause.
62
62
  - **Plaintext secrets in entity tables.** Store vault `key_name` ref, resolve via `crypto_store.load()`.
63
63
  - **Static strings in SQL schema.** No `DEFAULT 'value'`/`CHECK (col IN ('a','b'))`. Enforce in app via named constants.
64
- - **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log.
64
+ - **Resolving/printing credentials.** `op read 'op://...'` at runtime — never paste/log. Vault names come from the environment, never from a rules file.
65
65
  - **Force-push default branch** (`main`/`master`).
66
66
  - **Install-process launches in core paths.** Native SDKs. Exception: personal dev tools (`op`/`gh`/`glab`/`oracle`).
67
67
 
@@ -80,18 +80,18 @@ Auto-memory is **disabled** (`autoMemoryEnabled: false` + `CLAUDE_CODE_DISABLE_A
80
80
  ### Operational defaults
81
81
 
82
82
  - Workspace `~/Projects`. `gh`/`glab` not browsers. `trash` not `rm`. Conventional Commits. Process decisions → repo docs, not chat. "Make a note" → update `AGENTS.md`/repo docs.
83
- - **Symlinked edits land in the repository that owns the file.** A file whose `readlink` resolves outside this checkout — into a dotfiles or config repository — is an edit to *that* repository, not this one. Detect via `readlink` BEFORE editing. After: `cd` to the resolved repository, `git add <files> && git commit && git push` on its default branch. Never leave uncommitted. <!-- oracle-packs:persona.indy -->
83
+ - **Symlinked edits land in the repository that owns the file.** A file whose `readlink` resolves outside this checkout — into a dotfiles or config repository — is an edit to *that* repository, not this one. Detect via `readlink` BEFORE editing. After: `cd` to the resolved repository, `git add <files> && git commit && git push` on its default branch. Never leave uncommitted.
84
84
  - **Docs-repo edits on own branch.** `~/Projects/docs/` is shared across milestones: check `git status` first; from `main` (checkout or worktree off `main`) commit on `chore/m{N}-{slug}-changelog`; recovery = stash, re-apply on a fresh branch.
85
- - Other dotfiles (`.zshrc`/`.gitconfig`/etc.): timestamped backup; minimal edits. <!-- oracle-packs:persona.indy -->
85
+ - Other dotfiles (`.zshrc`/`.gitconfig`/etc.): timestamped backup; minimal edits.
86
+ - **Check a sibling repository for an existing pattern in the same language before inventing one.** A shape shipping next door is prior art; a new one needs a reason.
87
+ - **Read the reference implementation before designing.** Where the language's dispatch page names a canonical external source, that read is mandatory in review and its guideline identifiers are cited — applied or diverged from. "Broken for us" means the delta was missed: diff our call site against theirs before blaming the principle.
86
88
  - Before commit/push: `gitleaks` must pass.
87
89
  - No new `make` targets without a distinct caller (CI job, spec-mandated gate, or a workflow existing targets can't express) — check `make/*.mk` first; extend over near-duplicate wrappers.
88
90
  - `*.zig` → read `dispatch/write_zig.md`; ZIG GATE fires. <!-- oracle-packs:language.zig -->
89
91
  - Auth-flow (token-minting handlers, credential-typed spec dimensions, the repository's auth directories) → read the repository's `docs/AUTH.md` first, where it exists. <!-- oracle-packs:domain.auth -->
90
92
  - `conn.query()` requires `.drain()` in same fn before `deinit()`. Verify `make lint-governance`. Use `conn.exec()` for no-rows. <!-- oracle-packs:product.agentsfleet -->
91
93
  <!-- oracle-packs:start persona.indy -->
92
- - **Vault (1Password `op`).** Resolve secrets via the `op` CLI, never hand-paste/log. Vault names come from the environment, never from a rules file.
93
- - Check a sibling repository under `$HOME/Projects/` for an existing pattern in the same language before inventing one.
94
- - **Reference canon (read before designing):** TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop` + Microsoft's Rust guidelines (`oss/rust-guidelines/all.txt`, fetch if absent; mandatory in review). Missing checkout → ask, then clone into `~/Projects/oss/`. "Broken for us" → call-site diff first.
94
+ - **Indy's reference checkouts** (the machine-local half of the rule above): TypeScript → `oss/supabase/apps/studio` + `oss/supabase/packages/{ui,ui-patterns}` + `oss/cli`; Zig → `oss/ghostty/src/`; Rust → `oss/bun/src/` + `oss/exonum` + `oss/core_api-develop`. Missing checkout → ask, then clone into `~/Projects/oss/`.
95
95
  <!-- oracle-packs:end -->
96
96
 
97
97
  **Forge detection:** `github.com` → `gh`; `gitlab.com` → `glab`. Check `git remote -v`.
@@ -186,7 +186,9 @@ Non-trivial (full lifecycle) if it: touches >1 file · new abstraction · data m
186
186
 
187
187
  **With spec:** `CHORE(open) → PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT → CHORE(close)`. **Without spec** (bug fix/config/refactor): `PLAN → EXECUTE → CONFORM → VERIFY → REVIEW → DOCUMENT → COMMIT`. CHORE bookends iff work creates/continues a spec under `docs/v*/{active,pending}/`. Stage runbooks (checklists, recipes, formats): `dispatch/lifecycle.md`.
188
188
 
189
- **Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason. `orly gate` runs `work → verify → pr`, stops at the first red group, and only reads; green unlocks CHORE(close) but never performs it. No spec → spec criteria skip, quality gates still run. Slow suites (`verify.integration`, `verify.memory`) run only when the branch carries code. A user-surface change without a docs change is red until docs land or an override says why not.
189
+ **Anchor invariant — `orly gate` proves the boundary.** No PR opens unless every `orly gate pr` criterion is green or carries an `Orly-Override` trailer the user recorded with a reason.
190
+
191
+ **One gate per cadence, each tier run once.** `work` = the declared `conform` command, no git state (a commit hook's tree is dirty by construction). `verify` = spec dimensions, docs language, the fast `verify.*` set. `pr` = branch shape, clean tree, pushed branch, every spec criterion, the slow suites. `orly gate pr` is the command CHORE(close) runs; `git.pushed` proves HEAD is exactly what the pre-push `orly gate verify` already graded. A bare `orly gate` chains all three, for a branch whose earlier gates never ran. Stage map: `dispatch/lifecycle.md`. Both stop at the first red group and only read; green unlocks CHORE(close) but never performs it. No spec → spec criteria skip, quality gates still run. Slow suites (`verify.integration`, `verify.memory`) run only when the branch carries code. A user-surface change without a docs change is red until docs land or an override says why not.
190
192
 
191
193
  **LAND (after merge, or when the user confirms it merged):** pull the default branch, prune the merged worktree + branch, `make down` where defined.
192
194
 
@@ -213,16 +215,16 @@ Edit only approved scope; no opportunistic refactors. Stay in active worktree. C
213
215
 
214
216
  ### CONFORM
215
217
 
216
- Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance.
218
+ Runs after EXECUTE, before VERIFY. Invokes the `conform` commands the repository declares in `.oracle/orly.json` and aggregates every gate verdict. Any 🔴 returns to EXECUTE; the lifecycle does not advance. This is the `work` gate's tier and the pre-commit hook runs it at every commit: declare a `conform` costing seconds, not minutes.
217
219
  <!-- oracle-packs:start product.agentsfleet -->
218
220
  In `agentsfleet` this stage is `make harness-verify`; its output block and end-of-turn audit detail live in `docs/HARNESS_VERIFY_OUTPUT.md`. Required rows: FILE SHAPE, PUB GATE, LENGTH GATE, MILESTONE-ID GATE, ZIG GATE, UI GATE, DESIGN TOKEN GATE, UFS GATE, SCHEMA GUARD, GREPTILE GATE, Architecture consult, Coverage, and `/orly-write-unit-test`.
219
221
  <!-- oracle-packs:end -->
220
222
 
221
223
  ### VERIFY
222
224
 
223
- Run the repository's declared `verify.*` commands from `.oracle/orly.json`. A package-scoped command never replaces a listed one.
225
+ **Two cadences, one boundary.** Inside a Section, run CONFORM plus the declared lane covering the surface the Section touched — enough to prove a Section, reported as a Section claim. The repository's full declared `verify.*` set runs ONCE, at the milestone boundary, before the PR. A package-scoped command never replaces a listed one at either cadence, and a Section lane never satisfies a "tests pass" claim about the repository.
224
226
  <!-- oracle-packs:start product.agentsfleet -->
225
- The `agentsfleet` output block and exact tiers live in `docs/VERIFY_TIERS.md`. **FIRST: `/orly-write-unit-test`** audits diff coverage. **LAST: the Test Delta row** compares against the CHORE(open) baseline; zero or negative unit growth on a code-adding diff needs justification or a return to EXECUTE. Paste memory-leak evidence into PR Session Notes or cite its Continuous Integration (CI) URL. After refactors, list newly dead code before removing it.
227
+ The `agentsfleet` output block and both cadences live in `docs/VERIFY_TIERS.md`. **FIRST: `/orly-write-unit-test`** audits diff coverage. **LAST: the Test Delta row** compares against the CHORE(open) baseline; zero or negative unit growth on a code-adding diff needs justification or a return to EXECUTE. Paste memory-leak evidence into PR Session Notes or cite its Continuous Integration (CI) URL. After refactors, list newly dead code before removing it.
226
228
  <!-- oracle-packs:end -->
227
229
 
228
230
  ### REVIEW
@@ -250,7 +252,7 @@ Required when spec involved — after last COMMIT, before PR. Also runs when par
250
252
 
251
253
  | # | When | Skill |
252
254
  |---|---|---|
253
- | 1 | VERIFY | `/orly-write-unit-test`, then `/orly-write-integration-test` — both before the PR, never skipped, never deferred |
255
+ | 1 | VERIFY | `/orly-write-unit-test` once per Section over that Section's diff, and again at the boundary — never skipped, never deferred. Then `/orly-write-integration-test` at the boundary **when the diff crosses a module boundary with real input/output**; otherwise record `N/A — <reason>` in Session Notes. |
254
256
  | 2 | REVIEW | gstack `/review` — every runtime, same route; address findings or record a user-acked deferral; reviewer unavailable → Session Notes: *"skipped — <reason> <ts>; rerun before merge"* |
255
257
  | 3 | After every push | `orly-babysit-prs` — CI check runs + greptile inline threads + PR-level summary; stops on two consecutive empty polls with CI green; never `gh pr checks --watch` for greptile |
256
258
 
package/dispatch/lib.sh CHANGED
@@ -80,6 +80,7 @@ declare -A DISPATCH_GLOSS=(
80
80
  [LOG]="LOGging discipline (scoped event, error_code, severity, redaction)"
81
81
  [MSID]="Milestone-ID ban in source (M{N}_{NNN} / §x.y / T{N} / dim)"
82
82
  [ERR]="ERror Registry (UZ-XXX-NNN declared + referenced)"
83
+ [ERR-RS]="Rust ERror discipline (one type per crate, Result alias, no stringified cause)"
83
84
  [GRP]="GREptile rule audit (diff vs greptile-learnings/RULES.md codes)"
84
85
  [LDC]="Legacy-Design Consult (A remove / B patch / C keep)"
85
86
  )
@@ -92,13 +93,35 @@ dispatch_init() {
92
93
  DISPATCH_EXTS=("$@")
93
94
  }
94
95
 
96
+ # Paths this repository's engine wrote, one per line, from the install record.
97
+ #
98
+ # A managed file is orly's, not the repository's: the next `orly update`
99
+ # overwrites it, so a finding against one is a chore nobody can complete. Left
100
+ # unfiltered, staging a fresh install put orly's own leaf helpers in front of
101
+ # orly's own length gate — audits/logging.sh is 444 lines against a 350 cap —
102
+ # and the first commit of an adoption failed on the adoption itself.
103
+ dispatch_managed_paths() {
104
+ local config="$TARGET_ROOT/.oracle/orly.json"
105
+ [ -f "$config" ] || return 0
106
+ sed -n '/"managed"[[:space:]]*:[[:space:]]*\[/,/\]/p' "$config" \
107
+ | grep -oE '"[^"]+"' \
108
+ | sed -e 's/^"//' -e 's/"$//' \
109
+ | grep -vx 'managed' || true
110
+ }
111
+
95
112
  # Resolve targets: explicit file args, or --staged self-discovery via git.
113
+ # Explicit arguments are never filtered — naming a file is asking about it.
96
114
  dispatch_resolve_files() {
97
115
  DISPATCH_FILES=()
98
116
  if [ "${1:-}" = "--staged" ]; then
99
- local pathspecs=() e
117
+ local pathspecs=() e managed
100
118
  for e in "${DISPATCH_EXTS[@]}"; do pathspecs+=("$e"); done
101
- while IFS= read -r f; do [ -n "$f" ] && DISPATCH_FILES+=("$f"); done \
119
+ managed="$(dispatch_managed_paths)"
120
+ while IFS= read -r f; do
121
+ [ -n "$f" ] || continue
122
+ [ -n "$managed" ] && grep -qxF "$f" <<<"$managed" && continue
123
+ DISPATCH_FILES+=("$f")
124
+ done \
102
125
  < <(git -C "$TARGET_ROOT" diff --cached --name-only --diff-filter=ACMRT -- "${pathspecs[@]}" \
103
126
  | grep -vE '(^|/)(vendor|third_party|node_modules|\.zig-cache|dist|build|\.next)/' || true)
104
127
  elif [ "$#" -ge 1 ]; then
@@ -124,9 +147,11 @@ dispatch_resolve_files() {
124
147
  # dispatch must never claim a file its gates cannot parse.
125
148
  dispatch_add_shebang_files() {
126
149
  [ "${1:-}" = "--staged" ] || return 0
127
- local f base path firstline
150
+ local f base path firstline managed
151
+ managed="$(dispatch_managed_paths)"
128
152
  while IFS= read -r f; do
129
153
  [ -n "$f" ] || continue
154
+ [ -n "$managed" ] && grep -qxF "$f" <<<"$managed" && continue
130
155
  base="${f##*/}"
131
156
  case "$base" in *.*) continue ;; esac
132
157
  path="$TARGET_ROOT/$f"; [ -f "$path" ] || path="$f"; [ -f "$path" ] || continue
@@ -10,6 +10,33 @@ that moment. 🤔 judgment-only — no `.sh` pair; CONFORM's deterministic gates
10
10
  Section-scan first (`grep -n "^## " dispatch/lifecycle.md`); read the stage you
11
11
  are entering, not the whole file.
12
12
 
13
+ ## What runs at each stage
14
+
15
+ The mechanical half of the lifecycle, in one table. Everything in the "runs"
16
+ column is a command, not a claim — `orly gate` reads the repository's declared
17
+ `.oracle/orly.json` commands and never invents one.
18
+
19
+ | Stage | Runs | Fired by |
20
+ |---|---|---|
21
+ | CHORE(open) | the declared `verify.unit` once, to record `Test Baseline:` | the agent |
22
+ | PLAN | nothing — no file mutations | — |
23
+ | EXECUTE | the dispatch façade for each edited file type; DOC READ recorded via `audits/doc-read.sh log` | the agent, and the runtime's read hook where it has one |
24
+ | CONFORM | the declared `conform` command | `orly gate work` — the generated pre-commit hook |
25
+ | VERIFY (Section) | `conform` plus the declared lane covering the surface touched | the agent |
26
+ | VERIFY (milestone) | the fast `verify.*` set: lint, unit, version | `orly gate verify` — the generated pre-push hook |
27
+ | REVIEW | gstack `/review` over the diff | the agent |
28
+ | DOCUMENT | nothing mechanical | — |
29
+ | COMMIT | `conform` again, on the staged tree | the pre-commit hook |
30
+ | CHORE(close) | every spec criterion, branch shape, clean tree, pushed branch, docs surface, and the slow `verify.*` suites | `orly gate pr`, by hand, before `gh pr create` |
31
+ | LAND | nothing gated | — |
32
+
33
+ Three properties hold this together. A gate only asks what it can answer
34
+ honestly at that moment: `work` judges no git state, because a commit hook's
35
+ tree is dirty by construction and a new spec is committed on the default branch.
36
+ Each tier runs once per cadence — `conform` at commit, the fast set at push, the
37
+ slow set at the close. And `orly gate pr` can skip the fast set because
38
+ `git.pushed` proves HEAD is the commit pre-push already graded.
39
+
13
40
  ## CHORE (open) — runbook
14
41
 
15
42
  1. Spec `docs/v*/pending/` → `active/`; `Status: IN_PROGRESS`; `Branch:` set.
@@ -31,24 +31,52 @@ Package-scoped runners (`bun run test`, `vitest <file>`, `cargo test -p <crate>`
31
31
  are **not** verification — they skip every other package's lint and tests and the
32
32
  cross-language gates. The declared `make` targets are the canonical gates.
33
33
 
34
- ## Required runs
34
+ ## Two cadences, one boundary
35
+
36
+ "Always" is not a cadence. A Section closing and a milestone closing are
37
+ different claims, and running the whole declared set at both spends the
38
+ wall-clock of the comprehensive tier to answer a question the scoped lane
39
+ already answered.
40
+
41
+ The gates match those cadences: `orly gate work` runs the declared `conform`
42
+ alone (every commit, seconds), `orly gate verify` runs the fast `verify.*` set
43
+ (every push), and `orly gate pr` runs the whole-branch criteria and the slow
44
+ suites (the close). Each tier runs once per cadence.
45
+
46
+ **Section lane (per Section, and per done-claim inside one).** `make
47
+ harness-verify` — the declared `conform`, seconds — plus the lane that covers
48
+ the surface the Section touched (`make test-unit-rustd`, `make test-unit-app`,
49
+ …). This proves a Section. It is not a repository claim and never satisfies a
50
+ VERIFY row on its own.
51
+
52
+ **Milestone boundary (once, at CHORE(close), before the PR).** Every command
53
+ `.oracle/orly.json` declares, in one pass: `conform`, `verify.lint`,
54
+ `verify.unit`, `verify.version`, and the slow tier (`verify.integration`,
55
+ `verify.memory`). This is the repository claim, and it is what the done-message
56
+ below reports.
57
+
58
+ Run `orly gate pr` at the close. It does not re-run the fast tier, and it does
59
+ not need to: its own `git.pushed` criterion proves HEAD is exactly the commit
60
+ the pre-push `orly gate verify` already graded. A bare `orly gate` chains all
61
+ three gates — use it on a branch whose earlier gates never ran.
35
62
 
36
63
  | Target | When |
37
64
  |---|---|
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`. |
65
+ | `make harness-verify` | Section lane and boundary. The declared `conform` — the deterministic gate audit. |
66
+ | `make lint-all` | Boundary. The declared `verify.lint`. |
67
+ | `make test-unit-all` | Boundary. The declared `verify.unit` — the cargo workspace plus every package coverage gate. |
68
+ | `make check-version` | Boundary. The declared `verify.version`. |
69
+ | `make test-integration-rustd` | Boundary. The declared `verify.integration` — live Postgres and Redis; `orly gate pr` skips it on a branch carrying no code. |
42
70
  | `make wire-fixtures` | The `/v1/runners` wire types changed. Regenerate, then review the diff — a changed fixture IS the wire change. |
43
71
  | `make bench` (local) | Diff touches request-path code, allocator wiring, or startup/shutdown sequencing. |
44
72
  | `API_BENCH_URL=https://api-dev.agentsfleet.net/healthz make bench` | After branch deploys to dev. |
45
73
  | `/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>`. |
46
74
  | Acceptance e2e (live tier) | Diff touches a surface the live/acceptance tier covers — relevant suites green, or their opt-in skip matrix recorded. |
47
75
 
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.
76
+ The declared set in `.oracle/orly.json` is the source of truth for what exists,
77
+ and a lane listed there is a lane that runs at the boundary — including the slow
78
+ tier. A package-scoped runner (`cargo test -p afd_wire`, `bun run test` inside a
79
+ package) proves that package and never the repository, at either cadence.
52
80
 
53
81
  ## Wire-fixture evidence rule
54
82
 
@@ -70,10 +98,16 @@ generated output; a type change with no fixture diff means the emitter never ran
70
98
 
71
99
  ## Required output (done-message)
72
100
 
73
- **Success:**
101
+ **Success (boundary):**
74
102
 
75
103
  ```
76
- ✅ Verified: 🧪 lint-all ✓ · 🧪 test-unit-all ✓ <N>p/<M>s · 🔆 harness-verify ✓ · 🔆 check-version ✓
104
+ ✅ Verified: 🧪 lint-all ✓ · 🧪 test-unit-all ✓ <N>p/<M>s · 🧩 test-integration-rustd ✓ · 🔆 harness-verify ✓ · 🔆 check-version ✓
105
+ ```
106
+
107
+ **Success (Section lane)** — names the lane it ran, never the repository:
108
+
109
+ ```
110
+ ✅ Section verified: 🔆 harness-verify ✓ · 🧪 test-unit-<surface> ✓ <N>p/<M>s
77
111
  ```
78
112
 
79
113
  **Failure (any required target failed):**
@@ -22,5 +22,13 @@ a working value.
22
22
  Table-driven tests are the default shape. Concurrency work needs a `-race` run,
23
23
  and a deterministic contention test rather than a sleep.
24
24
 
25
+ A string literal repeated in one file becomes a named constant — `audits/ufs.sh`
26
+ reads `*.go` and enforces it. Two Go shapes are held out, because no rename can
27
+ fix them: members of a `const ( … )` group, which state the binding keyword once
28
+ on the opening line rather than per member, and backtick struct tags, whose
29
+ quoted halves (`json:"id"`) are addressed by reflection and cannot hold a const.
30
+ Both are carve-outs for what the *language* makes unfixable, not permission —
31
+ a flag name or a label spelled inline twice is still the rule.
32
+
25
33
  The repository owns formatting, vet, lint, build, and test commands — it
26
34
  declares them in `.oracle/orly.json`.