diff --git a/public/_headers b/public/_headers index 0aa34af9..4687d9cf 100644 --- a/public/_headers +++ b/public/_headers @@ -22,6 +22,9 @@ /sitemap.xml Cache-Control: public, max-age=86400 +/blog/feed.xml + X-Robots-Tag: noindex + /_astro/* Cache-Control: public, max-age=31536000, immutable diff --git a/public/_redirects b/public/_redirects index 4caf4b1e..c3082c54 100644 --- a/public/_redirects +++ b/public/_redirects @@ -14,6 +14,29 @@ /plain/docs/pilot-director /plain/docs/pilot-mom 301 /blog/pilot-raises-4-5m-seed /news/pilot-raises-4-5m-seed 301 +# Preserve authority from retired routes that have direct successors. +# Explicit file-style rules avoid a generic .html redirect landing on a 404. +/networks /for/networks 301 +/networks/ /for/networks 301 +/plain/terms /terms 301 +/plain/terms/ /terms 301 +/docs/polo /for/networks 301 +/docs/polo/ /for/networks 301 +/docs/console-guide /enterprise/autonomous-ai-agents 301 +/docs/console-guide/ /enterprise/autonomous-ai-agents 301 +/blog/service-agents-overlay-network /docs/service-agents 301 +/blog/understanding-autonomous-agent-networking-distributed-ai /blog/autonomous-agent-networking-distributed-ai 301 +/blog/openclaw-task-delegation-polo-reputation /blog/build-openclaw-agent-self-organizes-pilot 301 +/blog/openclaw-task-delegation-polo-reputation.html /blog/build-openclaw-agent-self-organizes-pilot 301 +/blog/decentralized-task-marketplace-agents /blog/build-ai-agent-marketplace-discovery-reputation 301 +/blog/decentralized-task-marketplace-agents.html /blog/build-ai-agent-marketplace-discovery-reputation 301 +/blog/pilot-console-manage-agent-networks /enterprise/autonomous-ai-agents 301 +/blog/ten-thousand-agents-three-vms /blog/scaling-openclaw-fleets-thousands-agents 301 +/blog/ten-thousand-agents-three-vms.html /blog/scaling-openclaw-fleets-thousands-agents 301 +/blog/polo-score-reputation-without-blockchain /blog/preferential-attachment-ai-networks-trust-graph 301 +/blog/how-to-wrap-legacy-protocols-for-p2p-ai-networks /blog/legacy-protocol-integration-for-secure-distributed-ai 301 +/blog/network-security-checklist-for-decentralized-p2p-ai-systems /blog/network-security-for-multi-agent-systems-key-strategies 301 + # Consolidate an overlapping P2P explainer into the more complete article. /blog/why-secure-direct-p2p-connections-matter-for-ai-agents /blog/why-direct-p2p-connections-power-secure-ai-networking 301 @@ -21,9 +44,9 @@ /blog/boarding-pilotagent-org-alternatives-3 /blog/pilot-vs-tailscale-nebula-zerotier-ai-agents 301 # Normalize legacy file-style links to the clean public routes. +/brand/index.html /brand/ 301 /blog/:slug.html /blog/:slug 301 /docs/:slug.html /docs/:slug 301 /learn/:slug.html /learn/:slug 301 /news/:slug.html /news/:slug 301 /apps/:slug.html /apps/:slug 301 -/brand/index.html /brand/ 301 diff --git a/scripts/check-site-integrity.mjs b/scripts/check-site-integrity.mjs index a7ac93b6..ab00b608 100644 --- a/scripts/check-site-integrity.mjs +++ b/scripts/check-site-integrity.mjs @@ -461,6 +461,42 @@ if (!existsSync(sitemapPath)) { } } +const redirectsPath = join(DIST, '_redirects'); +if (!existsSync(redirectsPath)) { + errors.push('_redirects is missing from the build'); +} else { + const redirects = await readFile(redirectsPath, 'utf8'); + for (const [index, rawLine] of redirects.split(/\r?\n/).entries()) { + const line = rawLine.trim(); + if (!line || line.startsWith('#')) continue; + const [source, destination, status, ...extra] = line.split(/\s+/); + if (!source || !destination || !status || extra.length) { + errors.push(`_redirects:${index + 1}: expected “source destination status”`); + continue; + } + if (!/^3\d\d$/.test(status)) { + errors.push(`_redirects:${index + 1}: invalid redirect status “${status}”`); + } + if (/[:*]/.test(source) || /[:*]/.test(destination)) continue; + + let target; + try { target = new URL(destination, ORIGIN); } + catch { + errors.push(`_redirects:${index + 1}: invalid destination “${destination}”`); + continue; + } + if (target.origin !== ORIGIN) continue; + const sourceUrl = new URL(source, ORIGIN); + if (sourceUrl.pathname === target.pathname && sourceUrl.search === target.search) { + errors.push(`_redirects:${index + 1}: source redirects to itself`); + continue; + } + if (!await targetFor(target.pathname)) { + errors.push(`_redirects:${index + 1}: destination “${destination}” is not a generated route`); + } + } +} + if (errors.length) { console.error(`✗ Site integrity check failed with ${errors.length} problem(s):\n`); for (const error of errors) console.error(` - ${error}`);