diff --git a/.gitignore b/.gitignore index 088f4ab..3b8eb23 100644 --- a/.gitignore +++ b/.gitignore @@ -190,6 +190,7 @@ publish/ # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj +.worktrees/ # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained @@ -391,4 +392,4 @@ coverage-report/ # DocFX generated outputs (do not commit generated artifacts) docs/_site/ -docs/api/ \ No newline at end of file +docs/api/ diff --git a/docs/architecture-layering.md b/docs/architecture-layering.md new file mode 100644 index 0000000..378bdf4 --- /dev/null +++ b/docs/architecture-layering.md @@ -0,0 +1,36 @@ +# Architecture Layering + +## Allowed Dependencies + +- `Kernel` depends only on BCL and internal kernel contracts. +- `Platform Services` may depend on `Kernel` but never on `Experience Extensions`. +- `Experience Extensions` may depend on `Kernel` and `Platform Services`. +- `Product Surface` may depend on all public APIs but cannot introduce reverse dependencies into lower layers. + +## Allowed Public API Categories + +- `Kernel API` — core lifecycle, scheduling, bridge contracts, and invariant enforcement. +- `Platform API` — host-neutral service abstractions (storage, shell, diagnostics, security policy). +- `Extension API` — framework adapters and plugin contracts with explicit support tier labels. +- `Product API` — template-facing composition APIs and app bootstrap surfaces. + +## Classification Decision Tree + +1. Does the API define runtime invariants used across hosts/frameworks? + - Yes: classify as `Kernel API`. +2. Does the API expose host-neutral platform service behavior? + - Yes: classify as `Platform API`. +3. Does the API bind to framework/host/plugin-specific behavior? + - Yes: classify as `Extension API`. +4. Is the API only for app composition/template setup? + - Yes: classify as `Product API`. +5. If none apply, keep internal and do not publish. + +## Kernel API Architectural Approval Rule + +- Any new public `Kernel API`, breaking `Kernel API` change, or semantic behavior change requires architecture approval before merge. +- Approval must include: + - dependency-boundary impact statement, + - compatibility plan, + - rollback/fallback plan, + - linked governance evidence for release gates. diff --git a/docs/docfx.json b/docs/docfx.json index 6890d1d..12eaf39 100644 --- a/docs/docfx.json +++ b/docs/docfx.json @@ -37,6 +37,7 @@ "articles/**/toc.yml", "demo/**.md", "demo/**/toc.yml", + "framework-capabilities.json", "toc.yml", "*.md" ] diff --git a/docs/framework-capabilities.json b/docs/framework-capabilities.json new file mode 100644 index 0000000..ae86449 --- /dev/null +++ b/docs/framework-capabilities.json @@ -0,0 +1,66 @@ +{ + "schemaVersion": "1.0", + "updatedAtUtc": "2026-03-26T00:00:00Z", + "capabilities": [ + { + "id": "kernel.bridge.contracts", + "name": "Kernel Bridge Contracts", + "layer": "Kernel", + "status": "stable", + "supportTier": "LTS", + "owner": "runtime-core", + "breakingChangePolicy": "architecture-approval-required", + "compatibilityScope": "all-supported-hosts-and-framework-bindings", + "rollbackStrategy": "revert-kernel-contract-change-and-restore-previous-bridge-schema", + "observability": { + "traces": true, + "metrics": true, + "structuredErrors": true + }, + "security": { + "boundaryValidated": true, + "defaultDeny": true + } + }, + { + "id": "platform.shell.integration", + "name": "Platform Shell Integration", + "layer": "Platform Services", + "status": "stable", + "supportTier": "standard", + "owner": "platform-services", + "breakingChangePolicy": "release-gate-required", + "compatibilityScope": "all-supported-desktop-hosts", + "rollbackStrategy": "disable-shell-feature-flag-and-fallback-to-previous-platform-adapter", + "observability": { + "traces": true, + "metrics": true, + "structuredErrors": true + }, + "security": { + "boundaryValidated": true, + "defaultDeny": true + } + }, + { + "id": "extension.framework.adapter.react", + "name": "React Framework Adapter", + "layer": "Experience Extensions", + "status": "provisional", + "supportTier": "best-effort", + "owner": "framework-extensions", + "breakingChangePolicy": "compatibility-note-required", + "compatibilityScope": "react-adapter-consumers-on-supported-runtime-version", + "rollbackStrategy": "pin-previous-adapter-version-and-disable-new-extension-binding", + "observability": { + "traces": true, + "metrics": false, + "structuredErrors": true + }, + "security": { + "boundaryValidated": true, + "defaultDeny": true + } + } + ] +} diff --git a/docs/index.md b/docs/index.md index 956e0cb..f7d24eb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -29,6 +29,16 @@ All of this is type-checked at compile time, AOT-safe, and works the same on Win | Build an AI chat app with streaming | [AI Integration Guide](ai-integration-guide.md) | | Understand how the runtime works | [Architecture](articles/architecture.md) | +## Platform Documents + +| Platform document | Purpose | +|---|---| +| [Product Platform Roadmap](product-platform-roadmap.md) | Positioning, strategy, layering model, capability contract, and P0-P5 roadmap | +| [Architecture Layering](architecture-layering.md) | Dependency boundaries, API category rules, and kernel approval policy | +| [Platform Status](platform-status.md) | Current governed platform snapshot | +| [Release Governance](release-governance.md) | Stable release rules, gates, and promotion flow | +| [Framework Capabilities](framework-capabilities.json) | Machine-readable capability registry baseline | + ## Developer Resources | Resource | Description | diff --git a/docs/platform-status.md b/docs/platform-status.md new file mode 100644 index 0000000..2d0ba11 --- /dev/null +++ b/docs/platform-status.md @@ -0,0 +1,18 @@ +# Platform Status + +## Current Snapshot + +This page is a controlled snapshot placeholder for platform status reporting. + +- Snapshot date: `TBD` +- Channel status: `TBD` +- Kernel stability: `TBD` +- Extension readiness: `TBD` +- Security gate status: `TBD` +- Observability gate status: `TBD` +- Release gate status: `TBD` + +## Notes + +- Replace `TBD` values through the release governance process. +- Keep this document aligned with `release-governance.md` and capability registry updates. diff --git a/docs/product-platform-roadmap.md b/docs/product-platform-roadmap.md new file mode 100644 index 0000000..fbb398c --- /dev/null +++ b/docs/product-platform-roadmap.md @@ -0,0 +1,74 @@ +# Product Platform Roadmap + +## Positioning + +Fulora is a product platform for shipping web-first applications as native desktop products with governed runtime contracts. + +## Strategic Direction + +- Keep the runtime core host-neutral and stable. +- Push host, framework, and ecosystem change into explicit extension lanes. +- Scale releases through machine-checkable governance artifacts. + +## Stable Core vs Extensions + +- Stable core: runtime kernel, bridge contracts, lifecycle invariants, security primitives, and diagnostics contracts. +- Extensions: framework adapters, host-specific integration layers, optional plugins, and ecosystem templates. +- Rule: extension velocity must not break stable core compatibility guarantees. + +## Layering Model + +- `Kernel` — cross-platform runtime contracts and execution invariants. +- `Platform Services` — persistence, networking, shell integration, and policy enforcement abstractions. +- `Experience Extensions` — framework adapters, plugins, and host-specific implementations. +- `Product Surface` — templates, samples, and product applications consuming public contracts. + +## Capability Support Contract + +- Capabilities are registered in `framework-capabilities.json` with explicit lifecycle states. +- Each capability declares owner, support tier, compatibility scope, and rollback strategy. +- Breaking capability changes must follow each capability's `breakingChangePolicy`. +- Architecture approval is mandatory for kernel-level changes and capability policies that explicitly require it. +- release-gate evidence is required for all breaking capability changes. + +## Security Model + +- Validate all external inputs at the boundary. +- Default-deny privileged operations; require explicit capability enablement. +- Enforce secret isolation through environment/runtime providers only. +- Apply security review gates before stable release promotion. + +## Observability Model + +- Every capability exposes baseline traces, metrics, and structured error events. +- Release candidates must include machine-readable evidence for health, regression, and fallback readiness. +- Observability artifacts are part of release governance, not optional diagnostics. + +## Release Governance + +- Stable channel changes require passing release gates defined in `release-governance.md`. +- Kernel API and support-tier changes require architecture review sign-off. +- Regression or policy gate failures block promotion until evidence is updated. + +## Developer Defaults + +- Safe-by-default templates and policy presets. +- Stable APIs first; extension APIs are opt-in and explicitly marked. +- New capabilities start as provisional until governance evidence is complete. + +## P0-P5 Roadmap + +| Phase | Focus | Outcome | +|---|---|---| +| P0 | Baseline platform contracts | Kernel and governance envelope established | +| P1 | Layering enforcement | Dependency rules and API categories formalized | +| P2 | Capability registry | Machine-readable capability metadata and support tiers | +| P3 | Security + observability hardening | Required gates and evidence pipelines active | +| P4 | Release automation | Stable release gates fully automated in CI | +| P5 | Ecosystem scale-out | Extension lanes expand without core contract drift | + +## Documentation Governance + +- Platform docs are first-class governance artifacts and must stay DocFX-discoverable. +- `docs/index.md` and `docs/toc.yml` must expose platform documents as top-level navigation. +- Governance tests enforce presence and linkage for required platform pages. diff --git a/docs/release-governance.md b/docs/release-governance.md new file mode 100644 index 0000000..ea3944d --- /dev/null +++ b/docs/release-governance.md @@ -0,0 +1,27 @@ +# Release Governance + +## Stable Release Rules + +1. Stable releases must preserve kernel compatibility contracts unless architecture approval is recorded. +2. Capability lifecycle changes (`provisional` -> `stable`, deprecations, removals) require evidence updates in `framework-capabilities.json`. +3. Security and observability controls are mandatory gates for promotion. +4. Breaking capability changes must satisfy each capability's `breakingChangePolicy`, and release-gate evidence is mandatory. +5. Any failed gate blocks release promotion until remediation evidence is published. + +## Release Gates + +| Gate | Required Evidence | Block Condition | +|---|---|---| +| Compatibility | Kernel/API diff review + approval record | Unapproved breaking change | +| Capability Registry | Updated `framework-capabilities.json` entries | Missing or stale capability metadata | +| Security | Security review report + boundary validation checks | Unresolved critical/high security findings | +| Observability | Trace/metric/error baseline report | Missing baseline or regression beyond threshold | +| Documentation | Top-level docs presence and link governance tests | Required platform docs not discoverable | +| Quality | Targeted unit/integration/e2e governance test pass | Any required governance suite failing | + +## Promotion Flow + +1. Prepare candidate and refresh capability + status snapshots. +2. Run governance and release gate checks. +3. Approve or reject promotion with machine-readable evidence. +4. Publish stable release only when all gates pass. diff --git a/docs/superpowers/plans/2026-03-26-fulora-platform-roadmap-migration.md b/docs/superpowers/plans/2026-03-26-fulora-platform-roadmap-migration.md new file mode 100644 index 0000000..040385d --- /dev/null +++ b/docs/superpowers/plans/2026-03-26-fulora-platform-roadmap-migration.md @@ -0,0 +1,534 @@ +# Fulora Platform Roadmap Migration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the OpenSpec-based roadmap/governance system with a `docs/`-first platform definition, then align repository docs, build gates, and tests to the new model without leaving broken links or stale governance checks behind. + +**Architecture:** The migration proceeds in layers. First add the new normative platform documents and machine-readable capability placeholders, then redirect public documentation to those sources, then remove OpenSpec assets and all build/test/workflow dependencies on them, and finally re-baseline governance tests around the new documentation model. This keeps the repository valid at every checkpoint and avoids a long broken intermediate state. + +**Tech Stack:** Markdown, JSON, DocFX docs, Nuke build targets, xUnit governance tests, ripgrep, git + +--- + +### Task 1: Add the Replacement Platform Documents + +**Files:** +- Create: `docs/product-platform-roadmap.md` +- Create: `docs/architecture-layering.md` +- Create: `docs/platform-status.md` +- Create: `docs/framework-capabilities.json` +- Create: `docs/release-governance.md` +- Modify: `docs/toc.yml` +- Modify: `docs/index.md` +- Test: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` + +- [ ] **Step 1: Write the failing documentation governance tests** + +Create `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` with assertions that: + +- `docs/product-platform-roadmap.md` exists +- `docs/architecture-layering.md` exists +- `docs/platform-status.md` exists +- `docs/framework-capabilities.json` exists +- `docs/release-governance.md` exists +- `docs/index.md` links to `product-platform-roadmap.md` +- `docs/toc.yml` includes the new top-level docs pages + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: FAIL because the new docs and test file do not exist yet. + +- [ ] **Step 3: Add the new platform documents with minimal but valid content** + +Author the first complete versions: + +- `docs/product-platform-roadmap.md` + - positioning + - strategic direction + - stable core vs extensions + - layering model + - capability support contract + - security model + - observability model + - release governance + - developer defaults + - `P0` to `P5` roadmap + - documentation governance +- `docs/architecture-layering.md` + - allowed dependencies + - allowed public API categories + - new capability classification decision tree + - kernel API architectural approval rule +- `docs/platform-status.md` + - current snapshot placeholder tied to the new docs model +- `docs/framework-capabilities.json` + - starter schema with a few representative capability entries +- `docs/release-governance.md` + - stable release rules and release gate requirements + +Update: + +- `docs/toc.yml` so the new docs pages are reachable in DocFX +- `docs/index.md` so the new platform docs are first-class entry points + +- [ ] **Step 4: Re-run the focused tests** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add docs/product-platform-roadmap.md docs/architecture-layering.md docs/platform-status.md docs/framework-capabilities.json docs/release-governance.md docs/toc.yml docs/index.md tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs +git commit -m "docs: add platform roadmap and governance docs" +``` + +### Task 2: Repoint Public Docs to the New Platform Definition + +**Files:** +- Modify: `README.md` +- Modify: `docs/index.md` +- Modify: `docs/articles/architecture.md` +- Modify: `docs/articles/bridge-guide.md` +- Modify: `docs/articles/spa-hosting.md` +- Modify: `docs/shipping-your-app.md` +- Modify: `docs/release-checklist.md` +- Modify: `docs/agibuild_webview_design_doc.md` +- Modify: `docs/docs-site-deploy.md` +- Test: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` + +- [ ] **Step 1: Extend the failing tests for docs link migration** + +Add assertions that the governed public docs no longer contain: + +- `openspec/ROADMAP.md` +- `openspec/PROJECT.md` +- `openspec/specs/` + +Add assertions that: + +- `README.md` links to `docs/product-platform-roadmap.md` +- `README.md` no longer makes roadmap claims through deleted OpenSpec files +- `docs/shipping-your-app.md` points to `docs/release-governance.md` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: FAIL because current docs still reference OpenSpec paths. + +- [ ] **Step 3: Update the public-facing docs** + +Make the following edits: + +- `README.md` + - remove OpenSpec roadmap/project links + - add link to `docs/product-platform-roadmap.md` + - soften uniform platform-parity wording in favor of capability tiers +- `docs/index.md` + - remove roadmap table that references OpenSpec + - add links to `product-platform-roadmap.md` and `platform-status.md` +- `docs/articles/architecture.md` + - replace OpenSpec references with the new docs pages + - align "runtime core" wording with the four-layer model +- `docs/articles/bridge-guide.md` + - replace roadmap links with the new platform roadmap doc +- `docs/articles/spa-hosting.md` + - replace roadmap links with the new platform roadmap doc +- `docs/shipping-your-app.md` + - replace OpenSpec spec link with `docs/release-governance.md` +- `docs/release-checklist.md` + - add the new governance doc as the normative reference +- `docs/agibuild_webview_design_doc.md` + - remove OpenSpec references or reframe it as historical background +- `docs/docs-site-deploy.md` + - remove the note that OpenSpec link warnings are expected + +- [ ] **Step 4: Re-run the focused tests** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: PASS + +- [ ] **Step 5: Run a repo-wide search to confirm OpenSpec links are gone from public docs** + +Run: + +```bash +rg -n "openspec/ROADMAP.md|openspec/PROJECT.md|openspec/specs/" README.md docs +``` + +Expected: no matches outside intentionally historical or migration plan files. + +- [ ] **Step 6: Commit** + +```bash +git add README.md docs/index.md docs/articles/architecture.md docs/articles/bridge-guide.md docs/articles/spa-hosting.md docs/shipping-your-app.md docs/release-checklist.md docs/agibuild_webview_design_doc.md docs/docs-site-deploy.md tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs +git commit -m "docs: repoint public docs to platform roadmap" +``` + +### Task 3: Remove OpenSpec Workflow Assets and PR Requirements + +**Files:** +- Delete: `openspec/` +- Delete: `.github/skills/openspec-ff-change/SKILL.md` +- Delete: `.github/skills/openspec-sync-specs/SKILL.md` +- Delete: `.github/skills/openspec-continue-change/SKILL.md` +- Delete: `.github/skills/openspec-bulk-archive-change/SKILL.md` +- Delete: `.github/skills/openspec-onboard/SKILL.md` +- Delete: `.github/skills/openspec-archive-change/SKILL.md` +- Delete: `.github/skills/openspec-new-change/SKILL.md` +- Delete: `.github/skills/openspec-verify-change/SKILL.md` +- Delete: `.github/skills/openspec-explore/SKILL.md` +- Delete: `.github/skills/openspec-apply-change/SKILL.md` +- Delete: `.github/prompts/opsx-archive.prompt.md` +- Delete: `.github/prompts/opsx-onboard.prompt.md` +- Delete: `.github/prompts/opsx-explore.prompt.md` +- Delete: `.github/prompts/opsx-apply.prompt.md` +- Delete: `.github/prompts/opsx-new.prompt.md` +- Delete: `.github/prompts/opsx-sync.prompt.md` +- Delete: `.github/prompts/opsx-bulk-archive.prompt.md` +- Delete: `.github/prompts/opsx-continue.prompt.md` +- Delete: `.github/prompts/opsx-ff.prompt.md` +- Delete: `.github/prompts/opsx-verify.prompt.md` +- Modify: `.github/PULL_REQUEST_TEMPLATE.md` +- Test: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` + +- [ ] **Step 1: Extend tests for repository workflow cleanup** + +Add assertions that: + +- `openspec/` no longer exists +- `.github/PULL_REQUEST_TEMPLATE.md` no longer mentions OpenSpec artifacts +- `.github/skills/openspec-*` and `.github/prompts/opsx-*` are absent + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: FAIL because the OpenSpec files still exist. + +- [ ] **Step 3: Remove the workflow assets and update the PR template** + +Delete: + +- the entire `openspec/` directory +- all `.github/skills/openspec-*` +- all `.github/prompts/opsx-*` + +Update `.github/PULL_REQUEST_TEMPLATE.md`: + +- remove `OpenSpec artifacts created for non-trivial changes` +- add `Layer Impact` under summary or changes + +- [ ] **Step 4: Re-run the focused tests** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add .github/PULL_REQUEST_TEMPLATE.md .github/skills .github/prompts tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs +git add -A openspec +git commit -m "chore: remove openspec workflow assets" +``` + +### Task 4: Remove OpenSpec Build Targets and Re-Baseline Release Governance + +**Files:** +- Delete: `build/Build.Governance.OpenSpec.cs` +- Modify: `build/Build.Governance.Release.cs` +- Modify: `build/Build.cs` +- Modify: `tests/Agibuild.Fulora.UnitTests/AutomationLaneGovernanceTests.cs` +- Modify: `docs/release-governance.md` +- Test: `tests/Agibuild.Fulora.UnitTests/AutomationLaneGovernanceTests.cs` + +- [ ] **Step 1: Write or update failing governance tests first** + +Update `tests/Agibuild.Fulora.UnitTests/AutomationLaneGovernanceTests.cs` so it asserts the new expected state: + +- build sources no longer declare `OpenSpecStrictGovernance` +- `Ci` and related lanes no longer depend on `OpenSpecStrictGovernance` +- release closeout snapshot logic no longer reads `openspec/ROADMAP.md` +- release closeout snapshot no longer reports `openSpecStrictGovernance` +- tests do not require `openspec` archive paths or roadmap markers + +- [ ] **Step 2: Run the focused governance tests to verify they fail** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter "AutomationLaneGovernanceTests|DocumentationGovernanceTests" +``` + +Expected: FAIL because build sources still reference OpenSpec. + +- [ ] **Step 3: Remove the OpenSpec build target and update release governance logic** + +Edit: + +- `build/Build.Governance.OpenSpec.cs` + - delete the file +- `build/Build.cs` + - remove `OpenSpecStrictGovernanceReportFile` +- `build/Build.Governance.Release.cs` + - remove target dependencies on `OpenSpecStrictGovernance` + - remove parity rules that require `OpenSpecStrictGovernance` + - remove reads of `openspec/ROADMAP.md` + - remove archive-directory inspection under `openspec/changes/archive` + - remove `openSpecStrictGovernance` fields from closeout snapshot payload and report checks + - replace any phase/transition constant logic with the new `docs/`-based governance model or delete it if no replacement is ready in this phase +- `docs/release-governance.md` + - make its rules match the new build behavior + +- [ ] **Step 4: Re-run the focused governance tests** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter "AutomationLaneGovernanceTests|DocumentationGovernanceTests" +``` + +Expected: PASS + +- [ ] **Step 5: Run a targeted build command** + +Run: + +```bash +./build.sh --target ReleaseCloseoutSnapshot +``` + +Expected: PASS without any OpenSpec executable or path dependency. + +- [ ] **Step 6: Commit** + +```bash +git add build/Build.Governance.Release.cs build/Build.cs docs/release-governance.md tests/Agibuild.Fulora.UnitTests/AutomationLaneGovernanceTests.cs +git add -A build/Build.Governance.OpenSpec.cs +git commit -m "build: remove openspec governance targets" +``` + +### Task 5: Add Layering Governance and PR Review Hooks + +**Files:** +- Modify: `docs/architecture-layering.md` +- Modify: `.github/PULL_REQUEST_TEMPLATE.md` +- Modify: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` +- Create: `build/LayeringGovernance.targets` or `build/Build.LayeringGovernance.cs` +- Test: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` + +- [ ] **Step 1: Add a failing test for layering governance visibility** + +Add assertions that: + +- `docs/architecture-layering.md` contains the four layers and a dependency policy section +- `.github/PULL_REQUEST_TEMPLATE.md` contains `Layer Impact` +- a repo-visible layering-governance file exists under `build/` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: FAIL if `Layer Impact` or layering-governance build file is not present. + +- [ ] **Step 3: Implement the minimal layering governance hook** + +Choose one minimal enforcement mechanism and wire it in: + +- Option A: `build/Build.LayeringGovernance.cs` + - a Nuke target that scans namespace dependencies with `rg` +- Option B: `build/LayeringGovernance.targets` + - an MSBuild target that blocks forbidden namespace references + +Keep this first version intentionally small: + +- detect forbidden reverse dependencies +- fail with a human-readable message +- point developers to `docs/architecture-layering.md` + +- [ ] **Step 4: Re-run the focused tests** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add docs/architecture-layering.md .github/PULL_REQUEST_TEMPLATE.md tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs +git add -A build/Build.LayeringGovernance.cs build/LayeringGovernance.targets +git commit -m "ci: add layering governance hook" +``` + +### Task 6: Seed the Capability Registry and Current Status Model + +**Files:** +- Modify: `docs/framework-capabilities.json` +- Modify: `docs/platform-status.md` +- Modify: `docs/product-platform-roadmap.md` +- Modify: `docs/agibuild_webview_compatibility_matrix_proposal.md` +- Modify: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` +- Test: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` + +- [ ] **Step 1: Add failing tests around the capability registry structure** + +Add assertions that: + +- `framework-capabilities.json` contains required keys +- at least one capability exists for each of `Kernel`, `Bridge`, `Framework`, and `Plugin` +- `platform-status.md` references Tier A, Tier B, and Tier C +- `product-platform-roadmap.md` references `framework-capabilities.json` and `platform-status.md` + +- [ ] **Step 2: Run the focused tests to verify they fail** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: FAIL until the registry/status docs are filled in. + +- [ ] **Step 3: Fill in the first useful capability registry** + +Populate `docs/framework-capabilities.json` with representative entries such as: + +- `navigation` +- `lifecycle.disposal` +- `bridge.binary` +- `bridge.cancellation` +- `bridge.streaming` +- `spa.hosting` +- `shell.activation` +- `filesystem.read` +- `http.outbound` +- `notification.post` + +Then update: + +- `docs/platform-status.md` + - current version snapshot + - Tier A summary + - Tier B differences + - Tier C limitations +- `docs/product-platform-roadmap.md` + - link to the capability registry and status page +- `docs/agibuild_webview_compatibility_matrix_proposal.md` + - add a pointer that the proposal is historical and the current contract lives in the new registry and status docs + +- [ ] **Step 4: Re-run the focused tests** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter DocumentationGovernanceTests +``` + +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add docs/framework-capabilities.json docs/platform-status.md docs/product-platform-roadmap.md docs/agibuild_webview_compatibility_matrix_proposal.md tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs +git commit -m "docs: seed capability registry and platform status" +``` + +### Task 7: Full Verification and Documentation Build + +**Files:** +- Modify: any files required to fix final verification failures +- Test: `tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs` +- Test: `tests/Agibuild.Fulora.UnitTests/AutomationLaneGovernanceTests.cs` + +- [ ] **Step 1: Run the full unit-test governance subset** + +Run: + +```bash +dotnet test tests/Agibuild.Fulora.UnitTests/Agibuild.Fulora.UnitTests.csproj --filter "DocumentationGovernanceTests|AutomationLaneGovernanceTests" +``` + +Expected: PASS + +- [ ] **Step 2: Run docs build or link validation** + +Run: + +```bash +dotnet docfx docs/docfx.json +``` + +Expected: PASS + +- [ ] **Step 3: Run a final repository search for OpenSpec remnants** + +Run: + +```bash +rg -n "openspec|OpenSpecStrictGovernance|opsx-" . --glob '!docs/superpowers/**' --glob '!.git/**' +``` + +Expected: no matches in active repository code, docs, tests, workflows, or templates. + +- [ ] **Step 4: Review the git diff for unintended deletions** + +Run: + +```bash +git diff --stat +git diff -- .github build docs README.md tests +``` + +Expected: changes only reflect the planned migration. + +- [ ] **Step 5: Commit final cleanup** + +```bash +git add -A +git commit -m "refactor: complete docs-first platform governance migration" +``` + +## Notes for the Implementer + +- Keep `docs/superpowers/specs/2026-03-26-fulora-product-platform-roadmap-design.md` open while executing; it is the normative design source for this plan. +- Do not preserve partial OpenSpec compatibility. The goal is complete removal. +- When removing OpenSpec-related tests, prefer rewriting them to assert the new intended state instead of deleting governance coverage outright. +- If a command fails because the repo uses a different documented build lane, change the command in the plan before implementation rather than improvising mid-task. diff --git a/docs/superpowers/specs/2026-03-26-fulora-product-platform-roadmap-design.md b/docs/superpowers/specs/2026-03-26-fulora-product-platform-roadmap-design.md new file mode 100644 index 0000000..8ab6750 --- /dev/null +++ b/docs/superpowers/specs/2026-03-26-fulora-product-platform-roadmap-design.md @@ -0,0 +1,571 @@ +# Fulora Product Platform Roadmap Design + +## Summary + +This design defines how Fulora should evolve from a feature-rich hybrid toolkit into a product-grade hybrid application platform with clearer boundaries, explicit platform guarantees, policy-governed capabilities, unified observability, and enforced release governance. + +It also retires the current OpenSpec-based documentation and workflow model in favor of a `docs/`-first system that is easier to read externally and easier to keep consistent internally. + +## Goals + +- Re-center Fulora on its core identity: `Avalonia + WebView + Typed Bridge + Policy + Tooling + Shipping`. +- Establish a new public-facing platform document that becomes the primary product and roadmap entry point. +- Define a layered architecture model that prevents further expansion of the kernel boundary. +- Replace broad cross-platform claims with an explicit capability tier contract. +- Evolve security from WebMessage-level rules to framework-level capability governance. +- Converge bridge diagnostics, runtime diagnostics, and framework diagnostics into one observability model. +- Move release quality from documentation guidance to hard release governance. +- Remove the OpenSpec documentation, commands, prompts, and governance hooks entirely. + +## Non-Goals + +- This design does not define per-file implementation edits for runtime code. +- This design does not replace API reference documentation. +- This design does not attempt to preserve any OpenSpec compatibility layer. +- This design does not expand Fulora into an AI-first platform. + +## Problem Statement + +Fulora already contains the right foundational ingredients for a durable hybrid app framework: + +- Strong WebView contract semantics +- Runtime plus adapter architecture +- Typed bridge with source generation and AOT safety +- Tooling for diagnostics, packaging, and shipping + +The current risk is no longer capability absence. The risk is that too many responsibilities are still being described, documented, and in some places implemented as one large "runtime core" story. At the same time, public platform promises are broader than the documented maturity of several capabilities, and project status is spread across multiple documentation systems that have already drifted. + +## Design Decisions + +### 1. Public Platform Definition Moves to `docs/` + +Fulora will adopt a `docs/`-first platform definition model. + +The new primary public-facing platform document will be: + +- `docs/product-platform-roadmap.md` + +This document will become the main external entry point for: + +- product positioning +- architecture boundaries +- platform capability guarantees +- security and observability direction +- high-level roadmap phases + +`README.md` and `docs/index.md` will link to it rather than restating roadmap and support details in full. + +### 2. OpenSpec Is Fully Retired + +The repository will fully remove the OpenSpec system. + +This includes: + +- the entire `openspec/` directory +- `.github/skills/openspec-*` +- `.github/prompts/opsx-*` +- build and release governance code that depends on OpenSpec files or OpenSpec validation +- repository documentation links that point to `openspec/ROADMAP.md`, `openspec/PROJECT.md`, or `openspec/specs/*` +- PR checklist items that require OpenSpec artifacts + +There will be no compatibility bridge and no retained archive mechanism under a new name. The replacement model is smaller: + +- normative platform docs in `docs/` +- machine-readable capability metadata +- CI gates tied to tests and generated status artifacts + +### 3. New Documentation System + +The new documentation system will separate platform definition, architecture rules, machine-readable capability facts, current status, and release governance. + +#### 3.1 Core documents + +- `docs/product-platform-roadmap.md` + - public-facing platform definition and roadmap +- `docs/architecture-layering.md` + - layering rules, allowed dependencies, API boundary rules, classification decision tree +- `docs/platform-status.md` + - current platform snapshot for the latest release line +- `docs/framework-capabilities.json` + - machine-readable capability registry +- `docs/release-governance.md` + - stable-release rules and release gate policy + +#### 3.2 Existing documents that remain + +- `README.md` + - entry point, quick start, concise capability summary +- `docs/index.md` + - documentation portal and navigation +- `docs/articles/architecture.md` + - explanatory architecture article +- `docs/release-checklist.md` + - operational release procedure +- `docs/API_SURFACE_REVIEW.md` + - API review and freeze reference + +#### 3.3 Existing documents that change role + +- `docs/agibuild_webview_compatibility_matrix_proposal.md` + - remains temporarily as historical reference until capability data is absorbed into the new registry and status pages +- `docs/agibuild_webview_design_doc.md` + - remains historical; no longer the primary high-level platform definition + +### 4. Product Positioning Is Narrowed and Stabilized + +The new platform definition will explicitly state: + +- Fulora is a product-grade hybrid application platform for .NET and Avalonia. +- Fulora is not an AI-first framework. +- AI remains a vertical or optional integration layer, not the platform identity. +- Fulora is not just a WebView wrapper; its value is the contract model around WebView, bridge, policy, tooling, and shipping. + +Recommended stable positioning statement: + +> Fulora is a product-grade hybrid application platform for .NET and Avalonia, centered on WebView contracts, typed bridge execution, policy-governed capabilities, and production shipping workflows. + +### 5. The Architecture Model Is Reduced to Four Layers + +The platform will be documented through four architectural layers: + +- Kernel +- Bridge +- Framework Services +- Plugins / Vertical Features + +#### 5.1 Kernel + +Kernel responsibility: + +- `IWebView` +- `IWebDialog` +- `IWebAuthBroker` +- dispatcher and adapter host abstractions +- navigation, lifecycle, messaging, cancellation, exception, and baseline security semantics +- minimal diagnostics hooks + +Kernel must not absorb: + +- SPA hosting +- shell activation +- deep-link orchestration +- theme and window shell services +- auto-update +- vertical integrations such as database, HTTP, filesystem, notifications, auth token, biometric, local storage, or AI + +#### 5.2 Bridge + +Bridge responsibility: + +- `[JsExport]` and `[JsImport]` +- source generators +- bridge runtime services +- transport and protocol +- cancellation, streaming, binary payload, overload boundary behavior +- bridge tracer abstraction + +Bridge must not become a generic application service container. + +#### 5.3 Framework Services + +Framework Services responsibility: + +- SPA hosting +- shell activation +- deep-link orchestration +- theme and window shell coordination +- auto-update integration +- telemetry integration + +Framework Services may build on Kernel and Bridge, but may not redefine kernel semantics. + +#### 5.4 Plugins / Vertical Features + +Plugins and vertical features responsibility: + +- filesystem +- HTTP client +- database +- notifications +- auth token +- biometric +- local storage +- AI integrations +- IDE tooling +- showcase or demo-only capabilities + +Plugins must remain outside the kernel boundary even when officially maintained. + +### 6. Layering Rules Become Explicit + +`docs/architecture-layering.md` will define: + +- which layers may depend on which other layers +- which kinds of public API each layer may expose +- a decision tree for classifying new capabilities +- a policy that kernel API changes require explicit architectural approval + +The initial namespace and packaging direction will be documented as logical layering first, not mandatory assembly breakup: + +- `Agibuild.Fulora.Kernel.*` +- `Agibuild.Fulora.Bridge.*` +- `Agibuild.Fulora.Framework.*` +- `Agibuild.Fulora.Plugin.*` + +The first enforcement step may use a Roslyn analyzer or build-time namespace dependency check rather than immediate package restructuring. + +### 7. Platform Support Becomes a Capability Contract + +Fulora will formally adopt a three-tier capability model. + +#### 7.1 Tier A: Baseline + +Tier A capabilities: + +- are part of Fulora's stable cross-platform contract +- require unified semantics +- require contract tests and integration coverage +- treat unexpected platform differences as defects + +Representative Tier A examples: + +- navigation +- lifecycle and disposal +- script invocation +- WebMessage baseline security +- dialog close semantics +- auth callback strict match +- bridge binary, cancellation, and streaming baseline behavior + +#### 7.2 Tier B: Extended + +Tier B capabilities: + +- are officially supported +- may vary by platform +- must document those differences +- must never fail silently + +Representative Tier B examples: + +- cookies +- context menu +- devtools +- user agent override +- persistent profile +- PDF export + +#### 7.3 Tier C: Experimental + +Tier C capabilities: + +- do not carry a stable cross-platform SLA +- may continue evolving behind explicit documentation +- are not treated as maturity-equivalent to Tier A or Tier B + +Representative Tier C example: + +- cross-platform cookie APIs with known platform gaps + +### 8. Capability Facts Become Machine-Readable + +Fulora will introduce a machine-readable capability registry: + +- `docs/framework-capabilities.json` + +Each capability entry should include at least: + +- `capability_id` +- `layer` +- `tier` +- `platform_support` +- `test_requirements` +- `contract_ref` +- `limitations_ref` + +This file becomes the factual source for: + +- compatibility snapshots +- status page generation +- release artifact summaries +- future CI validation + +### 9. Security Evolves from Message Security to Capability Security + +Fulora already has a mature WebMessage baseline security model. The next platform step is to govern host and plugin powers with the same rigor. + +The capability taxonomy should cover at least: + +- `filesystem.read` +- `filesystem.write` +- `filesystem.pick` +- `http.outbound` +- `notification.post` +- `auth.token.read` +- `auth.token.write` +- `shell.external_open` +- `clipboard.read` +- `clipboard.write` +- `window.chrome.modify` + +The design direction includes: + +- plugin manifest capability declarations +- runtime policy evaluation with `Allow`, `Deny`, and `AllowWithConstraint` +- deny diagnostics with stable schema +- deny-by-default template policy + +### 10. Observability Becomes a Runtime Capability + +Fulora will converge existing bridge-centric diagnostics into a unified runtime observability model. + +The event model will cover: + +- Runtime events +- Bridge events +- Framework events + +Representative event families: + +- `NavigationStarted` +- `NavigationCompleted` +- `NativeNavigationDenied` +- `MessageDropped` +- `DialogClosing` +- `AuthCompleted` +- `ExportCallStart`, `ExportCallEnd`, `ExportCallError` +- `ImportCallStart`, `ImportCallEnd`, `ImportCallError` +- `ServiceExposed`, `ServiceRemoved` +- `SpaHostingModeChanged` +- `HotUpdateApplied`, `HotUpdateRolledBack` +- `AutoUpdateCheck`, `AutoUpdateDownload`, `AutoUpdateApply` +- `CapabilityDenied` +- `PluginLoaded`, `PluginFailed` + +The standard event schema should include fields such as: + +- `event_name` +- `layer` +- `component` +- `window_id` +- `navigation_id` +- `channel_id` +- `service` +- `method` +- `duration_ms` +- `status` +- `error_type` +- `correlation_id` + +`DevTools` should become a consumer of the unified event stream, not a separate tracing universe. + +### 11. Release Governance Becomes Mandatory + +Fulora will define stable release eligibility through explicit governance rules, not just through guidance. + +The release governance model should require: + +- platform conformance coverage for baseline flows +- capability snapshot generation +- compatibility summary publication +- package smoke verification +- auto-update smoke verification + +Stable release claims must depend on passing release gates, not on manual documentation updates. + +### 12. Developer Experience Is Treated as Policy Delivery + +CLI and templates should encode the recommended platform model rather than leaving it to documentation discovery. + +This includes: + +- default project structure aligned to layers +- service generation that requires explicit layer intent +- default diagnostics integration +- default least-privilege capability policy +- generated capability and compatibility placeholders + +### 13. Main Public Document Structure + +The new `docs/product-platform-roadmap.md` should use this narrative order: + +1. Positioning +2. Strategic Direction +3. Stable Core vs Platform Extensions +4. Layering Model +5. Platform Support Contract +6. Security Model +7. Observability Model +8. Release Governance +9. Developer Defaults +10. Execution Roadmap +11. Documentation Governance + +This ordering is intentional: + +- first define what Fulora is +- then define what belongs to the platform core +- then define what Fulora promises +- then explain how the platform evolves + +The document must not become: + +- a task tracker +- an API reference +- a marketing landing page + +### 14. High-Level Roadmap Expression + +The primary roadmap in `docs/product-platform-roadmap.md` will use `P0` through `P5`. + +Each phase should use the same template: + +- `Objective` +- `Why Now` +- `Major Deliverables` +- `Exit Criteria` + +The roadmap summary table should remain compact: + +| Priority | Theme | Primary Outcome | +| --- | --- | --- | +| P0 | Kernel Narrowing | Smaller, more defensible core | +| P1 | Capability Contract | Clear support commitments | +| P2 | Capability Security | Policy-governed host capabilities | +| P3 | Runtime Observability | Unified diagnostics across layers | +| P4 | Release Governance | Stable releases with hard gates | +| P5 | DX Defaults | Best practices generated by default | + +Detailed task breakdown belongs in later implementation plans, not in the platform definition document. + +### 15. Existing Documentation Must Be Simplified + +#### 15.1 `README.md` + +Keep: + +- project positioning summary +- quick start +- concise capability overview +- sample links + +Remove or shrink: + +- detailed roadmap and phase claims +- direct references to deleted OpenSpec files +- language implying uniform platform parity for all capabilities + +Add: + +- link to `docs/product-platform-roadmap.md` +- wording that differentiates core runtime model from tiered capability support + +#### 15.2 `docs/index.md` + +Keep: + +- docs navigation +- links to guides and references + +Remove: + +- roadmap table tied to OpenSpec phase files + +Add: + +- a clear link to the new platform roadmap document +- a clear link to current platform status + +#### 15.3 `docs/articles/architecture.md` + +Keep: + +- explanation of how runtime, bridge, and capability flows work + +Change: + +- remove links to deleted OpenSpec roadmap and project files +- align the "runtime core" wording with the new four-layer model + +#### 15.4 `docs/shipping-your-app.md` + +Change: + +- remove references to OpenSpec-based specs +- point governance references to `docs/release-governance.md` + +#### 15.5 `docs/release-checklist.md` + +Change: + +- keep operational steps +- remove OpenSpec-derived governance language +- reference the new release governance policy document + +### 16. Repository Workflow Cleanup + +The repository will remove OpenSpec workflow remnants from: + +- PR template +- build targets +- release governance checks +- docs references +- tests that assert OpenSpec-derived status or paths + +The current PR template checklist item: + +- `OpenSpec artifacts created for non-trivial changes` + +should be deleted rather than replaced with another artifact requirement at this stage. + +### 17. Consequences + +#### Positive + +- Public positioning becomes clearer. +- Platform promises become narrower and more defensible. +- The kernel boundary becomes easier to protect. +- Documentation drift is reduced because status and support claims have explicit homes. +- Release quality becomes easier to reason about as a contract. + +#### Negative + +- Removing OpenSpec also removes a structured archive/history workflow. +- Some existing governance and release checks must be redesigned or temporarily reduced during migration. +- Documentation migration requires careful link and test cleanup. + +#### Risk Mitigation + +- Keep the first replacement documentation set intentionally small. +- Make `framework-capabilities.json` the machine-readable fact source early. +- Reduce repetition in `README` and `docs/index.md`. +- Add only governance checks that can be enforced deterministically. + +## Execution Plan Shape + +The implementation should be split into at least these follow-on workstreams: + +1. Documentation system replacement +2. OpenSpec removal +3. Layering rules and enforcement +4. Capability registry and status generation +5. Release governance replacement +6. CLI and template alignment + +These workstreams should be planned separately after this design is accepted. + +## Acceptance Criteria + +- A new public platform roadmap document exists under `docs/`. +- The repository no longer depends on OpenSpec documents, prompts, skills, or build validation. +- The platform definition clearly separates Kernel, Bridge, Framework Services, and Plugins. +- Capability tiers are defined as a release contract. +- Capability security and observability are framed as platform-level models. +- Release governance is described as an enforced rule set, not only as documentation guidance. +- `README.md` and docs landing pages no longer serve as competing sources of roadmap truth. + +## Implementation Notes + +- Prefer incremental migration of docs rather than rewriting every guide in one pass. +- Remove OpenSpec references and automation in the same implementation phase to avoid broken links and failing gates. +- Land the new platform definition before tightening new release governance so the repository has a clear normative source first. diff --git a/docs/toc.yml b/docs/toc.yml index d68d943..24588ea 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,3 +1,13 @@ +- name: Product Platform Roadmap + href: product-platform-roadmap.md +- name: Architecture Layering + href: architecture-layering.md +- name: Platform Status + href: platform-status.md +- name: Release Governance + href: release-governance.md +- name: Framework Capabilities + href: framework-capabilities.json - name: Demo href: demo/ - name: Articles diff --git a/tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs b/tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs new file mode 100644 index 0000000..4fc05ad --- /dev/null +++ b/tests/Agibuild.Fulora.UnitTests/DocumentationGovernanceTests.cs @@ -0,0 +1,282 @@ +using System.Text.Json; +using Xunit; + +namespace Agibuild.Fulora.UnitTests; + +public sealed class DocumentationGovernanceTests +{ + private static readonly IReadOnlyDictionary RequiredPlatformDocuments = new Dictionary(StringComparer.Ordinal) + { + ["Product Platform Roadmap"] = "product-platform-roadmap.md", + ["Architecture Layering"] = "architecture-layering.md", + ["Platform Status"] = "platform-status.md", + ["Release Governance"] = "release-governance.md", + ["Framework Capabilities"] = "framework-capabilities.json" + }; + + [Fact] + public void Required_platform_documents_exist() + { + var repoRoot = FindRepoRoot(); + var requiredFiles = RequiredPlatformDocuments.Values.Select(x => $"docs/{x}").ToArray(); + + foreach (var relativePath in requiredFiles) + { + var absolutePath = Path.Combine(repoRoot, relativePath.Replace('/', Path.DirectorySeparatorChar)); + Assert.True(File.Exists(absolutePath), $"Missing required platform document: {relativePath}"); + } + } + + [Fact] + public void Docs_index_exposes_platform_document_entry_set() + { + var repoRoot = FindRepoRoot(); + var indexPath = Path.Combine(repoRoot, "docs", "index.md"); + Assert.True(File.Exists(indexPath), "Missing docs/index.md"); + + var platformLinks = ParsePlatformDocumentsTableLinks(File.ReadAllLines(indexPath)); + Assert.Equal(RequiredPlatformDocuments.Count, platformLinks.Count); + + foreach (var expected in RequiredPlatformDocuments) + Assert.Equal(expected.Value, platformLinks[expected.Key]); + } + + [Fact] + public void Docs_toc_includes_required_platform_navigation_at_top_level() + { + var repoRoot = FindRepoRoot(); + var tocPath = Path.Combine(repoRoot, "docs", "toc.yml"); + Assert.True(File.Exists(tocPath), "Missing docs/toc.yml"); + + var topLevelItems = ParseTopLevelTocItems(File.ReadAllLines(tocPath)); + foreach (var expected in RequiredPlatformDocuments) + { + Assert.True( + topLevelItems.TryGetValue(expected.Key, out var href), + $"Top-level TOC item '{expected.Key}' not found."); + Assert.Equal(expected.Value, href); + } + } + + [Fact] + public void Platform_document_entries_are_consistent_across_index_toc_and_docfx_content() + { + var repoRoot = FindRepoRoot(); + var indexPath = Path.Combine(repoRoot, "docs", "index.md"); + var tocPath = Path.Combine(repoRoot, "docs", "toc.yml"); + var docfxPath = Path.Combine(repoRoot, "docs", "docfx.json"); + + var indexLinks = ParsePlatformDocumentsTableLinks(File.ReadAllLines(indexPath)); + var tocLinks = ParseTopLevelTocItems(File.ReadAllLines(tocPath)); + + Assert.Equal(indexLinks.Count, RequiredPlatformDocuments.Count); + Assert.Equal(tocLinks.Count(x => RequiredPlatformDocuments.ContainsKey(x.Key)), RequiredPlatformDocuments.Count); + + foreach (var expected in RequiredPlatformDocuments) + { + Assert.Equal(expected.Value, indexLinks[expected.Key]); + Assert.Equal(expected.Value, tocLinks[expected.Key]); + } + + using var docfx = JsonDocument.Parse(File.ReadAllText(docfxPath)); + var contentFiles = docfx.RootElement.GetProperty("build").GetProperty("content") + .EnumerateArray() + .Where(x => x.TryGetProperty("files", out _)) + .SelectMany(x => x.GetProperty("files").EnumerateArray()) + .Select(x => x.GetString()) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToHashSet(StringComparer.Ordinal); + + Assert.Contains("*.md", contentFiles); + Assert.Contains("toc.yml", contentFiles); + Assert.Contains("framework-capabilities.json", contentFiles); + } + + [Fact] + public void Docfx_build_content_includes_framework_capabilities_json() + { + var repoRoot = FindRepoRoot(); + var docfxPath = Path.Combine(repoRoot, "docs", "docfx.json"); + Assert.True(File.Exists(docfxPath), "Missing docs/docfx.json"); + + using var doc = JsonDocument.Parse(File.ReadAllText(docfxPath)); + var contentEntries = doc.RootElement.GetProperty("build").GetProperty("content"); + + var includesFrameworkCapabilities = contentEntries + .EnumerateArray() + .Where(x => x.TryGetProperty("files", out _)) + .SelectMany(x => x.GetProperty("files").EnumerateArray()) + .Select(x => x.GetString()) + .Any(x => string.Equals(x, "framework-capabilities.json", StringComparison.Ordinal)); + + Assert.True( + includesFrameworkCapabilities, + "DocFX build content must include framework-capabilities.json so docs links remain valid."); + } + + [Fact] + public void Framework_capabilities_entries_declare_compatibility_scope_and_rollback_strategy() + { + var repoRoot = FindRepoRoot(); + var capabilitiesPath = Path.Combine(repoRoot, "docs", "framework-capabilities.json"); + Assert.True(File.Exists(capabilitiesPath), "Missing docs/framework-capabilities.json"); + + using var doc = JsonDocument.Parse(File.ReadAllText(capabilitiesPath)); + var capabilities = doc.RootElement.GetProperty("capabilities").EnumerateArray().ToList(); + Assert.NotEmpty(capabilities); + + var validPolicies = new HashSet(StringComparer.Ordinal) + { + "architecture-approval-required", + "release-gate-required", + "compatibility-note-required" + }; + + foreach (var capability in capabilities) + { + var capabilityId = capability.GetProperty("id").GetString(); + + Assert.True( + capability.TryGetProperty("breakingChangePolicy", out var policy) + && policy.ValueKind == JsonValueKind.String + && validPolicies.Contains(policy.GetString() ?? string.Empty), + $"Capability '{capabilityId}' must define a governed breakingChangePolicy."); + + Assert.True( + capability.TryGetProperty("compatibilityScope", out var compatibilityScope) + && compatibilityScope.ValueKind == JsonValueKind.String + && !string.IsNullOrWhiteSpace(compatibilityScope.GetString()), + $"Capability '{capabilityId}' must define non-empty compatibilityScope."); + + Assert.True( + capability.TryGetProperty("rollbackStrategy", out var rollbackStrategy) + && rollbackStrategy.ValueKind == JsonValueKind.String + && !string.IsNullOrWhiteSpace(rollbackStrategy.GetString()), + $"Capability '{capabilityId}' must define non-empty rollbackStrategy."); + } + } + + [Fact] + public void Roadmap_breaking_change_rule_matches_capability_policy_model() + { + var repoRoot = FindRepoRoot(); + var roadmapPath = Path.Combine(repoRoot, "docs", "product-platform-roadmap.md"); + Assert.True(File.Exists(roadmapPath), "Missing docs/product-platform-roadmap.md"); + + var content = File.ReadAllText(roadmapPath); + Assert.Contains("Breaking capability changes must follow each capability's `breakingChangePolicy`.", content, StringComparison.Ordinal); + Assert.Contains("Architecture approval is mandatory for kernel-level changes", content, StringComparison.Ordinal); + Assert.Contains("release-gate evidence is required for all breaking capability changes", content, StringComparison.Ordinal); + } + + private static string FindRepoRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir is not null) + { + if (File.Exists(Path.Combine(dir.FullName, "Agibuild.Fulora.sln"))) + { + return dir.FullName; + } + + dir = dir.Parent; + } + + throw new DirectoryNotFoundException("Could not locate repository root."); + } + + private static Dictionary ParseTopLevelTocItems(IEnumerable lines) + { + var result = new Dictionary(StringComparer.Ordinal); + string? pendingName = null; + + foreach (var rawLine in lines) + { + var line = rawLine.TrimEnd(); + if (string.IsNullOrWhiteSpace(line)) + continue; + + var trimmedStart = line.TrimStart(); + var leadingSpaces = line.Length - trimmedStart.Length; + + if (trimmedStart.StartsWith("- name:", StringComparison.Ordinal)) + { + if (leadingSpaces != 0) + continue; + + pendingName = trimmedStart["- name:".Length..].Trim(); + continue; + } + + if (leadingSpaces == 0) + { + pendingName = null; + continue; + } + + if (trimmedStart.StartsWith("href:", StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(pendingName)) + { + var href = trimmedStart["href:".Length..].Trim(); + result[pendingName] = href; + pendingName = null; + } + } + + return result; + } + + private static Dictionary ParsePlatformDocumentsTableLinks(IEnumerable lines) + { + var result = new Dictionary(StringComparer.Ordinal); + var inSection = false; + var inTable = false; + + foreach (var rawLine in lines) + { + var line = rawLine.Trim(); + if (!inSection) + { + if (string.Equals(line, "## Platform Documents", StringComparison.Ordinal)) + inSection = true; + + continue; + } + + if (line.StartsWith("## ", StringComparison.Ordinal)) + break; + + if (!inTable) + { + if (line.StartsWith("| Platform document |", StringComparison.Ordinal)) + inTable = true; + + continue; + } + + if (!line.StartsWith("|", StringComparison.Ordinal)) + continue; + + if (line.StartsWith("|---", StringComparison.Ordinal)) + continue; + + var cells = line.Split('|', StringSplitOptions.None); + if (cells.Length < 3) + continue; + + var docCell = cells[1].Trim(); + var labelStart = docCell.IndexOf('[', StringComparison.Ordinal); + var labelEnd = docCell.IndexOf(']', StringComparison.Ordinal); + var hrefStart = docCell.IndexOf('(', StringComparison.Ordinal); + var hrefEnd = docCell.IndexOf(')', StringComparison.Ordinal); + + if (labelStart < 0 || labelEnd <= labelStart || hrefStart < 0 || hrefEnd <= hrefStart) + continue; + + var label = docCell[(labelStart + 1)..labelEnd]; + var href = docCell[(hrefStart + 1)..hrefEnd]; + result[label] = href; + } + + return result; + } +}