Source for hivecommons.dev (GitHub Pages). Static site: index.html, style.css, and
assets/ — no build step.
Custom domain is pinned via CNAME. llms.txt carries a short machine-readable summary
for LLM crawlers (its integration and sponsor lists are gated against index.html by
scripts/llms-txt.test.mjs), and sitemap.xml lists the canonical top-level pages (not every
shortcut redirect) for robots.txt's Sitemap: entry — when adding a new top-level
page, add it to sitemap.xml too.
.well-known/security.txt is the RFC 9116 security contact for the domain (it points at
security@hivecommons.dev and the org SECURITY.md). Its Expires: field must be at
most a year out, so bump it once a year. GitHub Pages builds the site with Jekyll, which
drops dot-directories unless they are listed in _config.yml's include: — that is the
only reason _config.yml exists; keep .well-known in it.
Short paths like /docs, /code, /discord are meta-refresh redirect pages. All of them
are generated by make-redirects.sh: edit the MAP at the top of the script, run
./make-redirects.sh, then commit the regenerated index.html files — don't hand-edit
them, since a future run of the script will silently overwrite any hand edit.
scripts/check-redirects.sh enforces this: it regenerates the pages into a scratch
directory and fails if any committed redirect page is missing from MAP, any MAP
entry has no committed page, or a committed page differs from what the script emits.
The Link check workflow (.github/workflows/links.yml) runs its links, test and
redirects jobs on every pull request and on every push to main, so commits pushed
straight to main are checked too and main carries a same-named baseline to compare a
red PR job against.
scripts/check-links.sh [--external-warn]— resolves every internal link/anchor and probes external URLs (CI runs it with--external-warnon PRs and pushes; the weekly scheduled run and manual dispatch hard-fail on broken external links). Besides every committed page and themake-redirects.shmap, it checks the URI fields of.well-known/security.txt(RFC 9116Contact,Canonical,Policy,Acknowledgments,Hiring) and everyhttps?://URL inllms.txt, so a rotted security-contact link or machine-readable-summary link is reported too. It also checksog:image/og:url/twitter:imagecontent=URLs and JSON-LDurl/logo/codeRepository/sameAs/licensevalues.scripts/check-links.test.sh— fixture-based self-test of the link checker; runs offline (fakecurl).node --test scripts/story-dialog.test.mjs— runs the Share-your-story dialog script fromstories/index.htmlagainst a stub DOM and checks the prefilled-issue URL, validation, 1500-char trim, and popup-blocked fallback. Zero dependencies.node --test scripts/acmm-levels.test.mjs— runs the ACMM levels tablist script fromindex.htmlagainst a stub DOM with deterministic timers and checks tab activation, roving tabindex, arrow/Home/End keys, auto-rotation, and every pause condition (hover, focus, hidden tab, reduced motion, out of view). Zero dependencies.node --test scripts/carousels.test.mjs— runs the hero carousel and projects carousel scripts fromindex.htmlagainst a stub DOM with deterministic timers and checks dot generation, slide/aria-currentstate, live-region announcements, keyboard/swipe/hash navigation, auto-rotate and every pause condition, hero height measurement (clones, resize debounce), and projects scroll-sync (debounced scroll,scrollend, IntersectionObserver). Zero dependencies.node --test scripts/page-scripts.test.mjs— static gate over every committed HTML page: each inline<script>parses, each JSON-LD block is valid schema.org JSON, no external<script src>is introduced, every inline behaviour script (other than the one-lineno-jstoggle) is registered inHARNESSED_SCRIPTSwith the harness test that extracts and runs it — so a new inline script cannot ship with only a syntax check — andsitemap.xmllists exactly the canonical top-level pages (redirect shortcuts excluded, new pages required). Zero dependencies.node --test scripts/page-markup.test.mjs— static markup gate over every committed non-redirect HTML page:ids are unique, every ARIA idref (aria-controls,aria-labelledby,aria-describedby, …) andlabel forresolves to anidon the page, every<img>hasalt,<html>declareslang,role="tab"/"tabpanel"carry their ARIA pair, and every[data-*]selector an inline script queries exists in the markup (so a dropped attribute cannot make a carousel silently no-op). Each rule has a fixture self-test. Zero dependencies.node --test scripts/page-structure.test.mjs— static tree gate over every committed HTML page (redirect shortcuts and404.htmlincluded). Browsers never reject malformed HTML, they repair it silently, so the gate parses each page the way the HTML tokenizer does (comments, raw-text<script>/<style>/<textarea>/<title>bodies, quoted attributes, the spec's optional end tags for<li>/<p>/<option>/table parts, foreign content for inline SVG) and fails on anything a browser would have to repair: a stray or mismatched end tag, an element never closed,</br>or a self-closing<div/>, a block element inside<p>(which leaves its</p>stray), an attribute repeated on one element (the second is dropped), a raw</>inside an attribute value (which truncates the tag every sibling gate tokenizes with[^>]*),<li>/<option>/<tr>/<figcaption>/… outside their required parent, interactive content nested inside<a>/<button>/<label>, non-metadata content in<head>, and a page without exactly one<html>,<head>and<body>(plus exactly one<main>on non-redirect pages). Each rule has a fixture self-test. Zero dependencies.node --test scripts/style-contract.test.mjs— static gate overstyle.css(every local stylesheet the pages link) and the pages' inline<style>blocks: comments, strings, braces and parens balance and every declaration isname: value; everyvar(--x)without a fallback names a custom property defined somewhere (stylesheet, inlinestyle=, or a scriptsetProperty); every[data-*]selector in CSS matches an element on some page; every class an inline script adds/toggles is styled by a rule (or read back by the script); every custom property a script sets (--hero-h) is read by CSS; every localurl()(self-hosted@font-facefiles, images) names a committed file whose magic bytes match itsformat()hint or extension; and every font file underassets/fonts/is referenced by someurl(). Each rule has a fixture self-test. Zero dependencies.node --test scripts/no-js-controls.test.mjs— static gate over every committed non-redirect page's script-only controls: each<button type="button">(the kind that does nothing until an inline script attaches a handler) must be unreachable without JavaScript — it or an ancestor carrieshidden(the script reveals it), it sits inside a closed<dialog>, or a.no-js … { display: none; }rule in the page's stylesheets matches it or an ancestor — unless it is arole="tab"whosearia-controlspanel is already visible without JavaScript. Submit/reset buttons have native form behaviour and are left alone. A new JavaScript-driven call to action can therefore not ship as a dead button forno-jsvisitors, the way[data-story-open]on/stories/did (#216). Each rule has a fixture self-test. Zero dependencies.node --test scripts/font-licenses.test.mjs— static gate over the licence files that must travel with the self-hosted fonts: every font family underassets/fonts/(the file name up to the first-) has anOFL-<Family>.txt,Apache-2.0-<Family>.txtorUFL-<Family>.txt. Every licence still matches a shipped family and carries the markers for its type (SIL Open Font License 1.1, Apache License 2.0 or Ubuntu Font Licence 1.0); OFL files also open with a Copyright notice. The directory holds only fonts and these licence files. Preserve the upstream licence text and notices, including attribution in the same file. The gate checks markers, not the complete text or whether the licence applies to the font. Additional licence types need aLICENSE_TYPESentry and fixtures in the script.style-contract.test.mjschecks font bytes; this gate catches a deleted licence or a new family added without one. Each rule has a fixture self-test. Zero dependencies.node --test scripts/page-images.test.mjs— static gate over every local image the pages embed (<img src>/srcset, icon andpreload as="image"<link>s,og:image): each is a committed, non-empty file whose magic bytes match its extension (a PNG re-exported as SVG under the old.pngname fails); every<img>withwidth/heightdeclares the file's intrinsic aspect ratio (PNG IHDR, JPEG SOF, GIF header, SVGviewBox) and is not drawn larger than a raster's pixels; every<img>SVG has a rootviewBox; and every image file underassets/is referenced by some page or stylesheeturl(). Each rule has a fixture self-test. Zero dependencies.node --test scripts/page-meta.test.mjs— static gate over every page's<head>and the Markdown/llms.txtdocuments: each canonical page has exactly one<title>, a description,charset/viewport, acanonicalthat matches the path the page is served at,og:urlequal to it, the full Open Graph + Twitter card set withog:*/twitter:*pairs in agreement, no duplicated meta, nonoindex;og:imageis a committed file whose PNG header matches any declaredog:image:width/height; each redirect page isnoindexwith acanonicalequal to its refresh target, carriesviewportand a Content-Security-Policy withscript-src 'none'; and every relative Markdown link andhivecommons.devURL inREADME.md,runbooks/*.mdandllms.txtresolves to a committed file. Each rule has a fixture self-test. Zero dependencies.node --test scripts/page-404.test.mjs— static gate over the custom error page, whichpage-meta.test.mjsdeliberately skips. GitHub Pages serves/404.htmlfor every unknown path at any depth, so the page must live at the repository root and nowhere else, keep exactly onenoindexrobots meta, carry nocanonical/og:*/twitter:*identity and no meta-refresh, use only root-absolute or externalhref/src(a relativestyle.cssbreaks under/some/deep/path), link back to/, resolve every root-absolute reference to a committed file, and keep body text thatscripts/check-links.sh'sSOFT_404_REmatches (so a misrouted URL serving the page with HTTP 200 is still flagged); no other page may match that regex. Each rule has a fixture self-test. Zero dependencies.node --test scripts/story-cards.test.mjs— static gate over every<article class="story-card">instories/index.html, which are hand-copied per PR: the avatarsrc, itsalt, the handle linkhrefand its text all name the same GitHub handle; the avatar keepsloading="lazy",referrerpolicy="no-referrer", width/height and theonerrorhide; exactly one<h3>; at least one<a class="inline-link">pointing at a GitHub pull/issue/commit; every link in the card ishttps://github.com/…(the page promises public GitHub evidence only); everyMonth D, YYYYis a real date; no handle has two cards. Each rule has a fixture self-test. Zero dependencies.node --test scripts/story-form-contract.test.mjs— contract gate between the Share-your-story dialog and the GitHub issue form it prefills inhivecommons/hive(itscontributor-story.ymlissue template), whichstory-dialog.test.mjscannot see: GitHub silently ignores an unknowntemplate=(blank-issue chooser, nocontributor-storylabel) and silently drops unknown field ids. The gate reads thetemplate=, title prefix and field idsbuildIssue()sends, the<select id="story-role">options, the fallback link and the promised label fromstories/index.html, fetches the template from the hive's default branch (raw.githubusercontent.com/…/HEAD/…, 10 s timeout) and requires: the promised label is one the template applies; the templatetitle:equals the page's prefix; every id sent is a template field; every templateinput/textareaand every required non-checkbox field is sent (GitHub never prefills checkboxes from the URL); theroledropdown options equal the page's options in order; the fallback link opens the same template. A transport failure or a non-404 HTTP status (429 rate limit, 5xx outage) skips only the live test (offlinenode --teststays green and araw.githubusercontent.comhiccup never reads as drift); a 404 on every template directory fails it.STORY_FORM_TEMPLATE=<path>checks a local copy with no network. Each rule has a fixture self-test. Zero dependencies.node --test scripts/page-csp.test.mjs— static gate over the<meta http-equiv="Content-Security-Policy">on every non-redirect page. GitHub Pages cannot send response headers, so the policy ships as a<meta>tag and inline scripts are allow-listed bysha256-hash: the test recomputes the hash of every inline<script>body andon*=handler and fails when the policy is missing one or carries a stale one (a drifted hash silently disables that script in every browser — after editing any inline script, runnode scripts/update-csp-hashes.mjsto regenerate the hashes in the page's CSP meta; the failure message says so). It also requires exactly one CSP meta and ascript-srcdirective, requiresdefault-src 'none', and rejects'unsafe-inline','unsafe-eval',*,http:,https:anddata:inscript-src. Inline event handlers require'unsafe-hashes'plus their hashes;'unsafe-hashes'without handlers is rejected. The meta must precede every<script>/<link>/<img>/<style>in<head>. Every external<img>origin must be allowed byimg-src(ordefault-srcwhenimg-srcis absent), either explicitly or viahttps:.base-urimust contain'none'or'self', andform-actionmust be present. Each rule has a fixture self-test. Zero dependencies.node --test scripts/update-csp-hashes.test.mjs— fixture self-test ofscripts/update-csp-hashes.mjs, the tool that regenerates thesha256-allow-list the CSP gate checks: a drifted hash is replaced and the gate then passes; non-hashscript-srctokens, the other directives, their order and the;separators are kept byte-for-byte;'unsafe-hashes'plus the handler hashes appear exactly when a page has inlineon*=handlers; redirect pages are skipped, while missing or duplicate policies andscript-srcdirectives are reported as errors, never invented or rewritten; token order is not enforced (a policy the gate accepts is never rewritten);--checkreports stale pages and exits 1 without writing. Explicit[page.html ...]arguments update only the selected files, leaving other stale pages untouched. Then pins the live tree: every committed content page is already up to date, so the tool andpage-csp.test.mjsagree on what correct means, and a failure names the regenerate command. Zero dependencies.node --test scripts/llms-txt.test.mjs— static gate keepingllms.txtin step withindex.html: the agent CLIs it names must equal the Agent CLIs chips, the inference engines/gateways it names must equal the engine and gateway chips (classifier chips excluded), and its infrastructure thanks must equal the infra-thanks logo labels — in both directions, so adding, renaming or dropping a chip without updatingllms.txtfails CI. Also requires the "See all integrations" docs URL to appear inllms.txt. Each rule has a fixture self-test. Zero dependencies.node --test scripts/jsonld-contract.test.mjs— static gate keeping the schema.org JSON-LD onindex.htmlin step with the page it describes (page-scripts.test.mjsonly checks that it parses): exactly oneapplication/ld+jsongraph with@contexthttps://schema.org; theOrganization'ssponsorandfunderlists are identical and name exactly the infra-thanks logos (label minus its parenthetical, e.g.Akamai (Linode)→Akamai) with eachurlequal to that logo chip's link;Organization.urlis the site root andlogoan absolute site URL; the HiveSoftwareApplication.keywordsnames every integration chip (classifier chips included), every keyword is a chip or visible page text (case-insensitive, plural allowed), none repeats, and every name anincluding …list infeatureListenumerates is a chip; everyurl,codeRepository,sameAsand sponsorurlishttpsand linked by anhrefsomewhere on the page (trailing-slash differences ignored); and the Projects carousel and theSoftwareApplicationnodes describe the same set of projects — every card (its<h3>minus thetagbadge) has a node of that name whosecodeRepositoryis the card's GitHub link, and every node has a card. Each rule has a fixture self-test. Zero dependencies.node --test scripts/availability-probes.test.mjs— static gate over the URL list the scheduledavailabilityjob inlinks.ymlprobes: every probed URL ishttps, underhivecommons.dev, carries its trailing slash when it is a directory page (GitHub Pages answers301for/docs, whichcurlwithout-Lreports as a failure) and resolves to a committed page; every canonical page insitemap.xmland/404.htmlis probed; at least one redirect shortcut is probed; no duplicates. Fixing a failure means editing the workflow's probe list. Each rule has a fixture self-test. Zero dependencies.node --test scripts/site-origin.test.mjs— static gate tying the site origin toCNAME, the one file GitHub Pages actually reads for the host:CNAMEmust be a single bare lowercase DNS host (no scheme, path, port, trailing dot, second line or CRLF); everySITE_ORIGINconstant the other gates hardcode, everysitemap.xml<loc>, therobots.txtSitemap:URL and every canonical page's<link rel="canonical">/og:urlmust be athttps://<CNAME>; and no committed page, stylesheet or text asset may link thehivecommons.github.iofallback host (which bypasses the custom domain). Each rule has a fixture self-test. Zero dependencies.node --test scripts/security-txt.test.mjs— static gate over.well-known/security.txt(RFC 9116), whoseExpiresfield nothing else reads (links.ymlonly probes that the URL answers 200): exactly oneExpiresthat is a valid RFC 3339 timestamp at least 30 days away and no more than ~1 year ahead, at least oneContact(anhttps://,mailto:ortel:URI), aCanonicalequal to the published URL,https://for everyCanonical/Policy/Acknowledgments/Hiringvalue, and at most onePreferred-Languages— so the contact is rotated before scanners treat it as abandoned and the file never ships in a shape they reject. Each rule has a fixture self-test. Zero dependencies.node --test scripts/pages-build.test.mjs— static gate over what GitHub Pages' Jekyll build actually deploys, which no other gate sees:_config.ymlmust parse, carry onlyinclude:(the README's "nothing else is configured"), and name only directories that exist; every root dot-directory holding site files (.well-known) must be ininclude:; every URL the site publishes (eachsitemap.xml<loc>, eachlinks.ymlavailability-probe URL, thesecurity.txtCanonical) must map to a file Jekyll will publish — not under anexclude:or default-exclude path and not behind an_/./#/~segment thatinclude:does not list; and no published file may open with a---front matter block (Jekyll would run Liquid over it). Each rule has a fixture self-test. Zero dependencies.node --test scripts/redirect-targets.test.mjs— static gate over what a browser makes of each shortcut-redirect page.make-redirects.shinterpolates everyMAPurl and label into HTML through itshtml_escape(), so the gate parses theMAP, rejects any url or label that carries",<or>or a label that decodes as a character reference (defense in depth: nothing a shortcut target needs), and then decodes each committed page's refreshurl=, canonicalhrefand<a href>under HTML attribute rules (legacy no-semicolon entities such as¶,®,©included) and its<title>/link text under text rules, requiring each to resolve to exactly theMAPurl/label; any other ambiguous ampersand on the page is reported (&,<,>and"— whathtml_escape()emits — are the accepted escapes). It also runs the real generator body against a hostile fixtureMAP(raw&separators, legacy/numeric references,",<,>in url and label) in a scratch directory and requires every emitted page to decode back to exactly its entry, sohtml_escape()is exercised directly rather than only through the characters the committedMAPhappens to contain. Each rule has a fixture self-test. Zero dependencies.scripts/check-redirects.sh— redirect-page drift gate described above.scripts/check-redirects.test.sh— fixture-based self-test of the drift gate; runs offline against throwaway sites with a two-entry generator.node --test scripts/ci-wiring.test.mjs— static gate keepinglinks.yml,scripts/and this list in step: everyscripts/*.shgate and*.test.shself-test is run by arun:step in a job that runs on every PR/push (they are listed by hand, unlike thenode --test scripts/*.test.mjsglob, which must itself be present and run on every PR/push). A job-levelif:counts as a gate only when it can skip apushorpull_requestevent:scripts/actions-if.mjsevaluates the expression for both, so thegithub.event_name != 'schedule' || …trim that lets the 6-hourly cron runavailabilityalone is accepted, while an unrecognised expression is not; everyscripts/*.shgate has a sibling self-test; no.mjsthat importsnode:testsits outside the glob's*.test.mjsname; the workflow runs onpull_requestand on push tomain; every shell script is executable; and this## Checkssection names every gate and self-test. Each rule has a fixture self-test. Zero dependencies.node --test scripts/actions-if.test.mjs— direct unit test ofscripts/actions-if.mjs, the job-levelif:evaluatorci-wiring.test.mjsandworkflow-docs.test.mjsshare:==/!=ongithub.event_nameandgithub.event.schedule,!,&&over||precedence, parentheses, escaped string literals, and that every unsupported context, function, operator or malformed expression returnsundefinedso the caller treats the job as gated rather than guessing. Zero dependencies.node --test scripts/html-pages.test.mjs— direct unit test ofscripts/html-pages.mjs, the page-discovery helper every static page gate imports to decide which pages it inspects:htmlFiles()recurses, sorts, keeps only.html, and skips exactly theSKIP_DIRStooling directories (at any depth, directories only);SKIP_DIRSnever names a content directory;isRedirectPage()matches the generator'shttp-equiv="refresh"in every quote/case/attribute-order spelling and nothing else;lineOf()is 1-based. Then pins the live tree:index.html,404.html,stories/index.htmland everymake-redirects.shMAP page are discovered, nothing under a skipped directory is, and the redirect pages are exactly the MAP paths — so an over-broadSKIP_DIRSor a broken walker cannot make the gates pass vacuously. Zero dependencies.node --test scripts/workflow-docs.test.mjs— static gate keeping the prose aboutlinks.ymlin step with it: in this README andrunbooks/*.md, the workflow name must match the workflow'sname:; every job the docs name must exist underjobs:; jobs the docs say run on every PR/push must have noif:, or one thatscripts/actions-if.mjsevaluates true for bothpushandpull_request, and the ones they call weekly-only or manual-only must carry anif:onschedule/workflow_dispatch(which the workflow must declare); every weekday andHH:MM UTCmust match thecron:expression; and the--external-warnflag must be what the workflow passes outside schedule/dispatch and whatscripts/check-links.shaccepts. Each rule has a fixture self-test. Zero dependencies.node --test scripts/automation-docs.test.mjs— static gate keeping the prose about every workflow under.github/workflows/in step with it. Wherever this README orrunbooks/*.mdintroduces a workflow as**<name>** … (.github/workflows/), the file must be committed and itsname:must equal the bold text, and the introducing paragraph's claims must match the triggers: "push tomain" needs apushtrigger coveringmain; "every pull request" needspull_request; "weekly" needs aschedulewith a plain weekly cron; a literal cron`M H * * D`must equal the workflow'scron:; "manual dispatch" needsworkflow_dispatch; back-ticked pull request activity types (closed, …) must be exactlypull_request.types; and "code scanning" needs agithub/codeql-action/upload-sarifstep. Every committed workflow must be introduced that way in this README, so new automation cannot land undocumented. Each rule has a fixture self-test. Zero dependencies.node --test scripts/workflow-hardening.test.mjs— static gate over the supply-chain hardening of every workflow under.github/workflows/: everyuses:is pinned to a 40-hex commit SHA with a trailing# <version>comment (./local actions exempt,docker://images by@sha256:digest), including the reusable-workflowuses:; the workflow declares a top-levelpermissions:block and nothing grantswrite-all; everyactions/checkoutstep setspersist-credentials: false; and every job withruns-onsetstimeout-minutes. OpenSSF Scorecard reads the same files but runs only onmainand a lower score fails nothing, so a tag re-pin or a droppedpersist-credentials: falsewould otherwise merge green. Each rule has a fixture self-test. Zero dependencies.node --test scripts/dependabot-config.test.mjs— static gate over.github/dependabot.yml, the only thing that moves the SHA pinsworkflow-hardening.test.mjsrequires: the file exists and isversion: 2; everyupdates:entry names apackage-ecosystemDependabot knows, adirectory:and aschedule.intervalit accepts; every ecosystem the tree contains (workflows under.github/workflows/→github-actions, a rootpackage.json→npm, …) is covered by an entry at/, no entry names an ecosystem the tree lacks, and no two entries cover the same ecosystem and directory. Without it a deleted or mistyped config leaves every pin valid but silently rotting — Scorecard's Dependency-Update-Tool check reads the same file but only onmain, and a lower score fails nothing. Each rule has a fixture self-test. Zero dependencies.node --test scripts/owners-file.test.mjs— static gate over everyOWNERSfile in the tree andOWNERS_ALIASESwhen present, the files prow'sapproveplugin and tide read to decide who may/approveand whether a PR merges: the rootOWNERSexists; every file is flat YAML ofkey:blocks of- itemlines indented with spaces; only keys prow knows (approvers,reviewers,emeritus_approvers,emeritus_reviewers,labels,optionswith its boolean flags); the root file names at least one approver; every entry is a syntactically valid GitHub login (no@, no email) or an alias declared inOWNERS_ALIASES; and no list repeats an entry or names someone as both active and emeritus for the same role. Prow treats every one of those mistakes as "no approvers here", so without the gate a typo would land green and surface only as PRs that never merge. Login syntax only — the gate has no network access. Each rule has a fixture self-test. Zero dependencies.node --test scripts/doc-paths.test.mjs— static gate over the repository paths named in backticks byREADME.mdandrunbooks/*.md: a word in an inline code span is a path claim when it starts with./, or when its first segment is a committed top-level directory or it is root-absolute and it ends in a file extension or/(so/404.html,/stories/andscripts/*.share checked, while URL-path examples such as/some/deep/path, action references and bare basenames are not); every claim must resolve to a committed file or directory, a*in the last segment matching at least one.page-meta.test.mjsresolves Markdown links only andci-wiring.test.mjschecks that every existing gate is described here, not that every described path still exists, so a renamed script or dropped workflow would otherwise leave the prose pointing at the old name. Each rule has a fixture self-test. Zero dependencies.
Prerequisites: Python 3 (any recent version) for previewing, and Node.js (the node --test
runner) for the gates; no npm install is needed.
python3 -m http.server 8000 # then open http://localhost:8000/
node --test scripts/*.test.mjs # every Node gate (what CI runs)
scripts/check-links.test.sh # link-checker self-test (offline)
scripts/check-redirects.test.sh # redirect-gate self-test (offline)
scripts/check-redirects.sh # redirect-page drift gate
node scripts/update-csp-hashes.mjs # after editing an inline <script>: refresh its CSP hashRun these from the repository root before opening a PR. The previewed pages are served
from the checkout as-is, so root-absolute links (/stories/) resolve locally the same way
they do on the live site. Each gate is described under Checks.
The Link check workflow runs every Monday at 09:17 UTC, every 6 hours for
the availability probe alone, and can also be run manually from the Actions tab.
On those runs, its separate availability job
requests the live homepage, /stories/, a sample of the shortcut redirects and
/404.html over HTTPS and fails unless every one returns HTTP 200 (the list is
kept in step with the committed pages by scripts/availability-probes.test.mjs).
Connection/TLS errors and timeouts also fail the job. Requests
have bounded retries for transient failures. PR and push-to-main runs only check
the checkout; they do not probe the production site.
Review failed runs in Actions and configure GitHub Actions notifications for the
workflow if you operate the site. See the availability runbook
for a failing probe and the rollback runbook
for a bad deploy. This 6-hourly probe is a basic availability signal,
not continuous uptime monitoring or a check after every deployment. The 6-hourly
runs skip the heavier jobs; the full link check runs weekly. External link
failures remain warnings in the separate links job.
Traffic analytics and shortcut usage tracking are not configured. Adding them requires an operator to choose a backend/property and settle privacy and consent requirements first (see issue #73).
OpenSSF Scorecard (.github/workflows/scorecard.yml) — Runs OpenSSF security scorecard analysis on every push to main, weekly (cron 23 5 * * 1), and on manual dispatch from the Actions tab. Results are published to GitHub's code scanning dashboard.
Close linked issues (.github/workflows/close-linked-issues.yml) — Automatically closes issues linked to a closed pull request when a PR transitions to closed.
Incident response — When a bad deploy or incident impacts visitors of hivecommons.dev (broken pages, dead redirects, domain issues), use the postmortem template to document it blamelessly and fact-based. Coordinate recovery using the release-rollback runbook.
Hive Commons website contributors are expected to follow the CNCF Code of Conduct.