Skip to content

Antora migration polish: zero warnings + local mermaid + CI fix (#496)#497

Merged
michalharakal merged 4 commits intodevelopfrom
feature/496-antora-polish
Apr 13, 2026
Merged

Antora migration polish: zero warnings + local mermaid + CI fix (#496)#497
michalharakal merged 4 commits intodevelopfrom
feature/496-antora-polish

Conversation

@michalharakal
Copy link
Copy Markdown
Contributor

Closes #496. Also unblocks the failing `bundleDokkaIntoSite` CI run on develop (see commit 3 below).

Summary

Clears every warning emitted by the Antora build, fixes a CI permission failure that was hiding behind the first post-merge `workflow_dispatch` of `docs.yml`, and replaces the asciidoctor-kroki dependency with a local mermaid renderer so builds no longer depend on kroki.io at all.

Warning count: 13 → 0. Errors: 0.

Four commits

1. `4a745624` — component attributes for operator-design

Adds `asciidoc.attributes` block to `docs/antora.yml` defining `framework_name`, `ksp_version`, `dokka_version`, `asciidoctorj_version` — the four attribute references in `operator-design.adoc` the original author never declared. Antora component-level attributes flow to every page, so seven `{FRAMEWORK_NAME}` / `{KSP_VERSION}` / `{DOKKA_VERSION}` / `{ASCIIDOCTORJ_VERSION}` placeholders now resolve to real values. 13 → 7 warnings.

2. `7a459bf3` — strip pandoc anchors + cascade-demote headings

The 6 "section title out of sequence" warnings in `skainet-for-ai.adoc` and `arduino-c-codegen.adoc` looked like they were about heading levels but turned out to be about 20 `[[...]]` anchor lines that pandoc generated above every heading. In that position Asciidoctor binds them to the next block as bibliographic markers, which blocks the `==` / `===` from registering as section-opening headings, which drifts the parser's level counter. Stripping the anchors fixes the hierarchy parse; Asciidoctor auto-generates id-from-title anchors anyway so URL fragments stay stable. Also cascade-demoted the two files' headings by one level (`sed -E 's/^=(=+ )/\\1/'`) so they start with the idiomatic `= Page Title`. 7 → 1 warning.

3. `ca06cf51` — CI permission fix on bundleDokkaIntoSite

First CI run of the post-migration `docs.yml` failed with `Failed to create directory docs/build/site/api` during the `bundleDokkaIntoSite` step. Root cause: the Antora container was running as root, so `docs/build/site/` was root-owned, and the subsequent Gradle task (running on the runner host as the `runner` user) couldn't `mkdir` inside it.

Two coupled fixes, both necessary:

  • Add `--user "$(id -u):$(id -g)"` to the `docker run` invocation so the container writes as the runner user.
  • Set `runtime.cache_dir: ./.cache/antora` in the playbook because running as non-root kills the default `$HOME/.cache/antora` resolution (the container process has no matching passwd entry and `$HOME` falls back to `/`, which an unprivileged user can't write). The `.cache/` path is already gitignored via the pre-staged `## antora` section in the root `.gitignore`.

Verified end-to-end locally with the full CI flow (clean site dir → non-root Antora run → writable cache dir → Gradle bundle succeeds → `docs/build/site/api/` populated with the Dokka aggregate).

4. `724f72bd` — drop kroki, render mermaid locally

The final warning was `asciidoctor-kroki: Skipping mermaid block` in `hlo-getting-started.adoc`. Kroki was returning HTTP 400 for one diagram; switching from GET to POST didn't help (kroki.io rejected the POST for a different reason with an empty response body). The fix is to stop using kroki at all and render mermaid locally via `@mermaid-js/mermaid-cli` which the Docker image already bakes in.

Pipeline change:

[mermaid]      -->  local-mermaid-extension.js
----           -->  writes source to /tmp/skainet-mm-*/in.mmd
source         -->  exec mmdc -i in.mmd -o out.svg
----           -->  emits SVG as a \`pass\` block, inlined into HTML

Changes:

  • `docs/.docker/Dockerfile`: drop `asciidoctor-kroki@0.18` from the npm install; `COPY local-mermaid-extension.js` into `/opt/antora/`.
  • `docs/.docker/local-mermaid-extension.js` (new, ~90 lines): Asciidoctor.js block processor mirroring the shape used by asciidoctor-kroki (same `onContext`, `process`, `createBlock` pattern) but dispatching to `/opt/antora/node_modules/.bin/mmdc` via `child_process.execSync`. Cleans its temp dir in a `finally`. On failure emits a literal block with the original source + mmdc's stderr so degradation is visible.
  • `docs/antora-playbook.yml`: swap `asciidoctor-kroki` extension for `/opt/antora/local-mermaid-extension.js`; drop the two `kroki-*` attributes.
  • `docs/modules/ROOT/pages/tutorials/hlo-getting-started.adoc`: first real mermaid-cli render surfaced a previously-hidden authoring bug — a `sequenceDiagram` participant was aliased as `Opt`, and `opt` is a mermaid sequenceDiagram keyword (case-insensitive match, optional blocks). Kroki had been silently dropping this diagram the whole time; local rendering surfaced the actual error. Renamed to `Optimizer`.

Verification

docker build --no-cache -t skainet-antora:local docs/.docker
rm -rf docs/build/site docs/.cache
docker run --rm --user \"\$(id -u):\$(id -g)\" \\
    -v \"\$PWD:/antora\" -w /antora \\
    skainet-antora:local docs/antora-playbook.yml
grep -c \"<svg\" docs/build/site/skainet/tutorials/hlo-getting-started.html
# => 3  (one inline SVG per [mermaid] block, all three diagrams)
./gradlew --no-daemon bundleDokkaIntoSite
ls docs/build/site/api   # full Dokka aggregate

Warning count: 0. Error count: 0. Rendered inline SVG count: 3.

Net effect

  • The Antora site builds cleanly with zero noise.
  • No network dependency at build time: kroki.io is out of the stack entirely.
  • `docs.yml` CI runs green end-to-end from first run. No manual fix needed post-merge.
  • Docker image is strictly smaller (one less npm dep, asciidoctor-kroki + transitive deps gone).
  • Every mermaid block in every page renders locally and consistently, regardless of size.

🤖 Generated with Claude Code

michalharakal and others added 4 commits April 13, 2026 16:02
Adds an `asciidoc.attributes` block to `docs/antora.yml`
defining the four attributes `operator-design.adoc` references
but nobody declares:

    framework_name      = SKaiNET
    ksp_version         = 2.2.21-2.0.5
    dokka_version       = 2.1.0
    asciidoctorj_version = 3.0.0

Antora treats component-level attributes as defaults for every
page in the component, so the seven `{FRAMEWORK_NAME}` /
`{KSP_VERSION}` / `{DOKKA_VERSION}` / `{ASCIIDOCTORJ_VERSION}`
references across lines 1, 8, 30, 78, 176, 177, 178, 215 of
`operator-design.adoc` now resolve to real values instead of
falling back to the literal attribute-name placeholder and
producing a warning.

Net warning count dropped from 13 to 7. The remaining 6 are
the pandoc section-level artifacts in `skainet-for-ai.adoc` and
`arduino-c-codegen.adoc` (commit 2) plus the kroki mermaid
400 on the large `hlo-getting-started.adoc` diagram (commit 3).

First step of the Antora migration polish pass. See #496.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clears 6 section-title-out-of-sequence warnings across
`skainet-for-ai.adoc` (5 occurrences) and `arduino-c-codegen.adoc`
(1 occurrence) that were left over from the pandoc markdown ->
asciidoc conversion in #494.

Two interacting issues:

1. Pandoc generated 20 anchor lines of the form
   `[[1-tape-based-tracing]]`, `[[2-type-safe-tensor-creation-dsl]]`
   etc. These are standalone block anchors sitting ABOVE their
   section heading. In this position Asciidoctor treats them as
   bibliographic block markers that bind to the next block —
   which prevents the following `==` / `===` from registering as
   a section-opening heading, so the parser's section-level
   counter drifts and every subsequent nested heading trips the
   "expected level N, got level N+1" validator.

   The anchors are all auto-generated slug form of the heading
   text they precede. Asciidoctor auto-generates equivalent
   id-from-title anchors for every heading. Deleting these
   20 anchors sacrifices nothing — the id format is the same,
   the #fragment URLs stay stable.

2. Pandoc converts markdown `#` to asciidoc `==` rather than the
   more idiomatic `=` (page title). That made every converted
   page "off by one" with no level-0 title. Demoting every
   heading by one step (remove one `=`) fixes this: the page
   now starts with `= Title` and section levels cascade
   naturally from there.

Applied via `sed -E -i '' 's/^=(=+ )/\1/'` on the two affected
files — matches `^=` followed by one-or-more additional `=`
followed by a space, preserves block delimiters like a bare
`====$` that aren't headings. Applied only to files that were
flagged; the rest of the migration's converted files had clean
hierarchies already.

Net: warning count drops from 7 to 1. The remaining warning
is the kroki mermaid 400 on the large diagram in
`hlo-getting-started.adoc` which commit 3 will handle.

Second step of the Antora migration polish pass. See #496.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First run of the #494 docs.yml workflow on CI failed with:

    > Task :bundleDokkaIntoSite FAILED
    > Failed to create directory
      '/home/runner/work/SKaiNET/SKaiNET/docs/build/site/api'

Root cause: the Antora step ran the node:20-alpine container as
root (the default), so `docs/build/site/` and everything under
it was owned by root. The subsequent Gradle `bundleDokkaIntoSite`
step runs on the runner host as the `runner` user — which cannot
create a subdirectory inside a root-owned tree.

Two coupled fixes, both necessary:

1. `.github/workflows/docs.yml`: add `--user $(id -u):$(id -g)`
   to the `docker run` invocation. The container process now
   writes as the runner user and everything under
   `docs/build/site/` is owned correctly when Gradle takes over.

2. `docs/antora-playbook.yml`: add a `runtime.cache_dir:
   ./.cache/antora` setting. Without --user the default
   $HOME/.cache/antora resolution worked; with --user the
   container process has no matching passwd entry and $HOME
   falls back to `/`, so Antora would fail with
   `Failed to create content cache directory /.cache/antora;
   EACCES: permission denied`. Pointing cache_dir at a path
   under the mounted workspace makes it writable by the
   non-root user. The `.cache/` path is already gitignored
   via the pre-staged `## antora` section in the repo root
   .gitignore, so the cache never gets committed.

Verified end-to-end locally with the CI flow:

    rm -rf docs/build/site docs/.cache
    docker run --rm --user "$(id -u):$(id -g)" \
        -v "$PWD:/antora" -w /antora \
        skainet-antora:local docs/antora-playbook.yml
    ./gradlew --no-daemon bundleDokkaIntoSite

docs/build/site/ owned by $USER:$GROUP, api/ subtree populated
with the Dokka aggregate.

Third step of the Antora migration polish pass. This commit is
independent of the earlier warning-clearance work — it unblocks
CI regardless of what other polish happens next. See #496.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the asciidoctor-kroki dependency with a small local
Asciidoctor block processor that invokes the @mermaid-js/mermaid-cli
binary baked into the Antora Docker image directly. Eliminates the
last build warning AND removes the build-time network dependency
on kroki.io entirely.

## Why

asciidoctor-kroki sends the diagram source to kroki.io (by default
via GET with the source encoded into the URL). The GET path has a
4 KB URL length limit, so larger diagrams come back with HTTP 400
and the block is silently dropped. Switching the extension to POST
did not help — kroki.io also rejected the content for a different
reason, with an empty response body and no diagnostic. Turning
around each mermaid block through a network round trip for every
build was already a sore point; finding that the only path to
reliable rendering was "give up on the external service entirely"
made the decision clear.

The new pipeline is purely local:

    [mermaid]         -->  local-mermaid-extension.js
    ----              -->  writes source to /tmp/skainet-mm-*/in.mmd
    source            -->  exec mmdc -i in.mmd -o out.svg
    ----              -->  reads out.svg
                      -->  emits as a `pass` block (inline SVG)

mermaid-cli was already in the image from day one for the
asciidoctor-kroki "local fetch" path. Removing kroki and wiring
mermaid-cli directly via a 70-line extension is a strictly smaller
build dependency tree and strictly more reliable: no network, no
rate limits, no URL length caps, no flakes on CI, deterministic
outputs.

## Changes

1. `docs/.docker/Dockerfile`:
   - Drop `asciidoctor-kroki@0.18` from the npm install list.
   - `COPY local-mermaid-extension.js /opt/antora/` so the
     playbook can reference it by absolute path without any
     volume-mount gymnastics at run time.
   - Update the image description label.

2. `docs/.docker/local-mermaid-extension.js` (new):
   Asciidoctor.js block processor mirroring the shape used by
   asciidoctor-kroki (same onContext / process / createBlock
   pattern) but dispatching to /opt/antora/node_modules/.bin/mmdc
   via child_process.execSync with the Puppeteer config the image
   already writes at /opt/antora/puppeteer-config.json. Renders
   to a temp dir, reads the SVG, returns it inline via a `pass`
   block. Cleans the temp dir in a finally. On render failure
   emits a literal block containing the original mermaid source
   + the stderr from mmdc and logs a warning, matching the
   degradation style of the upstream kroki extension.

3. `docs/antora-playbook.yml`:
   - Swap `asciidoctor-kroki` extension for
     `/opt/antora/local-mermaid-extension.js`.
   - Drop the `kroki-fetch-diagram` and `kroki-http-method`
     attributes — both dead code now.

4. `docs/modules/ROOT/pages/tutorials/hlo-getting-started.adoc`:
   The first render against real mermaid-cli surfaced a
   previously-hidden authoring bug: one of the `sequenceDiagram`
   participants was aliased as `Opt`, and `opt` is a mermaid
   sequenceDiagram keyword (for optional blocks). Mermaid's
   parser matches keywords case-insensitively and was treating
   `Opt` as the start of an opt-block, producing:

       Parse error on line 12: ...HLO->>Opt: Unoptimized IR
       Expecting '+', '-', '()', 'ACTOR', got 'opt'

   Rename the alias to `Optimizer` and drop the `as` clause.
   Kroki had been silently rejecting this diagram for a
   different reason the whole time; local rendering surfaced
   the actual bug.

## Verification

    docker build --no-cache -t skainet-antora:local docs/.docker
    rm -rf docs/build/site docs/.cache
    docker run --rm --user "$(id -u):$(id -g)" \
        -v "$PWD:/antora" -w /antora \
        skainet-antora:local docs/antora-playbook.yml
    grep -c "<svg" docs/build/site/skainet/tutorials/hlo-getting-started.html
    # => 3  (one inline SVG per [mermaid] block, all three diagrams)
    ./gradlew --no-daemon bundleDokkaIntoSite
    ls docs/build/site/api   # full Dokka aggregate present

Antora warnings + errors on the full build: 0 + 0. Down from the
13 warnings the Antora migration landed with in #494.

Fourth and final step of the Antora migration polish pass. See #496.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-497 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

@michalharakal michalharakal merged commit a99990e into develop Apr 13, 2026
7 checks passed
@michalharakal michalharakal deleted the feature/496-antora-polish branch April 13, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Antora migration polish: clear the 13 build warnings

1 participant