Skip to main content
docs-system | Docs Systemdocs-system in the source-owned Docs System documentation.Docs Systemreferencedocs-systemreferencedeveloperoperatorcontribute-maintainreference

docs-system

The public contract and reusable tooling behind the unified beyond10x documentation site. Docs System validates repository-owned publication declarations, safely collects their passive content, builds deterministic discovery and change data, and supplies shared React renderers and GitHub Pages compatibility routes.

Consumer repositories pin an immutable Git commit. This package is private to the Git transport and is not published to npm:

{
"dependencies": {
"@beyond10x/docs-system": "github:beyond10x/docs-system#0123456789abcdef0123456789abcdef01234567"
}
}

Documentation sources​

Every participating repository owns a root b10x.docs.yaml. b10x-docs/v4 keeps the v3 data-only source and central-delivery boundary and adds explicit experience and page contracts. It declares:

  • the repository display name and public documentation surfaces;
  • canonical route bases, publication state, experience IDs, relationships, and document defaults;
  • allowlisted Markdown/MDX, structured data, blog, asset, OpenAPI, and JSON Schema inputs;
  • an optional fixed vocabulary of shared components; and
  • the Website publisher and beyond10x.github.io delivery origin.

b10x-docs/v5 is v4 plus declared navigation and built assets. source.navigation.sidebar is either autogenerated/flat or a list of categories (label, optional collapsed, items) whose leaves are document paths relative to source.root; source.navigation.landing names the document served at the route base; source.navigation.menuWithheld lists routable documents kept out of the menu; the surface-level builtAssets list names built files the repository's own credential-free workflow commits. Validation refuses any sidebar leaf, landing or withheld path that source.documents does not publish (or that lies under a directory the collector never enters), any non-canonical or repeated path, a built asset that is a glob, a directory or under a directory the collector never enters, and a withheld document that is also a sidebar leaf, naming the path; invisible characters in a refused path are shown as \uXXXX. A withheld document may be the landing document. The public registry carries a v5 surface in the v4 shape, without the sidebar tree, landing, withheld documents or built assets.

Publication answers whether and where documentation is delivered. Access answers who can use an adoption path or artifact; publishing a page never implies that its required product artifact is public. Versions 1 through 3 remain byte-immutable and readable during migration. Validate any supported contract with:

b10x-docs validate b10x.docs.yaml changes/*.yaml sources.lock.json redirects.yaml

The v3/v4 collector never imports repository code. It refuses path traversal, symlinks, executable MDX, undeclared components, invalid source types, unmatched declarations, duplicate outputs, and duplicate specification routes. Whole-line MDX comment expressions and validated trailing explicit heading ids are accepted as inert Docusaurus syntax; inline or executable expressions remain refused. Validation never rewrites those markers, so collected and copied files retain their original bytes. The collector can validate and hash in place or copy only the declared bytes:

b10x-docs collect --manifest b10x.docs.yaml --repository-root . \
--index-out collection.json --document-index-out documents.json --out .generated/sources

The deterministic index contains every repository-relative source path, staged output path, byte size, SHA-256 digest, and one aggregate contentSha256. A b10x-sources/v1 lock binds that digest and the manifest digest to an exact 40-character Git commit. b10x-docs-collection/v1 and b10x-sources/v1 are unchanged by v4; effective page metadata is emitted in the separate b10x-doc-index/v1 sidecar.

Normalized producer bundles​

b10x-docs-bundle/v1 is the immutable handoff from source CI to the aggregate Website. Its root contains exactly:

bundle.json
b10x.docs.yaml
collection.json
tree/<repository-relative source path>

tree/ is the union of every sourcePath in b10x-docs-collection/v1 and every regular changes/**/*.yaml impact record. The sorted bundle.json.files inventory binds each path, byte size, and SHA-256 digest. contentSha256 is the SHA-256 of that inventory's compact canonical JSON; manifestSha256 and collectionSha256 bind the two exact input files. The builder also independently recomputes collection.json.contentSha256, refuses overlaps, traversal, symbolic links, non-regular files, unsupported change schemas, and changed source bytes, and creates only a new output directory. The JSON Schema is available at schema/b10x.docs.bundle.v1.schema.json and through the @beyond10x/docs-system/schema/bundle/v1 package subpath.

Build and independently validate a bundle with the Rust CLI:

node dist/cli.js collect --manifest b10x.docs.yaml --repository-root . \
--index-out .generated/collection.json
cargo run --locked --bin b10x-docs-bundle -- build \
--repository-root . --collection .generated/collection.json \
--out .generated/bundle --commit "$GITHUB_SHA" --producer-run-id "$GITHUB_RUN_ID"
cargo run --locked --bin b10x-docs-bundle -- validate \
--bundle .generated/bundle --commit "$GITHUB_SHA" --producer-run-id "$GITHUB_RUN_ID"

The manifest always records the producer run. artifactId and artifactDigest are an optional pair for contexts where both already exist; a normal upload learns them only after construction and records that post-upload identity in Atlas. The credential-free composite action at .github/actions/bundle/action.yml wraps collection and construction for exact Git-pinned callers. Producer workflows must trigger for b10x.docs.yaml, every manifest-declared source path, and changes/**/*.yaml, or impact and release feeds can become stale.

Bundle versions move consumer first. Website and Atlas first accept versions N and N+1, producers then emit N+1, durable published inputs migrate, and support for N is removed only in a later control-plane change. A v1 validator deliberately refuses an unknown version with this ordering in its error rather than guessing compatibility.

Experiences and page metadata​

The Website owns one b10x-experiences/v1 catalog. Repository v4 surfaces and pages reference its stable experience IDs. An experience contains ordered adoption paths. Each path declares support, its own access requirement, an optional URL, and every required artifact ID. Artifacts independently declare kind, availability, and access.

Effective path access is the most restrictive of the path and all required artifacts:

public < account-required < approval-required < private

A path is actionable only when support is supported, preview, or experimental, every required artifact is available, effective access is not private, and the path has a URL. Otherwise the evaluator returns typed blockers and a human-readable explanation for a status-only presentation. Immutable v1-v3 adoption actions normalize with unspecified access/support, no artifacts, and actionable: false; unspecified is never accepted in a v4 declaration.

V4 documentDefaults always declare audiences, experience IDs, support, and access. A page may replace them by placing a b10x-doc-page/v1 value under the ordinary top-level b10x frontmatter property; unrelated Docusaurus frontmatter remains untouched:

---
title: Validate a documentation source
b10x:
schema: b10x-doc-page/v1
audiences: [operator]
experienceIds: [validate-documentation]
support: preview
access: account-required
---

Validate embedded page metadata directly with b10x-docs validate-page docs/example.md. Node orchestration can use readExperienceCatalog, normalizeManifestExperiences, resolveDocumentPageMetadata, and buildDocumentPageIndex from the Node-safe experiences, documents, and manifest package subpaths.

Node orchestration imports the dedicated server-safe subpaths. The package root also exports React components and therefore belongs in Docusaurus, not a plain Node collector process:

import {readManifest, readSourceLock} from '@beyond10x/docs-system/manifest';
import {collectManifestSources} from '@beyond10x/docs-system/collector';
import {writeRedirectMap} from '@beyond10x/docs-system/redirects';

Discovery, changes, and compatibility​

Generate or check the standard marked README entry block rather than hand-writing cross-ecosystem links:

b10x-docs readme --manifest b10x.docs.yaml --file README.md
b10x-docs readme --manifest b10x.docs.yaml --file README.md --check

b10x-change/v2 adds typed affected repositories, surfaces, components, and APIs. Existing v1 change documents remain valid. Snapshot generation keeps explicit impact records and unenriched technical releases in distinct typed channels while retaining a deterministic combined ledger:

b10x-docs snapshot --registry-out ecosystem.json --ledger-out changes.json \
--rss-out changes/feed.xml --json-feed-out changes/feed.json \
--release-facts release-facts.json ../*/b10x.docs.yaml ../*/changes/*.yaml

Generate repository Pages compatibility façades from b10x-redirects/v1:

b10x-docs redirects --map redirects.yaml --out public --alias-root unified-artifact

HTML routes receive deterministic canonical/noindex redirect pages that preserve the query and fragment. RSS, JSON, OpenAPI, schemas, and downloads use byte-preserving static aliases instead of HTML redirects.

Shared components​

  • PageHeader and SectionHeader for consistent page hierarchy
  • CardGrid, ContentCard, ProjectCard, and AdoptionCard
  • FactGrid, SearchField, and FilterChipGroup for discovery views
  • Callout with note, success, warning, and danger tones; BoundaryNotice is its compatibility wrapper
  • CodeExample, CommandExample, and CodeTabs
  • DataCatalog and DependencyGraph for typed repository-owned data
  • Diagram for Mermaid source or generated SVG; DiagramFrame for an existing renderer
  • ScrollableTable for native tables with keyboard access to overflowing columns
  • OpenApiReference and JsonSchemaViewer for read-only OpenAPI 3.1 documents
  • StatusBadge and EcosystemSwitcher
  • EcosystemFamilyGateway for a metadata-driven public entry experience
  • ChangeTimelineEntry for ecosystem impact feeds

Import @beyond10x/docs-system/tokens.css once. Components use stable b10x-* class names; project identity is data, while Website owns the unified shell. The semantic light/dark tokens meet at least WCAG AA contrast for normal text and 3:1 for component boundaries. Existing --b10x-ink, --b10x-text, and related names remain aliases; new integrations should prefer the --b10x-color-* tokens. Footer shell colors are available as --b10x-footer-*.

Every Docusaurus consumer should also load the shared Prism grammar list. It covers Bash, C/C++, Go, HTTP, Python, Rust, shell transcripts, TOML, YAML, and the other languages present in public repository documentation:

import {PRISM_ADDITIONAL_LANGUAGES} from '@beyond10x/docs-system/code';

export default {
themeConfig: {
prism: {additionalLanguages: [...PRISM_ADDITIONAL_LANGUAGES]},
},
};

Use normalizeMarkdownFenceLanguage when preparing repository-owned Markdown. It makes common aliases deterministic: sh becomes bash, console becomes shell-session, and yml becomes yaml. Unknown project-specific grammars are retained instead of silently losing highlighting. describeMarkdownFenceLanguage adds the canonical visible label and distinguishes commands, terminal transcripts, plain output, and source. CommandExample always uses shell-session; CodeExample normalizes the requested language. The shared tokens apply the same labelled code surface to ordinary Docusaurus Markdown fences; a line wider than the block wraps inside it with a 2ch hanging indent, so nothing is cut at the edge.

OpenApiReference is an embeddable section, never a page landmark. It defaults to an h2 and can sit under an existing documentation h2 without introducing another h1 or main:

<OpenApiReference document={openapi} sourceUrl={sourceUrl} headingLevel={3} />

Diagrams retain their full canvas inside one bounded, focusable, two-axis keyboard-scrollable viewport. Diagram centers the initial view after Mermaid or SVG content is measured. A dependency graph always supplies a node and relationship list; generic diagrams can provide the same complete alternative with alternative. Set minWidth for a dense graph without adding a second viewport; use initialPosition="start" only when reading order should begin at the canvas origin:

<DependencyGraph
nodes={nodes}
edges={edges}
title="Public repository relationships"
description="Provider and consumer relationships declared by owning repositories."
minWidth="112rem"
/>

DiagramFrame wraps an existing renderer without calling Mermaid itself. It preserves the child's accessible SVG description, uses the visual's intrinsic width, and starts at the canvas origin by default. Nested frames reuse the outer viewport, so a theme can frame ordinary Mermaid fences without adding another viewport to Diagram or DependencyGraph:

<DiagramFrame title="Request flow" description="A request passes through admission to execution.">
<OriginalMermaid value={source} />
</DiagramFrame>

ScrollableTable accepts native table props and children, including captions and header scopes. It adds a focusable, named scroll region and visible keyboard instructions only when columns overflow. Use label to name the region when the surrounding context is insufficient:

<ScrollableTable label="Operation outcomes">
<caption>Operation outcomes</caption>
<thead><tr><th scope="col">Outcome</th><th scope="col">Meaning</th></tr></thead>
<tbody><tr><th scope="row">Refused</th><td>No provider request was sent.</td></tr></tbody>
</ScrollableTable>

The family gateway accepts either the registry or its surface array. It treats an ungrouped front-door surface as Start, derives membership and item order from surface.source.navigation.group/order, and takes family order from the shell. Families not named by the caller follow in lexical order, so membership and taxonomy never live in component code:

<EcosystemFamilyGateway
registry={registry}
current="harness/docs"
familyOrder={['Foundation', 'Build', 'Services', 'Products']}
/>

For non-React preparation code, deriveEcosystemNavigation is available from the Node-safe @beyond10x/docs-system/navigation subpath. It also reports ungrouped surfaces rather than silently assigning them to an invented family.

Gate​

npm ci --ignore-scripts
npm run gate

Product sites​

A product repository (Canon, ELS, Commission, Loom, a2a) can turn its Docusaurus site into a product front page with one wrapping call. Documentation pages keep their Markdown and inherit the same tokens.

import {withProductSite} from '@beyond10x/docs-system/product-site';

export default withProductSite(config, {landing: './product.json', mark: 'C'});

Options: landing, product (canon, els, loom, commission, ess or a2a: the signature hue on the navbar mark, kicker dot and hero emphasis), mark, favicon (a path under one of the site's static directories, passed to Docusaurus as favicon; a favicon set in the config wins), fonts (default on) and admonitionGuard (default on: the build fails when a raw :::kind Title line reaches a page).

Colour roles are reserved and validated per mode: status is an ordinal ramp with ○ ◐ ● glyphs, truth values carry ✓ ? ✕, protocol kinds keep the graph colours, and the product hue is identity only. In docs, table cells and inline code that are exactly TRUE, FALSE, UNKNOWN, shipped, decided or planned become chips, and a protocol kind becomes a chip in a table cell (in prose it stays code; use <Kind> for a kind chip there). Short inline code with a space or hyphen, such as cargo install, stays on one line; :::shipped[Title] and admonitions titled "Shipped …", "Decided …" or "Planned …" take the status tone. Recorded terminal sessions may carry tones ({"passed:": "true"}); exits show in a gutter. protocol/1 fences get a kind gutter. Sites using @docusaurus/theme-mermaid get diagrams themed from the tokens and should drop their own mermaid.theme.

The wrapper adds the product plugin (the shared tokens plus product-components.css and product-theme.css, a theme path that registers the product MDX components, status admonitions and the @theme/ProductLandingPage route component, and a not-found page linking to the main doc and the home page, with a meta description and noindex), the font directory to staticDirectories, a default 1200 × 630 share card at <baseUrl>b10x-social/card.png as themeConfig.image (a site that sets themeConfig.image keeps its own), a generated SVG favicon at <baseUrl>b10x-favicon.svg (the mark in white on the product's ink, ringed with its hue, or on the family green without a product) when neither the config nor the favicon option names one, an empty .nojekyll at the root of the build output when the build has none, the chips pass and status admonition keywords to the docs and pages plugins, Prism themes whose every token clears 4.5:1 on its code surface with the kind-gutter magic comments, a console Prism language (a fence of bare commands is highlighted as Bash with a drawn $ prompt that is neither selected nor copied; a transcript that writes its own prompts is highlighted as shell-session), and a dark default that still follows the reader's system setting. The landing page renders at /, so the docs plugin moves to routeBasePath: 'docs'.

product.json is a b10x-product-landing/1 document: the promise (one entry per line, the last one emphasised), the lede and actions, optional hero art and KPI row, and ordered sections of kind features, flow, status, protocol-graph, domain-graph, terminal or related. A section may name a JSON file instead of embedding it; the plugin reads and checks it at build time. Every field below is optional and additive: a site that sets none of them renders as before.

FieldShapeEffect
product.descriptiona string of at most 160 charactersthe home page's meta and og:description; without it the lede, cut at the last whole sentence or word that fits
product.art{kind, data or session, caption?}, kind terminal, protocol-graph, domain-graph, case, code-pairhero aside drawn from that file; replaces product.terminal
product.kpiscount names or {count, label?}: entities, relations, lifecycles, nodes, actions, evidence, claims, outcomes, obligations, commands, capabilities, shipped, decided, plannedKPI row; graph counts read the art, then the first graph section; status counts read the status section; a count without a source fails the build
status section itemsa path to a b10x-status/1 filethe strip and <StatusTable data={…}/> read the same file
related section tools[]{id, relation, via?}, id canon, els, loom, commission, ess, a2a; relation uses, used-by, beside, specifies, specified-byname, mark, hue, description and link from the family registry
features items[].icona protocol kindthe kind's glyph heads the card

With product set, the footer gains the family strip (current product highlighted) and a "This build" line naming the docs-system commit (from B10X_DOCS_SYSTEM_REVISION, the site's package-lock.json pin, or this checkout), and the navbar wordmark gets a product switcher; drop the hand-written family footer column. Documentation pages whose front matter sets status (shipped, decided, planned), lede or source (with optional source_url and kicker) open with a page header whose kicker is "Category · n of m" from the sidebar ("n of m" is dropped for a one-page category; a front-matter kicker replaces the whole kicker); other pages are unchanged. With trailingSlash: false the build writes x/index.html for every x.html: a copy of the page with a canonical link to x that moves to x only when the path ends in /, so no server order can make it redirect to itself (trailingSlashRedirects: false turns it off).

Every input that can be derived is generated by the owning repository's Rust generator, never typed:

InputFormatDerived fromSchema subpath
Terminal sessionb10x-terminal/1running the commandsschema/terminal/v1
Protocol graphb10x-protocol-graph/1canon compile (canon-ir/1)schema/protocol-graph/v1
Domain graphb10x-domain-graph/1ess specify compileschema/domain-graph/v1
Statusb10x-status/1the repository's status recordschema/status/v1
Case cardb10x-case/1evaluator runs, for example canon evaluateschema/case/v1
Code pairb10x-code-pair/1a source excerpt and the generated excerpt, quoted verbatimschema/code-pair/v1
Session chartssession-composition-instance/0-dummya session recordschema/session-composition/v0

A protocol graph has nodes of kind action, evidence, claim, outcome and obligation (sorted by kind, then name; ids <kind>:<name>) and edges produces (action → evidence), establishes (evidence → claim, qualifier = result), supports (claim → claim), requires (claim → outcome) and gates (claim → action, a precondition, traced on hover only). Predicates keep the canon-ir/1 shape (all, any, not, claim, evidence, input, first, before, after, since).

The order forms come from Canon 0.3.0's canon-ir/1, for protocols whose obligations depend on the order of evidence. {"first": {"kind": "test_result", "result": "fail"}} holds when the first evidence of that kind has that result, and reads as first evidence = fail: test_result. {"before": {"first": "test_result", "then": "implementation_change"}} holds when evidence of the first kind precedes evidence of the second, and reads as evidence test_result before implementation_change. Either may name an artifact in subject (a non-empty string, written only when present), which the line ends with as about <subject>. kind and result, and first and then, are required non-empty strings; no other field is accepted. parseProtocolGraph refuses a before whose first and then name the same kind, naming the path; the schema cannot compare the two and accepts it. Both forms go wherever a predicate does, including an obligation's applicable_when and inside all, any and not.

Canon 0.7.0 adds a third order form, for a rule that reads a later record. {"after": {"first": "tests_change", "then": {"kind": "test_result", "result": "fail"}}} holds when evidence of the later kind with that result was observed after the first evidence of the first kind, and reads as evidence = fail: test_result after tests_change. first, then.kind and then.result are required non-empty strings, subject is optional as above, and no other field is accepted at either level. The two kinds may be the same. It goes wherever a predicate does.

Canon 0.8.0 adds a fourth, for a rule that reads a record since the latest one of a kind. {"since": {"last": "tests_change", "then": {"kind": "test_result", "result": "fail"}}} holds when evidence of the later kind with that result, at the same subject revision as the latest evidence of the last kind, was observed after that record, and reads as evidence = fail: test_result since tests_change. last, then.kind and then.result are required non-empty strings, subject is optional as above, and no other field is accepted at either level. The two kinds may be the same. It goes wherever a predicate does.

Case inputs follow canon-ir/1 too, so one protocol can describe several kinds of case. An optional top-level case declares them: {"inputs": {"change_kind": {"description": "…" or null, "values": ["bugfix", "feature"]}}}. An input predicate leaf tests one, {"input": {"id": "change_kind", "in": ["bugfix"]}}, and reads as input change_kind in bugfix; it may name only a declared input and its declared values. A node of kind obligation may carry applicable_when, a predicate saying when the obligation applies; any other kind carrying it is refused. The figure lists the case inputs and their values under the caption, and shows an obligation's "applicable when" lines before the predicate that discharges it, in the tooltip and in the text listing. A graph without these fields validates and renders as before.

The graphs use the layered layout in graph-layout: the caller fixes the columns, the layout inserts dummy nodes for long edges, reduces crossings by barycentric sweeps and transposition, and places nodes by exact isotonic regression. It is synchronous and pure, so the static HTML already contains the finished SVG. ELK.js was not used because it lays out only asynchronously (no prerender) and adds about 1.4 MB; dagre cannot pin columns by kind without phantom nodes.

Node-kind colours follow the dataviz reference palette, validated per mode: adjacent CVD ΔE ≥ 9.1 light and ≥ 8.4 dark. The session charts colour protocols blue, amber, rose and green, validated all-pairs because any two protocols can sit side by side in a timeline: normal-vision ΔE ≥ 19.6 light and ≥ 19.3 dark; the dark amber/green pair sits in the CVD 6–8 band (ΔE 6.9), so bars keep a surface gap and every chart also carries a text or table view.

examples/product-site is the reference site: every component with real inputs (Canon protocols, the Commission ESS model, a recorded canon session, recorded canon evaluate and ess generate excerpts, a role-named session record), served below /product-site/ with trailingSlash: false. Build it from a Docusaurus 3.10 installation with docusaurus build --out-dir <dir> in that directory; with this checkout as the installation, link it first (ln -s ../.. node_modules/@beyond10x/docs-system).

ESS contract viewer​

EssContractViewer renders ESS's existing ess-docs/1 documentation projection. It provides page navigation, searchable declarations, typed cross references, lifecycle and relationship diagrams, shareable section links, and source provenance. It is passive: it executes no command, accepts no credential, and makes no claim that a declared behavior is implemented.

Generate the input with ESS, using the shared output root:

ess generate --path systems/example --kind docs-ir --out generated

In a Docusaurus host using the shared tokens and Mermaid theme:

import {EssContractViewer} from '@beyond10x/docs-system/ess-contract-viewer';
import contract from './generated/docs-ir/document.json';

export function ContractReference() {
return <EssContractViewer document={contract} id="example" sourceUrl="/data/example/document.json" />;
}

Keep id stable and unique when embedding several viewers. title, sourceRepository, and initialPage are optional. Use Mermaid's securityLevel: 'strict' and flowchart.htmlLabels: false; active directives and external diagram resources are refused by the document reader. Unknown link destinations remain visibly unresolved. Unsupported document formats and malformed structures render an error instead of a partial contract. Provenance is displayed as supplied by ESS; the viewer does not recompute model digests.

Node preparation code imports parseEssDocument, essText, and resolveEssTarget from the Node-safe @beyond10x/docs-system/ess-document subpath. Declare the generated JSON in source.data.include. A v4 manifest can also name EssContractViewer in source.components; the immutable v3 component vocabulary stays unchanged. Website recognizes ess-docs/1 data and supplies the shared viewer, a static contents summary, and the exact JSON download. Other data catalogs retain their existing rendering.

The unit suite consumes complete ESS 0.77.0 projections from Mandate and Connectors. Browser checks build a standalone Docusaurus fixture in this repository and exercise both consumers:

npx playwright install chromium
npm run test:browser

Set B10X_CHROME_BIN to use an installed Chromium browser. ESS_VIEWER_PREVIEW_URL can instead exercise a running Mandate preview with a /contracts/ route. The browser test is separate from npm run gate because it requires a browser installation.

Apache-2.0. See LICENSE.