Satisfies docs/phase-4-corpus.md. content/ stays the single source of truth:
one pass over it writes both indexes, so the static pages and the retrieval
corpus cannot drift.
- corpus/: stdlib-only pipeline. validate -> chunk -> enrich -> embed -> upsert.
Chunking is block-based, so a boundary cannot fall inside a fenced block, a
table or front-matter (verified structurally, not by inspection).
- Deterministic ids: sha256(source_path + chunk_index + content_hash), rendered
as a UUID. Re-running updates points; re-chunking deletes the old ones by
source filter, so duplicates and orphans are both impossible.
- Incremental by content hash against a SQLite manifest. A provider, dimension
or chunker change is refused instead of mixed: it needs a rebuild.
- Payloads per section 4.2 plus provenance, validated before any vector is
written; a missing field fails the run and names the document and chunk.
- SQLite FTS5 over chunks (the keyword half of hybrid retrieval, decision D10)
with area/confidence filters and a `terms` column so front-matter tech and
tags are findable.
- Freshness audit: stale documents, due soon, index drift, contract drift;
optional Apprise alert; exit code for Prefect.
- Embeddings behind one interface: omniroute (OpenAI-compatible, retries, key
from the environment only) and local-hashing (offline placeholder). The
provider decision is still Sam's; the pipeline does not depend on it.
- deploy/qdrant.yml: mem_limit = memswap_limit = 400m, loopback only.
- docs/phase-4-notes.md: operator manual, DoD evidence, measured memory.
- 76 unit tests, no container and no network.
Measured, Qdrant 1.20.0 under a 400 m cap at 1024 dimensions: 500 / 5,000 /
20,000 chunks settle at 128 / 138 / 151 MiB, peaking at 149 / 305 / 292 MiB
during ingest. Qdrant's default settings are OOM-killed between 5,000 and
20,000, so the collection is created with HNSW and payloads on disk; recorded in
reference/stack.md with the ceiling and the escalation order.
Note: the `make corpus*` targets were swept into the Phase 3 deploy commit
24590cc by concurrent work in this tree; the Makefile content is correct, and
this commit contains the pipeline they call.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Two failures from the first real deploy, both repo-side:
1. The container logged "exec /usr/bin/resume-server: no such file or
directory" and restarted until the health gate failed. The build host has
cgo enabled, so the binary carried the nix glibc interpreter, and the
distroless/static image has no dynamic loader. Every SQLite path already
uses pure-Go modernc.org/sqlite, so build with CGO_ENABLED=0 and ship a
static binary.
2. Compose warned about orphan containers ai-resume-backend-1 and
ai-resume-db-1: /home/sam/deployment/ai-resume on .13 already owns the
`ai-resume` compose project (on the ai-mesh network). Sharing the name makes
each stack visible as orphans to the other, so a `compose down
--remove-orphans` in either could stop the other. Run the site as project
`ai-resume-site`; the image tag stays ai-resume:local and the port binding,
mem_limit and memswap_limit are untouched.
The first deploy failed before the health gate:
Network ai-resume_default Error Error response from daemon:
all predefined address pools have been fully subnetted
.13's docker daemon has 31 bridge networks already, which exhausts its
default-address-pools, so any new implicit compose network fails to create.
Pinning 10.89.0.0/28 (no route, bridge or LAN overlap on .13) makes this
project independent of that host condition without touching another project,
container or the daemon config. mem_limit, memswap_limit and the 127.0.0.1
port binding are unchanged.
The release directory under /home/sam/deployment/resume is the rollback unit;
deploy.sh stages releases/<timestamp>-<sha>/, flips `current` atomically,
builds the image from that directory and gates on /healthz, restoring the
previous release on any failure.
The rehearsal recipe in the header is kept as comments only; the pasted spec
lost the comment prefixes on the multi-line printf, which would have made the
script exit 1 on startup. It now uses \n escapes on one commented line.
DoD satisfied (evidence in AUD-142): A1 with no AI services, one binary,
deterministic templ generate, 6 L1 + 6 L2 + artefact pages, FTS5 search,
Lighthouse 100/100 mobile and desktop, <100ms first byte, stage panel driven
by route or stream, print legible.
One Go binary serves a pre-rendered site. No Node, no bundler, no node_modules:
htmx and the SSE extension are vendored files, Markdown and code highlighting
run at build time (goldmark + chroma), and the server only writes files.
Implements docs/phase-2-static-site.md:
2.1 service skeleton cmd/build (content -> dist), cmd/server (dist -> HTTP),
env-configured, 127.0.0.1:8096, /healthz with separate
static / ai / search readiness, --health-check probe,
deploy/compose.yml + deploy/Dockerfile at 120m
2.2 rendering templ components (Layout, Nav, PillarCard, Stage,
EvidenceTable, CodeBlock, Timeline, Footer);
`make generate` is a build step and is byte-stable
2.3 navigation six-area rail, <details> disclosure under 900px,
breadcrumbs, aria-current, L1/L2/L3 with artefact pages
2.4 stage panel internal/stage owns the contract; RouteDriver (fragment
swap) and SSEDriver (event stream) differ only in the
Binding, never in layout. fragment ids live in the
contract so route files, links and streamed frames agree
2.5 search SQLite FTS5 over titles and body, rebuilt by the build;
one handler returns a page without HTMX, a fragment with
it. Visitors' FTS operators are never honoured
2.6 quality floor print stylesheet, JS-off navigation, focus states,
semantic landmarks, dark default; security headers
Definition of Done evidence (see issue AUD-142):
A1 all pages 200 in a network namespace with no external route
one binary serves the site; make dist writes 45 pages, 86 fragments, 40 docs
templ generate is deterministic (md5 identical across runs)
6 L1 + 6 L2 pages, artefact pages reachable in <= 3 clicks
FTS5 returns sensible hits for zigbee / signage / routing (tested)
Lighthouse 100 performance and 100 accessibility, mobile and desktop
first byte well inside 100ms over loopback (test)
panel renders from a route driver and re-points at the stream untouched
print legible: /edge/ renders to a 3-page PDF with the print stylesheet
Also fixes found while finishing the build:
- reject "." / ".." path segments before ServeMux redirects them into a file
outside the asset directory (/static/../build.json now 404, was 307->200)
- make test is the real gate: go test ./... plus the validator self-test
'The spine' records 50+ containers across 3 hosts and a 4-host estate; the
pages asserted containers 'across four hosts'. Reworded so the container
count and the host count are each stated without implying a distribution
the sources do not give. No metric value changed.
DoD: schema valid (make validate clean), metrics still trace to the
approved chip set.
Satisfies the Phase 1 Definition of Done:
- content/ validates cleanly against the frozen schema. `make validate`
reports OK: 40 document(s) validated, 0 violations.
- Six areas exist, each with an L1 entry: ai-overview, platform-overview,
edge-overview, products-overview, history-overview, about-overview.
- All six areas have an L2 entry: ai-systems, platform-estate, edge-fleet,
products-business, history-engagements, about-narrative.
- 28 L3 artefact stubs under content/<area>/l3/. Every artefacts[].url in
every document resolves to one of them; the validator proves there are no
dangling references (and a deliberately broken copy exits 1).
- Every metric value traces to a source line in the evidence notes or the
approved metric set in reference/content-map.md (per-metric citations are
recorded in the AUD-131 completion comment).
- Every document carries confidence and last_verified=2026-10-26; the
validator rejects a missing or non-canonical value.
- docs/content-redaction.md records the redaction pass: identifiers, third
party personal data, credentials and infrastructure detail excluded, with
reasons, plus the two numbers that are reported rather than smoothed.
Honesty positions held: 3 businesses active (12 adoptions over time),
Where-Woof deployed and pre-market, failures kept in (edge-failures), no
promotion of in-progress work to delivered.
No site code, no templates, no CSS, no rendering (Phase 2).
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__).