todo: one file per item, grouped by theme, with a generated index
TODO.md had reached 1050 lines and 72 open items across 28 sections, read mostly on a phone. It is now a directory: todo/<theme>/<item>.md, one card per file, with frontmatter carrying id, status, created date and order, and the prose body copied across verbatim. TODO.md becomes a generated index (bun run todo:index). Dates: where an item's prose stated one it is used; otherwise its old section's; otherwise 2026-08-20, floored at the day before the split rather than invented. date_source records which. Shaped for a two-way Kanban board, filed as todo/tooling — a card is a file, id is the identity, frontmatter is the machine's half and the body is the owner's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
---
|
||||
theme: headscale
|
||||
heading: "Headscale"
|
||||
kind: theme
|
||||
---
|
||||
|
||||
Gaps against headscale's own API, found while reading the app on 2026-08-05 — all closed the same
|
||||
day. Kept here for what the policy work turned up, which is not obvious from the code.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
id: headscale/a-node-can-be-moved-between-users
|
||||
title: "A node can be moved between users"
|
||||
theme: headscale
|
||||
status: done
|
||||
created: 2026-08-20
|
||||
date_source: assumed
|
||||
order: 2
|
||||
---
|
||||
|
||||
**A node can be moved between users.** `/api/v1/node/{id}/user`, in the expanded node card next
|
||||
to the tag editor — both being the things that decide which policy rules apply to a node.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
id: headscale/acl-policy
|
||||
title: "ACL policy"
|
||||
theme: headscale
|
||||
status: done
|
||||
created: 2026-08-20
|
||||
date_source: assumed
|
||||
order: 1
|
||||
---
|
||||
|
||||
**ACL policy.** `/api/v1/policy` GET/PUT behind `/_officer/policy`, with a plain HuJSON textarea
|
||||
that sends the text byte for byte and shows headscale's verdict verbatim (line and column
|
||||
included). Officer does not pre-validate: headscale owns the only parser that resolves groups,
|
||||
tags and hosts, and a second weaker one would disagree with the thing that actually enforces.
|
||||
**The mode cannot be read.** A file-backed policy is still served over GET; only a PUT refuses,
|
||||
with "update is disabled for modes other than 'database'". So the first save is what discovers
|
||||
writability, and a refusal becomes a persistent read-only banner. Verified live, not inferred.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
id: headscale/device-invites-platform-side
|
||||
title: "Device invites, platform side"
|
||||
theme: headscale
|
||||
status: done
|
||||
created: 2026-08-05
|
||||
date_source: stated
|
||||
order: 5
|
||||
---
|
||||
|
||||
**Device invites, platform side** (`COMMS/OFFSCALE_INVITE_ENROLLMENT.md` §5, built 2026-08-05).
|
||||
Authorize-new-device form, the one-time link with copy/share/QR, and the invite list with revoke —
|
||||
`/_officer/enroll/invites` in `invites.ts`, `InvitesView`. **The records are not Officer's.** They
|
||||
proxy to the server's Officer Companion, because the joining phone has to claim without an Officer
|
||||
account and this sidecar is loopback-only behind our auth; the spec's own "an invite must work when
|
||||
the platform is down" argument says the same. Officer stores no invite and no claim token.
|
||||
**Live on all four servers since 2026-08-05.** The 401 that blocked it was a companion-side prefix
|
||||
parse (a Headscale key prefix is a fixed 12 chars and may contain `-`; they split on the first one);
|
||||
fixed upstream. Verified end to end against pastilhas-eu: create returns the
|
||||
`officer-offscale://join#…` link, list shows the record, revoke flips it to `revoked`. The list
|
||||
envelope is not in the spec, so `pickInvites` takes the body's array whatever it is keyed under.
|
||||
Four gaps were sent back to the spec author: `keys.ts` has no
|
||||
sub-day key TTL for the 5-minute claim key, "the sidecar must refuse plaintext" is unenforceable
|
||||
behind nginx, `tailnet` is not a headscale concept and has to be recorded on the invite, and
|
||||
`/api/v1/enroll/*` collides with headscale's own namespace.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
id: headscale/polling
|
||||
title: "Polling"
|
||||
theme: headscale
|
||||
status: done
|
||||
created: 2026-08-20
|
||||
date_source: assumed
|
||||
order: 4
|
||||
---
|
||||
|
||||
**Polling** — `useHeadscaleNodes` has had `refetchInterval: 20_000` all along; also stale.
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
id: headscale/user-rename
|
||||
title: "User rename"
|
||||
theme: headscale
|
||||
status: done
|
||||
created: 2026-08-20
|
||||
date_source: assumed
|
||||
order: 3
|
||||
---
|
||||
|
||||
**User rename** — was already shipped end to end (`/users/:id/rename`, `UsersView`); this entry
|
||||
was stale when it was written.
|
||||
Reference in New Issue
Block a user