diff --git a/README.md b/README.md
index 9a4e427..cdfc18e 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,10 @@ board **at request time**, across four hosts.
| [`prs.bounded.tools`](https://prs.bounded.tools) | what is open and awaiting a check | `selectPrs` |
| [`desk.bounded.tools`](https://desk.bounded.tools) | all three at a glance — the front door | `selectOverview` |
+The front door also carries a fourth section, **Repo health** (#81): which public repos run
+the org's standard CI and whether it passes, read from the snapshot `bounded-systems/.github`
+publishes daily (`selectCi`). It has no host of its own yet, so it links to the snapshot.
+
One Worker, selected by hostname, because the selection rules **are** the
product: four Workers would be four deploys, four broker entries, and four
chances for "claimable" to come to mean four different things.
diff --git a/src/render.js b/src/render.js
index 0822f19..372ffa2 100644
--- a/src/render.js
+++ b/src/render.js
@@ -165,9 +165,11 @@ const row = ({ marker, markerLabel = "", title, url, repo, number, noun, suffix
? `${esc(c.subject)} ${esc(c.delta)}`
: esc(title)
}
- ${vh(" — ")}${esc(shortRepo(repo))} · ${vh(
- noun + " ",
- )}${esc(number)}${suffix ? ` · ${esc(suffix)}` : ""}
+ ${vh(" — ")}${esc(shortRepo(repo))}${
+ number == null
+ ? ""
+ : ` · ${vh(noun + " ")}${esc(number)}`
+ }${suffix ? ` · ${esc(suffix)}` : ""}
`;
};
@@ -663,20 +665,44 @@ const SECTION_COPY = {
markerLabel: "Status", noun: "issue" },
prs: { title: "PRs", label: "open", blurb: "changes awaiting a check",
markerLabel: "Pull request", noun: "pull request" },
+ ci: { title: "Repo health", label: "with findings",
+ blurb: "which public repos run the standard CI, and whether it passes — measured daily by the standard's own repo",
+ markerLabel: "Findings", noun: "repo" },
};
const EMPTY_COPY = {
issues: "Nothing claimable right now.",
claims: "Nothing is claimed right now.",
prs: "No open pull requests.",
+ ci: "Every public repo calls the standard CI, and every standard run is green.",
};
+/**
+ * The repo-health denominator, said out loud (desk#81). "42 with findings" on
+ * its own hides the fact that matters most — how many repos call the standard
+ * at all, and whether the ones that do are green — so the section carries the
+ * lane's own totals and this turns them into one sentence. Gaps are named as
+ * gaps: "could not be measured" is not "healthy", and the lane keeps them in a
+ * separate field for exactly this reason.
+ */
+function ciSummary(s) {
+ const t = s.totals;
+ if (!t || !t.caller || !t.standard_run) return "";
+ const rows = t.rows ?? "?";
+ const gaps = t.gaps ? ` ${esc(t.gaps)} could not be measured.` : "";
+ const selftest = s.standard ? ` The standard's own selftest is ${esc(s.standard)}.` : "";
+ return `
${esc(t.caller.present)} of ${esc(rows)} public repos call the standard: ${esc(t.standard_run.green)} green, ${esc(t.standard_run.red)} red. ${esc(t.caller.absent)} do not call it.${gaps}${selftest}
`;
+}
+
function overviewSection(s) {
const copy = SECTION_COPY[s.key] || { title: s.key, label: "", blurb: "", markerLabel: "", noun: "item" };
+ // A section with no host of its own links wherever its feed lives (repo
+ // health links to the snapshot itself until a `ci.` host exists).
+ const link = s.href || `https://${s.host}`;
// The heading carries the id the points at, so each section is a
// NAMED landmark rather than three anonymous regions a reader has to count.
const heading = `
-
+
${s.ok ? `${esc(s.count)} ${esc(copy.label)}` : "unreadable"}
`;
@@ -689,7 +715,7 @@ function overviewSection(s) {
${heading}
This section could not be read. It is not empty — the feed behind
-
${esc(s.host)} did not answer in a way this page can stand behind,
+
${esc(s.host)} did not answer in a way this page can stand behind,
so nothing is shown rather than a count that would be made up.
${esc(s.reason)}
@@ -726,12 +752,13 @@ function overviewSection(s) {
const more = !s.count
? ""
: s.count > s.items.length
- ? `Showing the first ${esc(s.items.length)} of ${esc(s.count)} — the rest are at ${esc(s.host)}.
`
- : `All of them, in full, at ${esc(s.host)}.
`;
+ ? `Showing the first ${esc(s.items.length)} of ${esc(s.count)} — the rest are at ${esc(s.host)}.
`
+ : `All of them, in full, at ${esc(s.host)}.
`;
return `
${heading}
${esc(copy.blurb)}
+ ${s.key === "ci" ? ciSummary(s) : ""}
${body}
${more}
`;
diff --git a/src/select.js b/src/select.js
index d459187..3a136ea 100644
--- a/src/select.js
+++ b/src/select.js
@@ -5,7 +5,8 @@
// issues.bounded.tools what is worth picking up select()
// claims.bounded.tools what someone is already on selectClaims()
// prs.bounded.tools what is open and awaiting a check selectPrs()
-// desk.bounded.tools all three at a glance selectOverview()
+// desk.bounded.tools all three at a glance, plus selectOverview()
+// repo health from the standard CI selectCi()
//
// THE RANK IS THE BOARD'S. Nothing here scores. `Score` is carried through
// unchanged and only sorted on; a ranking computed here would be a different
@@ -246,8 +247,90 @@ export function selectPrs(feed) {
};
}
+// ── repo health (desk#81) ────────────────────────────────────────────────────
+
+/**
+ * Where the repo-standard conformance snapshot is published — `.github`#381's
+ * lane, daily, as main plus one API commit on a branch of the repo that owns
+ * the standard. Until a host of its own exists, the section links here.
+ */
+export const CI_SNAPSHOT_URL =
+ "https://raw.githubusercontent.com/bounded-systems/.github/repo-standard-conformance/repo-standard-conformance.json";
+export const CI_SECTION_HOST = "github.com/bounded-systems/.github";
+
+/**
+ * The finding codes the conformance lane emits, as sentences. A code this file
+ * does not know passes through AS WRITTEN rather than being dropped: the lane
+ * may grow a finding before this page learns its name, and an unnamed finding
+ * is still a finding.
+ */
+export const FINDING_COPY = {
+ "caller-absent": "does not call the standard CI",
+ "pin-not-sha": "calls the standard at a ref that is not a commit SHA",
+ "pull-request-missing": "the caller has no pull_request trigger",
+ "pull-request-filtered": "the caller's pull_request trigger is path-filtered, so it does not report on every PR",
+ "pull-request-no-synchronize": "the caller's pull_request trigger does not re-run on a push",
+ "test-lane-absent": "carries a toolchain but no test lane — its tests, if any, gate nothing",
+ "standard-run-red": "the latest standard run on its default branch is red",
+};
+
/**
- * Compose the three selections into the front door — desk.bounded.tools.
+ * Reduce the conformance snapshot to the repos with findings — the fourth
+ * section of desk.bounded.tools.
+ *
+ * NOTHING IS RE-COUNTED. `totals` are the lane's own and are carried through
+ * as published; this page sorts and truncates. A FINDING is the repo's (no
+ * caller, an unpinned ref, a red run); a GAP is the lane's (a listing it could
+ * not read). The lane keeps them in separate fields and never sums them, and
+ * neither does this — `count` is repos with findings, and the gaps ride along
+ * in `totals` for the summary line to say out loud.
+ *
+ * Worst first: most findings, then name — the same order the lane publishes.
+ */
+export function selectCi(feed) {
+ requireBoardFeed(
+ feed,
+ "repo-standard-conformance",
+ "Only the conformance snapshot may be rendered as repo health — any other feed is the wrong page's data.",
+ );
+
+ const repos = Array.isArray(feed.repos) ? feed.repos : [];
+ const t = feed.totals && typeof feed.totals === "object" ? feed.totals : {};
+ const flagged = repos.filter((r) => Array.isArray(r.findings) && r.findings.length > 0);
+ const sorted = [...flagged].sort(
+ (a, b) => b.findings.length - a.findings.length || String(a.repo).localeCompare(String(b.repo)),
+ );
+
+ return {
+ generated_at: feed.generated_at,
+ href: CI_SNAPSHOT_URL,
+ count: sorted.length,
+ totals: {
+ rows: t.rows ?? null,
+ caller: t.caller ?? null,
+ standard_run: t.standard_run ?? null,
+ test_lane: t.test_lane ?? null,
+ findings: t.findings ?? null,
+ gaps: t.gaps ?? null,
+ },
+ // The one "is the org CI good" signal that exists today: the standard's
+ // own selftest on main (`.github`#382 is what it still lacks).
+ standard: feed.standard?.selftest?.state ?? null,
+ items: sorted.map((r) => ({
+ repo: r.repo,
+ url: `https://github.com/${r.repo}`,
+ findings: r.findings,
+ // hasOwn, not a bare lookup: a code named like an Object.prototype member
+ // ("constructor", "toString") must read as the text it is, not resolve to
+ // a function whose source becomes the sentence.
+ summary: r.findings.map((f) => (Object.hasOwn(FINDING_COPY, f) ? FINDING_COPY[f] : String(f))).join("; "),
+ standard_run: r.standard_run?.state ?? null,
+ })),
+ };
+}
+
+/**
+ * Compose the four selections into the front door — desk.bounded.tools.
*
* TAKES OUTCOMES, NOT FEEDS, and that is the point: each section is fetched and
* selected independently, so this function is where "one of the three could not
@@ -266,7 +349,7 @@ export function selectPrs(feed) {
* make one page's "12 claimed" mean the same as another's is for both to be the
* same expression.
*/
-export function selectOverview({ issues, claims, prs }, head = OVERVIEW_HEAD) {
+export function selectOverview({ issues, claims, prs, ci }, head = OVERVIEW_HEAD) {
const section = (key, host, outcome, shape) =>
outcome?.ok
? { key, host, ok: true, ...shape(outcome.value), generated_at: outcome.value.generated_at }
@@ -296,6 +379,18 @@ export function selectOverview({ issues, claims, prs }, head = OVERVIEW_HEAD) {
repo: i.repo, number: i.number, title: i.title, url: i.url, note: `#${i.number}`,
})),
})),
+ // Repo health (desk#81). No host of its own yet, so `href` points at the
+ // snapshot; rows are repos, not issues, so they carry no number.
+ section("ci", CI_SECTION_HOST, ci, (d) => ({
+ count: d.count,
+ shown: d.items.length,
+ href: d.href,
+ totals: d.totals,
+ standard: d.standard,
+ items: d.items.slice(0, head).map((i) => ({
+ repo: i.repo, number: null, title: i.summary, url: i.url, note: String(i.findings.length),
+ })),
+ })),
];
// The OLDEST readable stamp, not the newest. The page shows three feeds side
diff --git a/src/worker.js b/src/worker.js
index 98a04a7..994facc 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -34,6 +34,7 @@ import {
select,
selectClaims,
selectPrs,
+ selectCi,
selectOverview,
DEFAULT_LIMIT,
FeedError,
@@ -1335,9 +1336,12 @@ export default {
// No destination is baked in: where the filtered feeds are published is a
// maintainer decision (see site#241), so they arrive as configuration and
// an absent one is reported rather than guessed at.
- const [board, prsFeed] = await Promise.all([
+ const [board, prsFeed, ciFeed] = await Promise.all([
readFeed(env.FEED_URL, "FEED_URL"),
readFeed(env.PRS_FEED_URL, "PRS_FEED_URL"),
+ // Repo health (desk#81): the conformance snapshot the standard's own
+ // repo publishes. Fails closed per section like the other two.
+ readFeed(env.CI_FEED_URL, "CI_FEED_URL"),
]);
// Both issue-side sections read the SAME feed — one origin read, and the
// two pages can never disagree about which snapshot they are describing.
@@ -1345,6 +1349,7 @@ export default {
issues: selected(board, (f) => select(f, limit)),
claims: selected(board, selectClaims),
prs: selected(prsFeed, selectPrs),
+ ci: selected(ciFeed, selectCi),
});
const status = overview.ok ? 200 : 502;
return wantsJson
diff --git a/test/render.test.mjs b/test/render.test.mjs
index b61f2fb..b3b4657 100644
--- a/test/render.test.mjs
+++ b/test/render.test.mjs
@@ -308,12 +308,27 @@ const section = (key, host, o = {}) => ({
items: [{ repo: "bounded-systems/prx", number: 434, title: `${key} row`, url: "https://e/1", note: "n" }],
...o,
});
+// Repo health (desk#81): rows are repos, not issues — no number — and the
+// section carries the lane's totals for the summary sentence.
+const ciSection = (o = {}) => section("ci", "github.com/bounded-systems/.github", {
+ href: "https://raw.example/ci.json",
+ totals: {
+ rows: 90, caller: { present: 49, absent: 40, unreadable: 1 },
+ standard_run: { green: 49, red: 0, other: 0, none: 0, unreadable: 0 }, findings: 42, gaps: 3,
+ },
+ standard: "green",
+ count: 42, shown: 1,
+ items: [{ repo: "bounded-systems/bare", number: null, title: "does not call the standard CI",
+ url: "https://github.com/bounded-systems/bare", note: "1" }],
+ ...o,
+});
const overview = (o = {}) => ({
ok: true, generated_at: "2026-08-25T12:00:00Z", head: 5,
sections: [
section("issues", "issues.bounded.tools"),
section("claims", "claims.bounded.tools"),
section("prs", "prs.bounded.tools"),
+ ciSection(),
],
...o,
});
@@ -692,3 +707,27 @@ test("no motion ships without a reduced-motion guard", async () => {
}
assert.equal(declared, guarded, `${declared} motion declarations, ${guarded} inside a reduced-motion guard`);
});
+
+// ── repo health on the overview (desk#81) ────────────────────────────────────
+
+test("the repo-health section links to its snapshot, says its denominator, and rows carry no number", () => {
+ const html = renderOverview(overview(), AT, 60);
+ assert.match(html, /Repo health/);
+ assert.ok(html.includes('href="https://raw.example/ci.json"'), "links to the href, not a host");
+ assert.match(html, /49 of 90 public repos call the standard: 49 green, 0 red\. 40 do not call it\. 3 could not be measured\. The standard's own selftest is green\.|49 of 90 public repos call the standard: 49 green, 0 red\. 40 do not call it\. 3 could not be measured\. The standard's own selftest is green\./);
+ assert.match(html, /does not call the standard CI/);
+ assert.match(html, /42 with findings/);
+ assert.doesNotMatch(html, /repo null/);
+ assert.doesNotMatch(html, /issue null/);
+});
+
+test("a repo-health section with nothing flagged says so, and only then", () => {
+ const html = renderOverview(overview({ sections: [ciSection({ count: 0, items: [] })] }), AT, 60);
+ assert.match(html, /every standard run is green/);
+ const failed = renderOverview(overview({
+ ok: false,
+ sections: [{ key: "ci", host: "github.com/bounded-systems/.github", ok: false, reason: "feed responded 504", count: null, items: [] }],
+ }), AT, 60);
+ assert.doesNotMatch(failed, /every standard run is green/);
+ assert.match(failed, /feed responded 504/);
+});
diff --git a/test/select.test.mjs b/test/select.test.mjs
index f5117b4..b119f73 100644
--- a/test/select.test.mjs
+++ b/test/select.test.mjs
@@ -1,8 +1,8 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import {
- select, selectPrs, selectClaims, selectOverview,
- FeedError, DEFAULT_LIMIT, OVERVIEW_HEAD,
+ select, selectPrs, selectClaims, selectOverview, selectCi,
+ FeedError, DEFAULT_LIMIT, OVERVIEW_HEAD, CI_SNAPSHOT_URL, FINDING_COPY,
} from "../src/select.js";
const item = (o = {}) => ({
@@ -256,20 +256,50 @@ const boardFeed = feed([
item({ number: 2, fields: { Status: "Todo", Score: 9 } }),
claimed({ number: 3, fields: { Status: "In Progress", Score: 1 } }),
]);
+// A conformance snapshot the way `.github`'s lane publishes it (desk#81):
+// totals are the lane's, rows carry findings (the repo's) and gaps (the lane's).
+const ciRepo = (o = {}) => ({
+ repo: "bounded-systems/x", findings: [], gaps: [],
+ caller: { state: "present" }, standard_run: { state: "green" }, extra: [],
+ ...o,
+});
+const ciFeed = (repos = [
+ ciRepo({ repo: "bounded-systems/bare", findings: ["caller-absent"], caller: { state: "absent" }, standard_run: null }),
+ ciRepo({ repo: "bounded-systems/clean" }),
+], o = {}) => ({
+ feed: "repo-standard-conformance", generated_at: "2026-09-04T20:38:14Z",
+ totals: {
+ rows: 2, caller: { present: 1, absent: 1, unreadable: 0 },
+ standard_run: { green: 1, red: 0, other: 0, none: 0, unreadable: 0 },
+ test_lane: { present: 1, absent: 1, "n/a": 0, unmeasured: 0 }, findings: 1, gaps: 0,
+ },
+ standard: { selftest: { state: "green" } },
+ repos, ...o,
+});
const outcomes = (o = {}) => ({
issues: ok(select(boardFeed)),
claims: ok(selectClaims(boardFeed)),
prs: ok(selectPrs(prFeed([prItem({ number: 4 })]))),
+ ci: ok(selectCi(ciFeed())),
...o,
});
-test("the overview carries all three sections, in reading order", () => {
+test("the overview carries all four sections, in reading order", () => {
const r = selectOverview(outcomes());
- assert.deepEqual(r.sections.map((s) => s.key), ["issues", "claims", "prs"]);
+ assert.deepEqual(r.sections.map((s) => s.key), ["issues", "claims", "prs", "ci"]);
assert.deepEqual(r.sections.map((s) => s.host), [
- "issues.bounded.tools", "claims.bounded.tools", "prs.bounded.tools",
+ "issues.bounded.tools", "claims.bounded.tools", "prs.bounded.tools", "github.com/bounded-systems/.github",
]);
assert.equal(r.ok, true);
+ // Repo health has no host of its own yet, so it says where its feed lives.
+ assert.equal(r.sections[3].href, CI_SNAPSHOT_URL);
+});
+
+test("a missing repo-health outcome fails the overview closed, like any other section", () => {
+ const r = selectOverview(outcomes({ ci: undefined }));
+ const ci = r.sections.find((s) => s.key === "ci");
+ assert.equal(ci.ok, false);
+ assert.equal(r.ok, false);
});
// The whole point of composing rather than re-counting: the overview's number
@@ -280,6 +310,7 @@ test("every count comes from the selector that owns it", () => {
assert.equal(by.issues.count, select(boardFeed).items.length);
assert.equal(by.claims.count, selectClaims(boardFeed).count);
assert.equal(by.prs.count, 1);
+ assert.equal(by.ci.count, selectCi(ciFeed()).count);
});
test("a section that could not be read keeps its slot and its reason", () => {
@@ -301,12 +332,13 @@ test("the overview's age is the OLDEST readable stamp, not the newest", () => {
issues: ok(select(feed([], { generated_at: "2026-08-25T10:00:00Z" }))),
claims: ok(selectClaims(feed([], { generated_at: "2026-08-25T10:00:00Z" }))),
prs: ok(selectPrs(prFeed([], { generated_at: "2026-08-27T10:00:00Z" }))),
+ ci: ok(selectCi(ciFeed([], { generated_at: "2026-08-26T10:00:00Z" }))),
});
assert.equal(r.generated_at, "2026-08-25T10:00:00Z");
});
test("an overview with nothing readable states no age rather than inventing one", () => {
- const r = selectOverview({ issues: bad("x"), claims: bad("x"), prs: bad("x") });
+ const r = selectOverview({ issues: bad("x"), claims: bad("x"), prs: bad("x"), ci: bad("x") });
assert.equal(r.generated_at, null);
assert.equal(r.ok, false);
});
@@ -316,9 +348,69 @@ test("each section shows only its head, and says how many it counted", () => {
item({ number: n, fields: { Status: "Todo", Score: n } })));
const r = selectOverview({
issues: ok(select(many)), claims: ok(selectClaims(many)), prs: ok(selectPrs(prFeed([]))),
+ ci: ok(selectCi(ciFeed())),
});
const issues = r.sections.find((s) => s.key === "issues");
assert.equal(issues.items.length, OVERVIEW_HEAD);
assert.equal(issues.count, 12);
assert.equal(r.head, OVERVIEW_HEAD);
});
+
+// ── selectCi — repo health (desk#81) ─────────────────────────────────────────
+
+test("selectCi refuses any feed that is not repo-standard-conformance", () => {
+ assert.throws(() => selectCi(feed([])), FeedError);
+ assert.throws(() => selectCi(prFeed([])), FeedError);
+ assert.throws(() => selectCi(ciFeed([], { feed: "front-desk-public" })), /expected the 'repo-standard-conformance' feed/);
+});
+
+test("selectCi refuses a snapshot it cannot date", () => {
+ assert.throws(() => selectCi(ciFeed([], { generated_at: "yesterday" })), FeedError);
+});
+
+test("selectCi lists only repos with findings, worst first, as sentences", () => {
+ const r = selectCi(ciFeed([
+ ciRepo({ repo: "bounded-systems/clean" }),
+ ciRepo({ repo: "bounded-systems/b", findings: ["caller-absent"] }),
+ ciRepo({ repo: "bounded-systems/a", findings: ["pin-not-sha", "pull-request-filtered"] }),
+ ]));
+ assert.equal(r.count, 2);
+ assert.deepEqual(r.items.map((i) => i.repo), ["bounded-systems/a", "bounded-systems/b"]);
+ assert.equal(r.items[1].summary, FINDING_COPY["caller-absent"]);
+ assert.match(r.items[0].summary, /not a commit SHA; .*path-filtered/);
+ assert.equal(r.items[0].url, "https://github.com/bounded-systems/a");
+ assert.equal(r.href, CI_SNAPSHOT_URL);
+});
+
+test("selectCi carries the lane's totals through, and never re-counts them", () => {
+ // The totals say 5 findings; the rows carry 1. The page shows the lane's
+ // number as the lane's number and its own count as its own — two facts,
+ // not one reconciled by this file.
+ const r = selectCi(ciFeed(undefined, { totals: { ...ciFeed().totals, findings: 5, gaps: 3 } }));
+ assert.equal(r.totals.findings, 5);
+ assert.equal(r.totals.gaps, 3);
+ assert.equal(r.count, 1);
+ assert.deepEqual(r.totals.caller, { present: 1, absent: 1, unreadable: 0 });
+ assert.equal(r.standard, "green");
+});
+
+test("a finding code this page does not know passes through as written", () => {
+ const r = selectCi(ciFeed([ciRepo({ repo: "bounded-systems/n", findings: ["something-new"] })]));
+ assert.equal(r.items[0].summary, "something-new");
+ // …including one that happens to name an Object.prototype member.
+ const p = selectCi(ciFeed([ciRepo({ repo: "bounded-systems/p", findings: ["constructor", "toString"] })]));
+ assert.equal(p.items[0].summary, "constructor; toString");
+});
+
+test("a snapshot with no findings is a real answer, not an error", () => {
+ const r = selectCi(ciFeed([ciRepo({ repo: "bounded-systems/clean" })]));
+ assert.equal(r.count, 0);
+ assert.deepEqual(r.items, []);
+});
+
+test("a snapshot predating totals or repos degrades to empty rather than throwing", () => {
+ const r = selectCi({ feed: "repo-standard-conformance", generated_at: "2026-09-04T20:38:14Z" });
+ assert.equal(r.count, 0);
+ assert.equal(r.totals.rows, null);
+ assert.equal(r.standard, null);
+});
diff --git a/test/worker.test.mjs b/test/worker.test.mjs
index ade31b8..b3a0b2d 100644
--- a/test/worker.test.mjs
+++ b/test/worker.test.mjs
@@ -27,9 +27,27 @@ const PRS = {
items: [{ repo: "bounded-systems/prx", number: 7, title: "a change", url: "https://e/7", labels: [], claimed: false }],
};
+// The repo-standard conformance snapshot (desk#81), the way `.github`'s lane
+// publishes it: one repo with a finding, one clean.
+const CI = {
+ feed: "repo-standard-conformance",
+ generated_at: new Date().toISOString(),
+ totals: {
+ rows: 2, caller: { present: 1, absent: 1, unreadable: 0 },
+ standard_run: { green: 1, red: 0, other: 0, none: 0, unreadable: 0 },
+ test_lane: { present: 1, absent: 1, "n/a": 0, unmeasured: 0 }, findings: 1, gaps: 0,
+ },
+ standard: { selftest: { state: "green" } },
+ repos: [
+ { repo: "bounded-systems/bare", findings: ["caller-absent"], gaps: [], caller: { state: "absent" }, standard_run: null, extra: [] },
+ { repo: "bounded-systems/prx", findings: [], gaps: [], caller: { state: "present" }, standard_run: { state: "green" }, extra: [] },
+ ],
+};
+
const ENV = {
FEED_URL: "https://feed.example/board.json",
PRS_FEED_URL: "https://feed.example/prs.json",
+ CI_FEED_URL: "https://feed.example/ci.json",
DESK_LIMIT: "25",
};
@@ -37,9 +55,9 @@ let realFetch;
/** Serve each configured feed, or fail the one the test names. */
function stubFeeds({ fail = null, status = 500 } = {}) {
globalThis.fetch = async (url) => {
- const which = url === ENV.FEED_URL ? "board" : "prs";
+ const which = url === ENV.FEED_URL ? "board" : url === ENV.CI_FEED_URL ? "ci" : "prs";
if (fail === which) return new Response("nope", { status, statusText: "Server Error" });
- return new Response(JSON.stringify(which === "board" ? BOARD : PRS), {
+ return new Response(JSON.stringify(which === "board" ? BOARD : which === "ci" ? CI : PRS), {
status: 200, headers: { "content-type": "application/json" },
});
};
@@ -141,7 +159,7 @@ test("/board.json serves that host's selection", async () => {
const claims = await (await get("claims.bounded.tools", "/board.json")).json();
assert.deepEqual(claims.items.map((i) => i.number), [2]);
const overview = await (await get("desk.bounded.tools", "/board.json")).json();
- assert.deepEqual(overview.sections.map((s) => s.key), ["issues", "claims", "prs"]);
+ assert.deepEqual(overview.sections.map((s) => s.key), ["issues", "claims", "prs", "ci"]);
});
test("/healthz does not touch the feed, so it answers when the feed does not", async () => {
@@ -190,6 +208,46 @@ test("the overview renders what it could read and 502s on what it could not", as
assert.doesNotMatch(html, /No open pull requests/);
});
+// ── repo health on the overview (desk#81) ────────────────────────────────────
+
+test("the overview's repo-health section reads CI_FEED_URL and names the repos with findings", async () => {
+ const res = await get("desk.bounded.tools");
+ assert.equal(res.status, 200);
+ const html = await res.text();
+ assert.match(html, /Repo health/);
+ assert.match(html, /1 of 2 public repos call the standard: 1 green, 0 red\. 1 do not call it\./);
+ assert.match(html, /does not call the standard CI/);
+ // The attribute, not the bare URL: this asserts the LINK TARGET, and a bare
+ // URL substring is what CodeQL reads as sanitization (js/incomplete-url-substring-sanitization).
+ assert.ok(html.includes('href="https://github.com/bounded-systems/bare"'), "the row links to the repo");
+ assert.doesNotMatch(html, /repo null/);
+});
+
+test("an unreadable conformance feed keeps its slot and 502s the overview", async () => {
+ stubFeeds({ fail: "ci" });
+ const res = await get("desk.bounded.tools");
+ assert.equal(res.status, 502);
+ const html = await res.text();
+ assert.match(html, /pick me up/); // the board sections survived
+ assert.match(html, /This section could not be read/);
+ assert.doesNotMatch(html, /every standard run is green/); // never the empty sentence
+});
+
+test("the board feed served as CI_FEED_URL is refused, not rendered as repo health", async () => {
+ const inner = globalThis.fetch;
+ globalThis.fetch = async (url, init) =>
+ url === ENV.CI_FEED_URL ? new Response(JSON.stringify(BOARD), { status: 200 }) : inner(url, init);
+ const res = await get("desk.bounded.tools");
+ assert.equal(res.status, 502);
+ assert.match(await res.text(), /expected the 'repo-standard-conformance' feed/);
+});
+
+test("a missing CI_FEED_URL is the Worker's own fault, and says which var", async () => {
+ const res = await get("desk.bounded.tools", "/", { ...ENV, CI_FEED_URL: "" });
+ assert.equal(res.status, 502);
+ assert.match(await res.text(), /CI_FEED_URL is not configured/);
+});
+
test("the overview reads the board feed once for both board-side sections", async () => {
const seen = [];
const inner = globalThis.fetch;
diff --git a/wrangler.jsonc b/wrangler.jsonc
index b727ff8..c3d4155 100644
--- a/wrangler.jsonc
+++ b/wrangler.jsonc
@@ -78,6 +78,12 @@
// (scripts/prs.sh) — the Worker refuses to render it anywhere but the prs
// host, and refuses any feed that does not name itself front-desk-prs-public.
"PRS_FEED_URL": "https://raw.githubusercontent.com/bounded-systems/front-desk-feed/feed/front-desk-prs.json",
+ // Repo health (desk#81): the repo-standard conformance snapshot, published
+ // daily by bounded-systems/.github (its #381 lane) as main plus one API
+ // commit on a branch of the repo that owns the standard. The Worker refuses
+ // to render it anywhere but the overview's repo-health section, and refuses
+ // any feed that does not name itself repo-standard-conformance.
+ "CI_FEED_URL": "https://raw.githubusercontent.com/bounded-systems/.github/repo-standard-conformance/repo-standard-conformance.json",
"DESK_LIMIT": "25"
// NOT HERE, and deliberately: SESSION_SECRET, the key desk login's session