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.
Problem / Intent
Section titled “Problem / Intent”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.
Global Constraints
Section titled “Global Constraints”- 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_requestevents from forks run WITHOUT repository secrets — and GitHub forcesGITHUB_TOKENread-only on a forkpull_requestregardless of the workflowpermissions: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, node24.18.0, moon2.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 inapps/eng-docs/package.jsonfollowing 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 :cion PRs andmoon run :cion 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 newapps/eng-docsproject MUST be registered in.moon/workspace.ymlin the same change that adds the tree, or its CI tasks silently gate nothing (workspace.yml:41-47: “Aforks/<name>/moon.ymlwith 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
*.mdwithignores: ["forks/*/**"](compass/.markdownlint-cli2.jsonc:8-10); migrated records must pass it. - Hygiene-rule carve-out for design records. compass
AGENTS.md:55-58bans “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.
Approach
Section titled “Approach”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 layout —
apps/eng-docs/{astro.config.mjs,package.json,moon.yml, tsconfig.json,src/,public/,scripts/{gather.ts,deploy.ts}}withsrc/content/docs/+src/sidebar.generated.tsgenerated 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 keysdocs/<domain>/…to the matching section and routes everything else to contributing/packages (gather.ts:94-123). Exclusions come from the canonical.markdownlint-cli2.jsoncignoreslist — “the single source of truth” (gather.ts:126-137,parseExclusionsreturns[...ignores, "oss/seal/**", "**/outputs/**"]). - Per-page editUrl —
editUrlForbuildshttps://github.com/${REPO_SLUG}/edit/main/${sourcePath}(gather.ts:203-205), withREPO_SLUG = "sealedsecurity/sealed"(gather.ts:25). - Starlight config —
pagefind: true, generatedsidebar,editLink,lastUpdated, expressiveCode tokyo-night (astro.config.mjs:51-66). - moon tasks —
gather(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), anddeploy/deploy-preview(runInCI: false, bothbun scripts/deploy.ts,moon.yml:69-74).
The compass adaptation:
- Sections:
Designs,Specs,Contributing,Packages— compass has nodocs/research/ordocs/team/orinfra/tree (top level verified this session:go/ packages/ apps/ proto/ tools/ forks/ docs/ agent-image/).docs/architecture/classifies under Designs? No — it keeps its ownArchitecturesection (adocs/domain like the others); the DOMAINS list becomes["designs", "specs", "architecture"]. REPO_SLUG = "sealedsecurity/compass"; editUrl points here.- Exclusions: compass’s
.markdownlint-cli2.jsoncignoresis["forks/*/**"](compass/.markdownlint-cli2.jsonc:10); the gather adds its own tree +**/outputs/**as sealed’s does, and drops theoss/seal/**special case (no such tree here). packagePath’sossspecial case drops (sealed gather.ts:78special-casesapps|osstwo-segment package ids; compass keepsappsand gains nothing foross, which does not exist here).- No Cloudflare Access, no PostHog identity inheritance — sealed’s
cross_subdomain_cookiePostHog 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.ymlprojects. Itsbuild+checktasks joinmoon ci :ciautomatically (“a new project with acitask 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/**/*.mdglob (mirroring sealed), andbuild/checkdepend on it inside the one requiredmoon ci :cigate — so moon’s affected graph triggers a full gather +astro build+astro checkon 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 — scopegatherinputs todocs/**/*.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) andpull_request(preview). The deploy job guardsif: 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, needsCLOUDFLARE_API_TOKEN/CLOUDFLARE_ACCOUNT_IDsecrets, 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 runsmoon run compass-eng-docs:buildthenbun scripts/deploy.ts(ormoon run compass-eng-docs:deployviamoon exec-equivalent; the task keepsrunInCI: falseso the CI gate never runs it). - Env adaptation:
deploy.tsreads WoodpeckerCI_*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 fromgithub.event.pull_request.number, the commit SHA from an EXPLICITPR_HEAD_SHA(=github.event.pull_request.head.sha) on PRs — NOTGITHUB_SHA, which on apull_requestevent is the ephemeral MERGE commit (refs/pull/N/merge), absent from the PR branch and unlinkable (GITHUB_SHAis correct only onpush), and the workflow’sGITHUB_TOKEN(withpermissions: pull-requests: writefor the comment;contents: readotherwise). TheDeployEnvinterface 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-actionexists but adds nothing over invoking wrangler fromdeploy.tsas 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:
- 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. - 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 plainSEA-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. BareSEA-1234mentions are untouched. Public readers see an opaque internal ticket ID — honest and harmless. oss/compass/**path rewrite. Strip the prefix. 19 records citeoss/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) isgo/internal/runtime/image.gohere,oss/compass/apps/ui/src/stub-data.ts(compass-ade-shell/design.md:16) isapps/ui/src/stub-data.ts(top-levelgo/ packages/ apps/ proto/confirmed this session against the compass root). Mechanical rewrite:oss/compass/→ “ (empty) in code spans, links, and prose paths; a bareoss/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 publishedoss/compass/artifact”) get a one-line editorial bracket noting the record now IS published — never silent rewording of frozen prose.- Threat-model / security-boundary / egress sections. Keep, verbatim. Matt’s explicit ruling; 13 records carry them. Not blockers, not edited.
(d) Migration timing
Section titled “(d) Migration timing”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.
T1 — Site scaffold + moon registration
Section titled “T1 — Site scaffold + moon registration”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.mjs—siteset per Q4’s ruling (placeholderhttps://compass-eng-docs.pages.devuntil the domain lands),starlight({ title: "Compass Engineering Docs", pagefind: true, sidebar, editLink: {}, lastUpdated: true, tableOfContents: {...}, expressiveCode: {...} }); NOheadPostHog 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); depsastro,@astrojs/starlight,@astrojs/check,github-slugger,sharpviacatalog:entries added to the rootpackage.jsonworkspaces.catalog.apps/eng-docs/moon.yml— tasksgather(commandbun 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 acitask depending on['build', 'check']so the project joinsmoon ci :ci..moon/workspace.yml— addcompass-eng-docs: 'apps/eng-docs'underprojects, same change (perworkspace.yml:41-47’s silent-inert warning).apps/eng-docs/.gitignore—src/content/docs/,src/sidebar.generated.ts,dist/,.astro/.
- Test cycle:
moon run compass-eng-docs:buildandmoon run compass-eng-docs:checkgreen locally;moon query projectslistscompass-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.tsas the port source. - Produces:
apps/eng-docs/scripts/gather.tswith:const REPO_SLUG = "sealedsecurity/compass"(editUrl base, mirrorssealed gather.ts:25,203-205).const DOMAINS = ["designs", "specs", "architecture"] as const(replaces sealed’s["designs","specs","research","team"],gather.ts:31).SECTIONSin sidebar order: Designs, Specs, Architecture, Packages, Contributing (drops sealed’s Research/Team/Infra,gather.ts:43-51).packagePathdrops theossarm (sealed gather.ts:78, keep theappstwo-segment case).parseExclusionsreturns[...ignores, "**/outputs/**"](drops"oss/seal/**",sealed gather.ts:131-137); ignores read fromcompass/.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 ofdocs/designs/repo/x.md,go/README.md→ packages, exclusion offorks/oh-my-pi/**). - Test cycle:
bun test apps/eng-docs/scripts/gather.test.tsgreen;moon run compass-eng-docs:gatherthenmoon run compass-eng-docs:buildrenders this record at/designs/repo/compass-eng-docs/design/with a working editUrl.
T3 — Deploy script + GHA workflow
Section titled “T3 — Deploy script + GHA workflow”Port deploy.ts to GHA env vars; add the CD workflow.
Interfaces:
- Consumes:
sealed/apps/docs/scripts/deploy.ts(495 lines) +deploy.test.tsas the port source; Cloudflare Direct Upload docs (cited in Approach (b)). - Produces:
apps/eng-docs/scripts/deploy.tswith: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 WoodpeckerCI_*set (sealed deploy.ts:21-31). The recorded commit SHA isPR_HEAD_SHAon PRs (=github.event.pull_request.head.sha) andGITHUB_SHAon push; neverGITHUB_SHAon 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 onGITHUB_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 }}withcancel-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.prototoolspins (reuseci.yml’s pin-reading step,ci.yml:154-196),moon run compass-eng-docs:build, thenbun scripts/deploy.tswithenv: { 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.tsgreen; after Q4’s project exists, a scratch PR shows the preview comment with a live*.pages.devURL, 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-*plussealed/docs/designs/product/compass.md, the v0.3 base record whose name has no hyphen and so is not matched by thecompass-*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 forseal-*.mdtargets → 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 rootCONTRIBUTING.md) stating the four rules as the standing policy for records authored here. - Test cycle:
bun test tools/docs-migrateover 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): zerolinear.appmatches, zerooss/compassmatches underdocs/designs/**+docs/specs/**;markdownlint-cli2clean.
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
mainat 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 underdocs/designs/repo/),docs/specs/product/{compass.md,README.md}, plus (Q1 ruled MOVE)docs/designs/product/DECISIONS.md+tools/design-ledger-gate/**(ported;PRODUCT_DIRstaysdocs/designs/product,index.ts:45) + adesign-ledger-gatemoon project registered in.moon/workspace.ymlwith acitask. - Produces (sealed PR, sequenced after the compass PR merges): delete the
migrated trees; leave
sealed/docs/designs/product/README.mdpointing 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 :cigreen (docsite builds the full corpus; ledger gate green after the move); site renders the ledger and both spec pages; sealedmoon run docs-site:buildgreen 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 domaineng.compass.rigel.build, provisioned via sealed’s Pulumi IaC lane. - Produces: the
compass-eng-docsPages 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 secretsCLOUDFLARE_API_TOKEN(Pages:Edit-scoped, least-privilege perpipeline.ts:321-324) +CLOUDFLARE_ACCOUNT_IDonsealedsecurity/compass; production branch set tomain. 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-docsscaffold +compass-eng-docsmoon registration; build/check green inmoon ci :ci. - T2 —
gather.ts/gather.test.tsported to the compass taxonomy; this record renders. - T3 —
deploy.tson GHA env +.github/workflows/eng-docs-deploy.ymlwith 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).
Open Questions
Section titled “Open Questions”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 hardcodesPRODUCT_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.tsis 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## Sealheading, neither supersedes nor is superseded by any row, and they are the only rows whose Record link points at aseal-*.mdfile — 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.mdat ~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 owngather.tsrenders 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-sideREADME.mdpointer T5 already leaves atdocs/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
mattuser name, and theCo-Authored-By: sealcommit 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 newcompass-eng-docsproject + a Pages:Edit-scoped token via sealed’s existing Pulumi IaC lane (the SEA-1119 precedent,pipeline.ts:334-336), production branchmain; custom domaineng.compass.rigel.build(Matt ruled). Thedocs.compassnaming anddocs.compass.rigel.builddomain are deliberately RESERVED for the future user-facing product docsite (a separate site), so this engineering docs site takes theeng.subdomain. Until the domain lands the site ships oncompass-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.