From 494e32fa8156cedf9f78558a1e7a232712389044 Mon Sep 17 00:00:00 2001 From: Richard Shade Date: Tue, 29 Sep 2026 12:24:05 -0500 Subject: [PATCH] openspec: propose user documentation restructure Add the add-user-documentation change. It proposes moving the README into a docs/ tree organized by Diataxis quadrant (tutorial, how-to, reference, explanation), leaving a short README as the landing page. New pages cover a first-monitor tutorial, the config, CLI and metrics references, and troubleshooting. Contributor material moves to docs/contributing/. The change sets skip_specs because it alters no system behavior. Updates tailscale/tailscale#5967 Signed-off-by: Richard Shade --- .../add-user-documentation/.openspec.yaml | 3 + .../changes/add-user-documentation/design.md | 98 +++++++++++++++++++ .../add-user-documentation/proposal.md | 72 ++++++++++++++ .../changes/add-user-documentation/tasks.md | 53 ++++++++++ 4 files changed, 226 insertions(+) create mode 100644 openspec/changes/add-user-documentation/.openspec.yaml create mode 100644 openspec/changes/add-user-documentation/design.md create mode 100644 openspec/changes/add-user-documentation/proposal.md create mode 100644 openspec/changes/add-user-documentation/tasks.md diff --git a/openspec/changes/add-user-documentation/.openspec.yaml b/openspec/changes/add-user-documentation/.openspec.yaml new file mode 100644 index 0000000..c7c6694 --- /dev/null +++ b/openspec/changes/add-user-documentation/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-09-29 +skip_specs: true diff --git a/openspec/changes/add-user-documentation/design.md b/openspec/changes/add-user-documentation/design.md new file mode 100644 index 0000000..ab5fcd8 --- /dev/null +++ b/openspec/changes/add-user-documentation/design.md @@ -0,0 +1,98 @@ +# Design + +## Context + +The only user documentation is `README.md` (about 625 lines, with 336 lines of +uncommitted additions from `add-notifications`). Reference facts live in three other +places: the Go source (`config.go`, `config_sinks.go`, `notify_metrics.go`, the flag +sets in `main.go`), the archived specs under `openspec/specs/`, and +`tailnet-reachability-monitor-design.md`. `examples/tail-watch.yaml` and +`examples/compose.yaml` are the only runnable samples. markdownlint runs with MD013 at +90 columns (tables and code blocks exempt) and ignores `.claude/**` and `node_modules/**`. + +## Goals / Non-Goals + +**Goals:** + +- Every operator question maps to exactly one page: learn, do, look up, or understand. +- Every command and YAML snippet in the docs is checked against the built binary, not + copied from memory. +- A fact lives in one page; other pages link to it. + +**Non-Goals:** + +- Documenting internal architecture (`netEnv`, clocks, mutex layout). That stays in + `CLAUDE.md` and `openspec/`. +- Auto-generating reference pages from code. The config surface is small enough to write + by hand and check. + +## Decisions + +### Diataxis layout under `docs/` + +```text +docs/ + README.md index, one line per page + tutorial/first-monitor.md + how-to/ deploy.md acls.md remote-subnets.md + slack-sink.md webhook-sink.md troubleshoot.md + reference/ config.md checks.md cli.md metrics.md payload.md + explanation/ failure-layers.md alert-state.md incidents-and-queues.md + contributing/ live-notification-test.md releases.md +``` + +Alternative: a single long `docs/guide.md`. Rejected because it recreates the README +problem and gives readers no stable links. + +### README becomes a landing page + +Keep the purpose paragraph, requirements summary, a five-line quickstart, and a link +list. Alternative: keep the README complete and add `docs/` as extras. Rejected: two +copies of the sink options and payload drift as soon as one changes. + +### Accuracy is checked mechanically where possible + +- Each YAML snippet that is a full config is saved and run through + `tail-watch check-config`. +- Each shell command is run, or, when it needs a tailnet, is marked as such and matches a + command exercised by `scripts/live-stack.sh` or the live tests. +- Metric names in `reference/metrics.md` are compared against the `Name:` entries in + `notify_metrics.go` and the collector metrics, so none is documented that does not exist. + +Alternative: extract snippets into Go tests. Rejected as over-built for a docs change. + +### Source of truth per page + +| Page | Source | +| ---- | ------ | +| `reference/config.md` | `config.go`, `config_sinks.go`, design doc "Config" | +| `reference/cli.md` | flag sets in `main.go`, `notifytest.go` | +| `reference/metrics.md` | metric registrations, design doc "Metrics" | +| `reference/payload.md` | `event.go`, `testdata/payload` golden files | +| `explanation/*` | archived specs, design doc "What a failed check means", "Alert state machine" | + +Where code and design doc disagree, the code and archived spec win and the page follows +them; note the mismatch in the task that found it. + +### Tutorial uses the shipped example + +The tutorial starts from `examples/compose.yaml`, adds one failing target, and ends with +the alert arriving (Slack webhook or a local webhook receiver). This keeps it runnable +without inventing a new sample. Alternative: a self-contained tutorial with its own +compose file. Deferred: revisit only if the example proves too far from a first run. + +## Risks / Trade-offs + +- [Docs describe behavior that `add-notifications` still changes] → Apply after that + change is archived; the README diff is the input. +- [Restructure loses README content] → Move first, then trim; a task diffs old README + headings against the new pages and confirms each is accounted for. +- [Existing README anchors are linked from elsewhere] → Search the repo and release notes + for `README.md#` links and keep or redirect them. +- [Reference pages drift from code over time] → The metrics/config checks above are + repeatable, and `CLAUDE.md` gets a note to update `docs/` with config or metric + changes. + +## Open Questions + +- Should `docs/` link check run in CI, or only locally in the validation group? diff --git a/openspec/changes/add-user-documentation/proposal.md b/openspec/changes/add-user-documentation/proposal.md new file mode 100644 index 0000000..dcdb5df --- /dev/null +++ b/openspec/changes/add-user-documentation/proposal.md @@ -0,0 +1,72 @@ +# Proposal + +## Why + +`README.md` has grown to about 625 lines and carries four kinds of content at once: +explanation (failure layers, alert state), how-to (Docker, ACLs, Slack webhook), reference +(checks, sink options, payload) and contributor notes (live notification test, +releases). A new operator has no path from "what is this" to "first alert in Slack", and +nothing documents the full `tail-watch.yaml` schema, the `tailwatch_*` metrics, or the +flags in one place. Phase 1 is feature-complete once `add-notifications` lands, so the +behavior is stable enough to document now. + +## What Changes + +- Restructure user documentation into `docs/` following the Diataxis quadrants + (tutorial, how-to, reference, explanation), written with the `documentation-writer` + skill. +- Reduce `README.md` to a short overview: what tail-watch is, a five-line quickstart, + and links into `docs/`. Move the existing README material into the matching pages + rather than keeping two copies. +- Add pages that do not exist today: a first-monitor tutorial, a configuration reference, + a metrics reference, a CLI reference (flags, `check-config`, `notify-test`), and a + troubleshooting guide. +- Split contributor material (live notification test, development and releases) into + `docs/contributing/` so it is out of the operator path. +- Lint the new Markdown with markdownlint and keep it in the existing lint task. + +This change adds documentation only. It does not change code, config keys, metric names or +payload fields; every documented fact is checked against the source, the archived specs +and `tailnet-reachability-monitor-design.md`. + +Implements no design-doc section. The design doc has no user-documentation section, and +documentation is not a phase 1 deliverable there. It describes behavior from the design +doc sections "Check types", "What a failed check means", "Alert state machine", +"Notifications", "Config", "ACLs" and "Metrics". + +Phase: MVP (phase 1). Non-goals: + +- Phase 2 and 3 features (PagerDuty, Slack bot-token mode, `target.degraded`, quorum + across collectors, tsnet mode). Pages mention them only as "not yet supported". +- A generated documentation site or hosting (mkdocs, Hugo, GitHub Pages). +- Man pages, shell completion, or in-binary help text changes. +- Translating or versioning the docs per release. + +## Capabilities + +### New Capabilities + +None. Documentation does not change system behavior, so no spec changes. The change sets +`skip_specs: true` in `.openspec.yaml`. + +### Modified Capabilities + +None. + +## Impact + +- New: `docs/` tree (tutorial, how-to, reference, explanation, contributing). +- Changed: `README.md` (slimmed), `CLAUDE.md` (layout note for `docs/`), markdownlint + configuration if `docs/` needs exclusions or the line-length rule needs adjusting. +- No Go code, dependency, or runtime impact. +- Sequencing: this change should be applied after `add-notifications` is archived, so the + notification pages describe final behavior. The uncommitted README edits from that + change are the source for those pages. + +## Assumptions + +- The primary reader is a tailnet admin or SRE who knows Tailscale ACLs but not tail-watch. +- The README is restructured, not left alongside new pages. +- The tutorial builds on `examples/compose.yaml` and `examples/tail-watch.yaml`. + +These were proposed during exploration and not contradicted; revise them here if wrong. diff --git a/openspec/changes/add-user-documentation/tasks.md b/openspec/changes/add-user-documentation/tasks.md new file mode 100644 index 0000000..5b1ee4b --- /dev/null +++ b/openspec/changes/add-user-documentation/tasks.md @@ -0,0 +1,53 @@ +# Tasks + +## 1. Outline and scaffolding + +- [ ] 1.1 Use the `documentation-writer` skill to confirm audience, goal and scope per page, and get the outline approved; verify the approved outline matches the tree in `design.md` +- [ ] 1.2 Create `docs/README.md` index and the empty quadrant directories; verify every planned page is listed and links resolve +- [ ] 1.3 Add a heading-by-heading map from current `README.md` sections to target pages (kept in the change directory, not committed to `docs/`); verify no README section is unassigned +- [ ] 1.4 Search the repo for `README.md#` anchors and inbound links (`CLAUDE.md`, `CHANGELOG.md`, `examples/`); verify each is listed with its new target + +## 2. Reference pages + +- [ ] 2.1 Write `docs/reference/config.md` from `cmd/tail-watch/config.go` and `config_sinks.go`; verify every key in `examples/tail-watch.yaml` and every `sinks` key appears with type, default and required/optional +- [ ] 2.2 Save each full-config snippet from `docs/reference/config.md` and run `tail-watch check-config`; verify all pass +- [ ] 2.3 Write `docs/reference/cli.md` (flags, `TAIL_WATCH_CONFIG`, `TAIL_WATCH_LOG_LEVEL`, `check-config`, `notify-test`, exit codes) from `cmd/tail-watch/main.go` and `notifytest.go`; verify against `tail-watch --help` and `notify-test --help` +- [ ] 2.4 Write `docs/reference/metrics.md` from the metric registrations under `cmd/tail-watch/`; verify each documented `tailwatch_*` name appears in the source and each registered name is documented +- [ ] 2.5 Write `docs/reference/checks.md` (ICMP, TCP, HTTP, tsping options and semantics) from `checks.go`; verify against the `reachability-checks` spec +- [ ] 2.6 Write `docs/reference/payload.md` (event types, fields, severities, signature header) from `event.go` and `testdata/payload`; verify the field list matches the golden files + +## 3. Explanation pages + +- [ ] 3.1 Write `docs/explanation/failure-layers.md` from the README "Failure layers" section and the `failure-classification` and `dependency-learning` specs; verify each layer name matches the `failure_layer` metric label values +- [ ] 3.2 Write `docs/explanation/alert-state.md` covering grace, flap suppression and dependency suppression, from the `alert-state-machine` and `dependency-suppression` specs; verify each state and timer name matches the config keys in `reference/config.md` +- [ ] 3.3 Write `docs/explanation/incidents-and-queues.md` covering `state_dir`, at-least-once delivery, replay and open incidents; verify against the `add-notifications` spec set once archived + +## 4. How-to guides + +- [ ] 4.1 Write `docs/how-to/deploy.md` (binary, Docker, Compose without a dedicated host) from README "Build and run" and "Docker"; verify the Docker build command runs and `--version` prints +- [ ] 4.2 Write `docs/how-to/acls.md` from README "ACLs"; verify the policy snippet is valid HuJSON +- [ ] 4.3 Write `docs/how-to/remote-subnets.md` from README "Remote subnets and local LANs"; verify each `network` value and startup failure named in it exists in `netenv.go` and `config.go` +- [ ] 4.4 Write `docs/how-to/slack-sink.md` including getting a webhook URL and using `*_env`; verify the config snippet passes `check-config` and `notify-test --sink` steps match `notifytest.go` +- [ ] 4.5 Write `docs/how-to/webhook-sink.md` (signing, headers, batching, retries); verify the signature verification example against `webhook_test.go` +- [ ] 4.6 Write `docs/how-to/troubleshoot.md` (userspace tailscaled refusal, missing `tag:monitor`, ICMP permissions, socket path, missing secrets, corrupt state); verify each symptom's message text against the source strings + +## 5. Tutorial + +- [ ] 5.1 Write `docs/tutorial/first-monitor.md` starting from `examples/compose.yaml`, adding a failing target, and ending with a delivered alert; verify by following it on a clean checkout with `scripts/live-stack.sh` or the Compose file +- [ ] 5.2 Record any step that needed a fix while following it, and update the page; verify a second read-through needs no unwritten step + +## 6. README and contributor split + +- [ ] 6.1 Move "Live notification test" and "Development and releases" from `README.md` to `docs/contributing/`; verify commands still match `CLAUDE.md` and `scripts/live-stack.sh` +- [ ] 6.2 Trim `README.md` to overview, quickstart and links; verify against the 1.3 map that every removed section exists in a page +- [ ] 6.3 Fix or redirect the inbound links found in 1.4; verify a grep for the old anchors finds none +- [ ] 6.4 Add a `docs/` layout and "update docs with config or metric changes" note to `CLAUDE.md`; verify the note matches the final tree +- [ ] 6.5 Add a `CHANGELOG.md` entry if the release process lists documentation; verify against `release-please-config.json` + +## 7. Validation + +- [ ] 7.1 Run `mise exec -- go build ./...`, `go vet ./...` and `go test ./...`; verify all pass, confirming no code changed +- [ ] 7.2 Run `mise run lint` (golangci-lint) and `govulncheck ./...`; verify no findings +- [ ] 7.3 Run `markdownlint` over `README.md`, `docs/` and `openspec/changes/add-user-documentation/`; verify zero findings +- [ ] 7.4 Run a link check over `docs/` and `README.md` (local, relative links); verify none are broken +- [ ] 7.5 Run `mise exec -- openspec validate add-user-documentation --strict`; verify it passes