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>
26 lines
1.5 KiB
Markdown
26 lines
1.5 KiB
Markdown
---
|
|
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.
|