docs: document the live Gitea Actions gate and deterministic-gen convention
gates / consistency-and-conventions (push) Successful in 43s

CI is now live (act_runner on the Gitea host), so the docs stop saying a
runner 'needs to be registered' and record how the gate works:
- AGENTS.md: CI bullet now notes the live runner, red run = merge-blocker,
  and the byte-order deterministic generator rule (LC_ALL=C, learned when the
  CI container's locale reordered the category-less pos-config/pos-tree keys
  and the gen-drift gate caught it).
- DEV.md: definition-of-done mentions the live CI re-run; new 'CI: Gitea
  Actions Gate' section (runner location, CONFIG_FILE run.sh gotcha,
  --add-host pin, one-time tokens, runnerv1 status enum 1=success/2=failure,
  deterministic-generator convention, static-only limits).
- AGENT_Context step 7: pushing re-runs the gates, red run blocks.
- AGENT_TODO: dropped a duplicated 'gate added' Done entry and fixed its
  stale 'runner pending' phrasing.
This commit is contained in:
he
2026-08-14 16:08:09 -04:00
parent b507d17e3d
commit 59e6c3530b
4 changed files with 38 additions and 7 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ CRITICAL: real guidance lives in DOC/. When you encounter a reference below, use
- **Tool model:** `bin/pos-<category>-<command>`, or **category-less** `bin/pos-<cmd>` for dispatcher/dev-level commands that fit no category (`pos-config`, `pos-tree`) — they dispatch like any tool and show with an empty category in the generated tables. `bin/pos` dispatches by longest-prefix arg matching. New tools are auto-discovered but must be executable (`100755`) and carry a `# POS: <cat> <cmd> — <desc>` header right after the shebang; `make gen` only uses the text after the first `— ` (the leading words are convention-only), so keep the one-line description concise. `# POS_FLAGS:` / `# POS_SUBCMDS:` / `# POS_CONFIG:` headers feed tab-completion and the `pos config` scope registry. A missing `# POS:` header hard-fails `make gen`. Legacy `bin/wr-*`, `mp3`, `mp4`, `vbox`, `ssh-load-all` are thin forwarders to `pos` — keep them that way.
- **Categories:** `ai`, `communication`, `docker`, `entertainment`, `media`, `network`, `share` (usb, nfs, smb), `ssh`, `system`, plus category-less `config`/`tree`. `pos tree` (bin/pos-tree) is the authoritative structure — it derives the hierarchy from `bin/pos-*` filenames + `# POS:`/`# POS_SUBCMDS:` headers.
- **Generated code:** blocks between `GEN:START`/`GEN:END` markers in `DOC/AGENT_Context_Project.md` (tree, dispatch, selfcontained, filetable, docmap) and `completions/pos.bash` (flags, subcmds, config scopes) are `make gen` output — never hand-edit them. After touching `bin/pos-*`, run `make gen`, then `make check`, then `make lint` (definition of done: check green + lint ends `0 FAIL, 0 WARN`). `make check` (`scripts/check-sync.sh`) is the self-consistency gate — bash -n + exec-bit check + doc-sync + dispatch smoke; `make lint` (`scripts/lint-conventions.sh`) is the convention gate — it enforces every rule in this file (shebang/strict-mode, exec bits, `# POS:` headers, `-h|--help` present and after deps guards, stdin-readers in `INTERACTIVE_CMDS`, POS.md coverage, plugin/app/unit/wrapper/secrets/env-seam classes — see `DOC/DEV.md → Convention Lint Gate`). Hand-maintained, not gen-checked: `DOC/POS.md`, the line-count rows above the filetable marker in `DOC/AGENT_Context_Project.md` (the non-`pos-*` files — `install.sh`, `preinstall.sh`, `postinstall.sh`, `lib/*`, `features/*`; bump a row's count only when that file's length changes), `bin/pos` usage() EXAMPLES, root README. CI: `.gitea/workflows/lint.yml` runs `make gen` + `git diff --exit-code` + `make check` + `make lint` on every push/PR (needs a registered Gitea act_runner; the gates still must be run locally lint isn't in the pre-commit hook).
- **Generated code:** blocks between `GEN:START`/`GEN:END` markers in `DOC/AGENT_Context_Project.md` (tree, dispatch, selfcontained, filetable, docmap) and `completions/pos.bash` (flags, subcmds, config scopes) are `make gen` output — never hand-edit them. Generators must be **byte-order deterministic** (sort with `LC_ALL=C`, as `scripts/gen-docs.sh` does) or CI's `git diff --exit-code` trips on a locale that collates differently. After touching `bin/pos-*`, run `make gen`, then `make check`, then `make lint` (definition of done: check green + lint ends `0 FAIL, 0 WARN`). `make check` (`scripts/check-sync.sh`) is the self-consistency gate — bash -n + exec-bit check + doc-sync + dispatch smoke; `make lint` (`scripts/lint-conventions.sh`) is the convention gate — it enforces every rule in this file (shebang/strict-mode, exec bits, `# POS:` headers, `-h|--help` present and after deps guards, stdin-readers in `INTERACTIVE_CMDS`, POS.md coverage, plugin/app/unit/wrapper/secrets/env-seam classes — see `DOC/DEV.md → Convention Lint Gate`). Hand-maintained, not gen-checked: `DOC/POS.md`, the line-count rows above the filetable marker in `DOC/AGENT_Context_Project.md` (the non-`pos-*` files — `install.sh`, `preinstall.sh`, `postinstall.sh`, `lib/*`, `features/*`; bump a row's count only when that file's length changes), `bin/pos` usage() EXAMPLES, root README. CI: `.gitea/workflows/lint.yml` runs `make gen` + `git diff --exit-code` + `make check` + `make lint` on every push/PR via the **live** Gitea act_runner (`linux-post-install`, registered on the Gitea host) — a red run means gen drift or a gate failure and is a merge-blocker; still run the gates locally too (lint isn't in the pre-commit hook).
- **Stdin gotcha:** any tool that reads stdin must be added to `INTERACTIVE_CMDS` in `bin/pos` — otherwise the logging `tee` pipe hangs on (or swallows) the prompt.
- **Deps:** apt packages → `PACKAGES` array in `preinstall.sh`; non-apt/manual installers (e.g. `usbsrv`) → `command -v <bin> || err "…"` guard inside the tool, never in PACKAGES.
- **Secrets:** never commit keys/tokens. `config/authorized_keys` and `config/rclone.conf` are gitignored; runtime tool config is `~/.config/linux_post_install/<tool>.env` (chmod 600, env-var precedence). Mask tokens in `config` output.
+1 -3
View File
@@ -22,9 +22,7 @@ summary (newest last).
- **2026-08-14** — Gitea Actions gate is now **live and green end-to-end**: act_runner (v0.6.1, labels `ubuntu-latest`) registered on `100.100.1.2` (`~/srv/gitea/runner/`, standalone compose next to the ScaleTail gitea; `CONFIG_FILE=/config.yaml` env required or `run.sh` never reads the config; `--add-host gitea.skink-platy.ts.net:100.111.241.54` so the job container reaches gitea). First real runs **caught a deterministic gen-drift**: plain `sort` in `scripts/gen-docs.sh` is locale-dependent (category-less tool keys start with `|`, which collates after letters under the CI container's locale → `pos-config`/`pos-tree` reordered), so the `git diff --exit-code` step failed. Fixed with `export LC_ALL=C` in gen-docs.sh (byte-order sort) + regenerated `DOC/AGENT_Context_Project.md` (config/tree now sort after the letter categories); `make check` OK, `make lint` 0 FAIL / 0 WARN. Live CI verdicts: the run for `e0b5b11` (workflow commit) and the empty trigger `98a767c` both **FAILED** on the drift; the run for `9d058b7` (the fix) **SUCCEEDED** (`🏁 Job succeeded`).
- **2026-08-14** — Gitea Actions gate added: `.gitea/workflows/lint.yml` runs `make gen` + `git diff --exit-code` (gen-drift) + `make check` + `make lint` on every push/PR. Verified locally the exact four steps pass (gen idempotent, check OK, lint 0 FAIL / 0 WARN). "no CI" lines updated in AGENTS.md (Quick facts → CI bullet, notes a registered act_runner is required) and DEV.md (stub harnesses note: CI runs static gates only, not behaviour suites). Gitea 1.26.4 confirmed reachable; runner setup still pending (see Next).
- **2026-08-14** — Gitea Actions gate added: `.gitea/workflows/lint.yml` runs `make gen` + `git diff --exit-code` (gen-drift) + `make check` + `make lint` on every push/PR. Verified locally the exact four steps pass (gen idempotent, check OK, lint 0 FAIL / 0 WARN). "no CI" lines updated in AGENTS.md (Quick facts → CI bullet, notes a registered act_runner is required) and DEV.md (stub harnesses note: CI runs static gates only, not behaviour suites). Gitea 1.26.4 confirmed reachable; runner setup still pending (see Next).
- **2026-08-14** — Gitea Actions gate added: `.gitea/workflows/lint.yml` runs `make gen` + `git diff --exit-code` (gen-drift) + `make check` + `make lint` on every push/PR. Verified locally the exact four steps pass (gen idempotent, check OK, lint 0 FAIL / 0 WARN). "no CI" lines updated in AGENTS.md (Quick facts → CI bullet, notes a registered act_runner is required) and DEV.md (stub harnesses note: CI runs static gates only, not behaviour suites). Gitea 1.26.4 confirmed reachable; runner registration completed the same day (see the entry above).
- **2026-08-14** — Convention-drift maintenance fix session (completed the audit backlog `MAINTENANCE.md`, M-001..M-023, all VERIFIED; gate `scripts/lint-conventions.sh` + `make lint` now 0 FAIL / 0 WARN; `make gen && make check` green). P0 bugs: M-002/003/004 added `docker-compose docker-vbox network-hotspot` to `INTERACTIVE_CMDS` (stdin/log-pipe prompt swallow); M-005 `install.sh --steps` now expands documented `N-M` ranges via `normalize_steps_spec()` (dry-run verified); M-006 feature-vs-docs decision: `--send`/`--markdown` **not restored** (health is a console-only reporter by design since fe7708f; scheduler `NOTIFY=always` covers delivery) — 5 docs corrected instead; M-007 `lib/notify.sh:57` fallback routed to stderr (stdout-leak on standalone source). P1: M-008..M-014 deps guards moved **before** `-h|--help` in docker-health/docker-ps (converted to `command -v X || err`), network-scan, share-usb-server, media-mp3/mp4 (guards before help with a `--dry-run` pre-scan preserving the documented no-deps preview); system-health documented as the sanctioned graceful-degradation no-guard pattern in DEV.md — lint refined accordingly (`first_guard_line` only matches real guards; `first_line` skips comments; precision fixes, not weakenings); M-015 system-firewall gained `usage()`+`-h|--help` (root-gated first; verified via sudo); M-016 `ffmpeg` added to preinstall PACKAGES. P2: M-017/M-018 autostart + usb-automount gained the feature-template preamble (flags.sh load, usage); M-019 `chmod +x apps/media/scrcpy.sh`; M-020 `SCALE_DIR`/`CONFIG_ENV` `:-` seams in pos-docker-compose (verified via overrides; follow-on fix: `DIM` color var missing from common.sh crashed `pos docker compose config` — added it); M-021 `CONFIG_DIR` centralized as the canonical XDG-aware seam in common.sh, per-file duplicates dropped (standalone-sourced notify.sh/config-ui.sh/matrix+telegram tools keep an identical guarded copy — "no shared lib? inline fallbacks"); M-022 `plugin_*` prefix collision resolved by renaming the internal registry helpers to `ent_plugin_*` (the documented plugin-authoring API `plugin_have`/`plugin_require`/`plugin_load_config`/`plugin_http_json` kept for user plugins); M-023 six tools (pos-config, pos-tree, pos-entertainment-{config,enable,disable,status}) now filename-referenced in DOC/POS.md. Hand-maintained AGENT_Context line-count rows bumped (install.sh 223→248, preinstall 75→76, common.sh 144→151, notify.sh 76→87 stale-corrected, autostart 14→50, usb-automount 134→138); `make lint` target wired in the Makefile. `MAINTENANCE.md` kept as the working record (uncommitted by design).
+1 -1
View File
@@ -530,7 +530,7 @@ System-wide flag store at `/usr/local/share/linux_post_install/flags/`:
4. Add system deps to `PACKAGES` array in `preinstall.sh` (if needed); non-apt/manual installers → `command -v` guard in the tool instead
5. Add config logic to `postinstall.sh` (if needed, with `.gitignore` for secrets); runtime tool config → `~/.config/linux_post_install/<tool>.env` (600)
6. Update docs: `DOC/POS.md` (section table + detail — hand-written); `DOC/HOWTO.md` index row + a section in `DOC/howto/<category>.md` (recipes/troubleshooting); `DOC/AGENT_Context_Project.md` generated sections (bin tree, dispatch table, self-contained list, line-count table) and completion flags update via `make gen` — never hand-edit between `GEN:START`/`GEN:END` markers, but hand-add a row to the "Common Tasks for Agents" table; `AGENTS.md` Quick facts if a structural fact changed; root `README.md` only if the category list changes; move the task to `AGENT_TODO.md` Done (dated) in the same commit
7. Test: `make gen && make check && make lint``make check` (bash -n + doc/code sync + smoke) and `make lint` (0 FAIL / 0 WARN, `scripts/lint-conventions.sh`) together are the definition of done; also `bin/pos help <full command> && bin/pos <category> --help`. For tools needing root/systemd/absent deps, behaviour-test via env-override paths + stub PATH (see DEV.md "Testing tools that need root / systemd / missing deps")
7. Test: `make gen && make check && make lint``make check` (bash -n + doc/code sync + smoke) and `make lint` (0 FAIL / 0 WARN, `scripts/lint-conventions.sh`) together are the definition of done; also `bin/pos help <full command> && bin/pos <category> --help`. For tools needing root/systemd/absent deps, behaviour-test via env-override paths + stub PATH (see DEV.md "Testing tools that need root / systemd / missing deps"). Pushing to Gitea re-runs the same four gates on the live Actions runner (`.gitea/workflows/lint.yml`) — a red run is a merge-blocker.
### Testing
+35 -2
View File
@@ -187,7 +187,7 @@ make check # full self-consistency gate (syntax, exec bits, d
make lint # convention gate (scripts/lint-conventions.sh) — must end 0 FAIL, 0 WARN
```
`make check` + `make lint` (0 FAIL / 0 WARN) are the definition of done. `make check` is also run as a pre-commit hook once you've run `make hook`; `make lint` is not part of the hook — run it yourself.
`make check` + `make lint` (0 FAIL / 0 WARN) are the definition of done. `make check` is also run as a pre-commit hook once you've run `make hook`; `make lint` is not part of the hook — run it yourself. Pushing to Gitea re-runs all four gates on the live Actions runner (see `CI: Gitea Actions Gate` below) — a red run is a merge-blocker.
### Testing tools that need root / systemd / missing deps
@@ -199,7 +199,7 @@ make lint # convention gate (scripts/lint-conventions.sh)
Example (session-learned): `PATH=/tmp/stubs:$PATH SMB_CONF=/tmp/smb.conf bin/pos-share-smb-server share /tmp/media …`.
Stub harnesses are **throwaway by design**: no `tests/` dir in this repo — build them outside the project (`/tmp/opencode/<tool>-test/`: `stubs/` + `run-tests.sh` with a `check "desc" "expected" "$actual"` helper and a pass/fail count), run them, then leave them in `/tmp`. Only the *pattern* above is worth keeping in the repo. (CI — `.gitea/workflows/lint.yml` — runs the *static* gates `make gen`+`git diff --exit-code`/`make check`/`make lint` on push/PR; it does not run behaviour suites.)
Stub harnesses are **throwaway by design**: no `tests/` dir in this repo — build them outside the project (`/tmp/opencode/<tool>-test/`: `stubs/` + `run-tests.sh` with a `check "desc" "expected" "$actual"` helper and a pass/fail count), run them, then leave them in `/tmp`. Only the *pattern* above is worth keeping in the repo. (CI — `.gitea/workflows/lint.yml`, live act_runner — runs the *static* gates `make gen`+`git diff --exit-code`/`make check`/`make lint` on every push/PR; it does not run behaviour suites.)
---
@@ -356,6 +356,39 @@ weaken it — and note the change in `MAINTENANCE.md`'s lint section.
---
## CI: Gitea Actions Gate
`.gitea/workflows/lint.yml` re-runs the four gates on every `push` and
`pull_request`: `make gen`, `git diff --exit-code` (gen drift), `make check`,
`make lint`. A red run is a merge-blocker; runs are visible under Gitea →
Actions.
- **Runner** — act_runner v0.6.1 (`linux-post-install`, labels `ubuntu-latest`
job image `node:20-bullseye`) is registered on the Gitea host and always on:
compose project `~/srv/gitea/runner/` (`docker compose up -d`,
`restart: unless-stopped`), standalone next to the ScaleTail gitea compose.
- **Gotchas (session-learned):**
- act_runner's `run.sh` `cd`s into `/data` and only reads the config when the
`CONFIG_FILE` env var is set — the compose service must pass
`CONFIG_FILE=/config.yaml`, not just mount the file.
- The job container can't resolve `gitea.skink-platy.ts.net` by itself; pin it
with `container.options: "--add-host gitea.skink-platy.ts.net:100.111.241.54"`
in `config.yaml`.
- Registration tokens are one-time use; the token lives in `runner/.env`
(chmod 600) and is burned after the first registration.
- Inspecting runs via sqlite: Gitea's status enum is runnerv1-consistent —
**1 = success, 2 = failure** (not the old 0/1/2/3 scheme).
- **Deterministic generators** — any script whose output is committed (gen docs,
completions) must sort in byte order: plain `sort` collates differently per
locale, and the CI container tripped exactly this (category-less tool keys
like `pos-config` start with `|`, which collated after letters under that
locale, reordering the generated tables). `scripts/gen-docs.sh` sets
`export LC_ALL=C`; keep that in mind for any new generator.
- **Limits** — CI proves the *static* gates only; it never runs behaviour suites
(stub harnesses stay throwaway in `/tmp`).
---
## Best Practices
### Alerting