From 82d4e28d62702a27d5eab99d8af6b8551f4e98ee Mon Sep 17 00:00:00 2001 From: Arun Kumar Thiagarajan Date: Sat, 27 Jun 2026 23:22:08 +0530 Subject: [PATCH 1/4] Serve a branded homepage fallback when the here.now shell is unavailable The homepage is rendered by fetching the here.now shell, injecting the catalog, and serving it. When that fetch throws or the shell returns a 5xx, the Worker passed the upstream failure straight through (loop-routes.js), so visitors hit an unbranded, catalog-less dead end even though the Worker already holds every loop record in its own database. Render a minimal branded fallback homepage from that catalog instead: it keeps the site chrome, lists every published loop with working links, and points agents at catalog.json and llms.txt. The real failure status is preserved (the upstream 5xx, or 502 when the shell is unreachable) so the outage stays honest and the page is marked noindex. Covered by two tests. --- loop-library/worker/src/loop-routes.js | 29 ++++++++- loop-library/worker/src/render-loops.js | 63 ++++++++++++++++++++ loop-library/worker/test/loop-routes.test.js | 47 +++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) diff --git a/loop-library/worker/src/loop-routes.js b/loop-library/worker/src/loop-routes.js index 8365d3f..2bf655c 100644 --- a/loop-library/worker/src/loop-routes.js +++ b/loop-library/worker/src/loop-routes.js @@ -9,6 +9,7 @@ import { renderAgentInstructions, renderCatalogMarkdown, renderFeed, + renderHomepageFallback, renderLoopPage, renderSitemap, } from "./render-loops.js"; @@ -159,7 +160,33 @@ export async function handleLoopRoute( conditional: false, range: false, }); - const originResponse = await dependencies.fetch(originRequest); + let originResponse; + try { + originResponse = await dependencies.fetch(originRequest); + } catch { + // The here.now shell is unreachable. Serve a branded fallback built from + // the catalog the Worker already holds so every loop stays reachable, + // rather than leaking the upstream failure. Report it honestly as 502. + return textResponse( + renderHomepageFallback(loops), + "text/html; charset=utf-8", + 502, + CACHE_HEADERS, + request.method, + ); + } + + if (originResponse.status >= 500) { + // Upstream shell error: serve the same fallback and preserve the real + // failure status instead of passing through the upstream error page. + return textResponse( + renderHomepageFallback(loops), + "text/html; charset=utf-8", + originResponse.status, + CACHE_HEADERS, + request.method, + ); + } if (!originResponse.ok) { return originResponse; diff --git a/loop-library/worker/src/render-loops.js b/loop-library/worker/src/render-loops.js index 9dcf477..8f45fd1 100644 --- a/loop-library/worker/src/render-loops.js +++ b/loop-library/worker/src/render-loops.js @@ -384,6 +384,69 @@ export function renderLoopPage(loop, loops) { `; } +export function renderHomepageFallback(loops) { + const items = loops + .map( + (loop) => + `
  • ${escapeHtml(loop.title)}

    ${escapeHtml(loop.summary)}

  • `, + ) + .join("\n"); + + return ` + + + + + + + + + + + + + + + ${escapeHtml(SITE.name)} — temporarily unavailable + + + + +
    +

    The Loop Library is briefly unavailable

    +

    The full homepage could not be loaded right now. Every published loop is still listed below, and the machine-readable catalog.json and agent instructions remain available.

    +

    Showing ${loops.length} loops.

    + +
    + + +`; +} + function shareActions(loop, url) { const text = `Try "${loop.title}" from the Loop Library: ${loop.summary}`; return `
    `; diff --git a/loop-library/worker/test/loop-routes.test.js b/loop-library/worker/test/loop-routes.test.js index 9df6e3c..6aaf479 100644 --- a/loop-library/worker/test/loop-routes.test.js +++ b/loop-library/worker/test/loop-routes.test.js @@ -472,6 +472,53 @@ test("renders the mounted homepage through a here.now proxy", async () => { assert.match(await response.text(), /The database publishing loop/); }); +test("serves a branded fallback homepage when the here.now shell errors", async () => { + const env = makeEnv(); + await handleRequest(adminRequest(exampleLoop()), env); + const response = await handleRequest( + new Request(`${SITE_ORIGIN}/loop-library/`), + env, + undefined, + { + async fetch() { + return new Response("upstream is down", { status: 503 }); + }, + }, + ); + const html = await response.text(); + + // Preserves the real failure status instead of masking it as 200. + assert.equal(response.status, 503); + assert.equal(response.headers.get("Cache-Control"), "no-store"); + // Branded fallback that still lists every loop, not the upstream error body. + assert.doesNotMatch(html, /upstream is down/); + assert.match(html, /briefly unavailable/); + assert.match(html, /The database publishing loop/); + assert.match(html, /loops\/database-publishing-loop\//); + assert.match(html, /catalog\.json/); + assert.match(html, / { + const env = makeEnv(); + await handleRequest(adminRequest(exampleLoop()), env); + const response = await handleRequest( + new Request(`${SITE_ORIGIN}/loop-library/`), + env, + undefined, + { + async fetch() { + throw new Error("connection refused"); + }, + }, + ); + const html = await response.text(); + + assert.equal(response.status, 502); + assert.match(html, /briefly unavailable/); + assert.match(html, /The database publishing loop/); +}); + test("normalizes legacy Forward Future domains on every public catalog surface", async () => { const env = makeEnv(); await handleRequest( From 23d7909ab90cf9f10a12ff32d05395e37f9c1295 Mon Sep 17 00:00:00 2001 From: Clay <“”william.c.hooten@gmail.com> Date: Mon, 29 Jun 2026 11:10:25 -0500 Subject: [PATCH 2/4] fix: harden loopy harness checks --- AGENTS.md | 5 +- README.md | 1 + loop-library/scripts/check.mjs | 18 ++ loop-library/worker/src/auth-votes.js | 1 + loop-library/worker/test/auth-votes.test.js | 18 ++ loop-library/worker/test/loop-routes.test.js | 174 ++++++++++++++++++- 6 files changed, 213 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7de8574..ba5a62b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -53,13 +53,14 @@ this repository layout. node loop-library/scripts/check.mjs npm --prefix loop-library/worker run check python3 -m json.tool loop-library/site/.herenow/data.json >/dev/null + python3 -m json.tool loop-library/site/.herenow/proxy.json >/dev/null python3 -m json.tool loop-library/scripts/seo-geo-query-benchmark.json >/dev/null git diff --check ``` - Do not publish a loop unless its public homepage row, detail page, - `catalog.json`, `catalog.md`, sitemap, and feed all read back from production - with the expected slug and modified date. + `catalog.json`, `catalog.md`, `catalog.txt`, `llms.txt`, sitemap, and feed + all read back from production with the expected slug and modified date. ## Protected forms diff --git a/README.md b/README.md index 3408b36..2483528 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,7 @@ node --check loop-library/site/script.js node loop-library/scripts/check.mjs npm --prefix loop-library/worker run check python3 -m json.tool loop-library/site/.herenow/data.json >/dev/null +python3 -m json.tool loop-library/site/.herenow/proxy.json >/dev/null python3 -m json.tool loop-library/scripts/seo-geo-query-benchmark.json >/dev/null git diff --check ``` diff --git a/loop-library/scripts/check.mjs b/loop-library/scripts/check.mjs index e35f621..bbd71ce 100644 --- a/loop-library/scripts/check.mjs +++ b/loop-library/scripts/check.mjs @@ -40,6 +40,7 @@ const [ legacySkillPublish, readme, agents, + ciWorkflow, ] = await Promise.all([ readFile(path.join(siteRoot, "index.html"), "utf8"), readFile(path.join(siteRoot, "learn", "index.html"), "utf8"), @@ -69,6 +70,7 @@ const [ readFile(path.join(legacySkillRoot, "references", "publish.md"), "utf8"), readFile(path.join(repoRoot, "README.md"), "utf8"), readFile(path.join(repoRoot, "AGENTS.md"), "utf8"), + readFile(path.join(repoRoot, ".github", "workflows", "ci.yml"), "utf8"), ]); const workerPackage = JSON.parse(workerPackageSource); @@ -371,5 +373,21 @@ assert(readme.includes("loops:export")); assert(readme.includes("loops:restore")); assert(agents.includes("Do not commit")); assert(agents.includes("Never publish the empty shell")); +assert( + agents.includes("`catalog.json`, `catalog.md`, `catalog.txt`, `llms.txt`, sitemap, and feed"), +); +for (const command of [ + "node --check loop-library/site/script.js", + "node loop-library/scripts/check.mjs", + "npm --prefix loop-library/worker run check", + "python3 -m json.tool loop-library/site/.herenow/data.json >/dev/null", + "python3 -m json.tool loop-library/site/.herenow/proxy.json >/dev/null", + "python3 -m json.tool loop-library/scripts/seo-geo-query-benchmark.json >/dev/null", + "git diff --check", +]) { + assert(readme.includes(command), `README.md missing validation command: ${command}`); + assert(agents.includes(command), `AGENTS.md missing validation command: ${command}`); + assert(ciWorkflow.includes(command), `.github/workflows/ci.yml missing validation command: ${command}`); +} console.log("Loop Library database-only checks passed."); diff --git a/loop-library/worker/src/auth-votes.js b/loop-library/worker/src/auth-votes.js index 5e30d08..f33f644 100644 --- a/loop-library/worker/src/auth-votes.js +++ b/loop-library/worker/src/auth-votes.js @@ -42,6 +42,7 @@ export async function handleAuthVoteRoute( if (body instanceof Response) return body; const viewer = await readSession(body.sessionToken, env); if (!viewer) return jsonResponse({ viewer: null, viewerVotes: {} }); + if (!env.VOTE_STORE) return unavailable("Voting is not configured."); const response = await voteStoreFetch( env, `/votes?voter=${encodeURIComponent(viewer.sub)}`, diff --git a/loop-library/worker/test/auth-votes.test.js b/loop-library/worker/test/auth-votes.test.js index df1ed45..b783b74 100644 --- a/loop-library/worker/test/auth-votes.test.js +++ b/loop-library/worker/test/auth-votes.test.js @@ -219,6 +219,24 @@ test("voting UI is fail-closed unless the launch flag is exactly true", async () assert.equal((await enabled.json()).uiEnabled, true); }); +test("session lookup fails closed when vote storage is unavailable", async () => { + const env = makeEnv(); + const sessionToken = await githubSession(env); + delete env.VOTE_STORE; + + const session = await handleAuthVoteRoute( + new Request(`${BASE}/auth/session`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ sessionToken }), + }), + env, + ); + + assert.equal(session.status, 503); + assert.equal((await session.json()).code, "not_configured"); +}); + test("vote writes reject anonymous, cross-site, malformed, and unpublished requests", async () => { const env = makeEnv(); const anonymous = await handleAuthVoteRoute( diff --git a/loop-library/worker/test/loop-routes.test.js b/loop-library/worker/test/loop-routes.test.js index 9df6e3c..a913fdd 100644 --- a/loop-library/worker/test/loop-routes.test.js +++ b/loop-library/worker/test/loop-routes.test.js @@ -16,8 +16,13 @@ class MemoryLoopCatalogNamespace { revisions = new Map(); restore = null; - constructor(active = true) { + constructor(active = true, options = {}) { this.active = active; + this.validateWrites = options.validateWrites === true; + + for (const loop of options.seedLoops || []) { + this.loops.set(loop.slug, { ...loop }); + } } idFromName(name) { @@ -39,6 +44,10 @@ class MemoryLoopCatalogNamespace { { status: 409 }, ); } + const catalogError = this.catalogIntegrityError([ + { loop: body.loop, status: body.status }, + ]); + if (catalogError) return catalogError; this.loops.set(body.loop.slug, { ...body.loop, status: body.status }); const revisions = this.revisions.get(body.loop.slug) || []; revisions.unshift({ @@ -76,6 +85,10 @@ class MemoryLoopCatalogNamespace { if (init.method === "POST" && url.pathname === "/import") { const body = JSON.parse(init.body); + const catalogError = this.catalogIntegrityError( + body.loops.map((loop) => ({ loop, status: body.status })), + ); + if (catalogError) return catalogError; for (const loop of body.loops) this.loops.set(loop.slug, { ...loop, status: body.status }); if (body.activate) this.active = true; return Response.json({ imported: body.loops.length }); @@ -191,11 +204,31 @@ class MemoryLoopCatalogNamespace { }, }; } + + catalogIntegrityError(changes) { + if (!this.validateWrites) return null; + + try { + assertCatalogIntegrity([...this.loops.values()], changes); + return null; + } catch (error) { + return Response.json( + { + error: error instanceof Error ? error.message : "Invalid catalog graph", + code: "invalid_catalog_graph", + }, + { status: 409 }, + ); + } + } } function makeEnv(options = {}) { return { - LOOP_CATALOG: new MemoryLoopCatalogNamespace(options.active ?? true), + LOOP_CATALOG: new MemoryLoopCatalogNamespace(options.active ?? true, { + validateWrites: options.validateCatalog === true, + seedLoops: options.seedLoops, + }), LOOP_PUBLISH_TOKEN: "test-publish-token", BOOTSTRAP_CATALOG_DIGEST: options.bootstrapDigest || "test-bootstrap-digest", BOOTSTRAP_LOOP_COUNT: String(options.bootstrapLoopCount ?? 50), @@ -237,6 +270,63 @@ function exampleLoop(overrides = {}) { }; } +function overnightDocsLoop(overrides = {}) { + return exampleLoop({ + number: "900", + slug: "overnight-docs-sweep", + title: "The overnight docs sweep", + summary: "Keeps documentation refreshed during a bounded maintenance pass.", + seoTitle: "Overnight Docs Sweep | Loop Library", + description: "A loop for refreshing docs with a concrete overnight check.", + prompt: "Review stale docs, update the highest-value page, verify links, and stop.", + verifyTitle: "Docs pass the configured link and freshness checks.", + verifyDetail: "Run the documentation checks and inspect the changed page.", + useWhen: "Use this when documentation has drifted after product or code changes.", + steps: [ + "Find the stale documentation surface.", + "Make one focused update.", + "Run the documentation verification checks.", + ], + why: "Small bounded updates keep docs useful without broad rewrites.", + note: "Stop when the check passes or when ownership is unclear.", + keywords: ["documentation", "maintenance", "freshness"], + related: ["catalog-support-loop"], + ...overrides, + }); +} + +function catalogSupportLoop(overrides = {}) { + return exampleLoop({ + number: "901", + slug: "catalog-support-loop", + title: "The catalog support loop", + summary: "Provides a stable fixture for catalog graph validation.", + seoTitle: "Catalog Support Loop | Loop Library", + description: "A support loop used to validate related-loop relationships.", + prompt: "Check the catalog graph, confirm related links resolve, and stop.", + verifyTitle: "Every related loop slug resolves.", + verifyDetail: "Read the generated catalog and verify each related link.", + useWhen: "Use this when validating catalog graph behavior.", + steps: [ + "Load the catalog records.", + "Trace each related-loop slug.", + "Report any missing or unpublished relationship.", + ], + why: "Graph validation prevents public pages from hiding broken relationships.", + note: "Keep the fixture plain so tests can assert the target loop explicitly.", + keywords: ["catalog graph", "related loops", "validation"], + related: ["overnight-docs-sweep"], + ...overrides, + }); +} + +function publishedSupportLoops() { + return [ + { ...overnightDocsLoop(), status: "published" }, + { ...catalogSupportLoop(), status: "published" }, + ]; +} + function adminRequest(loop, options = {}) { const headers = { "Content-Type": "application/json" }; if (options.authorized !== false) headers.Authorization = "Bearer test-publish-token"; @@ -286,6 +376,60 @@ test("rejects unauthorized and invalid publishing requests", async () => { assert.equal((await invalid.json()).code, "invalid_loop"); }); +test("publishing routes reject invalid catalog graphs through the catalog store", async () => { + const missingRelated = makeEnv({ validateCatalog: true }); + const missingRelatedResponse = await handleRequest( + adminRequest(exampleLoop({ related: ["missing-loop"] })), + missingRelated, + ); + assert.equal(missingRelatedResponse.status, 409); + assert.match( + (await missingRelatedResponse.json()).error, + /references unavailable related loop missing-loop/, + ); + + const invalidImport = makeEnv({ validateCatalog: true }); + const importResponse = await handleRequest( + new Request(`${WORKER_ORIGIN}/admin/loops/import`, { + method: "POST", + headers: { + Authorization: "Bearer test-publish-token", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + loops: [exampleLoop({ related: ["missing-loop"] })], + status: "published", + }), + }), + invalidImport, + ); + assert.equal(importResponse.status, 409); + assert.equal((await importResponse.json()).code, "invalid_catalog_graph"); + + const duplicateMetadata = makeEnv({ + validateCatalog: true, + seedLoops: [ + ...publishedSupportLoops(), + { ...exampleLoop({ related: ["overnight-docs-sweep"] }), status: "published" }, + ], + }); + const duplicateResponse = await handleRequest( + adminRequest( + exampleLoop({ + number: "052", + slug: "duplicate-title-loop", + related: ["overnight-docs-sweep"], + }), + ), + duplicateMetadata, + ); + assert.equal(duplicateResponse.status, 409); + assert.match( + (await duplicateResponse.json()).error, + /duplicate-title-loop.title duplicates database-publishing-loop.title/, + ); +}); + test("rejects a stale publisher instead of overwriting a newer revision", async () => { const env = makeEnv(); const loop = exampleLoop(); @@ -412,6 +556,31 @@ test("renders database content into the canonical homepage and detail page", asy assert.match(detailHtml, /name="twitter:image:alt"/); }); +test("renders related links from a catalog graph validated by the store", async () => { + const env = makeEnv({ + validateCatalog: true, + seedLoops: publishedSupportLoops(), + }); + const publish = await handleRequest( + adminRequest(exampleLoop({ related: ["overnight-docs-sweep"] })), + env, + ); + assert.equal(publish.status, 201); + + const detail = await handleRequest( + new Request(`${SITE_ORIGIN}/loop-library/loops/database-publishing-loop/`), + env, + ); + const detailHtml = await detail.text(); + + assert.equal(detail.status, 200); + assert.match(detailHtml, /Related loops/); + assert.match( + detailHtml, + /href="\.\.\/overnight-docs-sweep\/">The overnight docs sweep<\/a>/, + ); +}); + test("renders homepage headers for HEAD by fetching the origin shell with GET", async () => { const env = makeEnv(); await handleRequest(adminRequest(exampleLoop()), env); @@ -563,6 +732,7 @@ test("generates catalogs, sitemap, and feed from the same record", async () => { for (const [path, expected] of [ ["catalog.json", '"loopCount":1'], ["catalog.md", "The database publishing loop"], + ["catalog.txt", "The database publishing loop"], ["llms.txt", "Published loops: 1."], ["sitemap.xml", "/loops/database-publishing-loop/"], ["feed.xml", "The database publishing loop"], From c21bc6403b07a00c06a2d97ff00943124070bd0e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 2 Jul 2026 02:26:11 +0000 Subject: [PATCH 3/4] Add dedicated Most popular sort to the loop library Co-authored-by: mberman84 --- loop-library/scripts/check.mjs | 2 ++ loop-library/site/index.html | 1 + loop-library/site/script.js | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/loop-library/scripts/check.mjs b/loop-library/scripts/check.mjs index e35f621..409db21 100644 --- a/loop-library/scripts/check.mjs +++ b/loop-library/scripts/check.mjs @@ -184,6 +184,8 @@ assert(browserScript.includes('params.set("sort", activeSort)')); assert(browserScript.includes("function comparePopular")); assert(browserScript.includes("Number(b.dataset.upvotes || 0)")); assert(html.includes('')); +assert(html.includes('')); +assert(browserScript.includes('activeSort === "popular"')); assert(browserScript.includes("library-pagination")); assert(!browserScript.includes("innerHTML")); diff --git a/loop-library/site/index.html b/loop-library/site/index.html index 3850efc..ab4ec44 100644 --- a/loop-library/site/index.html +++ b/loop-library/site/index.html @@ -430,6 +430,7 @@

    Sort