Commit Graph

5 Commits

Author SHA1 Message Date
Josh Ford
c145f6e522 test(docs): doc-fact contract tests for CLI, manifest, and Responses claims (doc-truth PR 3/5) (#7378)
* docs: fix live drift in extension, responses API, and channel docs

The public tutorial taught the retired manifest v2 authoring format
([[host_api]] / [capability_provider.tools] / runtime_credentials), which
the v3 parser hard-rejects, and never mentioned origin_gate_matrix; the
Responses API page claimed temperature is rejected (accepted 0.0-2.0 and
forwarded), claimed model must be "default" (any well-formed name <= 256
bytes), claimed max_output_tokens is rejected (accepted and ignored by DTO
policy), and omitted the required model field from every request example;
the channel tutorial pointed at two files that no longer exist.

- docs/extensions/building-a-tool.md: rewrite manifest sections to the v3
  [[tools]] / [[tools.credentials]] / [auth.<vendor>] shape, document
  origin_gate_matrix (origins, policies, ratchet), correct the hosted-MCP
  [mcp] section, packaging via ironclaw_extension_support package modules,
  and v3 test references; drop the nonexistent script runtime kind.
- docs/api/responses.mdx: correct model/temperature/tools/tool_choice
  rejection rules, document unknown-field tolerance, add the required
  model field to all 15 request examples.
- docs/channels/building-a-channel.mdx: replace dead
  crates/ironclaw_first_party_extensions + available_extensions.rs
  registration instructions with the current package-directory mechanism.
- docs/reborn/contracts/extensions.md: state that production manifests
  author v3 (lowering into the v2 resolved model described there); label
  the v2 examples as legacy.
- docs/reborn/how-to-port-tool-to-reborn.md: superseded banner pointing at
  the v3 guides.

Part of #7317 (doc-truth pipeline, PR 1 of 5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): extend the reference gate to the docs/ surface

The public Mintlify tree had no path-reference validation — a published
tutorial told contributors to edit files that no longer exist and nothing
caught it. check-guidance.py already owned the machinery (tracked-tree
resolution, fence exclusion, suppress markers, shrink-only debt, fail-closed
floors), so the docs surface joins the same gate rather than a fork.

- discover_guidance() now collects every tracked docs/**.md|.mdx: published
  pages, the zh/ locale mirror, and the living contract corpus
  docs/reborn/contracts/. Dated archives (docs/internal/, the non-contract
  parts of docs/reborn/) are excluded as classes — measured 2026-08-07,
  705 of 709 dangling docs references sat in those historical corpora, and
  forcing dated plans/ADRs to track today's tree would either rewrite
  history or drown KNOWN_MISSING.
- docs/ files extract backticked inline paths only; Mintlify markdown link
  targets are site routes (extensionless pages, site-absolute /using/cli),
  a different namespace than the tracked tree, so the link extractor is off
  there by design.
- _reference_lines learns MDX comments ({/* ... */}), including
  {/* check-guidance: path-ok */} as the .mdx suppress-marker form, with the
  same one-reference-per-marker and multi-line semantics as HTML comments.
- Floors re-measured and re-dated (364 files / 2276 references; floors
  180/1100), plus a dedicated MIN_DOCS_FILES=60 floor: the aggregate floors
  sit below the guidance-only remainder, so the docs branch of discovery
  silently breaking needs its own refusal. --json now reports docs_files.
- Fixes the four real dangles the new scan found in docs/reborn/contracts/
  (moved nested_dispatch_stream.rs test home, retired event-store migrations
  directory, loop_driver_host tests->src move). KNOWN_MISSING stays empty.
- Self-tests: 8 new cases (dangling docs path fails; Mintlify links are not
  references; MDX marker suppresses exactly one reference; multi-line MDX
  comment hides content; zh discovered; archives excluded but contracts
  scanned; docs fence fails closed; docs floor refuses).
- ws12_workflow_contracts.py: docs/api/responses.mdx and docs/zh/index.mdx
  join the has_guidance in-scope probes so a narrowed trigger regex cannot
  silently skip the gate for public docs.

Part of #7317 (doc-truth pipeline, PR 2 of 5); stacked on #7375.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(docs): pin CLI, manifest, and Responses doc claims to code

Three deterministic doc-fact contract tests, each living in the crate that
owns the truth it checks, so the drift #7317 describes fails CI instead of
shipping:

- crates/app/ironclaw_cli/tests/docs_cli_reference.rs: parses the real
  binary's --help and cross-checks docs/using/cli.mdx table rows both ways
  (every visible subcommand documented, any alias form counting; every
  documented command real), with a fail-closed row floor. Doc gaps this
  surfaced are fixed here: ironhub had no rows at all, completion was
  fence-only, and the Trace Commons table lacked the `ironclaw` prefix the
  rest of the page uses.
- crates/extensions/ironclaw_extension_registry/tests/
  docs_manifest_schema_version.rs: walks the published docs tree (the
  frozen .mintignore fence mirrored as constants) and asserts zero
  occurrences of the retired reborn.extension_manifest.v2 literal, fenced
  code included; asserts building-a-tool.md names
  MANIFEST_SCHEMA_VERSION_V3 verbatim and documents origin_gate_matrix.
- crates/product/ironclaw_openai_compat/tests/docs_responses_contract.rs:
  docs/api/responses.mdx now carries a machine-readable
  {/* doc-fact:responses-request-policy */} marker block (invisible when
  rendered); the test parses it and drives every claim through the same
  route-level seam as the sibling *_contract.rs suites — the marker's
  values parameterize the assertions (temperature accepted at the
  documented max and rejected just above it, model accepted at the byte
  cap and rejected past it, tool_choice always 400, tools 400 without /
  registered with external-tool wiring, empty tools treated as omitted,
  unknown fields like max_output_tokens accepted and ignored, and one
  request carrying every documented field accepted).

Part of #7317 (doc-truth pipeline, PR 3 of 5); stacked on #7376.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: address Copilot and CodeRabbit review on doc-drift PR

- responses.mdx: tool_choice is rejected only without external-tools wiring;
  with external tools enabled it passes validation and is currently ignored
  (validate_responses_supported_fields_with_external_tools never checks it).
- building-a-tool.md: clarify that effect-derived host ports are validation
  vocabulary against the HostPortCatalog allowlist; adapters are built by
  host-runtime services after authorization/obligations, never from manifests.
- how-to-port-tool-to-reborn.md: mark the decision tree's RuntimeKind targets
  historical (v3 accepts only wasm|first_party; MCP is top-level [mcp];
  process/CLI work is the sandbox lane).
- building-a-channel.mdx: document the user install flow — virtual package
  root /system/extensions/<id>/manifest.toml, ironclaw extension search /
  install <extension-id> (ID, not path), WebUI Extensions lifecycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(responses): align the limits bullet with the corrected tool_choice claim

The rejection list was corrected in the previous commit (tool_choice is
rejected only without external-tools wiring); the "Limits and quirks"
bullet still said "not supported ... rejected with 400". Same claim, one
wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(docs): tool_choice is conditionally rejected, not always

Copilot review on the docs PR caught that
validate_responses_supported_fields_with_external_tools never checks
tool_choice — with external tools wired it is accepted and ignored, not
400'd. The doc-fact marker moves tool_choice into
rejected_without_external_tools, and the dedicated test now proves both
sides: 400 naming the param on the plain router, accepted-and-ignored
(submit succeeds, nothing registers) with external-tool wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: apply verified code-review findings on the drift PR

A full code review of this PR against live code surfaced claims the
original drift pass got wrong or missed; every fix below was re-verified
against the cited source before editing:

- responses.mdx: standard `ironclaw serve` deployments always wire
  external tools (OpenAiCompatRouteMountPorts requires the store/resume
  pair; mount.rs wires them unconditionally), so `tools` is accepted and
  `tool_choice` is accepted-and-ignored on shipped binaries — the
  conditional 400s apply only to custom compositions without the wiring
  (now a Note). temperature is validated and carried in the submitted turn
  payload but not applied as a provider sampling parameter. Non-streaming
  wait timeout is 30 s (DEFAULT_RESPONSES_WAIT_TIMEOUT), not 120. usage on
  retrieval is read best-effort from persisted run state incl. USD cost
  (read_run_usage), not always zero.
- building-a-tool.md: the [auth.example] oauth2_code recipe gains the
  required token_response map (deny_unknown_fields rejects the example as
  previously written); Gmail/Google Calendar corrected to first_party
  runtimes (their manifests declare kind = "first_party"); the worked
  api_key recipe is github's, not slack's; the tail "Quick implementation
  checklist" and reference list were still v2-era (script lane,
  assets/<extension>/ path, "manifest v2", v2.rs pointer) and now teach
  the v3 shape; composition/CLI package-naming claim narrowed (the binary
  does link slack/telegram adapter crates).
- contracts/extensions.md: legacy-format paragraph no longer claims
  host-bundled packages ship v2 (none do), and origin_gate_matrix is
  attributed to capability.rs + building-a-tool.md instead of
  extension-runtime/overview.md §3, which does not mention it.
- how-to-port banner: `script` manifest authoring is retired; the
  RuntimeKind::Script symbol survives as the process-sandbox lane's kind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(contracts): repoint delivery_resolution.rs to its family directory

PR #7157 (merged to main 2026-08-07) cited
crates/ironclaw_outbound/src/delivery_resolution.rs in the
communication-delivery-resolution contract; the crate lives at
crates/domains/ironclaw_outbound/. Caught by this branch's docs surface of
check-guidance.py on the first merge of main after the gate landed —
exactly the drift class it exists for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(test-plan): route docs pages to the doc-fact tests that read them

docs/ sat in IGNORED_PREFIXES as a pure-prose class, which this PR's
doc-fact tests falsify: three cargo tests now read published pages, so
a docs-only PR would have selected zero crate tests and merged green,
leaving the failure to land on whichever unrelated change ran the full
plan next.

Published Markdown now selects the registry's schema-version sweep;
docs/using/cli.mdx and docs/api/responses.mdx additionally select
their owning crates. All selections are direct exact test targets —
no reverse-dependency widening, since prose only changes the doc-fact
assertions that read it. Fenced trees (docs/internal/, docs/reborn/,
drafts) and non-page files keep the prose classification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): harden the docs gate and fix review-surfaced doc drift

Applies the verified findings from the PR #7376 code review:

- The loop-exit and turn-runner contract docs claimed the deleted
  loop_driver_host checkpoint-rejection test had 'moved into the
  module'; it was deleted in #6696 and the fenced verification command
  could not run. Both now cite the real surviving pins
  (planned_driver.rs executor test + the ironclaw_turns projection
  test mapped in scripts/reborn-e2e-rust.sh), with runnable commands.
- An unterminated comment now refuses at EOF like an unterminated
  fence; before, one typo'd closer silently un-scanned the rest of the
  file.
- Markdown links in the re-included corpora are now checked as repo
  paths (they are never published, so the Mintlify-route rationale did
  not apply); this alone added ~165 verified references.
- Each DOCS_REINCLUDED_PREFIXES entry must match at least one tracked
  page or discovery refuses, so the planned docs/reborn consolidation
  cannot silently drop the corpus from the scan.
- The living extension-runtime spec pages (overview.md,
  standard-operations.md) and guidance-conventions.md join the scan;
  guidance-conventions.md now describes the docs surface and the MDX
  marker form, and its one dangling test path is repointed.
- Floors comment corrected (57 rule globs, not 38).

Also fixes four drifted claims from #7375's pages, verified against
live code: the interleaved function_call_output example was rejected
with 400 (resume input must be exclusively function_call_output items
with previous_response_id); model is echoed only on create (GET/cancel
report the 'reborn' placeholder); output_schema_ref is optional; and
the unknown-fields claim now names the two deliberate exemptions.

Self-tests: 43 pass (three new arms — unterminated comment refusal in
both syntaxes, re-included links as repo claims, stale re-included
prefix refusal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): sync module docstring with re-included link checking

CodeRabbit caught the docstring still claiming the link extractor is
off for all of docs/** — stale since b172f69c7 enabled it for the
re-included corpora. The docstring now states the exception and the
current re-include set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(docs): drop the retired reborn/ entry from the publication-fence mirrors

reborn/ left docs/.mintignore when #7559 consolidated it into internal/;
the fence mirrors in docs_manifest_schema_version.rs and
reborn_pr_test_plan.py still listed it. Fixture paths follow the move.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(docs): tighten doc-fact comments and docstrings

Same behavior; module docs and test docstrings trimmed to the point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(docs): harden the doc-fact suites per CodeRabbit review

- CLI: validate full documented command paths via `ironclaw <path> --help`
  (immediately caught and removed the nonexistent `extension activate` row)
  and match visible aliases as exact tokens, not substrings.
- Responses: seed a real prior response so `previous_response_id` is
  actually submitted and accepted; document `metadata` in the visible table
  to match the marker.
- Manifest sweep: parse the publication fence from docs/.mintignore instead
  of mirroring it, so a removed fence entry widens the scan with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(docs): correct the completion syntax and parse the fence in the planner

Review findings (sub-agent /code-review):
- docs/using/cli.mdx taught `ironclaw completion <shell>`; the binary only
  accepts `--shell <shell>`. The contract test stops extracting at flags,
  so it could not catch this.
- The planner's doc-fact arm mirrored the .mintignore fence as constants —
  the same hand-maintained-mirror class the PR removes elsewhere. It now
  parses docs/.mintignore via docs_publication_boundary, and a .mintignore
  edit itself routes to the published sweep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(test-plan): treat a missing docs/.mintignore as no fence, not a crash

Matches docs_publication_boundary.find_violations(): fence gone means
everything is published, so every page routes to the sweep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(docs): replace the doc-fact count floors with derived anchors

Same move as #7376's MIN_DOCS_FILES removal: MIN_DOC_COMMAND_ROWS was
redundant with the completeness check (the binary defines the expected
set), and MIN_SCANNED_PAGES is now a docs.json nav-coverage assertion —
every source-backed navigation route must be among the walked pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(docs): assert the current schema version instead of scanning for a retired literal

Hardcoding `reborn.extension_manifest.v2` was backward-looking: retiring
v3 would need a hand-edit or the test goes stale. The scan now extracts
every `reborn.extension_manifest.<version>` mention in published pages
and asserts it equals `MANIFEST_SCHEMA_VERSION_V3`, with the family
prefix derived from the same constant — the next schema bump retargets
the test by itself, and typo'd or older versions (v1, v33) are caught
too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 16:40:54 +00:00
Josh Ford
d82c9584e5 ci(check-guidance): extend the reference gate to the docs/ surface (doc-truth PR 2/5) (#7376)
* docs: fix live drift in extension, responses API, and channel docs

The public tutorial taught the retired manifest v2 authoring format
([[host_api]] / [capability_provider.tools] / runtime_credentials), which
the v3 parser hard-rejects, and never mentioned origin_gate_matrix; the
Responses API page claimed temperature is rejected (accepted 0.0-2.0 and
forwarded), claimed model must be "default" (any well-formed name <= 256
bytes), claimed max_output_tokens is rejected (accepted and ignored by DTO
policy), and omitted the required model field from every request example;
the channel tutorial pointed at two files that no longer exist.

- docs/extensions/building-a-tool.md: rewrite manifest sections to the v3
  [[tools]] / [[tools.credentials]] / [auth.<vendor>] shape, document
  origin_gate_matrix (origins, policies, ratchet), correct the hosted-MCP
  [mcp] section, packaging via ironclaw_extension_support package modules,
  and v3 test references; drop the nonexistent script runtime kind.
- docs/api/responses.mdx: correct model/temperature/tools/tool_choice
  rejection rules, document unknown-field tolerance, add the required
  model field to all 15 request examples.
- docs/channels/building-a-channel.mdx: replace dead
  crates/ironclaw_first_party_extensions + available_extensions.rs
  registration instructions with the current package-directory mechanism.
- docs/reborn/contracts/extensions.md: state that production manifests
  author v3 (lowering into the v2 resolved model described there); label
  the v2 examples as legacy.
- docs/reborn/how-to-port-tool-to-reborn.md: superseded banner pointing at
  the v3 guides.

Part of #7317 (doc-truth pipeline, PR 1 of 5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): extend the reference gate to the docs/ surface

The public Mintlify tree had no path-reference validation — a published
tutorial told contributors to edit files that no longer exist and nothing
caught it. check-guidance.py already owned the machinery (tracked-tree
resolution, fence exclusion, suppress markers, shrink-only debt, fail-closed
floors), so the docs surface joins the same gate rather than a fork.

- discover_guidance() now collects every tracked docs/**.md|.mdx: published
  pages, the zh/ locale mirror, and the living contract corpus
  docs/reborn/contracts/. Dated archives (docs/internal/, the non-contract
  parts of docs/reborn/) are excluded as classes — measured 2026-08-07,
  705 of 709 dangling docs references sat in those historical corpora, and
  forcing dated plans/ADRs to track today's tree would either rewrite
  history or drown KNOWN_MISSING.
- docs/ files extract backticked inline paths only; Mintlify markdown link
  targets are site routes (extensionless pages, site-absolute /using/cli),
  a different namespace than the tracked tree, so the link extractor is off
  there by design.
- _reference_lines learns MDX comments ({/* ... */}), including
  {/* check-guidance: path-ok */} as the .mdx suppress-marker form, with the
  same one-reference-per-marker and multi-line semantics as HTML comments.
- Floors re-measured and re-dated (364 files / 2276 references; floors
  180/1100), plus a dedicated MIN_DOCS_FILES=60 floor: the aggregate floors
  sit below the guidance-only remainder, so the docs branch of discovery
  silently breaking needs its own refusal. --json now reports docs_files.
- Fixes the four real dangles the new scan found in docs/reborn/contracts/
  (moved nested_dispatch_stream.rs test home, retired event-store migrations
  directory, loop_driver_host tests->src move). KNOWN_MISSING stays empty.
- Self-tests: 8 new cases (dangling docs path fails; Mintlify links are not
  references; MDX marker suppresses exactly one reference; multi-line MDX
  comment hides content; zh discovered; archives excluded but contracts
  scanned; docs fence fails closed; docs floor refuses).
- ws12_workflow_contracts.py: docs/api/responses.mdx and docs/zh/index.mdx
  join the has_guidance in-scope probes so a narrowed trigger regex cannot
  silently skip the gate for public docs.

Part of #7317 (doc-truth pipeline, PR 2 of 5); stacked on #7375.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: address Copilot and CodeRabbit review on doc-drift PR

- responses.mdx: tool_choice is rejected only without external-tools wiring;
  with external tools enabled it passes validation and is currently ignored
  (validate_responses_supported_fields_with_external_tools never checks it).
- building-a-tool.md: clarify that effect-derived host ports are validation
  vocabulary against the HostPortCatalog allowlist; adapters are built by
  host-runtime services after authorization/obligations, never from manifests.
- how-to-port-tool-to-reborn.md: mark the decision tree's RuntimeKind targets
  historical (v3 accepts only wasm|first_party; MCP is top-level [mcp];
  process/CLI work is the sandbox lane).
- building-a-channel.mdx: document the user install flow — virtual package
  root /system/extensions/<id>/manifest.toml, ironclaw extension search /
  install <extension-id> (ID, not path), WebUI Extensions lifecycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(responses): align the limits bullet with the corrected tool_choice claim

The rejection list was corrected in the previous commit (tool_choice is
rejected only without external-tools wiring); the "Limits and quirks"
bullet still said "not supported ... rejected with 400". Same claim, one
wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: apply verified code-review findings on the drift PR

A full code review of this PR against live code surfaced claims the
original drift pass got wrong or missed; every fix below was re-verified
against the cited source before editing:

- responses.mdx: standard `ironclaw serve` deployments always wire
  external tools (OpenAiCompatRouteMountPorts requires the store/resume
  pair; mount.rs wires them unconditionally), so `tools` is accepted and
  `tool_choice` is accepted-and-ignored on shipped binaries — the
  conditional 400s apply only to custom compositions without the wiring
  (now a Note). temperature is validated and carried in the submitted turn
  payload but not applied as a provider sampling parameter. Non-streaming
  wait timeout is 30 s (DEFAULT_RESPONSES_WAIT_TIMEOUT), not 120. usage on
  retrieval is read best-effort from persisted run state incl. USD cost
  (read_run_usage), not always zero.
- building-a-tool.md: the [auth.example] oauth2_code recipe gains the
  required token_response map (deny_unknown_fields rejects the example as
  previously written); Gmail/Google Calendar corrected to first_party
  runtimes (their manifests declare kind = "first_party"); the worked
  api_key recipe is github's, not slack's; the tail "Quick implementation
  checklist" and reference list were still v2-era (script lane,
  assets/<extension>/ path, "manifest v2", v2.rs pointer) and now teach
  the v3 shape; composition/CLI package-naming claim narrowed (the binary
  does link slack/telegram adapter crates).
- contracts/extensions.md: legacy-format paragraph no longer claims
  host-bundled packages ship v2 (none do), and origin_gate_matrix is
  attributed to capability.rs + building-a-tool.md instead of
  extension-runtime/overview.md §3, which does not mention it.
- how-to-port banner: `script` manifest authoring is retired; the
  RuntimeKind::Script symbol survives as the process-sandbox lane's kind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(contracts): repoint delivery_resolution.rs to its family directory

PR #7157 (merged to main 2026-08-07) cited
crates/ironclaw_outbound/src/delivery_resolution.rs in the
communication-delivery-resolution contract; the crate lives at
crates/domains/ironclaw_outbound/. Caught by this branch's docs surface of
check-guidance.py on the first merge of main after the gate landed —
exactly the drift class it exists for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): harden the docs gate and fix review-surfaced doc drift

Applies the verified findings from the PR #7376 code review:

- The loop-exit and turn-runner contract docs claimed the deleted
  loop_driver_host checkpoint-rejection test had 'moved into the
  module'; it was deleted in #6696 and the fenced verification command
  could not run. Both now cite the real surviving pins
  (planned_driver.rs executor test + the ironclaw_turns projection
  test mapped in scripts/reborn-e2e-rust.sh), with runnable commands.
- An unterminated comment now refuses at EOF like an unterminated
  fence; before, one typo'd closer silently un-scanned the rest of the
  file.
- Markdown links in the re-included corpora are now checked as repo
  paths (they are never published, so the Mintlify-route rationale did
  not apply); this alone added ~165 verified references.
- Each DOCS_REINCLUDED_PREFIXES entry must match at least one tracked
  page or discovery refuses, so the planned docs/reborn consolidation
  cannot silently drop the corpus from the scan.
- The living extension-runtime spec pages (overview.md,
  standard-operations.md) and guidance-conventions.md join the scan;
  guidance-conventions.md now describes the docs surface and the MDX
  marker form, and its one dangling test path is repointed.
- Floors comment corrected (57 rule globs, not 38).

Also fixes four drifted claims from #7375's pages, verified against
live code: the interleaved function_call_output example was rejected
with 400 (resume input must be exclusively function_call_output items
with previous_response_id); model is echoed only on create (GET/cancel
report the 'reborn' placeholder); output_schema_ref is optional; and
the unknown-fields claim now names the two deliberate exemptions.

Self-tests: 43 pass (three new arms — unterminated comment refusal in
both syntaxes, re-included links as repo claims, stale re-included
prefix refusal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): sync module docstring with re-included link checking

CodeRabbit caught the docstring still claiming the link extractor is
off for all of docs/** — stale since b172f69c7 enabled it for the
re-included corpora. The docstring now states the exception and the
current re-include set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): drop the docs/reborn re-include machinery after the docs/internal migration

The docs-surface scan carried a double negative — exclude docs/reborn/ as
an archive class, then re-include its living pages via
DOCS_REINCLUDED_PREFIXES — because the old tree mixed dead archives with
living specs. #7559 moved everything under docs/internal/, so the structure
is now: one excluded archive class (docs/internal/), and the living spec
pages (the contract corpus, the two extension-runtime spec pages,
guidance-conventions.md) named in INTERNAL_GUIDANCE_PREFIXES and scanned as
first-class guidance files — full link checking, guarded by the same
per-prefix zero-match refusal. The published-docs floor now counts only the
Mintlify surface (measured 2026-08-13: 82 pages; floor re-halved to 40).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): validate docs discovery against docs.json navigation instead of a count floor

MIN_DOCS_FILES was an arbitrary magnitude tripwire (half of last measured,
hand-re-dated) that only caught the docs branch of discovery losing ~half
its pages. The published surface already has an independent definition —
docs.json navigation, owned by docs_publication_boundary.py — so the gate
now asserts every navigation page's source file is in the reference scan
(reusing the boundary script's nav walker and OpenAPI pseudo-page filter).
Discovery breaking refuses on the first missing published page, unreadable
or page-less navigation refuses rather than passing vacuously, and there
is no docs count floor left to tune. --json reports nav_pages_covered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): count the living internal spec pages in the docs_files metric

CodeRabbit: docs_files under-reported the scan — the living internal spec
pages are scanned docs files but were excluded from the count, a leftover
of the deleted MIN_DOCS_FILES floor's published-only semantics. The metric
now reports every scanned file under docs/ (131 at measurement); published
surface health has its own signal in nav_pages_covered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(check-guidance): tighten comments and docstrings

Same behavior; the docs-surface comments and test docstrings were carrying
paragraph-length rationale better kept in the PR description.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 20:27:31 +00:00
Josh Ford
6dccbfbcbd docs: fix live drift in extension, responses API, and channel docs (doc-truth PR 1/5) (#7375)
* docs: fix live drift in extension, responses API, and channel docs

The public tutorial taught the retired manifest v2 authoring format
([[host_api]] / [capability_provider.tools] / runtime_credentials), which
the v3 parser hard-rejects, and never mentioned origin_gate_matrix; the
Responses API page claimed temperature is rejected (accepted 0.0-2.0 and
forwarded), claimed model must be "default" (any well-formed name <= 256
bytes), claimed max_output_tokens is rejected (accepted and ignored by DTO
policy), and omitted the required model field from every request example;
the channel tutorial pointed at two files that no longer exist.

- docs/extensions/building-a-tool.md: rewrite manifest sections to the v3
  [[tools]] / [[tools.credentials]] / [auth.<vendor>] shape, document
  origin_gate_matrix (origins, policies, ratchet), correct the hosted-MCP
  [mcp] section, packaging via ironclaw_extension_support package modules,
  and v3 test references; drop the nonexistent script runtime kind.
- docs/api/responses.mdx: correct model/temperature/tools/tool_choice
  rejection rules, document unknown-field tolerance, add the required
  model field to all 15 request examples.
- docs/channels/building-a-channel.mdx: replace dead
  crates/ironclaw_first_party_extensions + available_extensions.rs
  registration instructions with the current package-directory mechanism.
- docs/reborn/contracts/extensions.md: state that production manifests
  author v3 (lowering into the v2 resolved model described there); label
  the v2 examples as legacy.
- docs/reborn/how-to-port-tool-to-reborn.md: superseded banner pointing at
  the v3 guides.

Part of #7317 (doc-truth pipeline, PR 1 of 5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: address Copilot and CodeRabbit review on doc-drift PR

- responses.mdx: tool_choice is rejected only without external-tools wiring;
  with external tools enabled it passes validation and is currently ignored
  (validate_responses_supported_fields_with_external_tools never checks it).
- building-a-tool.md: clarify that effect-derived host ports are validation
  vocabulary against the HostPortCatalog allowlist; adapters are built by
  host-runtime services after authorization/obligations, never from manifests.
- how-to-port-tool-to-reborn.md: mark the decision tree's RuntimeKind targets
  historical (v3 accepts only wasm|first_party; MCP is top-level [mcp];
  process/CLI work is the sandbox lane).
- building-a-channel.mdx: document the user install flow — virtual package
  root /system/extensions/<id>/manifest.toml, ironclaw extension search /
  install <extension-id> (ID, not path), WebUI Extensions lifecycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(responses): align the limits bullet with the corrected tool_choice claim

The rejection list was corrected in the previous commit (tool_choice is
rejected only without external-tools wiring); the "Limits and quirks"
bullet still said "not supported ... rejected with 400". Same claim, one
wording.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: apply verified code-review findings on the drift PR

A full code review of this PR against live code surfaced claims the
original drift pass got wrong or missed; every fix below was re-verified
against the cited source before editing:

- responses.mdx: standard `ironclaw serve` deployments always wire
  external tools (OpenAiCompatRouteMountPorts requires the store/resume
  pair; mount.rs wires them unconditionally), so `tools` is accepted and
  `tool_choice` is accepted-and-ignored on shipped binaries — the
  conditional 400s apply only to custom compositions without the wiring
  (now a Note). temperature is validated and carried in the submitted turn
  payload but not applied as a provider sampling parameter. Non-streaming
  wait timeout is 30 s (DEFAULT_RESPONSES_WAIT_TIMEOUT), not 120. usage on
  retrieval is read best-effort from persisted run state incl. USD cost
  (read_run_usage), not always zero.
- building-a-tool.md: the [auth.example] oauth2_code recipe gains the
  required token_response map (deny_unknown_fields rejects the example as
  previously written); Gmail/Google Calendar corrected to first_party
  runtimes (their manifests declare kind = "first_party"); the worked
  api_key recipe is github's, not slack's; the tail "Quick implementation
  checklist" and reference list were still v2-era (script lane,
  assets/<extension>/ path, "manifest v2", v2.rs pointer) and now teach
  the v3 shape; composition/CLI package-naming claim narrowed (the binary
  does link slack/telegram adapter crates).
- contracts/extensions.md: legacy-format paragraph no longer claims
  host-bundled packages ship v2 (none do), and origin_gate_matrix is
  attributed to capability.rs + building-a-tool.md instead of
  extension-runtime/overview.md §3, which does not mention it.
- how-to-port banner: `script` manifest authoring is retired; the
  RuntimeKind::Script symbol survives as the process-sandbox lane's kind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 04:57:12 +00:00
Josh Ford
1fd85b2c45 docs: restructure the docs site around the shipped 1.0 binary (#6692)
* docs: stop publishing internal engineering docs to the public site

docs/.mintignore only excluded plans/ and internal/, so docs/reborn/,
superpowers/, qa/, adr/ and architecture-video/ were shipped to the
public Mintlify site. 33 internal architecture documents were live and
serving full content on docs.ironclaw.com, including the entire
reborn/contracts/ freeze set (kernel-boundary, secrets, capabilities,
trust-boundary-hardening, network, ...), the current architecture map,
and the product-manager architecture guide.

Verified by probing all 139 internal doc paths against the live site:
33 returned HTTP 200, 94 returned 404. The 404s were not protected by
policy -- they are simply newer than the last build -- so superpowers/
and the rest were exposed to the same leak on any rebuild.

Files stay where they are so they keep versioning with the code and
stay reachable by the knowledge graph; only publication changes.

No navigation page lives under a newly ignored path, so the public
site's page set is unchanged.

* docs: rewrite get-started pages against the shipped binary

The quickstart told users to start the agent by running `ironclaw` with
no arguments. The shipped 1.0.0-rc.1 binary requires a subcommand and
exits with code 2 and a usage error, so step 2 could not succeed.

Verified against the released binary rather than the source tree:

- install -> `ironclaw onboard` -> `ironclaw serve` is the real path;
  onboarding prints a `login_link` with the web token
- onboarding writes config.toml, providers.json, webui-token and
  .onboard-completed.json under ~/.ironclaw/reborn, and provisions the
  master key in the OS keychain
- `serve` listens on 127.0.0.1:3000 and takes --host/--port
- real onboard flags are --force/--dry-run/--no-service/--import-history,
  and --import-history is not wired up yet

Also drops the v1 onboarding steps that no longer exist (database
backend picker, embeddings toggle, tunnel/channel wizard, Docker
sandbox prompt), removes the stale PID-file troubleshooting for a
codepath the Reborn binary does not have, fixes the /channels and
/capabilities/jobs links on the index page, and corrects the provider
count from "7+" to 25+.

Removes the /home/agent and /home/user absolute paths flagged by
.claude/rules/doc-hygiene.md.

* docs: document the web interface, CLI, service, and Trace Commons

None of the shipped binary's primary surfaces had a page. The site
documented an agent you configure with environment variables and talk
to in a terminal; the product is a web application you start with
`ironclaw serve` and administer from the browser.

Adds four pages, all written from the verified 1.0.0-rc.1 command tree
rather than the source tree:

- using/webui: serve flags and defaults (127.0.0.1:3000), the
  webui-token login link, Google/GitHub SSO env vars, and the visible
  surfaces. Notes that sessions carry user identity only and do not
  inherit operator config privileges.
- using/cli: the full command surface. Flags `channels list`,
  `hooks list` and `logs` as registered-but-unimplemented so readers
  do not file bugs against known stubs.
- using/service: launchd/systemd unit naming, log paths under
  ~/.ironclaw/reborn/logs, and the headless master-key note.
- capabilities/traces: the Trace Commons opt-in, preview/submit/revoke
  flow, and instance enrollment. Calls out that bare `traces opt-out`
  on an enrolled instance disables the whole enrollment.

Documents only the SPA routes that are actually in the sidebar;
Projects/Jobs/Routines/Missions/Logs are registered but hidden while
their v2 APIs land, and the page says so rather than describing a
sidebar entry that is not there.

* docs: rewrite configuration and storage against the real config model

The configuration page documented a ~/.ironclaw/.env bootstrap layer
plus database-backed settings, and cited src/config/ as its source of
truth. src/ no longer exists. None of DATABASE_BACKEND, DATABASE_URL,
LIBSQL_PATH, LLM_BACKEND, HTTP_PORT or SECRETS_MASTER_KEY are read by
the shipped binary.

Rewritten from `ironclaw config list`, `config path`, `config init`
output and the generated config.toml on 1.0.0-rc.1:

- config.toml (selection) + providers.json (catalog) under
  ~/.ironclaw/reborn, both optional
- precedence: compiled defaults < config.toml < env < CLI flags
- secrets referenced by env var NAME; pasting a value is rejected at
  parse time
- the six real boot profiles, flagged as a security boundary
- real key groups incl. budget.* and trigger_poller.*, neither of
  which was documented anywhere
- IRONCLAW_REBORN_* override namespace

Storage: the page claimed PostgreSQL is the default and that starting
without DATABASE_URL fails. The shipped StorageBackend enum has one
variant, Postgres, and local-dev stores state in embedded database
files with no server at all. Rewritten around that, keeping the
pgvector setup and adding the pool-sizing and sslmode=require notes
from the generated config comments.

Inference providers: the catalog is 26 built-in providers, not the 17
listed. OpenRouter, Together and Fireworks are first-class provider
ids rather than openai_compatible aliases, and Groq, DeepSeek,
Cerebras, SambaNova, Venice, NVIDIA, Z.AI, Tinfoil, native Gemini and
openai_codex were missing entirely. Every api_key_env in the table is
taken from providers.json. Also drops `ironclaw onboard
--provider-only`, which is not a real flag, in favour of
`ironclaw models set-provider`.

Deletes tunnel.mdx and zh/tunnel.mdx. Tunnels have no Reborn
implementation: no tunnel.* config key, no Rust source reference, and
the WebUI networking settings section is commented out. Removes the
now-dangling tunnel card from the channels overview.

* docs: cut channels to what the 1.0 binary actually supports

The site documented seven channels. The Reborn stack implements two.

Deleted, with no implementation in crates/:
- discord: appears only in comments and test fixtures as a
  hypothetical future package
- signal: every match is a unix-signal or state-signal identifier,
  not a messenger
- wechat / wecom: wechat survives only as the standalone
  ironclaw_silk_decoder voice helper; wecom has zero references
- local: documented the v1 Ratatui terminal UI and web gateway with
  GATEWAY_*/HTTP_* env vars. The real equivalents are `ironclaw repl`
  and `ironclaw serve`, now covered under Using IronClaw.
- webhook: documented a standalone webhook channel with a shared
  secret and HTTP_* config. No such config key exists; webhook ingress
  now belongs to the extension host and serves Slack/Telegram events.

Telegram rewritten: the old page told users to run
`ironclaw onboard --channels-only`, which is not a real flag. Setup is
now WebUI channel setup plus the pairing step, with the real
/webhooks/telegram/updates callback.

Slack added. It has a first-party package, a v2 adapter, a [slack]
config section and a live /webhooks/extensions/slack/events route, and
had no page at all.

The overview says plainly that Discord, Signal, WeChat and WeCom were
v1 features and are not in 1.0, so readers who used them know to stay
on v1 rather than assume the docs are broken.

* docs: correct Responses API prerequisites and auth

The endpoints themselves are real and covered by
crates/ironclaw_reborn_composition/tests/webui_v2_serve.rs, but the
page's prerequisites were not.

- Drops the note requiring ENGINE_V2=true for external tools. Engine
  v2 was removed; the only trace left in the tree is a stale comment.
  Requests with tools[] no longer depend on a server flag.
- Replaces GATEWAY_AUTH_TOKEN and the self-service /api/tokens CRUD
  surface, neither of which exists in crates/, with the real auth
  paths: the onboarding-provisioned webui-token (or
  IRONCLAW_REBORN_WEBUI_TOKEN) for single-user, and admin-minted
  one-time per-user bearers via /api/webchat/v2/admin/users.
- Notes that the instance token is an operator credential while
  per-user and SSO bearers carry user identity only, matching the
  privilege split the gateway actually enforces.

* docs: replace extension commands that do not exist, consolidate MCP

Fourteen published pages taught commands the shipped binary does not
have. `ironclaw tool`, `ironclaw registry`, `ironclaw mcp` and
`ironclaw pairing` were v1 CLI subcommands; the Reborn CLI exposes
`ironclaw extension {search,install,activate,remove}` instead.

Rewrote the invocations across the Google suite, GitHub, web-search and
sandboxed-tools, and corrected the authorization prose: activating an
extension starts its setup flow, which is completed from Extensions in
the web interface, not from a CLI auth subcommand.

MCP had two pages, both built entirely on the nonexistent
`ironclaw mcp` CLI (add/list/test/toggle/remove/auth). Consolidated
into extensions/mcp and deleted capabilities/mcp, since MCP is a
runtime an extension declares rather than a capability of its own.

The stdio and Unix-socket transports the old page documented are not
supported: ironclaw_mcp rejects stdio outright ("unsupported until
process-level egress controls land"). The page now says HTTP only and
warns about stdio instead of giving instructions that cannot work.

Adds redirects for every page removed in this branch (capabilities/mcp,
tunnel, and the six retired channels) so existing inbound links land
somewhere useful instead of 404ing.

Full internal-link audit across all published English pages: 0 broken.

* docs: fix hosting guides and reconcile zh with the new structure

The three hosting tutorials ended with `ironclaw` as the start command,
so a reader who followed DigitalOcean, GCP or AWS end to end hit the
same usage error as the quickstart. They now run `onboard` then
`serve`, forward the port over SSH instead of exposing it, and install
the service so the agent survives disconnect.

Retires GATEWAY_AUTH_TOKEN (infrastructure/google, extensions/shell) in
favour of IRONCLAW_REBORN_WEBUI_TOKEN.

Chinese pages: deleted the two orphans that were never in navigation
(zh/capabilities/jobs, zh/extensions/google-calendar), applied the same
command corrections across 14 pages, and rewrote zh/channels/overview,
which still advertised Local, Signal, WeChat, WeCom, Webhook and Tunnel
as live cards pointing at deleted pages.

Full audit across all 76 navigation pages in both languages: 0 broken
internal links, no page missing from disk.

Left as follow-up: zh/quickstart, zh/onboard, zh/extensions/mcp and
zh/channels/telegram still mirror the pre-rewrite English structure and
need retranslation, not mechanical patching. They are flagged rather
than half-translated.

The /home/ironclaw paths in the hosting guides are deliberate — they
are the server-side service account, not developer-local paths, so
doc-hygiene does not apply to them.

* docs: address PR review, pinned to the released 1.0.0-rc.1 surface

These docs describe the version users can install today. Ground truth
is the `ironclaw-v1.0.0-rc.1` tag and the released binary, not `main`.

Two review findings were verified as main-only drift and deliberately
not applied:

- `ironclaw` with no subcommand. main added args_with_default_serve in
  cb34f3769 so bare `ironclaw` starts the WebUI. At rc.1, `Cli.command`
  is a required subcommand and the binary exits 2 with a usage error.
  Kept the rc.1 behavior.
- `extension activate`. main removed it in 25ef441e7. rc.1 still ships
  Search/Install/Activate/Remove, confirmed in the tagged source and in
  the released binary's help. Kept it documented.

Both will need a follow-up pass when the tree main is on becomes the
released version.

Everything else from review applied after re-verifying against the tag:

- extension install takes an extension id, never a path or URL (true at
  rc.1 as well) — rewrote the sandboxed-tools install flow, which had
  inherited a v1 concept under a new command name
- Telegram: /webhooks/extensions/telegram/updates; both routes exist at
  rc.1, and the documented setup goes through the Telegram extension
- only worker_count and the three concurrency caps have
  IRONCLAW_REBORN_RUNNER_* overrides at rc.1; heartbeat_interval_secs
  and poll_interval_ms are file-only
- openai_compatible needs base_url, present on the rc.1 slot type and
  previously undocumented; noted that the LLM_* env blocks are the
  supported fallback, not stale v1 syntax
- traces revoke is local-only without --endpoint; documented the real
  flag and its IRONCLAW_TRACE_SUBMIT_TOKEN default
- foreground serve and the service both bind the port, so the
  quickstart and all three hosting guides described a sequence that
  fails; they now stop the foreground server first
- retired the last GATEWAY_HOST/GATEWAY_PORT (en + zh GCE guide) and
  HTTP_WEBHOOK_SECRET (zh shell); neither exists at rc.1
- marked the unimplemented CLI stubs inline rather than contradicting
  the table in a following note
- .mintignore: removed the claim that omission from navigation keeps a
  page private. It does not, which is how 33 internal contracts were
  served; .mintignore is the only publication boundary
- added the missing /zh/* redirects and retranslated zh/extensions/mcp
  and zh/channels/telegram, which still carried `ironclaw mcp` and
  `ironclaw pairing`

Separately found by auditing every documented invocation against the
rc.1 command list: extensions/github.md used `ironclaw secret set`,
which has never existed. There is no CLI alias for a GitHub PAT, so it
now points at the extension setup flow.

Validation: every `ironclaw ...` invocation in all 76 navigation pages
checked against the rc.1 command surface, 0 mismatches; 0 broken
internal links; 17 redirects resolving.

* docs: channel setup traps, OAuth redirect URIs, Slack manifest, hosted restart

Covers #6668 and #6671, plus the redirect URIs, Slack app manifest, and
hosted-instance configuration steps that were missing entirely.

Discoverability (#6668, #6671). Both issues are one failure from a
user's side: the working setup surface exists but is not where anyone
looks, and the two obvious routes to it dead-end. Verified against the
rc.1 frontend -- channels-tab.tsx renders the Slack and Telegram connect
cards *below* the web/webhook/CLI/REPL rows in the Built-in panel, and
extensions-page.tsx defaults `tab` to "registry". The real path is
Extensions -> Channels tab -> bottom of Built-in -> Configure, and
neither half of that is discoverable.

Documented where a stuck user lands: a "Where Channel Setup Lives"
walkthrough in channels/overview; the tab-and-scroll location on both
channel pages; a "What Onboarding Doesn't Set Up" section in onboard,
which was silent on channels; and troubleshooting entries keyed to the
literal error string and to "I asked the agent and it said it couldn't".

The Telegram page now separates the operator bot-token step from the
per-user pairing step. Conflating them is what makes "An administrator
must configure the Telegram bot first" read as a permissions problem
when it means "wrong tab".

Redirect URIs, each checked against the routes rc.1 serves:

- Slack events:   /webhooks/slack/events
- Slack personal: /api/reborn/product-auth/oauth/slack_personal/callback
- Google:         /api/reborn/product-auth/oauth/google/callback

with matching IRONCLAW_REBORN_* variables and exact-match warnings,
since a trailing-slash difference is a silent authorization failure on
both providers.

Fixes a bug this branch introduced: channels/slack.mdx used
/webhooks/extensions/slack/events, which rc.1 does not serve.

Google keeps both flows because both exist at rc.1 -- the fixed-port
loopback callback (ironclaw_auth/src/loopback_oauth.rs, 9876) and the
server-hosted callback -- and the page now says which is which. Also
retires GOOGLE_OAUTH_CLIENT_ID/SECRET, v1 names absent from rc.1, in
favour of `ironclaw config set google.*`.

Slack app manifest added, JSON-validated, using the full
/api/reborn/product-auth/... redirect path; a shortened form 404s.

NEAR AI hosted instances. Configure over SSH with `ironclaw config set
google.client_id` / `google.redirect_uri` / `google.client_secret`, then
restart from the Agent Dashboard -- `ironclaw service` shells out to
`systemctl --user`, which a hosted instance does not provide, so every
`service` subcommand fails there including `restart`. That limitation is
now called out on every page that recommends `service`: using/service,
using/cli, quickstart, onboard, configuration, and the Google OAuth
setup. `config set` never restarts anything itself -- it prints "to
apply: ironclaw service restart" and the old value keeps serving until
the instance is restarted.

Also corrects two invalid examples this branch introduced: `config set
runner.poll_interval_ms` and `config set webui.listen_port` are not
accepted -- `set` takes only the alias-table keys, while `list`/`get`
read everything. Those are edited in config.toml.

Mirrored into zh/channels/overview, zh/channels/telegram, and
zh/extensions/google/oauth-setup.

Docs only. The underlying UX defects stand: #6671 needs Registry's
Configure to branch to admin setup when the bot token is unset, and
#6668 needs the capability-description/prompt fix. Documenting the
workaround does not close either.

Validation on a local `mint dev` preview: `mint broken-links` clean,
all navigation pages render, every removed page redirects to its
replacement, and every internal doc (reborn/, superpowers/, plans/,
qa/, adr/, reborn-binary) returns 404 -- including
reborn/contracts/kernel-boundary, which is live on production today.
Every ironclaw invocation across all 76 pages checked against the rc.1
command surface: 0 mismatches.

* docs: second review pass — extension install shape, provider ids, tool discovery

Five review findings applied; one rejected as main-only drift.

Applied:

- `extension install` takes an extension id, never a path or URL. The
  earlier mechanical rename left `./my-tool.wasm` and https:// examples
  in zh/capabilities/sandboxed-tools and zh/extensions/building-a-tool.
  Both now use search-then-install-by-id and say the path form fails.
- Tool discovery in capabilities/sandboxed-tools described the v1
  `~/.ironclaw/tools/` and `<workspace>/tools/` directories, which do
  not exist anywhere in rc.1 crates. Packages are discovered from
  `/system/extensions/<id>/manifest.toml` through the Reborn runtime,
  and a self-built package is imported from the WebUI rather than
  dropped in a directory.
- llm-providers contradicted itself: the table gave Together and
  Fireworks dedicated provider ids while the sections below still
  configured them as LLM_BACKEND=openai_compatible. Both now use
  `models set-provider`. The remaining openai_compatible examples are
  vLLM/LiteLLM/LM Studio, which is what that provider is for.
- `config set <key> <value>` implied a value is always required;
  secret-destination keys take none and prompt. Synopsis is now
  `<key> [value]`.

Rejected, with evidence:

- "Slack Event Subscriptions should be /webhooks/extensions/slack/events".
  That is the route on main, not at rc.1. rc.1 serves
  /webhooks/slack/events; every /webhooks/extensions/* reference in the
  tagged tree is Telegram. The route moved in 59afb4f9e (#6443), which
  `git merge-base --is-ancestor` confirms is NOT an ancestor of
  ironclaw-v1.0.0-rc.1. This is the third main-only drift on this PR,
  after default-serve and extension activate.

Validation: 76 nav pages, 0 broken links, 0 unbalanced components, and
every ironclaw invocation still matches the rc.1 command surface.

* docs: stop advising an open firewall port for the SSH OAuth tunnel

The Google OAuth page told operators to whitelist port 9876 in the
server firewall "to allow the tunnel to work properly". That is both
unnecessary and a downgrade: `ssh -L 9876:127.0.0.1:9876` carries the
callback inside the existing SSH connection, so no inbound port is
opened, and whitelisting 9876 would expose the loopback OAuth callback
to the internet for no benefit.

Replaced the tip with a warning not to open the port, and reworded the
step so it explains what the tunnel is actually bridging: the callback
listens on loopback inside the server, the browser runs on the
operator's machine.

Pre-existing content, not introduced by this branch. Applied to the
English and Chinese pages.

* docs: drop the dead loopback OAuth flow, stop showing secrets in shell exports

The Google OAuth page documented two callback flows. Only one works on
the shipped binary.

`crates/ironclaw_auth/src/loopback_oauth.rs` — the fixed-port
127.0.0.1:9876 listener — says in its own header: "for the v1 (legacy)
stack only", "## Not used by Reborn", "The sole consumer is the root
`ironclaw` v1 binary", "## DELETE WITH V1". At rc.1 nothing outside
ironclaw_auth references OAUTH_CALLBACK_PORT or bind_callback_listener,
and the v1 binary is not shipped: the release contains only the Reborn
CLI. So the loopback redirect URI and the whole SSH-tunnel step were
instructions a reader could not complete.

This also resolves the inconsistency reviewers flagged: the page told
users to open an SSH tunnel for the loopback callback and then set
google.redirect_uri to the hosted callback, which would have produced a
redirect_uri_mismatch and made the tunnel pointless. There is now one
documented callback,
https://<host>/api/reborn/product-auth/oauth/google/callback, so the
two cannot drift apart.

Corrects my earlier claim on this PR that both flows exist at rc.1. The
module exists in the tree; it has no consumer in the shipped binary.

Secrets in shell history:

- Removed the interactive `export ...CLIENT_SECRET=` example from the
  Google setup. The page already said prompted input avoids shell
  history, then contradicted itself. Env vars are now listed by name
  only, for service/container injection from a secret manager.
- Same caution added to the WebUI SSO section, where the variables are
  real but the interactive export is not how they should be set.
- extensions/github used GITHUB_OAUTH_CLIENT_ID/SECRET, which do not
  exist at rc.1 at all; credentials go through the extension setup flow.

Also rewrote the zh quickstart start step, which still told users to
delete a stale PID file and re-run bare `ironclaw` — a v1 codepath the
Reborn binary does not have. It now matches the English page.

No published page references port 9876, a retired env var, or a
nonexistent command. Validation: 76 nav pages, mint broken-links clean,
0 unbalanced components, 0 command mismatches against rc.1.

* docs: correct admin create-user response shape and Google redirect claim

Two review findings, both verified against rc.1.

The Responses API auth tab showed the admin create-user response as
`{"id":"...","api_token":"..."}`, implying a top-level `id`. The real
shape is `RebornAdminUserCreatedResponse { user: AdminUserRecord,
api_token: String }` — the token is top-level, the identity is nested
under `user`, and the id field is `user_id`. Confirmed in
admin_users.rs and in the frontend's own normalizer
(`{ ...normalizeUser(response?.user), token: response?.api_token }`).
Replaced the inline comment with a full response body so a reader
parsing it does not reach for the wrong field.

The Google OAuth page said "Google will not redirect to a loopback
address", which attributes an IronClaw constraint to Google. Google
does support loopback redirect URIs; the reason the hosted callback is
required here is that IronClaw's product-auth flow receives callbacks
on a gateway HTTP route and has no loopback listener. Reworded in both
languages to say that plainly.

* docs: fix commands and config claims that fail against rc.1

Six corrections found by review, each verified against the
ironclaw-v1.0.0-rc.1 tag rather than main:

- Web Search page documented a Brave Search API key and the extension
  id `web-search`. Neither exists at rc.1: the extension is
  `web-access` (Exa-backed) and needs no credentials at all — the
  product's own onboarding copy says "Web Access does not need
  credentials". Rewritten in EN and zh around the real setup.
- `traces opt-in`, `submit` and `enqueue` were shown without their
  required `--endpoint` / `--envelope` flags, so the page's core
  opt-in-then-contribute walkthrough could not be followed. Added them.
- `traces preview`, `enroll-instance`, `ingest-health` and `profile
  set` were shown with positional args that are actually named flags
  (`--recorded-trace`, `--invite`, `--endpoint`, `--handle`).
- `POST /admin/users` example omitted `role`, which has no serde
  default and is therefore required; the request failed to deserialize
  before creating anything.
- Claimed `identity.tenant` / `default_agent` / `default_project` are
  rejected. `runtime_identity()` consumes the first two, and `serve`
  consumes the third — only `run`/`repl` reject `default_project`.
- Told operators to pair `[storage]` with
  `hosted-single-tenant-volume`, which is not one of the three profiles
  that wire it, so the instance fails to boot. Reordered the steps so
  the profile is set before `[storage]`, and corrected the profiles
  table, which implied that profile is PostgreSQL-backed.

Five further review findings were declined: they describe main, not
rc.1 (bare `ironclaw` defaulting to serve, `extension activate` being
removed, the Slack ingress route move, the `slack_personal` provider
retirement, and its redirect-URI env var). All five are still the rc.1
behavior this branch documents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 21:39:31 +00:00
Illia Polosukhin
b921b42998 docs(api): document the Responses API end-to-end (#3709)
* docs(api): document the Responses API end-to-end

Covers auth (gateway token vs per-user /api/tokens), session
continuity via previous_response_id, streaming, externally-provided
function tools (the engine-native round trip landed in #3122),
structured x_context, per-request instructions, error envelope, and
the current limits (one-call-per-turn, no tool_choice, no
temperature).

Wires the new page into docs.json under a top-level "API" group so it
sits next to Channels and Extensions rather than buried under
Capabilities.

* docs(api): address PR #3709 review feedback

Verified against the current code, not just the bot suggestions:

- Auto-generated gateway token: 32 random bytes hex-encoded => 64
  chars, not 32 (src/channels/web/mod.rs:118-121).
- Token create response field: token_prefix, not prefix
  (src/channels/web/handlers/tokens.rs:92).
- Streaming events: response.in_progress is defined but the worker
  only emits response.created (src/channels/web/responses_api.rs:1530-1536),
  so drop the row that promises an event clients never see.
- Auth errors: the gateway middleware returns plain-text 401/403/503
  before the request reaches the Responses handler
  (src/channels/web/platform/auth.rs:1133), not the JSON envelope.
- previous_response_id ownership: 404 is the GET behavior only; POST
  decodes the UUID and dispatches into the agent
  (src/channels/web/responses_api.rs:1237-1244), so cross-user resume
  fails at the turn level, not via 404.
- instructions caveat: IronClaw currently prepends the <instructions>
  block into the user message and persists it
  (responses_api.rs:1231-1234), so it does replay across turns even
  though the OpenAI spec promises one-turn semantics.
- Anchor: #input-shapes never existed; point to #external-tools where
  the array form is actually shown.

The Gemini bot also flagged `resp.output_text` as a phantom property
across both SDK examples. Verified directly against
openai-python/src/openai/types/responses/response.py:310-322 and
openai-node/src/resources/responses/responses.ts:963 - both expose
output_text as a convenience aggregator over output_text content
blocks, and their own docstrings recommend it over walking output[0].
SDK examples kept as-is.
2026-05-16 16:07:09 -07:00