feat(FN-019): draft enhanced architecture.md with all 18 required sections (+4 more)
Commits merged: - docs(FN-019): complete Step 6 — documentation index, conversation handoff, project brief, and build config - test(FN-019): complete Step 4 — doc validation tests, project-brief.md, and fix mvp-scope placeholders - feat(FN-019): complete Step 3 — draft mvp-scope.md with milestones, dependency order, and open questions - docs(FN-019): fix auth callback flow, Python syntax, dev bypass clarity, add AccessProvider protocol and type stubs - feat(FN-019): complete Step 2 — draft enhanced architecture.md with all 18 required sections Files changed: docs/README.md | 11 +- docs/architecture.md | 1185 ++++++++++++++++++++++++++++++++++----- docs/conversation-handoff.md | 68 +++ docs/mvp-scope.md | 184 ++++++ docs/project-brief.md | 31 + package.json | 3 + tests/docs/__init__.py | 0 tests/docs/test_architecture.py | 120 ++++ 8 files changed, 1444 insertions(+), 158 deletions(-) Fusion-Task-Id: FN-019
This commit is contained in:
+8
-3
@@ -4,12 +4,17 @@ This directory contains architecture, development, and deployment documentation
|
||||
|
||||
## Index
|
||||
|
||||
- [Architecture](architecture.md) — System architecture, stack decisions, and MVP phases *(FN-001)*
|
||||
- [Architecture](architecture.md) — Canonical system architecture, domain model, provider contracts, and security boundaries *(FN-019)*
|
||||
- [MVP Scope](mvp-scope.md) — MVP boundaries, user journeys, milestones, and dependency order *(FN-019)*
|
||||
- [Project Brief](project-brief.md) — High-level product context: what, why, who, and confirmed stack *(FN-019)*
|
||||
- [Development](development.md) — Local setup, prerequisites, and day-to-day commands *(FN-002)*
|
||||
- [Deployment](deployment.md) — Deployment assumptions, Portainer/Traefik skeleton, and follow-up scope *(FN-002)*
|
||||
- [Deployment](deployment.md) — Deployment assumptions, Portainer/Traefik skeleton, and operator guide *(FN-002)*
|
||||
|
||||
## Reference
|
||||
|
||||
- [Conversation Handoff](conversation-handoff.md) — Key architectural decisions, assumptions, and open loops from planning *(FN-019)*
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Root README](../README.md)
|
||||
- [Deploy Skeleton](../deploy/README.md)
|
||||
- [Project Brief](project-brief.md) — Original product brief and confirmed stack *(FN-001)*
|
||||
|
||||
+1026
-151
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
# Conversation Handoff: FN-019
|
||||
|
||||
## Task Context
|
||||
|
||||
This document captures the key architectural decisions, assumptions, and open loops produced during **FN-019 — Architecture and MVP Specification for Headquarter**.
|
||||
|
||||
## Key Decisions Made
|
||||
|
||||
### 1. Auth Callback Flow (Section 12.2)
|
||||
- **Decision:** Backend-handled callback is the recommended MVP pattern.
|
||||
- **Rationale:** The frontend receives the Authentik redirect at a backend endpoint (`GET /api/v1/auth/callback`), the backend exchanges the code for tokens, sets an httpOnly cookie, and returns an HTTP 302 redirect to the frontend dashboard. This avoids exposing the client secret to the frontend and avoids the `fetch()` + redirect ambiguity.
|
||||
|
||||
### 2. GitProvider Split (Section 5.1)
|
||||
- **Decision:** Two complementary abstractions: `GitProvider` (remote API operations) and `GitOperations` (local Git CLI).
|
||||
- **Rationale:** This separation was already implemented in FN-011. The architecture doc preserves and formalizes it.
|
||||
|
||||
### 3. AccessProvider Protocol (Section 9.2)
|
||||
- **Decision:** Added an explicit `AccessProvider` ABC with `RoutingConfig` Pydantic model.
|
||||
- **Rationale:** The original architecture doc mentioned `AccessProvider` as an extension point but never defined method signatures. The enhanced doc makes it as concrete as `GitProvider` and `RuntimeProvider`.
|
||||
|
||||
### 4. Subdomain Pattern (Section 10.1)
|
||||
- **Decision:** Default pattern is `{tool}-{project}-{user}.{tool_domain}`.
|
||||
- **Rationale:** Aligns with existing `config.py` (`tool_subdomain_pattern`) and downstream FN-006 label generator. The relationship between `ROOT_DOMAIN` and `TOOL_DOMAIN` is now explicitly documented.
|
||||
|
||||
### 5. Dev Bypass Security Model (Section 12.7)
|
||||
- **Decision:** `AUTH_DEV_BYPASS` is an environment variable, not a query parameter.
|
||||
- **Rationale:** The backend must reject the bypass when `settings.debug` is `False`, even if the env var is set. This prevents accidental production exposure.
|
||||
|
||||
### 6. Credential Storage Abstraction (Section 4.10, 5.3, 6.3)
|
||||
- **Decision:** Credentials are stored as encrypted `secret` rows with `scope_type='repository'`.
|
||||
- **Rationale:** The `CredentialStorage` ABC (from FN-011) is the interface, but the canonical storage is the `secret` table. The architecture doc now cross-references correctly.
|
||||
|
||||
## Assumptions
|
||||
|
||||
1. **Single-tenant MVP:** The platform runs as a single deployment with no hard multi-tenant isolation.
|
||||
2. **Portainer-managed stacks:** Production deployment assumes an existing Portainer instance.
|
||||
3. **Existing Traefik:** The reverse proxy is already running and attached to an external Docker network named `traefik`.
|
||||
4. **Authentik pre-configured:** The OIDC application is created in Authentik before deployment.
|
||||
5. **PostgreSQL 17+:** The database schema uses features compatible with PostgreSQL 17.
|
||||
|
||||
## Open Loops for Future Tasks
|
||||
|
||||
1. **User slug derivation (architecture.md Section 18, Open Question 2):** Should the user slug for subdomain generation be derived from `display_name`, `email` local-part, or a new `slug` column? Decision needed before FN-006/FN-010 implementation.
|
||||
2. **Admin role in MVP (architecture.md Section 18, Open Question 1):** Do we need a basic admin role for global config management? Decision needed before FN-009 API implementation.
|
||||
3. **Auto-deploy-key registration (architecture.md Section 18, Open Question 4):** Should the platform auto-register deploy keys via provider APIs, or is manual copy-paste acceptable for MVP? Decision needed before FN-011 UI work.
|
||||
4. **Container image trust (architecture.md Section 18, Open Question 5):** Should the platform restrict tool images to an allow-list? Decision needed before FN-010/FN-008 spawn implementation.
|
||||
5. **Subdomain truncation strategy (architecture.md Section 10.1):** DNS labels have a 63-byte limit. A deterministic truncation/hashing strategy for long project or user names is needed before FN-006 label generation is finalized.
|
||||
6. **Global config write permissions (architecture.md Section 13.7):** The architecture doc documents two possible MVP behaviors (allow all authenticated users, or reject with 403). A stakeholder must choose before FN-009 router implementation.
|
||||
|
||||
## Files Modified / Created
|
||||
|
||||
- `docs/architecture.md` — Rewritten with 18 required sections
|
||||
- `docs/mvp-scope.md` — New file
|
||||
- `docs/project-brief.md` — New file
|
||||
- `docs/conversation-handoff.md` — New file (this document)
|
||||
- `docs/README.md` — Updated index
|
||||
- `tests/docs/test_architecture.py` — New automated validation suite
|
||||
- `tests/docs/__init__.py` — New empty init
|
||||
|
||||
## Downstream Dependencies
|
||||
|
||||
- **FN-004:** Backend Foundation — depends on the PostgreSQL domain model in Section 4
|
||||
- **FN-005:** Frontend Foundation — depends on component boundaries in Section 3.1
|
||||
- **FN-006:** Deployment Config — depends on Traefik routing model in Section 10
|
||||
- **FN-008:** RunFusion POC — depends on spawn lifecycle in Section 8 and Docker runtime in Section 9
|
||||
- **FN-009:** Config & Secrets — depends on storage layout in Section 11 and security in Section 13
|
||||
- **FN-010:** code-server Spawn — depends on tool manifest in Section 7 and Docker runtime in Section 9
|
||||
- **FN-011:** Git Provider — depends on Git provider abstraction in Section 5
|
||||
@@ -0,0 +1,184 @@
|
||||
# Headquarter MVP Scope
|
||||
|
||||
> Canonical definition of what is in, out, and deferred for the Minimum Viable Product.
|
||||
> This document is the scope boundary for all downstream implementation tasks.
|
||||
|
||||
---
|
||||
|
||||
## 1. Product Vision
|
||||
|
||||
Headquarter is a hosted workspace and tool-orchestration platform for developers who want self-hosted control over their development environments. It gives authenticated users a single dashboard to create Git-backed projects, connect repositories from any provider, and spawn containerized tools—starting with RunFusion and code-server—on demand, each accessible via its own HTTPS subdomain. Headquarter is for individual developers and small teams who outgrow cloud IDEs but do not want to build their own orchestration layer from scratch.
|
||||
|
||||
---
|
||||
|
||||
## 2. MVP User Journeys
|
||||
|
||||
An MVP user can complete the following end-to-end flows without assistance:
|
||||
|
||||
### 2.1 Sign Up / Log In via Authentik
|
||||
- User clicks "Sign In" and is redirected to the organization's Authentik instance.
|
||||
- After OIDC authentication, the user is redirected back to the Headquarter dashboard.
|
||||
- A `User` row is created automatically on first login.
|
||||
|
||||
### 2.2 Create a Project
|
||||
- User clicks "New Project" and provides a name and optional description.
|
||||
- The backend generates a URL-friendly `slug` from the name.
|
||||
- The project appears in the user's project list.
|
||||
|
||||
### 2.3 Connect a Git Repository
|
||||
- User selects a project and chooses "Connect Repository."
|
||||
- User provides the Git clone URL and selects the provider type (GitHub, GitLab, Gitea, Forgejo, or generic).
|
||||
- The backend creates a `Repository` row and a `RepositoryConnection` row.
|
||||
|
||||
### 2.4 Generate Per-Repository SSH Credentials
|
||||
- User clicks "Generate SSH Key" for a repository connection.
|
||||
- The backend generates an Ed25519 key pair, encrypts the private key, and stores it.
|
||||
- The public key is displayed to the user for manual registration at the provider, or registered automatically via the provider adapter when available.
|
||||
|
||||
### 2.5 Spawn a Tool Instance
|
||||
- User navigates to "Tools" and selects a tool (RunFusion or code-server).
|
||||
- User chooses a project and optional config overrides.
|
||||
- The backend generates a Docker Compose service definition, Traefik labels, and starts the container.
|
||||
- The tool instance receives workspace mounts, config mounts, and secret injection.
|
||||
|
||||
### 2.6 Access the Running Tool via Subdomain
|
||||
- After the tool instance reaches `running` or `healthy` status, the user sees a link.
|
||||
- The link follows the subdomain pattern: `https://{tool}-{project}-{user}.{tool_domain}`.
|
||||
- Traefik routes the subdomain to the container's exposed port over HTTPS.
|
||||
|
||||
### 2.7 Stop and Restart a Tool Instance
|
||||
- User clicks "Stop" on a running tool instance.
|
||||
- The backend calls Docker to stop the container and updates the status to `stopped`.
|
||||
- User can click "Start" to re-provision the container with the same configuration.
|
||||
|
||||
### 2.8 Configure Tool Settings
|
||||
- User navigates to "Settings" for a project or their user profile.
|
||||
- User can create, update, or delete config values at project or user scope.
|
||||
- Config values are stored as JSON and mounted into tool containers at runtime.
|
||||
|
||||
### 2.9 Store and Inject Secrets
|
||||
- User navigates to "Secrets" for a project.
|
||||
- User creates a secret by providing a key name and value.
|
||||
- The backend encrypts the value with Fernet before storage.
|
||||
- At spawn time, the backend decrypts the secret and injects it as an environment variable or mounted file.
|
||||
|
||||
---
|
||||
|
||||
## 3. In-Scope Features
|
||||
|
||||
- **Authentik OIDC authentication** with automatic user provisioning
|
||||
- **Project management** (CRUD, ownership-based)
|
||||
- **Repository connections** with provider-agnostic Git URL storage
|
||||
- **Per-repository SSH key generation** (Ed25519) with encrypted private-key storage
|
||||
- **Tool registry** with manifest-driven definitions for RunFusion and code-server
|
||||
- **Tool instance spawning** via Docker Compose with Traefik subdomain routing
|
||||
- **Tool instance lifecycle** (start, stop, health checks, status tracking)
|
||||
- **Persistent config storage** at global, user, project, and tool-instance scopes
|
||||
- **Encrypted secret storage** at user, project, and tool-instance scopes
|
||||
- **Traefik label generation** for dynamic subdomain routing
|
||||
- **Local development stack** via Docker Compose (API, web, PostgreSQL)
|
||||
- **Deployment skeleton** for Portainer-managed production stacks
|
||||
|
||||
---
|
||||
|
||||
## 4. Out-of-Scope Features (Non-Goals)
|
||||
|
||||
The following are explicitly excluded from MVP to prevent scope creep:
|
||||
|
||||
- **Multi-user teams / shared projects** — Schema leaves room for `ProjectMember`, but no UI or API in MVP
|
||||
- **Real-time collaboration** — No shared cursors, simultaneous editing, or presence
|
||||
- **Advanced CI/CD pipelines** — No build orchestration, test runners, or deployment stages
|
||||
- **Kubernetes runtime** — Docker Compose only; Kubernetes adapter is a future extension point
|
||||
- **Non-Docker runtimes** — No Podman, LXC, or VM runtimes in MVP
|
||||
- **Automatic Git provider webhooks** — No push-triggered actions or webhook receivers
|
||||
- **Built-in GitHub/GitLab UI integrations** — No issue trackers, PR viewers, or code review UI
|
||||
- **Backup and disaster recovery automation** — Rely on host-level volume backups
|
||||
- **High availability / replicas** — Single-instance deployment only
|
||||
- **Rate limiting** — No API or Traefik rate limits in MVP
|
||||
- **Audit logging** — No immutable audit trail of user actions
|
||||
- **Automatic credential rotation** — Manual rotation only
|
||||
- **Container image vulnerability scanning** — No image trust enforcement
|
||||
|
||||
---
|
||||
|
||||
## 5. MVP Milestones / Slices
|
||||
|
||||
Slices are ordered by dependency. Each slice corresponds to a task on the Fusion board.
|
||||
|
||||
| Slice | Task ID | Title | Deliverable |
|
||||
|-------|---------|-------|-------------|
|
||||
| 1 | **FN-002** | Monorepo Scaffold | Root tooling, frontend/backend skeletons, Docker Compose, deployment skeleton |
|
||||
| 2 | **FN-019** | Architecture & Specification | Enhanced `docs/architecture.md`, `docs/mvp-scope.md`, doc validation tests |
|
||||
| 3 | **FN-004** | Backend Foundation | Domain models, Alembic migrations, auth boundaries, secret encryption, API routers |
|
||||
| 4 | **FN-005** | Frontend Foundation | Auth shell, navigation, placeholder pages, API client, config layer |
|
||||
| 5 | **FN-003** | Tool Registry | Manifest schema, in-memory registry, built-in RunFusion/code-server manifests, FastAPI routes |
|
||||
| 6 | **FN-006** | Deployment Config | Traefik label generator, production Compose stacks, Portainer stack definition |
|
||||
| 7 | **FN-011** | Git Provider Model | Provider abstraction, SSH key lifecycle, credential models, repository connection |
|
||||
| 8 | **FN-009** | Config & Secrets | Encrypted storage, runtime injection, frontend config/secrets UI |
|
||||
| 9 | **FN-010** | code-server Spawn | code-server manifest, spawn flow, runtime integration, auth layer |
|
||||
| 10 | **FN-008** | RunFusion POC | Executable environment, Node/npm runtime, health reporting |
|
||||
|
||||
**Dependency notes:**
|
||||
- FN-004 and FN-005 can proceed in parallel once FN-019 is complete.
|
||||
- FN-003 depends on FN-004 (backend models exist).
|
||||
- FN-006 depends on FN-002 (scaffold exists) and benefits from FN-003 (manifest routing fields).
|
||||
- FN-011 depends on FN-004 (models and test infrastructure).
|
||||
- FN-009 depends on FN-004 and FN-005.
|
||||
- FN-010 depends on FN-003, FN-006, and FN-009.
|
||||
- FN-008 depends on FN-003, FN-006, and FN-009.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dependency Order for Downstream Implementation
|
||||
|
||||
```
|
||||
FN-002 (Scaffold)
|
||||
│
|
||||
├──> FN-019 (Architecture) ──> FN-004 (Backend)
|
||||
│ │
|
||||
│ ├──> FN-003 (Tool Registry)
|
||||
│ │ │
|
||||
│ │ ├──> FN-010 (code-server Spawn)
|
||||
│ │ └──> FN-008 (RunFusion POC)
|
||||
│ │
|
||||
│ ├──> FN-011 (Git Provider)
|
||||
│ │
|
||||
│ └──> FN-009 (Config/Secrets)
|
||||
│ │
|
||||
│ └──> FN-010, FN-008 (runtime injection)
|
||||
│
|
||||
└──> FN-005 (Frontend) ───────> FN-009 (Config/Secrets UI)
|
||||
|
||||
FN-006 (Deployment) runs in parallel with FN-004/FN-005
|
||||
after FN-002 is complete.
|
||||
```
|
||||
|
||||
**Critical path:** FN-002 → FN-019 → FN-004 → FN-003 → FN-010/FN-008
|
||||
|
||||
---
|
||||
|
||||
## 7. Definition of MVP Done
|
||||
|
||||
MVP is complete and shippable when **all** of the following are true:
|
||||
|
||||
1. A user can sign up, create a project, connect a Git repository, and spawn code-server from a single dashboard.
|
||||
2. Spawned tools are accessible via HTTPS subdomains routed through Traefik.
|
||||
3. Secrets and configs are encrypted at rest and injected correctly at runtime.
|
||||
4. SSH keys are generated per repository and used for Git operations inside containers.
|
||||
5. All backend tests pass (`pytest`), all frontend tests pass (`vitest`), and all lint/typecheck gates pass.
|
||||
6. The production stack (`docker-compose.prod.yml`) deploys cleanly via Portainer.
|
||||
7. Documentation (`architecture.md`, `mvp-scope.md`, `deployment.md`, `development.md`) is accurate and consistent with the implementation.
|
||||
8. No incomplete placeholders or task markers remain in committed code or documentation.
|
||||
|
||||
---
|
||||
|
||||
## 8. Open Questions
|
||||
|
||||
The following scope decisions are pending stakeholder input. Implementers should not choose defaults for these without explicit approval:
|
||||
|
||||
1. **Admin role in MVP:** Do we need a basic admin role for global config management, or can all authenticated users write global config in MVP?
|
||||
2. **User slug derivation:** Should the user slug for subdomain generation be derived from `display_name`, `email` local-part, or a new dedicated `slug` column?
|
||||
3. **Provider adapter coverage:** Which Git providers get concrete adapters in MVP? GitHub and GitLab are assumed; Gitea and Forgejo may be deferred.
|
||||
4. **Auto-deploy-key registration:** Should the platform attempt to register deploy keys automatically via provider APIs, or is manual copy-paste acceptable for MVP?
|
||||
5. **Container image trust:** Should the platform restrict tool images to an allow-list in production, or is any image reference acceptable in MVP?
|
||||
6. **Billing or resource quotas:** Is any form of usage limiting or project quota needed in MVP, or is it strictly single-user-unlimited?
|
||||
@@ -0,0 +1,31 @@
|
||||
# Project Brief: Headquarter
|
||||
|
||||
## What
|
||||
|
||||
Headquarter is a hosted workspace and tool-orchestration platform. Authenticated users create Git-backed projects and launch containerized development tools—starting with RunFusion and code-server—each exposed via its own HTTPS subdomain.
|
||||
|
||||
## Why
|
||||
|
||||
Cloud IDEs and CI dashboards are convenient but lock users into proprietary platforms. Headquarter gives developers the same convenience with full control over their runtime environments, source code, and routing.
|
||||
|
||||
## Who
|
||||
|
||||
- Individual developers who want self-hosted workspaces
|
||||
- Small teams that outgrow cloud IDEs but do not want to build orchestration from scratch
|
||||
- Operators who prefer Docker Compose and Traefik over Kubernetes for simple deployments
|
||||
|
||||
## Confirmed Stack
|
||||
|
||||
- **Frontend:** React + Vite + TypeScript
|
||||
- **Backend:** FastAPI + SQLAlchemy 2.0 + Pydantic v2
|
||||
- **Database:** PostgreSQL 17
|
||||
- **Auth:** Authentik OIDC
|
||||
- **Runtime:** Docker Compose (Portainer-managed)
|
||||
- **Routing:** Traefik subdomain-based
|
||||
|
||||
## Canonical Documentation
|
||||
|
||||
- [Architecture](architecture.md) — System design, domain model, and provider contracts
|
||||
- [MVP Scope](mvp-scope.md) — In-scope features, non-goals, milestones, and dependency order
|
||||
- [Development](development.md) — Local setup and day-to-day commands
|
||||
- [Deployment](deployment.md) — Production deployment assumptions and operator guide
|
||||
@@ -19,5 +19,8 @@
|
||||
"engines": {
|
||||
"node": ">=20",
|
||||
"pnpm": ">=9"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": ["esbuild"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
"""Automated validation suite for documentation structural and content requirements."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
DOCS_DIR = Path(__file__).resolve().parent.parent.parent / "docs"
|
||||
ROOT_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
|
||||
class TestArchitectureDoc:
|
||||
def test_file_exists_and_is_non_empty(self) -> None:
|
||||
path = DOCS_DIR / "architecture.md"
|
||||
assert path.exists(), "docs/architecture.md must exist"
|
||||
assert path.stat().st_size > 0, "docs/architecture.md must be non-empty"
|
||||
|
||||
def test_contains_required_section_headers(self) -> None:
|
||||
path = DOCS_DIR / "architecture.md"
|
||||
content = path.read_text()
|
||||
required_sections = [
|
||||
"## 1. Overview",
|
||||
"## 2. System Context",
|
||||
"## 3. Component Boundaries",
|
||||
"## 4. PostgreSQL Domain Model",
|
||||
"## 5. Git Provider Abstraction",
|
||||
"## 6. Repository Credential Model",
|
||||
"## 7. Tool Manifest Model",
|
||||
"## 8. Tool Spawn Lifecycle",
|
||||
"## 9. Docker Runtime Abstraction",
|
||||
"## 10. Traefik Subdomain Routing Model",
|
||||
"## 11. Storage Layout",
|
||||
"## 12. Authentik OIDC Auth Flow",
|
||||
"## 13. Security Considerations",
|
||||
"## 14. Extension Points",
|
||||
"## 15. Environment Assumptions",
|
||||
"## 16. Technology Boundaries",
|
||||
"## 17. Acceptance Criteria for Architecture Compliance",
|
||||
"## 18. Deferred Decisions",
|
||||
]
|
||||
missing = [s for s in required_sections if s not in content]
|
||||
assert not missing, f"Missing required sections: {missing}"
|
||||
|
||||
def test_contains_required_references(self) -> None:
|
||||
path = DOCS_DIR / "architecture.md"
|
||||
content = path.read_text()
|
||||
required_refs = [
|
||||
"GitProvider",
|
||||
"RuntimeProvider",
|
||||
"AccessProvider",
|
||||
"ToolManifest",
|
||||
"Authentik",
|
||||
"Traefik",
|
||||
"Portainer",
|
||||
"PostgreSQL",
|
||||
]
|
||||
missing = [r for r in required_refs if r not in content]
|
||||
assert not missing, f"Missing required references: {missing}"
|
||||
|
||||
|
||||
class TestMvpScopeDoc:
|
||||
def test_file_exists_and_is_non_empty(self) -> None:
|
||||
path = DOCS_DIR / "mvp-scope.md"
|
||||
assert path.exists(), "docs/mvp-scope.md must exist"
|
||||
assert path.stat().st_size > 0, "docs/mvp-scope.md must be non-empty"
|
||||
|
||||
def test_contains_required_sections(self) -> None:
|
||||
path = DOCS_DIR / "mvp-scope.md"
|
||||
content = path.read_text()
|
||||
required_sections = [
|
||||
"## 1. Product Vision",
|
||||
"## 2. MVP User Journeys",
|
||||
"## 3. In-Scope",
|
||||
"## 4. Out-of-Scope",
|
||||
"## 5. MVP Milestones",
|
||||
"## 6. Dependency Order",
|
||||
"## 7. Definition of MVP Done",
|
||||
"## 8. Open Questions",
|
||||
]
|
||||
missing = [s for s in required_sections if s not in content]
|
||||
assert not missing, f"Missing required sections: {missing}"
|
||||
|
||||
|
||||
class TestAllDocs:
|
||||
def test_no_todo_or_fixme_in_docs(self) -> None:
|
||||
markdown_files = list(DOCS_DIR.rglob("*.md"))
|
||||
assert markdown_files, "No markdown files found in docs/"
|
||||
violations = []
|
||||
for path in markdown_files:
|
||||
content = path.read_text()
|
||||
if re.search(r"\bTODO\b", content, re.IGNORECASE):
|
||||
violations.append(f"{path.name} contains TODO")
|
||||
if re.search(r"\bFIXME\b", content, re.IGNORECASE):
|
||||
violations.append(f"{path.name} contains FIXME")
|
||||
assert not violations, f"Documentation contains placeholders: {violations}"
|
||||
|
||||
def test_internal_links_point_to_existing_files(self) -> None:
|
||||
markdown_files = list(DOCS_DIR.rglob("*.md"))
|
||||
link_pattern = re.compile(r"\]\(([^)]+)\)")
|
||||
violations = []
|
||||
for path in markdown_files:
|
||||
content = path.read_text()
|
||||
for match in link_pattern.finditer(content):
|
||||
link = match.group(1)
|
||||
# Skip external URLs and anchors
|
||||
if link.startswith("http") or link.startswith("#") or link.startswith("mailto:"):
|
||||
continue
|
||||
# Resolve relative to the docs directory or repo root
|
||||
if link.startswith("docs/"):
|
||||
target = ROOT_DIR / link
|
||||
elif link.startswith("../"):
|
||||
target = path.parent / link
|
||||
elif link.startswith("./"):
|
||||
target = path.parent / link
|
||||
else:
|
||||
# Assume relative to docs dir for bare paths like "architecture.md"
|
||||
target = DOCS_DIR / link
|
||||
if not target.exists():
|
||||
violations.append(f"{path.name}: broken link to '{link}'")
|
||||
assert not violations, f"Broken internal links found: {violations}"
|
||||
Reference in New Issue
Block a user