Phase 1a: content schema validator (freeze the schema)

Implements AGENTS.md section 7 front-matter schema as a single-file,
standard-library-only Python validator; no third-party YAML dependency
and no framework. Adds the `make validate` gate, a 20-case fixture set
and docs/content-schema.md.

DoD satisfied:
- make validate runs and exits 0 on an empty/clean content/ tree
  (missing dir and empty dir both exit 0; fixture ok-empty covers it).
- Every violation class has a fixture under test/fixtures/fail-*; each
  exits non-zero with one line per violation (file:line: field: rule).
  `make test` runs all 20 cases: 20/20 pass.
- Dangling artefact reference is fatal (fail-dangling-artefact;
  artefacts[].url must resolve to an existing in-repo L3 stub or asset).
- Unknown front-matter field is fatal (fail-unknown-field), including
  unknown keys nested inside artefacts[]/metrics[].
- docs/content-schema.md committed; states the frozen schema, links to
  AGENTS.md section 7, and records that any later schema change is a
  breaking change requiring Sam's approval.
- Scope respected: no content authored, no rendering, no site code.

Files: Makefile, tools/validate_content.py, test/fixtures/**,
docs/content-schema.md, .gitignore (__pycache__).
This commit is contained in:
2026-10-26 17:26:00 +11:00
parent b267e55f8c
commit 0eb151117e
79 changed files with 1733 additions and 0 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: security
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
area: must be one of ai, platform, edge, products, history, about

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "https://example.invalid/rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
artefacts[1].url: must be an in-repo path to an L3 stub or asset, not an external URL

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
artefacts[1].url: missing required field

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: screenshot
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
artefacts[0].type: must be one of repo, image, video, doc

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
invalid YAML: unterminated quoted string

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: planned
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
confidence: must be one of delivered, running, in-progress, abandoned, historic

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/not-written-yet.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
artefacts[0].url: dangling reference: 'content/edge/l3/not-written-yet.md' does not exist

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1,21 @@
---
id: audio
area: platform
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1 @@
duplicate id 'audio'

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: edge-audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
must match the file name (expected 'audio', got 'edge-audio')

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-13-45
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
last_verified: must be a real calendar date (YYYY-MM-DD)

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: "20 October 2026"
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
last_verified: must be a date written as YYYY-MM-DD

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 4
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
level: must be the integer 1, 2 or 3

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
metrics[0].label: must be non-empty

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,20 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
claim: missing required field

View File

@@ -0,0 +1,3 @@
# Distributed Audio
No front-matter block at all.

View File

@@ -0,0 +1 @@
missing front-matter block

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,22 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
author: "Sam"
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

View File

@@ -0,0 +1 @@
author: unknown field (the schema is frozen; see docs/content-schema.md)

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,19 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg", caption: "the rack"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---

View File

@@ -0,0 +1,15 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---

View File

@@ -0,0 +1 @@
artefacts[1].caption: unknown field (artefacts allow only 'type', 'label' and 'url')

View File

@@ -0,0 +1,25 @@
---
# a leading comment is allowed
id: background
area: about
level: 1
title: >-
Twenty-five years of delivery
claim: |
Shipped software since 2000.
Now agent systems and the platform under them.
confidence: delivered
period: "2000-"
tech:
- "NixOS"
- 'Golang'
metrics:
- label: years
value: 25
artefacts: []
public: true
last_verified: 2026-10-20
tags: [narrative, career]
---
# Twenty-five years of delivery

View File

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"/>

After

Width:  |  Height:  |  Size: 65 B

View File

@@ -0,0 +1,21 @@
---
id: audio
area: edge
level: 2
title: "Distributed Audio"
claim: "Eight synchronised endpoints with music ducking for voice."
confidence: running
period: "2023-"
tech: [Snapcast, Mopidy, Spotify]
metrics: [{label: "endpoints", value: 8}]
artefacts:
- type: doc
label: "Architecture note"
url: "content/edge/l3/audio-architecture.md"
- {type: image, label: "Rack diagram", url: "content/assets/audio-rack.svg"}
public: true
last_verified: 2026-10-20
tags: [audio, snapcast]
---
# Distributed Audio

View File

@@ -0,0 +1,17 @@
---
id: audio-architecture
area: edge
level: 3
title: "Audio architecture note"
claim: "How the eight endpoints stay in sync."
confidence: running
period: "2023-"
tech: []
metrics: []
artefacts: []
public: true
last_verified: 2026-10-20
tags: [audio]
---
# Audio architecture note

0
test/fixtures/ok-clean/expect.txt vendored Normal file
View File

View File

0
test/fixtures/ok-empty/expect.txt vendored Normal file
View File