Skip to content

Compass engineering docs site

Design record for making Compass build-in-the-open: relocate the Compass design records + product specs into this PUBLIC repo (sealedsecurity/compass) and stand up a public Astro Starlight engineering docs site with production deploy on push:main and a per-PR Cloudflare Pages preview, driven by GitHub Actions. Matt’s intent, verbatim: “build in the open — people can look through and see how we built Compass using all of these practices. I’m OK with exposing more than most products would.”

This site is the engineering docs — the design records, specs, and architecture that document how Compass is built. It is deliberately named apart from the future user-facing product docsite (the end-user product documentation), which is a separate site that reserves the docs.compass naming and docs.compass.rigel.build domain. This site takes eng.compass.rigel.build.

This record is itself the first artifact under the new convention: it is authored in sealedsecurity/compass at docs/designs/repo/compass-eng-docs/design.md, not in sealed.

The Compass design corpus (39 records + the living spec) lives in the PRIVATE sealedsecurity/sealed monorepo, invisible to the public the product is being built in the open for; the public sealedsecurity/compass repo has no docsite and no docs/designs/ tree (only docs/architecture/build-and-ci.md, verified this session). Move the Compass records + specs here and publish them on a public Starlight engineering docs site with sealed’s per-PR-preview UX, re-based from Woodpecker onto GitHub Actions and with no Cloudflare Access gate.

  • Public repo, public site — no access gate. sealed’s docsite is “gated to the team via Cloudflare Access” (sealed/apps/docs/astro.config.mjs:9-10: “to sealed-docs.sealedsecurity.com via Cloudflare Pages, gated to the team via Cloudflare Access”). The compass mirror deliberately drops the Access gate: the site is public. Nothing in the docsite build, deploy, or content may assume an authenticated reader.
  • Fork-PR secret hygiene. The repo is public, so pull_request events from forks run WITHOUT repository secrets — and GitHub forces GITHUB_TOKEN read-only on a fork pull_request regardless of the workflow permissions: block. The preview-deploy job MUST guard on same-repo PRs (github.event.pull_request.head.repo.full_name == github.repository). Fork PRs therefore get the build/check gate but STRUCTURALLY cannot preview (no secrets to reach Cloudflare, a read-only token that would 403 the comment upsert) — not a deliberate reduced-service tier but an unavoidable consequence.
  • Toolchain pins. bun 1.3.13, node 24.18.0, moon 2.4.2 (compass/.prototools:6-8). New shared JS deps ride the root catalog (compass/package.json:5: “pins shared dependency versions via the catalog”); app runtime deps (astro, @astrojs/starlight, github-slugger, sharp) go in apps/eng-docs/package.json following sealed’s shape (sealed/apps/docs/package.json:13-19).
  • One CI job, no matrix. compass CI is deliberately a single job running moon ci :ci on PRs and moon run :ci on main (compass/.github/workflows/ci.yml:4: “ONE JOB, NOT A MATRIX — the central structural decision”; :281-292). The docsite’s build/check tasks join that gate as a new moon project; only the DEPLOY (CD, secret-bearing) is a separate workflow.
  • moon projects are an explicit map. “moon discovers projects from this explicit map” (compass/.moon/workspace.yml:37-38); the new apps/eng-docs project MUST be registered in .moon/workspace.yml in the same change that adds the tree, or its CI tasks silently gate nothing (workspace.yml:41-47: “A forks/<name>/moon.yml with no entry below is silently inert”).
  • Sanitization policy is repeatable rules, not a blind copy. The four content classes below (§Approach — Sanitization policy) govern every migrated record AND every future record authored here. No per-file ad-hoc judgment outside those rules.
  • Security sections are kept. Matt ruled that the 13 records carrying threat-model / security-boundary / egress sections are fine to expose. Do not strip or soften them.
  • Generated content is gitignored. As in sealed (sealed/apps/docs/package.json:5: “Content under src/content/docs/ is generated by scripts/gather.ts … and is gitignored; edit the canonical sources, not the generated copy”).
  • markdownlint governs the canonical sources. compass already lints all *.md with ignores: ["forks/*/**"] (compass/.markdownlint-cli2.jsonc:8-10); migrated records must pass it.
  • Hygiene-rule carve-out for design records. compass AGENTS.md:55-58 bans “planning metadata (issue IDs, phase numbers)” in source. Design records are historical records, not source: SEA-#### identifiers survive as plain-text provenance (see sanitization class 2). The AGENTS.md hygiene paragraph gains one sentence scoping itself to code, in the migration PR.

Mirror sealed’s docsite mechanism — one Starlight app, a gather script that mirrors the repo’s reviewed markdown into a gitignored content collection, a generated sidebar, per-page GitHub editUrl, pagefind search, and a deploy script that pushes dist/ to Cloudflare Pages and upserts a preview-URL PR comment — with two deliberate divergences: CI is GitHub Actions, not Woodpecker, and the site is public with no Access gate.

(a) The site: apps/eng-docs, a compass-adapted mirror of sealed’s docsite

Section titled “(a) The site: apps/eng-docs, a compass-adapted mirror of sealed’s docsite”

sealed’s shape, extracted at source this session:

  • App layoutapps/eng-docs/{astro.config.mjs,package.json,moon.yml, tsconfig.json,src/,public/,scripts/{gather.ts,deploy.ts}} with src/content/docs/ + src/sidebar.generated.ts generated and gitignored.
  • Gather mirrors every tracked *.md (minus exclusions) into the content collection, classified into nav sections. sealed’s taxonomy (gather.ts:43-51): designs / specs / research / team / packages / infra / contributing. Its classifier keys docs/<domain>/… to the matching section and routes everything else to contributing/packages (gather.ts:94-123). Exclusions come from the canonical .markdownlint-cli2.jsonc ignores list — “the single source of truth” (gather.ts:126-137, parseExclusions returns [...ignores, "oss/seal/**", "**/outputs/**"]).
  • Per-page editUrleditUrlFor builds https://github.com/${REPO_SLUG}/edit/main/${sourcePath} (gather.ts:203-205), with REPO_SLUG = "sealedsecurity/sealed" (gather.ts:25).
  • Starlight configpagefind: true, generated sidebar, editLink, lastUpdated, expressiveCode tokyo-night (astro.config.mjs:51-66).
  • moon tasksgather (inputs /**/*.md + the markdownlint config, so moon’s affected graph rebuilds the site whenever any gathered doc changes, moon.yml:18-25), build (bunx astro build, deps ['gather'], moon.yml:27-39), check (bunx astro check, moon.yml:43-52), and deploy/deploy-preview (runInCI: false, both bun scripts/deploy.ts, moon.yml:69-74).

The compass adaptation:

  • Sections: Designs, Specs, Contributing, Packages — compass has no docs/research/ or docs/team/ or infra/ tree (top level verified this session: go/ packages/ apps/ proto/ tools/ forks/ docs/ agent-image/). docs/architecture/ classifies under Designs? No — it keeps its own Architecture section (a docs/ domain like the others); the DOMAINS list becomes ["designs", "specs", "architecture"].
  • REPO_SLUG = "sealedsecurity/compass"; editUrl points here.
  • Exclusions: compass’s .markdownlint-cli2.jsonc ignores is ["forks/*/**"] (compass/.markdownlint-cli2.jsonc:10); the gather adds its own tree + **/outputs/** as sealed’s does, and drops the oss/seal/** special case (no such tree here).
  • packagePath’s oss special case drops (sealed gather.ts:78 special-cases apps|oss two-segment package ids; compass keeps apps and gains nothing for oss, which does not exist here).
  • No Cloudflare Access, no PostHog identity inheritance — sealed’s cross_subdomain_cookie PostHog block (astro.config.mjs:26-46) depends on the Access-identified apex; the compass site ships either no analytics or a plain PostHog snippet (Open Question Q4 covers the domain; analytics rides the same decision).
  • moon registration: compass-eng-docs: 'apps/eng-docs' added to .moon/workspace.yml projects. Its build + check tasks join moon ci :ci automatically (“a new project with a ci task is picked up the moment it is registered — no matrix entry to forget”, ci.yml:16-18), so a docs-affecting PR builds the site as part of the one CI job at zero workflow churn.
  • Accepted cost — any markdown edit rebuilds the site in the required check. gather’s inputs are the repo-wide /**/*.md glob (mirroring sealed), and build/check depend on it inside the one required moon ci :ci gate — so moon’s affected graph triggers a full gather + astro build + astro check on ANY PR touching ANY *.md, not just docs PRs (a one-line README fix blocks on a full corpus build). Same tradeoff sealed accepts for its one-job gate (ci.yml:20-22); named here, not free. The narrow alternative — scope gather inputs to docs/**/*.md + the contributing files — is rejected to keep parity with sealed’s glob and avoid a package-README doc link silently going stale un-rebuilt.

(b) Per-PR preview: a GHA workflow doing wrangler pages deploy

Section titled “(b) Per-PR preview: a GHA workflow doing wrangler pages deploy”

sealed’s preview UX, at source: Woodpecker’s Petrel policy fans out docs-site:deploy on push:main and docs-site:deploy-preview on pull_request (sealed/ci/pipeline.ts:554-563), both runInCI: false moon tasks running bun scripts/deploy.ts (sealed/apps/docs/moon.yml:69-74). deploy.ts picks the Pages --branch from the event — production is the "main" alias, a PR deploys under its SOURCE branch and refuses to fall back (“Never fall back to a target branch on a PR — that would risk deploying a preview over production”, deploy.ts:62-79) — runs wrangler pages deploy dist --project-name=… --branch=… --commit-dirty=true (deploy.ts:82-94), parses the preview URL from wrangler output (deploy.ts:101-104), and upserts a marker-keyed PR comment with the preview link + changed-page deep links via the gh CLI (deploy.ts:41, COMMENT_MARKER = "<!-- docs-preview -->").

The compass version keeps deploy.ts (adapted) and swaps the driver from Woodpecker to a dedicated GHA workflow, .github/workflows/eng-docs-deploy.yml:

  • Triggers: push: branches: [main] (production) and pull_request (preview). The deploy job guards if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository — fork PRs have no secrets and must not attempt a deploy (Global Constraints).
  • Not part of moon ci :ci: the deploy is CD, needs CLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_ID secrets, and must not red the hermetic gate — the same split sealed makes (runInCI: false, moon.yml:63-68: “CD (runInCI:false): the Petrel policy … fans these out only when affected and gates the event”). The workflow runs moon run compass-eng-docs:build then bun scripts/deploy.ts (or moon run compass-eng-docs:deploy via moon exec-equivalent; the task keeps runInCI: false so the CI gate never runs it).
  • Env adaptation: deploy.ts reads Woodpecker CI_* vars (deploy.ts:21-31: CI_PIPELINE_EVENT, CI_COMMIT_SOURCE_BRANCH, CI_COMMIT_SHA, CI_REPO, CI_COMMIT_PULL_REQUEST, GH_TOKEN). The compass copy reads the GHA equivalents: GITHUB_EVENT_NAME, GITHUB_HEAD_REF, GITHUB_REPOSITORY, PR number from github.event.pull_request.number, the commit SHA from an EXPLICIT PR_HEAD_SHA (= github.event.pull_request.head.sha) on PRs — NOT GITHUB_SHA, which on a pull_request event is the ephemeral MERGE commit (refs/pull/N/merge), absent from the PR branch and unlinkable (GITHUB_SHA is correct only on push), and the workflow’s GITHUB_TOKEN (with permissions: pull-requests: write for the comment; contents: read otherwise). The DeployEnv interface is renamed to those keys — a fork of the script, not a shim layer.
  • Cloudflare mechanism: Direct Upload via wrangler — the documented CI pattern (“you can set up a GitHub Action to automate your subsequent deployments with Wrangler”, https://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/). Pages keys deployments by --branch: the production branch is the alias, any other branch yields a preview deployment with its own stable URL (https://developers.cloudflare.com/pages/configuration/preview-deployments/; same model sealed relies on, deploy.ts:33-34). cloudflare/wrangler-action exists but adds nothing over invoking wrangler from deploy.ts as sealed does — rejected to keep one deploy code path across both repos’ docsites.

Alternative considered — Cloudflare Pages GitHub integration (Pages builds the site itself on every push; automatic previews, zero workflow): rejected. The build would run outside moon (no toolchain pins, no affected graph, a second build definition drifting from apps/eng-docs/moon.yml), the gather step’s repo-wide markdown walk would need replicating in Pages’ build config, and there is no seam for the changed-pages PR comment deploy.ts provides. Direct Upload keeps the build in the one pinned toolchain.

Alternative considered — docsite deploy inside moon ci :ci: rejected; the CI job is the hermetic required check and must stay secret-free for fork PRs; deploys are event-gated CD (mirrors sealed’s runInCI: false split).

(c) Sanitization / rewrite policy — the four classes

Section titled “(c) Sanitization / rewrite policy — the four classes”

Repeatable rules applied by the migration task and binding on future records:

  1. Cross-product references (Warden, Cotal). Contextualize-and-keep. Warden is a designed Compass component, not a foreign product: the ledger’s first row reads “Compass is an agent development environment: named workstream agents supervised by a Dispatcher, gated by a Warden” (sealed/docs/designs/product/DECISIONS.md:37, DL-001), and the v0.3 design record’s §4.3 opens “Warden is the always-on security auditor” (sealed/docs/designs/product/compass.md:85-87 — the design record, not the living spec, whose Warden mentions are thin and scattered). Stripping it would gut the corpus. What stays PRIVATE is seal-the-product internals: records whose subject is seal (seal-restructure.md, seal-config-path-collision.md) do not migrate; compass records that LINK to them convert the link to plain text (“the seal restructure record, private”). Cotal is external Apache-2.0 OSS cited as a dependency (compass-0.4/design.md:25-27: “Cotal (Apache-2.0, NATS/JetStream)”) — keep verbatim. Rule of thumb: a reference is kept when the referent is a Compass component or public OSS; it is de-linked (kept as prose, link dropped) when the referent is a private sealed artifact.
  2. Private tracker links (SEA-####, linear.app). Keep the ID, strip the link. ~50 distinct SEA IDs are load-bearing provenance (records cite each other through them); a dead linear.app/sealedsecurity/... URL is worse than no URL. Rewrite BOTH link forms to plain SEA-1234: the inline [SEA-1234](https://linear.app/…) AND the reference-definition [SEA-1234]: https://linear.app/… (^\[.+\]:\s*https://linear\.app/) — the corpus’s one live URL is the latter form (compass-0.4/design.md:207), which an inline-only regex would miss. Bare SEA-1234 mentions are untouched. Public readers see an opaque internal ticket ID — honest and harmless.
  3. oss/compass/** path rewrite. Strip the prefix. 19 records cite oss/compass/<path> paths from sealed’s vendored era; the public repo’s layout is the same tree without the prefix — e.g. oss/compass/go/internal/runtime/image.go (compass-agent-container-runtime.md:54) is go/internal/runtime/image.go here, oss/compass/apps/ui/src/stub-data.ts (compass-ade-shell/design.md:16) is apps/ui/src/stub-data.ts (top-level go/ packages/ apps/ proto/ confirmed this session against the compass root). Mechanical rewrite: oss/compass/ → “ (empty) in code spans, links, and prose paths; a bare oss/compass (the vendored root itself, no trailing path — including quoted workspace literals like ["oss/seal", "oss/compass"]) → compass, since that root no longer exists in the published tree and the repo itself now IS that tree. One special case: self-referential internal-repo claims (e.g. compass-0.4/design.md:201-203: “this is an internal design record, not a published oss/compass/ artifact”) get a one-line editorial bracket noting the record now IS published — never silent rewording of frozen prose.
  4. Threat-model / security-boundary / egress sections. Keep, verbatim. Matt’s explicit ruling; 13 records carry them. Not blockers, not edited.

Compass-record design PRs may be in flight in sealed against the old convention at migration time (as of this session’s open-PR list: #1096 (docs/designs/platform/compass-dogfood-e2e/design.md), #1095 (edits a product compass record), #1087 (platform compass-agent GHCR record), plus #1089 and #1075 (compass-ux)). Q5 ruled all-at-once, so the migration is a SINGLE PR carrying the full corpus at sealed main at branch-cut — no wave-1/wave-2 split. An individual sealed design PR still open when the migration branch cuts is reconciled into that one PR rather than deferred: the executor either lands it in sealed first and includes the merged record, or re-targets it to compass. This is coherent with Q2 (compass is the source of truth): review continues in the public repo, so migrating a still-under-review record does not strand its threads — they simply continue here. New Compass records started AFTER this record merges are authored in compass directly (Q2).

Executable by an implementing agent; every task carries its own test cycle. Q1–Q5 are all RULED (see Open Questions); T6 is dropped per Q5.

Create apps/eng-docs/ mirroring sealed’s app shape, minus Access/PostHog, and register the project.

Interfaces:

  • Consumes: sealed/apps/docs/{astro.config.mjs,package.json,moon.yml,tsconfig.json,src/styles/custom.css,src/content.config.ts,.gitignore,public/favicon.svg} as templates (read from the sealed clone; do not import at build time).
  • Produces:
    • apps/eng-docs/astro.config.mjssite set per Q4’s ruling (placeholder https://compass-eng-docs.pages.dev until the domain lands), starlight({ title: "Compass Engineering Docs", pagefind: true, sidebar, editLink: {}, lastUpdated: true, tableOfContents: {...}, expressiveCode: {...} }); NO head PostHog block, NO Access assumptions.
    • apps/eng-docs/package.json — name @compass/eng-docs, scripts { gather, dev, build, preview, check } as sealed’s (sealed/apps/docs/package.json:6-12); deps astro, @astrojs/starlight, @astrojs/check, github-slugger, sharp via catalog: entries added to the root package.json workspaces.catalog.
    • apps/eng-docs/moon.yml — tasks gather (command bun scripts/gather.ts, inputs ['scripts/gather.ts', '/**/*.md', '/.markdownlint-cli2.jsonc'], outputs ['src/content/docs', 'src/sidebar.generated.ts']), build (bunx astro build, deps ['gather'], outputs ['dist']), check (bunx astro check, deps ['gather']), deploy + deploy-preview (bun scripts/deploy.ts, deps ['build'], options.runInCI: false), and a ci task depending on ['build', 'check'] so the project joins moon ci :ci.
    • .moon/workspace.yml — add compass-eng-docs: 'apps/eng-docs' under projects, same change (per workspace.yml:41-47’s silent-inert warning).
    • apps/eng-docs/.gitignoresrc/content/docs/, src/sidebar.generated.ts, dist/, .astro/.
  • Test cycle: moon run compass-eng-docs:build and moon run compass-eng-docs:check green locally; moon query projects lists compass-eng-docs; a scratch PR shows the CI job picking the project up via the affected graph.

T2 — Gather script adapted to the compass layout

Section titled “T2 — Gather script adapted to the compass layout”

Port gather.ts + gather.test.ts with the compass taxonomy.

Interfaces:

  • Consumes: sealed/apps/docs/scripts/gather.ts (491 lines) + gather.test.ts as the port source.
  • Produces: apps/eng-docs/scripts/gather.ts with:
    • const REPO_SLUG = "sealedsecurity/compass" (editUrl base, mirrors sealed gather.ts:25,203-205).
    • const DOMAINS = ["designs", "specs", "architecture"] as const (replaces sealed’s ["designs","specs","research","team"], gather.ts:31).
    • SECTIONS in sidebar order: Designs, Specs, Architecture, Packages, Contributing (drops sealed’s Research/Team/Infra, gather.ts:43-51).
    • packagePath drops the oss arm (sealed gather.ts:78, keep the apps two-segment case).
    • parseExclusions returns [...ignores, "**/outputs/**"] (drops "oss/seal/**", sealed gather.ts:131-137); ignores read from compass/.markdownlint-cli2.jsonc (currently ["forks/*/**"]).
    • CONTRIBUTING_FILES = compass’s root convention files: README.md, AGENTS.md, CONTRIBUTING.md, forks/README.md.
    • Everything else (frontmatter injection, H1 strip, link rewriting, sidebar generation, index page) ports unchanged.
  • Produces: apps/eng-docs/scripts/gather.test.ts — the ported unit suite with fixtures updated to compass paths (classification of docs/designs/repo/x.md, go/README.md → packages, exclusion of forks/oh-my-pi/**).
  • Test cycle: bun test apps/eng-docs/scripts/gather.test.ts green; moon run compass-eng-docs:gather then moon run compass-eng-docs:build renders this record at /designs/repo/compass-eng-docs/design/ with a working editUrl.

Port deploy.ts to GHA env vars; add the CD workflow.

Interfaces:

  • Consumes: sealed/apps/docs/scripts/deploy.ts (495 lines) + deploy.test.ts as the port source; Cloudflare Direct Upload docs (cited in Approach (b)).
  • Produces: apps/eng-docs/scripts/deploy.ts with:
    • const PROJECT_NAME = "compass-eng-docs" (Q4), COMMENT_MARKER = "<!-- compass-eng-docs-preview -->", SITE_LABEL = "Compass engineering docs".
    • interface DeployEnv { GITHUB_EVENT_NAME?; GITHUB_HEAD_REF?; GITHUB_SHA?; PR_HEAD_SHA?; GITHUB_REPOSITORY?; PR_NUMBER?; GH_TOKEN?; CLOUDFLARE_API_TOKEN?; CLOUDFLARE_ACCOUNT_ID? } — replaces the Woodpecker CI_* set (sealed deploy.ts:21-31). The recorded commit SHA is PR_HEAD_SHA on PRs (= github.event.pull_request.head.sha) and GITHUB_SHA on push; never GITHUB_SHA on a PR (it is the merge commit, not the branch head).
    • isPullRequest(env)env.GITHUB_EVENT_NAME === "pull_request"; deployBranch(env) keeps the refuse-to-fall-back invariant on GITHUB_HEAD_REF (sealed deploy.ts:68-79); wranglerArgs, parsePreviewUrl, commentBody, changedDocPages, escape/encode helpers port unchanged.
  • Produces: .github/workflows/eng-docs-deploy.yml:
    • on: { push: { branches: [main] }, pull_request: {} }; permissions: { contents: read, pull-requests: write }; concurrency: eng-docs-deploy-${{ github.ref }} with cancel-in-progress: true.
    • One job deploy, if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository (fork guard); steps: checkout, setup-bun/node/moon from .prototools pins (reuse ci.yml’s pin-reading step, ci.yml:154-196), moon run compass-eng-docs:build, then bun scripts/deploy.ts with env: { CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}, CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}, GH_TOKEN: ${{ github.token }}, PR_NUMBER: ${{ github.event.pull_request.number }}, PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} }.
  • Produces: apps/eng-docs/scripts/deploy.test.ts — ported pure-function suite (branch selection, fork of the env mapping, comment body, URL parse).
  • Test cycle: bun test apps/eng-docs/scripts/deploy.test.ts green; after Q4’s project exists, a scratch PR shows the preview comment with a live *.pages.dev URL, and a merge to main updates production.

T4 — Sanitization rewrite pass (Q3 ruled; policy classes final)

Section titled “T4 — Sanitization rewrite pass (Q3 ruled; policy classes final)”

Encode the four-class policy as a one-shot migration script + a documented checklist for future records.

Interfaces:

  • Consumes: the sealed corpus enumerated this session — the 38 product design records (37 matching sealed/docs/designs/product/compass-* plus sealed/docs/designs/product/compass.md, the v0.3 base record whose name has no hyphen and so is not matched by the compass-* glob), sealed/docs/designs/platform/compass-dogfood-loop/design.md, sealed/docs/specs/product/compass.md, sealed/docs/specs/product/README.md.
  • Produces: tools/docs-migrate/migrate.ts (one-shot, deleted at the end of the migration) applying, per file: (1) linear.app link strip — BOTH the inline [SEA-<n>](https://linear.app/…) and the reference-definition [SEA-<n>]: https://linear.app/… forms → SEA-<n>; (2) oss/compass/ prefix strip in paths/links/code spans; (3) link-to-private-record conversion for seal-*.md targets → plain prose; (4) NO edits to security sections. Emits a per-file diff summary for review. Editorial brackets (the self-referential internal-record claims, Approach (c) class 3) are applied by hand in the same PR, listed in the PR body.
  • Produces: docs/designs/CONTRIBUTING.md (or a section in root CONTRIBUTING.md) stating the four rules as the standing policy for records authored here.
  • Test cycle: bun test tools/docs-migrate over fixture snippets for each class (including the reference-definition link form); post-run grep gates (authoritative over the rewrite regexes — the run fails if any form slipped): zero linear.app matches, zero oss/compass matches under docs/designs/** + docs/specs/**; markdownlint-cli2 clean.

T5 — Full-corpus migration + sealed-side removal (Q1/Q2/Q5 ruled)

Section titled “T5 — Full-corpus migration + sealed-side removal (Q1/Q2/Q5 ruled)”

Land the whole Compass corpus in compass in a single PR (Q5: all at once); remove it from sealed. compass is the source of truth going forward (Q2), so design review continues here, not in sealed.

Interfaces:

  • Consumes: T4’s script output over the full corpus at sealed main at branch-cut — the 38 product design records, compass-dogfood-loop/design.md, and both product specs. Any sealed design PR still open at migration time is reconciled to compass rather than split into a later wave (executor sequences: merge-in-sealed-first then migrate, or re-target the PR to compass — both keep the corpus whole); the wave-1/wave-2 split is dropped per Q5.
  • Produces (compass PR): docs/designs/product/** (the 38 product records), docs/designs/product/compass-dogfood-loop/design.md (a Compass product-process design; platform/ — a sealed-monorepo domain — does not exist in compass, and repo-tooling records like THIS one live under docs/designs/repo/), docs/specs/product/{compass.md,README.md}, plus (Q1 ruled MOVE) docs/designs/product/DECISIONS.md + tools/design-ledger-gate/** (ported; PRODUCT_DIR stays docs/designs/product, index.ts:45) + a design-ledger-gate moon project registered in .moon/workspace.yml with a ci task.
  • Produces (sealed PR, sequenced after the compass PR merges): delete the migrated trees; leave sealed/docs/designs/product/README.md pointing at the public repo; sealed’s residual ledger keeps the seal-product rows DL-045/DL-046 (Q1); sealed’s gather needs no change (deleted files simply stop being gathered).
  • Test cycle: compass moon run :ci green (docsite builds the full corpus; ledger gate green after the move); site renders the ledger and both spec pages; sealed moon run docs-site:build green post-removal; tools/docs-migrate/ deleted after the one-shot run.

T6 — Wave-2 straggler sweep — DROPPED (Q5 ruled all at once)

Section titled “T6 — Wave-2 straggler sweep — DROPPED (Q5 ruled all at once)”

Q5 ruled “all at once”, so there is no separate wave-2 sweep: T5 migrates the whole corpus in one PR and deletes tools/docs-migrate/ at the end. Any sealed design PR still open at migration time is reconciled to compass by T5 (see T5 Consumes), not deferred to a later wave.

T7 — Cloudflare Pages project provisioning (Q4 ruled)

Section titled “T7 — Cloudflare Pages project provisioning (Q4 ruled)”

Interfaces:

  • Consumes: Q4 ruling (below) — project compass-eng-docs, custom domain eng.compass.rigel.build, provisioned via sealed’s Pulumi IaC lane.
  • Produces: the compass-eng-docs Pages project via sealed’s Pulumi IaC (sealed precedent: the docsite got “its OWN Pages:Edit-scoped token once SEA-1119 provisions the project”, sealed/ci/pipeline.ts:334-336); repo secrets CLOUDFLARE_API_TOKEN (Pages:Edit-scoped, least-privilege per pipeline.ts:321-324) + CLOUDFLARE_ACCOUNT_ID on sealedsecurity/compass; production branch set to main. NOTE: creating the Pages project + seeding the two repo secrets is an operator step (not agent-executable).
  • Test cycle: T3’s scratch-PR preview + main production deploy succeed end to end.
  • T1 — apps/eng-docs scaffold + compass-eng-docs moon registration; build/check green in moon ci :ci.
  • T2 — gather.ts/gather.test.ts ported to the compass taxonomy; this record renders.
  • T3 — deploy.ts on GHA env + .github/workflows/eng-docs-deploy.yml with the fork guard.
  • T4 — sanitization migration script + standing policy doc (Q3 ruled; classes final).
  • T5 — full-corpus migration (all records, one PR) + sealed-side removal; ledger + ledger-gate moved to compass (Q1/Q2/Q5 ruled).
  • T6 — DROPPED: Q5 ruled all-at-once; wave-2 sweep folded into T5.
  • T7 — Cloudflare Pages compass-eng-docs + eng.compass.rigel.build + secrets provisioned via Pulumi (Q4 ruled).

Batched for Matt — ALL RULED (below): Q1/Q2/Q4/Q5 ruled 2026-08-05, Q3 ruled earlier. T5 and T7 execute against these rulings; T6 is dropped (Q5).

  • Q1 — Does the design-ledger + ledger-gate + gather tooling move to compass, or stay in sealed? The ledger is Compass-titled and Compass-governed (“Compass design-decision ledger”, sealed/docs/designs/product/DECISIONS.md:1) and the gate hardcodes PRODUCT_DIR = "docs/designs/product" (sealed/tools/design-ledger-gate/index.ts:45), so it follows the corpus. RULED (Matt): MOVE ledger + ledger-gate to compass (the gate ports with its path constant unchanged); the two seal-product rows (DL-045, DL-046, DECISIONS.md:197-198) stay behind in a small sealed-side residual ledger next to the seal records they cite. gather.ts is NOT shared — each repo keeps its own copy (they already diverge on taxonomy); no extraction. Verified the cut is edge-clean: DL-045/DL-046 are the ONLY rows under the ## Seal heading, neither supersedes nor is superseded by any row, and they are the only rows whose Record link points at a seal-*.md file — so moving DL-001..DL-128 minus DL-045/046 to compass severs zero supersession edges and leaves zero dangling Record links; the gate’s dangling-pointer check stays green after the split (DL-046’s prose mentions “the Compass Warden security model” but that is not a ledger EDGE). Q1 is the most defensible choice, not the weakest.
  • Q2 — Is compass the SOURCE OF TRUTH for Compass records going forward, or a published mirror gathered from sealed? RULED (Matt): SOURCE OF TRUTH. A mirror keeps every design PR in the private repo, defeating build-in-the-open (readers see outputs, not the review process); it also needs a cross-repo sync job that can silently drift. Authoring here means the design PRs, review threads, and preview links are all public — the point of the exercise. This record dogfoods the ruling. Named consequence of moving the corpus out of sealed (T5 deletes the sealed trees): several ACTIVE non-compass sealed records cite INTO the compass corpus by bare file:line/§ anchor (verified: sea-1023-acp-session.md, sea-1115-agent-provisioning-cotal.md, platform/go-toolchain-default.md at ~40 refs, platform/repo-structure-reorg.md, others) whose lane is seal/agent/platform, so they do NOT migrate. Mitigating: these are bare-prose citations, NOT Markdown hyperlinks (a link-form grep returns zero), so sealed’s own gather.ts renders no 404 — only the human/agent grounding value is severed. Recommendation: leave-as-historical (the SEA ID and record name in the prose still name the target) plus the sealed-side README.md pointer T5 already leaves at docs/designs/product/; a per-file pointer stub is not worth the churn.
  • Q3 — Warden/Cotal cross-product references: hold-and-omit vs contextualize-and-keep? RULED (Matt): contextualize-and-keep for both. Warden is a designed Compass component (DL-001; v0.3 design record §4.3) and Cotal is public Apache-2.0 OSS. Matt ruled the seal-the-product detail that rides along in the kept Warden sections is a non-issue — “Seal is already OSS, the wasm setup is public” — so §6.5’s seal WASM-agent-loop internals and DL-046’s seal-crate restructure stay verbatim, with no scrub sub-rule. He likewise ruled internal fleet/agent handles, the matt user name, and the Co-Authored-By: seal commit trailers are non-issues (“fleet handles, matt, seal i don’t care about”) — so the policy adds NO internal-identifier scrub class. The two records whose SUBJECT is seal-the-product (seal-restructure.md, seal-config-path-collision.md) still do not migrate — out of the Compass corpus — and compass records linking to them de-link to plain prose (class 1).
  • Q4 — Cloudflare Pages project + custom domain: does one exist, who provisions? No compass Pages project exists (sealed’s pipeline knows only sealed-docs + the marketing site, sealed/ci/pipeline.ts:333-343). RULED (Matt): provision a new compass-eng-docs project + a Pages:Edit-scoped token via sealed’s existing Pulumi IaC lane (the SEA-1119 precedent, pipeline.ts:334-336), production branch main; custom domain eng.compass.rigel.build (Matt ruled). The docs.compass naming and docs.compass.rigel.build domain are deliberately RESERVED for the future user-facing product docsite (a separate site), so this engineering docs site takes the eng. subdomain. Until the domain lands the site ships on compass-eng-docs.pages.dev. Analytics (PostHog snippet, minus sealed’s Access-cookie inheritance) rides the domain decision.
  • Q5 — Migration scope: all 39 at once vs settled-now + in-flight-after freeze? RULED (Matt): all at once — migrate the full corpus in one PR, no two-wave split (supersedes the wave recommendation). Coherent with Q2: because compass is the source of truth, design review continues in the public repo, so the “strands review threads” cost the two-wave split was meant to avoid is instead accepted — any sealed design PR still open at migration time is reconciled to compass by T5 (merge-in-sealed-first then migrate, or re-target to compass — executor sequences), not deferred to a later wave. T6 is therefore dropped.