diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index c1d02bd..9c350bc 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -149,6 +149,9 @@ jobs: - name: Record deployed revision run: printf '%s\n' "$GITHUB_SHA" > docs/_site/deployment-revision.txt + - name: Remove internal DocFX build manifest + run: rm -- docs/_site/manifest.json + - name: Upload GitHub Pages artifact uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: diff --git a/docs/docfx.json b/docs/docfx.json index 42ac082..6a018b6 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -41,10 +41,11 @@ "_appLogoPath": "images/asibackbone-icon-50.png", "_appFaviconPath": "images/favicon.ico", "_enableSearch": true, + "_deferSearch": true, "_canonicalBaseUrl": "https://asibackbone.github.io/Learning/", "_structuredDataSiteName": "ASI Backbone Learning", "_structuredDataSiteAlternateName": "Accountable Systems Infrastructure (ASI) Backbone Learning", - "_structuredDataSiteDescription": "Accountable Systems Infrastructure (ASI) Backbone Learning provides practical .NET architecture tutorials, labs, and reference patterns for governed execution, secure applications, AI integration, and policy-driven systems.", + "_structuredDataSiteDescription": "Practical .NET architecture tutorials, labs, and reference patterns for governed execution, secure applications, AI integration, and policy-driven systems.", "_structuredDataPublisherName": "ASI Backbone", "_structuredDataPublisherAlternateName": "Accountable Systems Infrastructure (ASI) Backbone", "_structuredDataPublisherUrl": "https://github.com/AsiBackbone", diff --git a/docs/index.md b/docs/index.md index 4a5790c..abbdd5e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,222 +1,173 @@ --- +title: "Governed Execution & Secure .NET Architecture Tutorials" description: Practical .NET architecture tutorials, labs, and reference patterns for governed execution, secure applications, AI integration, and policy-driven systems. +_disableAffix: true +_disableBreadcrumb: true +_disableToc: true --- # ASI Backbone Learning -**Practical software architecture for governed execution, secure applications, AI integration, and policy-driven systems.** +
+

Practical .NET architecture for accountable systems

+

Learn governed execution, secure application design, policy-driven systems, and safe AI integration through focused tutorials, hands-on labs, and runnable examples.

+
+ Get Started + Find Your Path + Browse Articles +
+

A proposed action should become a governed decision before it becomes real-world execution.

+
-ASI Backbone Learning is the educational layer of the ASI Backbone organization. - -In this project, **ASI** means **Accountable Systems Infrastructure**. ASI Backbone Learning focuses on software architecture, governed execution, secure .NET applications, and AI integration; it is not affiliated with the Artificial Superintelligence Alliance. - -It exists to explain architectural ideas clearly, demonstrate them with focused examples, examine their tradeoffs, and connect those lessons to fuller working implementations. +ASI Backbone Learning explains architectural ideas, demonstrates them with focused examples, examines their tradeoffs, and connects the lessons to fuller working implementations. **ASI** means **Accountable Systems Infrastructure**. > **Read it. Run it. Question it. Improve it.** -## Start Here - -New to the project? - -Begin with [Getting Started](getting-started/index.md) for an introduction to the core concepts and recommended learning path. - -Already have a concrete problem in mind? Use [Find Your Path](getting-started/find-your-path.md) to choose a short, goal-specific route through the existing material without reading the repository in sequence. - -Evaluating the material from a developer, system engineering, enterprise architecture, platform, AI integration, or security/compliance role? Use [Adoption Personas and Entry Points](getting-started/adoption-personas-and-entry-points.md) to start from the responsibility you own. - -If the repository's vocabulary is new to you, use [Terminology and Established Architecture Concepts](architecture/terminology-and-established-concepts.md) to map Learning terms to established authorization, policy, capability, provenance, workflow, and mediation concepts. - -Already using ASP.NET Core authorization and unsure whether you need a broader governed-execution model? Read [When ASP.NET Core Authorization Is Enough](architecture/when-aspnet-core-authorization-is-enough.md). It presents the simpler framework-native approach first and explains where the architectural problem becomes larger than authorization. - -Learning does not require installing an `AsiBackbone` package. The material is intended to be useful as independent .NET architecture education. - -Looking for standalone technical writing rather than a curriculum path? Browse [Articles](articles/index.md). Articles are designed for direct external discovery and keep permanent `/articles//` publication URLs. - -The material is organized around a recurring separation of responsibilities: - -```text -Intent - ↓ -Context - ↓ -Constraints - ↓ -Decision - ↓ -Acknowledgment when required - ↓ -Scoped authority - ↓ -Host-owned execution - ↓ -Audit residue -``` - -The central idea is simple: - -> **A proposed action should become a governed decision before it becomes real-world execution.** - -## Choose a Learning Path - -### Architecture - -Explore the boundaries, responsibilities, tradeoffs, and structural patterns behind governed systems. - -[Explore Architecture →](architecture/index.md) - -### Governance - -Learn how intent, policy context, constraints, explicit decision outcomes, acknowledgment, scoped authority, and audit evidence fit together. - -[Explore Governance →](governance/index.md) - -### ASP.NET Core - -Study practical application architecture, secure defaults, middleware organization, operational structure, and implementation patterns for modern .NET applications. - -[Explore ASP.NET Core →](aspnetcore/index.md) - -### Security - -Examine security boundaries, least authority, explicit control flow, defensive defaults, and architecture that reduces accidental privilege. - -[Explore Security →](security/index.md) - -### AI Integration - -Apply governed-execution principles to AI-assisted systems, tool calls, agents, workflows, and host-controlled execution. - -> **The model may propose. The host retains execution authority.** - -[Explore AI Integration →](ai-integration/index.md) - -### Tutorials - -Follow focused, problem-first lessons that move from a common implementation through failure modes, architectural patterns, tradeoffs, and working references. - -[Browse Tutorials →](tutorials/index.md) - -### Executable Samples - -Move from architectural explanation toward runnable demonstrations of the same boundaries. - -The foundational sample set now contains five intentionally small .NET companion implementations, one for each foundational tutorial, with focused tests that make the architectural invariants observable. - -[Browse Executable Samples →](samples/index.md) - -### Labs - -Move from reading to reasoning with hands-on exercises, incomplete implementations, architecture critiques, policy scenarios, and design challenges. - -[Browse Labs →](labs/index.md) - -### Reference Architecture Case Studies - -See several Learning boundaries composed inside realistic, simulated scenarios without treating the specimen as a production framework or prescribed application design. - -[Browse Reference Architecture Case Studies →](case-studies/index.md) - -### Advanced - -Explore deeper architectural questions, alternative approaches, complex integration patterns, and topics that build on the foundational material. - -[Explore Advanced Topics →](advanced/index.md) - -## How Learning Works - -Tutorials generally follow a problem-first progression: - -```text -Problem - ↓ -Common or naive implementation - ↓ -Failure mode or limitation - ↓ -Architectural pattern - ↓ -Minimal teaching example - ↓ -Tradeoffs and alternatives - ↓ -Working repository example -``` - -The purpose is not to prove that one framework or architecture is always correct. - -The purpose is to make the reasoning visible. - -You are encouraged to: - -- Study a pattern without adopting an entire framework. -- Reimplement it differently. -- Compare it with another architecture. -- Identify cases where a simpler design is better. -- Challenge assumptions. -- Document alternative approaches. -- Use working repositories as architectural specimens rather than unquestioned templates. - -## The Working Repositories - -Learning connects concepts to two complementary implementation projects. - -### ASI Backbone - -[AsiBackbone/AsiBackbone](https://github.com/AsiBackbone/AsiBackbone) - -A .NET governance and policy-control framework for explicit, auditable, policy-governed decision pipelines. - -It provides working implementations of concepts such as policy evaluation, structured decision results, acknowledgment workflows, audit residue, capability-scoped authority, and host-owned execution boundaries. - -### .NET Core Application Template - -[AsiBackbone/NetCoreApplicationTemplate](https://github.com/AsiBackbone/NetCoreApplicationTemplate) - -An enterprise-oriented ASP.NET Core reference application demonstrating secure-by-default application architecture, structured logging, middleware organization, error handling, rate limiting, authentication-ready design, data-access patterns, and operational structure. - -Learning uses both repositories as working examples while keeping educational material smaller, more focused, and easier to question. - -## Canonical Does Not Mean Universal - -Some material may describe a **canonical pattern** aligned with the current ASI Backbone repositories. - -Other material may present an **alternative pattern** that solves the same problem differently. - -Both are useful. - -A canonical approach documents what the working implementations currently do. - -An alternative approach creates room for comparison, criticism, experimentation, and improvement. - -Architectural disagreement can be educational. - -## What This Project Is Not - -ASI Backbone Learning is an educational and architectural resource. - -It is not: - -- A compliance certification -- A legal standard -- A security guarantee -- An AI model -- An AGI or ASI implementation -- A robotics controller -- A replacement for application-specific security review -- A requirement to use the AsiBackbone package -- A claim that one architecture is universally correct - -Examples are teaching artifacts. Production systems remain responsible for their own security, infrastructure, persistence, regulatory requirements, safety controls, and operational execution. - -## Project Status - -**Living project — active development** - -Learning is expected to grow incrementally through focused tutorials, labs, diagrams, architectural comparisons, community questions, and practical experimentation. - -The goal is not to publish a large textbook all at once. - -The goal is to build a useful body of architectural knowledge one well-examined pattern at a time. +## Start with what describes you + +
+ + New to the project + Follow the recommended introduction to the core concepts. + + + I have a problem to solve + Choose a short, goal-specific route through the material. + + + I am evaluating by role + Start from your developer, architecture, platform, AI, or security responsibility. + + + The vocabulary is new + Map Learning terms to established architecture concepts. + + + I already use ASP.NET Core authorization + See when framework-native authorization is enough—and when the problem is broader. + +
+ +Learning does not require installing an `AsiBackbone` package. Use it as independent .NET architecture education or connect it to the working repositories. + +## The core model + +The material uses a recurring separation of responsibilities: + +
    +
  1. Intent
  2. +
  3. Context
  4. +
  5. Constraints
  6. +
  7. Decision
  8. +
  9. Acknowledgment when required
  10. +
  11. Scoped authority
  12. +
  13. Host-owned execution
  14. +
  15. Audit residue
  16. +
+ +## Choose a learning path + +
+
+ Beginner to advanced +

Architecture

+

Explore boundaries, responsibilities, tradeoffs, and structural patterns behind governed systems.

+ Explore Architecture +
+
+ Beginner to advanced +

Governance

+

Connect intent, policy context, explicit decisions, acknowledgment, scoped authority, and audit evidence.

+ Explore Governance +
+
+ Beginner to intermediate +

ASP.NET Core

+

Study secure defaults, middleware organization, operational structure, and modern .NET implementation patterns.

+ Explore ASP.NET Core +
+
+ Beginner to advanced +

Security

+

Examine trust boundaries, least authority, explicit control flow, defensive defaults, and accidental privilege.

+ Explore Security +
+
+ Intermediate to advanced +

AI Integration

+

Apply governed execution to AI-assisted systems, tool calls, agents, workflows, and host-controlled effects.

+ Explore AI Integration +
+
+ Beginner to intermediate +

Tutorials

+

Move from a familiar implementation through failure modes, tradeoffs, and working references.

+ Browse Tutorials +
+
+ Beginner to intermediate +

Executable Samples

+

Run five small .NET companions with focused tests that make architectural invariants observable.

+ Browse Executable Samples +
+
+ Intermediate to advanced +

Labs

+

Practice with exercises, incomplete implementations, architecture critiques, and policy scenarios.

+ Browse Labs +
+ +
+ Advanced +

Advanced Topics

+

Explore complex integration patterns, alternative approaches, and deeper architectural questions.

+ Explore Advanced Topics +
+
+ +## How Learning works + +Tutorials use a problem-first progression: + +
    +
  1. Problem
  2. +
  3. Common implementation
  4. +
  5. Failure mode
  6. +
  7. Architectural pattern
  8. +
  9. Teaching example
  10. +
  11. Tradeoffs
  12. +
  13. Working example
  14. +
+ +The goal is to make the reasoning visible—not to prove that one framework or architecture is always correct. Study a pattern, reimplement it, compare alternatives, identify when a simpler design is better, and treat working repositories as specimens rather than unquestioned templates. + +## Working repositories + +- [AsiBackbone/AsiBackbone](https://github.com/AsiBackbone/AsiBackbone) — a .NET governance and policy-control framework for explicit, auditable decision pipelines. +- [AsiBackbone/NetCoreApplicationTemplate](https://github.com/AsiBackbone/NetCoreApplicationTemplate) — an enterprise-oriented ASP.NET Core reference application with secure defaults and operational patterns. + +## Recently added + +This is a living project under active development. Recent publications include: + +- [A Passing Agent Diff Is Not Project Authority](articles/2026/a-passing-agent-diff-is-not-project-authority.md) +- [Why an AI Tool Call Is Only a Proposal](articles/2026/why-ai-tool-call-is-only-a-proposal.md) +- [Your Audit Log Is Not Evidence](articles/2026/your-audit-log-is-not-evidence.md) + +
+ Scope, terminology, and important limitations + +

Canonical patterns document what the working repositories currently do; alternative patterns create room for comparison, criticism, and improvement. Canonical does not mean universal.

+ +

ASI Backbone Learning is an educational architecture resource—not a compliance certification, legal standard, security guarantee, AI model, AGI or ASI implementation, robotics controller, replacement for application-specific security review, or requirement to use the AsiBackbone package. Examples are teaching artifacts; production systems remain responsible for their own security, infrastructure, persistence, regulatory requirements, safety controls, and execution.

+ +

ASI Backbone Learning is not affiliated with the Artificial Superintelligence Alliance.

+
--- diff --git a/docs/templates/layout/_master.tmpl b/docs/templates/layout/_master.tmpl index 20e45fd..187334e 100644 --- a/docs/templates/layout/_master.tmpl +++ b/docs/templates/layout/_master.tmpl @@ -11,11 +11,11 @@ {{/redirect_url}} {{^redirect_url}} - {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}}{{#_appTitle}} | {{_appTitle}}{{/_appTitle}} - + {{#_description}}{{/_description}} {{#description}}{{/description}} {{#_canonicalUrl}}{{/_canonicalUrl}} @@ -43,12 +43,14 @@ {{#_structuredDataJson}}{{/_structuredDataJson}} + {{#_appLogoPath}}{{/_appLogoPath}} - + {{^_disableToc}}{{/_disableToc}} {{#_noindex}}{{/_noindex}} {{#_enableSearch}}{{/_enableSearch}} + {{#_deferSearch}}{{/_deferSearch}} {{#_disableNewTab}}{{/_disableNewTab}} {{#_disableTocFilter}}{{/_disableTocFilter}} {{#docurl}}{{/docurl}} @@ -86,12 +88,13 @@ {{^redirect_url}} +
{{^_disableNavbar}}
-
+
{{^_disableToc}}
diff --git a/docs/templates/public/main.css b/docs/templates/public/main.css index adabd17..b9baa8d 100644 --- a/docs/templates/public/main.css +++ b/docs/templates/public/main.css @@ -25,3 +25,248 @@ margin-top: 0.25rem; font-size: 0.95rem; } + +.skip-link { + position: fixed; + top: 0.5rem; + left: 0.5rem; + z-index: 1100; + padding: 0.65rem 1rem; + border-radius: 0.375rem; + background: var(--bs-body-bg); + color: var(--bs-link-color); + font-weight: 600; + box-shadow: 0 0 0 3px var(--bs-focus-ring-color); + transform: translateY(-150%); + transition: transform 120ms ease-in-out; +} + +.skip-link:focus { + transform: translateY(0); +} + +.home-hero { + margin: 0.75rem 0 2rem; + padding: clamp(1.25rem, 4vw, 2.5rem); + border: 1px solid var(--bs-border-color); + border-radius: 1rem; + background: linear-gradient(135deg, rgba(var(--bs-primary-rgb), 0.12), transparent 65%); +} + +.home-kicker { + margin-bottom: 0.5rem; + color: var(--bs-primary-text-emphasis); + font-size: 0.875rem; + font-weight: 700; + letter-spacing: 0.045em; + text-transform: uppercase; +} + +.home-summary { + max-width: 52rem; + margin-bottom: 1.25rem; + font-size: clamp(1.1rem, 2.5vw, 1.4rem); + line-height: 1.55; +} + +.home-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.home-actions .btn { + min-width: 9rem; + font-weight: 600; +} + +.home-principle { + max-width: 54rem; + margin: 1.25rem 0 0; + padding-top: 1rem; + border-top: 1px solid var(--bs-border-color); + font-weight: 600; +} + +.entry-point-grid, +.learning-path-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin: 1rem 0 2rem; +} + +.entry-point { + display: flex; + flex-direction: column; + gap: 0.35rem; + padding: 1rem 1.1rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.75rem; + color: var(--bs-body-color); + text-decoration: none; + transition: border-color 120ms ease-in-out, box-shadow 120ms ease-in-out, transform 120ms ease-in-out; +} + +.entry-point:hover, +.entry-point:focus-visible { + border-color: var(--bs-primary); + box-shadow: 0 0.3rem 0.9rem rgba(var(--bs-primary-rgb), 0.12); + color: var(--bs-body-color); + transform: translateY(-2px); +} + +.entry-point strong { + color: var(--bs-link-color); +} + +.entry-point span { + color: var(--bs-secondary-color); +} + +.process-flow { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 1.5rem 1rem; + margin: 1.25rem 0 2rem; + padding: 0; + list-style-position: inside; +} + +.process-flow li { + position: relative; + min-height: 5rem; + padding: 1.65rem 0.75rem 1rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.75rem; + background: var(--bs-tertiary-bg); + font-weight: 600; + text-align: center; +} + +.process-flow li::marker { + color: var(--bs-primary); + font-weight: 700; +} + +.process-flow li span { + display: block; + margin-left: 0.3rem; + color: var(--bs-secondary-color); + font-size: 0.8rem; + font-weight: 400; +} + +.process-flow-short { + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + +.learning-path-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.learning-path-card { + display: flex; + min-width: 0; + flex-direction: column; + padding: 1.25rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.75rem; + background: var(--bs-body-bg); +} + +.learning-path-card h3 { + margin: 0.35rem 0 0.65rem; + font-size: 1.2rem; +} + +.learning-path-card p { + flex: 1; + color: var(--bs-secondary-color); +} + +.learning-path-card > a { + align-self: flex-start; + font-weight: 600; +} + +.path-level { + color: var(--bs-primary-text-emphasis); + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.035em; + text-transform: uppercase; +} + +.home-details { + margin: 2rem 0; + padding: 1rem 1.25rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.75rem; + background: var(--bs-tertiary-bg); +} + +.home-details summary { + cursor: pointer; + font-weight: 600; +} + +.home-details[open] summary { + margin-bottom: 1rem; +} + +@media (max-width: 991.98px) { + .learning-path-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .process-flow, + .process-flow-short { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .process-flow li:nth-child(4)::after { + right: -1.15rem; + bottom: auto; + transform: none; + } + + .process-flow li:nth-child(even):not(:last-child)::after { + right: 50%; + bottom: -1.45rem; + transform: translateX(50%) rotate(90deg); + } +} + +@media (max-width: 575.98px) { + .home-hero { + margin-top: 0.25rem; + padding: 1.1rem; + } + + .home-actions .btn { + width: 100%; + } + + .entry-point-grid, + .learning-path-grid, + .process-flow, + .process-flow-short { + grid-template-columns: 1fr; + } + + .process-flow li:not(:last-child)::after, + .process-flow li:nth-child(4)::after, + .process-flow-short li:nth-child(4)::after { + right: 50%; + bottom: -1.45rem; + transform: translateX(50%) rotate(90deg); + } +} + +@media (prefers-reduced-motion: reduce) { + .skip-link, + .entry-point { + transition: none; + } +} diff --git a/docs/templates/public/main.js b/docs/templates/public/main.js new file mode 100644 index 0000000..cba6d7f --- /dev/null +++ b/docs/templates/public/main.js @@ -0,0 +1,110 @@ +// Keep DocFX search available while avoiding a multi-megabyte index download on +// landing-page visits that never use it. DocFX calls start() before it creates +// the search worker, which lets this lightweight proxy delay only that worker. + +export default { + start: function () { + var deferSearch = document.querySelector('meta[name="docfx:defersearch"][content="true"]') + var searchInput = document.getElementById('search-query') + + if (!deferSearch || !searchInput || !window.Worker) { + return + } + + var NativeWorker = window.Worker + document.documentElement.dataset.searchIndex = 'deferred' + searchInput.form.addEventListener('submit', function (event) { + event.preventDefault() + }) + + function DeferredWorker(url, options) { + var workerUrl = String(url) + + if (!workerUrl.endsWith('/search-worker.min.js') && workerUrl !== 'public/search-worker.min.js') { + return new NativeWorker(url, options) + } + + var worker = null + var messages = [] + var proxy = this + + function activate() { + if (worker) { + return + } + + document.documentElement.dataset.searchIndex = 'loading' + worker = new NativeWorker(url, options) + worker.onerror = function (event) { + if (proxy.onerror) { + proxy.onerror(event) + } + } + worker.onmessageerror = function (event) { + if (proxy.onmessageerror) { + proxy.onmessageerror(event) + } + } + worker.onmessage = function (event) { + if (proxy.onmessage) { + proxy.onmessage(event) + } + + if (event.data && event.data.e === 'index-ready') { + document.documentElement.dataset.searchIndex = 'ready' + } + + if (event.data && event.data.e === 'index-ready' && searchInput.value) { + searchInput.dispatchEvent(new Event('input', { bubbles: true })) + } + } + + messages.forEach(function (message) { + if (message.transfer === undefined) { + worker.postMessage(message.data) + } else { + worker.postMessage(message.data, message.transfer) + } + }) + messages = [] + } + + this.postMessage = function (data, transfer) { + if (worker) { + if (transfer === undefined) { + worker.postMessage(data) + } else { + worker.postMessage(data, transfer) + } + return + } + + messages.push({ data: data, transfer: transfer }) + } + + this.terminate = function () { + messages = [] + if (worker) { + worker.terminate() + } + } + + this.addEventListener = function (type, listener, eventOptions) { + activate() + worker.addEventListener(type, listener, eventOptions) + } + + this.removeEventListener = function (type, listener, eventOptions) { + if (worker) { + worker.removeEventListener(type, listener, eventOptions) + } + } + + searchInput.addEventListener('focus', activate, { once: true }) + searchInput.addEventListener('input', activate, { once: true }) + } + + window.Worker = DeferredWorker + searchInput.disabled = false + } +} diff --git a/tools/validate-doc-metadata.cs b/tools/validate-doc-metadata.cs index 6e9ea49..782772e 100644 --- a/tools/validate-doc-metadata.cs +++ b/tools/validate-doc-metadata.cs @@ -11,6 +11,8 @@ static class MetadataValidator { private const int MaximumDescriptionLength = 160; + private const string SiteDescription = "Practical .NET architecture tutorials, labs, and reference patterns for governed execution, secure applications, AI integration, and policy-driven systems."; + private const string LandingPageTitle = "Governed Execution & Secure .NET Architecture Tutorials | ASI Backbone Learning"; private static readonly Uri SiteRoot = new("https://asibackbone.github.io/Learning/"); private static readonly Uri FeedUri = new(SiteRoot, "feed.xml"); @@ -143,6 +145,8 @@ public static int Run() ValidateRepresentativePage(outputRoot, page, errors); } + ValidateLandingPage(outputRoot, errors); + if (pageCount == 0) { errors.Add("No generated DocFX pages were found for canonical validation."); @@ -386,6 +390,13 @@ private static void ValidateRepresentativePage( string html = File.ReadAllText(path); ExpectCount(page.Path, "meta description", DescriptionRegex.Matches(html).Count, 1, errors); + if (!html.Contains( + "Skip to content", + StringComparison.Ordinal)) + { + errors.Add($"{page.Path}: expected a skip link targeting the main content."); + } + MatchCollection rssLinks = RssAutodiscoveryRegex.Matches(html); ExpectCount(page.Path, "RSS autodiscovery link", rssLinks.Count, 1, errors); if (rssLinks.Count == 1) @@ -451,6 +462,58 @@ private static void ValidateRepresentativePage( } } + private static void ValidateLandingPage(string outputRoot, ICollection errors) + { + const string relativePath = "index.html"; + string path = Path.Combine(outputRoot, relativePath); + + if (!File.Exists(path)) + { + return; + } + + string html = File.ReadAllText(path); + + if (!html.Contains($"{LandingPageTitle}", StringComparison.Ordinal)) + { + errors.Add($"{relativePath}: landing-page title is missing or does not use the descriptive SEO title."); + } + + if (!html.Contains("", StringComparison.Ordinal)) + { + errors.Add($"{relativePath}: search-index initialization must be deferred until search is used."); + } + + if (html.Contains("", StringComparison.Ordinal); + int actionsIndex = html.IndexOf("
= 0 + ? html.IndexOf("= 0 && actionsIndex > nextSectionIndex)) + { + errors.Add($"{relativePath}: primary learning calls to action must appear directly below the landing-page heading."); + } + + if (html.Contains("
", StringComparison.Ordinal))
+        {
+            errors.Add($"{relativePath}: visual learning flows must not be exposed as text code blocks.");
+        }
+
+        string runtimeScriptPath = Path.Combine(outputRoot, "public", "main.js");
+        if (!File.Exists(runtimeScriptPath) ||
+            !File.ReadAllText(runtimeScriptPath).Contains("DeferredWorker", StringComparison.Ordinal))
+        {
+            errors.Add("public/main.js: deferred search runtime was not included in the generated site.");
+        }
+    }
+
     private static void ValidateStructuredData(
         ExpectedPage page,
         JsonElement root,
@@ -483,6 +546,7 @@ private static void ValidateStructuredData(
             "alternateName",
             "Accountable Systems Infrastructure (ASI) Backbone Learning",
             errors);
+        ExpectProperty(page.Path, website, "WebSite", "description", SiteDescription, errors);
 
         ExpectProperty(page.Path, publisher, "Organization", "name", "ASI Backbone", errors);
         ExpectProperty(page.Path, publisher, "Organization", "url", "https://github.com/AsiBackbone", errors);
diff --git a/tools/validate-docfx-template-baseline.cs b/tools/validate-docfx-template-baseline.cs
index a65d56e..f131d66 100644
--- a/tools/validate-docfx-template-baseline.cs
+++ b/tools/validate-docfx-template-baseline.cs
@@ -13,6 +13,7 @@ static class DocFxTemplateBaselineValidator
     private const string BaselineRelativePath = "docs/templates/docfx-template-baseline.json";
     private const string DocFxConfigRelativePath = "docs/docfx.json";
     private const string CustomStylesheetRelativePath = "docs/templates/public/main.css";
+    private const string CustomRuntimeScriptRelativePath = "docs/templates/public/main.js";
     private const string LabAuthoringTemplateRelativePath = "community/lab-acceptance-criteria-template.md";
     private const string LocalTemplateDirectoryRelativePath = "docs/templates";
 
@@ -248,6 +249,14 @@ private static void ValidateLocalTemplateLayout(
                 $"The custom modern-template stylesheet must exist at '{CustomStylesheetRelativePath}'.");
         }
 
+        if (!File.Exists(Path.Combine(
+                repositoryRoot,
+                CustomRuntimeScriptRelativePath.Replace('/', Path.DirectorySeparatorChar))))
+        {
+            errors.Add(
+                $"The custom modern-template runtime script must exist at '{CustomRuntimeScriptRelativePath}'.");
+        }
+
         if (!File.Exists(Path.Combine(
                 repositoryRoot,
                 LabAuthoringTemplateRelativePath.Replace('/', Path.DirectorySeparatorChar))))