docs: editing code lives in the repo's config; campus names; unwatched connections stay

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 15:25:44 -04:00
co-authored by Claude Fable 5.1
parent 991006d8a3
commit ea625a470d
3 changed files with 35 additions and 35 deletions
+12 -12
View File
@@ -19,7 +19,7 @@ Values are typed once: every string that names a program, path, task or helper l
| `site.id` NEW | Site setup > Site (Advanced) | Site id | Short name for folders and files ("na"). | `[a-z0-9_-]+`; restart |
| `site.name`, `site.ingest_name`, `site.ingest_hostname`, `site.ingest_ips[]` NEW | Site setup > Site | Site name, this Mac's name, network name, addresses | Used in log headers, alert text, key notes and generated setup scripts (replaces "NA Ingest" and the IPs in windows-setup.ps1). [Check this Mac's network ports] fills the addresses. | non-empty; IPs valid; restart (a changed note alone never forces a re-import, D11) |
| `site.settings_port` NEW | Site setup > Site (Advanced) | Port | Where this page listens: loopback plus every private-address port, narrowed by this Mac's LAN list. Default 8020. | 1024-65535; the dialog states the consequence (the page moves to the new address); settings |
| editing code (keychain item `status-settings-edit-key`, 0600 file fallback `edit-key` in the data dir) NEW | Site setup > Site (Advanced) | "editing code: set <date>", [Make a new editing code] | Random word-list code the settings agent makes on its first start in the logged-in session; shown only on this Mac's screen at `http://127.0.0.1:8020/edit-key`; browsers remember it with the person's first name. The one code: no admin code, no address list. | never shown over the LAN; the dialog says every browser must enter the new code; settings |
| `site.editing_code` NEW | Site setup > Site (Advanced) | Editing code | The code that switches Editing on. It is a setting in this campus's `config.json`, kept in the private git repo, so everyone with the repo has it (the user's choice, 2026-10-13); browsers remember it with the person's first name. Masked in every API response unless the request carries it. The one code: no admin code, no address list, no page-generated code. | non-empty; changing it: the dialog says every browser must enter the new code; none |
| `site.live_services[]` NEW | Site setup > Schedules | Live services | When a service is on: Apply, Run now, Restart the watchdog, Re-import, Check the switcher and Install are refused and your draft stays saved; auto-fix may run; alarms are always on. Defaults to `auto_fix.windows`; the Phase 0 migration writes it. | days in mon..sun; `HH:MM`; end <= start = runs past midnight (allowed); warn if empty; restart (ServiceMode is built at start) |
| built-in live signal: a watched HyperDeck that "follows the recording" is recording | Site setup > Schedules ("Also counts as live", read-only line with "Live now?") | - | Derived from every hyperdeck check with "follows the recording"; the watchdog writes `live-now.json` on change / every 5 s with a timestamp; older than 30 s = schedule only (said in Review). No connection/variable/value triple on the page; "OBS is streaming" is added as a built-in kind only if the Phase 1 spike finds the module variable. | DNE; warn when no watched deck follows the recording |
| `site.contacts` NEW | Site setup > Site | Contacts | Printed on the cheat sheet. | free text; none |
@@ -193,12 +193,12 @@ The other NA records: **prop** (sshd, DVS daemon NA-ProPresenter-Mac, helper_dvs
## D. Safety and access
- **Who can read and change what.** The server binds IP addresses, not interface names: at start it resolves the IPv4 address of each LAN-use interface in `hosts.ingest.networks` from `ifconfig` (every private-address interface when the list is empty, with a grey notice), re-checks every minute and rebinds on change; 127.0.0.1 is always bound and is the only listener that serves `/edit-key`; never 0.0.0.0, and it answers only private ranges (10/8, 172.16/12, 192.168/16, 169.254/16, 127/8); everything else gets 403. **Everything is readable on the LAN without the code**: Overview, Keys, Computers (inventories, generated scripts, the Dante map), Logs, History (list and bodies), the key guide, Help and every Site setup screen, with the alert web hook masked. **Every change needs the editing code** (`X-Edit-Key` header equal to the code in this Mac's keychain item `status-settings-edit-key`, with the 0600 file fallback), a `Host`/`Origin` header naming this server (CSRF), and a name; lockout for 10 minutes after 5 wrong tries; every write is logged with address and name and History records "changed by <name> from <address>". The code is shown only at `http://127.0.0.1:8020/edit-key` on the Ingest Mac's own screen (404 on a LAN socket) and is rotated from Site setup > Site > Advanced. Changing the port or the code shows a dialog that states the consequence. There is no admin code, no address allow-list and no second code; the editing code is not a password for any other system and is never typed by Claude.
- **Who can read and change what.** The server binds IP addresses, not interface names: at start it resolves the IPv4 address of each LAN-use interface in `hosts.ingest.networks` from `ifconfig` (every private-address interface when the list is empty, with a grey notice), re-checks every minute and rebinds on change; 127.0.0.1 is always bound; never 0.0.0.0, and it answers only private ranges (10/8, 172.16/12, 192.168/16, 169.254/16, 127/8); everything else gets 403. **Everything is readable on the LAN without the code**: Overview, Keys, Computers (inventories, generated scripts, the Dante map), Logs, History (list and bodies), the key guide, Help and every Site setup screen, with the alert web hook masked. **Every change needs the editing code** (`X-Edit-Key` header equal to `site.editing_code` in the live config), a `Host`/`Origin` header naming this server (CSRF), and a name; lockout for 10 minutes after 5 wrong tries; every write is logged with address and name and History records "changed by <name> from <address>". The code lives in `config.json` in the private git repo, so everyone with the repo has it; it is masked in every response unless the request carries it, and is changed from Site setup > Site > Advanced or in the repo. Changing the port or the code shows a dialog that states the consequence. There is no admin code, no address allow-list and no second code; the editing code is not a password for any other system and is never typed by Claude.
- **Commands never run from the page before Apply.** `fixes.*.run`, `remote.cmd`, `process.match` and `pref.*` are "command edits": generated by builders, shown in red in Review, executed only by the watchdog after Apply; a raw Custom command (Advanced) asks for the editing code again before it is accepted into the draft. Probes run only the fixed set in `probes.py` with shlex-quoted / regex-checked parameters. "Run this fix now" never calls `execute_fix` in the settings process: it sends `test <key> <fix> <identity> <time>` on the command variable (`sp_cmd`), exactly as a hold arrives, and the watchdog resolves the check from that key (deck address from the snapshot, MIDI address from the last-seen peer address, recording-deck guard, cooldown, one MIDI fix at a time), logs it as TEST, and refuses a `test` whose fix is attached to no check on that key, a netgear_poe / dante-auto / atem fix, a recording deck, or a bare `test <fix>`; it is offered only for the start/reconnect kinds (start-only Windows/Mac/helper/DVS builders, Reconnect a Companion connection, Switch a HyperDeck off and on, Reconnect a MIDI peer, Play the Spotify playlist) and never during a live service. "Check the Dante network now" sends the `resurvey` verb the same way. The page never writes a runtime file the watchdog also writes: `dante-map.json`, `live-now.json`, `fixes-running.json`, `started.json`, `state.json` and the command variable are the watchdog's alone.
- **Server surface.** `GET /api/logs/<name>` serves only names on its list; `/api/live/spotify?url=` fetches only `open.spotify.com`; ATEM uploads are capped at 20 MB, XML with a DOCTYPE or entity declarations is refused, and the file is parsed with the watchdog's own parser in the settings process; the Companion DB reader returns label, module id and `config.host` only (the module version only if the spike finds it trivially; no surfaces, never a raw row); every page export is fetched with `includeSecrets=false`.
- **Secrets:** presence only (keychain items exist + account, login key exists, public key text, alert web hook "set, N chars"). `alert_webhook` and any key named `*token`, `*secret`, `*password` are masked in every API response, diff, History meta and site bundle, and every file the settings agent writes in the data dir (`draft.json`, `history/*`, `machines-live.json`, `settings.log`, the `edit-key` fallback) is created 0600; the real value lives only in `config.json` and is re-attached on restore. The page never reads, shows or writes the Netgear password, never stores a credential for any computer or for git, and never runs `security add-generic-password` or makes a key for a person; it shows the exact line to type. The only `ssh-keygen` calls it makes are the confirmed `-R <ip>` (Forget the old login key) and the confirmed creation of the panel's own login key pair (Make the login key).
- **Secrets:** presence only (keychain items exist + account, login key exists, public key text, alert web hook "set, N chars"). `alert_webhook`, `site.editing_code` and any key named `*token`, `*secret`, `*password` are masked in every API response, diff, History meta and site bundle, and every file the settings agent writes in the data dir (`draft.json`, `history/*`, `machines-live.json`, `settings.log`) is created 0600; the real value lives only in `config.json` and is re-attached on restore. The page never reads, shows or writes the Netgear password, never stores a credential for any computer or for git, and never runs `security add-generic-password` or makes a key for a person; it shows the exact line to type. The only `ssh-keygen` calls it makes are the confirmed `-R <ip>` (Forget the old login key) and the confirmed creation of the panel's own login key pair (Make the login key).
- **Never automated:** setup scripts on other computers (ARD / PowerShell by the user, guideline 12), firewall or system changes, BIOS WoL, Remote Login, macOS permissions (Automation, Accessibility), creating Companion custom variables or triggers unless the Phase 1 tRPC spike proves it safe (then only with explicit confirmation and never a full import), module imports, `push_page --force`, full Companion imports, rebooting any device, power-cycling a PoE port or pushing the switcher file from a Run button (those fixes have no Run now), restarting the Resi bridge during a service ("never by itself"), any Apply or watchdog restart during a live service without the Emergency override, git pulls on the Ingest Mac.
- **Confirmations, one style:** a dialog that states the consequence and asks once; a typed word only for the Emergency override during a live service (`LIVE` + reason) and for Stop the watchdog (`STOP`: "the deck goes purple and every alarm is off until Start"). The dialogs: deleting a computer, key, fix or check shows everything that refers to it ("Delete Lighting PC and also remove 3 fixes, 4 checks, 1 restart key?"); "Run this fix now" names the computer or device; Restart the watchdog; Go back to this / Undo; a page-number change before the old page is cleared; a saved switcher file must be ticked after its diff is shown; Undo the page-number buttons on every page (rewrites 97 pages); Prune history (with the count); Re-import the pages now; Install the staged folder (shows the staged version); Forget the old login key (names the computer); Make the login key; Make a new editing code and changing the port (state the consequence); a Custom command edit asks for the editing code again.
- **Confirmations, one style:** a dialog that states the consequence and asks once; a typed word only for the Emergency override during a live service (`LIVE` + reason) and for Stop the watchdog (`STOP`: "the deck goes purple and every alarm is off until Start"). The dialogs: deleting a computer, key, fix or check shows everything that refers to it ("Delete Lighting PC and also remove 3 fixes, 4 checks, 1 restart key?"); "Run this fix now" names the computer or device; Restart the watchdog; Go back to this / Undo; a page-number change before the old page is cleared; a saved switcher file must be ticked after its diff is shown; Undo the page-number buttons on every page (rewrites 97 pages); Prune history (with the count); Re-import the pages now; Install the staged folder (shows the staged version); Forget the old login key (names the computer); Make the login key; changing the editing code or the port (state the consequence); a Custom command edit asks for the editing code again.
- **Watchdog restart safety:** the Engine keeps a registry of running fix threads (every thread started in `run_plan`, `run_force` and the test verb) and publishes it as `fixes-running.json`; its SIGTERM handler sets the stop event, refuses new fixes (hold, force, auto or test answer RESTARTING SOON on the key) and joins the registry for up to ExitTimeOut-5 s before exiting; `install.sh` writes the derived `ExitTimeOut` (max fix timeout + verify + 10, floor 60; today launchd's deadline is 5 s) into the watchdog plist before the handler ships; Apply waits until the registry is empty (60 s) before the kickstart; a HyperDeck off/on fix re-enables the connection in a `finally`, so a kill between its off and on never leaves a deck connection switched off; acknowledged keys, auto-fix tries and fix cooldowns persist in `state.json` keyed by (key, check identity) so a restart never re-runs a fix early or forgets an acknowledgement; the command variable is cleared once at start. There is no SIGHUP partial reload and no stop flag: every Apply is a full restart on the new config.
- **Guideline fit:** no per-key colour options in the schema; the legacy `service_only` flag sits under Advanced with a warning and has no effect while alarms are armed all day; the alarms window is not on the page at all (1); one schedule, labelled so only auto-fix and the change guard use it and alarms stay armed, the one sentence everywhere (2); the validator warns when a key has nothing to fix and no "also when green", and the HOLDING 2 s WILL line shows the answer (3); restart keys always have a fix and the watch list keeps them flashing (4); builders generate real, tested fixes with a guarded Run now (5, 6); debounce defaults stay 3/1/0 s (7); the gentle list is enforced by load_config and "never by itself" (8); page-number buttons re-run inside Apply whenever a page changes, and the Companion section counts "coloured on N of M pages" when opened, with [Colour them now] (9); the live-service guard uses the clock and the built-in recording signal, refuses rather than queues, leaves the live config untouched and has one logged override (10); no passwords (11); scripts only shown (12); the repo stays private (13); help texts short (14); Apply waits for proof (`started.json`, verified import) before saying done (15); History and the generated key guide replace most README tables (16).
- The settings server imports only `load_config`, `CHECKS`, `RESTARTABLE`, `FIX_KINDS`, the ATEM parser and `make_page.build` from the watchdog code (never `run_once`), so a bug in the page cannot take the panel down; it runs as the GUI user with no sudo, as a separate process, so the page keeps working while the watchdog restarts or when the panel is purple.
@@ -216,11 +216,11 @@ schema.py the registry: top-level keys, site/host/run/gear re
validate(cfg, live=None) -> errors, warnings; plain_diff(a, b); mask_secrets(obj); phrases(cfg) (the
"may run by itself ..." and window sentences built from the config)
settings/__init__.py adds the parent folder to sys.path so status_panel/make_page import from the data dir
settings/server.py stdlib ThreadingHTTPServer bound to IP addresses, not interface names: 127.0.0.1 always (the only listener
that serves /edit-key) plus the IPv4 address of each LAN-use interface in hosts.ingest.networks resolved
settings/server.py stdlib ThreadingHTTPServer bound to IP addresses, not interface names: 127.0.0.1 always (always)
plus the IPv4 address of each LAN-use interface in hosts.ingest.networks resolved
from ifconfig at start (every private-address interface when the list is empty), re-checked every minute
and rebound on change; one thread per address; editing code + Origin check on every write; reads open
GET / /app.js /style.css /key-guide.html /cheat.html /where.html GET /edit-key (127.0.0.1 only)
GET / /app.js /style.css /key-guide.html /cheat.html /where.html
GET /api/schema /api/config (masked) /api/draft (ETag) PUT /api/draft (If-Match, 409 with the field) DELETE /api/draft
POST /api/validate /api/apply (lock; refused while live unless the body carries the Emergency word + reason)
POST /api/restore/<id> /api/undo
@@ -259,11 +259,11 @@ install.sh + --site / --config / --keep-config / --force-site-
remote-access/*.template, companion-objects/, atem_state.js; writes both LaunchAgents (watchdog plist with the
derived ExitTimeOut, settings plist with AbandonProcessGroup); restarts the watchdog first and waits for
started.json, the settings agent last (detached); never overwrites site-owned files once the page exists;
prints the /edit-key address; writes apply.log + a History entry
prints the page address; writes apply.log + a History entry
tests/test_schema.py test_wording.py test_apply.py test_probes.py test_settings.py (beside the existing tests/test_status_panel.py)
```
The page tools (`make_page.py`, `push_page.py`, `nav_keys.py`) run from and write to the **data dir** on the Ingest Mac (they load `HERE/config.json`; `nav_locations.json` and the `.companionconfig` outputs live there). The data dir holds, besides today's `config.json`, `atem-reference.xml`, `atem-reference.json`, `dante-map.json`, `nav_locations.json`, the two built pages, `status-panel.log`, `status-panel.err.log` and the `config.json.bak-*` files: `draft.json`, `history/` (+ `history/atem/<date>/`), `apply.log`, `apply.lock` (the server-side Apply lock: a second Apply is refused with "an Apply is already running"), `machines-live.json`, `settings.log`, `key-guide.html`, `build-hashes.json`, `fixes-running.json`, `live-now.json`, `started.json`, `state.json` (replaces `service-mode.json`, migrated once), `pages-out-of-sync.json`, `edit-key` (when the keychain is unavailable), and the deployed `remote-access/*.template` and `companion-objects/`. Every file the settings agent writes there (`draft.json`, `history/*`, `machines-live.json`, `settings.log`, the `edit-key` fallback) is created 0600.
The page tools (`make_page.py`, `push_page.py`, `nav_keys.py`) run from and write to the **data dir** on the Ingest Mac (they load `HERE/config.json`; `nav_locations.json` and the `.companionconfig` outputs live there). The data dir holds, besides today's `config.json`, `atem-reference.xml`, `atem-reference.json`, `dante-map.json`, `nav_locations.json`, the two built pages, `status-panel.log`, `status-panel.err.log` and the `config.json.bak-*` files: `draft.json`, `history/` (+ `history/atem/<date>/`), `apply.log`, `apply.lock` (the server-side Apply lock: a second Apply is refused with "an Apply is already running"), `machines-live.json`, `settings.log`, `key-guide.html`, `build-hashes.json`, `fixes-running.json`, `live-now.json`, `started.json`, `state.json` (replaces `service-mode.json`, migrated once), `pages-out-of-sync.json`, and the deployed `remote-access/*.template` and `companion-objects/`. Every file the settings agent writes there (`draft.json`, `history/*`, `machines-live.json`, `settings.log`) is created 0600.
**Code changes in the watchdog** (`status_panel.py`, additive, keeps its `.get(key, literal)` defaults): a `VERSION` constant, the "status panel running (code X)" line and `started.json` `{pid, started_at, config_sha}` at start; new keys read with defaults (`site.{id,name,ingest_name,ingest_hostname,ingest_ips,contacts,alert_prefix,live_services (defaults to auto_fix.windows; the migration writes it),dvs_helper,solo_dir,settings_port}`, `timing.{fix_cooldown_seconds,alert_after_seconds}`, `page_hold_ms`, `page_grid`, `page_stale_seconds`, `variables.flash`, `companion.{export_version,build,db}`, `auto_fix.{forget_seconds,keep_months}`, `dante_max_shared`, `atem.{compare,source_folder}`, `spotify.{playlist,name,open_wait,play_wait}`, `fixes.*.{never_auto,template,params}`, `checks.*.{template,params}`, `remote.shell`, `hosts.*.{name,os,role,notes,pingable,self,runs,networks,manual_done}`, `gear.*.{location,notes}`); overnight windows in `in_windows` (end <= start runs past midnight); the "HOLD 2s" and "WAIT 1 MIN" texts built from `page_hold_ms` / `fix_cooldown_seconds` (one `self.cooldown`); a fix-thread registry (started in `run_plan`, `run_force` and the test verb) that writes `fixes-running.json`; a SIGTERM handler (stop event, refuse new fixes with RESTARTING SOON, join the registry up to ExitTimeOut-5); the HyperDeck off/on fix re-enabling in a `finally`; `live-now.json` written on change / every 5 s with a timestamp from the hyperdeck checks that follow the recording (`status == record`); one atomic writer for `state.json` with acknowledgements, auto-fix tries and `fix_last` keyed by (key, check identity) per `IDENTITY`, dropped for a key whose checks changed, with `ServiceMode` merging into it (migrating `service-mode.json` once); the command variable cleared once at start (logged, a queued hold dropped); a `test <key> <fix> <identity> <unix>` verb in `handle_commands` that resolves the check from the key as `run_fixes` does (deck ip from the snapshot, MIDI address from the last-seen peer address, recording guard, cooldown) and refuses netgear_poe / dante-auto / atem / a fix not attached to that key / an identity not on the key; a `resurvey` verb; the Windows (`cmd`) variant of remote-check batching as `(cmd) >nul 2>&1 && echo SPn=0 || echo SPn=1` pairs joined with `&` (never a bare `%ERRORLEVEL%`), with cmd-batched checks refused for a PC whose DefaultShell is not cmd; wording from `site.*` and `schema.phrases` (log header, Dante log line, webhook text); fix kind `spotify` (playlist and name from `spotify.*`); `FIX_KINDS` exported and validated in `load_config`; the host validated for every fix, remote check and MIDI link (exists, has ssh, not self); `never_auto`; `EXPECTED_MODULE` per check type; `hyperdeck_probe` removed in P0 together with `TestHyperDeckProbe`/`FakeDeck` (dead since the HyperDeck hold toggles the connection); a python 3.9-3.12 self-check at start (telnetlib). No SIGHUP reload, no `then` chain, no stop flag, no commands folder poll.
@@ -287,7 +287,7 @@ The source's test 10 (`test_apply_class`) went with the apply classes and is not
12. `tests/test_wording.py`: scans `settings/ui/strings.json`, every schema help and label, and the Help pages (not the rendered DOM) for the forbidden-token list (underscored words, `--flags`, `.py/.json/.plist`, `sp_state`-style names, launchd labels, `r2c6`-style positions, bare connection labels outside a pill, "tile", "machine", "host", "builder", "edit key", "nav key", "Sunday", "North Attleboro", "always on", "detect", "Refresh") outside Advanced sections and generated text; the four chosen words (key, computer, fix, connection) and "editing code", "login key", "page-number buttons" are allowed; every button label starts with a listed verb and read-only ones with "Check"; the alarms sentence appears verbatim wherever alarms are mentioned.
13. `test_push_page_markers`: `TestPage.test_push_page_looks_at_what_is_on_the_page` extended with a page holding only our marked tiles plus the two jump keys (accepted), a page with only nav keys (accepted), a page with the expected name and one foreign key (refused, with the key's position in the reason), and one with a foreign key and another name (refused).
14. `test_test_verb`: in `TestFixes` with the stubbed executor, `test HD2 hyperdeck <identity> <now>` resolves HyperDeck_2's conn and ip, `test MIDI midi_reconnect {"host":"tracks","session":"NA Tracks","peer":"Studio B"} <now>` resolves exactly that peer's address and leaves the other five alone, a recording deck is left alone, a bare `test <fix>` is refused, an identity not on the key is refused.
15. `test_settings`: routes against the fake Companion: every read answers without the code (logs, history bodies, masked config, inventories, generated scripts) and every write is refused without it; `/edit-key` 404 on a LAN socket and 200 on loopback (the only listener that serves it); the bind set resolved to IPv4 addresses from a canned `ifconfig` with a full / empty / stale `networks` list, 127.0.0.1 always present, and a changed address rebound on the minute re-check; lockout after 5 wrong codes; 409 field-wise merge and the same-field message; a Custom command edit needs the editing code typed again; secrets masked in every response; every file the settings agent writes in the data dir (`draft.json`, `history/*`, `machines-live.json`, `settings.log`, the `edit-key` fallback) is created 0600; the Origin check refuses a write from another host.
15. `test_settings`: routes against the fake Companion: every read answers without the code (logs, history bodies, masked config, inventories, generated scripts) and every write is refused without it; the code is read from `site.editing_code` and masked in `/api/config`; the bind set resolved to IPv4 addresses from a canned `ifconfig` with a full / empty / stale `networks` list, 127.0.0.1 always present, and a changed address rebound on the minute re-check; lockout after 5 wrong codes; 409 field-wise merge and the same-field message; a Custom command edit needs the editing code typed again; secrets masked in every response; every file the settings agent writes in the data dir (`draft.json`, `history/*`, `machines-live.json`, `settings.log`) is created 0600; the Origin check refuses a write from another host.
16. Existing tests: the behaviour suite loads `tests/fixture-config.json`; `TestPage`/`TestFixes` use `find_tile(cfg, id)`; `TestHyperDeckProbe`/`FakeDeck` deleted with `hyperdeck_probe` in P0 (dead since the HyperDeck hold toggles the connection); the valid-fix-kinds assertion reads `status_panel.FIX_KINDS`; the committed-pages test points at `sites/na` after Phase 6.
17. Phase 5: `test_generate_from_data_dir`: install.sh's copy step into a temp data dir, then generate.py finds every template and fills it.
@@ -369,7 +369,7 @@ Every inventory item, and where the plan handles it or why it is deliberately no
| coremidi NETWORK_DRIVERS, client names, identity rules | DNE; "no network MIDI driver found" light on the Ingest card and in the MIDI pickers; typed fallback when a Mac cannot be read |
| make_page EXPORT_VERSION/GRID/STALE/HOLD_MS/stale texts/style/jump keys/NAV_LOCATIONS/nav_templates/hold decision/notes/output names/action ids | editable: stamps, grid, stale, hold, jump chips; hold decision = built-page hash detection with verified import (§C, active from Phase 2 as a refusal); jump keys carry markers; nav template per version from companion-objects; outputs to the data dir; the rest DNE |
| push_page defaults/import mechanics/safety rule; nav_keys defaults/walk/nav_locations.json | defaults from the config; marker-only `MARKERS` rule in the Apply path (name shortcut removed), foreign keys listed by position, refusal surfaced, never --force; export walk with the 5-miss end; "coloured on N of M pages" counted when the Companion section is opened (no periodic probe), [Colour them now] and [Undo]; per site, data dir |
| install.sh label/DEST/file list/plist/deploy path/page workflow | updated (§E): derived ExitTimeOut, AbandonProcessGroup, pre-validation, prev/ + --rollback with VERSION, watchdog first + started.json wait, settings agent last, one-shot launchd install job; Apply replaces the page workflow; `--site/--config/--keep-config/--force-site-files`; site-owned files never overwritten; the editing code made by the settings agent; phase rule (§C.7) |
| install.sh label/DEST/file list/plist/deploy path/page workflow | updated (§E): derived ExitTimeOut, AbandonProcessGroup, pre-validation, prev/ + --rollback with VERSION, watchdog first + started.json wait, settings agent last, one-shot launchd install job; Apply replaces the page workflow; `--site/--config/--keep-config/--force-site-files`; site-owned files never overwritten; the editing code read from the config; phase rule (§C.7) |
| NA Ingest key, Automation permission, keychain, DVS helper on Ingest, Companion app checklist, connection labels | Ingest card (§B) + Site setup > Companion; one Automation grant (watchdog); Make the login key |
| windows-setup.ps1 $Key/$IngestIPs/app list/task names/elevation/sshd/firewall/rtpMIDI/WoL/DefaultShell/manual steps/RESULT | template from `site.ingest_ips` + `runs` (§B), regex-checked (no `!`/`^`), deployed to the data dir; RESULT paste; elevation logic unchanged; DefaultShell set to cmd.exe (the user OKs); Get-CimInstance probes; by hand on the PC |
| mac-authorize.sh key, mac-dvs-helper.sh, install-solo-mode.sh, solo-mode-agent.swift paths/menu titles/bundle id/Accessibility | templates in the data dir incl. the swift template with `{{SOLO_DIR}}`, `{{PT_BUNDLE_ID}}`, `{{SOLO_MENU_PATH}}`; Accessibility light and re-grant note; test 17; by hand on that Mac |
@@ -395,7 +395,7 @@ Every inventory item, and where the plan handles it or why it is deliberately no
| LiveStream PC: host, WoL MAC/broadcast, sshd/firewall/keys, Start OBS elevated, OBS streaming variable | card; WoL light; BIOS tick; the OBS streaming variable is not a live signal in v1 (only the built-in recording-deck signal); "OBS is streaming" is added as a built-in kind only if the Phase 1 spike finds the module variable and its on/off values |
| Bible TV PC: host (Wi-Fi, blocks ping), sshd/firewall/keys, Start BibleApp at logon, Wi-Fi power settings | card; `pingable:false`; Wi-Fi tick + note; the page is not served on that Wi-Fi network once the LAN port is named |
| devices: ATEM, HyperDecks, LED, Tascam, X32, Shure, SuperRack, Resi decoder/encoder/cloud, gateway, Netgear switches, Studio B PoE boxes | the device list under each Map row and the **Gear** filter (one row each: address, kind, where it is, keys that watch it, light) linked from key editors, the Dante map, the ATEM section and the Help card; recording decks feed the live-service guard; the Resi encoder's HTTP answer is deliberately not a live signal (a Resi live kind is offered only if the spike finds a module variable) |
| secrets & permissions: ssh key, known_hosts (stale host key: Forget button), Netgear login, sudoers x4, Accessibility, Automation (watchdog only), Remote Login (incl. Ingest for the MacBook alias), ARD, Windows admin/execution policy, MacBook keychain, alert_webhook, editing code | lights (§B); §D never stored, masked everywhere incl. every data-dir copy (0600), re-attached on restore; by hand on each computer; web hook masked on Site; the one code made in the GUI session and shown only on loopback; lockout; command edits ask for the code again |
| secrets & permissions: ssh key, known_hosts (stale host key: Forget button), Netgear login, sudoers x4, Accessibility, Automation (watchdog only), Remote Login (incl. Ingest for the MacBook alias), ARD, Windows admin/execution policy, MacBook keychain, alert_webhook, editing code | lights (§B); §D never stored, masked everywhere incl. every data-dir copy (0600), re-attached on restore; by hand on each computer; web hook masked on Site; the one code kept in the repo's config and masked in responses; lockout; command edits ask for the code again |
| workflow: deploy, rebuild/import pages, nav keys, offline tests, --once, new ATEM file, Companion one-time setup | §C Apply; no preview run: the key editor's WHAT THIS KEY WILL SAY line shows what a key will say and validate builds both pages in memory; ATEM upload / pick-newest with diff; Companion lights; Install the staged folder (one-shot launchd job, validated, rollback-able, survives the agent restart) |
**Lens 4: operator workflow and Companion-side state**