Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .adr-kit.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ADR kit configuration for repository-level decision governance.
adr_directory: docs/adr
adr_directory: adr
filename_pattern: "ADR-[0-9]{4}-[a-z0-9-]+\.md"
statuses:
- Proposed
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/adr-governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: ADR Governance
on:
pull_request:
paths:
- 'docs/adr/**'
- 'adr/**'
- '.adr-kit.yaml'
- 'scripts/adr-governance.py'
- '.github/workflows/adr-governance.yml'
Expand All @@ -12,7 +12,7 @@ on:
- main
- master
paths:
- 'docs/adr/**'
- 'adr/**'
- '.adr-kit.yaml'
- 'scripts/adr-governance.py'
- '.github/workflows/adr-governance.yml'
Expand Down
67 changes: 67 additions & 0 deletions adr/ADR-0002-adrs-live-at-repo-root-not-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ADR-0002: Architecture Decision Records live at repo-root `/adr`, not `docs/adr`

- **Status:** Accepted
- **Date:** 2026-07-14
- **Decision owners:** Jim Collinson
- **Supersedes:** none
- **Superseded by:** none
- **Related:** ADR-0001 (adopt ADRs — sets `docs/adr` as the team default this repo re-points), ADR-0008 (GitBook platform — why `docs/` is the published product), ADR-0012 (the developer skill kept out of `docs/` for the same reason); `.gitbook.yaml` (`root: ./docs/`), `.adr-kit.yaml` (`adr_directory`), `scripts/adr-governance.py` (`ADR_DIR`), `.github/workflows/adr-governance.yml`

> Current (prospective) decision — made while adopting ADRs in this repo, not a retrospective reconstruction. It re-points the team standard's default ADR location for this repo's specific circumstances; the standard's mechanics are untouched.

## Context

The team ADR standard (ADR-0001, shipped via PR #56) defaults ADRs to `docs/adr/` — correct in a typical repo where `docs/` holds internal documentation. This repo is different: `.gitbook.yaml` sets `root: ./docs/`, so the **entire `docs/` tree is the published developer-documentation product**, synced to GitBook at `docs.autonomi.com/developers`. Placing ADRs under `docs/adr/` puts internal architecture records inside that published product tree.

## Decision Drivers

- Internal architecture records must not publish to, or surface on, the public developer-docs site — nav, search, or the auto-generated `llms.txt`.
- ADRs must stay out of the docs' machinery: the upstream-sweep scanner walks `docs/**/*.md` for verification blocks, and doc tooling (Diátaxis templates, terminology lint, link-checking) globs the same tree.
- A contributor browsing `docs/` as "the documentation" should not find architecture decisions mixed in with product pages.
- The team standard should still be honoured — its own `.adr-kit.yaml` `adr_directory` knob exists precisely so a repo can relocate the instance.
- Consistency with the existing decision to keep the developer skill at `skills/`, not `docs/skills/` (ADR-0012), for exactly this reason.

## Considered Options

1. **Keep ADRs at `docs/adr/`** (the team default). Rejected here: `docs/` is the published GitBook root, so ADRs would sit inside the product tree, risk leaking to the public site / `llms.txt`, and be swept by doc tooling.
2. **Keep `docs/adr/` but exclude it from GitBook** (SUMMARY omission or a `.gitbook.yaml` rule). Rejected: fragile — `root: ./docs/` keeps the files in scope, so it relies on GitBook silently ignoring unlisted files, and the conceptual mixing remains.
3. **Relocate ADRs to repo-root `/adr`.** Chosen. (`planning/adr/` was also considered and rejected: ADRs are formal, ratified decisions, distinct from the loose-thinking design docs that live in `planning/`.)

## Decision

Architecture Decision Records live at repo-root **`/adr`** in this repo. This is a repo-local *configuration* of the team standard, not a fork of it:

- `.adr-kit.yaml` → `adr_directory: adr`.
- `scripts/adr-governance.py` → `ADR_DIR = Path("adr")`.
- `.github/workflows/adr-governance.yml` → path filters point at `adr/**`.
- Move the standard's own files (`TEMPLATE.md`, `TOOLING.md`, `README.md`, and `ADR-0001`) from `docs/adr/` to `/adr`.

`/adr` is chosen over `planning/adr/` because ADRs are formal, ratified decisions — distinct from the loose-thinking design material (`implementation-plan.md`, `verification-workflow.md`, `routines/`, `sweeps/`) that already lives in `planning/`.

## Consequences

### Positive

- Architecture records stay entirely out of the published product tree and its tooling.
- Mirrors the skill's out-of-`docs/` placement (ADR-0012): one consistent rule — product content in `docs/`, internal artifacts at the repo root.
- Still fully governed — the gate validates ADRs at `/adr` once `ADR_DIR` is re-pointed.

### Negative / Trade-offs

- Diverges from the team-standard default (`docs/adr`), so a contributor arriving from another WithAutonomi/Saorsa repo must notice the repo-local location — this ADR is the signpost, and `docs/adr/` is left empty/removed so nothing looks half-moved.
- Requires the three config points (`adr_directory`, `ADR_DIR`, workflow paths) to stay in agreement; a mismatch silently skips validation.

### Neutral / Operational

- The change is small and repo-local; the shared standard's mechanics (template, lifecycle, immutability) are unchanged — only the configurable location differs.
- Decided as too minor to route through the standard's owner; recorded here so the deviation is explicit and auditable.

## Validation

- `scripts/adr-governance.py` (with `ADR_DIR=adr`) validates the `/adr` set on every PR; `docs/adr/` no longer exists.
- Spot-check after the move: no ADR path appears in the GitBook nav, site search, or `llms.txt`.
- Review trigger: a change to `.gitbook.yaml`'s `root`, or a move to a different docs platform, may reopen the location question.

## Notes for AI-assisted work

AI tools may help draft this ADR, but **must not mark it Accepted without human review**. Accepted ADRs are immutable: create a new superseding ADR rather than editing an Accepted ADR.
72 changes: 72 additions & 0 deletions adr/ADR-0003-verification-block-source-of-truth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ADR-0003: Verification-block source-of-truth model for developer docs

- **Status:** Accepted
- **Acceptance:** Retrospective — predates the ADR process; ratified by the implementation built on it and by this review pass, not by prospective pre-implementation review.
- **Date:** 2026-07-14
- **Decision owners:** Jim Collinson
- **Supersedes:** none
- **Superseded by:** none
- **Related:** ADR-0001 (Adopt ADRs); `planning/verification-workflow.md`; `planning/implementation-plan.md` §8; original build 2026-04-02 → 2026-04-22 (repo baseline `a1110ea`, "Reverify against current merged truth" `3eaf653`)

> Retrospective ADR. The decision was taken and implemented across April 2026; this record captures it after the fact so the reasoning is preserved. Evidence is cited in **Related** and **Validation**.

## Context

The Autonomi developer docs describe fast-moving upstream code (`ant-sdk`, `ant-client`, `ant-node`, `ant-protocol`, `saorsa-*`, and others). Documentation that drifts from the code is worse than no documentation: it silently misleads developers. The repo needed a way to state, for every technical claim on every page, *exactly which upstream commit that claim was verified against*, so drift is detectable mechanically rather than by re-reading everything by eye.

A second tension: docs must describe what a developer can do **now** (merged, released code), not the expected future product shape. Without an explicit rule, pages tend to drift toward aspirational descriptions of unmerged branches. But a launch-hardening pass needs the opposite — the ability to pin a set of pages to a fixed release target and *stop* following moving branches.

## Decision Drivers

- Drift must be **detectable by a machine**, not only by human review.
- Every rendered claim must be **traceable to an exact upstream commit SHA**.
- Docs must default to **current, merged, released** truth, never future state.
- A launch/release pass must be able to **pin** pages to fixed refs without inverting the day-to-day default.
- Provenance metadata must not leak into rendered prose.

## Considered Options

1. **No embedded provenance** — track freshness out-of-band (a spreadsheet, issue tracker, or reviewer memory). Rejected: unmaintainable, not machine-checkable, drifts immediately.
2. **Page-level "last reviewed" date only.** Rejected: a date says *when* someone looked, not *what upstream state* was true; it cannot detect that upstream moved.
3. **Per-claim verification blocks pinning `(source_repo, source_ref, source_commit, verified_date, verification_mode)`, with two explicit modes.** Chosen.

## Decision

We will embed **verification blocks** in the docs and skill sources as the single machine-readable record of what each page was verified against.

- Every documented surface carries one or more `<!-- verification: ... -->` blocks in `docs/**/*.md`, and the skill carries equivalent `verified_commits` records in `skills/start/version.json` and `skills/start/SKILL.md` frontmatter. Each block records `source_repo`, `source_ref`, `source_commit` (an exact SHA), `verified_date`, and `verification_mode`.
- Two verification modes are defined, and they are an **invariant of the model**:
- **`current-merged-truth`** (default): verify against the latest merged commit on the upstream default branch at audit time. Unmerged branches and PRs are out of scope.
- **`target-manifest`**: launch/release-hardening mode. Verify against refs pinned in `target-manifest.yml`; **do not** follow moving default branches for pages in this mode.
- A page presented as verified must never carry `source_commit: TBD`.
- Provenance/verification language lives only in the comment blocks and metadata — never in rendered body text.

This model is the foundation the automation in ADR-0004 through ADR-0007 operates on: the scanner, the guards, and the routine all read and write these blocks and nothing else about freshness.

## Consequences

### Positive

- Drift becomes a deterministic diff: compare each `source_commit` against upstream HEAD (see ADR-0004). No human re-reading required to *detect* staleness.
- Every claim is auditable to an exact SHA, so a reviewer can reproduce the evidence.
- The default keeps docs honest about shipped behaviour; the `target-manifest` escape hatch supports launch hardening without weakening the default.

### Negative / Trade-offs

- Authors and automation must keep the blocks accurate; a stale-but-present block is a false "verified" signal, so block integrity itself must be guarded (ADR-0006) and reachability-checked (ADR-0007).
- Multi-repo pages carry multiple blocks, adding markup weight to sources.

### Neutral / Operational

- The block schema (`source_repo`, `source_ref`, `source_commit`, `verified_date`, `verification_mode`) becomes a stable contract that every downstream tool depends on; changing it is itself an architectural change requiring a superseding ADR.
- `target-manifest` blocks are deliberately excluded from automated bumping (see ADR-0007) so a launch pin is never silently overwritten.

## Validation

- The scanner (`scripts/sweep_poll.py`) parses every block and fails closed on a malformed block (missing `source_repo`/`source_ref`/`source_commit`/`verification_mode`) or an unknown mode — so a schema violation is caught, not ignored.
- `sweep-sha-reachability` (CI) confirms every recorded SHA is reachable on the declared upstream ref.
- Review trigger: any change to the block schema, the mode set, or the "no `TBD`" rule must supersede this ADR rather than edit it.

## Notes for AI-assisted work

AI tools may help draft this ADR, but **must not mark it Accepted without human review**. Accepted ADRs are immutable: create a new superseding ADR rather than editing an Accepted ADR.
76 changes: 76 additions & 0 deletions adr/ADR-0004-deterministic-scanner-model-audit-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# ADR-0004: Tiered execution — deterministic detection, efficient-model metadata sweeps, frontier-model prose

- **Status:** Accepted
- **Acceptance:** Retrospective — predates the ADR process; ratified by the implementation built on it and by this review pass, not by prospective pre-implementation review.
- **Date:** 2026-07-14
- **Decision owners:** Jim Collinson
- **Supersedes:** none
- **Superseded by:** none
- **Related:** ADR-0003 (verification blocks); `scripts/sweep_poll.py`; `planning/routines/upstream-sweep.md` §"Opus audit/write/verify loop"; build commit `a6bce6be` (2026-05-04); hardening commits `ddeb45a` (2026-05-09), `8c541eb` (2026-05-09), `f447f78`/`1ce7ef9`/`8348c72` (2026-05-11, session `019cz1M7pXuXFQykMZxfdXHm`)

> Retrospective ADR. Implemented 2026-05-04 and hardened through 2026-05-11.

## Context

The pipeline actually contains three kinds of work, each with a different cost/capability profile, and treating them as one wastes money and trust:

1. **Drift detection** — for each verification block, resolve upstream HEAD and compare SHAs. A pure, repeatable computation. No model judgement at all.
2. **Metadata-only sweeps** — when a drifted upstream change has no developer-facing impact, the work is to audit the diff, confirm it is genuinely metadata-only, and re-stamp the SHA (see ADR-0006's `claude/sweep-*` track). This needs light judgement, but not deep source comprehension or authoring.
3. **Prose sweeps** — when a change alters real behaviour, the work is to read the upstream diff and source, judge developer-facing impact, and write correct, voice-consistent documentation (ADR-0006's `claude/prose-*` track). This needs a frontier model.

Conflating these produces a system that is neither trustworthy (an LLM "detecting" drift is non-reproducible) nor efficient (running frontier-model inference to re-stamp a SHA that changed nothing a developer sees is pure cost). We also learned during hardening that a "detector" which silently swallows edge cases (missing token, an org that refuses fine-grained PATs, a null value in a map) is dangerous: it reports "no drift" when it actually failed to look. The detector therefore has to be strict and fail-closed, which is far easier to guarantee in deterministic code than in a model prompt.

## Decision Drivers

- Drift detection must be **deterministic and reproducible** — the same inputs give the same report every run.
- Detection must **fail closed**, never fail silent, on auth/network/parse/shape errors.
- Prose audit, rewriting, and verification need **frontier-model judgement** and cannot be scripted.
- Cheaper work must not pay frontier-model prices: **match the execution tier to the job**.
- Each tier should be independently testable and independently hardenable.

## Considered Options

1. **Model does everything**, including "notice what looks out of date." Rejected: non-reproducible, no audit trail, easy to hallucinate freshness, expensive to run over the whole repo.
2. **Script does everything**, including regenerating prose via templates. Rejected: cannot judge developer-facing impact or write correct, voice-consistent prose from an upstream diff.
3. **One frontier model for all model work** (detection stays scripted, but a single frontier model handles both metadata sweeps and prose). This is what the routine ran at build time. Retained as workable, but rejected as the target: it pays frontier prices to re-stamp SHAs that change nothing developer-facing.
4. **Tiered execution: deterministic detection, an efficient model for metadata sweeps, a frontier model for prose.** Chosen.

## Decision

We will run the pipeline in **three tiers, matching cost and capability to the work**:

- **Detection — `scripts/sweep_poll.py`, deterministic, no model.** It walks every verification block in `docs/**/*.md`, `version.json`, and `SKILL.md` frontmatter; resolves `(repo, ref)` via `repo-registry.yml` + GitHub default branch; and emits a per-record JSON drift report. It **fails closed** on auth, network, parse, shape, and unknown-mode errors (see ADR-0007), deliberately skips `target-manifest` blocks into a separate array, and never writes docs. Its report is a **candidate list, not a directive** — SHAs are bumped only after a per-page audit succeeds.
- **Metadata-only sweeps — an efficient model is sufficient.** Auditing a diff, confirming it carries no developer-facing change, and re-stamping the SHA (the `claude/sweep-*` track, ADR-0006) does not require a frontier model; an efficient model (Sonnet-class) is an appropriate floor.
- **Prose sweeps — a frontier model is required.** Reading upstream source, judging developer-facing impact, and writing/verifying documentation prose (the `claude/prose-*` track, ADR-0006) requires a frontier model, provider-agnostic (a Claude Opus-class or GPT-5.x-class model). There is no subagent layer for this work.
- **Model names are deliberately de-versioned in this decision.** The requirement is the *tier* ("efficient" vs "frontier"), not a specific model. For provenance: at build time the prose floor was specified as Opus 4.7+ (or GPT-5.5/5.6+), raised to Opus 4.8+ on 2026-06-18 (session `011PdDcd7X5C1aSz7sYqjSnz`); the current frontier is a later generation again. The floor should track "the current frontier," not a frozen version string.

## Consequences

### Positive

- Detection is reproducible and cheap; a human can re-run the scanner and get the identical drift report.
- Fail-closed detection means "no drift" genuinely means "looked, found nothing," not "failed to look."
- Prose quality is owned by a frontier model competent to write it, gated by audit and verification.
- Cost tracks value: the common case (no-drift and metadata-only re-stamps) never pays frontier-model prices; frontier inference is spent only where prose is actually rewritten.
- The tiers harden independently — most of the May 9–11 fixes were scanner shape/HTTP robustness with zero change to the prose loop.

### Negative / Trade-offs

- Frontier-model prose is the expensive tier, with cost and availability implications on any run that rewrites prose.
- Tiering adds routing complexity: the metadata-vs-prose classification must be reliable, or a prose-impacting change could be handled by the cheaper tier. Classification is the routine's job (see the page-batching rule) and ambiguous records fail closed to human review (ADR-0007), which bounds this risk.
- Multiple artifacts to maintain (scanner code + routine prompt), and potentially more than one model configured.

### Neutral / Operational

- The scanner's JSON output schema (`status`, `records`, `errors`, `target_manifest_skipped`, `notices`) is a contract consumed by the routine; additive fields are safe, shape changes are not.
- Which concrete models fill the "efficient" and "frontier" tiers is routine config (see ADR-0005), not a repo change; only the *tier requirement* is recorded in the prompt and policy.

## Validation

- Re-running `scripts/sweep_poll.py` must return `status: "ok"` and a reproducible `records` list; the routine re-runs it as a post-write check.
- Scanner unit behaviour is exercised by the shape/type guards added in `ddeb45a` and `8c541eb` (non-string SHA, null/empty `verified_commits`, empty frontmatter all fail closed).
- Review trigger: any move of audit/verification responsibility into the scanner, or of detection into the model, supersedes this ADR.

## Notes for AI-assisted work

AI tools may help draft this ADR, but **must not mark it Accepted without human review**. Accepted ADRs are immutable: create a new superseding ADR rather than editing an Accepted ADR.
Loading
Loading