Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions .github/workflows/theme-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
name: Theme CI

on:
pull_request:
push:

permissions:
contents: read

concurrency:
group: theme-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
source:
name: Source and browser verification
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
cache: pip

- name: Install source and development tools
run: python -m pip install --disable-pip-version-check '.[dev]'

- name: Install workflow linter
env:
ACTIONLINT_VERSION: v1.7.12
run: |
actionlint_directory="${RUNNER_TEMP}/actionlint"
mkdir -p "${actionlint_directory}"
GOBIN="${actionlint_directory}" go install \
"github.com/rhysd/actionlint/cmd/actionlint@${ACTIONLINT_VERSION}"
echo "${actionlint_directory}" >> "${GITHUB_PATH}"

- name: Verify source and unit behavior
run: |
python -m ruff format --check .
python -m ruff check .
python -m mypy src
python -m pytest -m 'not visual'
actionlint -color

- name: Audit workflow security
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with:
inputs: .
collect: all
online-audits: false
persona: auditor
min-severity: medium
version: v1.30.1
token: unused-offline
advanced-security: false
annotations: true

- name: Install browser
run: python -m playwright install --with-deps chromium

- name: Verify responsive and accessible presentation
env:
STATEFORGE_VISUAL_EVIDENCE: evidence/visual
STATEFORGE_VISUAL_TESTS: "1"
run: python -m pytest tests/test_visual.py

- name: Preserve visual evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: stateforge-docs-theme-visual-evidence
path: evidence/visual
if-no-files-found: warn

build:
name: Build reproducible distributions
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"
cache: pip

- name: Install build frontend
run: python -m pip install --disable-pip-version-check 'build>=1.2'

- name: Build and compare distributions
run: |
set -euo pipefail
SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
export SOURCE_DATE_EPOCH
mkdir -p dist-first dist-second dist evidence/distribution
python -m build --outdir dist-first
python -m build --outdir dist-second
cmp dist-first/*.whl dist-second/*.whl
cmp dist-first/*.tar.gz dist-second/*.tar.gz
cp dist-first/* dist/
sha256sum dist/* | tee evidence/distribution/SHA256SUMS
python -m zipfile -l dist/*.whl | tee evidence/distribution/wheel-contents.txt
tar -tzf dist/*.tar.gz | tee evidence/distribution/sdist-contents.txt
grep -F 'stateforge-mark.svg' evidence/distribution/wheel-contents.txt
grep -F 'stateforge-favicon.svg' evidence/distribution/wheel-contents.txt
grep -F 'stateforge.css' evidence/distribution/wheel-contents.txt
grep -F 'main.html' evidence/distribution/wheel-contents.txt
grep -F 'stateforge-mark.svg' evidence/distribution/sdist-contents.txt
grep -F 'stateforge-favicon.svg' evidence/distribution/sdist-contents.txt
grep -F 'stateforge.css' evidence/distribution/sdist-contents.txt
grep -F 'main.html' evidence/distribution/sdist-contents.txt

- name: Upload exact distributions
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: stateforge-docs-theme-distributions
path: |
dist
evidence/distribution
if-no-files-found: error

installed:
name: Installed ${{ matrix.artifact }}
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
include:
- artifact: wheel
pattern: "*.whl"
- artifact: sdist
pattern: "*.tar.gz"
steps:
- name: Check out fixture
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.13"

- name: Download exact distributions
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: stateforge-docs-theme-distributions
path: artifacts

- name: Install and build outside the source tree
env:
ARTIFACT_KIND: ${{ matrix.artifact }}
PACKAGE_PATTERN: ${{ matrix.pattern }}
run: |
set -euo pipefail
package="$(find artifacts/dist -type f -name "${PACKAGE_PATTERN}" -print -quit)"
test -n "${package}"
python -m venv "${RUNNER_TEMP}/consumer"
"${RUNNER_TEMP}/consumer/bin/python" -m pip install --disable-pip-version-check "${package}"
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/consumer/bin/python" -m mkdocs build \
--strict \
--config-file "${GITHUB_WORKSPACE}/tests/fixtures/site/mkdocs.yml" \
--site-dir "${RUNNER_TEMP}/site-${ARTIFACT_KIND}"
test -f "${RUNNER_TEMP}/site-${ARTIFACT_KIND}/v0.1.0/index.html"
test -f "${RUNNER_TEMP}/site-${ARTIFACT_KIND}/assets/images/stateforge-mark.svg"
test -f "${RUNNER_TEMP}/site-${ARTIFACT_KIND}/assets/images/stateforge-favicon.svg"
206 changes: 206 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# StateForge Docs

Shared documentation infrastructure for the StateForge project.

This repository produces the `stateforge-docs-theme` Python distribution. It
extends Material for MkDocs with StateForge branding, shared presentation
components, and build-time validation. Documentation content remains in the
repository that owns it.

## Interactive procedures

The theme includes a declarative procedure player for explaining interactions
between any number of ordered entities. Processes, services, storage,
resources, external systems, and users can participate in the same flow. Each
step declares its active entities, concurrent transitions, internal activity,
shared context, success paths, and failure paths. The renderer contains no
Agent, Provider, Bootstrap, or other procedure-specific topology.

Documentation versions come exclusively from the source directory. The whole
site for a release lives below that version, including its procedure pages and
declarations:

```text
docs/
├── v0.1.0/
│ ├── index.md
│ ├── bootstrap.md
│ └── procedures/
│ └── bootstrap.yaml
└── v0.2.0/
└── ...
```

The filename stays `bootstrap.yaml`. A declaration must not contain a
`version`, `protocol_version`, or `documentation_version` field. The build
derives `v0.1.0` from the documentation tree and rejects ambiguous or missing
version locations.

Embed a declaration from Markdown:

````markdown
```stateforge-procedure
source: procedures/bootstrap.yaml
scenario: success
```
````

The source path is relative to the Markdown page and must remain within the
MkDocs `docs_dir`. Builds reject traversal, oversized declarations, unknown
fields, malformed actors, invalid scenarios, and unsafe inline data. The
generated page contains a static ordered fallback before JavaScript enhances
it into the interactive player.

## Interactive lifecycle summaries

Landing pages may use the compact lifecycle component when two endpoints
exchange a short, ordered series of messages. The visible stage buttons remain
the complete static explanation. With JavaScript enabled, selecting a stage
updates the endpoint states and plays its declared messages once.

Each `[data-sf-flow-step]` button declares:

- `data-source-state` and `data-target-state` for the endpoint summaries;
- `data-result` for the stable state reached after playback; and
- `data-events` as a JSON array of `direction`, `label`, and `detail` values.

`forward` moves from source to target and `reverse` moves from target to source.
The component uses native buttons, reports the selected stage with
`aria-pressed`, exposes one polite live status, and skips playback when the
visitor requests reduced motion. The shared script contains no
repository-specific actors or messages.

## Declarative entity flows

Use an entity flow for a compact, animated explanation of one responsibility
transfer across several real components. The renderer, interaction controls,
accessibility behavior, StateForge branding, and bundled third-party marks live
in this package. The owning repository supplies only version-local YAML.

````markdown
```stateforge-entity-flow
source: trigger-publication-overview.yaml
```
````

An entity declaration describes identity, visual role, and initial state. Each
step declares the active entities, state changes, directed transitions, and
the exact owner of responsibility at that point:

```yaml
schema: stateforge.docs/entity-flow/v1
flow: trigger-publication
title: Trigger publication responsibility flow
entities:
- id: provider
label: Example Provider
kind: StateForge Provider
brand: stateforge
accent: provider
state: {label: Pending queue, value: Empty}
- id: agent
label: StateForge Agent
kind: StateForge Agent
brand: stateforge
accent: agent
state: {label: Trigger record, value: Not accepted}
steps:
- id: publish
phase: Trigger publication
title: Publish the trigger
detail: The Provider retains the publication while sending it.
active: [provider, agent]
states:
provider: {label: Pending queue, value: 1 retained publication}
transitions:
- {source: provider, target: agent, label: TriggerPublication}
responsibility:
entities: [provider]
label: The Provider remains responsible until durable acceptance.
```

Supported bundled brands are `stateforge`, `service`, and `none`. `service` is
a neutral service glyph for factual product references; `none` uses a compact
text mark derived from the entity label.

## Consumer configuration

```yaml
theme:
name: stateforge

plugins:
- material/search
- stateforge:
component: protocol
documentation_version: v0.1.0
latest_version: v0.2.0
versions:
- label: v0.2.0
url: v0.2.0/
- label: v0.1.0
url: v0.1.0/
```

Version URLs are relative to the documentation root. The theme resolves them
correctly from root pages and nested procedure or reference pages.

Built-in component accents are provided for `stateforge`, `agent`, `provider`,
`protocol`, `control-plane`, and `sf-aop`. A future repository may use any
lowercase hyphenated component identity. It inherits the StateForge accent or
supplies an explicit six-digit `accent`; the theme chooses a WCAG-compliant
light or dark foreground automatically.

The optional version configuration renders one keyboard-accessible selector.
When `documentation_version` differs from `latest_version`, the theme also
renders a historical-version warning with a link to the latest documentation.
Publishing and redirect behavior remain owned by later documentation
milestones.

## Theme ownership and Material boundary

The package owns only shared presentation: the StateForge mark and favicon,
brand tokens, system typography, light and dark palettes, focus treatment,
component accents, repository-link styling, code presentation, the version
selector, and reusable interactive components. Consumers continue to own
their Markdown, navigation, repository metadata, generated references, and
repository-specific plugins.

The Material integration is deliberately limited to:

- one theme inheritance declaration in `mkdocs_theme.yml`;
- one `main.html` override that calls `super()` and adds shared assets,
version navigation, and the historical warning; and
- CSS layered over Material's public class surface.

No documentation content depends on a Material template. A future centrally
managed Zensical migration can replace this package's template and CSS layer
without rewriting consumer Markdown.

The theme disables remote web fonts and uses a local system-font stack. All
StateForge branding required at runtime is packaged inside the distribution.

## Distribution verification

Build the wheel and source distribution from the same source revision:

```bash
python -m build
```

The repository test and CI suites inspect both archives, install each artifact
in a clean environment, select `theme.name: stateforge`, and build the fixture
site strictly. Browser checks cover desktop and mobile layouts, light and dark
schemes, focus visibility, component contrast, version navigation, and bundled
logo and favicon resolution.

## Development

```bash
python -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/ruff format --check .
.venv/bin/ruff check .
.venv/bin/mypy src
.venv/bin/pytest
```
Loading