Files
Yggdrasil/AGENT.md
Klagarge 9ffc6a14d2 fix: apply fresh-start review findings
Independent review (second attempt) passed all 7 verification steps at
19f6007, including the fresh-clone embed check, full build from clean
state (9.7s), server curl matrix (public 200, protected + all theme
sidecars 503, searchindex 404, no content in 503 bodies), and just check.

Findings applied:
- README: prerequisite Go version corrected to 1.27.1+ to match go.mod
  (a Go 1.22-1.27 user got a hard 'requires go >= 1.27.1' failure).
- CI: upload-artifact v3 -> v4 with a fallback note for older runners;
  CI now also runs the gofmt guard like 'just check'.
- Justfile: curl -fsSL so a bad download fails at curl instead of tar;
  comment clarifying that --dest-dir is resolved relative to docs/.
- AGENT.md: version-pairing trap now covers both pin locations
  (Justfile and .gitea/workflows/build.yml).
2026-10-10 19:05:34 +02:00

4.1 KiB

AGENT.md — operating rules for agents working on Yggdrasil

Personal knowledge base: Typst sources (docs/, a shiroa book) compiled to a static site, embedded into a Go binary that enforces per-page OIDC group permissions (Authentik). README.md is the human-facing documentation; this file is the working rulebook. Follow it; when it conflicts with the user, the user wins.

Commands

  • just — shows the command overview (bare just never builds anything).
  • just build — parse ACL, compile the book, build ./server.
  • just run — build everything, then serve on :8080.
  • just check — go vet, go test, gofmt. Run it before every commit.
  • just clean — removes dist/ and server (keeps tools/ and restores the committed dist/.gitkeep).
  • The shiroa toolchain is pinned and self-bootstrapped into tools/ by the tools recipe. Never assume a global shiroa/typst install exists; do not install shiroa globally; do not bump the pin casually (see Traps).

Layout

  • docs/ — shiroa book. book.typ lists every built page; files not listed there are not part of the site. Categories are = part headers in the summary, pages are # chapter("path.typ")[Title] entries. Final URL = source path with .typ replaced by .html.
  • cmd/parser — build step: scans docs/ for #let access-groups = (..) tags and emits acl.json. Untagged pages are public.
  • cmd/server + embed.go (repo root) — //go:embed of dist/ and acl.json into a single binary.
  • internal/acl — ACL loading and page-key normalization (security critical).
  • internal/oidc — OIDC authorization-code client.
  • internal/middleware — sessions, login/callback/logout, access gate.
  • legacy_docs/ — frozen previous site. Never modify or migrate it unless the user asks explicitly.

Security invariants — do not break

  • Fail closed: a malformed acl.json aborts startup; protected pages with no configured IdP answer 503, never the content.
  • Page keys: shiroa pages keep their rendered content in sibling <page>.<theme>.multi.sir.in files fetched by client-side JS. acl.PageKey reduces any URL to a page key so those files inherit the page's protection. If the shiroa output format changes, re-verify this mapping: content in any new sidecar file must be covered or blocked.
  • /searchindex.json contains the text of protected pages: blocked at the router, always.
  • Session cookies: HttpOnly, SameSite=Lax, Secure unless COOKIE_SECURE=false. OAuth2 states are random and single use; post-login redirect targets are sanitized (open-redirect guard).

Traps

  • Shiroa version pairing: the CLI and the @preview/shiroa:* typst package must match (CLI v0.4.0-rc2 pairs with package 0.4.0; the v0.3.0 release CLI rejects 0.4.0 books). The pin lives in two places: the Justfile shiroa-version and .gitea/workflows/build.yml. Bump all of them together with the package import and rebuild.
  • Typst call syntax: no space between ) and the trailing content block — #chapter("x.typ")[Title], not #chapter("x.typ") [Title].
  • dist/ and acl.json are embedded at Go build time; there is no runtime reload. After changing docs or ACL, rebuild the binary.
  • go build needs dist/ and acl.json to exist: fresh clones carry dist/.gitkeep and a committed acl.json for exactly this reason. Never gitignore those two.

Testing expectations

  • Test what the user runs, not a proxy for it: from a clean state (rm -rf tools && just clean), just build and just run must succeed with no global shiroa on PATH. Test the bare just output too. After any clean/test cycle, git status must be clean: dist/.gitkeep is tracked, and committing a worktree where it is missing breaks fresh clones (go:embed needs dist/).
  • Server behavior: curl a public page (200), a protected page (503 without OIDC env; it must never serve protected content), and /searchindex.json (404). Extend the unit tests in internal/acl and internal/middleware whenever touching those paths.
  • A live OIDC round trip needs a real IdP and is not testable here. Say that explicitly when reporting what was verified.