mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-03 08:06:01 +08:00
automation/codebase-graph-refresh
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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
|
||
|
|
9fd1e638a6 |
feat(inspector): complete statistics, navigation, and localization (#7291)
* feat(inspector): add operator inspection API * docs(inspector): assign product service ownership * test(inspector): ratchet diagnostic contracts * feat(inspector): add debug panel shell * test(inspector): cover debug panel shell e2e * fix(inspector): stop diagnostics when panel closes * feat(inspector): add prompt inspection * fix(inspector): follow current webui ownership * feat(inspector): add model call statistics * test(inspector): cover model statistics e2e * fix(inspector): avoid uncollected tool metrics * test(inspector): cover prompt diagnostics e2e * test(inspector): align statistics e2e scope * fix(inspector): redact prompt metadata * fix(inspector): preserve per-call model identity * fix(inspector): classify prompt instruction sources * test(inspector): assert reported token usage * feat(inspector): add activity timeline and turn navigation * test(inspector): cover activity timeline in browser * fix(inspector): read current run before publishing activity * feat(inspector): add bounded tool execution details * test(inspector): cover bounded tool details in browser * fix(inspector): validate retained tool result sizes * test(inspector): add security and operator coverage * test(inspector): cover browser workflows end to end * fix(inspector): address review feedback * fix(inspector): retry transient snapshot failures * fix(inspector): address prompt diagnostic review findings * fix(inspector): follow debug query navigation * feat(inspector): complete frontend diagnostics * test(inspector): cover frontend parity in browser * fix(inspector): preserve stream terminal state * fix(inspector): capture full capability surface * fix(inspector): scope projection activity to its run * fix(inspector): harden activity diagnostics * fix(inspector): bound tool result diagnostic capture * fix(inspector): harden tool diagnostic pipeline * fix(llm): request usage for NEAR AI streams * fix(inspector): address prompt diagnostic review feedback * fix(webui): harden inspector stream coverage * fix(inspector): preserve debug session statistics * fix(inspector): keep diagnostics active while hidden * test(e2e): cover hidden inspector observation * fix inspector model call stats review findings * fix inspector refresh and truncation regressions * fix(inspector): address activity timeline review feedback * fix(inspector): harden activity lifecycle handling * fix(composition): move tool diagnostics to loop host * fix(inspector): keep a settled stream live and complete locale parity A live diagnostic update's debounced snapshot refresh was announcing LOADING, so an open, healthy stream read as "Connecting" indefinitely once a run settled — the settling stats update is the last one. That refresh is now a background read. Incomplete snapshot statistics no longer accumulate as real zeros, browser-session inspector state is namespaced by the authenticated caller, an evicted pinned run rejoins the latest turn instead of the oldest, tool status is localized, and the inspector strings now cover all ten locales. * test(inspector): put the inspector locale sidecar under the parity gate The inspector's English copy is registered from its lazy chunk instead of src/i18n/en.ts, so the all-locale parity test — which derives the required key set from en.ts — never covered those keys; a locale could drop one and fall back to English silently. The test now treats the English key set as the union of en.ts and a declared sidecar list. Keeping the copy in en.ts is not an option: measured, it puts /chat at 217.4 KB gzip against a 217.0 KB budget. * fix(inspector): reject malformed model breakdowns and correct locale copy A `calls_per_model` entry with a negative or non-integer `calls` passed the statistics decoder and was then coerced to zero during accumulation without marking the breakdown truncated, presenting a fabricated "0 calls" for a model. Every entry is now validated before a record is accepted. German turn navigation used "Zug" (a train, or a game move); it now reads "Runde", with the determiner agreement that noun requires. Spanish and Portuguese tool-status values were written feminine against a masculine "Estado"/"Status" label. * fix(inspector): bound the model breakdown before scanning and retaining it The statistics decoder validated every calls_per_model entry but never the array length, so an out-of-contract response was scanned in full and then retained by the accumulator for up to 128 runs. The host truncates this breakdown at MAX_MODELS_IN_STATS and reports it as truncated, so a longer array cannot conform; the client now mirrors that ceiling and rejects the record before the scan. * fix(inspector): align turn navigation with host diagnostic retention The browser offered 32 turns of navigation per thread while the host retained diagnostics for 2 runs per session, so every turn past the second rendered blank. Each layer was individually correct and the e2e scenario stopped at two turns, so nothing saw the dead zone. Retention moves to 4 and the navigation window mirrors it, pinned by a new architecture gate that reads both constants; the scenario now walks back two turns and asserts real activity. Retention is a ceiling as well as a default, and capture is unconditional, so 4 is a resident memory choice — roughly 80 MB worst case across the eight tracked sessions. * fix(composition): delimit the i18n bundle guard with an i18n-owned marker The guard sliced the concatenated chunk bundle from the i18n provider up to `QueryClient`, a symbol another module owns, so the segment's extent tracked Rollup's chunk boundaries. A split that merely folded react-query into the entry chunk removed that marker from everything appended after the provider and failed an i18n guard with no i18n change. It now ends on the AVAILABLE_LANGUAGES literal that follows the provider in the same module; string literals survive minification, and every existing assertion holds against the tighter segment. * fix(architecture): resolve the inspector gate's SPA path through crate_path The gate joined a family-nested literal onto the workspace root, the idiom crate_path exists to replace: a crate family move would have turned this into a read failure rather than a resolved path. It now names the SPA file in the logical flat spelling and resolves it, and the assertion reports the resolved path so the message still points at a file that exists. * test(inspector): follow a pinned turn explicitly when a new turn arrives The multi-turn scenario assumed the panel would jump to an arriving turn, but a selection the operator navigated to is deliberately sticky: the new turn widens the window without yanking them off the turn they are reading. The scenario now asserts that guarantee, then clicks Latest to follow, then walks back two turns as before. Verified by running the inspector scenarios locally rather than by reading, which is how this slipped through the first time. |
||
|
|
9edf3fe394 |
test(inspector): add browser, security, and operator coverage (#7280)
* feat(inspector): add operator inspection API * docs(inspector): assign product service ownership * test(inspector): ratchet diagnostic contracts * feat(inspector): add debug panel shell * test(inspector): cover debug panel shell e2e * fix(inspector): stop diagnostics when panel closes * feat(inspector): add prompt inspection * fix(inspector): follow current webui ownership * feat(inspector): add model call statistics * test(inspector): cover model statistics e2e * fix(inspector): avoid uncollected tool metrics * test(inspector): cover prompt diagnostics e2e * test(inspector): align statistics e2e scope * fix(inspector): redact prompt metadata * fix(inspector): preserve per-call model identity * fix(inspector): classify prompt instruction sources * test(inspector): assert reported token usage * feat(inspector): add activity timeline and turn navigation * test(inspector): cover activity timeline in browser * fix(inspector): read current run before publishing activity * feat(inspector): add bounded tool execution details * test(inspector): cover bounded tool details in browser * fix(inspector): validate retained tool result sizes * test(inspector): add security and operator coverage * test(inspector): cover browser workflows end to end * fix(inspector): address review feedback * fix(inspector): retry transient snapshot failures * fix(inspector): address prompt diagnostic review findings * fix(inspector): follow debug query navigation * fix(inspector): preserve stream terminal state * fix(inspector): capture full capability surface * fix(inspector): scope projection activity to its run * fix(inspector): harden activity diagnostics * fix(inspector): bound tool result diagnostic capture * fix(inspector): harden tool diagnostic pipeline * fix(inspector): address prompt diagnostic review feedback * fix(webui): harden inspector stream coverage * fix inspector model call stats review findings * fix inspector refresh and truncation regressions * fix(inspector): address activity timeline review feedback * fix(inspector): harden activity lifecycle handling * fix(composition): move tool diagnostics to loop host * fix(inspector): address review findings |
||
|
|
1ce5250a5a |
refactor(ws6): consolidate the six Wave 4 PRs into one (#7124, #7117, #7106, #7099, #7101, #7128) (#7139)
* refactor(loop-host): move system-prompt content out of the composition root (WS6)
CHECKLIST WS6 "Composition behavior evictions" — the `system-prompt content
→ owning prompt asset` clause. PROPOSAL §6.10.1 lists it among the items still
resident in `ironclaw_reborn_composition`; `families/app.md` already says
"prompt content of any kind" never belongs to the app family.
The four assets move from `ironclaw_reborn_composition/assets/prompts/` to
`ironclaw_loop_host/prompts/`, beside the five prompt assets that crate already
ships and beside `identity_context.rs`, whose `HostIdentityContextSource` is
what puts them in front of a model. `system_prompt_assets.rs` exports them as
`pub const`; composition consumes the consts instead of `include_str!`.
Resolved owner is the **loop** half of "loop/product owner": the port is
loop_host's, and loop_host already owns `prompts/`.
What deliberately did *not* travel: the seeding/validation of the on-disk,
user-editable `SYSTEM.md`. That is boot-time `std::fs` work on a real host
path and `ironclaw_loop_host` has zero `std::fs` uses — moving it would put
host-path I/O into a loops crate. Composition keeps assembly + seeding.
The runtime storage path `system/prompts/default-system.md` is unchanged; it
is where existing installs' user-edited file lives, so renaming it would be a
behavior change, not a move.
Enforcement (new, in the same diff):
`reborn_composition_boundaries.rs::composition_root_embeds_no_prompt_content`
fails on either half of the debt — a re-added `include_str!("….md")` in
composition source, or a re-added shipped `.md` asset under the crate that is
not crate guidance. Sabotage-checked both halves independently. It is keyed on
markdown, not on `include_str!`, so `builtin_capability_policy.toml`
(config-as-data, composition's charter) is untouched.
Un-masking:
- `ironclaw_loop_host` 803 → 806 tests; the diff of the unfiltered `--list`
rosters is exactly the three new `system_prompt_assets::tests::*`.
- `ironclaw_reborn_composition` 928 → 928; roster diff is empty.
- No existing test edited.
Docs corrections, each quoting the text it replaces:
- CHECKLIST WS6 + PROPOSAL §6.10.1: the `local_dev` misnomer's "one residue:
the local variable at `runtime.rs:3016`" is wrong twice. The variable is at
`runtime.rs:3095`, and `local_runtime` appears 191 times in composition's
`src` — including six public API symbols, the public type
`RebornLocalRuntimeIdentity`, and an assembly struct field.
`reborn_standalone_typename_ratchet` stayed green because it governs *type*
names only. Tracked as #7098 as a pure-rename PR, not folded in here.
- PROPOSAL §2: `root/default_system_prompt.rs` is re-described as assembly +
seeding now that its content assets are gone.
- `families/loop.md` + loop_host `AGENTS.md`/`CLAUDE.md` record the new owner
and the enforcing test.
Refs #7098
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* review(ws6): fail-close the markdown ownership gate; fix two stale doc measurements
Addresses both CodeRabbit threads on #7099. Both were right; verified before
fixing, and each fix is sabotage-checked.
**1. The markdown ownership gate had three false-negative paths.**
- `include_str!` / `include_bytes!` were matched per *line*, so a `rustfmt`-wrapped
invocation — `include_str!(\n "…/some-prompt.md"\n)`, which is what the
formatter produces for a long path — evaded the scan entirely. Replaced with
`markdown_include_sites()`, which scans complete invocations across line
breaks, plus four unit tests including the multiline regression case. Verified
by planting a multiline `include_str!("../../AGENTS.md")` in composition
source: the gate now fails and names the flattened site.
- `markdown_assets()` skipped unreadable directories and entries with
`let Ok(..) else { continue }`, so "the walk could not see it" and "there is
nothing there" looked identical to an ownership gate. It now panics on a
failed `read_dir`, entry, or `file_type`.
- Extensions were compared case-sensitively; `.MD` slipped past. Now
`eq_ignore_ascii_case`, on both the extension and the guidance-file exemption.
Also added a scanned-file floor (>= 50 sources) so a broken walk fails instead
of reporting clean — the same "measured scan" idiom
`reborn_registration_pipeline_boundary.rs` uses.
**2. PROPOSAL §2.4 still carried the pre-correction `local_runtime` measurement.**
Line 81 said `runtime.rs:3016` and "the local *variable* name survived" while
§6.10.1 (line 670) already carried the correction — a document contradicting
itself. §2.4 now cites `runtime.rs:3095`, states the 191-occurrence scope, and
points at §6.10.1 and #7098. The one surviving `:3016` in the file is inside the
verbatim quote of the text being replaced, which is deliberate.
**Also in this commit — two WS6 rows re-measured, because they would otherwise
have been redone.** `RebornRuntime` slimming, at `origin/main` @ `0f897e9366`:
- "~40 `_for_test` accessors behind `test-support`" is **already done**:
`runtime.rs` has 38 and zero are ungated; crate-wide 149, and all 13 without
their own attribute sit in a module gated at its declaration site
(`lib.rs:64-65`, `factory.rs:1388-1389`). No `_for_test` function compiles
into a production build.
- "delete the dead `product_live_adapters` export block" is **refuted**: it is
live cross-crate test-support API. `ironclaw_product` declares
`ironclaw_reborn_composition = { …, features = ["test-support"] }` as a
dev-dependency and its `tests/support/planned_agent_loop.rs` imports seven of
the eight names; composition has a suite dedicated to them. Deleting it would
strand a sibling crate's test support.
Only the third clause (re-export wall vs. snapshot) is still live.
`crates/AGENTS.md`'s `ironclaw_loop_host` row now names the prompt assets and
says the seeding stays in the composition root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(ci): stop the Reborn test planner failing closed on the crate-family map
`crates/AGENTS.md`, `crates/Architecture.md` and `crates/README.md` sit directly
under `crates/` and belong to no package directory. The planner skips markdown
only at the repository root (`path.endswith(".md") and "/" not in path`), and
`IGNORED_PREFIXES` does not include `crates/`, so all three fell through to the
fail-closed package-resolution arm:
Reborn PR test planner failed: unmapped crate path: crates/AGENTS.md
That failed `Detect Reborn test scope`, which failed the `Tests (Reborn)`
roll-up — on **any** PR that edited them. Hit while updating `crates/AGENTS.md`
in this branch; filed as #7100 with the blast radius.
It blocks the exact maintenance the house rule asks for: `crates/AGENTS.md` is
the crate-level map WS11 requires updating when crate ownership changes, and
`crates/Architecture.md` is already recorded in PROPOSAL §2 as carrying a stale
`build_reborn_services` reference that WS11 has to fix.
Fix: classify markdown *directly* under `crates/` as crate-family guidance with
no test surface, ahead of the package-resolution arm. Deliberately narrow:
- markdown *inside* a package directory is untouched and stays package-owned
(`test_nested_crate_markdown_remains_package_owned` still passes);
- anything non-markdown directly under `crates/` still falls through to the
explicit-decision arm, which is the point of that arm.
Two regression tests beside the existing nested-markdown one: all three
family-map files plan to `mode=none` with no changed packages, and
`crates/unexpected.txt` still raises `unmapped crate path`. Sabotage-checked by
breaking the new arm's path-depth test — 3 errors, restored to green.
Verified end to end: the planner run over this branch's own 14-file diff now
succeeds and selects `ironclaw_architecture`, `ironclaw_loop_host`,
`ironclaw_reborn_composition`. 44/44 planner tests pass.
Fixes #7100
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* revert(ci): back out the planner fix — #7084 already carries it, better
I hit `Reborn PR test planner failed: unmapped crate path: crates/AGENTS.md`
after adding one line to the crate-family map, diagnosed it as an unhandled
fail-closed arm, filed #7100 and fixed it. Then I checked whether other open PRs
touch those files — #7084 and #7065 do — and expected them to be red for the
same reason. **They are green**, which refuted the "any PR that edits them
fails" framing and sent me to look at why.
#7065 branched before the planner existed (#6952). **#7084 already modifies
`scripts/ci/reborn_pr_test_plan.py` and already fixes this**, in the same
function and the same arm I was editing:
if package is None:
# Markdown that belongs to no crate is prose, in the same class
# as `docs/` and `.claude/` … Depth-independent by construction,
# so it keeps holding for `crates/AGENTS.md` and for a future
# `crates/<family>/AGENTS.md` after the WS7 family move.
if path.endswith(".md"):
continue
with a regression test (`test_markdown_owned_by_no_crate_is_prose`) covering
`crates/AGENTS.md`. Their rule is **strictly better than mine**: mine keyed on
`path.count("/") == 1`, which would silently stop covering the file the moment
WS7 moves crates under family directories. Theirs is depth-independent.
So this reverts my planner change and its two tests, and drops the
`crates/AGENTS.md` edit that provoked it — #7084 is on the do-not-disturb list
and this would have collided with it line-for-line.
The guidance follow-up is recorded on the CHECKLIST WS6 row with the exact text
owed and the condition (#7084 landing) that unblocks it. #7100 is updated to
say it is already fixed rather than left implying open work.
Everything else on this branch is unchanged: the system-prompt asset eviction,
the markdown ownership gate, and the doc corrections all stand.
Refs #7100, #7084
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* review(ws6): statement-bounded include scan; fail-close the Rust-source walk
Second CodeRabbit round on #7099. Both findings verified against the code before
fixing; both were right.
**1. `markdown_include_sites` missed a nested argument macro.** Confirmed:
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/prompt.md"))
The first-`)` scan stopped at `(concat!(env!("CARGO_MANIFEST_DIR")` — before the
path — and reported clean.
Rather than teach the scan balanced-delimiter parsing (which then also owes
string-literal, raw-string and comment handling — each an independent silent
leak), the span is now bounded by the **statement**: from the macro-name
occurrence to the next `;`. Whatever the nesting, spacing or line breaks, the
path literal is inside that span. It also requires the name to be a whole
identifier followed by optional whitespace and `!`, so `my_include_str!` and a
plain `include_str_path` variable are not findings.
It over-reports rather than under-reports — a comment mentioning `.md` inside an
include statement is flagged — and says so. A false positive is a loud failure a
human clears in one line; a false negative is prompt content silently back in
the composition root.
Seven scanner unit tests now: single-line, multiline, nested argument macro,
whitespace before `!`, a comment inside the argument, uppercase `.MD`,
non-markdown (`builtin_capability_policy.toml`, which must stay clean), and
similar identifiers. Sabotage-checked against the real crate with the exact
nested form above: the gate fails and prints the flattened site.
**2. The file-count floor did not close the `rust_sources` hole.** Right — it
only catches an empty-ish walk; an unreadable directory *after* 50 files still
passed silently. `rust_sources` now panics on a failed `read_dir` and a failed
entry, matching what it already did for unreadable file contents — this is
consistency inside that function, not a new policy, and it hardens the three
other tests in the file that share it.
The floor is kept and re-justified for the case that stays silent even so: a
walk that reads a perfectly good directory which is no longer the crate. After
the WS7 family move relocates `crates/…` under family directories, a stale path
can resolve to something small and readable rather than erroring.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(arch): restore four tests my previous commit silently deleted
`fe641b7709` rewrote `reborn_composition_boundaries.rs` by replacing a *span*
between two doc-comment anchors. The two anchors were at opposite ends of the
file — `markdown_include_sites` near the top, `markdown_assets` near the bottom
— so the replacement swallowed everything between them:
- `composition_public_pub_use_surface_matches_snapshot`
- `extension_host_cluster_stays_internal`
- `reborn_binary_main_is_thin_bootstrap`
- `composition_crate_installs_installed_tier_only_through_registrar`
- helpers `composition_src_path`, `extract_pub_use_surface`, `has_module_decl`,
`is_test_module_file`, `strip_test_module`
It compiled and the file's own suite went green, because each deleted test left
with the helpers only it used — which is exactly why "the suite passed" is not
evidence. It was caught by diffing the function roster against `origin/main`
rather than by a test, and by the commit's own −301/+114 line count.
This restores the file from `origin/main` and re-applies the change with
targeted edits instead of a span replacement. The roster is now **purely
additive** against `origin/main` — 9 functions added, **0 removed**, verified
with `comm -23`:
- `composition_root_embeds_no_prompt_content` (the gate)
- `markdown_include_sites`, `markdown_assets` (helpers)
- 8 scanner unit tests
7 tests on `origin/main` -> 16 here. Both halves of the gate re-sabotage-checked
after the restore: a nested `include_str!(concat!(env!(…), "…default_system.md"))`
fails it, and a shipped `assets/prompts/s.MD` fails it.
Also fixes what `Fast deterministic checks` caught on `fe641b7709`: clippy's
`items after a test module` (the scan's test module now sits at the end of the
file, after every helper) and two `doc list item without indentation` warnings
(the doc comment is prose, not a list). `cargo clippy -p ironclaw_architecture
--benches --tests --examples --all-features` is clean.
The substance of `fe641b7709` is unchanged and still stands: statement-bounded
include scanning, and `rust_sources` failing closed on unreadable directories
and entries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* review(arch): skip Rust trivia when bounding the include statement
Third CodeRabbit round on #7099. Both findings verified, both real, both fixed.
**1. `.find(';')` could end the span before the path.** A semicolon inside a
comment above the argument (`// see the note; below`) or inside the path literal
itself (`"../a;b/prompt.md"`) terminated the scan early — and an ownership gate
that ends early goes quiet, which is the failure mode this gate exists to
prevent.
`statement_end_after` now finds the first `;` that actually terminates a
statement, skipping line comments, nestable block comments, normal strings with
escapes, raw strings with any number of hashes, and char literals (while not
mistaking a lifetime for one). It only has to locate a delimiter, not parse the
expression, which keeps it ~50 lines.
Three new tests, and the third is the one that keeps the fix honest: the span
must still *stop*, or a markdown path in the **next** statement would make every
non-markdown include a false positive. Sabotage-checked against the real crate
with a semicolon-in-comment form — the gate fails.
**2. `path.is_dir()` swallowed metadata errors in `rust_sources`.** Right:
`Path::is_dir()` returns `false` on an error, so an unreadable directory left
the walk silently. It now asks `entry.file_type()` and panics, matching
`markdown_assets`.
**Not done, with a reason rather than silently:** the suggested regression test
for "an unreadable directory beneath an otherwise readable workspace". The only
portable way to create one is `chmod 000`, which does not make a directory
unreadable for `root` — and the CI containers run as root, so the test would
pass locally and be vacuous in CI. A test that cannot fail where it matters is
worse than none. The invariant is instead carried by construction: every read in
both walks is `unwrap_or_else(panic!)`, with no `let Ok(..) else` and no
`is_dir()` left in either.
`reborn_composition_boundaries.rs` is 7 tests on `origin/main` -> 19 here, and
the function roster is still purely additive (`comm -23` empty). Full
`ironclaw_architecture` suite green; clippy `--all-features` clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* review(arch): reject symlinks in both composition ownership walks
Fourth CodeRabbit round on #7099, and it is right. `DirEntry::file_type()`
reports the **link's** type without following it, so a symlink pointing at a
source directory is neither `is_dir()` nor an `.rs` file: both walks stepped
over the entire subtree and the gate reported clean on source it never opened.
Same "uninspected reads as absent" failure the fail-closed reads added in the
previous round exist to prevent — one level further out.
`reject_symlink` now panics for either walk, naming the path and the two ways
forward. Rejecting is chosen over following deliberately: following needs
canonical-root containment plus cycle detection to be safe, and neither scanned
crate has ever contained a symlink (`find crates/ironclaw_reborn_composition/src
-type l` is empty). The panic is where that decision gets made on purpose rather
than silently.
Regression test `a_symlinked_subtree_fails_the_walk_instead_of_being_skipped`
builds a tempdir with a real source directory plus a symlink to it and asserts
**both** `rust_sources` and `markdown_assets` panic. `#[cfg(unix)]`, since the
workspace has a Windows lane and `std::os::unix::fs::symlink` is not portable.
Sabotage-checked: commenting out both `reject_symlink` call sites turns the test
red ("a symlinked subtree must fail the walk, not be skipped"); restoring them
returns 20/20.
`reborn_composition_boundaries.rs`: 7 tests on `origin/main` -> 20 here, roster
still purely additive (`comm -23` empty). Full `ironclaw_architecture` suite
green; clippy `--all-features` clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(event-store): stop leaking the Postgres driver in the public API (WS6)
CHECKLIST WS6 / PROPOSAL §6.3.2: "stop leaking `deadpool_postgres::Pool` in the
public API (wrap)". `ironclaw_reborn_event_store`'s public API now names
`deadpool_postgres` zero times; the driver survives only inside its private
`postgres_backed` module, which is where the TLS policy and pool construction
§6.3.2 assigns this crate actually live.
"Wrap" turned out to be three things, not one.
**1. Half the leak was dead code, so it is deleted rather than wrapped.**
`open_postgres_pool` and `open_postgres_pool_with_max_size` had exactly one
caller each — composition's `open_reborn_postgres_pool` and
`open_reborn_postgres_pool_with_max_size` — and those two had **zero** callers
anywhere in `crates/`, `tests/`, `tools/` or `scripts/`. A four-function
pass-through chain across two crates whose only remaining effect was to publish
a third-party type in two public APIs.
**2. The survivors take a carrier.** `open_postgres_pool_with_tls_options`
returns `ironclaw_filesystem::PostgresConnectionPool` and
`RebornEventStoreConfig::PostgresPool` holds one.
The newtype lives in `ironclaw_filesystem`, not in event_store, for two reasons:
it is the only crate `event_store`, `auth` and `composition` can all name
without a new dependency edge, and that crate *is* the Postgres substrate, so
the driver is chartered there (§11.2.6) rather than leaked. It is a carrier, not
an abstraction — `driver()` / `into_driver()` exist for code that runs SQL — and
it deliberately has no `Deref` (an implicit unwrap re-admits the driver into a
signature unnoticed) and a hand-written `Debug` that renders nothing. The
driver's own `Debug` prints its `tokio_postgres::Config`, which redacts the
password (`tokio-postgres-0.7.16/src/config.rs:766-776`) but still prints
`user`, `dbname`, `host`, `hostaddr`, `port` and `ssl_mode` — deployment
topology that a derived `Debug` on any holder would inherit.
**3. Stated residue: composition still names the driver, by charter.** §11.2.6
makes it "the one app-layer crate permitted a database driver", and it needs the
raw pool for `PostgresRootFilesystem::new` and
`CredentialRefreshLeaderLock::for_postgres`. It unwraps the carrier at exactly
one site (`factory.rs`, `open_postgres_pool_from_source`). Pushing the carrier
further down means changing `PostgresRootFilesystem::new`, which has **13 call
sites across 5 crates plus `tests/integration/support/builder.rs`** — a separate
test-wide slice, not this row. Recorded in both docs rather than left implied.
**Enforcement (new file, lands with the change):**
`crates/ironclaw_architecture/tests/reborn_persistence_driver_boundary.rs`
- a shrink-only ratchet on which crates may hold a *normal* `deadpool-postgres`
dependency (8 today, read from `cargo metadata`, not by eye), and
- a scan proving event_store names the driver only below its private
`postgres_backed` module — including that the module stays private, since a
`pub mod` would silently defeat the scan.
Both halves sabotage-checked: a planted
`pub fn sabotage(p: deadpool_postgres::Pool)` fails the second and names the
line; a planted `deadpool-postgres` dep on `ironclaw_projects` fails the first
and names the crate.
**Un-masking** (unfiltered `--list`, name-by-name, against `origin/main` in a
clean baseline worktree):
- `ironclaw_reborn_event_store` 71 → 71, roster identical
- `ironclaw_reborn_composition` 928 → 928, roster identical
- `ironclaw_filesystem` 296 → 296, roster identical
- `ironclaw_architecture` 206 → 208, exactly the two new gate tests
Deleting the four dead functions surfaced nothing, which is the evidence they
were dead. No existing test edited.
Guidance travels: `ironclaw_filesystem/CLAUDE.md` documents the carrier and its
two deliberate omissions; `ironclaw_reborn_event_store/AGENTS.md` records that
the driver cone is owned but not exported, and names the gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* review(arch): reject a symlink handed in as the walk root too
Fifth CodeRabbit round on #7099, and right again — the previous fix closed the
hole one level too late. `reject_symlink` only sees entries `read_dir` yields,
but both walks push their **root** onto the stack before that ever runs, so a
symlinked root was followed to its target silently. The regression test I added
covered symlinked children only.
`reject_symlink_root` now validates the root with `symlink_metadata` (which does
not follow) before either walk starts, reusing the same rejection so the message
and the policy stay in one place.
The regression test is extended rather than duplicated: it now also symlinks a
root and asserts **both** `rust_sources` and `markdown_assets` panic on it.
Sabotage-checked — removing the two `reject_symlink_root` calls turns it red
("a symlinked walk root must fail rust_sources, not be followed").
Roster still purely additive against `origin/main` (`comm -23` empty); 20 tests
in this file; full `ironclaw_architecture` suite green; clippy `--all-features`
clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* review(arch): widen the driver-boundary scan past its two blind spots
Three CodeRabbit threads on #7101, all naming the same real defect from
different angles, and all correct: `take(module_start)` stopped the scan at the
`mod postgres_backed` **header**, so the gate was strictly weaker than the three
places documenting it claimed.
Two blind spots, both now sabotage-fixtures rather than prose:
- anything **after** the module body in `lib.rs` — a `pub fn` there naming
`deadpool_postgres::Pool` kept the gate green;
- **every sibling file** in the crate (`coalescing_sink.rs`, `durable_log.rs`),
which the scan never opened at all.
The scan now reads every `.rs` file under `crates/ironclaw_reborn_event_store/
src/` minus the brace-matched **body** of the private module. The brace match is
trivia-aware (line comments, nestable block comments, strings, raw strings, char
literals) so a `}` inside a literal cannot end the body early and silently drag
the rest of the file into the exempt range — the same failure class one level
down. It panics on an unterminated body rather than exempting to end-of-file,
and asserts it saw at least two source files.
Four unit tests on the brace matcher: a mention inside the body is exempt, a
mention after the body is not, a brace in a literal does not end the body, and a
file without the module has no exempt range.
Sabotage-checked against the real crate for both former blind spots:
- `pub fn sabotage_after_body(p: deadpool_postgres::Pool)` appended to `lib.rs`
-> fails, naming `lib.rs:2215`
- the same appended to `coalescing_sink.rs`
-> fails, naming `coalescing_sink.rs:321`
Also corrected the prose the reviewer flagged as over-claiming, in both places:
`ironclaw_reborn_event_store/AGENTS.md` and the CHECKLIST WS6 row now say
"module **body**" and state that the scan covers every file in the crate, with
the earlier revision's blind spots recorded rather than quietly fixed.
Clippy `--all-features` clean (the scan's test module moved to the end of the
file for `items after a test module`); full `ironclaw_architecture` suite green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(extractors,observability): typed extraction failures and a one-dependency latency crate (WS6)
CHECKLIST WS6 row "extractors: typed error across the boundary + delete
caller-less `extract_text` (§6.4.10); observability: `json_value_bytes`
eviction (§6.2.5)". Measurements from #7102.
## extractors (§6.4.10)
Failures now cross the boundary as `ExtractionError`, not `String`, at both
public sites (`DocumentExtraction::Failed` and
`extract_document_text_by_filename`). Two variants: `UnsupportedType { mime }`
(nothing was attempted) and `NotExtractable { detail }` (an extractor ran and
could not produce text). `Display` renders the classification and nothing
else; `Debug` carries the payload.
That is not a shape change. The invariant — "carries the error reason for
logging only; callers render a model-safe marker, never this string" — lived
as a doc comment on one of the two boundary sites, and the *other* one leaked:
`ironclaw_extension_support`'s `read_file` interpolated the raw extractor
diagnostic into a model-facing safe summary (`coding/file.rs:325-329`) while
carefully redacting the path one argument earlier. With `Display` content-free
that call site is safe unchanged. Its regression test sits at the call site,
not on `Display`, because the wrapper composing the summary is what leaked.
`extract_text` and `TRUNCATION_MARKER` were both `pub` with zero external
callers; both are private now. The row only named the first. The second
mattered more: `ironclaw_agent_loop` and `ironclaw_mcp` each declare their own
`TRUNCATION_MARKER` with a different value, so it must be resolved by crate,
not by name. The census is exact — no crate writes `use ironclaw_extractors::…`,
so a full-path grep is complete. The private ZIP-safety enum was renamed
`ExtractionError` -> `ZipEntryError` to free the natural name.
## observability (§6.2.5) — delegated ruling, PROPOSAL §12.12 D-K
`json_value_bytes` and its `JsonByteCounter` are localized into the two
consumers; `serde_json` leaves the manifest with them, so the crate now holds
exactly one dependency, `tracing`.
The row's stated reason ("gravity-well hygiene") was wrong; the ruling
survives on a measured one. Of five call sites in extension_support, three
feed `ResourceUsage::set_output_bytes` — resource accounting, not a trace
field — so "it is a latency helper, in charter" is false. And sharing bought
no invariant: `output_bytes` is already computed three different ways in
production (this counter, `output.stdout.len()` in `ironclaw_scripts`,
`Value::to_string().len()` in `ironclaw_loop_host`), because each producer
measures what it produced. `ironclaw_common` was rejected (the crate the
restructure is actively narrowing) and `ironclaw_host_api` was rejected
explicitly rather than by omission (behavior in the contracts leaf is the
specific criticism already on record against it). Cost, stated: ~18 lines and
2 unit tests duplicated across two crates.
## Guidance and docs
New `AGENTS.md` for both crates (both rows asked for one). PROPOSAL §6.4.10
and §6.2.5 amended with dated notes quoting what they replace; §12.12 opened
as the Wave 4 delegated-decision log, continuing §12.11's lettering and
marking discipline. `families/domains.md` and `families/substrates.md`
updated, including a sharpened "never contains" test for observability and a
corrected security role for extractors (its failure type is a redaction
boundary; "none" was wrong).
## Tests
Unfiltered per-crate `--list`, before -> after: extractors 26 -> 28,
observability 2 -> 2, attachments 39 -> 39, host_runtime 1247 -> 1249,
extension_support 152 -> 156, architecture 206 -> 206. Nothing deleted;
nothing edited for content. Observability's two tests moved with the function
and are now duplicated in both consumers (2 -> 4 workspace-wide); its two
replacements pin what actually remains in the crate. Both new guards were
sabotage-verified: break the invariant, confirm red with the right message,
restore, confirm green.
Coverage floors untouched and deliberately so: the source crate
(`ironclaw_observability`) has no floor entry, and the destination
`ironclaw_host_runtime` gains covered lines rather than losing them.
Found and filed rather than patched: #7103 (the coding tool computes its JSON
byte count before checking whether latency tracing is on) and #7104 ("no text
found" classifies as `Failed` rather than `Empty`, so the model is told the
wrong thing about a valid but text-free document).
* fix(extractors): ASCII-only extension normalization + narrow the Debug-payload guidance
Review triage for #7106.
**CodeRabbit thread 2 — accepted.** `.claude/rules/types.md:170` and
`review-discipline.md:45` require case-insensitive external values to be
normalized with `to_ascii_lowercase()`, not Unicode case folding. Both
extension registries in this crate used `to_lowercase()`; the sibling
registry in `ironclaw_extension_support::coding::file`
(`should_extract_document_before_text`) already got it right, so this is the
outlier. Note it is a latent-hazard fix, not a live bug: the eight keys
(pdf/docx/pptx/xlsx/doc/ppt/xls/rtf) contain none of the letters a Unicode
fold can produce from a foreign codepoint, so I could not construct an input
where the two differ today. It removes the hazard for the next key added.
Test pins both halves: ASCII case-insensitivity still works, and a non-ASCII
extension is not folded into an ASCII key.
**CodeRabbit thread 1 — guidance tightened, code change refuted.** The
reviewer is right that this crate's doc told callers to `tracing::debug!(?error,
…)` without naming a ceiling, while `ironclaw_host_runtime/AGENTS.md:28`
forbids unredacted user content in that crate's logs. Both docs now say the
payload belongs in an operator log and nowhere else, and record what it
actually carries. The proposed code change is refused with measurement in
the PR thread: it would log strictly less than `main` does today.
* fix(extractors): the Unicode extension fold was a live bug, not a latent one
Correcting my own claim in
|
||
|
|
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
|