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
13 changes: 11 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Dependency updates (docs/CI-CD.md CI-13): once a week per ecosystem, minor and patch updates in
# one pull request, each major in a pull request of its own. Every ignore says why on the line
# Dependency updates (docs/CI-CD.md CI-13): once a week per ecosystem (the docs pin once a month),
# minor and patch updates in one pull request, each major in a pull request of its own. Every ignore says why on the line
# above it; tests/dependabot.test.ts fails on an ignore without a reason.
#
# Merge these with an explicit subject and body (`gh pr merge --squash --subject ... --body ...`):
Expand Down Expand Up @@ -46,3 +46,12 @@ updates:
groups:
actions-minor-and-patch:
update-types: [minor, patch]

# The docs site's one pin (zensical). Monthly, not weekly: Zensical releases every week or two,
# and the strict build and site check in the Docs workflow prove each bump.
- package-ecosystem: "pip"
directory: "/docs"
schedule:
interval: "monthly"
commit-message:
prefix: "build(docs)"
335 changes: 335 additions & 0 deletions .github/scripts/site-check.py

Large diffs are not rendered by default.

62 changes: 39 additions & 23 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@ name: Docs

# Copy to .github/workflows/docs.yml. Before the first run, set the repository's
# Pages source to "GitHub Actions": Settings -> Pages -> Source, or
# `gh api -X POST repos/GeiserX/<repo>/pages -f build_type=workflow` for a repo
# with no Pages yet. For a repo already publishing from a branch the API is a
# PUT, and its docs list `source.branch` and `source.path` as required, so the
# bare `-f build_type=workflow` PUT may 422: try it on CashPilot first and
# record which form worked. A deploy while the source is still a branch is
# expected to fail; that is the test that the switch happened.
# `gh api -X PUT repos/GeiserX/<repo>/pages -f build_type=workflow` (HTTP 204;
# the API keeps reporting the old source branch afterwards, which is cosmetic).
# A repo with no Pages at all uses `-X POST` with the same field. A repo
# already on the artifact flow needs no change.
#
# Never for a repo whose gh-pages branch holds a Helm chart index (index.yaml,
# artifacthub-repo.yml): Pumperly, LynxPrompt, genieacs-container,
# duplicacy-container. Those use docs-pages-helm.yml instead.
# artifacthub-repo.yml): Pumperly, LynxPrompt, genieacs-container. Those use
# docs-pages-helm.yml instead.
#
# Pull requests that touch the docs run the strict build, so a broken link, a
# missing page or an unreachable page fails the check instead of reaching the
# site. A push to main deploys, and only after the same build has passed.
# Pull requests that touch the docs run the strict build and the site check,
# so a broken link, a missing page or an unreachable page fails the check
# instead of reaching the site. A push to main deploys, and only after the
# same build has passed.
#
# The site is published as a Pages artifact, never with `mkdocs gh-deploy`:
# no gh-pages branch, no `contents: write`, and one artifact is the whole site.
# The site is published as a Pages artifact: no gh-pages branch, no
# `contents: write`, and one artifact is the whole site.
on:
push:
branches:
- main
paths:
- 'docs/**'
- 'mkdocs.yml'
- 'zensical.toml'
- '.github/workflows/docs.yml'
- '.github/scripts/site-check.py'
# Add every file a page includes with pymdownx.snippets (for example
# docker-compose.yml, .env.example): a change to it must rebuild the
# site, and a rename must fail the build.
Expand All @@ -36,9 +36,12 @@ on:
pull_request:
workflow_dispatch:

# One run at a time per ref, in push order, never cancelled. Locking only the
# deploy job is not enough: an older push whose build finishes later would
# deploy last and publish a stale site. Pull requests get their own group.
# One run at a time per ref, never cancelled. GitHub keeps one running and one
# pending run per group, and a run that joins later replaces the pending one.
# Runs join in the order GitHub queues them, which is not promised to be push
# order, so a newer push is likely, not certain, to deploy last.
# Locking only the deploy job is not enough: an older push whose build finishes
# later could deploy last and publish a stale site. Pull requests get their own group.
concurrency:
group: pages-${{ github.ref }}
cancel-in-progress: false
Expand All @@ -55,32 +58,45 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# This job never pushes; a persisted token is readable by pip and every MkDocs plugin.
# This job never pushes; a persisted token is readable by pip and by the build.
persist-credentials: false

- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: '3.13'
# pip's download cache only. Zensical's own build cache (.cache/) is
# never kept between runs: its docs advise against it on CI, and
# --clean below starts from nothing anyway.
cache: pip
cache-dependency-path: docs/requirements-docs.txt

- name: Install dependencies
run: pip install -r docs/requirements-docs.txt

# mkdocs.yml sets `strict: true`; the flag here keeps the build strict
# even if someone removes that line.
# zensical.toml sets `strict = true`; the flag here keeps the build strict
# even if someone removes that line. `python -m zensical`, never the bare
# `zensical` command: it puts the repo root on sys.path, which the awesome
# lists' Markdown extension needs, and every repo builds the same way.
- name: Build
run: mkdocs build --strict
run: python -m zensical build --clean --strict

# What the strict build does not check: a page in no nav entry, a nav
# entry for a missing file, a missing image, a /rooted link, a raw <title>
# in a page. Each one was proven to turn this step red (validate-zensical.sh).
- name: Check the built site
run: python -I .github/scripts/site-check.py site

# Push to main only: workflow_dispatch can be run on any branch, and the
# push filter above does not apply to it. Same line as docs-pages-helm.yml.
- name: Upload the site
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: site

deploy:
# Runs on push to main only, after the build job has passed.
if: github.event_name != 'pull_request'
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ artifacts/
# Beads: bead text lives in the Dolt tracker only, never in git
.beads/*.jsonl

# mkdocs build output
# docs build output (zensical)
/site/
2 changes: 1 addition & 1 deletion docs/CI-CD.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ CI-3 (squash only) is now part of CI-1. CI-6 (remove the leftover workflow) and

| # | Item | P | From | Acceptance | Today |
|---|---|---|---|---|---|
| CI-13 | Dependabot, grouped and weekly | P1 | audit (disabled); Minutes groups by ecosystem | `.github/dependabot.yml` for `bun`, `cargo` and `github-actions`: minor and patch grouped in one PR per ecosystem per week, majors alone, each ignore with its reason. Dependabot alerts and security updates turned on. Squash-merged with an explicit subject and body so no trailer is added | has: `.github/dependabot.yml` (ignores `@types/bun`, which moves with `.bun-version`, and `electrobun`, which moves with the Hutch pins), checked by `tests/dependabot.test.ts`; alerts and security updates on since 2026-09-25 |
| CI-13 | Dependabot, grouped and weekly | P1 | audit (disabled); Minutes groups by ecosystem | `.github/dependabot.yml` for `bun`, `cargo` and `github-actions`: minor and patch grouped in one PR per ecosystem per week, majors alone, each ignore with its reason. Dependabot alerts and security updates turned on. Squash-merged with an explicit subject and body so no trailer is added | has: `.github/dependabot.yml` (ignores `@types/bun`, which moves with `.bun-version`, and `electrobun`, which moves with the Hutch pins), checked by `tests/dependabot.test.ts`; a monthly `pip` entry for `/docs` bumps the docs site's `zensical` pin; alerts and security updates on since 2026-09-25 |
| CI-14 | Audits beyond alerts | P2 | alerts miss advisories that `audit` finds | Nightly `bun audit` and `cargo audit`; a high or critical finding fails the night | missing |
| CI-15 | CodeQL | P1 | audit (no code scanning) | Code scanning default setup on, first analysis visible under the Security tab | has: default setup on since 2026-09-25 (actions, JavaScript and TypeScript, Rust); the first analysis ran green and opened 13 medium alerts (12 `js/prototype-polluting-assignment` in `src/main/asr/live-worker.ts`, one `js/bad-code-sanitization` in a test), all closed by #42; none open |
| CI-16 | Workflow lint and security scan | P1 | Minutes (actionlint), anarlog (zizmor); public repo | actionlint and zizmor run in `lint` on any `.github/` change; zizmor findings at medium or above fail. zizmor's `unpinned-uses` audit runs with a hash-pin policy for every action, so an action referenced by tag fails; `dangerous-triggers` fails on `pull_request_target`; `artipacked` requires `persist-credentials: false` on every checkout that does not push. One more line in `lint` fails on `secrets.` referenced by any job a `pull_request` event can run. A job may read secrets only when its `if:` limits it to a tag push or `workflow_dispatch`, so a packaging dry run on a pull request builds unsigned. Positive controls: a scratch workflow using `actions/checkout@v4` by tag fails `lint`, and so does a job that reads `secrets.` with a `pull_request` trigger and no such `if:` | partial: SHA pins and no `pull_request_target`; nothing enforces them. The release workflow reads every secret behind `github.event_name == 'push'` or `!= 'pull_request'`, which `tests/release.test.ts` checks |
Expand Down
3 changes: 1 addition & 2 deletions docs/requirements-docs.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
mkdocs-material==9.7.7
mkdocs-redirects==1.2.2
zensical==0.1.0
15 changes: 14 additions & 1 deletion docs/stylesheets/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,18 @@
* scroll. Styling both would change every page title too.
*/
.md-header__topic:first-child .md-ellipsis {
color: #f2f3f5;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: lowercase;
}

/* That colour only on the dark scheme: Zensical's modern header takes the page
background, so on the light scheme it is white and a light name would vanish.
There the name keeps the default text colour. */
[data-md-color-scheme="slate"] .md-header__topic:first-child .md-ellipsis {
color: #f2f3f5;
}

/* Keep the mark and the name close, so they read as one unit. */
.md-header__button.md-logo {
padding-right: 0.2rem;
Expand Down Expand Up @@ -184,3 +190,10 @@
grid-template-columns: 1fr;
}
}

/* The logo is the #f2f3f5 mark alone, which vanishes on the light scheme's
white header. There it sits on the app icon's plate, as in the Dock. */
[data-md-color-scheme="default"] .md-header__button.md-logo img {
background: linear-gradient(#212327, #0e1014);
border-radius: 22%;
}
159 changes: 0 additions & 159 deletions mkdocs.yml

This file was deleted.

Loading
Loading