A `/policies` section for governance documents
- Status: Accepted
- Date: 2026-09-05
- Deciders: Principal, Engineering
Context
Section titled “Context”B Corp certification (B Lab’s Purpose & Stakeholder Governance requirements) obliges the firm to publish a class of documents the site never had a home for: a stakeholder grievance mechanism first, with a responsible-marketing policy, an accessibility statement, and impact reporting to follow. The site’s only documents of this kind were two legal pages at top-level slugs, /privacy and /terms, each a hand-authored .astro page with an eyebrow reading “Legal”, no index, and a footer strip as their only entry point.
Adding the grievance page as a third top-level slug would have been the path of least resistance and the wrong information architecture: three unrelated slugs, three page templates, no way to enumerate the class for the footer or the sitemap, and nothing to tell a visitor that these documents belong together. The class is small today and will grow; the decision is where it lives and how it is modelled.
Decision
Section titled “Decision”Governance and legal documents live under one section, /policies, backed by one content collection.
- Routes.
/policiesis an index that lists every published policy grouped by category. Each document is/policies/<slug>. The grievance mechanism is/policies/grievances, with its confirmation at/policies/grievances/received(ADR 0017). - Content model. A
policiescollection (apps/web/src/content/policies/<slug>.mdx, flat MDX, Zod-validated per ADR 0004):title,summary(the index blurb and meta description, held to the 70–160-character hygiene rule),category(governance|legal, extensible),updated,owner(the accountable role, never a person’s name — people change and a name on a policy page rots),order,interactive,sitemap,draft. Category labels and order live inapps/web/src/lib/policies.ts. - Templates. One layout,
apps/web/src/layouts/Policy.astro: category eyebrow with a route back to the index, title, “last updated” stamp, accountable role, prose body.pages/policies/[...slug].astroprerenders every non-interactive entry. Aninteractive: trueentry (a document with a live process, such as the grievance form) is excluded from the static template and rendered by its own on-demand route under the same directory, while remaining a collection entry so the index, footer, and sitemap treat it as one of the class. - Privacy and Terms move into the collection at
/policies/privacyand/policies/terms. The old paths issue 301 redirects fromapps/web/public/_redirects, the same edge mechanism as the/cases/*continuity in ADR 0011, so contracts and inbound links that cite the old URLs stay whole. Both keepsitemap: false: thin legal boilerplate never needs to rank. The sitemap integration reads that flag (andupdated, forlastmod) from the collection’s frontmatter, and lists the on-demand grievance page explicitly, since on-demand routes are invisible to it. - Entry points. Governance documents are ways to reach us, not boilerplate, so they sit with the contact channels: the footer’s contact column gains Raise a concern; the footer’s legal strip becomes Privacy · Terms · All policies; the contact page routes non-project intent (“Have a concern rather than a project?”) to the mechanism; the careers page does the same for candidates and staff; the privacy policy describes and links the grievance data handling;
llms.txtlists the section. Nothing enters the header: these are not marketing destinations.
Consequences
Section titled “Consequences”Positive
- Adding a policy is adding one MDX file. The index, the footer strip’s “All policies” link, the sitemap, and the docs all follow from the collection; no new page template per document.
- The class is legible: a visitor who finds one policy can find them all, and every document carries its category, its last-updated date, and the role that owns it.
- Interactive documents and static prose share a shell and a URL scheme, so the grievance mechanism reads as a policy with a form in it, not as a separate product.
- The static
/policiesindex joins the backbone-route contract automatically (e2e, axe, SEO); the static policy template is Lighthouse-gated via/policies/privacy.
Negative / costs
- Two published URLs change. Slash-less
/privacyand/termsredirect in one 301; trailing-slash forms chain through the edge’s slash normalization exactly as ADR 0011 describes for/cases/*. Both pages were already excluded from the sitemap, so the search cost is negligible. summaryis held to the meta-description window, which constrains how a policy is described on the index. That is the intent.
Alternatives considered
Section titled “Alternatives considered”- More top-level slugs (
/grievances,/accessibility, …). Zero migration, but no grouping, no index, and a footer that grows a link per document. Rejected: the class needs a home. /legal/. The natural home for privacy and terms, but a grievance mechanism, an accessibility statement, and an impact report are not legal documents, and the label would misdescribe most of the class. Rejected./governance/or/impact/. B Lab’s own vocabulary, but opaque to the general public the grievance page is for, and “impact” describes reports rather than policies. Rejected; either may later appear as a category inside/policies.- Leave privacy and terms where they were, cross-listed on the index. Avoids the redirects, at the price of a class whose members live at two depths. Rejected in favour of one consistent scheme; the redirect cost is small and already paid for by the ADR 0011 mechanism.
- Hand-authored
.astropages per policy, no collection. What the two legal pages were. Rejected: it is the situation this record replaces.