Repository navigation
cli-std: <cli> llm content rots — derive fact sections from in-binary sources of truth and gate the rest (llms.txt becomes generated) #9
Description
Problem
<cli> llm is the agent-facing entry point of every tool, yet its content
is the only fact-bearing surface in the repo with ZERO drift protection:
cli_std::llm::Topic.body is a hand-written &'static str baked at compile
time. Facts restated there (command inventories, API routes, env names,
backup destinations, feature availability) rot silently as the binary
evolves. The 2026-07-23/24 audit wave caught this exact failure class
repeatedly on sibling surfaces: backup docs claiming file/s3-only while
gs:// was unconditional (faberline/axiom#2483), rotation docs claiming restart-needed
while a 15s watcher existed (faberline/axiom#2480), a committed openapi.json stale by two
releases, a Dockerfile ARG stale by five. Every llms.txt is a third
hand-maintained copy of the same facts.
Design: derive facts, gate prose
-
cli-std: composed topics — extend the topic model so bodies are
sections, and fact-bearing sections are RENDERED AT CALL TIME from the
binary's own sources of truth:pub enum TopicSection { Prose(&'static str), // narrative only Generated { id: &'static str, render: fn() -> String }, }
Canonical generators every CLI gets nearly for free:
- command/verb inventory → the clap command tree (already powers --help)
- API topic → the same
routes_json()/openapi()the spec verb serves - backup destinations → a
SUPPORTED_SCHEMESconst exported by
service-backup(makes the tape: stale backup-destination docs claim file://+s3:// only — gs:// is unconditional and GKE-proven axiom#2483 class impossible) - feature availability →
cfg!flags; version →CARGO_PKG_VERSION - standard env names → the same constants the serve path reads
-
Drift gate — a shared cli-std test helper
(llm_topics_render_and_cross_check) required by the CLI convention:
every Generated section renders non-empty; per-CLI parity asserts where
independent sources exist (llm api section ≡spec --format routes≡
served/openapi.json) — the proven
openapi_committed_snapshot_matches_live_generationpattern,
generalized and made mandatory. -
Kill the third copy —
llms.txtbecomes generated output
(<cli> llm --format md > llms.txt) with a committed-snapshot parity
test, never hand-edited. -
Convention — CONTRIBUTING's CLI-convention section gains the rule
(inverse of the 2026-07-24 trace-context projection rule): hand-written
llm prose MUST NOT restate machine-derivable facts; facts are Generated
sections or they do not appear.
Rollout
| Order | Step |
|---|---|
| 1 | cli-std TopicSection + render support + the conformance helper (back-compat: existing static topics keep working). |
| 2 | lumen (actively being adopted) and tape migrate their fact-bearing topic content + llms.txt generation + parity tests. |
| 3 | Remaining CLIs (aw, vat, keep, relay, defer, sift, meter, guard, …) adopt per lane; the CLI convention doc makes it part of "a CLI is not done until". |
| 4 | Optional: an aw health docs-freshness signal once 1-3 prove the shape. |
Refs: faberline/axiom#2483 / faberline/axiom#2480 (the drift class), faberline/axiom#2491 (the projection-rule sibling for capability docs), lumen Agent Task Navigation faberline/axiom#1683 (cli_std::llm::v2 — the typed-protocol precedent showing the lib already evolves this direction).
Activity
chrischeng-c4 commentedon Jul 24, 2026
Design refinement from the owner discussion (2026-07-24):
Audience insight — the content is FOR LLMs, so machine-shaped output is preferable, not a compromise: structured dense blocks (route tables, flag inventories, scheme lists) beat polished prose for the actual consumer. The critical consumer is the agent WITHOUT repo access (an integrator's agent holding only the installed binary) — in-repo agents demonstrably bypass
llmand read source directly (this session's audits never invoked it once), but out-of-repo agents have no fallback, which is exactly where staleness poisons.Two grades of 'machine translation', pick per layer:
aw td create --from-sourcesemantic mirrors, kept honest bytd lock/code-check. The llm topics' narrative sections should be a projection of those TD docs (a build-time render step with a parity test), not a fourth hand-written copy and not a free-running build-time LLM summarize (nondeterministic, hallucination-prone, unreviewed). This closes the loop: source → semantic TD (gated) → llm narrative (projected) → llms.txt (generated), with exactly ONE hand-authored layer in the chain.chrischeng-c4 commentedon Jul 24, 2026
Build-time mechanism (owner constraint: the installed binary has NO repo access) — everything
llmserves must be self-contained in the binary:fn() -> Stringrenders from in-memory structures (clap tree,openapi(), exported constants) that ship inside the binary. No repo needed.build.rs(read TD → distill → OUT_DIR →include!) or directinclude_str!of the narrative blocks. This extends the repo's existing 'CLI is the in-binary form of the checked-in fixture' convention (k8s operator render/dockerfile renderare alreadyinclude_str!projections). Same-commit consistency is guaranteed by the td-lock/code-check gates that already bind TD ↔ source at commit time; the freshness/parity tests gate the release tag in CI.tape@0.4.10'sllmoutput describes exactly 0.4.10's behavior. Doc/binary version skew becomes structurally impossible, which no external doc site can offer.Chain: commit-gated TD ↔ source → build-time embed (narrative) + compiled renderers (facts) → CI parity gate → tag → self-contained binary.
chrischeng-c4 commentedon Jul 24, 2026
Third layer (owner follow-up): the topic TAXONOMY itself rots too — a tool grows a new subsystem and nothing forces a topic to exist for it or the outline to mention it. Design:
Topic registry with declared sources + bidirectional gates. Topics stay hand-curated (grouping/naming is taste and rots slowly) but become a thin declaration table: each
TopicSpeclists its sources (clap paths, TD docs) and its sections (Generated fact renderers, Projected narratives). Two conformance gates in cli-std:The default
outlinetopic becomes fully generated from the registry (topic index + verb inventory), so it is complete by construction.Final shape — hand-authored content shrinks to exactly two slow-rotting places, both gated: TD narrative bodies (td-lock/code-check) and topic grouping/naming (bidirectional coverage gates). Facts derived, narrative projected, taxonomy declared-and-covered.
chrischeng-c4 commentedon Jul 24, 2026
Deprioritized by owner decision (2026-07-24) — for integration specifically, the load-bearing surfaces are already machine-derived and gated: the contract surface is
<cli> spec(offline OpenAPI ≡ served ≡ generated clients, snapshot-parity tested; the one route drift found, faberline/axiom#2482, is fixed and gated) and the debugging surface is structured logs + traceparent correlation. llm narrative staleness costs agent onboarding speed, not integration correctness.Keep this issue as the recorded design (three-layer: derived facts / TD-projected narrative / declared-and-covered taxonomy) for when it becomes load-bearing — the trigger condition is out-of-repo AGENT-DRIVEN CLI usage at scale (agents holding only the installed binary and relying on
llmto drive it), not human-adjacent integration against the HTTP contract. No implementation scheduled.chrischeng-c4 commentedon Jul 24, 2026
Re-prioritized (owner decision, same day): the trigger condition is already met — an integrator is actively consuming the installed lumen binary (faberline/axiom#2456), so their agents' only self-documentation surface is
lumen llmtoday, not someday.Execution plan (MVP first, targeting lumen@0.4.25):
TopicSection::{Prose, Generated}with call-time rendering, back-compat with existing static topics, plus thellm_topics_render_and_cross_checkconformance helper.llm --format mdwith a committed-snapshot parity test.Ships with lumen@0.4.25 alongside the faberline/axiom#2489 fix so the integrator's next upgrade carries both.
feat(cli-std): call-time-generated llm topic sections with a freshnes…
test(lumen): DX contract command-template flags stay live against the…
chrischeng-c4 commentedon Jul 24, 2026
Lib-layer work complete (2026-07-24). Phase map:
TopicSection::{Prose,Generated}+render_sectioned+assert_topics_render029669f6c2(shipped lumen@0.4.25)SUPPORTED_SCHEMES(SchemeInfowithcfg!-derived availability) +sectioned_topic()c7ee43c8ba— also fixed TWO hand-coded scheme lists inside service-backup itself (from_urierror +fetch_backup_objectbail), the exact faberline/axiom#2483 drift classBack-compat:
service_backup::llm::topic()byte-identical (lumen's spec.rs compiles unchanged); CLIs migrate tosectioned_topic()at their own pace. Tests: service-backup 19/19 (+s3 21/21), cli-std 35/35, clippy -D warnings clean.Remaining before close: per-CLI adoption (lumen/beam/keep/defer/relay/tape wire sectioned topics + the drift gate; cross-app batch) and the llms.txt-generated piece where applicable.
feat(cli-std): derive service-backup scheme facts from SUPPORTED_SCHE…
feat(cli-std): call-time-generated llm topic sections with a freshnes…
test(lumen): DX contract command-template flags stay live against the…
feat(cli-std): derive service-backup scheme facts from SUPPORTED_SCHE…
fix(tape): derive backup-destination docs from SUPPORTED_SCHEMES — gs…
feat(tape): wire the sectioned backup llm topic into the tape llm verb (
fix(tape): derive backup-destination docs from SUPPORTED_SCHEMES — gs…
feat(tape): wire the sectioned backup llm topic into the tape llm verb (
chrischeng-c4 commentedon Oct 23, 2026
Goal
Finish the part of #9 that lives in core: the
llms.txtsnapshot-parity helper and the written convention. TheTopicSectionmodel,render_sectioned, andassert_topics_renderare already incli-std(see the lib-layer comment on #9). What is missing is (a) a shared helper that checks a committedllms.txtequals the CLI's ownllm --format mdoutput, so the third hand-kept copy cannot drift, and (b) the rule incrates/cli-std/CONTRIBUTING.mdthat llm prose must not restate machine-derivable facts. Per-CLI adoption in other repos is out of scope here.Acceptance
crates/cli-std/src/application/llm.rsgainspub fn assert_llms_snapshot_matches<T: RenderableTopic>(project: &str, version: &str, topics: &[T], committed: &str): it renders every topic the same way the mdllmoutput does and panics with a message naming the first differing line (or the first missing/extra topic) when the result differs fromcommitted. Trailing-whitespace-only differences at end of file are ignored.assert_topics_renderincrates/cli-std/src/api/llm.rs.crates/cli-std/src/application/llm/tests.rscover: a matching snapshot passes; a snapshot with one changed line panics and the message names that line; a snapshot missing a topic panics; works for both&[Topic]and&[SectionedTopic].crates/cli-std/CONTRIBUTING.mdgains a "CLI llm convention" section that states: fact-bearing llm content is aGeneratedsection or does not appear;llms.txtis generated from<cli> llm --format mdand checked withassert_llms_snapshot_matches; every CLI callsassert_topics_render. The section links the README capability, not a design tree.crates/cli-std/README.md## Capabilitiesmentions the new helper if it listsassert_topics_render; otherwise unchanged.Never
llms.txtfile, includingcrates/cli-std/llms.txt.render,render_sectioned,Topic, orSectionedTopic(callers inservice-backupand downstream CLIs pin them), and do not rename existing public items..github/, or the rootCONTRIBUTING.md.Gate
cargo test -p cli-std --libcargo test -p service-backup --libcargo clippy -p cli-std --all-targets -- -D warningscargo fmt --check -p cli-stdchrischeng-c4 commentedon Oct 24, 2026
Scope of this spec
Phase 1 of this issue is already on
main:TopicSection,SectionedTopic,render_sectioned, andassert_topics_render(crates/cli-std/src/application/llm.rs), andservice-backupalready derives its destinations fromSUPPORTED_SCHEMES. This spec covers the part that still lives in this repository: the committed-snapshot parity helper (design point 3) and the convention rule (design point 4). Migrating individual CLIs and generating theirllms.txthappens in the downstream repos and is out of scope here.Goal
Give every CLI a shared, mandatory way to prove its committed
llms.txtequals the output of<cli> llm --format md, and write down the rule that hand-written llm prose must not restate machine-derivable facts. Without the helper each CLI has to invent its own parity test, which is how the third copy of the facts rots.Acceptance
crates/cli-std/src/application/llm.rsgainspub fn assert_snapshot_matches(committed: &str, generated: &str, regen_hint: &str). It compares the two strings after normalizing line endings (\r\n->\n) and a single trailing newline. On a mismatch it panics with a message that names the first differing line number and includesregen_hint(the command that regenerates the file).crates/cli-std/src/api/llm.rsnext toassert_topics_render, and the module doc of that file mentions it.crates/cli-std/src/application/llm/tests.rs: equal strings pass; strings that differ only in line endings or one trailing newline pass; a differing line panics with the line number and the hint (usecatch_unwindlikeconformance_helper_catches_empty_generated_section).crates/cli-std/CONTRIBUTING.mdgains a## CLI llm conventionsection stating: (1) fact-bearing content is aTopicSection::Generatedsection or it does not appear; (2) hand-written prose must not restate machine-derivable facts; (3)llms.txtis generated output, checked byassert_snapshot_matches, never hand-edited; (4) each CLI's tests callassert_topics_renderandassert_snapshot_matches.crates/cli-std/README.mdevidence line is unchanged except for naming the new helper wherellmis listed, if that list names helpers; otherwise leave it alone.Never
Topic,SectionedTopic,TopicSection,render,render_sectioned, or the golden output bytes tests.crates/cli-std/llms.txt(cli-std has nollmcommand to generate it from).crates/cli-std/Cargo.toml.assert_topics_rendersignature or messages.Gate
cargo test --locked -p cli-std cargo clippy --locked -p cli-std --all-targets -- -D warnings cargo doc --locked -p cli-std --no-depschrischeng-c4 commentedon Oct 24, 2026
Work submitted in #72; waiting for required CI on the current head.