Skip to content

Show one navigation pane on the landing page and the docs - #366

Merged
rocklambros merged 27 commits into
integrationfrom
feat/361-site-nav
Oct 31, 2026
Merged

rocklambros merged 27 commits into
integrationfrom
feat/361-site-nav

Conversation

@rocklambros

@rocklambros rocklambros commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

The landing page and the documentation now show one left navigation pane with the same rows in the same order, and mkdocs.yml is its only source. The landing renderer escapes and validates every value it reads from mkdocs.yml and adds a hashed content security policy, while the docs get the pane through two recorded overrides of Material's partials with a tool that re-derives them on a theme bump. A test holds the two panes equal on every sampled page, the docs 404 is now served from the site root, and a new deploy step and monitor job check the published pane.

Which issue does this implement

Closes #361

  • This is an editorial correction (typo, grammar, link, formatting) with no change in
    meaning, so it needs no issue

Base branch

  • integration, because this touches the specification, schemas, a reference
    implementation, an adapter, tests, or CI
  • main, because every changed path is on the documentation lane allowlist in
    CONTRIBUTING.md

Type of change

  • Specification change (schema, hooks, events, AgBOM)
  • Reference implementation or adapter
  • Documentation
  • Tooling or CI
  • Governance (licensing, security policy, contributor docs)

I tested this

  • I synced my branch with the base branch before opening this
  • uv run pytest -v passes on my machine
  • uv run mkdocs build --strict passes on my machine

The suite on the branch head: 1074 passed, 1 skipped. The strict build with GITHUB_PAGES_URL set ends Documentation built in 0.55 seconds with no warning.

zizmor on the two workflows this changes:

$ uv run --locked --only-group zizmor zizmor .github/workflows/deploy-pages.yml .github/workflows/monitor-pages.yml
 INFO zizmor: zizmor v1.30.1
 INFO audit: zizmor: completed .github/workflows/deploy-pages.yml
 INFO audit: zizmor: completed .github/workflows/monitor-pages.yml
No findings to report. Good job! (8 suppressed)

The five pre-merge checks, each run in a copy outside the working tree.

(a) A scratch relock to mkdocs-material 9.8.0, the bump that arrives first. The plan expected the override test to fail with the upstream-equality message. It does not, because the 9.8.0 copies of nav.html and nav-item.html are byte-identical to the 9.7.7 copies committed under overrides/.upstream/partials/, so there is no delta to report. The "relock first" message comes from the re-derivation tool instead, which is the expected answer when nothing upstream changed.

$ uv lock --upgrade-package mkdocs-material==9.8.0
Updated mkdocs-material v9.7.7 -> v9.8.0
$ uv sync --locked
$ uv run pytest tests/test_site_nav.py tests/test_overrides.py -q
49 passed in 2.69s
$ uv run python tools/rederive_overrides.py
mkdocs-material 9.8.0
installed partials equal the upstream copies; relock first
exit=1
$ cmp <installed 9.8.0 partial> overrides/.upstream/partials/<name>
nav.html identical to committed 9.7.7 copy
nav-item.html identical to committed 9.7.7 copy

(b) Zensical 0.0.73 with the classic variant. variant: classic added under theme: in a scratch copy, built with the pinned package and the test fixture's base URL so the canonical links match.

$ GITHUB_PAGES_URL=https://example.org/agent-control-standard/docs/ uvx --from zensical==0.0.73 zensical build
Build started
No issues found
Build finished in 0.51s
__nav_home occurrences: 4
__nav_ext occurrences: 4
landing rows: 53, zensical front page rows: 53
walk_docs(zensical front page) == walk_landing(landing): True

Caveat: Zensical overrides replace Zensical's own partials, not Material's, so this proves the override templates render under Zensical and walk to the same rows. It does not prove they are the right partials to override there.

(c) The landing page in a browser. Rendered with uv run --no-dev python tools/render_landing.py landing /tmp/nav-t10/_site, served by python3 -m http.server 8123 --bind 127.0.0.1, and opened in Chromium through Playwright. At 1366x768 the sidebar column shows Home open with its eight sections, then Trustworthy Agents, ACS, FAQ, Topics, Concepts, Specification, About, the External resources heading with GitHub, Roadmap, and Slack, and the theme toggle at y=642, inside the 768px column without scrolling it. At 400px wide, pressing Menu opens the drawer with the same rows and the toggle. The console at both widths:

Total messages: 0 (Errors: 0, Warnings: 0)

No message contains Content Security Policy or Refused to. The screenshots of the sidebar column and the open phone drawer are available on request.

(d) The live verifier, one attempt against the published site. The tool has no single-attempt flag, so check was called directly. Before merge it reports GitHub's default 404, as expected.

$ python3 -c 'import sys; sys.path.insert(0, "tools"); import verify_site_nav as v; problems = v.check("https://genai-security-project.github.io/agent-control-standard/", v.nonce()); [print(p) for p in problems]; sys.exit(1 if problems else 0)'
https://genai-security-project.github.io/agent-control-standard/ returned 200 with no aria-label="Navigation" landmark
https://genai-security-project.github.io/agent-control-standard/docs/does-not-exist-1793329653/ returned 404 with no aria-label="Navigation" landmark. This is GitHub's default 404 page, so _site/404.html did not reach the artifact.
exit=1

(e) The third-party guard.

$ uv run pytest tests/test_third_party_guard.py -q
69 passed in 0.03s

Checklist

  • Commits are signed off with git commit -s (required by the DCO)
  • Prose follows STYLE.md
  • No secrets, tokens, or internal URLs in the diff

Security

  • This change has no security impact

mkdocs.yml becomes a landing build input, so every value the renderer reads from it is treated as untrusted text. Titles must match the title pattern, anchors the anchor pattern, and URLs either a relative .md path or an https URL on a reviewed host. Every emitted value is HTML-escaped, and a value that fails a pattern stops the render with an error naming the entry instead of dropping the link. The docs overrides apply | e to every value they emit.

The landing page carries a content security policy with default-src 'none', and its one inline script and one inline style are allowed by sha256 hashes computed from the rendered bodies. A test recomputes the hashes and holds the page to no other inline script or style, no style attribute, and no data: URI, so the hashes stay complete.

A token test scans the built docs for on* attributes and for javascript:, data:, and vbscript: URLs, and checks the hosts in the built theme scripts against an allowlist keyed by bundle name.

tools/rederive_overrides.py will not adopt a change in Material's partials until it is run again with --accept-delta and the sha256 of the delta it printed, so a theme bump cannot rewrite the overrides without someone reading what changed upstream. Its --record mode never re-baselines an upstream copy that differs from the installed partial.

Permission changes

.github/CODEOWNERS adds @Lrd0036 to four lines: /landing/, /mkdocs.yml, /overrides/, and /docs/stylesheets/. That widens who can approve those paths, so it is a permission change for the owner to confirm. /pyproject.toml and /uv.lock are unchanged and stay with the leads pending his call.

Before merge

Rollback, from the Hosting section of CLAUDE.md: before re-running an older deploy, run gh variable set ROADMAP_REFRESH_ENABLED --body false, or the 05:15 UTC nightly redeploys main. Expect the navigation job in monitor-pages.yml to stay red until the revert reaches main, since it checks out main's verifier against the rolled-back site. Re-enable the variable once the revert deploys. A red navigation verify after a green Deploy to Pages means the site is live and the edge may be stale. Re-run once, then read the step summary.

…r, and the live check

Signed-off-by: rocklambros <rock@rockcyber.com>
…t pass

Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
…the landing page

Signed-off-by: rocklambros <rock@rockcyber.com>
…policy

Signed-off-by: rocklambros <rock@rockcyber.com>
…ides

Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
…ests

Each build measured at under a second; the suite stays about 22 seconds locally and 8 seconds in CI's Run the guards.

Signed-off-by: rocklambros <rock@rockcyber.com>
…epair

Signed-off-by: rocklambros <rock@rockcyber.com>
…s on it

Signed-off-by: rocklambros <rock@rockcyber.com>
…n its own step

Signed-off-by: rocklambros <rock@rockcyber.com>
…host allowlist by role

Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
@github-actions

Copy link
Copy Markdown

This pull request's description says it closes #361, which is on the project roadmap. When it merges, GitHub closes that issue and the roadmap counts it as delivered. Please confirm the change delivers the whole issue before merging.

Signed-off-by: rocklambros <rock@rockcyber.com>
…e panel on an outside tap

Signed-off-by: rocklambros <rock@rockcyber.com>
…s nav entry cannot stop the deploy of main

Signed-off-by: rocklambros <rock@rockcyber.com>
…hen there is nothing to re-derive

Signed-off-by: rocklambros <rock@rockcyber.com>
…t so the step cap never kills it before the summary

Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
…eplaying an override diff

Signed-off-by: rocklambros <rock@rockcyber.com>
…oved out of a protected tree still reaches the path rule

Signed-off-by: rocklambros <rock@rockcyber.com>
@rocklambros

Copy link
Copy Markdown
Collaborator Author

@bar-capsule @afogel can one of you review this and approve it? I can't approve my own PR, and one approval from either of you covers all 41 files.

I ran head a7697cc merged onto integration at 19b1197. pytest gives 1087 passed and 1 skipped, no integration test went missing, and mkdocs build --strict passes. With PR #368 on top there's no conflict in either order, and pytest gives 1418 passed and 1 skipped.

I also served the built site locally the way Pages does. tools/verify_site_nav.py passes, and it fails when I remove the root 404.html. Chromium shows no CSP errors at 1366px or 400px, and the phone drawer works. The bump steps in overrides/README.md work on a scratch edit, and a wrong hash or a changed context line writes nothing.

What's left:

  1. Lance hasn't replied on issue Site navigation: one left pane for the landing page and the docs #361. When he says yes, I'll push the GOVERNANCE sentence.
  2. Please decide the CODEOWNERS change. This PR adds Lance to /landing/, /mkdocs.yml, /overrides/, and /docs/stylesheets/, so he could approve a PR that touches only those paths. test_mkdocs_build_surface_is_pinned stops a hooks: key there, and /tests/ stays with the leads. If you want those lines out, tell me and I'll drop them.
  3. Lance or the lead who approves runs the rehearsal once, on a scratch edit of an upstream copy. Send me the name and I'll push the Rehearsed by: line.
  4. After this merges, the navigation job in monitor-pages.yml goes red every six hours until the next promotion, because main has no tools/verify_site_nav.py yet. Either a small guard goes in that push or I promote the same day.

Please review now, and approve after that push. The ruleset drops any approval when I push.

Signed-off-by: rocklambros <rock@rockcyber.com>
…he job stays green before the first promotion

Signed-off-by: rocklambros <rock@rockcyber.com>
Signed-off-by: rocklambros <rock@rockcyber.com>
@rocklambros
rocklambros merged commit f8b55db into integration Oct 31, 2026
13 checks passed
@github-project-automation github-project-automation Bot moved this from In progress to Done in ACS Project Tracker Oct 31, 2026
@github-actions github-actions Bot mentioned this pull request Nov 1, 2026
@rocklambros
rocklambros deleted the feat/361-site-nav branch November 1, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Site navigation: one left pane for the landing page and the docs

1 participant