@open-agent-toolkit/cli 0.2.54 → 0.2.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/configuration.md +12 -2
  3. package/assets/docs/docs-tooling/commands.md +36 -5
  4. package/assets/docs/reference/oat-directory-structure.md +56 -4
  5. package/assets/docs/workflows/skills/recon.md +28 -12
  6. package/assets/public-package-versions.json +4 -4
  7. package/assets/skills/oat-dispatch-subagents/SKILL.md +4 -105
  8. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +0 -226
  9. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +2 -2
  10. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +2 -2
  11. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
  12. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
  13. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
  14. package/assets/skills/recon/SKILL.md +76 -38
  15. package/assets/skills/recon/references/packet-contract.md +138 -105
  16. package/assets/skills/recon/scripts/lib/contracts.mjs +181 -674
  17. package/assets/skills/recon/scripts/lib/validated-run.mjs +10 -16
  18. package/assets/skills/recon/scripts/render-packet.mjs +7 -20
  19. package/assets/skills/recon/scripts/validate-packet.mjs +177 -344
  20. package/dist/commands/config/index.d.ts +1 -0
  21. package/dist/commands/config/index.d.ts.map +1 -1
  22. package/dist/commands/config/index.js +51 -4
  23. package/dist/commands/docs/index-generate/generator.d.ts +43 -1
  24. package/dist/commands/docs/index-generate/generator.d.ts.map +1 -1
  25. package/dist/commands/docs/index-generate/generator.js +169 -3
  26. package/dist/commands/docs/index-generate/index.d.ts +8 -3
  27. package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
  28. package/dist/commands/docs/index-generate/index.js +340 -24
  29. package/dist/commands/docs/init/scaffold.d.ts +11 -1
  30. package/dist/commands/docs/init/scaffold.d.ts.map +1 -1
  31. package/dist/commands/docs/init/scaffold.js +11 -2
  32. package/dist/config/oat-config.d.ts +14 -0
  33. package/dist/config/oat-config.d.ts.map +1 -1
  34. package/dist/config/oat-config.js +61 -0
  35. package/dist/fs/assets.d.ts +33 -4
  36. package/dist/fs/assets.d.ts.map +1 -1
  37. package/dist/fs/assets.js +127 -12
  38. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "oatVersion": "0.2.54"
3
+ "oatVersion": "0.2.56"
4
4
  }
@@ -90,6 +90,7 @@ Common keys in `.oat/config.json`:
90
90
  - `worktrees.root` — where OAT-managed worktrees live
91
91
  - `git.defaultBranch` — base branch fallback for PR workflows
92
92
  - `documentation.root`, `documentation.tooling`, `documentation.config` — docs-surface ownership
93
+ - `documentation.excludes` — a JSON array of globs, relative to the docs directory, that `oat docs generate-index` leaves out of the generated index. `oat config set` takes the list as one comma-separated value (`"a/**,b.md"`) and stores it as an array; repeated `--exclude` flags extend it, and an empty value clears the key
93
94
  - `documentation.requireForProjectCompletion` — whether docs sync is a completion gate
94
95
  - `archive.s3Uri` — base S3 archive prefix
95
96
  - `archive.s3SyncOnComplete` — upload archived projects to S3 during completion
@@ -242,8 +243,17 @@ packaged `assets/` directory next to the installed CLI. Setting a non-empty
242
243
  directory.
243
244
  - The override is validated exactly like the packaged root and fails closed:
244
245
  the path must be a directory containing bundle metadata whose version matches
245
- the running CLI (`OAT_VERSION`). A missing, malformed, or version-mismatched
246
- override is an error — the CLI never silently falls back to packaged assets.
246
+ the running CLI (`OAT_VERSION`) **and** the seven top-level directories the
247
+ bundle producer creates (`skills`, `agents`, `templates`, `scripts`, `docs`,
248
+ `migration`, `config`), each of which must be a directory. A missing,
249
+ malformed, version-mismatched, or structurally incomplete override is an
250
+ error with exit code 2 that names the first offending path — the CLI never
251
+ silently falls back to packaged assets, and a metadata-only directory is no
252
+ longer accepted as an empty installation.
253
+ - Remedies are source-aware: an `OAT_ASSETS_DIR` failure tells you to fix or
254
+ unset the override, never to rebuild or reinstall the CLI; a packaged-bundle
255
+ failure keeps the rebuild/reinstall guidance. A directory that exists but
256
+ cannot be read reports the underlying error code.
247
257
  - Produce a matching bundle with `bash packages/cli/scripts/bundle-assets.sh`
248
258
  while `OAT_ASSETS_DIR` points at the target directory (the script already
249
259
  honors the variable as its destination).
@@ -128,23 +128,54 @@ Key behavior:
128
128
  - recursively walks the docs directory
129
129
  - extracts page titles from frontmatter (falls back to first `# heading`, then filename title-case)
130
130
  - includes descriptions from frontmatter when present
131
- - outputs to app root (`index.md`) by default, not inside the docs directory
132
- - updates `documentation.index` in `.oat/config.json`
131
+ - resolves omitted paths from `.oat/config.json` rather than the current directory: `--docs-dir` defaults to `<documentation.root>/docs` when that directory exists and to `<documentation.root>` otherwise, and `--output` defaults to the app-root manifest `<documentation.root>/index.md`
132
+ - treats explicitly supplied `--docs-dir` / `--output` paths as overrides resolved from the current directory
133
+ - fails with exit code `2` before generating or writing anything when an omitted path has no non-empty `documentation.root` to resolve against, or when the configured root is not a directory
134
+ - refuses unsafe output targets with exit code `1` before generation, for derived and explicit paths alike, comparing symlink-resolved paths: inside the docs directory it indexes, equal to `documentation.config`, or ending in `.yml` / `.yaml` in any case. A derived output whose existing file lacks the `AUTOGENERATED` header is refused too; naming that path with `--output` overwrites it explicitly. A refused target is an actionable flag error (`1`); unusable path configuration is a separate condition and exits `2`, so scripts can branch on the two
135
+ - writes configuration only for the Fumadocs manifest transition — recording the manifest it just wrote inside `documentation.root` in `documentation.index`, when `documentation.tooling` is `fumadocs` or the config declares neither `tooling` nor `config`. MkDocs configurations are never written, and `documentation.root` and `documentation.config` are never modified
133
136
  - prepends an `AUTOGENERATED` warning comment to the output and rewrites the file on every run; do not hand-edit the generated `index.md`
134
137
  - should be freshness-checked against authored `docs/**/index.md` `## Contents` maps before treating it as navigation evidence
135
138
  - sorting: `index.md` first, then directories before files, then lexical
139
+ - reports the derived docs directory in human output and as `docsDir` / `docsDirSource` under `--json`
140
+ - omits pages matching the exclusion list. Nothing is excluded by default; once listed, non-page Markdown such as `CLAUDE.md` or `AGENTS.md` stops reaching the manifest. Patterns come from `documentation.excludes` in `.oat/config.json` and from repeated `--exclude` flags, and the flags **extend** the configured list rather than replacing it. A directory left empty by exclusion emits no heading, and an empty list produces byte-identical output to no exclusions at all
136
141
 
137
142
  Supported flags:
138
143
 
139
- - `--docs-dir <path>` (default: `docs`)
140
- - `--output <path>` (default: `index.md` at app root)
144
+ - `--docs-dir <path>` (default: `<documentation.root>/docs`, falling back to `<documentation.root>`)
145
+ - `--output <path>` (default: `<documentation.root>/index.md`)
146
+ - `--exclude <glob>` (repeatable; additive to `documentation.excludes`)
147
+
148
+ ### Exclusion patterns
149
+
150
+ Patterns match the path of each candidate **relative to the docs directory being indexed** — `api/auth.md`, `api/nested` — never an absolute or current-directory-relative path, and never the repository-relative path.
151
+
152
+ | Pattern | Matches |
153
+ | -------------- | -------------------------------------------------------- |
154
+ | `CLAUDE.md` | only the root-level `CLAUDE.md`; patterns are anchored |
155
+ | `**/CLAUDE.md` | `CLAUDE.md` at any depth, including the docs root |
156
+ | `*.md` | root-level Markdown only; `*` never crosses `/` |
157
+ | `drafts/` | the `drafts` directory and everything beneath it |
158
+ | `drafts` | the same directory; the trailing `/` only forbids a file |
159
+ | `api/**/*.md` | Markdown at any depth under `api/`, including `api/x.md` |
160
+
161
+ A trailing `/` restricts a pattern to directories and never matches a file; without it, a pattern that matches a directory path still prunes that directory. `**` spans `/` only as a whole path segment — inside a segment (`a**b`) it is an ordinary single-segment wildcard. Matching is case-sensitive, `/` is the separator on every platform, and only `*` and `**` are metacharacters — every other character, `.` included, is literal. A leading `./` or `/` is stripped, so both spellings anchor at the docs root.
141
162
 
142
163
  Example:
143
164
 
144
165
  ```bash
145
- oat docs generate-index --docs-dir docs
166
+ # Resolve both paths from .oat/config.json
167
+ oat docs generate-index
168
+
169
+ # Portable explicit form, resolved from the current directory
170
+ oat docs generate-index --docs-dir apps/oat-docs/docs --output apps/oat-docs/index.md
171
+
172
+ # Persist the repository's own exclusions, then add a one-off
173
+ oat config set documentation.excludes "**/CLAUDE.md,**/AGENTS.md"
174
+ oat docs generate-index --exclude 'drafts/'
146
175
  ```
147
176
 
177
+ See [Documentation path resolution](../reference/oat-directory-structure.md#documentation-path-resolution) for the app-root meaning of `documentation.root` and the legacy source-root compatibility rule.
178
+
148
179
  The Fumadocs scaffold runs this automatically via `predev`/`prebuild` npm
149
180
  script hooks.
150
181
 
@@ -117,10 +117,11 @@ Current schema keys:
117
117
  | `projects.root` | `string` | `".oat/projects/shared"` | Default root directory for OAT projects |
118
118
  | `projects.defaultScope` | `string` | `"synced"` | Scope used by project creation when `--scope` is omitted: `shared`, `local`, or `synced`. Environment override: `OAT_PROJECTS_DEFAULT_SCOPE`. |
119
119
  | `localPaths` | `string[]` | - | Gitignored directories to sync between main repo and worktrees. Supports glob patterns. Managed via `oat local add/remove`. |
120
- | `documentation.root` | `string` | - | Root directory containing documentation source files (e.g., `apps/docs/docs`) |
120
+ | `documentation.root` | `string` | - | Docs **app root** — the directory `oat docs init` scaffolds (e.g., `apps/docs`). Authored pages live under `<root>/docs`. See [Documentation path resolution](#documentation-path-resolution). |
121
121
  | `documentation.tooling` | `string` | - | Documentation framework identifier (`mkdocs` or `fumadocs`) |
122
122
  | `documentation.config` | `string` | - | Path to the documentation framework config file (e.g., `mkdocs.yml`, `next.config.js`) |
123
- | `documentation.index` | `string` | - | Path to the docs surface entry point (e.g., `index.md` for Fumadocs, `mkdocs.yml` for MkDocs). Set by `oat docs init` and updated by `oat docs generate-index`. |
123
+ | `documentation.index` | `string` | - | Path to the docs surface entry point: the generated app-root manifest `<root>/index.md` for Fumadocs, `mkdocs.yml` for MkDocs. Set by `oat docs init`. `oat docs generate-index` writes it only for the Fumadocs manifest transition and never for MkDocs. |
124
+ | `documentation.excludes` | `string[]` | - | Globs, relative to the docs directory, that `oat docs generate-index` leaves out of the generated index. See [Excluding pages from the generated index](#excluding-pages-from-the-generated-index). |
124
125
  | `documentation.requireForProjectCompletion` | `boolean` | `false` | When `true`, OAT project completion gates require documentation to be updated |
125
126
  | `git.defaultBranch` | `string` | `"main"` | Default branch for PR creation. Auto-detected during `oat init` via `gh repo view` or `origin/HEAD`. Used by `oat-project-pr-final` and `oat-project-pr-progress`. |
126
127
  | `workflow.autoReviewAtHillCheckpoints` | `boolean` | unset | When `true`, completing a HiLL checkpoint automatically runs the extra lifecycle review. Does not control Tier 1 per-phase `oat-reviewer` gates. Can be overridden per-project via `oat_auto_review_at_hill_checkpoints` in `plan.md` frontmatter. Legacy `autoReviewAtCheckpoints` remains a fallback. |
@@ -157,14 +158,65 @@ Example:
157
158
  "root": ".worktrees"
158
159
  },
159
160
  "documentation": {
160
- "root": "apps/docs/docs",
161
+ "root": "apps/docs",
161
162
  "tooling": "mkdocs",
162
- "config": "mkdocs.yml",
163
+ "config": "apps/docs/mkdocs.yml",
164
+ "index": "apps/docs/mkdocs.yml",
163
165
  "requireForProjectCompletion": false
164
166
  }
165
167
  }
166
168
  ```
167
169
 
170
+ ### Documentation path resolution
171
+
172
+ `documentation.root` canonically names the docs **app root** — exactly the directory `oat docs init` scaffolds. Authored Markdown lives under `<root>/docs`, and the generated Fumadocs manifest is written to `<root>/index.md`, outside the tree it indexes.
173
+
174
+ `oat docs generate-index` resolves omitted options from this configuration:
175
+
176
+ | Option | Omitted | Supplied |
177
+ | ------------ | ---------------------------------------------------------------------------------------- | ----------------------------------- |
178
+ | `--docs-dir` | `<documentation.root>/docs` when that directory exists, otherwise `<documentation.root>` | Resolved from the current directory |
179
+ | `--output` | `<documentation.root>/index.md` | Resolved from the current directory |
180
+
181
+ If an omitted option has no non-empty `documentation.root` to resolve against, or the configured root is not a directory, the command fails with exit code `2` before generating or writing anything.
182
+
183
+ **Compatibility behavior.** Preferring `<root>/docs` exists for legacy configs whose `root` names a docs _source_ directory rather than an app root; it is not a second meaning of the key. Because the rule is a plain directory-existence test, a source root that happens to contain its own `docs` subdirectory is narrowed to it. The command prints the directory it derived in both human and JSON output (`docsDir`, `docsDirSource`), and `--docs-dir` is the escape hatch when the derivation is wrong.
184
+
185
+ A legacy source root with no `docs` child resolves `--docs-dir` and `--output` to the same tree, so a bare run is refused rather than allowed to overwrite an authored page. Pass an explicit `--output` outside the source tree, or repoint `documentation.root` at the app root.
186
+
187
+ **Refused output targets.** Checked before generation for derived and explicit paths alike, the command refuses to write an output that is inside the docs directory it indexes, equals `documentation.config`, or ends in `.yml` / `.yaml`. A derived output whose existing file lacks the `AUTOGENERATED by oat docs generate-index` header is also refused; naming that same path with `--output` overwrites it explicitly.
188
+
189
+ **Configuration writes.** The only configuration write generation performs is the Fumadocs manifest transition: when the written manifest lies inside `documentation.root` and `documentation.tooling` is `fumadocs`, the repo-relative output path is recorded in `documentation.index`. A config that declares neither `documentation.tooling` nor `documentation.config` is treated as the same Fumadocs-shaped case. Any other declared tooling — `mkdocs` included — is never written, and neither are `documentation.root` and `documentation.config`. The value is always repo-relative: when `documentation.root` points outside the repository, the manifest is still generated but the key is left untouched rather than recording a machine-specific path.
190
+
191
+ ### Excluding pages from the generated index
192
+
193
+ `documentation.excludes` and the repeatable `--exclude <glob>` flag feed one matcher. Flags **extend** the configured list rather than replacing it, so a one-off exclusion cannot silently republish pages the repository deliberately excluded. A directory left empty by exclusion emits no heading, and an empty list produces byte-identical output to no exclusions at all.
194
+
195
+ Patterns are matched against each candidate's path **relative to the docs directory being indexed** — the directory `--docs-dir` resolved to, not `documentation.root` and not the repository root.
196
+
197
+ | Pattern | Matches |
198
+ | -------------- | -------------------------------------------------------- |
199
+ | `CLAUDE.md` | only the root-level `CLAUDE.md`; patterns are anchored |
200
+ | `**/CLAUDE.md` | `CLAUDE.md` at any depth, including the docs root |
201
+ | `*.md` | root-level Markdown only; `*` never crosses `/` |
202
+ | `drafts/` | the `drafts` directory and everything beneath it |
203
+ | `api/**/*.md` | Markdown at any depth under `api/`, including `api/x.md` |
204
+
205
+ A trailing `/` restricts a pattern to directories and never matches a file; without it, a pattern that matches a directory path still prunes that directory. `**` spans `/` only as a whole path segment — inside a segment (`a**b`) it is an ordinary single-segment wildcard. Matching is case-sensitive, `/` is the separator on every platform, and only `*` and `**` are metacharacters — every other character, `.` included, is literal. A leading `./` or `/` is stripped, so both spellings anchor at the docs root. Entries are trimmed and de-duplicated; a malformed `documentation.excludes` (a non-array, or an empty or non-string entry) is rejected rather than silently ignored. `oat docs generate-index` reports it with exit code `2`; `oat config` surfaces the same error as an ordinary exit-`1` user error. `oat config set documentation.excludes` can still replace or clear a malformed value, so the repair the error names always works.
206
+
207
+ Set the list with the comma-separated grammar, and clear it with an empty value:
208
+
209
+ ```bash
210
+ oat config set documentation.excludes "**/CLAUDE.md,**/AGENTS.md"
211
+ oat config set documentation.excludes ""
212
+ ```
213
+
214
+ This repository pins the invocation explicitly, which is the portable fallback anywhere the configured defaults are not what you want:
215
+
216
+ ```bash
217
+ oat docs generate-index --docs-dir apps/oat-docs/docs --output apps/oat-docs/index.md
218
+ ```
219
+
168
220
  ### Worktree root precedence
169
221
 
170
222
  When resolving the worktree root directory, `oat-worktree-bootstrap` uses this strict precedence (stops at the first match):
@@ -70,10 +70,17 @@ maximum concurrency; they do not silently choose a different model tier.
70
70
  | `thorough` | Expensive failure or correlated blind spots | Standard work plus redundant gathering and verification and contradiction resolution | `verified` |
71
71
 
72
72
  The approval manifest shows the exact adaptive lane count, concurrency, pass
73
- topology, conditional lane caps, deadlines, retries, and source/write authority.
74
- Declining approval launches nothing. A material change to a provider, model,
75
- effort, route, role, service tier, or execution cap requires renewed approval;
76
- there is no silent substitution after approval.
73
+ topology, deadlines, retries, and source/write authority. Declining approval
74
+ launches nothing. A change to a provider, model, effort, route, role, service
75
+ tier, or execution cap requires renewed approval; there is no silent
76
+ substitution after approval.
77
+
78
+ Before any worker launches, recon confirms that the live launch surface can run
79
+ the approved role, model, effort, and authority level. If it cannot, the run
80
+ stays at `awaiting-approval` with a provider/dispatch diagnostic and nothing is
81
+ launched. Deadlines are approved execution limits chosen for the task class,
82
+ not short watchdogs; an accepted lane is never interrupted by hand because a
83
+ displayed deadline elapsed.
77
84
 
78
85
  ## Destination Precedence
79
86
 
@@ -105,12 +112,12 @@ non-publishable candidate generation withdraws any existing `packet.md`:
105
112
 
106
113
  - `packet.md` is the compact consumer view and is published last.
107
114
  - `claims.json` is the canonical claim ledger.
108
- - `manifest.json` records request, source, dispatch, topology, stage, and gap
109
- provenance.
110
- - `reviews/` contains compact locator, semantic, adversarial, coverage, and
115
+ - `manifest.json` records request, source, approved execution envelope, and
116
+ gap provenance.
117
+ - `reviews/` contains compact semantic, adversarial, coverage, and
111
118
  reconciliation evidence.
112
- - `raw/` contains worker dossiers, dispatch receipts, candidate artifacts, and
113
- safe failure diagnostics. It is not normal consumer input.
119
+ - `raw/` contains worker dossiers, candidate artifacts, and safe failure
120
+ diagnostics. It is not normal consumer input.
114
121
 
115
122
  The ledger compiler and packet validator enforce categorical referential
116
123
  integrity on `synthesis.keyClaimIds` and `synthesis.unresolvedQuestionIds`. Any
@@ -137,7 +144,8 @@ percentages:
137
144
  - `unsupported`: valid supporting evidence is absent or verification failed.
138
145
 
139
146
  A quick packet never promotes a claim to `verified`. Stronger profiles can do
140
- so only when their required review artifacts and receipts validate.
147
+ so only when their required review artifacts validate and were written by
148
+ approved lanes.
141
149
 
142
150
  When an independent semantic review rejects a proposed claim, the reconciler
143
151
  transitions it to `unsupported` rather than deleting it. This preserves the
@@ -179,7 +187,15 @@ A structurally valid run can publish an honest `partial` packet. The manifest
179
187
  and `packet.md` then identify the requested and achieved profiles, failed or
180
188
  omitted passes, material gaps, affected claims, and required assurance
181
189
  downgrades. A run may be partial even when it achieved the requested profile if
182
- a material evidence gap remains.
190
+ a material evidence gap remains. The achieved profile is derived from the
191
+ complete typed artifacts in the packet; each required pass without a complete
192
+ result needs a material `PASS_FAILED` or `PASS_OMITTED` gap naming it.
193
+
194
+ Status updates and the final handoff label each failure as `worker`,
195
+ `provider/dispatch`, `contract validation`, or `source availability`, and
196
+ say whether every accepted lane reached a terminal result. Workers that
197
+ completed are never reported as failed because the controller could not
198
+ publish.
183
199
 
184
200
  Before assurance or publication, candidate runs are normalized into a deeply
185
201
  immutable `ValidatedRun`. To prevent premature or invalid promotion,
@@ -189,6 +205,6 @@ runs (`failed`, `running`, `preparing`, `awaiting-approval`) withdraw any
189
205
  existing `packet.md` under the unchanged-root guard, leave no `ValidatedRun`,
190
206
  and cause the exported renderer to fail closed with `PACKET_NOT_PUBLISHABLE`.
191
207
 
192
- If the manifest, ledger, topology, receipts, source identities, or publication
208
+ If the manifest, ledger, approval envelope, source identities, or publication
193
209
  boundary cannot be validated, the run is `failed`. It may retain safe raw
194
210
  diagnostics, but it does not publish a misleading `packet.md`.
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.2.54",
3
- "docs-config": "0.2.54",
4
- "docs-theme": "0.2.54",
5
- "docs-transforms": "0.2.54"
2
+ "cli": "0.2.56",
3
+ "docs-config": "0.2.56",
4
+ "docs-theme": "0.2.56",
5
+ "docs-transforms": "0.2.56"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-dispatch-subagents
3
- version: 1.2.5
3
+ version: 1.2.6
4
4
  description: Use when an OAT skill or workflow needs provider-neutral selection, launch, recovery, or evidence for bounded subagent work without project lifecycle policy.
5
5
  disable-model-invocation: true
6
6
  user-invocable: false
@@ -129,11 +129,6 @@ decomposition.
129
129
  The optional policy and ceiling are already-resolved inputs. Do not infer where
130
130
  they came from or resolve project state to obtain them.
131
131
 
132
- The optional `operation` is `prepare`, `execute`, or `dispatch`. `prepare` and
133
- `execute` form the approval-bound two-step path below. When `operation` is
134
- omitted, treat it as `operation: dispatch` and preserve the existing one-step
135
- selection-and-launch path for backwards compatibility.
136
-
137
132
  ## Native Dispatch Lineage
138
133
 
139
134
  For a project-aware dispatch, the calling workflow records native dispatch
@@ -375,99 +370,9 @@ Before explicit selection:
375
370
  Do not launch a diagnostic child solely to obtain a catalog the provider
376
371
  cannot expose before selection. Record the visibility timing instead.
377
372
 
378
- ## Approval-Bound Preparation and Execution
379
-
380
- Use this path when a caller must show the exact selection and execution
381
- envelope for approval before any child starts. The caller still owns the user
382
- interaction; this skill produces and validates the approval evidence.
383
-
384
- ### `operation: prepare`
385
-
386
- Preparation is selection-only. It must observe the live catalog in the exact
387
- dispatch context and return a complete prepared dispatch record without
388
- launching, reserving capacity, or obtaining a child handle.
389
-
390
- Before selecting a target:
391
-
392
- 1. Classify all planned and conditional waves before selection. Record every
393
- wave's `task_class` and `model_class_floor`.
394
- 2. Compute the run-wide maximum model-class floor using the durable task-class
395
- order. Conditional waves count even when the caller may later skip them.
396
- 3. Select one exact target at or above that maximum floor and pin it across all
397
- prepared waves. The pin includes provider, dispatch context, route, role,
398
- model and selector granularity, effort, reasoning mode, and service tier.
399
- Do not weaken a wave floor, split the target, or silently substitute a
400
- cheaper target. If one target cannot satisfy every wave, return a blocked
401
- preparation.
402
- 4. Materialize each wave's exact authority, deadline, retry limit,
403
- concurrency, lane cap, scopes, lane IDs, context controls, fallback, and
404
- canonical redacted payload digest. Record the catalog observation identity
405
- and the fingerprint of the relevant selectable facts.
406
- 5. Construct the canonical approval projection defined in
407
- `references/record-schema.md`, return `dispatch_state: prepared`, and stop.
408
-
409
- The calling workflow may combine prepared wave records into a run manifest,
410
- but it must preserve the run ID, maximum floor, pinned target, per-wave axes,
411
- canonical approval fingerprint, and prepared timestamp byte-for-byte.
412
-
413
- ### Approval
414
-
415
- The caller presents the complete approval projection and records explicit
416
- approval by changing only `dispatch_state`, `approved_at`, and the approval
417
- evidence fields allowed by the schema. Approval is valid only for the exact
418
- canonical fingerprint. Editing any bound axis creates a new preparation; it
419
- does not update an already approved record in place.
420
-
421
- ### `operation: execute`
422
-
423
- Execution accepts one approved prepared record. Before invoking the launcher:
424
-
425
- 1. Validate the prepared-record version, legal state, approval timestamp, and
426
- approval fingerprint against the unchanged canonical projection.
427
- 2. Re-observe the live catalog in the same dispatch context and compare the
428
- current relevant selectable facts with the prepared observation. A new
429
- unrelated candidate alone is not relevant drift; removal, renaming,
430
- semantic change, or loss of selectability for an approved control is.
431
- 3. Re-resolve the actual launch controls without choosing alternatives and
432
- compare every approval-bound axis exactly. Provider-native opaque values
433
- stay byte-for-byte exact.
434
- 4. Refuse launch and mark the record `stale` when any comparison differs. The
435
- caller must run `prepare` again and obtain reapproval.
436
- 5. Launch the approved payload once. Record `accepted` only from positive
437
- launcher acceptance; otherwise record `not-accepted`.
438
-
439
- A model change is model drift. An effort change is effort drift. A provider
440
- change is provider drift. A route change is route drift. A role change is role
441
- drift. A service tier change is service tier drift. An authority change is
442
- authority drift. A deadline change is deadline drift. A concurrency change is
443
- concurrency drift. A lane cap change is lane cap drift. A relevant catalog
444
- change is catalog drift. The same fail-closed rule covers reasoning mode,
445
- selector granularity, retry limit, writable roots, context controls, fallback,
446
- wave topology, scope, lane identity, payload digest, floor, or pinned-target
447
- drift. Never adjust one of these controls to make execution fit the current
448
- surface.
449
-
450
- Launch acceptance remains terminal for replacement eligibility. After a
451
- prepared launch is accepted, no alternate route, replacement, or target
452
- substitution is allowed. Only continuation through the already accepted handle
453
- is permitted. A linked fresh same-target launch is forbidden, regardless of
454
- the generic caller lifecycle recovery exception. If that accepted handle is
455
- lost or cannot resume, record the terminal outcome and stop; the approval-bound
456
- record never authorizes a second child.
457
-
458
- ### One-Step Backwards Compatibility
459
-
460
- Existing callers may omit `operation` or provide `operation: dispatch`. This
461
- retains exactly the current one-step selection-and-launch flow in
462
- Full-Information Selection: observe, select, record, and launch once in the
463
- same operation. Do not require those callers to invent a prepared record,
464
- approval timestamp, or approval fingerprint. New approval-bound callers must
465
- use `prepare` and `execute`; they may not collapse that path into `dispatch`.
466
-
467
373
  ## Full-Information Selection
468
374
 
469
- For every legacy one-step dispatch and for the selection portion of
470
- `operation: prepare`:
375
+ For every dispatch:
471
376
 
472
377
  1. Validate the caller request and capability state.
473
378
  2. Resolve provider, context, role class, optional task class and model-class
@@ -482,14 +387,8 @@ For every legacy one-step dispatch and for the selection portion of
482
387
  7. Record route, selection source, selection reason, candidates, catalog
483
388
  source, model, effort, reasoning mode, service tier, guidance version,
484
389
  authority, and deadline.
485
- 8. For `operation: prepare`, construct the prepared record and return without
486
- launch. Do not continue into the legacy launch steps.
487
- 9. For a legacy one-step dispatch only, launch once.
488
- 10. Record launch acceptance separately from child outcome and runtime identity.
489
-
490
- The following diagram is the legacy one-step flow. Approval-bound preparation
491
- stops after selection and record construction; its later `execute` operation
492
- performs the separately approved launch.
390
+ 8. Launch once.
391
+ 9. Record launch acceptance separately from child outcome and runtime identity.
493
392
 
494
393
  ```mermaid
495
394
  flowchart TD
@@ -69,232 +69,6 @@ retain the original role-based selection and fallback behavior. The legacy
69
69
  `explicit-downgrade` example above is valid only for an unconstrained request
70
70
  without task-class metadata or a declared class floor.
71
71
 
72
- ## Approval-Bound Prepared Record
73
-
74
- Approval-bound records use `prepared_record_version: 1`. They extend the
75
- complete Record below: every baseline request, selection, route, payload,
76
- fallback, diagnostic, and invocation-evidence field remains required when it
77
- applies. Preparation fills all launch controls but performs no launch.
78
-
79
- ```yaml
80
- prepared_record_version: 1
81
- operation: prepare
82
- dispatch_state: prepared
83
- approval_projection: <exact Canonical Approval Projection v1 object below>
84
- approval_canonical_json: <RFC-8785-JSON-text>
85
- approval_fingerprint: sha256:<64-lowercase-hex>
86
- approved_at: null
87
- approval_evidence: null
88
- launch_acceptance: null
89
- terminal_outcome: null
90
- ```
91
-
92
- ### Canonical Approval Projection v1
93
-
94
- `approval_projection` is exactly the object shape below. Its object keys and
95
- nested structure are immutable for `prepared_record_version: 1`; missing or
96
- extra keys are invalid. The complete ordered `execution.waves` array and every
97
- ordered wave-local `lanes` array are values in the projection, not paths to be
98
- looked up elsewhere. Any future field or aggregation change requires a new
99
- prepared-record version. Scalar values and example array lengths below are
100
- illustrative; field names, nesting, value types, and each wave and lane element
101
- shape are normative. Wave and lane arrays may vary in length only within their
102
- approved caps.
103
-
104
- ```json
105
- {
106
- "schema": "oat-dispatch-approval/v1",
107
- "prepared_record_version": 1,
108
- "run_id": "recon-run-2026-07-12-001",
109
- "prepared_at": "2026-07-12T00:00:00Z",
110
- "request": {
111
- "request_id": "dispatch-recon-001",
112
- "caller": "recon",
113
- "objective": "Gather bounded evidence",
114
- "action": "analysis",
115
- "expected_output": "versioned-dossiers",
116
- "verification_evidence": "artifact-digests",
117
- "escalate_when": ["approved scope is insufficient"]
118
- },
119
- "selection": {
120
- "provider": "codex",
121
- "dispatch_context": "nested-native",
122
- "dispatch_policy": "economy",
123
- "dispatch_ceiling": "high",
124
- "selected_route": "native",
125
- "selection_source": "native-default",
126
- "candidates_considered": ["exact-provider-model"],
127
- "selection_reason": "native-catalog",
128
- "role_name": "recon-worker",
129
- "role_class": "recon",
130
- "role_selector": "recon-worker",
131
- "model_selector": "exact-provider-model",
132
- "model_selector_granularity": "exact-native-model-choice",
133
- "effort_selector": "high",
134
- "reasoning_mode_selector": null,
135
- "service_tier_selector": "standard",
136
- "guidance_reference": "subagent-orchestration/references/provider-codex.md",
137
- "guidance_version": "2026-07-21",
138
- "guidance_verified_at": "2026-07-21",
139
- "guidance_status": "fresh"
140
- },
141
- "execution": {
142
- "waves": [
143
- {
144
- "wave_id": "gathering",
145
- "conditional": false,
146
- "task_class": "intelligent-recon",
147
- "model_class_floor": "intelligent-recon",
148
- "scope": "repo:packages/cli",
149
- "lanes": [
150
- {
151
- "lane_id": "commands",
152
- "scope": "packages/cli/src/commands"
153
- },
154
- {
155
- "lane_id": "engine",
156
- "scope": "packages/cli/src/engine"
157
- }
158
- ],
159
- "authority": "read-only",
160
- "authorization_scope": "this-recon-run",
161
- "writable_roots": ["packet/raw/dossiers/gathering"],
162
- "deadline_seconds": 300,
163
- "retry_limit": 1,
164
- "fallback": { "mode": "block" },
165
- "dispatch_mode": "background",
166
- "context_fork_controls": { "fork_turns": "all" },
167
- "concurrency": 2,
168
- "lane_cap": 4,
169
- "payload_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
170
- },
171
- {
172
- "wave_id": "contradiction-resolution",
173
- "conditional": true,
174
- "task_class": "hard-reasoning",
175
- "model_class_floor": "hard-reasoning",
176
- "scope": "packet:claims",
177
- "lanes": [
178
- {
179
- "lane_id": "reconcile",
180
- "scope": "packet/reviews"
181
- }
182
- ],
183
- "authority": "read-only",
184
- "authorization_scope": "this-recon-run",
185
- "writable_roots": ["packet/reviews/reconciliation"],
186
- "deadline_seconds": 300,
187
- "retry_limit": 0,
188
- "fallback": { "mode": "block" },
189
- "dispatch_mode": "background",
190
- "context_fork_controls": { "fork_turns": "all" },
191
- "concurrency": 1,
192
- "lane_cap": 1,
193
- "payload_digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111"
194
- }
195
- ],
196
- "run_maximum_floor": "hard-reasoning",
197
- "pinned_target": {
198
- "provider": "codex",
199
- "dispatch_context": "nested-native",
200
- "selected_route": "native",
201
- "role_selector": "recon-worker",
202
- "model_selector": "exact-provider-model",
203
- "model_selector_granularity": "exact-native-model-choice",
204
- "effort_selector": "high",
205
- "reasoning_mode_selector": null,
206
- "service_tier_selector": "standard"
207
- }
208
- },
209
- "catalog_observation": {
210
- "id": "nested-native-2026-07-12-001",
211
- "source": "tool-schema",
212
- "dispatch_context": "nested-native",
213
- "observed_at": "2026-07-12T00:00:00Z",
214
- "relevant_catalog_fingerprint": "sha256:2222222222222222222222222222222222222222222222222222222222222222"
215
- }
216
- }
217
- ```
218
-
219
- The values in the complete Record and launch payload must exactly match their
220
- counterparts in `approval_projection`; a mismatch is invalid rather than an
221
- implicit override. `execution.waves` lists every planned and conditional wave
222
- before selection. Each wave records its caller-classified task class, equal
223
- model-class floor, conditionality, ordered lane identities and scopes, and all
224
- wave-local execution controls. Compute `run_maximum_floor` using this increasing
225
- order: `mechanical-recon`, `intelligent-recon`, `default-implementation`,
226
- `hard-reasoning`, `consequential`. `pinned_target` is one exact target shared by
227
- every prepared wave and must satisfy the maximum; per-wave selection or floor
228
- weakening is invalid.
229
-
230
- Absent optional values in the projection are JSON `null`. Preserve caller order
231
- for waves and lanes; for sets such as writable roots, require the caller to
232
- provide a stable sorted array. Do not normalize opaque provider values.
233
- Canonicalize the whole `approval_projection` as canonical JSON using RFC 8785,
234
- encode that text as UTF-8, hash it with SHA-256, format lowercase hexadecimal,
235
- and prefix the value with `sha256:`. `payload_digest` is the same construction
236
- over that wave's complete redacted launch payload. The approval timestamp is
237
- `approved_at`; `prepared_at` records when selection evidence was fixed.
238
-
239
- `catalog_observation` identifies the original source, dispatch context, and
240
- observation time. Its `relevant_catalog_fingerprint` covers the approved
241
- target's selectability plus the role, model, effort, reasoning-mode, service-
242
- tier, route, and launcher controls used to qualify it. Execution preserves the
243
- original observation and separately records its fresh recheck. A new unrelated
244
- candidate is not drift, but removal, renaming, changed semantics, or loss of
245
- selectability for an approved control is relevant catalog drift and makes the
246
- record stale.
247
-
248
- ### Legal State Transitions
249
-
250
- - `prepared -> approved`: the caller explicitly approves the exact
251
- `approval_fingerprint`; set `approved_at` and immutable approval evidence.
252
- - `prepared -> not-accepted`: the caller declines or cancels before approval.
253
- - `prepared -> stale`: a bound axis or relevant catalog fact changes before
254
- approval is recorded.
255
- - `approved -> accepted`: `operation: execute` verifies the unchanged
256
- fingerprint and current relevant catalog, invokes the exact payload once,
257
- and receives positive launcher acceptance.
258
- - `approved -> not-accepted`: the exact launch is rejected before child start.
259
- - `approved -> stale`: fingerprint validation, an approval-bound axis, or the
260
- relevant catalog comparison differs before launch.
261
- - `accepted -> completed`: record exactly one terminal child outcome, including
262
- success, failure, timeout, interruption, `BLOCKED`, or contract refusal.
263
-
264
- `completed`, `not-accepted`, and `stale` are terminal record states. There is
265
- no transition from them back to `approved` or `accepted`; prepare a new record
266
- and obtain a new approval. `accepted` is terminal for replacement eligibility
267
- even before the child outcome changes the record to `completed`. An accepted
268
- record must not become `not-accepted` or `stale`, and must never authorize a
269
- replacement, alternate route, model substitution, provider substitution, or
270
- second child. Only continuation through the already accepted handle is
271
- permitted. A linked fresh same-target launch is forbidden, regardless of the
272
- generic lifecycle recovery exception. Handle loss or inability to resume is a
273
- terminal stop for this approval-bound record; generic recovery remains
274
- unchanged for non-approval-bound dispatches.
275
-
276
- ### Execute Validation and Drift
277
-
278
- `operation: execute` accepts only `dispatch_state: approved`. First recompute
279
- the approval fingerprint from the stored projection to detect mutation. Then
280
- re-observe the same live catalog and resolve the actual launch controls without
281
- selecting alternatives. A changed model, effort, provider, route, role,
282
- service tier, authority, deadline, retry limit, concurrency, lane cap,
283
- reasoning mode, selector granularity, writable root, context control, fallback,
284
- wave topology, scope, lane identity, payload digest, floor, pinned target, or
285
- relevant catalog fact changes the record to `stale`. Refuse the launch and
286
- return for reapproval through a newly prepared record.
287
-
288
- ### Legacy One-Step Compatibility
289
-
290
- Legacy callers that omit `operation`, or set `operation: dispatch`, retain the
291
- existing one-step selection-and-launch flow: selection and launch occur in the
292
- same operation and produce the existing Legacy Record or Record shapes. A
293
- legacy caller must not fabricate an `approval_fingerprint`, approval evidence,
294
- or a prepared record merely to remain compatible. `prepared_record_version`
295
- is required only for the approval-bound path; version `1` is the only supported
296
- prepared-record version, and unknown versions fail closed before launch.
297
-
298
72
  ## Legacy Record
299
73
 
300
74
  This baseline Record remains valid without optional model-guidance evidence: