DECISIONS consistency audit 2026-11-04 #341

Open
opened 2026-11-04 09:55:52 +00:00 by kayg · 3 comments
Owner

DECISIONS consistency audit 2026-11-04 (J183)

Base: origin/dev = 5d33f7cdd5ab0ae7527befde92835f4b342556b5 (2026-11-04 10:47 +01:00, "Merge decisions-d344"). I read all of DECISIONS.md (3,932 lines, D1–D344). Line numbers below are lines of that file at that SHA.
Method: full read, then git grep on origin/dev for the product-visible claims. No builds, no edits, nothing pushed. Code checks are greps, not behaviour tests.
Counts: (a) changes behaviour: 18 · (b) confusing wording only: 23 · product names in code: 0 violations, 2 borderline (section 3) · code vs decision notes: 4 (section 2).
Each item ends with a proposed one-line resolution and my recommendation. The owner decides.


1(a). Contradictions that change behaviour

A1. API-key lifetime has four incompatible models: D311, D333, D337, D342/D343.

  • D311 amendment (l.3404–3417): keys "expire after 30 days without use by default", "Authenticated use renews silently", "Rejected: permanent keys". It is a sliding idle lifetime, and api_key_lifetime_days is 1–365.
  • D333 (l.3743–3745): the approval page shows "lifetime (short by default, for example 24 hours)".
  • D337 (l.3807–3808): "Keys from before D333 end within 24 h at upgrade … and a key's lifetime is a hard end ("yes")".
  • D342 (l.3899–3900): "unlimited expiry keys are allowed". D343 (l.3920–3921): "Any API key may have no expiry".
  • Conflict: D343 vs D337 is reconcilable ("hard end" applies only to keys that have an expiry). D311 does not fit. It says renewal on use and rejects permanent keys, and neither the amendment nor its default carries a "superseded" note. Code on dev (migrations/0034_api_key_expiry.sql: default 30, CHECK BETWEEN 1 AND 365) still implements D311 only.
  • Resolution: one "API key lifetime" decision. The expiry is chosen when the key is approved (default 24 h), is a hard end, and is never extended by use. "No expiry" is an explicit choice that needs the per-action tap (D333), and D311's amendment is marked superseded.
  • Recommendation: do this. It keeps D337 and D343 both true.

A2. D230 says sign-in asks for a username; D98 and the code say it never does.

  • D230 (l.2057–2058): "Sign-in asks only for the username, then the passkey."
  • D98 (l.414–417): "one usernameless 'Continue with passkey' button … typing a username is never required". D95 (l.399–400) agrees. Code: apps/tracer/src/onboarding.ts:72 has only "Continue with passkey".
  • D239 Q33 (l.2250–2251) also says the server's page asks "for username and passkey".
  • Resolution: amend D230 and D239 Q33 to "username only at setup; sign-in is usernameless (D98)".
  • Recommendation: do this. The code already behaves that way.

A3. Linux WireGuard engine: the D163 amendment says GotaTun, D332 says kernel stays the default.

  • D163 amendment (l.1034–1046): "Linux nodes run the userspace GotaTun engine … Kernel WireGuard stays selectable per node only as a transition fallback."
  • D332 (l.3729–3736, "amends D163"): "kernel WireGuard stays the Linux default until the integrated GotaTun engine matches it".
  • D169 (l.1117–1120) and D313 Q1 (l.3439–3441: "direct paths stay entirely in the kernel WireGuard socket") also assume kernel. D316 (l.3545) says the same.
  • Conflict: D163's text was never edited, so it still reads as current.
  • Resolution: change D163's amendment to "Linux default is kernel until D332's gate; GotaTun is the target engine".
  • Recommendation: do this.

A4. How often a passkey tap is asked: D336 vs D333 (and D209, D274, D28).

  • D333 (l.3747–3748): "In the browser, one tap starts 15 minutes of admin actions without taps." D339 (l.3833–3834) relies on that window.
  • D336 (l.3775–3776): "Each passkey tap is bound to the one action shown on screen, not to 'any tap in the last 5 minutes'".
  • D209 (l.1744–1745): "saving needs a fresh passkey". D274 (l.2825): "Passkey at the last step, always". D28 (l.110–111): "A destructive UI action needs a fresh passkey tap". D333 refines D28 only.
  • Conflict: it is unclear whether the 15-minute window exists, and which of D209 and D274 sit inside it.
  • Resolution: state that D336 covers the per-action list in D333 plus key/install approvals, and the 15-minute window covers the rest of browser admin. Name D209 (inside the window) and D274 downgrade (outside).
  • Recommendation: do this, with those two placements.

A5. Mesh SSH default: D344 vs D197 and D289.

  • D344 (l.3932): "Mesh SSH stays off by default until its security review".
  • D197 M1 (l.1502–1507): add-host "gets the agent, which enables SSH and relay". D289 (l.3029–3031): adding a host "switches it to our SSH automatically, so every node needs it".
  • Resolution: say that D344 gates the mesh SSH listener only, and that D197's "enables SSH" and D289's switch wait for that gate.
  • Recommendation: do this.

A6. macOS shells: D335 vs D337.

  • D335 (l.3762–3765): a shell "runs inside the person's own login session while they are logged in, with their keychain, ssh-agent and privacy permissions".
  • D337 (l.3798–3800): "Mac shells join the person's session only for a screen or VNC login, because macOS unlocks the keychain only then".
  • Resolution: mark D337 as amending D335 and edit D335.
  • Recommendation: do this. D337 is the later and more specific answer.

A7. CLI attach: D303 vs D311 and D314.

  • D303 (l.3267–3268): sessions (list, attach, send, read, close).
  • D311 (l.3358–3361): "no terminal attach". D314 (l.3490, l.3506–3507): "automation only, never interactive … attach is never part of it".
  • Resolution: remove "attach" from D303 and add "names changed by D314".
  • Recommendation: do this.

A8. Session reaping: D56 vs D178.

  • D56 (l.237–239): a session unviewed for 7 days "gets a warning and is then reaped".
  • D178 (l.1233–1234): "Detached sessions are never ended automatically by default; an optional per-host idle limit exists."
  • Code on dev has no reaping, so it matches D178.
  • Resolution: mark D56 as superseded by D178.
  • Recommendation: do this.

A9. D41's "hard CI gates" have drifted.

  • D41 (l.178–179, l.184): "Keystroke-to-echo overhead below 5 ms p99", "Reconnect after a network switch below 300 ms", "Only the owner can loosen a budget."
  • D296 (l.3158–3160) makes 5 ms "not a merge gate" without naming D41. D259, D265 and D267 set RTT-relative budgets: D267 (l.3710–3712) says "four round trips + 50 ms", about 650 ms at 150 ms RTT.
  • Resolution: rewrite D41 with the current numbers, or add "see D259/D267/D296".
  • Recommendation: rewrite it. It is the first place people look.

A10. Scale design point: D124 vs D170.

  • D124 (l.555–557): "500 concurrent terminal sessions and 50 remote desktop sessions per server, up to 5 viewers per session".
  • D170 (l.1129–1131): "1,000 nodes, 200 concurrent terminal sessions and 50 users … Every per-unit × expected count budget uses these numbers."
  • Resolution: state in D170 that 200 sessions is the budget design point and 500/50 (D124) the stress-test headroom, or replace one.
  • Recommendation: that wording.

A11. Backup key: D147 vs D186.

  • D147 (l.781–782): backup "encrypted with a key derived from the recovery words".
  • D186 (l.1372–1373, labelled "extends D147"): "a dedicated key wrapped for every admin … not one person's recovery words".
  • Resolution: relabel D186 as "amends D147" and add a pointer in D147.
  • Recommendation: do this.

A12. Log retention: D235 vs D147 and D154.

  • D147 (l.789–790) and D154 G9 (l.863–865): audit log 90 days, and G9 includes "session open and end".
  • D235 (l.2176): "The community connection log keeps 30 days."
  • Resolution: say the connection log is a separate stream with its own 30 days, or align it to 90.
  • Recommendation: align to 90 unless 30 was chosen on purpose. One number is easier to explain, and I found no recorded reason for 30.

A13. Node membership expiry: D166 vs D237 and D342.

  • D166 (l.1075–1076): "node membership never expires; nodes stay until removed".
  • D237 Q17e (l.2207–2208): "a machine offline for more than 90 days needs a passkey re-approval". D342 (l.3899–3900): machines "marked ephemeral, removed after a while offline".
  • Resolution: add "except D237 Q17e and D342 ephemeral machines" to D166.
  • Recommendation: do this. Dev's migration comment still says "Membership has no expiry".

A14. What Community includes: D32 vs D30, D154, D187 and D334.

  • D32 (l.124–125): "Community: free, one user, all features".
  • D30 (l.115): "Login through your own IdP is free". D154 G2 and G8 (l.841–842, l.860–862): local groups are free, and local accounts are created by invite link. D187 designs several people on one instance. D334 (l.3755–3757): "Invites and multi-user management live in a separate EE repository".
  • Resolution: one decision that lists what a Community server allows: one local user, OIDC users yes or no, and groups and invites yes or no.
  • Recommendation: this one needs the owner's call, because it is a tier boundary.

A15. The proxy engine is treated as Traefik, but no decision picks it.

  • D316 (l.3560–3563): the engine "is chosen by the owner after measurement: Pingora, Sozu, rpxy against Traefik as a managed sidecar".
  • D320 (l.3607–3608) says "the way it runs Traefik (D316, #281)". D330 and D336 (l.3772–3773) plan "a cgroup per Traefik child". Dev code already runs Traefik (crates/plugins/proxy, plugins/mesh/src/firewall.rs:14).
  • Conflict: CLAUDE.md says a foundational engine is never chosen inside a job. D7 ("One static binary") also does not mention managed child processes (Traefik, OpenBao).
  • Resolution: record "Traefik is the proxy engine (managed sidecar), measured on #281" in a new decision and add a D7 note on managed components.
  • Recommendation: do this, or reopen the choice if it was never made.

A16. "Overlays are never opaque": D160 vs D189 and D322.

  • D160 (l.986–989): the overlay material "is never opaque".
  • D189 (l.1416) and D322 (l.3619–3622, 3631–3632): Solid is 100% tint and floating cards follow the preset. D329 (l.3689–3691) fixes the blur floor but not the tint.
  • Resolution: amend D160 to "except the Solid preset, which is opaque by choice".
  • Recommendation: do this.

A17. One-finger sideways swipe on touch: D159 vs D275 and D329.

  • D159 (l.956–958): one finger "stops [at the end] and never drags the canvas by accident"; D177 agrees.
  • D275 touch bullet (l.2850–2851): "a sideways swipe moves between windows". D329 (l.3692–3694) amends D275: content that can scroll sideways takes the swipe first.
  • Resolution: update D275's touch bullet to D329's wording and add a pointer in D159.
  • Recommendation: do this.

A18. Appearance vocabulary: "Colors", "Colour" and "Themes" (D322, D338, D341, D80, D329).

  • D322 (l.3629–3630): "'Themes' is renamed Colors". D338 (l.3813–3814) and D341 (l.3857–3858) say "Colour", and D341 says "Presets are called Themes … Colour keeps its name".
  • "Theme" now means a preset, while D80 (l.321) "Themes are curated and gated" and D329 (l.3695–3704) still use it for colour schemes.
  • Resolution: fix the UI spelling (Colour or Color), and use "colour scheme" for the colour sets in D80/D329.
  • Recommendation: "Colour", the owner's own spelling in D338/D341.

1(b). Stale text and confusing wording only

Behaviour is already settled elsewhere. Only the older text lacks a forward pointer. Proposed single resolution for all of them: add a one-line "→ see Dxxx" under the older decision. Items where that is not enough say so.

# Older text Later text that settles it
B1 D36 l.152–153 "uses calternal's tokens"; D47 l.203–205 shared UI with calternal D62 l.254–256 "must look different from calternal"; D65
B2 D73 l.295 and D87 l.352–355: Geist Mono is the default D175 l.1169 Maple Mono NF is the default (code fonts.ts:122 agrees)
B3 D25 l.104–105: recovery waits 24 h D113 l.494–498: immediate from a device with a live session
B4 D164 l.1047–1051 and D167 l.1085: "Deny by default" D216 l.1831 (allow in Community), D242, D252; restated by D302 l.3261
B5 D183 l.1309–1310: "One connection" D214 l.1809 "replaces D183's 'one connection'"
B6 D155 l.866–885: no public edge channel, no central update server, rollback restores the backup D186 l.1360–1363 (supersedes), D295 l.3138 (narrows recovery)
B7 D64 l.264–270 and D215 l.1826–1830: agent port 7443 D291 l.3049–3060: no agent port required
B8 D259 l.2549 and D265 l.2672: "three round trips + 50 ms" D267 l.3709–3712: four
B9 D241 l.2291–2300 D315 l.3511 "replaces D241's transparent skip"
B10 D173 l.1163 (256 KiB allowance), D205, D243 first paragraph, D263/D289/D298/D301/D306/D308 "caps raised" D243 amendment l.2313–2322: caps removed
B11 D206 l.1691 (⌃⌥R/⌃⌥D split) vs D212 l.1779–1782 (⌃⌥R/⌃⌥D new window) D222 l.1917 replaces D212's key table, amends D206; D212 has no pointer
B12 D35 l.145 "Alerting stays in Grafana" D260 l.2574 "Alerting is ours (amends D35)"
B13 D34 l.133–142: slice order D92 (VNC next), D261 (observability before containers), D200/D207
B14 D322 l.3619–3628 values (Tinted 75/0, Frosted 50/100) and D191 l.1439 "Plain"/Soft/Mesh D338 l.3809–3812 (Tinted 25%, Frosted 50% blur), D341, D322 round 2 (Solid/Gradient)
B15 "tab" in D36 l.150, D52 l.227, D55 l.235 D212 l.1783–1784 "There are no tabs"
B16 D48 l.216–217 "as the user kayg"; D311 l.3397 VM macos-02-std D170 l.1135 and D229 (root); that VM no longer exists per the job brief
B17 D303 l.3266 open <machine> "a shell right here" D314 l.3483–3487: ssh opens the shell, open shows the screen
B18 D70 l.285–288 "motion stays under about 250 ms" D190 l.1431 (~500 ms), D101 l.434 (1.6 s), D140 l.704 "springy, bouncy" vs D99 amendment l.424 (no bounce): list the exceptions in D70
B19 D154, D164, D187, D211, D235 use "grant" and "policy" D252 l.2431–2433 retires both words "in product, docs or code": add one note that pre-D252 text keeps them
B20 Numbering gap: D283 is followed by D285 (no D284) Add a "D284: not used" line or confirm it is intentional
B21 D237 l.2191 "No join tokens to copy"; D253 l.2469–2471 one-time join token, waits for approval D337 l.3803–3806 and D342–D343: join permission on API keys, no approval, flag named --setup-key. D342 says "no separate key type" but D337 and D343 say "setup keys". Pick one term
B22 D327 and D329 l.3708 "The unnamed-<id> naming scheme goes away everywhere" D339 l.3835–3837 falls back to <os>-<first 6 characters of the id>. Say it is a different scheme
B23 CLAUDE.md l.17 and D31 l.120–123 "except ee/" D277 l.2871–2874 and D334: commercial code is in kayg/infraternal-enterprise; dev has no ee/ directory. CLAUDE.md is the owner's file, so only flagged

2. Decisions the code on origin/dev visibly contradicts or has not caught up with

Spot-checked: sign-in, default font, critical-path budget, socket limit, API-key default, backgrounds, Window Sizes, ee/.

  • Contradiction (found above): D230 vs sign-in (A2). Everything else I checked matched the decisions: D175 font (fonts.ts:122), D188 critical budget (transfer-budget.ts:20: 320 KiB gzip / 267 KiB brotli), D133 seven-day certificates (ca.rs), D277 (ee/ absent).
  • Not implemented yet, expected (in flight, not defects):
    • D322/D338/D341 appearance presets. packages/ui/src/themes.ts:97 still has LEVEL_NAMES = ["Solid","Tinted","Clear","Frosted"] (D189 model). apps/tracer/src/core-screen.ts:388 still has background "none" | "soft" | "mesh".
    • D214 "The server's fixed limit of 32 attachments per socket goes". apps/tracer/src/connection.ts:47–53 still says the server accepts 32 and the pool is 28. The decision calls the 28 interim, so it is consistent as a stage.
    • D343 "no expiry". 0034_api_key_expiry.sql forces 1–365 days (see A1).
    • D56 reaping: absent, which matches D178.
  • Mesh default policy: DefaultPolicy allow/deny both exist, and migrations/0020 makes NULL mean "edition default". I did not verify the edition-specific default (D216) in code.

3. Product names in code and code comments (CLAUDE.md rule)

Searched crates, apps, packages and scripts (excluding .md, vendor, lockfiles, fixtures) for comparison phrasing ("like X", "inspired by", "re-implemented", "X-style") and for names of terminals, VNC clients, UI libraries and other products. No violation found. The ~1,500 mentions of tmux, Tailscale, NetBird, Ghostty and xterm are real integrations: CLIs we call, protocols, the vendored ghostty-web build, and the D314 tmux-compatible command set.
Borderline, for the owner to rule on:

  1. scripts/perf-run:4,7,23,26 names calternal in comments about the shared perf VM and its legacy lock file. It is a factual dependency, but it is another product's name in code. Suggested: reword as "the legacy lock file" and keep the path.
  2. apps/tracer/test/appwin/measure.ts:180 uses the login account calternal@10.69.69.21 on a VM. It is a real account name, so I would leave it.
    Also crates/infraternal-shim/examples/vt_compare.rs depends on alacritty_terminal as a benchmark candidate. That is a library we build against, so it is allowed.
    D96/D99/D101 say "reimplemented" about sveltebits behaviour. That sits in DECISIONS (allowed); grep finds no such phrase in code.

4. Questions for the owner (none blocking)

The items that need a real decision rather than wording: A1 (key-lifetime model), A4 (does the 15-minute window exist), A12 (30 vs 90 days), A14 (what Community includes), A15 (confirm Traefik), A18 (Colour/Color spelling).

# DECISIONS consistency audit 2026-11-04 (J183) **Base:** `origin/dev` = `5d33f7cdd5ab0ae7527befde92835f4b342556b5` (2026-11-04 10:47 +01:00, "Merge decisions-d344"). I read all of `DECISIONS.md` (3,932 lines, D1–D344). Line numbers below are lines of that file at that SHA. **Method:** full read, then `git grep` on `origin/dev` for the product-visible claims. No builds, no edits, nothing pushed. Code checks are greps, not behaviour tests. **Counts:** (a) changes behaviour: **18** · (b) confusing wording only: **23** · product names in code: **0 violations, 2 borderline** (section 3) · code vs decision notes: **4** (section 2). Each item ends with a proposed one-line resolution and my recommendation. The owner decides. --- ## 1(a). Contradictions that change behaviour **A1. API-key lifetime has four incompatible models: D311, D333, D337, D342/D343.** - D311 amendment (l.3404–3417): keys "expire after 30 days without use by default", "Authenticated use renews silently", "Rejected: permanent keys". It is a sliding idle lifetime, and `api_key_lifetime_days` is 1–365. - D333 (l.3743–3745): the approval page shows "lifetime (short by default, for example 24 hours)". - D337 (l.3807–3808): "Keys from before D333 end within 24 h at upgrade … and a key's lifetime is a hard end ("yes")". - D342 (l.3899–3900): "unlimited expiry keys are allowed". D343 (l.3920–3921): "Any API key may have no expiry". - Conflict: D343 vs D337 is reconcilable ("hard end" applies only to keys that have an expiry). D311 does not fit. It says renewal on use and rejects permanent keys, and neither the amendment nor its default carries a "superseded" note. Code on dev (`migrations/0034_api_key_expiry.sql`: default 30, `CHECK BETWEEN 1 AND 365`) still implements D311 only. - Resolution: one "API key lifetime" decision. The expiry is chosen when the key is approved (default 24 h), is a hard end, and is never extended by use. "No expiry" is an explicit choice that needs the per-action tap (D333), and D311's amendment is marked superseded. - Recommendation: do this. It keeps D337 and D343 both true. **A2. D230 says sign-in asks for a username; D98 and the code say it never does.** - D230 (l.2057–2058): "Sign-in asks only for the username, then the passkey." - D98 (l.414–417): "one usernameless 'Continue with passkey' button … typing a username is never required". D95 (l.399–400) agrees. Code: `apps/tracer/src/onboarding.ts:72` has only "Continue with passkey". - D239 Q33 (l.2250–2251) also says the server's page asks "for username and passkey". - Resolution: amend D230 and D239 Q33 to "username only at setup; sign-in is usernameless (D98)". - Recommendation: do this. The code already behaves that way. **A3. Linux WireGuard engine: the D163 amendment says GotaTun, D332 says kernel stays the default.** - D163 amendment (l.1034–1046): "Linux nodes run the userspace GotaTun engine … Kernel WireGuard stays selectable per node only as a transition fallback." - D332 (l.3729–3736, "amends D163"): "kernel WireGuard stays the Linux default until the integrated GotaTun engine matches it". - D169 (l.1117–1120) and D313 Q1 (l.3439–3441: "direct paths stay entirely in the kernel WireGuard socket") also assume kernel. D316 (l.3545) says the same. - Conflict: D163's text was never edited, so it still reads as current. - Resolution: change D163's amendment to "Linux default is kernel until D332's gate; GotaTun is the target engine". - Recommendation: do this. **A4. How often a passkey tap is asked: D336 vs D333 (and D209, D274, D28).** - D333 (l.3747–3748): "In the browser, one tap starts 15 minutes of admin actions without taps." D339 (l.3833–3834) relies on that window. - D336 (l.3775–3776): "Each passkey tap is bound to the one action shown on screen, not to 'any tap in the last 5 minutes'". - D209 (l.1744–1745): "saving needs a fresh passkey". D274 (l.2825): "Passkey at the last step, always". D28 (l.110–111): "A destructive UI action needs a fresh passkey tap". D333 refines D28 only. - Conflict: it is unclear whether the 15-minute window exists, and which of D209 and D274 sit inside it. - Resolution: state that D336 covers the per-action list in D333 plus key/install approvals, and the 15-minute window covers the rest of browser admin. Name D209 (inside the window) and D274 downgrade (outside). - Recommendation: do this, with those two placements. **A5. Mesh SSH default: D344 vs D197 and D289.** - D344 (l.3932): "Mesh SSH stays off by default until its security review". - D197 M1 (l.1502–1507): add-host "gets the agent, which enables SSH and relay". D289 (l.3029–3031): adding a host "switches it to our SSH automatically, so every node needs it". - Resolution: say that D344 gates the mesh SSH listener only, and that D197's "enables SSH" and D289's switch wait for that gate. - Recommendation: do this. **A6. macOS shells: D335 vs D337.** - D335 (l.3762–3765): a shell "runs inside the person's own login session while they are logged in, with their keychain, ssh-agent and privacy permissions". - D337 (l.3798–3800): "Mac shells join the person's session only for a screen or VNC login, because macOS unlocks the keychain only then". - Resolution: mark D337 as amending D335 and edit D335. - Recommendation: do this. D337 is the later and more specific answer. **A7. CLI `attach`: D303 vs D311 and D314.** - D303 (l.3267–3268): `sessions` (list, attach, send, read, close). - D311 (l.3358–3361): "no terminal attach". D314 (l.3490, l.3506–3507): "automation only, never interactive … attach is never part of it". - Resolution: remove "attach" from D303 and add "names changed by D314". - Recommendation: do this. **A8. Session reaping: D56 vs D178.** - D56 (l.237–239): a session unviewed for 7 days "gets a warning and is then reaped". - D178 (l.1233–1234): "Detached sessions are never ended automatically by default; an optional per-host idle limit exists." - Code on dev has no reaping, so it matches D178. - Resolution: mark D56 as superseded by D178. - Recommendation: do this. **A9. D41's "hard CI gates" have drifted.** - D41 (l.178–179, l.184): "Keystroke-to-echo overhead below 5 ms p99", "Reconnect after a network switch below 300 ms", "Only the owner can loosen a budget." - D296 (l.3158–3160) makes 5 ms "not a merge gate" without naming D41. D259, D265 and D267 set RTT-relative budgets: D267 (l.3710–3712) says "four round trips + 50 ms", about 650 ms at 150 ms RTT. - Resolution: rewrite D41 with the current numbers, or add "see D259/D267/D296". - Recommendation: rewrite it. It is the first place people look. **A10. Scale design point: D124 vs D170.** - D124 (l.555–557): "500 concurrent terminal sessions and 50 remote desktop sessions per server, up to 5 viewers per session". - D170 (l.1129–1131): "1,000 nodes, 200 concurrent terminal sessions and 50 users … Every per-unit × expected count budget uses these numbers." - Resolution: state in D170 that 200 sessions is the budget design point and 500/50 (D124) the stress-test headroom, or replace one. - Recommendation: that wording. **A11. Backup key: D147 vs D186.** - D147 (l.781–782): backup "encrypted with a key derived from the recovery words". - D186 (l.1372–1373, labelled "extends D147"): "a dedicated key wrapped for every admin … not one person's recovery words". - Resolution: relabel D186 as "amends D147" and add a pointer in D147. - Recommendation: do this. **A12. Log retention: D235 vs D147 and D154.** - D147 (l.789–790) and D154 G9 (l.863–865): audit log 90 days, and G9 includes "session open and end". - D235 (l.2176): "The community connection log keeps 30 days." - Resolution: say the connection log is a separate stream with its own 30 days, or align it to 90. - Recommendation: align to 90 unless 30 was chosen on purpose. One number is easier to explain, and I found no recorded reason for 30. **A13. Node membership expiry: D166 vs D237 and D342.** - D166 (l.1075–1076): "node membership never expires; nodes stay until removed". - D237 Q17e (l.2207–2208): "a machine offline for more than 90 days needs a passkey re-approval". D342 (l.3899–3900): machines "marked ephemeral, removed after a while offline". - Resolution: add "except D237 Q17e and D342 ephemeral machines" to D166. - Recommendation: do this. Dev's migration comment still says "Membership has no expiry". **A14. What Community includes: D32 vs D30, D154, D187 and D334.** - D32 (l.124–125): "Community: free, one user, all features". - D30 (l.115): "Login through your own IdP is free". D154 G2 and G8 (l.841–842, l.860–862): local groups are free, and local accounts are created by invite link. D187 designs several people on one instance. D334 (l.3755–3757): "Invites and multi-user management live in a separate EE repository". - Resolution: one decision that lists what a Community server allows: one local user, OIDC users yes or no, and groups and invites yes or no. - Recommendation: this one needs the owner's call, because it is a tier boundary. **A15. The proxy engine is treated as Traefik, but no decision picks it.** - D316 (l.3560–3563): the engine "is chosen by the owner after measurement: Pingora, Sozu, rpxy against Traefik as a managed sidecar". - D320 (l.3607–3608) says "the way it runs Traefik (D316, #281)". D330 and D336 (l.3772–3773) plan "a cgroup per Traefik child". Dev code already runs Traefik (`crates/plugins/proxy`, `plugins/mesh/src/firewall.rs:14`). - Conflict: CLAUDE.md says a foundational engine is never chosen inside a job. D7 ("One static binary") also does not mention managed child processes (Traefik, OpenBao). - Resolution: record "Traefik is the proxy engine (managed sidecar), measured on #281" in a new decision and add a D7 note on managed components. - Recommendation: do this, or reopen the choice if it was never made. **A16. "Overlays are never opaque": D160 vs D189 and D322.** - D160 (l.986–989): the overlay material "is never opaque". - D189 (l.1416) and D322 (l.3619–3622, 3631–3632): Solid is 100% tint and floating cards follow the preset. D329 (l.3689–3691) fixes the blur floor but not the tint. - Resolution: amend D160 to "except the Solid preset, which is opaque by choice". - Recommendation: do this. **A17. One-finger sideways swipe on touch: D159 vs D275 and D329.** - D159 (l.956–958): one finger "stops [at the end] and never drags the canvas by accident"; D177 agrees. - D275 touch bullet (l.2850–2851): "a sideways swipe moves between windows". D329 (l.3692–3694) amends D275: content that can scroll sideways takes the swipe first. - Resolution: update D275's touch bullet to D329's wording and add a pointer in D159. - Recommendation: do this. **A18. Appearance vocabulary: "Colors", "Colour" and "Themes" (D322, D338, D341, D80, D329).** - D322 (l.3629–3630): "'Themes' is renamed **Colors**". D338 (l.3813–3814) and D341 (l.3857–3858) say "Colour", and D341 says "Presets are called Themes … Colour keeps its name". - "Theme" now means a preset, while D80 (l.321) "Themes are curated and gated" and D329 (l.3695–3704) still use it for colour schemes. - Resolution: fix the UI spelling (Colour or Color), and use "colour scheme" for the colour sets in D80/D329. - Recommendation: "Colour", the owner's own spelling in D338/D341. --- ## 1(b). Stale text and confusing wording only Behaviour is already settled elsewhere. Only the older text lacks a forward pointer. Proposed single resolution for all of them: add a one-line "→ see Dxxx" under the older decision. Items where that is not enough say so. | # | Older text | Later text that settles it | |---|---|---| | B1 | D36 l.152–153 "uses calternal's tokens"; D47 l.203–205 shared UI with calternal | D62 l.254–256 "must look different from calternal"; D65 | | B2 | D73 l.295 and D87 l.352–355: Geist Mono is the default | D175 l.1169 Maple Mono NF is the default (code `fonts.ts:122` agrees) | | B3 | D25 l.104–105: recovery waits 24 h | D113 l.494–498: immediate from a device with a live session | | B4 | D164 l.1047–1051 and D167 l.1085: "Deny by default" | D216 l.1831 (allow in Community), D242, D252; restated by D302 l.3261 | | B5 | D183 l.1309–1310: "One connection" | D214 l.1809 "replaces D183's 'one connection'" | | B6 | D155 l.866–885: no public edge channel, no central update server, rollback restores the backup | D186 l.1360–1363 (supersedes), D295 l.3138 (narrows recovery) | | B7 | D64 l.264–270 and D215 l.1826–1830: agent port 7443 | D291 l.3049–3060: no agent port required | | B8 | D259 l.2549 and D265 l.2672: "three round trips + 50 ms" | D267 l.3709–3712: four | | B9 | D241 l.2291–2300 | D315 l.3511 "replaces D241's transparent skip" | | B10 | D173 l.1163 (256 KiB allowance), D205, D243 first paragraph, D263/D289/D298/D301/D306/D308 "caps raised" | D243 amendment l.2313–2322: caps removed | | B11 | D206 l.1691 (⌃⌥R/⌃⌥D split) vs D212 l.1779–1782 (⌃⌥R/⌃⌥D new window) | D222 l.1917 replaces D212's key table, amends D206; D212 has no pointer | | B12 | D35 l.145 "Alerting stays in Grafana" | D260 l.2574 "Alerting is ours (amends D35)" | | B13 | D34 l.133–142: slice order | D92 (VNC next), D261 (observability before containers), D200/D207 | | B14 | D322 l.3619–3628 values (Tinted 75/0, Frosted 50/100) and D191 l.1439 "Plain"/Soft/Mesh | D338 l.3809–3812 (Tinted 25%, Frosted 50% blur), D341, D322 round 2 (Solid/Gradient) | | B15 | "tab" in D36 l.150, D52 l.227, D55 l.235 | D212 l.1783–1784 "There are no tabs" | | B16 | D48 l.216–217 "as the user kayg"; D311 l.3397 VM `macos-02-std` | D170 l.1135 and D229 (root); that VM no longer exists per the job brief | | B17 | D303 l.3266 `open <machine>` "a shell right here" | D314 l.3483–3487: `ssh` opens the shell, `open` shows the screen | | B18 | D70 l.285–288 "motion stays under about 250 ms" | D190 l.1431 (~500 ms), D101 l.434 (1.6 s), D140 l.704 "springy, bouncy" vs D99 amendment l.424 (no bounce): list the exceptions in D70 | | B19 | D154, D164, D187, D211, D235 use "grant" and "policy" | D252 l.2431–2433 retires both words "in product, docs or code": add one note that pre-D252 text keeps them | | B20 | Numbering gap: D283 is followed by D285 (no D284) | Add a "D284: not used" line or confirm it is intentional | | B21 | D237 l.2191 "No join tokens to copy"; D253 l.2469–2471 one-time join token, waits for approval | D337 l.3803–3806 and D342–D343: join permission on API keys, no approval, flag named `--setup-key`. D342 says "no separate key type" but D337 and D343 say "setup keys". Pick one term | | B22 | D327 and D329 l.3708 "The `unnamed-<id>` naming scheme goes away everywhere" | D339 l.3835–3837 falls back to `<os>-<first 6 characters of the id>`. Say it is a different scheme | | B23 | CLAUDE.md l.17 and D31 l.120–123 "except `ee/`" | D277 l.2871–2874 and D334: commercial code is in `kayg/infraternal-enterprise`; dev has no `ee/` directory. CLAUDE.md is the owner's file, so only flagged | --- ## 2. Decisions the code on `origin/dev` visibly contradicts or has not caught up with Spot-checked: sign-in, default font, critical-path budget, socket limit, API-key default, backgrounds, Window Sizes, `ee/`. - **Contradiction (found above):** D230 vs sign-in (A2). Everything else I checked matched the decisions: D175 font (`fonts.ts:122`), D188 critical budget (`transfer-budget.ts:20`: 320 KiB gzip / 267 KiB brotli), D133 seven-day certificates (`ca.rs`), D277 (`ee/` absent). - **Not implemented yet, expected (in flight, not defects):** - D322/D338/D341 appearance presets. `packages/ui/src/themes.ts:97` still has `LEVEL_NAMES = ["Solid","Tinted","Clear","Frosted"]` (D189 model). `apps/tracer/src/core-screen.ts:388` still has background `"none" | "soft" | "mesh"`. - D214 "The server's fixed limit of 32 attachments per socket goes". `apps/tracer/src/connection.ts:47–53` still says the server accepts 32 and the pool is 28. The decision calls the 28 interim, so it is consistent as a stage. - D343 "no expiry". `0034_api_key_expiry.sql` forces 1–365 days (see A1). - D56 reaping: absent, which matches D178. - Mesh default policy: `DefaultPolicy` allow/deny both exist, and `migrations/0020` makes NULL mean "edition default". I did not verify the edition-specific default (D216) in code. ## 3. Product names in code and code comments (CLAUDE.md rule) Searched `crates`, `apps`, `packages` and `scripts` (excluding `.md`, `vendor`, lockfiles, fixtures) for comparison phrasing ("like X", "inspired by", "re-implemented", "X-style") and for names of terminals, VNC clients, UI libraries and other products. **No violation found.** The ~1,500 mentions of tmux, Tailscale, NetBird, Ghostty and xterm are real integrations: CLIs we call, protocols, the vendored `ghostty-web` build, and the D314 tmux-compatible command set. Borderline, for the owner to rule on: 1. `scripts/perf-run:4,7,23,26` names `calternal` in comments about the shared perf VM and its legacy lock file. It is a factual dependency, but it is another product's name in code. Suggested: reword as "the legacy lock file" and keep the path. 2. `apps/tracer/test/appwin/measure.ts:180` uses the login account `calternal@10.69.69.21` on a VM. It is a real account name, so I would leave it. Also `crates/infraternal-shim/examples/vt_compare.rs` depends on `alacritty_terminal` as a benchmark candidate. That is a library we build against, so it is allowed. D96/D99/D101 say "reimplemented" about sveltebits behaviour. That sits in DECISIONS (allowed); grep finds no such phrase in code. ## 4. Questions for the owner (none blocking) The items that need a real decision rather than wording: A1 (key-lifetime model), A4 (does the 15-minute window exist), A12 (30 vs 90 days), A14 (what Community includes), A15 (confirm Traefik), A18 (Colour/Color spelling).
Author
Owner

J384 start: Part A (23 wording items from section 1(b)) and Part B (morning grill file for the 18 items in 1(a)). Branch decisions-wording-341, base a894dd877 (origin/dev, D372). Docs-only: no code, so the gate does not apply. Part B file: ~/infraternal-ops/j384-morning-341.md on compute01; the same list will be posted here.

J384 start: Part A (23 wording items from section 1(b)) and Part B (morning grill file for the 18 items in 1(a)). Branch `decisions-wording-341`, base `a894dd877` (origin/dev, D372). Docs-only: no code, so the gate does not apply. Part B file: `~/infraternal-ops/j384-morning-341.md` on compute01; the same list will be posted here.
Author
Owner

J384 finish

Part A: branch decisions-wording-341, head b61ca5c474e46f4c4158dee905f76f206ffd85f2 (one commit on origin/dev a894dd877, pushed, not merged). All 23 items got a forward pointer: 123 lines added, 0 removed, no owner quote touched. Skipped inside B10: D205 (no cap in its text) and the first paragraph of D243 (its amendment sits right under it). B23: CLAUDE.md line 17 still says "except ee/"; it is the owner's file, so I did not edit it. None of the 23 was settled by D345-D372. Docs only, so the gate does not apply.

Part B: 4 of the 18 are resolved (A3 by D346, A6 by D352, A7 by D314, A18 by D338/D341), and 14 are still open (A1 is narrowed to one question). File: ~/infraternal-ops/j384-morning-341.md on compute01. The same list follows.



A1. API-key lifetime (open, narrowed)

Settled: D333, D337 and D343 fit together (approved hard end, "no expiry" allowed). D370 gave "never expires" keys a separate inactivity setting.
Conflicting: D311's amendment still says 30 idle days, renewal on use, "permanent keys" rejected, and is not marked superseded.
Today: both rules run: idle limit api_key_lifetime_days (default 30, migration 0034) and approved hard_expires_at (default 24 h, 0044). "Never expires" keys are not built.
Options:

  1. (Rec) Keep both. One idle setting also serves D370. Mark D311's "permanent keys rejected" superseded.
  2. Idle limit only for "never expires" keys.
  3. Drop the idle limit.
    Resolution: keys have an approved hard end (default 24 h) and an idle limit; "never expires" keeps only the idle limit.

A2. Does sign-in ask for a username? (open)

Conflict: D230 and D239 Q33 say sign-in asks for the username, then the passkey. D95 and D98 say one usernameless "Continue with passkey" button.
Today: apps/tracer/src/onboarding.ts:84 has only that button. The username is asked at setup.
Why: later UI work could add a username step from the old text.
Options:

  1. (Rec) Amend D230 and D239 Q33: username at setup only, sign-in usernameless.
  2. Add a username field back (an extra step on a phone).
    Resolution: the username is asked at setup only.

A3. Linux WireGuard engine (RESOLVED by D346)

D346 (amends D332) made GotaTun the default Linux engine, with the kernel as an explicit per-node opt-out. Code agrees: EngineKind::default() is GotaTun (crates/plugins/mesh/src/gotatun_node.rs:1094). D163's amendment now reads correctly. Left over: D332's "kernel stays the default" has no pointer (wording only).

A4. Passkey tap: 15-minute window vs per-action tap (open, narrowed)

Settled already: D333's window and D336's per-action tap fit together. Both are built: ADMIN_WINDOW_SECONDS = 900 (crates/infraternal-plugin-api/src/lib.rs:60) and auth/action_tap.rs.
Still open: which tap covers D209 ("saving [access rules] needs a fresh passkey") and D274 ("Passkey at the last step, always")? Mesh inventory changes accept the 15-minute window (plugins/mesh/src/api.rs:1638), and mesh settings use a 5-minute fresh tap (settings.rs:278). I could not tell which one access-rule saves use.
Options:

  1. (Rec) D209 saves sit inside the 15-minute window. D274's root downgrade gets its own tap, added to D333's per-action list.
  2. Both always need their own tap.
  3. Both sit inside the window.
    Resolution: D209 inside the window; D274 downgrade a per-action tap.

A5. Mesh SSH default (open)

Conflict: D344 says mesh SSH stays off until its security review. D197 M1 says add-host "enables SSH and relay", and D289 says add-host switches the host to our SSH automatically.
Today: off for every node until the owner turns it on (crates/infraternal-server/src/mesh_ssh.rs:16, table mesh_ssh_hosts).
Why: add-host copy could promise SSH that is still off.
Options:

  1. (Rec) D344 gates the mesh SSH login listener only. D197's and D289's switch wait for the review.
  2. Add-host turns it on for that host after the person confirms.
  3. One global owner switch.
    Resolution: D344 gates the mesh SSH listener; D197 and D289 wait for it.

A6. macOS shells (RESOLVED by D352, last bullet)

D352 says "logged in" on a Mac means a desktop login; someone logged in only over SSH gets the plain shell. That agrees with D337. Code agrees: launchctl asuser is used only for a GUI login (crates/infraternal-shim/src/session_shell.rs:122). Left over: D335's text has no pointer (wording only).

A7. CLI attach (RESOLVED by D314)

D314 made sessions automation-only and says attach is never part of it. Code agrees: docs/CLI.md:192 says there is no CLI attach and the CLI has no attach verb. Left over: D303 still lists "attach". My B17 pointer covers open, not that word. Strike it.

A8. Session reaping (open, easy)

Conflict: D56 says a session unviewed for 7 days gets a warning, then is reaped. D178 says detached sessions are never ended automatically by default, with an optional per-host idle limit.
Today: no reaping code, which matches D178.
Why: D56 reads like a live promise.
Options:

  1. (Rec) Mark D56 superseded by D178.
  2. Restore reaping as the default.
    Resolution: D56 superseded by D178.

A9. D41's "hard CI gates" (open)

Conflict: D41 says echo overhead under 5 ms p99 and reconnect under 300 ms. D296 made 5 ms not a merge gate. D267 says reconnect after a network change costs four round trips + 50 ms.
Today: docs/PROCESS.md §6 holds other numbers: keystroke p99 is report-only, then 17 ms; reconnect is RTT×2 + 300 ms. I found no decision for those two numbers.
Options:

  1. (Rec) Rewrite D41 with the numbers you approve. Only you can loosen a budget.
  2. Add "see D259, D267, D296" and leave the numbers.
    Resolution: D41 lists the current budgets and their decisions.

A10. Scale design point (open)

Conflict: D124 says 500 terminal sessions and 50 remote desktops per server. D170 says 1,000 nodes, 200 sessions and 50 users, and every budget uses those numbers.
Today: the memory measurement runs at 200 sessions (scripts/measure-session-memory.py). The shim's hard limit is 1,000 sessions (user_host.rs:76).
Options:

  1. (Rec) 200 is the budget design point; D124's 500/50 is stress-test headroom.
  2. Size budgets for 500.
  3. Replace D124 with D170.
    Resolution: D170 states the design point; D124 is headroom.

A11. Backup key (open)

Conflict: D147 encrypts backups with a key derived from the recovery words. D186, labelled "extends D147", says a dedicated key wrapped for every admin, not one person's words.
Today: no backup code exists yet, so nothing breaks.
Options:

  1. (Rec) Relabel D186 "amends D147" and add a pointer in D147.
  2. Keep the recovery-words key.
    Resolution: the backup key is wrapped for every admin (D186).

A12. Log retention (open)

Conflict: D147 and D154 G9 keep the audit log 90 days, including session open and end. D235 says the community connection log keeps 30 days.
Today: audit retention is 90 days (crates/infraternal-server/src/api_actions.rs:68). No connection log exists yet.
Options:

  1. (Rec) One number: 90 days. I found no recorded reason for 30.
  2. State the connection log as its own stream with 30 days.
  3. A configurable default of 30.
    Resolution: all Community logs keep 90 days.

A13. Node membership expiry (open)

Conflict: D166 says node membership never expires. D237 Q17e says a machine offline over 90 days needs a passkey re-approval. D342 and D370 remove ephemeral machines after being offline (default 1 h).
Today: the migration comment says "Membership has no expiry" (0020_mesh_dataplane.sql:2). Neither exception is built. remote_recovery.rs:28 has a 90-day recovery window after certificate expiry; it may be the Q17e code, which I could not confirm.
Options:

  1. (Rec) Add both exceptions to D166.
  2. Drop Q17e; only ephemeral machines expire.
  3. Make the 90 days configurable.
    Resolution: D166 says "except Q17e and ephemeral machines".

A14. What Community includes (open, tier boundary)

Conflict: D32 says Community is one user. D30 makes OIDC login free. D154 G2 and G8 make local groups and invite links free. D187 designs several people on one instance. D334 puts invites and multi-user management in EE.
Today: the server has one owner; there is no OIDC or user invitation (the invitations route is for hosts).
Why: it sets the tier boundary.
Options:

  1. (Rec) Community is one person (passkey or their own IdP). Invites, extra users and local groups are EE.
  2. Free OIDC for any number of IdP users; invites EE.
  3. Several local users free; EE only for SCIM, group mapping and recording.
    Resolution: one decision listing what a Community server allows.

A15. Proxy engine (open, foundational)

Conflict: D316 says you choose the engine after measuring Pingora, Sozu and rpxy against Traefik. D320 and D336 already treat Traefik as the engine ("cgroup per Traefik child… let's do it"). D7 says nothing about managed child processes.
Today: crates/plugins/proxy runs Traefik children in cgroups (cgroup.rs). I found no recorded comparison; docs/design/proxy-containment.md covers containment only.
Options:

  1. (Rec) Record "Traefik is the proxy engine (managed sidecar)" and add a D7 note on managed components (Traefik, OpenBao).
  2. Run the alternatives measurement (#281) first.
  3. Switch to an embeddable Rust engine.
    Resolution: a new decision names the engine.

A16. "Overlays are never opaque" (open)

Conflict: D160 says overlay material is never opaque. D322 and D338 make Solid 100% tint, 0% blur.
Today: Solid paints no transparency or blur (packages/ui/src/themes.ts:94).
Options:

  1. (Rec) Amend D160: "except Solid, opaque by choice".
  2. Cap Solid at about 98% tint.
  3. Remove Solid.
    Resolution: D160 excepts Solid.

A17. One-finger sideways swipe on touch (open)

Conflict: D159 and D177 say one finger scrolls and at the end "never drags the canvas". D275's touch bullet and D329 (you said "agreed") say content that can scroll sideways takes the swipe first, then the canvas gets it.
Today: apps/tracer/src/canvas/camera/gestures.ts:6: one finger always scrolls; two fingers move between windows. D329 is not built.
Options:

  1. (Rec) Follow D329, your latest word. Edit D159. Check accidental drags on a real iPhone.
  2. Keep two fingers only; amend D275 and D329.
  3. Swipe moves windows only from windows with no sideways scroll.
    Resolution: D329's rule applies; D159 points to it.

A18. Appearance vocabulary (RESOLVED in substance by D338 and D341)

You spell it "Colour" (D338, D341). D341 says presets are called Themes and Colour keeps its name; D329 already says "colour schemes". Left over, wording only: D80 still says "Themes" for colour schemes, and D322 says "'Themes' is renamed Colors". The UI still says "Theme" (catalog.ts:67) because D322 and D338 are not built. One line to confirm: the UI spelling is Colour.

## J384 finish **Part A:** branch `decisions-wording-341`, head `b61ca5c474e46f4c4158dee905f76f206ffd85f2` (one commit on origin/dev `a894dd877`, pushed, not merged). All 23 items got a forward pointer: 123 lines added, 0 removed, no owner quote touched. Skipped inside B10: D205 (no cap in its text) and the first paragraph of D243 (its amendment sits right under it). B23: CLAUDE.md line 17 still says "except ee/"; it is the owner's file, so I did not edit it. None of the 23 was settled by D345-D372. Docs only, so the gate does not apply. **Part B:** 4 of the 18 are resolved (A3 by D346, A6 by D352, A7 by D314, A18 by D338/D341), and 14 are still open (A1 is narrowed to one question). File: `~/infraternal-ops/j384-morning-341.md` on compute01. The same list follows. --- --- ## A1. API-key lifetime (open, narrowed) **Settled:** D333, D337 and D343 fit together (approved hard end, "no expiry" allowed). D370 gave "never expires" keys a separate inactivity setting. **Conflicting:** D311's amendment still says 30 idle days, renewal on use, "permanent keys" rejected, and is not marked superseded. **Today:** both rules run: idle limit `api_key_lifetime_days` (default 30, migration 0034) and approved `hard_expires_at` (default 24 h, 0044). "Never expires" keys are not built. **Options:** 1. (Rec) Keep both. One idle setting also serves D370. Mark D311's "permanent keys rejected" superseded. 2. Idle limit only for "never expires" keys. 3. Drop the idle limit. **Resolution:** keys have an approved hard end (default 24 h) and an idle limit; "never expires" keeps only the idle limit. ## A2. Does sign-in ask for a username? (open) **Conflict:** D230 and D239 Q33 say sign-in asks for the username, then the passkey. D95 and D98 say one usernameless "Continue with passkey" button. **Today:** `apps/tracer/src/onboarding.ts:84` has only that button. The username is asked at setup. **Why:** later UI work could add a username step from the old text. **Options:** 1. (Rec) Amend D230 and D239 Q33: username at setup only, sign-in usernameless. 2. Add a username field back (an extra step on a phone). **Resolution:** the username is asked at setup only. ## A3. Linux WireGuard engine (RESOLVED by D346) D346 (amends D332) made GotaTun the default Linux engine, with the kernel as an explicit per-node opt-out. Code agrees: `EngineKind::default()` is GotaTun (`crates/plugins/mesh/src/gotatun_node.rs:1094`). D163's amendment now reads correctly. Left over: D332's "kernel stays the default" has no pointer (wording only). ## A4. Passkey tap: 15-minute window vs per-action tap (open, narrowed) **Settled already:** D333's window and D336's per-action tap fit together. Both are built: `ADMIN_WINDOW_SECONDS = 900` (`crates/infraternal-plugin-api/src/lib.rs:60`) and `auth/action_tap.rs`. **Still open:** which tap covers D209 ("saving [access rules] needs a fresh passkey") and D274 ("Passkey at the last step, always")? Mesh inventory changes accept the 15-minute window (`plugins/mesh/src/api.rs:1638`), and mesh settings use a 5-minute fresh tap (`settings.rs:278`). I could not tell which one access-rule saves use. **Options:** 1. (Rec) D209 saves sit inside the 15-minute window. D274's root downgrade gets its own tap, added to D333's per-action list. 2. Both always need their own tap. 3. Both sit inside the window. **Resolution:** D209 inside the window; D274 downgrade a per-action tap. ## A5. Mesh SSH default (open) **Conflict:** D344 says mesh SSH stays off until its security review. D197 M1 says add-host "enables SSH and relay", and D289 says add-host switches the host to our SSH automatically. **Today:** off for every node until the owner turns it on (`crates/infraternal-server/src/mesh_ssh.rs:16`, table `mesh_ssh_hosts`). **Why:** add-host copy could promise SSH that is still off. **Options:** 1. (Rec) D344 gates the mesh SSH login listener only. D197's and D289's switch wait for the review. 2. Add-host turns it on for that host after the person confirms. 3. One global owner switch. **Resolution:** D344 gates the mesh SSH listener; D197 and D289 wait for it. ## A6. macOS shells (RESOLVED by D352, last bullet) D352 says "logged in" on a Mac means a desktop login; someone logged in only over SSH gets the plain shell. That agrees with D337. Code agrees: `launchctl asuser` is used only for a GUI login (`crates/infraternal-shim/src/session_shell.rs:122`). Left over: D335's text has no pointer (wording only). ## A7. CLI `attach` (RESOLVED by D314) D314 made sessions automation-only and says attach is never part of it. Code agrees: `docs/CLI.md:192` says there is no CLI attach and the CLI has no attach verb. Left over: D303 still lists "attach". My B17 pointer covers `open`, not that word. Strike it. ## A8. Session reaping (open, easy) **Conflict:** D56 says a session unviewed for 7 days gets a warning, then is reaped. D178 says detached sessions are never ended automatically by default, with an optional per-host idle limit. **Today:** no reaping code, which matches D178. **Why:** D56 reads like a live promise. **Options:** 1. (Rec) Mark D56 superseded by D178. 2. Restore reaping as the default. **Resolution:** D56 superseded by D178. ## A9. D41's "hard CI gates" (open) **Conflict:** D41 says echo overhead under 5 ms p99 and reconnect under 300 ms. D296 made 5 ms not a merge gate. D267 says reconnect after a network change costs four round trips + 50 ms. **Today:** `docs/PROCESS.md` §6 holds other numbers: keystroke p99 is report-only, then 17 ms; reconnect is RTT×2 + 300 ms. I found no decision for those two numbers. **Options:** 1. (Rec) Rewrite D41 with the numbers you approve. Only you can loosen a budget. 2. Add "see D259, D267, D296" and leave the numbers. **Resolution:** D41 lists the current budgets and their decisions. ## A10. Scale design point (open) **Conflict:** D124 says 500 terminal sessions and 50 remote desktops per server. D170 says 1,000 nodes, 200 sessions and 50 users, and every budget uses those numbers. **Today:** the memory measurement runs at 200 sessions (`scripts/measure-session-memory.py`). The shim's hard limit is 1,000 sessions (`user_host.rs:76`). **Options:** 1. (Rec) 200 is the budget design point; D124's 500/50 is stress-test headroom. 2. Size budgets for 500. 3. Replace D124 with D170. **Resolution:** D170 states the design point; D124 is headroom. ## A11. Backup key (open) **Conflict:** D147 encrypts backups with a key derived from the recovery words. D186, labelled "extends D147", says a dedicated key wrapped for every admin, not one person's words. **Today:** no backup code exists yet, so nothing breaks. **Options:** 1. (Rec) Relabel D186 "amends D147" and add a pointer in D147. 2. Keep the recovery-words key. **Resolution:** the backup key is wrapped for every admin (D186). ## A12. Log retention (open) **Conflict:** D147 and D154 G9 keep the audit log 90 days, including session open and end. D235 says the community connection log keeps 30 days. **Today:** audit retention is 90 days (`crates/infraternal-server/src/api_actions.rs:68`). No connection log exists yet. **Options:** 1. (Rec) One number: 90 days. I found no recorded reason for 30. 2. State the connection log as its own stream with 30 days. 3. A configurable default of 30. **Resolution:** all Community logs keep 90 days. ## A13. Node membership expiry (open) **Conflict:** D166 says node membership never expires. D237 Q17e says a machine offline over 90 days needs a passkey re-approval. D342 and D370 remove ephemeral machines after being offline (default 1 h). **Today:** the migration comment says "Membership has no expiry" (`0020_mesh_dataplane.sql:2`). Neither exception is built. `remote_recovery.rs:28` has a 90-day recovery window after certificate expiry; it may be the Q17e code, which I could not confirm. **Options:** 1. (Rec) Add both exceptions to D166. 2. Drop Q17e; only ephemeral machines expire. 3. Make the 90 days configurable. **Resolution:** D166 says "except Q17e and ephemeral machines". ## A14. What Community includes (open, tier boundary) **Conflict:** D32 says Community is one user. D30 makes OIDC login free. D154 G2 and G8 make local groups and invite links free. D187 designs several people on one instance. D334 puts invites and multi-user management in EE. **Today:** the server has one owner; there is no OIDC or user invitation (the invitations route is for hosts). **Why:** it sets the tier boundary. **Options:** 1. (Rec) Community is one person (passkey or their own IdP). Invites, extra users and local groups are EE. 2. Free OIDC for any number of IdP users; invites EE. 3. Several local users free; EE only for SCIM, group mapping and recording. **Resolution:** one decision listing what a Community server allows. ## A15. Proxy engine (open, foundational) **Conflict:** D316 says you choose the engine after measuring Pingora, Sozu and rpxy against Traefik. D320 and D336 already treat Traefik as the engine ("cgroup per Traefik child… let's do it"). D7 says nothing about managed child processes. **Today:** `crates/plugins/proxy` runs Traefik children in cgroups (`cgroup.rs`). I found no recorded comparison; `docs/design/proxy-containment.md` covers containment only. **Options:** 1. (Rec) Record "Traefik is the proxy engine (managed sidecar)" and add a D7 note on managed components (Traefik, OpenBao). 2. Run the alternatives measurement (#281) first. 3. Switch to an embeddable Rust engine. **Resolution:** a new decision names the engine. ## A16. "Overlays are never opaque" (open) **Conflict:** D160 says overlay material is never opaque. D322 and D338 make Solid 100% tint, 0% blur. **Today:** Solid paints no transparency or blur (`packages/ui/src/themes.ts:94`). **Options:** 1. (Rec) Amend D160: "except Solid, opaque by choice". 2. Cap Solid at about 98% tint. 3. Remove Solid. **Resolution:** D160 excepts Solid. ## A17. One-finger sideways swipe on touch (open) **Conflict:** D159 and D177 say one finger scrolls and at the end "never drags the canvas". D275's touch bullet and D329 (you said "agreed") say content that can scroll sideways takes the swipe first, then the canvas gets it. **Today:** `apps/tracer/src/canvas/camera/gestures.ts:6`: one finger always scrolls; two fingers move between windows. D329 is not built. **Options:** 1. (Rec) Follow D329, your latest word. Edit D159. Check accidental drags on a real iPhone. 2. Keep two fingers only; amend D275 and D329. 3. Swipe moves windows only from windows with no sideways scroll. **Resolution:** D329's rule applies; D159 points to it. ## A18. Appearance vocabulary (RESOLVED in substance by D338 and D341) You spell it "Colour" (D338, D341). D341 says presets are called Themes and Colour keeps its name; D329 already says "colour schemes". Left over, wording only: D80 still says "Themes" for colour schemes, and D322 says "'Themes' is renamed Colors". The UI still says "Theme" (`catalog.ts:67`) because D322 and D338 are not built. One line to confirm: the UI spelling is Colour.
Author
Owner

Owner answered all 14 open items inline (2026-11-09), recorded as D374 and D375 (merged on dev as e34516d19 and 80a09ad02).

Item Answer
A1 Both clocks; a never-expiring key keeps only its idle setting
A2 Sign-in shows "Continue with: Passkey / OIDC"
A4 Both actions work inside the admin window
A5 Mesh SSH stays off until the security review
A8 Idle sessions are never ended by default
A9 New 120 Hz target table in D375
A10 Design for 200 sessions, test to 500
A11 Backups use the recovery-words key (D147)
A12 Logs: 30 days, fixed, in Community; 90 days and configurable in EE
A13 Membership never expires, with two exceptions
A14 Community is one person, signing in with a passkey or their own identity provider
A15 Traefik is the proxy engine
A16 Solid is the exception to "never opaque"
A17 A swipe scrolls content only; moving the canvas is deliberate (two fingers on touch; on desktop, a trackpad swipe or a modifier key plus scroll, the modifier still to be measured)

Follow-up implementation jobs are being opened.

**Owner answered all 14 open items inline (2026-11-09), recorded as D374 and D375** (merged on dev as `e34516d19` and `80a09ad02`). | Item | Answer | |---|---| | A1 | Both clocks; a never-expiring key keeps only its idle setting | | A2 | Sign-in shows "Continue with: Passkey / OIDC" | | A4 | Both actions work inside the admin window | | A5 | Mesh SSH stays off until the security review | | A8 | Idle sessions are never ended by default | | A9 | New 120 Hz target table in D375 | | A10 | Design for 200 sessions, test to 500 | | A11 | Backups use the recovery-words key (D147) | | A12 | Logs: 30 days, fixed, in Community; 90 days and configurable in EE | | A13 | Membership never expires, with two exceptions | | A14 | Community is one person, signing in with a passkey or their own identity provider | | A15 | Traefik is the proxy engine | | A16 | Solid is the exception to "never opaque" | | A17 | A swipe scrolls content only; moving the canvas is deliberate (two fingers on touch; on desktop, a trackpad swipe or a modifier key plus scroll, the modifier still to be measured) | Follow-up implementation jobs are being opened.
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#341
No description provided.