/* Docs content — dense technical pages grounded in the real repositories.
 * Each page declares a `toc` (drives the right rail + scroll-spy + search index)
 * and a render(C) returning the article body. Exports:
 *   window.EQ_DOCS        — { id: page }
 *   window.EQ_DOC_ORDER   — ordered page ids (prev/next + left nav order)
 *   window.EQ_SEARCH      — flat search index [{pageId, sectionId, ...}]
 */
const H = (id, children) => <h2 id={id} key={id}>{children}</h2>;

const EQ_DOCS = {
  /* ------------------------------------------------------------------ */
  'quick-start': {
    group: 'stirrup', title: 'Quick start', kicker: 'stirrup',
    lead: 'stirrup is a production-grade coding harness for safe autonomous operation. This page takes you from a fresh build to a sandboxed execution run.',
    toc: [
      { id:'modes', label:'Two modes', snippet:'harness for local one-off tasks; job connects to a control plane as a Kubernetes Job.' },
      { id:'build', label:'On your machine', snippet:'just build produces ./stirrup and ./stirrup-eval; a bare invocation lands in read-only planning mode.' },
      { id:'pipeline', label:'Composing configs', snippet:'run-config emits resolved JSON; each pipeline stage layers one adjustment before the final run.' },
      { id:'k8s', label:'Running on Kubernetes', snippet:'Apply the standing objects, then run the agent in a sandbox Pod under gVisor.' },
    ],
    render: (C) => {
      const { CodeBlock, Callout } = C;
      return (<React.Fragment>
        {H('modes','Two modes of operation')}
        <p>stirrup has two main modes. <code>stirrup harness</code> is for local, one-off
        tasks that need no control plane; <code>stirrup job</code> connects to the control
        plane at <code>CONTROL_PLANE_ADDR</code> and is intended to run as a Kubernetes Job.</p>

        {H('build','On your machine')}
        <p>Build produces two binaries. The simplest invocation lands in read-only
        <code>planning</code> mode by default — the first safety ring is least privilege
        at the entrypoint:</p>
        <CodeBlock label="shell">
          <span><span className="eq-code__prompt">$ </span>just build <span className="tok-comment"># produces ./stirrup and ./stirrup-eval</span></span>{'\n\n'}
          <span>ANTHROPIC_API_KEY=… ./stirrup harness \</span>{'\n'}
          <span>{'  '}<span className="tok-flag">--prompt</span> <span className="tok-str">"Outline a fix for the failing test in main_test.go"</span></span>
        </CodeBlock>
        <Callout kind="note" title="Read-only by default">
          Opt in to writes and shell access with <code>--mode execution</code>. A bare
          invocation can read and plan, but never edits or runs a shell.
        </Callout>

        {H('pipeline','Composing configs in a pipeline')}
        <p>For workflows that build a <code>RunConfig</code> incrementally,
        <code>run-config</code> emits the resolved JSON without invoking the loop, and
        <code>harness</code> reads a base config from stdin — so each stage layers one more
        adjustment before the final stage runs the agent:</p>
        <CodeBlock label="pipeline" prompt="">
          <span>stirrup run-config <span className="tok-flag">--model</span> claude-opus-4-7 \</span>{'\n'}
          <span>{'  '}| stirrup run-config <span className="tok-flag">--max-turns</span> 100 \</span>{'\n'}
          <span>{'  '}| stirrup run-config <span className="tok-flag">--mode</span> execution <span className="tok-flag">--executor</span> container \</span>{'\n'}
          <span>{'  '}| stirrup harness <span className="tok-flag">--prompt</span> <span className="tok-str">"refactor module X"</span></span>
        </CodeBlock>

        {H('k8s','Running on Kubernetes')}
        <p>Apply the standing objects once per cluster, then run the agent in a sandbox
        Pod. The relevant flags select the executor, image, runtime class, and namespace:</p>
        <CodeBlock label="shell">
          <span><span className="eq-code__prompt">$ </span>kubectl apply -f examples/k8s/namespace.yaml</span>{'\n'}
          <span><span className="eq-code__prompt">$ </span>./stirrup harness \</span>{'\n'}
          <span>{'  '}<span className="tok-flag">--executor</span> k8s <span className="tok-flag">--container-runtime</span> gvisor \</span>{'\n'}
          <span>{'  '}<span className="tok-flag">--image</span> ghcr.io/rxbynerd/stirrup-sandbox:latest \</span>{'\n'}
          <span>{'  '}<span className="tok-flag">--mode</span> execution <span className="tok-flag">--prompt</span> <span className="tok-str">"Fix main_test.go"</span></span>
        </CodeBlock>
      </React.Fragment>);
    },
  },

  /* ------------------------------------------------------------------ */
  'architecture': {
    group: 'stirrup', title: 'Architecture', kicker: 'stirrup',
    lead: 'A pure-function core surrounded by thirteen injected components, composed from one declarative RunConfig. Swap any component without touching the loop.',
    toc: [
      { id:'core', label:'The pure-function core', snippet:'The agentic loop depends only on interfaces; thirteen components are composed via a single declarative RunConfig.' },
      { id:'components', label:'Thirteen components', snippet:'provider, router, prompt, context strategy, tools, executor, edit strategy, verifier, permission policy, transport, git, tracing, guardrail.' },
      { id:'executors', label:'Executors', snippet:'local, container, k8s, k8s-sandbox, and a shell-less api executor spanning trusted iteration to hardened Pod-per-run.' },
      { id:'providers', label:'Providers', snippet:'Five hand-rolled adapters: Anthropic SSE, Bedrock Converse, OpenAI Chat, OpenAI Responses, Gemini via Vertex.' },
    ],
    render: (C) => {
      const { CodeBlock, Callout, Tag } = C;
      return (<React.Fragment>
        {H('core','The pure-function core')}
        <p>The agentic loop depends only on interfaces. Thirteen components are composed
        via a single declarative <code>RunConfig</code>, so you can swap the provider or the
        executor without touching the loop. The loop itself is a deterministic Go state
        machine; the model is called only at decision points.</p>

        {H('components','Thirteen components')}
        <p>Every concrete type is injected. The full set:</p>
        <ul>
          <li><strong>provider</strong> · <strong>router</strong> · <strong>prompt</strong> — how the model is reached and steered.</li>
          <li><strong>context strategy</strong> · <strong>tools</strong> · <strong>edit strategy</strong> — what the agent sees and how it changes code.</li>
          <li><strong>executor</strong> · <strong>permission policy</strong> · <strong>guardrail</strong> — where code runs and what it may do.</li>
          <li><strong>verifier</strong> · <strong>git</strong> · <strong>transport</strong> · <strong>tracing</strong> — how work is checked, recorded, and reported.</li>
        </ul>
        <Callout kind="tip" title="One document, no code changes">
          The embedded single-binary mode and the multi-tenant service share the same core;
          only the bound components differ.
        </Callout>

        {H('executors','Executors')}
        <p>The agent runs inside one of five executors, selected with <code>--executor</code>:</p>
        <table className="tbl">
          <thead><tr><th>Executor</th><th>Boundary</th><th>Notes</th></tr></thead>
          <tbody>
            <tr><td><code>local</code></td><td>none (host process)</td><td>Trusted local iteration.</td></tr>
            <tr><td><code>container</code></td><td>Docker / Podman</td><td>Single-host sandbox with an in-process egress proxy.</td></tr>
            <tr><td><code>k8s</code></td><td>Pod on a cluster</td><td>Pod-per-run, hardened securityContext, NetworkPolicy egress.</td></tr>
            <tr><td><code>k8s-sandbox</code></td><td>Agent Sandbox CRD</td><td>Pod from an agents.x-k8s.io Sandbox; gVisor-only.</td></tr>
            <tr><td><code>api</code></td><td>no shell (VCS-backed)</td><td>Reviews and plans against a Git host without a workspace.</td></tr>
          </tbody>
        </table>

        {H('providers','Providers')}
        <p>Five providers, hand-rolled — each a few hundred lines of stdlib HTTP, every
        line auditable:</p>
        <div className="doc__toc" style={{flexWrap:'wrap'}}>
          <Tag>Anthropic SSE</Tag><Tag>Bedrock Converse</Tag><Tag>OpenAI Chat</Tag>
          <Tag>OpenAI Responses</Tag><Tag tone="brand">Gemini · Vertex</Tag>
        </div>
        <CodeBlock label="RunConfig" prompt="">
          <span>{'{'}</span>{'\n'}
          <span>{'  '}<span className="tok-flag">"provider"</span>: {'{'} <span className="tok-flag">"type"</span>: <span className="tok-str">"anthropic"</span>, <span className="tok-flag">"model"</span>: <span className="tok-str">"claude-sonnet-4-6"</span> {'}'},</span>{'\n'}
          <span>{'  '}<span className="tok-flag">"executor"</span>: {'{'} <span className="tok-flag">"type"</span>: <span className="tok-str">"k8s"</span>, <span className="tok-flag">"runtimeClass"</span>: <span className="tok-str">"gvisor"</span> {'}'},</span>{'\n'}
          <span>{'  '}<span className="tok-flag">"mode"</span>: <span className="tok-str">"execution"</span></span>{'\n'}
          <span>{'}'}</span>
        </CodeBlock>
      </React.Fragment>);
    },
  },

  /* ------------------------------------------------------------------ */
  'safety-rings': {
    group: 'stirrup', title: 'Safety rings', kicker: 'stirrup · security',
    lead: 'stirrup runs LLM-produced code, so it composes five deterministic controls the agent cannot circumvent. Each ring catches a different class of attack.',
    toc: [
      { id:'rings', label:'The five rings', snippet:'Kernel isolation, egress allowlist proxy, Cedar policy engine, the Rule of Two, and a post-edit scanner.' },
      { id:'depth', label:'Defence in depth', snippet:'No single ring is trusted alone; a run that fails any ring is blocked, not degraded.' },
      { id:'secrets', label:'Secrets & log scrubbing', snippet:'secret:// references resolved through env/file/SSM; a scrubber runs every log line so leakage is structurally impossible.' },
    ],
    render: (C) => {
      const { Callout, Badge } = C;
      return (<React.Fragment>
        {H('rings','The five rings')}
        <p>Each ring catches a different class of attack at a different point in the run.</p>
        <table className="tbl">
          <thead><tr><th>#</th><th>Ring</th><th>What it guards</th></tr></thead>
          <tbody>
            <tr><td>1</td><td>Kernel isolation</td><td>RuntimeClasses (gVisor, Kata) bound the blast radius of executed code.</td></tr>
            <tr><td>2</td><td>Egress allowlist proxy</td><td>An in-process proxy permits only declared destinations.</td></tr>
            <tr><td>3</td><td>Cedar policy engine</td><td>Every tool call is authorised against a declarative policy.</td></tr>
            <tr><td>4</td><td>Rule of Two</td><td>A structural invariant over untrusted input, private data, and external action.</td></tr>
            <tr><td>5</td><td>Post-edit scanner</td><td>A code scanner inspects every edit before it is committed.</td></tr>
          </tbody>
        </table>

        {H('depth','Defence in depth')}
        <p>No single ring is trusted alone. A run that passes four rings and fails the
        fifth is stopped — the outcome is <code>blocked</code>, not degraded.</p>
        <div className="doc__toc"><Badge status="danger">blocked</Badge><span className="doc__kicker">a stopped run, not a broken one</span></div>

        {H('secrets','Secrets & log scrubbing')}
        <p>API keys are <code>secret://</code> references resolved through env vars, files,
        or AWS SSM — never literals in <code>RunConfig</code>. The <code>slog.Handler</code>
        runs every string through a credential-pattern scrubber before any handler sees it.</p>
        <Callout kind="warning" title="Structurally impossible">
          Secret leakage through logs is not merely discouraged — the scrubber sits ahead of
          every handler, so a key cannot reach a log sink.
        </Callout>
      </React.Fragment>);
    },
  },

  /* ------------------------------------------------------------------ */
  'configuration': {
    group: 'stirrup', title: 'Configuration', kicker: 'stirrup',
    lead: 'RunConfig is the one declarative document that describes a run. Its values resolve by a fixed precedence, and its output surface is machine-readable.',
    toc: [
      { id:'precedence', label:'Resolution precedence', snippet:'Documented defaults, then a base config (file or piped stdin), then explicitly set flags — a piped value is never clobbered by a flag default.' },
      { id:'output', label:'Run output', snippet:'--output text|json|none selects the post-run summary surface; json emits a single STIRRUP_RESULT line.' },
      { id:'capture', label:'Capturing a config', snippet:'--output-runconfig writes the exact config a flag-only invocation would have used, for replays or pinning.' },
    ],
    render: (C) => {
      const { CodeBlock, Callout } = C;
      return (<React.Fragment>
        {H('precedence','Resolution precedence')}
        <p>Configuration resolves in a fixed order, so composition is predictable:</p>
        <ul>
          <li><strong>Documented defaults</strong> — the baseline every field starts from.</li>
          <li><strong>Base config</strong> — a <code>--config</code> file, or a document piped on stdin.</li>
          <li><strong>Explicitly set flags</strong> — only flags you actually pass.</li>
        </ul>
        <p>A piped value is never clobbered by a flag <em>default</em> — only by a flag you
        set on purpose. This is what makes the pipeline pattern safe.</p>

        {H('output','Run output')}
        <p><code>stirrup harness --output &lt;text|json|none&gt;</code> (alias <code>-o</code>)
        selects the post-run summary surface:</p>
        <table className="tbl">
          <thead><tr><th>Value</th><th>Surface</th></tr></thead>
          <tbody>
            <tr><td><code>text</code></td><td>Prints a human summary to stderr (default).</td></tr>
            <tr><td><code>json</code></td><td>Emits a single <code>STIRRUP_RESULT</code> line on stdout, parseable as <code>types.RunResult</code>.</td></tr>
            <tr><td><code>none</code></td><td>Suppresses both.</td></tr>
          </tbody>
        </table>
        <Callout kind="note" title="Same wire format">
          A <code>--output=json</code> invocation and a <code>resultSink.type=stdout-json</code>
          run produce the identical payload.
        </Callout>

        {H('capture','Capturing a config')}
        <p><code>--output-runconfig &lt;path&gt;</code> captures the exact config a flag-only
        invocation <em>would</em> have used — useful for post-mortem replays or pinning a
        stable configuration:</p>
        <CodeBlock label="shell">
          <span><span className="eq-code__prompt">$ </span>./stirrup harness <span className="tok-flag">--model</span> claude-opus-4-7 \</span>{'\n'}
          <span>{'  '}<span className="tok-flag">--output-runconfig</span> pinned.json <span className="tok-flag">--prompt</span> <span className="tok-str">"…"</span></span>
        </CodeBlock>
      </React.Fragment>);
    },
  },

  /* ------------------------------------------------------------------ */
  'chiron-overview': {
    group: 'chiron', title: 'Overview', kicker: 'chiron · researcher',
    lead: 'chiron drives a Gemini Deep Research agent end-to-end from the command line and emits a cited, structured Markdown report. It is research-only.',
    toc: [
      { id:'what', label:'What chiron is', snippet:'The researcher of the suite — the wise centaur to stirrup\u2019s tack. Where stirrup changes code, chiron investigates.' },
      { id:'commands', label:'The four commands', snippet:'research, research-config, get, and follow-up — one deterministic loop: start, await, format, emit.' },
      { id:'resume', label:'Stateless resume', snippet:'The interaction id is emitted on stderr the moment it is known; chiron get re-attaches to a stored interaction.' },
    ],
    render: (C) => {
      const { CodeBlock, Callout } = C;
      return (<React.Fragment>
        {H('what','What chiron is')}
        <p>chiron is the researcher of the suite — the wise centaur to stirrup's tack. Where
        stirrup <em>changes</em> code under a harness, chiron <em>investigates</em>. It never
        mutates a workspace, runs no shell, and applies no edits.</p>

        {H('commands','The four commands')}
        <p>The loop is deterministic Go — start, await, format, emit — with model work only
        inside the researcher:</p>
        <CodeBlock label="shell">
          <span><span className="eq-code__prompt">$ </span>chiron research <span className="tok-flag">--query</span> <span className="tok-str">"10BASE-T1L PHY vendors"</span> <span className="tok-flag">--out</span> report.md</span>{'\n'}
          <span><span className="eq-code__prompt">$ </span>chiron research-config <span className="tok-flag">--agent</span> deep-research-max</span>{'\n'}
          <span><span className="eq-code__prompt">$ </span>chiron get &lt;interaction-id&gt;</span>{'\n'}
          <span><span className="eq-code__prompt">$ </span>chiron follow-up &lt;interaction-id&gt; <span className="tok-flag">--query</span> <span className="tok-str">"…"</span></span>
        </CodeBlock>

        {H('resume','Stateless resume')}
        <p>Deep Research tasks run for minutes (most under 20, hard max 60) and are stored
        server-side, so chiron keeps no local state. The interaction id — the resume handle —
        is emitted on stderr the moment it is known, before anything else can fail.</p>
        <Callout kind="tip" title="Recover a crashed run">
          <code>chiron get &lt;id&gt;</code> re-attaches to the stored interaction, awaits
          whatever remains, and emits the report. Nothing new is created or spent.
        </Callout>
      </React.Fragment>);
    },
  },

  /* ------------------------------------------------------------------ */
  'chiron-cost': {
    group: 'chiron', title: 'Cost levers', kicker: 'chiron · researcher',
    lead: 'A single Deep Research task costs real money, so the cost controls are front and centre. Estimates are for the budget gate — not billing.',
    toc: [
      { id:'tiers', label:'Tiers & estimates', snippet:'deep-research \u2248 \u00a31.58 (\u00a30.79\u2013\u00a32.37, ~80 searches); deep-research-max \u2248 \u00a33.95 (~160 searches).' },
      { id:'budget', label:'The budget gate', snippet:'--budget blocks the run client-side, before any interaction is created, with exit code 4.' },
      { id:'plan', label:'Collaborative planning', snippet:'--plan opens a review of the research plan before any money is spent, bounded at five rounds.' },
    ],
    render: (C) => {
      const { Callout, Badge } = C;
      return (<React.Fragment>
        {H('tiers','Tiers & estimates')}
        <p>Estimates are the midpoints of Google's published per-tier envelopes, converted
        at a fixed planning rate. They are pre-run planning figures — real pricing and
        attribution belong to <code>stint</code>.</p>
        <table className="tbl">
          <thead><tr><th>Tier</th><th>Planning estimate</th><th>Searches</th></tr></thead>
          <tbody>
            <tr><td><code>deep-research</code></td><td>≈ £1.58 <span style={{color:'var(--text-muted)'}}>(£0.79–£2.37)</span></td><td>~80</td></tr>
            <tr><td><code>deep-research-max</code></td><td>≈ £3.95 <span style={{color:'var(--text-muted)'}}>(£2.37–£5.53)</span></td><td>~160</td></tr>
          </tbody>
        </table>

        {H('budget','The budget gate')}
        <p><code>--budget &lt;gbp&gt;</code> blocks the run client-side, before any interaction
        is created, when the tier's estimate exceeds the cap. The gate sits ahead of the
        plan phase too, because plan rounds also spend.</p>
        <div className="doc__toc"><Badge status="danger">Exit 4</Badge><span className="doc__kicker">blocked before any spend</span></div>

        {H('plan','Collaborative planning')}
        <p><code>--plan</code> requests a research plan first and opens an interactive review
        on stderr — accept, refine, or quit — before any research money is spent, bounded at
        five rounds including the proposal.</p>
        <Callout kind="tip" title="Unattended pipelines">
          In a pipeline with no terminal, pass <code>--accept-plan</code> to approve the
          first plan automatically.
        </Callout>
      </React.Fragment>);
    },
  },

  /* ------------------------------------------------------------------ */
  'steeplechase-sinks': {
    group: 'steeplechase', title: 'Sinks & routing', kicker: 'steeplechase',
    lead: 'steeplechase receives OTLP metrics, logs, and traces and fans them out to one or more destinations. Every sink receives every payload in parallel.',
    toc: [
      { id:'dsn', label:'Sink DSN format', snippet:'stdout, otlp+grpc, otlp+http(s), and mqtt destinations, each with optional query parameters.' },
      { id:'routing', label:'Best-effort routing', snippet:'Per-sink failures are logged and metered but do not propagate upstream unless every sink fails.' },
      { id:'admin', label:'Admin endpoints', snippet:'A dedicated listener exposes /healthz, /readyz, and Prometheus /metrics.' },
    ],
    render: (C) => {
      const { CodeBlock, Callout } = C;
      return (<React.Fragment>
        {H('dsn','Sink DSN format')}
        <p>Each <code>--sink</code> is a DSN. Repeat the flag to fan out to several
        destinations at once:</p>
        <CodeBlock label="dsn" prompt="">
          <span>stdout <span className="tok-comment"># write to process stdout</span></span>{'\n'}
          <span>otlp+grpc://HOST:PORT[?k=v&…]</span>{'\n'}
          <span>otlp+https://HOST:PORT[/BASE][?k=v&…]</span>{'\n'}
          <span>mqtt://[USER:PASS@]HOST:PORT/TOPIC[?k=v&…]</span>
        </CodeBlock>
        <p>Unknown query keys cause startup to fail loudly, so a typo becomes a hard error
        rather than silently-dropped configuration.</p>

        {H('routing','Best-effort routing')}
        <p>Every configured sink receives every OTLP payload in parallel. Per-sink failures
        are logged with structured context and exposed as metrics, but they do not propagate
        back to the upstream client unless <em>every</em> sink fails.</p>
        <Callout kind="note" title="Why best-effort">
          This prevents a single flaky backend from triggering upstream retries that would
          duplicate data at the healthy sinks.
        </Callout>

        {H('admin','Admin endpoints')}
        <p>A dedicated listener (default <code>:9090</code>) exposes liveness, readiness, and
        Prometheus metrics:</p>
        <table className="tbl">
          <thead><tr><th>Path</th><th>Behaviour</th></tr></thead>
          <tbody>
            <tr><td><code>GET /healthz</code></td><td>200 while the process is alive (liveness).</td></tr>
            <tr><td><code>GET /readyz</code></td><td>200 once started; 503 during shutdown so load balancers drain.</td></tr>
            <tr><td><code>GET /metrics</code></td><td>Prometheus text format.</td></tr>
          </tbody>
        </table>
      </React.Fragment>);
    },
  },
};

const EQ_DOC_ORDER = [
  'quick-start','architecture','safety-rings','configuration',
  'chiron-overview','chiron-cost','steeplechase-sinks',
];

/* Flat search index: one entry per page and one per section. */
const EQ_SEARCH = [];
EQ_DOC_ORDER.forEach(pid => {
  const p = EQ_DOCS[pid];
  EQ_SEARCH.push({ pageId:pid, sectionId:null, title:p.title, crumb:`${p.group} · page`, group:p.group, snippet:p.lead });
  p.toc.forEach(s => {
    EQ_SEARCH.push({ pageId:pid, sectionId:s.id, title:s.label, crumb:`${p.group} · ${p.title}`, group:p.group, snippet:s.snippet });
  });
});

window.EQ_DOCS = EQ_DOCS;
window.EQ_DOC_ORDER = EQ_DOC_ORDER;
window.EQ_SEARCH = EQ_SEARCH;
