From e39492801a17e08cd7751b7b80066b7164f08a45 Mon Sep 17 00:00:00 2001 From: Chris0Jeky Date: Sun, 6 Sep 2026 12:33:05 +0100 Subject: [PATCH 1/2] docs(status): twentieth block (walkthrough rulings, SC-11 sweep, SC-10 merges, Codex batch); second-pass rulings in OUTSTANDING_TASKS; Stage 1 private-instance runbook (CL-1) --- OUTSTANDING_TASKS.md | 25 ++- docs/STATUS.md | 33 ++- docs/ops/README.md | 1 + docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md | 229 ++++++++++++++++++++ docs/platform/SELF_HOST_TUNNEL_GUIDE.md | 4 + 5 files changed, 279 insertions(+), 13 deletions(-) create mode 100644 docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md diff --git a/OUTSTANDING_TASKS.md b/OUTSTANDING_TASKS.md index 02424e412..2bb681b6e 100644 --- a/OUTSTANDING_TASKS.md +++ b/OUTSTANDING_TASKS.md @@ -18,9 +18,9 @@ Last reviewed: 2026-09-06 - [ ] **RT-1 — publisher, product, domain, and route decision (`#2148`).** Record the legal publisher/product owner, exact Windows publisher string, canonical support/security domain posture, primary and fallback signing route, cost/renewal owners, and whether the work gates a release. This is a legal/owner decision; do not infer clearance or register/purchase anything. *(**2026-08-29, walkthrough q-5 = A — route decided:** SignPath Foundation first, Microsoft Artifact Signing as the paid fallback; signing gates no release before v0.3.x (v0.2.0 ships unsigned). **Still to supply:** legal publisher/product owner, exact publisher string, domain posture, cost/renewal owners — recorded on `#2148`. Agent follow-through authorized: SignPath application checklist + fail-closed unsigned rehearsal design for `#2149`/`#2150`.)* *(**2026-09-03, maintainer decision packet — rulings recorded on `#2148`:** the legal publisher/product owner is **deferred pending professional advice** (RT1_OWNER); **Microsoft Artifact Signing is now the primary route** and SignPath Foundation the fallback — this overturns the 2026-08-29 route order and matches the repository going private (RT1_SIGNING); the canonical support/security posture is a **dedicated Taskdeck domain** — registrar, exact name and purchase remain unauthorized (RT1_DOMAIN); signing **does not gate v0.3** and becomes mandatory only for paid/mature distribution (RT1_GATE). **Still to supply:** the legal owner and exact publisher string once the professional advice exists, the cost/renewal owner, and the domain itself. Box stays open.)* - [ ] **RT-2 — signing identity and protected CI enrolment (`#2149`).** After RT-1, complete the selected Artifact Signing, SignPath, or certificate-provider identity/enrolment/billing actions and approve the protected signing environment. Keep identity evidence, keys, PFX files/passwords, tokens, receipts, and recovery material out of GitHub; verify the redacted fail-closed rehearsal before checking this item. - [ ] **RT-3 — signed Windows installer acceptance (`#2150`, `#2151`).** On an exact published candidate, confirm the displayed publisher, UAC/install/upgrade/uninstall experience, data-preservation behaviour, and user-facing metadata. SmartScreen reputation QA follows a stable signed installer; a signature alone does not satisfy this subjective acceptance. -- [ ] **CL-1 — trusted private-instance decisions (`#1772`, `#1777`).** *(2026-08-30, RC deck q-4 = B: #1772 stays in v0.3; the maintainer will supply these values in a dedicated pass; prerequisites #2238 backup/restore tooling and #2239 connector-decrypt seam are **both closed as completed 2026-08-31**. The install-free hosted open beta is v0.4, #2243.)* Select the private access boundary and known users; accept Render or an explicit alternative such as Railway, the monthly budget/alerts and cost owner, the LLM payer/egress posture, backup retention/destination, connector-key custody, and clean restore target. Do not create or bill an account until authorized; this remains one instance/one SQLite volume with no public SaaS claim. *(**2026-08-29, walkthrough q-3 = A — decided, values pending:** two named accounts + InviteOnly→Closed; tunnel with an identity policy in front; host = self-host + tunnel (the 2026-08-19 q-1 ruling stands, Render stays parked on `#1777`); maintainer is sole cost owner and LLM payer with a breach action of live-providers-off; daily `backup.sh` + weekly encrypted off-platform copy; key in a password manager + offline copy, never beside the DB; restore drill into a fresh local container. **Still to supply:** the collaborator's handle, the monthly ceiling and alert threshold, the off-platform retention window — recorded on `#1772` as decided-pending-values. Stage 1 prerequisites recorded on `#1772` (`#1777` stays parked): backup tooling is absent from the production image; the restore drill needs a decrypt-verification seam; MFA stays disabled until `#1653`; an access policy fronts the tunnel. Box stays open until the values are supplied and the instance exists.)* *(**2026-09-03, maintainer decision packet — the three values are supplied:** **one named collaborator** (CL1_USERS — the collaborator's identity was given in the packet and is held by the maintainer privately; personal contact details are deliberately not recorded in this repository or on the public issue); a **£20/month all-in ceiling with a £10 alert** (CL1_BUDGET); **12 weekly encrypted off-platform copies, about 90 days** (CL1_BACKUP). ADR-0061's three "value pending" rulings now carry the values. Box stays open until the instance exists and the Stage 1 evidence is on `#1772`; deployment, account creation and billing remain human steps.)* -- [ ] **BEN-1 — private Student Pack/benefits ledger.** Privately verify eligibility, redemption window, expiry/renewal, billing transition, and exit reminders before using any GitHub Student Pack or Azure Student benefit. Keep identity/payment/account evidence private; use benefits only for disposable lab/staging work, never as Artifact Signing payment or permanent architecture. -- [ ] **DIST-1 — deferred channel enrolments.** Microsoft Store/winget and Apple Developer enrolment, agreements, billing, credentials, listings, and subjective acceptance stay deferred until a separate maintainer authorization. Do not open accounts or publish listings as part of the Windows direct-trust wave. +- [ ] **CL-1 — trusted private-instance decisions (`#1772`, `#1777`).** *(2026-08-30, RC deck q-4 = B: #1772 stays in v0.3; the maintainer will supply these values in a dedicated pass; prerequisites #2238 backup/restore tooling and #2239 connector-decrypt seam are **both closed as completed 2026-08-31**. The install-free hosted open beta is v0.4, #2243.)* Select the private access boundary and known users; accept Render or an explicit alternative such as Railway, the monthly budget/alerts and cost owner, the LLM payer/egress posture, backup retention/destination, connector-key custody, and clean restore target. Do not create or bill an account until authorized; this remains one instance/one SQLite volume with no public SaaS claim. *(**2026-08-29, walkthrough q-3 = A — decided, values pending:** two named accounts + InviteOnly→Closed; tunnel with an identity policy in front; host = self-host + tunnel (the 2026-08-19 q-1 ruling stands, Render stays parked on `#1777`); maintainer is sole cost owner and LLM payer with a breach action of live-providers-off; daily `backup.sh` + weekly encrypted off-platform copy; key in a password manager + offline copy, never beside the DB; restore drill into a fresh local container. **Still to supply:** the collaborator's handle, the monthly ceiling and alert threshold, the off-platform retention window — recorded on `#1772` as decided-pending-values. Stage 1 prerequisites recorded on `#1772` (`#1777` stays parked): backup tooling is absent from the production image; the restore drill needs a decrypt-verification seam; MFA stays disabled until `#1653`; an access policy fronts the tunnel. Box stays open until the values are supplied and the instance exists.)* *(**2026-09-03, maintainer decision packet — the three values are supplied:** **one named collaborator** (CL1_USERS — the collaborator's identity was given in the packet and is held by the maintainer privately; personal contact details are deliberately not recorded in this repository or on the public issue); a **£20/month all-in ceiling with a £10 alert** (CL1_BUDGET); **12 weekly encrypted off-platform copies, about 90 days** (CL1_BACKUP). ADR-0061's three "value pending" rulings now carry the values. Box stays open until the instance exists and the Stage 1 evidence is on `#1772`; deployment, account creation and billing remain human steps.)* *(**2026-09-06, second walkthrough pass q-31 = A: Stage 1 starts.** The runbook is `docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md` — nine ordered steps, every human step marked, the recorded values in its section 0, the restore drill before the invite, the evidence record shape; the maintainer executes it in a dedicated sitting with the coordinator. Still open here: the tunnel mechanism choice and the daily token ceiling, decided while executing.)* +- [ ] **BEN-1 — private Student Pack/benefits ledger.** *(2026-09-06, q-32 = A: still deferred.)* Privately verify eligibility, redemption window, expiry/renewal, billing transition, and exit reminders before using any GitHub Student Pack or Azure Student benefit. Keep identity/payment/account evidence private; use benefits only for disposable lab/staging work, never as Artifact Signing payment or permanent architecture. +- [ ] **DIST-1 — deferred channel enrolments.** *(2026-09-06, q-32 = A: still deferred.)* Microsoft Store/winget and Apple Developer enrolment, agreements, billing, credentials, listings, and subjective acceptance stay deferred until a separate maintainer authorization. Do not open accounts or publish listings as part of the Windows direct-trust wave. --- @@ -118,8 +118,8 @@ Analysis docs: `docs/PROJECT_TRAJECTORY.md` (strengths + path) and `docs/COURSE_ - [x] **#1274 parked-branch record — superseded by delivery.** PR #1362 shipped the Paper E2E/axe re-point and closed #1274. Do not resume the obsolete `993f188f` WIP branch; this unchecked row now awaits maintainer record review only. *(Checked off 2026-08-19 on the maintainer's explicit instruction — see the rule-3 note in the changelog.)* - [x] **#1347 / #1348 maintainer record check-off — delivered.** PRs #1360 and #1361 repaired the Paper enum wire contract and SQLite similar-past endpoint; both issues are closed. *(Checked off 2026-08-19 on the maintainer's explicit instruction — see the rule-3 note in the changelog.)* - [ ] **#1323 prompt-rail remainder** — Bind the hostile transcript/PDF/image and malformed-response fixtures from PR #1340 to PR #1312's effective prompt/parser path before GEN-04; prove grounded-task-or-empty verdicts and deterministic fallback for schema escapes. -- [ ] **#1770 — native it/es translation-quality review.** *(2026-08-23, walkthrough e-7 = B: until a speaker reviews them, the it/es locales are to be **marked machine-translated/unreviewed** in the locale switcher and docs — agent implements the caveat alongside the #1858 lazy-load PR.)* Read `frontend/taskdeck-web/src/locales/it/*` and `src/locales/es/*` before the Italian and Spanish locales are advertised as finished. The catalog guard (`src/tests/i18n/catalogs.spec.ts`) proves the three catalogs are *structurally* parallel — no missing or stale keys, no empty values, matching interpolation placeholders and plural segments — and that is all it proves; ADR-0054 records explicitly that translation quality has **no mechanical gate**. Each catalog carries a per-locale register note and a per-surface glossary to review against. Four surfaces plus Review are extracted today (PRs #1841, #1852), so the reviewable surface is bounded and will only grow with rollout step 3. Needs a speaker of the language; an agent cannot satisfy it. *(2026-08-29, walkthrough q-6 = B: keep the machine-translated caveat until a speaker pass happens; nothing downstream blocks on it.)* -- [ ] **#1821 — real-mobile keyboard verification of the `TdDialog` / `CardModal` visual-viewport binding.** PR #1864 bound `TdDialog` to the visual viewport (via the extracted `composables/useVisualViewport.ts`, shared with `CardModal`) so a software keyboard can no longer cover a dialog's Cancel/confirm actions, and replaced the E2E scope note with real bounding-box assertions in both the contracted and uncontracted cases. Those assertions run against an emulated viewport. Confirm on a **real** phone with a **real** software keyboard — open a card, trigger a nested confirmation, and check the footer actions stay reachable — including a browser without `visualViewport` support, which falls through to the `@supports (height: 100dvh)` path. An agent cannot satisfy this. *(2026-08-29, walkthrough q-7 = B: the LAN-access recipe was written first — `docs/platform/LAN_DEVICE_ACCESS_GUIDE.md` — so the check can run from exact steps; the real-phone run itself is still open.)* +- [ ] **#1770 — native it/es translation-quality review.** *(2026-09-06, second walkthrough pass q-25 = B: the caveat stays through v0.3, review deferred, no sheet prepared.)* *(2026-08-23, walkthrough e-7 = B: until a speaker reviews them, the it/es locales are to be **marked machine-translated/unreviewed** in the locale switcher and docs — agent implements the caveat alongside the #1858 lazy-load PR.)* Read `frontend/taskdeck-web/src/locales/it/*` and `src/locales/es/*` before the Italian and Spanish locales are advertised as finished. The catalog guard (`src/tests/i18n/catalogs.spec.ts`) proves the three catalogs are *structurally* parallel — no missing or stale keys, no empty values, matching interpolation placeholders and plural segments — and that is all it proves; ADR-0054 records explicitly that translation quality has **no mechanical gate**. Each catalog carries a per-locale register note and a per-surface glossary to review against. Four surfaces plus Review are extracted today (PRs #1841, #1852), so the reviewable surface is bounded and will only grow with rollout step 3. Needs a speaker of the language; an agent cannot satisfy it. *(2026-08-29, walkthrough q-6 = B: keep the machine-translated caveat until a speaker pass happens; nothing downstream blocks on it.)* +- [ ] **#1821 — real-mobile keyboard verification of the `TdDialog` / `CardModal` visual-viewport binding.** *(2026-09-06, q-28 = C: not now. Steps when ready: open the app on the phone over LAN per `docs/platform/LAN_DEVICE_ACCESS_GUIDE.md`; open a card, focus a text field so the keyboard shows, trigger a nested confirmation; check the footer Cancel/confirm stay reachable; repeat in a browser without `visualViewport` if available.)* PR #1864 bound `TdDialog` to the visual viewport (via the extracted `composables/useVisualViewport.ts`, shared with `CardModal`) so a software keyboard can no longer cover a dialog's Cancel/confirm actions, and replaced the E2E scope note with real bounding-box assertions in both the contracted and uncontracted cases. Those assertions run against an emulated viewport. Confirm on a **real** phone with a **real** software keyboard — open a card, trigger a nested confirmation, and check the footer actions stay reachable — including a browser without `visualViewport` support, which falls through to the `@supports (height: 100dvh)` path. An agent cannot satisfy this. *(2026-08-29, walkthrough q-7 = B: the LAN-access recipe was written first — `docs/platform/LAN_DEVICE_ACCESS_GUIDE.md` — so the check can run from exact steps; the real-phone run itself is still open.)* - [x] **#1552 fresh-runtime retirement check — passed 2026-08-23.** The maintainer ran `/hooks` in a fresh session at the repository root (walkthrough e-4): 4 hooks configured, **every one `[User]`-scoped** (PreToolUse/Bash, PostToolUseFailure/all, plus two user-level groups) — zero Taskdeck project handlers, confirming the #1552 retirement held at runtime. Surviving user-level hooks recorded here as the separate layer they are. The former #1456/#1538 Codex-adapter gate is closed as not planned. - [x] **Prune merged agent worktrees after inventory.** Plain `git worktree remove` is now supported for a verified-clean target (upstream [agent-harness#41](https://github.com/Chris0Jeky/agent-harness/issues/41)); inspect tracked, untracked, and ignored state first and never force removal. This checkbox remains human-owned even when agents perform and verify individual clean removals. @@ -139,8 +139,8 @@ Analysis docs: `docs/PROJECT_TRAJECTORY.md` (strengths + path) and `docs/COURSE_ - [x] **#2012 — commercial/licensing model decision before any proprietary transition.** *(Seeded 2026-08-23; audit half DELIVERED 2026-08-25 under the q-8 A authorization — posted on #2012: single-human-author history, zero external PRs ever opened, no vendored code, no copyleft direct dependency, license metadata fully consistent; the 87 AI-agent commits (Copilot 60, Claude 27) are the one authorship surface for counsel. The maintainer's statement — no external contributors accepted until the possible proprietary future is stated publicly — is recorded on the issue, and the contributions-paused notice now stands in CONTRIBUTING.md/README/LICENSING.md.)* Remaining and still open: the deliberate business-model choice (maintainer/legal-only) and, before contributions ever reopen, the inbound-rights instrument decision. *(**Ruled 2026-09-03, maintainer decision packet:** business model = **open-core + managed hosting/services** (COMM_MODEL); external contributions **stay paused, and reopening requires a relicensing-capable CLA or contribution grant first** (COMM_INBOUND). Recorded as ADR-0067 and on `#2012`, which closes on its own stop criterion. ADR-0050 and the GPL-3.0-only core are unchanged — no proprietary transition is chosen, so no ADR-0050 amendment is owed; the CLA instrument is drafted only when contributions are about to reopen.)* - [x] **#2013 — Guided five-destination IA + Agent-mode disposition.** *(RULED 2026-08-24, guided-walkthrough reply q-6 B: the five-destination Guided IA is ADOPTED; the agent workspace mode is KEPT for now — the maintainer asked for its history first, which is recorded on the closed issue (real #338/AGT-03 vision, PR #808 shipped Agents/Runs views, the 2026-04-25 sidebar IA reduction deleted the metadata reader and left the mode byte-identical). #1936/#1940-residual/#1946 execute under the decided IA; #1972 stays open re-scoped to giving agent mode an honest observable difference, with a recorded middle option — keep the enum value, drop the selector until behavior exists — the maintainer may still choose.)* - [x] **Gemini provider-removal record and BYOK follow-through (`#1879`).** *(Closed 2026-08-30: the maintainer ruled q-11 = A on the v0.3 RC deck — ADR-0055 plus the existing configuration surface and the `Verify LLM` probe is the v0.x provider-key experience; no in-app key UI. Recorded on the issue; the provider guide states it. The separate double-click startup defect is #2233.)* The executable adapter, tests, selectable configuration, DI/egress/deployment/demo surface, and active examples are removed by PR `#1887`; stale `Llm:Provider=Gemini`, `Llm:Gemini`, or a non-empty retired Compose wrapper fails with value-blind migration guidance, while historical provider strings and generic `x-goog-api-key` defenses remain. `Verify LLM` now carries a bounded server-measured probe duration into sanitized packaged evidence. The post-0.1.x provider-key UX question that kept this row open is now answered by the q-11 A ruling above (no in-app key UI in v0.x; reopen `#1879` with option B to change that). -- [ ] **Set `Llm__OpenAi__ApiKey` on the second dev machine (maintainer-only, secret).** The OneDrive/Middlesex laptop has no OpenAI key anywhere (no env vars in any scope, no user-secrets, no deploy/.env) — the existing key lives only on the primary machine's environment. Either reuse it or (better) create a second named key at platform.openai.com so each device's key can be revoked independently. Also set `Llm__EnableLiveProviders=true`, `Llm__AllowLiveProvidersInDevelopment=true`, `Llm__Provider=OpenAI`, then verify with the `Verify LLM` probe in Automation Chat. *(2026-08-29, walkthrough q-4 = B: **deferred again** — both the second-machine key and clearing this box's user-scope `Llm__OpenAi__Model=gpt-4o-mini` pin (which breaks live triage) stay open.)* -- [ ] **This OneDrive checkout cannot run the frontend test suite (maintainer-only, device-local).** Two independent blockers found 2026-08-20. (1) `frontend/taskdeck-web/.env` is an **unhydrated OneDrive cloud placeholder**: Vite's `loadEnv` fails with `UNKNOWN: unknown error, read` (errno -4094) before any spec runs, so *every* `vitest` invocation dies at startup — not just the LLM specs. `Get-Content` on the file returns "The cloud operation was not completed before the time-out period expired", and pinning it with `attrib +P -U` did not hydrate it. *How:* open OneDrive and force-download the repo folder (or mark it "Always keep on this device"), then re-run `npx vitest --run --maxWorkers=2` to confirm. (2) This box runs **Node v24.1.0** while the frontend pins **≥24.13.1** — worth updating before serious frontend work here. Until both are resolved, frontend verification on this machine has to lean on CI. *(Backend `dotnet test` is unaffected and runs normally.)* *(2026-08-29, walkthrough q-5 = B: deferred; frontend verification on that machine keeps leaning on CI.)* +- [ ] **Set `Llm__OpenAi__ApiKey` on the second dev machine (maintainer-only, secret).** *(2026-09-06, q-29 = C: not now.)* The OneDrive/Middlesex laptop has no OpenAI key anywhere (no env vars in any scope, no user-secrets, no deploy/.env) — the existing key lives only on the primary machine's environment. Either reuse it or (better) create a second named key at platform.openai.com so each device's key can be revoked independently. Also set `Llm__EnableLiveProviders=true`, `Llm__AllowLiveProvidersInDevelopment=true`, `Llm__Provider=OpenAI`, then verify with the `Verify LLM` probe in Automation Chat. *(2026-08-29, walkthrough q-4 = B: **deferred again** — both the second-machine key and clearing this box's user-scope `Llm__OpenAi__Model=gpt-4o-mini` pin (which breaks live triage) stay open.)* +- [ ] **This OneDrive checkout cannot run the frontend test suite (maintainer-only, device-local).** *(2026-09-06, q-30 = C: kept open, not now.)* Two independent blockers found 2026-08-20. (1) `frontend/taskdeck-web/.env` is an **unhydrated OneDrive cloud placeholder**: Vite's `loadEnv` fails with `UNKNOWN: unknown error, read` (errno -4094) before any spec runs, so *every* `vitest` invocation dies at startup — not just the LLM specs. `Get-Content` on the file returns "The cloud operation was not completed before the time-out period expired", and pinning it with `attrib +P -U` did not hydrate it. *How:* open OneDrive and force-download the repo folder (or mark it "Always keep on this device"), then re-run `npx vitest --run --maxWorkers=2` to confirm. (2) This box runs **Node v24.1.0** while the frontend pins **≥24.13.1** — worth updating before serious frontend work here. Until both are resolved, frontend verification on this machine has to lean on CI. *(Backend `dotnet test` is unaffected and runs normally.)* *(2026-08-29, walkthrough q-5 = B: deferred; frontend verification on that machine keeps leaning on CI.)* - [x] **Branch from an explicit base.** PR #1477 was closed-as-merged without anyone merging it. Root cause: `feat/1271-dogfooding-instrumentation` (PR #1478) was created with a bare `git checkout -b` **while standing on `fix/1123-release-dry-run`**, so it silently inherited `#1477`'s commit `0ebbf330`. Merging #1478 carried that commit onto `main`, GitHub detected `#1477`'s branch as fully merged and auto-closed it two seconds later, attributing the event to the acting token. The single-parent "rebase" appearance was GitHub recording the already-landed commit, not a rebase anyone performed. *How:* always name the base — `git checkout -b main` — and verify with `git log --oneline main..` before pushing. *(Added 2026-07-25. Supersedes an earlier item on this list that wrongly framed this as an unidentified autonomous merger — that alert was mine and it was wrong; caught by Codex on PR #1483.)* *(Acknowledged and checked off 2026-08-23, walkthrough e-2 — the lesson stays recorded here.)* ## I. Context Fabric wave (2026-08-30, ADR-0065 accepted under delegation) @@ -155,15 +155,15 @@ The maintainer directed (2026-08-30) that the repository goes **private for the - [x] **SC-1 — CI-00 delegated rulings — confirm or overturn (`#2324`).** Nine rulings made under the 2026-08-30 directive (personal-account mode; base-ref control plane; one stable `Smart CI / Required Gate` + branch-current; hosted-by-default execution mode; Linux baseline + Windows contract; tree-SHA landed verifier; shadow-first selection; change-driven nightly with mutation manual; storage first). Overturning any is one reply on `#2324`. *(**Confirmed 2026-09-03, maintainer decision packet SC1, with the private-Pro approval-boundary amendment:** all nine rulings stand; the amendment records that a personal Pro private repository has no organization ruleset or required-workflow boundary, so the approval boundary is the maintainer's own account — hosted-only execution (ruling 4) until CI-04 proves the isolated runners, the read-only `pull_request_target` control plane, and the maintainer's review plus the fresh-context review before any control-plane (T0/T2) or runner change. GitHub's workflow-run approval setting covers fork/outside-contributor runs only and gates **nothing** for a branch an agent pushes to this repository; that same-repo residual (ADR-0066 Decision 8) stays recorded and is closed only by the CI-14 `#2338` organization path. Recorded on `#2324` (with the correction) and in ADR-0066 as amended in PR `#2442`.)* - [x] **SC-2 — artifact storage before cutover (`#2333`, CI-09).** Measured 2026-08-30: **372.1 GB of unexpired artifacts** (359 GB are exported container images from `reusable-container-images.yml` at the default 90-day retention, oldest 2026-06-02) against the 1 GB Pro allowance; GitHub bills private-repository storage per GB-day, so this is the first cost the moment the repository is private. Agent side: retention classes in the workflows (PR on `#2333`) + the dry-run deletion command `node scripts/ci/smart-ci/artifact-cleanup.mjs --name-prefix container-image-artifacts --older-than-days 1` (2026-08-30 scoped dry run: **1,412 PR-lane candidates = 245.9 GB**; 520 artifacts from `main`/tag runs are excluded by design; nothing deleted; deletion needs `--delete --ids-file --confirm-count 1412`). **Human:** authorize the one-time deletion of the expired-by-policy artifacts (destructive; agents do not run it unasked) or record an accepted spend on `#2337`. *(**Executed 2026-09-03 under the maintainer decision packet SC2 = "authorize bounded deletion after a fresh dry run."** Fresh dry run: 1,499 PR-lane `container-image-artifacts` candidates = 263.3 GB (35,640 listed; 554 `main`/tag artifacts excluded by design). First delete pass aborted fail-closed on a 1,499→1,498 drift (one artifact expired in between), re-run with the re-verified count and killed by the host at ≥1,300 deleted with 0 failures; a third fresh dry run found the remaining 114 = 16.0 GB and deleted 114/114, 0 failed. **Total: 1,498 artifacts, about 263.2 GB, removed; zero `main`/tag artifacts touched.** Evidence on `#2333`. The retention classes (PR `#2408`, **merged 2026-09-04 00:47Z**, merge `f5163d3cb`) and the storage ledger remain CI-09 engineering work, not human actions.)* -- [x] **SC-3 — plan confirmation and spend ceiling (`#2337` A/B1).** Confirm the account is GitHub Pro, set a monthly Actions spend ceiling + alert, and verify how the Codex GitHub App and Copilot code review are billed on a private repository. *(**Re-ruled 2026-09-03 by the maintainer in-session, superseding the packet's $10/month value: SC-3 is deferred — no paid overage ceiling is set.** The GitHub Pro plan is **confirmed** and its included 3,000 minutes/month are the whole hosted budget, spent on **Linux** jobs only; Windows (x2) and any macOS (x10) legs run locally — the laptop runner once CI-04 `#2328` registers it, agent-run proving checks until then — or carry a local fallback, never hosted overage. GitHub's default $0 spending limit is the effective hard ceiling. **Still the maintainer's:** read the Billing → Spending limits page at cutover step J.7 to confirm the limit is $0 and record it on `#2337`; verify Codex/Copilot billing on a private repository. Recorded on `#2337`, `#2324`, `#2328`, `#2331`, checklist A and ADR-0066.)* *(**Ticked 2026-09-06, 2026-09-06 guided-walkthrough reply q-5 = A, values supplied in-session (`map:v1:e5beef60c3235e76726721636988f12e53ad55368f875b9a64c8ce71ae621b93`):** Billing → Budgets shows a **$0** Actions budget; a GitHub budget caps spend only when its "stop usage when limit is reached" toggle is on, and that toggle was not read in this pass, so confirming it is the residual J.7 step at cutover; the Codex connector bills through the maintainer's OpenAI subscription, its review allowance refreshes on that subscription's daily cycle and it reaches private owned repositories, so no GitHub-side billing applies; Copilot is the Student offer (134/200 included AI credits at read time, additional usage not enabled, $0 budget) and is not relied on. Recorded on `#2337`.)* +- [x] **SC-3 — plan confirmation and spend ceiling (`#2337` A/B1).** Confirm the account is GitHub Pro, set a monthly Actions spend ceiling + alert, and verify how the Codex GitHub App and Copilot code review are billed on a private repository. *(**Re-ruled 2026-09-03 by the maintainer in-session, superseding the packet's $10/month value: SC-3 is deferred — no paid overage ceiling is set.** The GitHub Pro plan is **confirmed** and its included 3,000 minutes/month are the whole hosted budget, spent on **Linux** jobs only; Windows (x2) and any macOS (x10) legs run locally — the laptop runner once CI-04 `#2328` registers it, agent-run proving checks until then — or carry a local fallback, never hosted overage. GitHub's default $0 spending limit is the effective hard ceiling. **Still the maintainer's:** read the Billing → Spending limits page at cutover step J.7 to confirm the limit is $0 and record it on `#2337`; verify Codex/Copilot billing on a private repository. Recorded on `#2337`, `#2324`, `#2328`, `#2331`, checklist A and ADR-0066.)* *(**Ticked 2026-09-06, 2026-09-06 guided-walkthrough reply q-5 = A, values supplied in-session (`map:v1:e5beef60c3235e76726721636988f12e53ad55368f875b9a64c8ce71ae621b93`):** Billing → Budgets shows a **$0** Actions budget; a GitHub budget caps spend only when its "stop usage when limit is reached" toggle is on, and that toggle was not read in the first pass; **confirmed on in the second pass (2026-09-06, q-22 = A, `map:v1:cfe8e597c6d5bbb5fac7db58f3e09fa8b62dab8f624eb25f77af37ab971c0451`), so the $0 budget is a hard ceiling** and the J.7 residual is closed; the Codex connector bills through the maintainer's OpenAI subscription, its review allowance refreshes on that subscription's daily cycle and it reaches private owned repositories, so no GitHub-side billing applies; Copilot is the Student offer (134/200 included AI credits at read time, additional usage not enabled, $0 budget) and is not relied on. Recorded on `#2337`.)* - [ ] **SC-4 — register the stable gate (`#2327`, `#2337` B3).** After >=20 PRs of observation without a false red, add `Smart CI / Required Gate` to the required checks, keep the three security contexts, and decide — on the recall evidence, not in advance — whether to set `strict: true` and `enforce_admins` or to document a break-glass (SC-4 ruling 2026-09-03: no pre-ruling). CI-03 supplies the exact `gh api` commands; agents do not change branch protection. *(**2026-09-03, maintainer decision packet SC4: do not pre-rule** — `strict`, `enforce_admins` and any break-glass are decided only after the observation window, on the recall evidence. Recorded on `#2327`.)* -- [ ] **SC-5 — flip `sha_pinning_required` after CI-11 (`#2335`).** Every external action is pinned first; the setting is the maintainer's. +- [ ] **SC-5 — flip `sha_pinning_required` after CI-11 (`#2335`).** Every external action is pinned first; the setting is the maintainer's. *(**Unblocked 2026-09-06:** `#2502` merged (`e0a824187`), all 152 external actions pinned. The command in that PR's body targets `actions/permissions/workflow`, which has no such field and silently ignores it; the setting lives on the Actions permissions root (read 2026-09-06: `sha_pinning_required: false`). Maintainer command: `gh api -X PUT repos/Chris0Jeky/Taskdeck/actions/permissions -F enabled=true -f allowed_actions=all -F sha_pinning_required=true`, verify with `gh api repos/Chris0Jeky/Taskdeck/actions/permissions --jq .sha_pinning_required`; recorded on `#2335`.)* - [ ] **SC-6 — change repository visibility to private (`#2337` B5).** The release-defining action, performed manually by the maintainer after checklist sections A–I; capture the settings evidence first (B2). Agents never change visibility or billing. - [ ] **SC-7 — register the isolated runners after cutover (`#2328`, `#2337` B8).** Registration tokens never enter the repository; no runner is attached while the repository is public; `CI_EXECUTION_MODE` moves to `hybrid` only after the runners are proven. - [x] **SC-8 — public-asset and launch-kit decision (`#2337` A; `#2242`).** GitHub Pages keeps publishing from a private repository on Pro (the site stays public); the launch kit and the REVIVAL public-source messaging assumed a public repository — decide keep / move / reword and record it on `#2337`. *(**Ruled 2026-09-03, maintainer decision packet SC8: private development repository + public release/source mirror.** Releases, checksums/provenance and the GPL source stay public through a mirror; development, CI and issues go private; GitHub Pages keeps publishing. Mechanics — which repository, what syncs on a tag, how the launch kit and `awesome-selfhosted` wording point at the mirror — are seeded as CI-16 `#2439` and recorded on `#2337`.)* - [x] **SC-9 — Codex review credits are exhausted (maintainer billing).** On 2026-09-03 at 22:34Z the Codex connector answered PR `#2462`'s second and third pushes with "You have reached your Codex usage limits for code reviews" (it still reviewed the first and the fourth head). Until credits are added at the Codex usage dashboard, the documentation-only review gate falls back to one fresh-context agent review per PR (global law 2), which is what `#2462` used. Decide: top up, or accept the fallback and record that on `#2337` A (review-integration billing). *(**Ticked 2026-09-06, 2026-09-06 guided-walkthrough reply q-4 = A:** the fresh-context agent review is the documentation-only gate whenever the connector is out of credits; the maintainer reported the allowance returns with the OpenAI subscription cycle on 2026-09-07, so Codex resumes as the primary connector when present and no top-up is bought. Recorded on `#2337` A.)* -- [ ] **SC-10 — control-plane PRs awaiting the maintainer's review (ADR-0066 amendment 2026-09-03).** The amendment says control-plane (T0/T2) and runner changes merge only after the maintainer's review plus the fresh-context review. Queued by the 2026-09-04 overnight orchestrator, each already reviewed clean by a fresh-context agent and proven hosted at its head **against the base as of that PR's last hosted run, not against today's `main`** (measured 2026-09-04 21:40Z: `#2502` is 138 commits behind `main`, `#2506` 102, `#2532` 72, `#2550` 46; branch protection is not strict, so GitHub reports them `CLEAN` regardless, and a base change counts as a head change under the global laws). Each therefore needs `gh pr update-branch` plus a fresh hosted run and a re-check of its review against the moved base immediately before merge, `#2502` above all because it rewrites `uses:` refs across 34 workflow files; the update-branch runs were deliberately not spent in advance because `main` will move again before the maintainer arrives: `#2502` (CI-11 slice 1: 152 external actions pinned to full SHAs, guard enforced in `smart-ci-self-test.yml`; SC-5 command in the body), `#2506` (Smart CI planner: accept a merge ref regenerated against the live base tip — closes the shadow false-red shape seen on `#2485`, `#2496`, `#2500`; its last review round, 03:55:47Z, predates the current head `e1e80d579`, which is a 04:07:34Z merge of `main` touching none of the PR's own six files, so the review still covers the content and only the record is ambiguous), `#2532` (release-cache scanner comment handling, CI-09), and `#2550` (register the Paper colour-audit scanner test in `reusable-paper-color-audit.yml`; `#2504`, fresh-context review posted on the PR). **`#2522` (dev-up port release, `#1898`) is queued and was conflicting for most of 2026-09-04** after two dev-up commits landed on `main` (`8c0779fca`, `6c10850e6`); its head `18d214ba2` merged `main` and resolved that, and the hosted run at that head (`33922229492`) showed two Windows reds diagnosed on the PR as `#2378` (Frontend Unit launcher timeout) and `#2572` (a fixed-delay `WorkerResilienceTests` assertion, seeded from that run), neither in the PR's own files. It still needs `gh pr update-branch` and a fresh hosted run before a maintainer review of it means anything; the reconciliation session that carried it has closed, so the beta lane holds it behind its own queue. Two further open PRs touch control paths and were not in the queue above when it was written; both now carry a first fresh-context review (posted 2026-09-04 by the open-PR reconciliation session) but are not merge-ready: `#2531` (localized `netstat` state, `#2526` — stacked on `#2522` by design; review verdict FIX-FIRST because the fallback stopped discriminating on socket state; its advisory `Smart CI / Required Gate` is red on the stacked-base planner defect `#2562`, not on its code; retarget with `gh pr edit 2531 --base main` only after the parent lands, and note that its `Closes #2526` currently registers **no closing reference at all**, because GitHub links closing issues only from PRs targeting the default branch — merging it against the stacked base would land the delta on the parent branch and leave `#2526` open with no record why, so confirm `baseRefName == main` and a non-empty `closingIssuesReferences` before merging) and `#2535` (frontend telemetry build version, `#2182` — touches `release-container.yml`, `release-desktop.yml` and `scripts/ci/`; review verdict SHIP on the code, parked here as T2; its earlier `Frontend Unit (windows-latest)` red was the `#2378` / `#2161` timeout class, and at 21:48Z on 2026-09-04 its head `42c07fdb7` showed no failing check). Also decide the CI-07 `#2331` proposal (move the Windows launcher regression suite off the required hosted `windows-latest` leg per SC-3). **Post-hoc disclosure:** `#2479` (Paper colour-audit scanner, touches `reusable-paper-color-audit.yml`) was merged on 2026-09-04 at 03:58:46Z (merge `e99832f3c`) with the fresh-context review but without the maintainer's review — please review post hoc; revert is a one-liner if wanted. **Post-hoc disclosure (2):** `#2529` (merge `b461be49f`, docs-only, `docs/STATUS.md` alone) merged on 2026-09-04 at 06:33:08Z while its `ci-required.yml` run (`33842570671`, head `62f21d847`) had concluded `cancelled`, not success: the `Frontend Unit (windows-latest)` leg hit its 20-minute budget and was never re-run, and the merge landed 2 min 38 s after the run's final update at 06:30:30Z. Branch protection did not stop it and could not have: only the three security contexts are required, so the `ci-required` red is agent-enforced only. No revert appears warranted (a STATUS-only change cannot affect frontend tests, and `main` is green on that leg again at `#2559`, run `33886539482`), but a red required gate was treated as non-blocking, which global law 1 forbids; the call on any further action stays yours. The reconciliation sweep of the 40 PRs merged up to `#2559` found no other non-success `ci-required` run on a merged PR head (a separate red `main` push run of the same `#2378` shape at `17e48815e` is recorded in the eighth `docs/STATUS.md` block and back-referenced by the ninth). Belongs to the Windows-timeout cohort `#2378` / `#2161` / `#2159`. **Post-hoc disclosure (3):** `#2548` (merge `f094d090a`) and `#2556` (merge `b8878c922`), both `scripts/ci/dev-up.test.mjs`, and `#2549` (merge `e0516ca9d`, the Stryker v10 bump in `frontend/taskdeck-web/package.json` plus its lockfile) touch declared `ci/policy.v1.json` control paths and merged on 2026-09-04 on a fresh-context review alone, without the maintainer's review; `#2548` merged after its own review-gate comment had declared it parked for the maintainer, with no reversal on the thread, which is the exact behaviour the amendment exists to prevent. They are recorded in the tenth `docs/STATUS.md` block and are listed here so they sit in the same queue as `#2479`. All three are low blast radius (two test-only edits and a devDependency bump), so post-hoc review rather than revert is suggested; as with `#2479`, revert is yours to choose. Nothing was lost: measured 2026-09-04, all 80 head and merge SHAs of the 40 most recently merged PRs are ancestors of `origin/main`. Disclosures (2) and (3) were measured by the open-PR reconciliation session on 2026-09-04 and re-verified against the Actions API and the PR file lists before being recorded here. **Added 2026-09-05:** `#2587` (CI-10 slice 1, the Smart CI nightly coordinator `scripts/ci/smart-ci/nightly-coordinator.mjs` plus its tests, head `929331247`; two new files, no workflow, policy or doc edits; fresh-context review round 1 FIX-FIRST on a needs-closure HIGH, fixed, round 2 SHIP; seven non-blocking findings recorded on the PR and on `#2334`) is queued for your review and left unmerged by the beta lane. Two more joined on 2026-09-05: `#2608` (CI-07 slice under `#2331`, head `f74f590a0`: `if: runner.os == 'Linux'` on the launcher regression suite step in `reusable-frontend-unit.yml`, a placement contract test under `scripts/ci/smart-ci/`, a `TESTING_GUIDE.md` paragraph; fresh-context review SHIP, no fix round; **it is also `#2378`'s disposition**: the beta lane measured two hosted Windows launcher-suite logs on 2026-09-05 (p50 about 3 s, p90 13 to 15 s, slowest 26 to 31 s; the cases that hit `spawnSync ETIMEDOUT` take 1 to 8 s on a green run, so a 20 s clip means the runner is 3 to 5 times slower and the whole suite would land at 16 to 30 minutes against the job's 20 minute ceiling), so a larger per-spawn budget would only move the failure to a job cancellation, and every recorded `#2378` occurrence is on the Windows leg that `#2608` removes from hosted runs; three consistency points recorded on the PR for your ruling, because `ci/policy.v1.json`'s `launchers-windows` group, cutover checklist section E, `SMART_CI.md` section 5 and the `ci-required.yml` header map still state the pre-SC-3 two-leg target) and `#2610` (`#2582`, head `0fc8fb257`: `cancel-in-progress` becomes `github.ref != 'refs/heads/main'` in `ci-required.yml` with the group key unchanged, plus a four-assertion contract test in the Planner Self-Test glob; fresh-context review SHIP, no fix round; one precision point for your ruling, recorded on the PR and on `#2582`: GitHub keeps one in-progress plus one pending run per group, so this guarantees the in-flight `main` run completes and the tip runs after a wave, not a completed run per landed commit; a per-SHA group is the one-line alternative if CI-03's landed-commit verifier needs one run per merge). Both are parked unmerged by the beta lane. A fourth from the same lane joined at 11:19Z on 2026-09-05: `#2684` (`#2250` items 4 and 5, head `6150eb1a5`: fence-aware `UPGRADING.md` extraction that fails closed on an unterminated fence instead of publishing older versions' notes, bare anchors and relative links in the lifted section rewritten to `blob/` URLs, the 53-test composer suite wired into `ci-required.yml`'s release-workflow-contract job, the release trust doc's layout section updated; two fresh-context rounds, both SHIP; residuals on the PR and on `#2250`; parked unmerged). A fifth followed at 12:00Z: `#2687` (`#2250` items 1 to 3, head `64cd68f58`: a `preview_tag` workflow-dispatch input that renders the real stable page on a no-publish rehearsal and is refused before any build on a publishing dispatch; the page renderer runs from the workflow revision's own checkout so a pre-0.3 tag can be re-dispatched; the changelog base becomes the newest stable release strictly before the target by semver, via `scripts/ci/select-changelog-base.mjs` with 17 tests wired into `ci-required.yml`; two fresh-context rounds, both SHIP; hosted Workflow Lint and Release Workflow Contract green at `6b591ebed`; parked unmerged). Proving `#2687` end to end is yours: one rehearsal dispatch with `preview_tag` set and one re-dispatch of `v0.2.0`. With `#2684` and `#2687` parked, every `#2250` item sits in a parked PR. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-1 = A, delegate all twelve:** the coordinator runs `gh pr update-branch`, a fresh hosted run at the new head, a fresh-context re-review wherever `main` touched the PR's own files **or anything they read or execute** (shared `scripts/ci/smart-ci/**` libraries, `ci/policy.v1.json`, reusable workflows the PR's workflow calls) since its last review, measured per PR before merge; a base change counts as a head change, so the hosted run is always re-proven, and merges in dependency order: `#2506` first, then `#2502`, `#2522` then `#2531` retargeted to `main` with a non-empty closing reference confirmed, then the rest; the maintainer reviews post hoc. **q-2 = A:** the five post-hoc merges `#2479`, `#2529`, `#2548`, `#2549`, `#2556` are acknowledged, no revert. `#2687`'s no-publish rehearsal dispatch (`preview_tag` set) is run by the coordinator after merge; a `v0.2.0` re-dispatch publishes and stays the maintainer's. Progress lands in the next `docs/STATUS.md` block; this row closes when the twelve are merged.)* -- [ ] **SC-11 — enable `delete_branch_on_merge`, then batch-delete the merged-PR heads (repository setting; maintainer-only).** Measured 2026-09-04: the repository has `delete_branch_on_merge: false`, and 448 remote branches exist, of which (per the 2026-09-04 branch-hygiene sweep) about 399 are heads of already-merged PRs, 24 belong to closed-unmerged PRs and 11 never had a PR, the oldest from 2026-07-25. Parked work such as `origin/issue-1961/dedupe-board-fetch` (tip `08be057c5`, never a PR) is invisible in that noise, and the merge-then-fix cascades this week each left another head behind. Repository-settings mutations sit outside agent authority (`.agent-harness/tier.json`), so the flip is yours. The one-time sweep of existing branches is a separate decision: it is destructive, agents do not run it unasked (as with SC-2), and it needs its own recorded maintainer authorization. Under that authorization an agent may delete merged-PR heads only, and only a tip that is proven an ancestor of `origin/main` immediately before deletion (a merged branch that received a later commit is skipped and listed); it must exclude every open-PR head and every branch that is the base of any open PR (global law 4: deleting a stacked base cascade-closes its children unreopenably; `issue-1898/port-release-deadline` while `#2531` targets it is the current example); and it must list the closed-unmerged, no-PR and otherwise unclassified branches (the three counted classes sum to 434 of the 448) for a separate decision rather than deleting them. Re-measured 2026-09-05 15:15Z: 553 remote branches, 539 without an open PR; `delete_branch_on_merge` is still `false`, so the thirty-one Codex-session heads merged under D-12 all persist. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-3 = A:** the maintainer flips `delete_branch_on_merge` (Settings → General → Pull Requests → "Automatically delete head branches", or `gh api -X PATCH repos/Chris0Jeky/Taskdeck -F delete_branch_on_merge=true`); the one-time sweep is **authorized** under the rules above: merged-PR heads only, each tip proven an ancestor of `origin/main` immediately before deletion, every open-PR head and every open-PR base excluded, the closed-unmerged, no-PR and unclassified branches listed for a separate decision. Sweep evidence lands on `#2337`; the row closes when the setting reads `true` and the sweep report is recorded.)* +- [ ] **SC-10 — control-plane PRs awaiting the maintainer's review (ADR-0066 amendment 2026-09-03).** The amendment says control-plane (T0/T2) and runner changes merge only after the maintainer's review plus the fresh-context review. Queued by the 2026-09-04 overnight orchestrator, each already reviewed clean by a fresh-context agent and proven hosted at its head **against the base as of that PR's last hosted run, not against today's `main`** (measured 2026-09-04 21:40Z: `#2502` is 138 commits behind `main`, `#2506` 102, `#2532` 72, `#2550` 46; branch protection is not strict, so GitHub reports them `CLEAN` regardless, and a base change counts as a head change under the global laws). Each therefore needs `gh pr update-branch` plus a fresh hosted run and a re-check of its review against the moved base immediately before merge, `#2502` above all because it rewrites `uses:` refs across 34 workflow files; the update-branch runs were deliberately not spent in advance because `main` will move again before the maintainer arrives: `#2502` (CI-11 slice 1: 152 external actions pinned to full SHAs, guard enforced in `smart-ci-self-test.yml`; SC-5 command in the body), `#2506` (Smart CI planner: accept a merge ref regenerated against the live base tip — closes the shadow false-red shape seen on `#2485`, `#2496`, `#2500`; its last review round, 03:55:47Z, predates the current head `e1e80d579`, which is a 04:07:34Z merge of `main` touching none of the PR's own six files, so the review still covers the content and only the record is ambiguous), `#2532` (release-cache scanner comment handling, CI-09), and `#2550` (register the Paper colour-audit scanner test in `reusable-paper-color-audit.yml`; `#2504`, fresh-context review posted on the PR). **`#2522` (dev-up port release, `#1898`) is queued and was conflicting for most of 2026-09-04** after two dev-up commits landed on `main` (`8c0779fca`, `6c10850e6`); its head `18d214ba2` merged `main` and resolved that, and the hosted run at that head (`33922229492`) showed two Windows reds diagnosed on the PR as `#2378` (Frontend Unit launcher timeout) and `#2572` (a fixed-delay `WorkerResilienceTests` assertion, seeded from that run), neither in the PR's own files. It still needs `gh pr update-branch` and a fresh hosted run before a maintainer review of it means anything; the reconciliation session that carried it has closed, so the beta lane holds it behind its own queue. Two further open PRs touch control paths and were not in the queue above when it was written; both now carry a first fresh-context review (posted 2026-09-04 by the open-PR reconciliation session) but are not merge-ready: `#2531` (localized `netstat` state, `#2526` — stacked on `#2522` by design; review verdict FIX-FIRST because the fallback stopped discriminating on socket state; its advisory `Smart CI / Required Gate` is red on the stacked-base planner defect `#2562`, not on its code; retarget with `gh pr edit 2531 --base main` only after the parent lands, and note that its `Closes #2526` currently registers **no closing reference at all**, because GitHub links closing issues only from PRs targeting the default branch — merging it against the stacked base would land the delta on the parent branch and leave `#2526` open with no record why, so confirm `baseRefName == main` and a non-empty `closingIssuesReferences` before merging) and `#2535` (frontend telemetry build version, `#2182` — touches `release-container.yml`, `release-desktop.yml` and `scripts/ci/`; review verdict SHIP on the code, parked here as T2; its earlier `Frontend Unit (windows-latest)` red was the `#2378` / `#2161` timeout class, and at 21:48Z on 2026-09-04 its head `42c07fdb7` showed no failing check). Also decide the CI-07 `#2331` proposal (move the Windows launcher regression suite off the required hosted `windows-latest` leg per SC-3). **Post-hoc disclosure:** `#2479` (Paper colour-audit scanner, touches `reusable-paper-color-audit.yml`) was merged on 2026-09-04 at 03:58:46Z (merge `e99832f3c`) with the fresh-context review but without the maintainer's review — please review post hoc; revert is a one-liner if wanted. **Post-hoc disclosure (2):** `#2529` (merge `b461be49f`, docs-only, `docs/STATUS.md` alone) merged on 2026-09-04 at 06:33:08Z while its `ci-required.yml` run (`33842570671`, head `62f21d847`) had concluded `cancelled`, not success: the `Frontend Unit (windows-latest)` leg hit its 20-minute budget and was never re-run, and the merge landed 2 min 38 s after the run's final update at 06:30:30Z. Branch protection did not stop it and could not have: only the three security contexts are required, so the `ci-required` red is agent-enforced only. No revert appears warranted (a STATUS-only change cannot affect frontend tests, and `main` is green on that leg again at `#2559`, run `33886539482`), but a red required gate was treated as non-blocking, which global law 1 forbids; the call on any further action stays yours. The reconciliation sweep of the 40 PRs merged up to `#2559` found no other non-success `ci-required` run on a merged PR head (a separate red `main` push run of the same `#2378` shape at `17e48815e` is recorded in the eighth `docs/STATUS.md` block and back-referenced by the ninth). Belongs to the Windows-timeout cohort `#2378` / `#2161` / `#2159`. **Post-hoc disclosure (3):** `#2548` (merge `f094d090a`) and `#2556` (merge `b8878c922`), both `scripts/ci/dev-up.test.mjs`, and `#2549` (merge `e0516ca9d`, the Stryker v10 bump in `frontend/taskdeck-web/package.json` plus its lockfile) touch declared `ci/policy.v1.json` control paths and merged on 2026-09-04 on a fresh-context review alone, without the maintainer's review; `#2548` merged after its own review-gate comment had declared it parked for the maintainer, with no reversal on the thread, which is the exact behaviour the amendment exists to prevent. They are recorded in the tenth `docs/STATUS.md` block and are listed here so they sit in the same queue as `#2479`. All three are low blast radius (two test-only edits and a devDependency bump), so post-hoc review rather than revert is suggested; as with `#2479`, revert is yours to choose. Nothing was lost: measured 2026-09-04, all 80 head and merge SHAs of the 40 most recently merged PRs are ancestors of `origin/main`. Disclosures (2) and (3) were measured by the open-PR reconciliation session on 2026-09-04 and re-verified against the Actions API and the PR file lists before being recorded here. **Added 2026-09-05:** `#2587` (CI-10 slice 1, the Smart CI nightly coordinator `scripts/ci/smart-ci/nightly-coordinator.mjs` plus its tests, head `929331247`; two new files, no workflow, policy or doc edits; fresh-context review round 1 FIX-FIRST on a needs-closure HIGH, fixed, round 2 SHIP; seven non-blocking findings recorded on the PR and on `#2334`) is queued for your review and left unmerged by the beta lane. Two more joined on 2026-09-05: `#2608` (CI-07 slice under `#2331`, head `f74f590a0`: `if: runner.os == 'Linux'` on the launcher regression suite step in `reusable-frontend-unit.yml`, a placement contract test under `scripts/ci/smart-ci/`, a `TESTING_GUIDE.md` paragraph; fresh-context review SHIP, no fix round; **it is also `#2378`'s disposition**: the beta lane measured two hosted Windows launcher-suite logs on 2026-09-05 (p50 about 3 s, p90 13 to 15 s, slowest 26 to 31 s; the cases that hit `spawnSync ETIMEDOUT` take 1 to 8 s on a green run, so a 20 s clip means the runner is 3 to 5 times slower and the whole suite would land at 16 to 30 minutes against the job's 20 minute ceiling), so a larger per-spawn budget would only move the failure to a job cancellation, and every recorded `#2378` occurrence is on the Windows leg that `#2608` removes from hosted runs; three consistency points recorded on the PR for your ruling, because `ci/policy.v1.json`'s `launchers-windows` group, cutover checklist section E, `SMART_CI.md` section 5 and the `ci-required.yml` header map still state the pre-SC-3 two-leg target) and `#2610` (`#2582`, head `0fc8fb257`: `cancel-in-progress` becomes `github.ref != 'refs/heads/main'` in `ci-required.yml` with the group key unchanged, plus a four-assertion contract test in the Planner Self-Test glob; fresh-context review SHIP, no fix round; one precision point for your ruling, recorded on the PR and on `#2582`: GitHub keeps one in-progress plus one pending run per group, so this guarantees the in-flight `main` run completes and the tip runs after a wave, not a completed run per landed commit; a per-SHA group is the one-line alternative if CI-03's landed-commit verifier needs one run per merge). Both are parked unmerged by the beta lane. A fourth from the same lane joined at 11:19Z on 2026-09-05: `#2684` (`#2250` items 4 and 5, head `6150eb1a5`: fence-aware `UPGRADING.md` extraction that fails closed on an unterminated fence instead of publishing older versions' notes, bare anchors and relative links in the lifted section rewritten to `blob/` URLs, the 53-test composer suite wired into `ci-required.yml`'s release-workflow-contract job, the release trust doc's layout section updated; two fresh-context rounds, both SHIP; residuals on the PR and on `#2250`; parked unmerged). A fifth followed at 12:00Z: `#2687` (`#2250` items 1 to 3, head `64cd68f58`: a `preview_tag` workflow-dispatch input that renders the real stable page on a no-publish rehearsal and is refused before any build on a publishing dispatch; the page renderer runs from the workflow revision's own checkout so a pre-0.3 tag can be re-dispatched; the changelog base becomes the newest stable release strictly before the target by semver, via `scripts/ci/select-changelog-base.mjs` with 17 tests wired into `ci-required.yml`; two fresh-context rounds, both SHIP; hosted Workflow Lint and Release Workflow Contract green at `6b591ebed`; parked unmerged). Proving `#2687` end to end is yours: one rehearsal dispatch with `preview_tag` set and one re-dispatch of `v0.2.0`. With `#2684` and `#2687` parked, every `#2250` item sits in a parked PR. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-1 = A, delegate all twelve:** the coordinator runs `gh pr update-branch`, a fresh hosted run at the new head, a fresh-context re-review wherever `main` touched the PR's own files **or anything they read or execute** (shared `scripts/ci/smart-ci/**` libraries, `ci/policy.v1.json`, reusable workflows the PR's workflow calls) since its last review, measured per PR before merge; a base change counts as a head change, so the hosted run is always re-proven, and merges in dependency order: `#2506` first, then `#2502`, `#2522` then `#2531` retargeted to `main` with a non-empty closing reference confirmed, then the rest; the maintainer reviews post hoc. **q-2 = A:** the five post-hoc merges `#2479`, `#2529`, `#2548`, `#2549`, `#2556` are acknowledged, no revert. `#2687`'s no-publish rehearsal dispatch (`preview_tag` set) is run by the coordinator after merge; a `v0.2.0` re-dispatch publishes and stays the maintainer's. Progress lands in the next `docs/STATUS.md` block; this row closes when the twelve are merged.)* *(**Progress 2026-09-06:** `#2506` merged `79d7efdb7`; `#2502` merged `e0a824187` after a re-review against the moved base caught a HIGH (the pin broke the Stryker workflow-contract text; fixed in-PR); `#2608` updated, re-reviewed SHIP with its three consistency points fixed in-PR, proving. Ten remain: `#2608`, `#2610`, `#2587`, `#2532`, `#2550`, `#2535`, `#2684`, `#2687`, `#2522` then `#2531`. Second-pass ruling q-24 = B: after `#2687` merges only the no-publish preview rehearsal runs; the `v0.2.0` re-dispatch is not run.)* +- [x] **SC-11 — enable `delete_branch_on_merge`, then batch-delete the merged-PR heads (repository setting; maintainer-only).** Measured 2026-09-04: the repository has `delete_branch_on_merge: false`, and 448 remote branches exist, of which (per the 2026-09-04 branch-hygiene sweep) about 399 are heads of already-merged PRs, 24 belong to closed-unmerged PRs and 11 never had a PR, the oldest from 2026-07-25. Parked work such as `origin/issue-1961/dedupe-board-fetch` (tip `08be057c5`, never a PR) is invisible in that noise, and the merge-then-fix cascades this week each left another head behind. Repository-settings mutations sit outside agent authority (`.agent-harness/tier.json`), so the flip is yours. The one-time sweep of existing branches is a separate decision: it is destructive, agents do not run it unasked (as with SC-2), and it needs its own recorded maintainer authorization. Under that authorization an agent may delete merged-PR heads only, and only a tip that is proven an ancestor of `origin/main` immediately before deletion (a merged branch that received a later commit is skipped and listed); it must exclude every open-PR head and every branch that is the base of any open PR (global law 4: deleting a stacked base cascade-closes its children unreopenably; `issue-1898/port-release-deadline` while `#2531` targets it is the current example); and it must list the closed-unmerged, no-PR and otherwise unclassified branches (the three counted classes sum to 434 of the 448) for a separate decision rather than deleting them. Re-measured 2026-09-05 15:15Z: 553 remote branches, 539 without an open PR; `delete_branch_on_merge` is still `false`, so the thirty-one Codex-session heads merged under D-12 all persist. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-3 = A:** the maintainer flips `delete_branch_on_merge` (Settings → General → Pull Requests → "Automatically delete head branches", or `gh api -X PATCH repos/Chris0Jeky/Taskdeck -F delete_branch_on_merge=true`); the one-time sweep is **authorized** under the rules above: merged-PR heads only, each tip proven an ancestor of `origin/main` immediately before deletion, every open-PR head and every open-PR base excluded, the closed-unmerged, no-PR and unclassified branches listed for a separate decision. Sweep evidence lands on `#2337`; the row closes when the setting reads `true` and the sweep report is recorded.)* *(**Ticked 2026-09-06:** the sweep ran the same day — 465 merged-PR heads deleted in 12 `git push --delete` batches, 0 failures, 45 merged-but-diverged, 27 closed-unmerged and 11 never-a-PR branches listed on `#2337` for a separate decision, remote heads 573 → 108 — and the maintainer flipped the setting in the second walkthrough pass (q-27; agent read `delete_branch_on_merge: true` at 11:2xZ).)* --- @@ -184,13 +184,14 @@ Source: the alpha lane audited 15 open v0.3 issues on 2026-09-04 against `main` - [x] **D-11 — `#2193` acceptance text.** Closed on evidence 2026-09-04 with residuals on `#2210` (surface the honest note to the reviewer) and `#2211` (`llm-triage.v3` prompt-version bump). One question survives: must `#2211` land before the v0.3.0 tag, given that prompt derivation changed twice under an unchanged version string? A = yes, add `#2211` to the milestone; B = no, v0.3.x. Proposed default: A. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-18 = A:** `#2211` lands before the v0.3.0 tag and is added to the v0.3 milestone. Recorded on `#2193` and `#2211`.)* - [x] **D-12 — the Codex session's thirty-one open PRs (ruled 2026-09-05).** Between 01:38Z and 06:19Z on 2026-09-05 a session using your local git identity opened thirty-three PRs under the alpha lane name with `Refs`-only bodies and no fresh-context review (two were closed as superseded the same morning: `#2595` duplicate of `#2592`, `#2596` superseded by `#2609`). Both Claude lanes confirmed they did not open them; the interim rule (first recorded claim or open PR on an issue wins, whatever the marker form) held while the item was open. **Ruled by you in the coordinator session on 2026-09-05 (about 13:15Z): the PRs are wanted; check them, give the sensitive or fragile ones a further Opus 5 review pass, and merge them when ready.** Done the same afternoon: every PR got one fresh-context read-only Opus 5 review (record on the PR; ten in a full worktree of the head), 26 SHIP and 5 FIX-FIRST with the MEDIUMs fixed in-PR (`#2643` classifier literal shared with its producer plus the quick-start entry; `#2626` the code-side CSP default; `#2633` a bounded pre-mount locale wait, with a scoped round-2 pass; `#2620` the cooperative-stop assertion; `#2641` the Claude GitHub-path wording), all 31 merged as merge commits between 13:27Z and 14:32Z (last: `#2626` at `6cf9ef7a1`), three unrelated reds attributed and re-proven once (`#2378` twice, `#2588` once, and a new Windows Backend Unit shape seeded as `#2691`). No PR used `Closes`, so no issue auto-closed: each of the 28 issues carries a coordinator comment naming the merge SHA, what remains and the tracked LOWs; `#1140` row 3 and `#1991` L2 are ticked; the issues left with no open PR moved Review to Pending on the board. Which session it was is still unknown and no longer matters for this item; the claim-search rule (both marker forms plus the open PR list before claiming) stays in `.codex/memories/00_ACTIVE.md`. - [x] **D-13 — `#1999` item 2, Paper triage edit on Triaged rows.** Paper gates Edit at `New | Failed` while the server and the Legacy inbox allow editing a `Triaged` row, and enabling the edit in Paper alone would create a dead write because re-triage answers 409 on a Completed queue row. A = keep Paper gated and record the skin divergence on the issue; B = allow the edit and add a backend re-triage path for Completed rows; C = allow the edit with no re-triage (the edit lands on the capture but never reaches the board). Proposed default: A. Items 1 and 4 are shipped (PR `#2426` for the stalled-load Close was never recorded in STATUS; the alpha lane records it in its next block); item 3 shipped in PR `#2698` (merge `a789dabbe`, the correction kept and announced, restored through Edit) and the close/reopen race in PR `#2668`, so only item 2 waits on this ruling; the held-draft line for a `Triaged` capture follows it. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-19 = B:** Paper allows the edit on a `Triaged` row **and** the backend gains a re-triage path for a Completed queue row, so the edit reaches the board; seeded as the item-2 slice on `#1999`. Recorded on `#1999`.)* -- [x] **D-14 — `#2439` CI-16 public mirror: nine questions before the workflow PR.** The agent-preparable half shipped as `docs/ci/PUBLIC_MIRROR_OPTIONS.md` (PR `#2680`, merge `62d1bb42b`): it scores the three SC-8 options and recommends (a), a source snapshot per release tag plus the Release assets, with a fail-closed export allowlist, private-asset checksum verification before anything public is written, and the mirror publish done last. Its section "Open questions for the maintainer" lists nine, with the memo's proposed default where it states one: (1) mirror repository name (default `Chris0Jeky/taskdeck-release`); (2) confirm option (a) over (b) and (c); (3) the snapshot export allowlist (`ci/mirror-export.txt`); (4) a source tarball on every Release in addition to the pushed tree (default yes); (5) backfill of v0.1.0 through v0.2.x as snapshot commits and releases; (6) Issues and Discussions on the mirror (default both off, README pointing back); (7) credential shape and rotation (`MIRROR_PUBLISH_TOKEN`, fine-grained, Contents read and write); (8) GHCR image visibility; (9) whether `ci/` and `scripts/ci/` count as ADR-0050 corresponding source under GPLv3. Answer by number on `#2439`; the workflow PR is control-plane and parks under SC-10 once written, and the mirror repository itself is created by you (repository creation is outside agent authority). Nothing is built until the numbers are answered; the memo's defaults are proposals. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-20 = A, the memo's nine defaults:** (1) `Chris0Jeky/taskdeck-release`; (2) option (a); (3) the default-deny allowlist `ci/mirror-export.txt`; (4) a source tarball on every Release; (5) backfill v0.1.0–v0.2.x once, dry-run first; (6) Issues and Discussions off; (7) fine-grained `MIRROR_PUBLISH_TOKEN`, 90-day rotation reminder on `#2337`; (8) GHCR image visibility raised on `#2337` as its own item; (9) `ci/` and `scripts/ci/` are stripped. The workflow PR is now writable and parks under SC-10 when written; creating the mirror repository stays the maintainer's. Recorded on `#2439`.)* +- [x] **D-14 — `#2439` CI-16 public mirror: nine questions before the workflow PR.** The agent-preparable half shipped as `docs/ci/PUBLIC_MIRROR_OPTIONS.md` (PR `#2680`, merge `62d1bb42b`): it scores the three SC-8 options and recommends (a), a source snapshot per release tag plus the Release assets, with a fail-closed export allowlist, private-asset checksum verification before anything public is written, and the mirror publish done last. Its section "Open questions for the maintainer" lists nine, with the memo's proposed default where it states one: (1) mirror repository name (default `Chris0Jeky/taskdeck-release`); (2) confirm option (a) over (b) and (c); (3) the snapshot export allowlist (`ci/mirror-export.txt`); (4) a source tarball on every Release in addition to the pushed tree (default yes); (5) backfill of v0.1.0 through v0.2.x as snapshot commits and releases; (6) Issues and Discussions on the mirror (default both off, README pointing back); (7) credential shape and rotation (`MIRROR_PUBLISH_TOKEN`, fine-grained, Contents read and write); (8) GHCR image visibility; (9) whether `ci/` and `scripts/ci/` count as ADR-0050 corresponding source under GPLv3. Answer by number on `#2439`; the workflow PR is control-plane and parks under SC-10 once written, and the mirror repository itself is created by you (repository creation is outside agent authority). Nothing is built until the numbers are answered; the memo's defaults are proposals. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-20 = A, the memo's nine defaults:** (1) `Chris0Jeky/taskdeck-release`; (2) option (a); (3) the default-deny allowlist `ci/mirror-export.txt`; (4) a source tarball on every Release; (5) backfill v0.1.0–v0.2.x once, dry-run first; (6) Issues and Discussions off; (7) fine-grained `MIRROR_PUBLISH_TOKEN`, 90-day rotation reminder on `#2337`; (8) GHCR image visibility raised on `#2337` as its own item — **ruled in the second pass, q-23 = A: the images stay public**, package visibility set to public by the maintainer before SC-6; (9) `ci/` and `scripts/ci/` are stripped. The workflow PR is now writable and parks under SC-10 when written; creating the mirror repository stays the maintainer's. Recorded on `#2439`.)* - [x] **D-15 — `#2638` items 1 and 3: the shell's scroll model decides whether anything in the Review view can pin.** The alpha lane measured on 2026-09-05 (Chromium 1280x720, the real stack, the real degraded warning raised by answering the proposals GET with 503 for three polls; release comment on `#2638` at 18:10Z) that neither the Paper decision rail's sticky handshake from `#2630` nor the `#2214`/`#2630` "pinned" degraded warning pins: the document is the scrollport (`documentElement` clientHeight 720, scrollHeight 3166) because `.td-shell` is `min-height: 100vh` and grows with content, and three ancestors that never overflow (`.td-content` in `AppShell.vue` plus two review-side `overflow: auto` rules) capture `position: sticky`; after `window.scrollTo(0, 788)` the rail sits at top -260 and the warning at -712, both moving 1:1 with the page. The `--paper-review-sticky-offset` ResizeObserver seam itself works (37 px). The acceptance's fallback (delete the inert rail handshake, keep the warning sticky) would leave an equally false claim, so the lane parked before implementation, no PR, worktree removed with no commits, and the thirteenth block's `#2630` sticky sentences are corrected in the next STATUS block as rules with no effect. Three options, measured not built: A = shell fix, make `.td-content` the bounded scroller (`.td-shell` at `height: 100vh` with a `min-height: 0` chain) and drop the two review-side `overflow: auto` rules; largest blast radius (every route, both themes, the responsive and E2E suites), the lane expects it to fix every sticky element that shares this ancestor chain (measured on the Review route only); B = bound the review grid inside `PaperReviewView.vue` with a definite height so its columns scroll internally (about 616 px at a 720 px viewport), magic numbers and three independent scrollers instead of one page scroll; C = honest comments only, delete the offset machinery and the warning's pinned rule and record the constraint, retiring the `#2214`/`#2630` sticky behaviour as shipped in name only. Proposed default: A, as its own slice with a browser-proven Playwright leg across the routes the responsive suite covers; it is yours to rule because it changes the scroll model of the whole shell. Answer by letter on `#2638`. Item 2 of `#2638` (the recovery sentence retired by an explicit load) is unaffected and claimable separately. *(**Ruled 2026-09-06, 2026-09-06 guided-walkthrough reply q-21 = A, the shell fix:** `.td-content` becomes the bounded scroller (`.td-shell` at `height: 100vh` with a `min-height: 0` chain) and the two review-side `overflow: auto` rules go, as its own slice with a browser-proven Playwright leg across the routes the responsive suite covers. Recorded on `#2638`.)* - *(Record 2026-09-06, guided-walkthrough replies `map:v1:e5beef60c3235e76726721636988f12e53ad55368f875b9a64c8ce71ae621b93`:)* `#2240` ruled **B** (q-6): the multiple-assignments sub-slice is the whole ADR-0060 stage-3 Assignment foundation and moves to v0.4 with `#2093`; the `decision` label is discharged. `#2004` (q-7 = A): no ADR-split question remains, the 2026-09-04 ruling (one ADR first, then implementation, all in v0.3) stands and the ADR draft is agent work. SC-4, SC-5, SC-6 and SC-7 were confirmed blocked on their stated prerequisites and were not re-asked. - Section K additions from the 2026-09-05 alpha audits, no ruling needed, recorded for the file's completeness: `#1949` cannot close on evidence (AC4 route-walking has no mechanization; AC3's registry has no stated inventory; wrapper buttons are invisible to the button scan, claimed as a guards-only slice); `#2090` width and collapse shipped (PRs `#2158`, `#2165`), titles-only open and Paper-only; `#1940` honest-empty slice claimed in the keep-the-disclosures shape (surface the empty truth and the confidence source at card level) because removing the expander would break the held `PaperReviewView.spec.ts`; this does not touch D-9; new `#1940` residual: the right rail shows proposal A's confidence and similar-past under B's identity while B loads. ## Changelog +- 2026-09-06 (second guided-walkthrough pass, 11 replies, `map:v1:cfe8e597c6d5bbb5fac7db58f3e09fa8b62dab8f624eb25f77af37ab971c0451`): **One `[x]`: §J SC-11** (sweep executed, setting flipped by the maintainer and read back `true`). Rulings recorded on their rows: SC-3 stop-usage toggle confirmed on (J.7 residual closed); GHCR images stay public (D-14 item 8); `#2687` preview rehearsal only, no `v0.2.0` re-dispatch; `#1770` caveat kept; `#1821`, second-machine key and the OneDrive checkout not now; **CL-1 Stage 1 starts** with `docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md`; BEN-1 and DIST-1 still deferred. SC-5 unblocked by `#2502` with the corrected command recorded (the PR body's endpoint has no such field). SC-10 progress noted on its row. Rule-3 authorization is the maintainer's in-session replies. - 2026-09-06 (guided walkthrough, 21 interactive replies plus the D-7 follow-up q-14b, `map:v1:e5beef60c3235e76726721636988f12e53ad55368f875b9a64c8ce71ae621b93`): **Thirteen `[x]`: §J SC-3 and SC-9; §K D-1, D-2, D-3, D-5, D-7, D-8, D-10, D-11, D-13, D-14, D-15.** Rule-3 authorization is the maintainer's in-session replies to the walkthrough prompts. Rulings recorded on each row; D-4, D-6, D-9 stay open on their human-only or pending halves; SC-10 (q-1 A, delegate all twelve; q-2 A, post-hoc merges acknowledged) and SC-11 (q-3 A, flip plus authorized sweep) stay open until executed. `#2240` moved to v0.4 (q-6 B); the `#2004` ADR-split handoff line retired (q-7 A); `#2211` added to v0.3 (D-11); `#2727` seeded (D-3 c). Non-default choices worth noticing: Legacy frozen for v0.3 (D-3 a), batch execute widened and single execute narrowed to 404 (D-4 a, b), read-only keys exempt from the revision lock, the `newAutomation` ledger row and the exhaustive ledger (D-5 a–c), `#1284` kept open with chat origins naming the dispatched provider and model (D-7), Paper triage edit plus a backend re-triage path (D-13). - 2026-09-05 (D-12 ruled in-session, about 13:15Z): **One `[x]`: the §K D-12 row.** Rule-3 authorization is the maintainer's in-session directive to the coordinator (check the Codex session's thirty-one open PRs, review the sensitive ones again with Opus 5, merge when ready); 31 PRs reviewed and merged the same afternoon, the last `#2626` at `6cf9ef7a1` (14:32Z). No other item changed state; SC-11 re-measured (553 remote branches, 539 without an open PR). - 2026-09-03 (later, maintainer in-session rulings on the packet follow-ups): **no new `[x]`.** Q1 on `#2337` ruled **A** against the agent's B recommendation — checklist section H is a cutover prerequisite in full, so CI-10 `#2334` moves from v0.4 to v0.3 (Priority I) and becomes a release blocker; no split, no CI-10a child. §J SC-3 re-ruled: the packet's $10/month overage ceiling is **deferred** — GitHub Pro confirmed, the included 3,000 minutes/month fund Linux hosted CI only, Windows/macOS legs go local (laptop runner via CI-04, or agent-run proving checks) or carry a local fallback; the row stays open for the J.7 $0-limit read-back and the Codex/Copilot billing check. Clause 2 of the v0.3 gate ruled: **all 35 un-gated milestone issues stay in v0.3.0** (the gate-work-only, Priority-I-plus-security and dogfooding-only splits were declined), with **one exception ruled in the same reply: `#1972` moves to v0.5 with CF-21 `#2274`** (dropping the Agent selector now, and pulling CF-21 forward, were both declined); v0.3.0 tags only when the whole milestone (51 open after the exception) is closed on evidence, and `#2004`/`#1936` still need their own `decision` rulings to close. The three judgement calls flagged in the packet handoff (collaborator held privately; SC-1 amendment wording; CF-22 go with the evidence report kept as the acceptance bar) were not overturned and stand. diff --git a/docs/STATUS.md b/docs/STATUS.md index 97ecca05f..5c61e20e7 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,6 +1,6 @@ # Taskdeck Status (Source of Truth) -Last Updated: 2026-09-05 +Last Updated: 2026-09-06 **Authority.** This file owns *shipped reality* - what is built, verified, and running today. **Precedence** when documents disagree: `docs/STATUS.md` > `AGENTS.md` > `CLAUDE.md`. @@ -839,6 +839,37 @@ Companion Active Docs: - `docs/MANUAL_TEST_CHECKLIST.md` - `docs/GOLDEN_PRINCIPLES.md` +v0.3 integration wave, twentieth block (2026-09-06, `main` `9b864ea78`, the nineteenth block's last merge, to `478fd7169`; written by the coordinator: no alpha or beta lane session ran in this range). The maintainer sat an interactive guided walkthrough in the coordinator session (21 replies plus one follow-up, decision map `map:v1:e5beef60c3235e76726721636988f12e53ad55368f875b9a64c8ce71ae621b93`, recorded in `OUTSTANDING_TASKS.md` by PR `#2728`, merge `f21bfd1f5`, and on every issue named), which unparked the control-plane queue and ruled the §K product batch; a Codex session opened a further batch of PRs during the same hours (`#2715`–`#2731`, all `Refs`-only, no fresh-context review) and the coordinator carried them under the standing D-12 route. Codex review credits stayed exhausted (SC-9, ruled accepted), so every PR below carried one fresh-context read-only Opus 5 reviewer, with MEDIUM+ findings fixed in-PR by the coordinator and LOWs recorded on the PR and its issue. + +Maintainer rulings (the record is `OUTSTANDING_TASKS.md` §J/§K and the issues; listed here as shipped direction): +- **Control plane unparked (SC-10 q-1 = A).** All twelve parked control-plane PRs are delegated to the coordinator: `gh pr update-branch`, a fresh hosted run, a fresh-context re-review wherever `main` touched the PR's own files or anything they read or execute, merge in dependency order (`#2506`, `#2502`, `#2522` then `#2531` retargeted, then the rest); the maintainer reviews post hoc. The five post-hoc merges (`#2479`, `#2529`, `#2548`, `#2549`, `#2556`) are acknowledged, no revert (q-2 = A). +- **Branch hygiene (SC-11 q-3 = A).** The one-time sweep was authorized and executed: 465 merged-PR heads deleted through `git push origin --delete` in 12 batches, each tip re-proven an ancestor of `origin/main` immediately before its batch, every open-PR head and open-PR base excluded, 0 failures; 45 merged-but-diverged, 27 closed-unmerged and 11 never-a-PR branches listed on `#2337` for a separate decision. Remote heads went from 573 to 108. The `delete_branch_on_merge` flip stays the maintainer's. +- **Billing (SC-3 q-5 = A, SC-9 q-4 = A).** The Actions budget reads $0 (whether its stop-usage toggle is on is the residual J.7 confirmation); the Codex connector bills through the maintainer's OpenAI subscription and refreshes on 2026-09-07; Copilot is the unused Student plan. Fresh-context agent review is the accepted documentation gate whenever the connector is out of credits. +- **Product batch (§K).** D-1 B (no column contract in v0.3; `#1984` closes on the truth slice, CF-20 owns targeting). D-2 (ii) A, (iii) B. **D-3 (a) B: the Legacy skin is frozen for v0.3** — `#2141` re-titled and closed on the Paper evidence, the Legacy halves of `#2215`, `#2214`, `#2591` and the queued `#1968` Legacy shortcut-map slice are dropped; (b) A, (c) **A: Paper transcript file upload seeded as `#2727`**, (d) A. **D-4 (a) B: batch execute widens to every Approved proposal** (authorization checks unchanged), **(b) B: single execute narrows to 404** for an unreadable board, (c) A (trust text beside GP-06); (d) the dogfooding week stays the maintainer's. **D-5 (a) B: read-only keys `P` and Space work under the revision-editor lock; (b) B: `newAutomation` is user-facing, ledger row built; (c) A: the Paper ledger is exhaustive**; (d) A bare `T`; (e) A; (f) A. D-6 (b) B, (c) B, (d) A, (e) A; (a) waits for candidates. **D-7 B with follow-up A: `#1284` stays open and chat-origin proposals claim the dispatched provider and model, stamped at dispatch.** D-8 A/A/B (`#2315` leaves the blocker set). D-9 (a) A; (b) (c) parked. D-10 A confirmed. D-11 A (`#2211` into v0.3). **D-13 B: Paper allows the Triaged-row edit and the backend gains a re-triage path for Completed rows.** D-14 A (the nine mirror defaults; `Chris0Jeky/taskdeck-release`, option (a), `ci/` and `scripts/ci/` stripped; GHCR visibility raised on `#2337`). **D-15 A: the shell fix, `.td-content` becomes the bounded scroller, own slice with a Playwright leg across routes.** `#2240` B (moved to v0.4 with `#2093`, `decision` label discharged). `#2004`: no ADR-split question remains; the 2026-09-04 ruling stands and the ADR draft is agent work. + +Control plane (SC-10, merged under the delegation): +- **Smart CI planner accepts a merge ref regenerated against the live base tip (`#2506`, merge `79d7efdb7`, head `86e28dc9e` after update-branch; Refs `#2327`).** Closes the shadow false-red shape seen on `#2485`, `#2496`, `#2500`, `#2617`. Review re-check against the moved base: `main` had touched none of its six files nor any `scripts/ci/smart-ci/**`, `ci/**` or Smart CI workflow since the review base, so the 2026-09-04 review stands; hosted run green at the head. The SC-4 observation window restarts from the first clean planner run after this merge (recorded on `#2327`). +- **Every external GitHub Action is pinned to a full commit SHA, with a guard in the Planner Self-Test lane (`#2502`, CI-11 slice 1, merge `e0a824187`, head `7a0b7793a` after update-branch; Refs `#2335`).** 152 external `uses:` refs across 34 workflow files carry a 40-hex SHA and a `# vX.Y.Z` comment; `action-pins.mjs --check` exits 1 on any unpinned external reference and runs on every PR in the advisory Planner Self-Test lane (it becomes a merge gate only when SC-4 registers the gate and SC-5 flips `sha_pinning_required`, which the code comment and `SMART_CI.md` now say). The fresh-context re-review against the moved base returned FIX-FIRST on a **HIGH the original review could not see**: `scripts/ci/Test-StrykerConfig.ps1` asserts the mutation workflow's `Upload Stryker report` step by exact text including `uses: actions/upload-artifact@v7`, so the pin would have failed every `workflow_dispatch` of Mutation Testing at its first backend step, invisible to `ci-required` because that workflow is dispatch-only; the coordinator reproduced it (`-SelfTest` red at the head), fixed the contract text (self-test 35/35 green), and re-proved `check-github-ops-governance.mjs` and the smart-ci suite (102/102) at the merged head. **SC-5 is unblocked**; the PR body's flip command targets an endpoint without the field, and the corrected command is on `#2335` and in `OUTSTANDING_TASKS.md`. +- **The frontend launcher regression suite runs on the Linux hosted leg only (`#2608`, CI-07 slice under `#2331`; in flight at `fb668d5c0` when this block was written, the next block records its merge).** This is `#2378`'s disposition. Re-review against the moved base: SHIP; its three recorded consistency points (the `ci-required.yml` header map, the `ci/policy.v1.json` `frontend-unit-windows` description, `SMART_CI.md` section 5) were fixed in-PR by the coordinator; `launchers-windows` routing in the policy stays as a CI-03/CI-08 follow-up recorded on the PR. + +Codex-session batch (all `Refs`; no issue auto-closed; each issue carries a coordinator disposition naming the merge SHA and the residuals): +- **Route mount reads are bounded in the E2E route-affordances spec (`#2715`, merge `76c91617a`; Refs `#2708`).** 15 s bound on the four production mount reads, matched to `activate`; a guard test proves the bound fails on its own label. MEDIUM fixed in-PR: the guard's 30 s test budget removed so it fails on its assertion, not the outer timeout. `docs/STATUS.md:403`'s "`#2708` … unclaimed" is superseded by this entry. +- **The extraction cancellation test synchronizes on worker entry instead of a timing assumption (`#2716`, merge `8a6f64211`; Refs `#2691`).** MEDIUM fixed in-PR: the assertion await is bounded to 30 s again so a propagation regression fails red instead of hanging the job (class re-run locally 18/18). `#2691` stays open for observation: the 5 s worker-entry wait is retained, async now. +- **Classifier regex timeouts are reported, never swallowed, without failing the turn (`#2717`, merge `f4507e47a`; Refs `#1134`).** Eight silent `catch (RegexMatchTimeoutException)` blocks become one `TryMatch` that logs a warning carrying only the rule id; `RegexTimeout` (2000 ms) stays the single source of truth. MEDIUM fixed in-PR: the code comment now states that a timed-out pattern's siblings still run, so the aggregate outcome can differ from the old per-group catch (review-first bounds the effect). Tracked on `#1134`: `MockLlmProvider`, the default provider, still classifies without a logger. +- **Fixture fsmonitor daemon is stopped in the worktree-helper test's `finally` (`#2718`, merge `67bf93777`; Refs `#2676`).** MEDIUM fixed in-PR: the fixture is registered for stop only after a proven start, so a failed start no longer turns cleanup into a second bogus failure. Tracked: no bounded retry between `stop` and `Remove-Item` (the `#2676` symptom narrowed, not closed). +- **Quota reservation commit outcomes are covered (`#2719`, merge `aa2411fb2`; Refs `#1431`).** Genuine coverage of all three `QuotaCommitResult` members with pinned arguments and a live cancellation token. +- **Empty evidence links keep a stable identity in `ReviewProvenance.vue` (`#2720`, merge `a4bf50821`; Refs `#1837`).** Per-instance sentinel (not module-scope as the body says), unfrozen unlike the repo's `EMPTY_EVIDENCE_LINKS` convention; the fallback is unreachable from the shipped path, so this is defensive hardening. +- **The Docker MCP profile test runner makes its Bash coverage explicit (`#2721`, merge `8dc6b3918`; Refs `#2312`).** `-RequireBash` fails when Bash is absent; otherwise the drill regressions SKIP with exit 0. MEDIUMs fixed in-PR: an explicitly named `-BashExecutable` that does not resolve throws instead of skipping; `docs/TESTING_GUIDE.md` documents the semantics. +- **Checkout-fingerprint guard failures are reported as finite reason codes with no path detail (`#2722`, merge `478fd7169`; Refs `#1711`).** Fail-closed exit preserved. MEDIUMs fixed in-PR: four live throw messages that fell through to `E_GUARD_INTERNAL_FAILURE` are mapped (`E_STATUS_BOUNDS`, `E_STATUS_INVENTORY`); the default remediation no longer points at diagnostics nothing writes. Tracked: the orphaned `ConvertTo-DiagnosticText`/`Get-ArtifactKindText` helpers. +- **Triage extractor DI wiring is asserted from a hand-built container (`#2723`, merge `dc37a2a54`; Refs `#2212`)** and **the completed-capture notice contract is documented in `DATA_MODEL.md` (`#2724`, merge `8846e4fb1`; Refs `#2212`).** Tracked: `CaptureTriageLlm:Enabled=true` in the test is a no-op (the default), so the binding is unproven; the host-level golden-path test already covers the seam. +- **Two comment-only clarifications (`#2725`, merge `803eba98b`, Refs `#2520`; `#2726`, merge `f00e77ba3`, Refs `#2578`).** Verified comment-only. +- **Recorded operation labels are normalized through one util (`#2729`, merge `9c43f2baa`; Refs `#1434`).** Tracked: a third humanizer copy survives in `ReviewAppliedDecisionRecord.vue`. +- **Archived capture toggles carry a per-row accessible name (`#2730`, merge `16963a79b`; Refs `#2075`).** Three catalogs updated in parity. MEDIUM fixed in-PR: a non-string excerpt is guarded as the sibling `captureLabel` already does. Tracked: equal or common-prefix excerpts still collide (77-character truncation, timestamp fallback only when empty). +- **A same-board history-mode transition reloads the review queue and supersedes late reads (`#2731`, merge `65976a817`; Refs `#2075`).** The reload is an explicit load, so the `#2638` retirement rule is untouched. Tracked: the new poll-side mode guard ships untested. +- **Not verified in this block:** the `#2730` spec was not run locally after the coordinator's fix (worktree without `node_modules`; the hosted Frontend Unit run at `70a687d46` is the proof); `#2718`'s fixture test was not run locally (multi-hundred-MB fixture). Windows `Frontend Unit` reds on `#2718`, `#2721`, `#2722`, `#2726` were the `#2378` launcher-suite class (`#2608` removes that hosted leg) and were re-run once each. + +Corrections to earlier blocks: the `#2708` "unclaimed lane backlog" line (`docs/STATUS.md:403`) is superseded by `#2715` above; `docs/releases/V0_3_0_READINESS.md:128` names a test `#2716` renamed. + ## Project Summary Taskdeck is a local-first execution system for developers, built with a .NET 8 backend and a Vue 3 frontend. diff --git a/docs/ops/README.md b/docs/ops/README.md index 71f3f993c..b7744eb96 100644 --- a/docs/ops/README.md +++ b/docs/ops/README.md @@ -21,6 +21,7 @@ This folder contains deployment, observability, and human-operator runbooks. - `TASKDECK_HUMAN_OPERATIONS.md` - `GITHUB_LABEL_TAXONOMY.md` - `DISASTER_RECOVERY_RUNBOOK.md` +- `STAGE1_PRIVATE_INSTANCE_RUNBOOK.md` — the ordered CL-1 deployment procedure for the trusted private instance (`#1772`) - `INCIDENT_REHEARSAL_CADENCE.md` - `SBOM_RELEASE_PROVENANCE.md` diff --git a/docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md b/docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md new file mode 100644 index 000000000..f8a52f005 --- /dev/null +++ b/docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md @@ -0,0 +1,229 @@ +# Stage 1 private instance — deployment runbook (CL-1) + +Last Updated: 2026-09-06 + +Purpose: the exact, ordered procedure for standing up the trusted private instance ruled on `#1772` +(ADR-0061, CL-1 in `OUTSTANDING_TASKS.md`): one self-hosted Taskdeck stack behind a tunnel with an +identity policy in front, two accounts (the maintainer and one named collaborator), a real spend +ceiling, daily encrypted backups with twelve weekly off-platform copies, key custody separate from +the data, and a restore drill into a fresh local container before the collaborator is invited. +Written under the maintainer's 2026-09-06 ruling (q-31 = A). It composes the shipped guides; where +they overlap, this document says which command to run and in what order. + +Every step marked **[human]** is performed by the maintainer: it creates an account, spends money, +handles a secret, or changes something outside the repository. Agents prepare and verify; they do +not perform those steps. Nothing below is inferred: the values in section 0 are the ones recorded +on `#1772` on 2026-09-03. + +Source guides (read them when a step needs more context, not before starting): + +- `docs/platform/SELF_HOST_TUNNEL_GUIDE.md` — stack, tunnel, accounts, live providers. +- `docs/ops/DISASTER_RECOVERY_RUNBOOK.md` — the encrypted backup and restore contract (`taskdeck-backup`, + `taskdeck-restore`, `.tdbk` archives). It supersedes the legacy `scripts/backup.sh` paragraph in the + tunnel guide's section 7 for the production image. +- `docs/ops/EVIDENCE_TEMPLATE.md` — the Stage 1 evidence record shape. +- `docs/security/MANAGED_KEY_USAGE_POLICY.md` — the disclosure the collaborator receives if live + triage is enabled. + +## 0. Recorded values (from `#1772`, 2026-09-03) + +| Item | Value | Where it is used | +| --- | --- | --- | +| Access boundary | Two accounts: the maintainer plus **one named collaborator** (identity held privately by the maintainer; never recorded in the repository) | Step 6, tunnel access policy in step 4 | +| Registration | `InviteOnly` while the collaborator registers, then `Closed` | Steps 1 and 6 | +| Host | Self-host on the maintainer's machine plus a tunnel with an identity policy (`#1777` Render stays parked) | Step 4 | +| Cost owner and LLM payer | The maintainer, alone | Step 7 | +| Budget | **£20/month all-in ceiling, £10 alert**; breach action: live providers off, instance stays up | Step 7 | +| Backups | Daily encrypted archive on the host; **12 weekly encrypted off-platform copies (about 90 days)**, rotated | Step 8 | +| Key custody | Backup key and connector key in a password manager plus one offline copy, never beside the database or the archives | Steps 1 and 8 | +| Restore target | A fresh local container, never the live volume | Step 5 | +| MFA | Stays disabled until `#1653` | Everywhere | + +Still open and **not** decided by this runbook: which tunnel mechanism (Cloudflare Access or +Tailscale Serve, step 4 offers both) and the exact daily token ceiling (step 7, derived from the +£10 alert at the provider's current prices on the day it is set). + +## 1. Secrets and keys — [human] + +Generate every secret yourself; none of them may pass through an agent transcript or a chat window. + +1. Create `deploy/.env` (gitignored): + + ```bash + cd deploy + printf 'TASKDECK_JWT_SECRET=%s\nTASKDECK_CONNECTORS_ENCRYPTION_KEY=%s\nTASKDECK_REGISTRATION_MODE=InviteOnly\nTASKDECK_PROXY_PORT=8080\n' \ + "$(openssl rand -base64 48)" "$(openssl rand -base64 32)" > .env + ``` + +2. Create the **backup key**, independent from the connector key, as a protected file outside the + repository and outside any synced folder (OneDrive included): + + ```bash + mkdir -p /secure && chmod 700 /secure + openssl rand -base64 32 > /secure/taskdeck-backup.key + chmod 600 /secure/taskdeck-backup.key + ``` + + On Windows use a directory under your profile that OneDrive does not sync, and restrict it to + your account in its Properties → Security tab; Docker Desktop mounts it read-only in step 8. + +3. Put **both** `deploy/.env` and `/secure/taskdeck-backup.key` in your password manager now, and + make the one offline copy. Losing the backup key makes every archive unrecoverable; losing the + connector key makes every stored connector credential unreadable. + +Done when: `deploy/.env` exists with four lines, the backup key file exists with mode 600, and both +are in the password manager. Do not paste either value anywhere. + +## 2. Build and start the stack — [human runs, agent may verify the health probe] + +```bash +docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline up -d --build +curl -fsS http://localhost:8080/health/ready +``` + +Record the exact image identity the instance runs on, so the evidence names a digest and not a tag: + +```bash +docker compose -f deploy/docker-compose.yml --env-file deploy/.env images +docker inspect --format '{{index .RepoDigests 0}} {{.Id}}' taskdeck-api:local +``` + +Done when: `/health/ready` answers 200 and the image id is written into the evidence record. + +## 3. Prepare the archive volume and take the first backup — [human runs] + +The split image runs as UID `10001`. Prepare the archive volume once, then take a first archive +before anything is exposed, so the restore drill in step 5 has a real artefact: + +```bash +docker volume create taskdeck-backups +docker run --rm --entrypoint sh -v taskdeck-backups:/backups taskdeck-api:local -c 'chown -R 10001:10001 /backups' + +docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline run --rm --no-deps \ + -v taskdeck-backups:/backups \ + -v /secure/taskdeck-backup.key:/run/secrets/taskdeck-backup.key:ro \ + -e TASKDECK_BACKUP_KEY_FILE=/run/secrets/taskdeck-backup.key \ + api taskdeck-backup --database /app/data/taskdeck.db --output /backups +``` + +Expected output is three lines: `archive=/backups/taskdeck-backup--schema--000001.tdbk`, +`schema=`, `integrity=ok`. Copy the archive name into the evidence record. + +## 4. Expose it behind an identity policy — [human] + +Pick one. Both proxy WebSockets (SignalR needs that). A quick tunnel (`cloudflared tunnel --url`) +has no access policy and is allowed only for a minutes-long smoke test, never for the instance. + +**Option A — Cloudflare named tunnel plus Cloudflare Access** (needs a domain on Cloudflare; the +dedicated Taskdeck domain from RT-1 is not purchased yet, so this option waits for it or uses a +domain you already hold): + +1. Zero Trust dashboard → Networks → Tunnels → Create a tunnel → run the printed `cloudflared` + install and `cloudflared tunnel run` commands on the host. +2. Public hostname: `.` → service `http://localhost:8080`. +3. Access → Applications → Add a self-hosted application for that hostname → one policy, action + Allow, rule "Emails" listing exactly your address and the collaborator's; session duration of + your choice. +4. Keep the tunnel running as a service so it survives a reboot (`cloudflared service install`). + +**Option B — Tailscale Serve inside a tailnet** (no domain needed; the URL is +`https://..ts.net`): + +1. Install Tailscale on the host and on the collaborator's device; invite the collaborator to the + tailnet (Admin console → Users → Invite). +2. On the host: `tailscale serve --bg 8080`. Do **not** use `tailscale funnel` (public internet). +3. If the tailnet has or ever gets a third user, add an ACL grant that limits the host's port 443 to + the two named identities. + +**Verification, either option:** from a device or identity **outside** the policy, open the URL: +the login page must be denied (Access login wall or a Tailscale connection refusal), and +`curl -s -o /dev/null -w '%{http_code}' https:///health/ready` must not return 200. Record +the URL, the mechanism and the outside-check result in the evidence. + +## 5. Restore drill into a fresh local container — [human runs; agent may review the evidence] + +Do this **before** inviting anyone, on the archive from step 3, into a throwaway volume, never the +live one: + +```bash +docker volume create taskdeck-drill-data +docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline run --rm --no-deps \ + -v taskdeck-drill-data:/app/data \ + -v taskdeck-backups:/backups:ro \ + -v /secure/taskdeck-backup.key:/run/secrets/taskdeck-backup.key:ro \ + -e TASKDECK_BACKUP_KEY_FILE=/run/secrets/taskdeck-backup.key \ + api taskdeck-restore --archive /backups/.tdbk --database /app/data/taskdeck.db +``` + +Expected: `restored=/app/data/taskdeck.db`, `schema=`, `integrity=ok`, +`connectors ok=N failed=0`, exit code 0. `ok=0 failed=0` only means no connector credentials +exist yet; it does not prove the connector key. Then remove the drill volume: +`docker volume rm taskdeck-drill-data`. Record elapsed time, the exact archive name, the output +lines and the exit code in the evidence. + +## 6. Accounts — [human] + +1. Open the URL yourself and **register first** (the first registration claims the bootstrap slot). +2. Mint one invite: `docker compose -f deploy/docker-compose.yml --env-file deploy/.env exec api dotnet /app/cli/Taskdeck.Cli.dll invite create --expires 7`. + Send the code to the collaborator over a channel you already trust; they register. +3. **Close registration:** set `TASKDECK_REGISTRATION_MODE=Closed` in `deploy/.env`, re-run the + `up -d` command from step 2 (or the two-file command from step 7 if live providers are already on) + so the container is recreated, then prove it: `curl -s -o /dev/null -w '%{http_code}' -X POST https:///api/auth/register -H 'Content-Type: application/json' -d '{}'` + must not be a 2xx. +4. Share a board: Boards → the board → Settings → Access → grant the collaborator `Editor`. + +Done when: exactly two users exist (`GET /api/users` while logged in), registration is refused, +and the collaborator can open the shared board. + +## 7. Live LLM provider, ceiling and disclosure — [human], optional + +Skip entirely to stay on the mock provider (nothing leaves the instance). If you enable live +triage, the instance becomes ADR-0061's operator-funded variant and all five sub-steps are +mandatory, in this order: + +1. Add to `deploy/.env`: `TASKDECK_LLM_ENABLE_LIVE_PROVIDERS=true`, `TASKDECK_LLM_PROVIDER=OpenAI`, + `TASKDECK_LLM_OPENAI_API_KEY=`. +2. Create `deploy/docker-compose.llm-quota.yml` setting `LlmQuota__GlobalBudgetCeilingTokens` to a + real daily number. Derive it from the **£10 alert**: at the provider's current price per million + tokens for the configured model, `tokens_per_day = (£10 / 30 days) / price_per_token`; round down. + Record the number and the price you used on `#1772`. +3. Recreate with **both** files, and from now on always with both: + + ```bash + docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.llm-quota.yml --env-file deploy/.env --profile baseline up -d --build + docker compose -f deploy/docker-compose.yml -f deploy/docker-compose.llm-quota.yml --env-file deploy/.env exec api printenv LlmQuota__GlobalBudgetCeilingTokens + ``` + +4. At the provider: set a monthly budget of **£20** with an alert at **£10** on the key's project. + Breach action, ruled: set `TASKDECK_LLM_ENABLE_LIVE_PROVIDERS=false` and recreate; the instance + stays up. +5. Send the collaborator the written disclosure (their captured content leaves the instance under + your provider account, you pay) pointing at `GET /api/privacy/egress` and + `docs/security/MANAGED_KEY_USAGE_POLICY.md`, **before** they capture anything real. + +## 8. Backup schedule, off-platform copies, retention — [human sets up; agent may verify the schedule file] + +- **Daily**, on the host: the step 3 backup command. On Windows, register it as a Task Scheduler job + running under your account (`schtasks /Create /SC DAILY /TN TaskdeckBackup /TR "" /ST 03:00`); + on Linux or macOS, a cron line. The packaged command writes one archive and never deletes; prune + host archives older than 14 days yourself in the same job. +- **Weekly**, copy the newest `.tdbk` to maintainer-controlled off-platform storage (the archive is + already AES-256-GCM encrypted; the storage does not need to be). Keep **12** copies, delete the + oldest when a thirteenth arrives. The backup key never goes to that storage. +- Record in the evidence: the schedule, the off-platform destination, the retention rule, and the + custodian of each key. + +## 9. Evidence — [agent writes the record from the maintainer's outputs; maintainer signs off] + +File `docs/ops/rehearsals/-stage1-private-instance.md` from `docs/ops/EVIDENCE_TEMPLATE.md` +with: image digest, tunnel mechanism and URL (host part only), the outside-the-policy check result, +the two user identities as "maintainer" and "collaborator" (no personal details), the registration +closure proof, the first archive name, the restore drill output and duration, the provider ceiling +and price if live triage is on, the backup schedule and off-platform destination class, and the key +custodians. Post the file path on `#1772`. Then the CL-1 row can be ticked by the maintainer. + +## Rollback + +Stop exposure first (`cloudflared` service stop, or `tailscale serve reset`), then +`docker compose … down`. Data survives in the `taskdeck_taskdeck-db` volume; archives in +`taskdeck-backups`. Nothing in this runbook deletes either. diff --git a/docs/platform/SELF_HOST_TUNNEL_GUIDE.md b/docs/platform/SELF_HOST_TUNNEL_GUIDE.md index 98327edb2..19388f990 100644 --- a/docs/platform/SELF_HOST_TUNNEL_GUIDE.md +++ b/docs/platform/SELF_HOST_TUNNEL_GUIDE.md @@ -198,6 +198,10 @@ Realtime presence and updates are per-board and re-check read access on join. ## 7. Care and feeding +> For the production image the encrypted `taskdeck-backup` / `taskdeck-restore` commands in +> `docs/ops/DISASTER_RECOVERY_RUNBOOK.md` supersede the `scripts/backup.sh` paragraph below +> (`#2238`, `#2239`); the ordered Stage 1 procedure is `docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md`. + - **Backup**: the database lives in the `taskdeck-db` volume (`/app/data/taskdeck.db`) in WAL mode — **never copy the file while the app is running** (`scripts/backup.sh` and `CLOUD_DEPLOYMENT_GUIDE.md` both warn a live copy From b12636f5e91b3b4a54361b0191a30ac1610ba9bf Mon Sep 17 00:00:00 2001 From: Chris0Jeky Date: Sun, 6 Sep 2026 12:46:20 +0100 Subject: [PATCH 2/2] docs: runbook closure proof requires the 403 closed message; content id for a local image; drill-volume pre-check; STATUS reflects the second pass (review HIGH-1, MEDIUM-2, MEDIUM-3, LOW-5) --- docs/STATUS.md | 5 +++-- docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md | 25 ++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/STATUS.md b/docs/STATUS.md index 5c61e20e7..c346a8f03 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -843,9 +843,10 @@ v0.3 integration wave, twentieth block (2026-09-06, `main` `9b864ea78`, the nine Maintainer rulings (the record is `OUTSTANDING_TASKS.md` §J/§K and the issues; listed here as shipped direction): - **Control plane unparked (SC-10 q-1 = A).** All twelve parked control-plane PRs are delegated to the coordinator: `gh pr update-branch`, a fresh hosted run, a fresh-context re-review wherever `main` touched the PR's own files or anything they read or execute, merge in dependency order (`#2506`, `#2502`, `#2522` then `#2531` retargeted, then the rest); the maintainer reviews post hoc. The five post-hoc merges (`#2479`, `#2529`, `#2548`, `#2549`, `#2556`) are acknowledged, no revert (q-2 = A). -- **Branch hygiene (SC-11 q-3 = A).** The one-time sweep was authorized and executed: 465 merged-PR heads deleted through `git push origin --delete` in 12 batches, each tip re-proven an ancestor of `origin/main` immediately before its batch, every open-PR head and open-PR base excluded, 0 failures; 45 merged-but-diverged, 27 closed-unmerged and 11 never-a-PR branches listed on `#2337` for a separate decision. Remote heads went from 573 to 108. The `delete_branch_on_merge` flip stays the maintainer's. -- **Billing (SC-3 q-5 = A, SC-9 q-4 = A).** The Actions budget reads $0 (whether its stop-usage toggle is on is the residual J.7 confirmation); the Codex connector bills through the maintainer's OpenAI subscription and refreshes on 2026-09-07; Copilot is the unused Student plan. Fresh-context agent review is the accepted documentation gate whenever the connector is out of credits. +- **Branch hygiene (SC-11 q-3 = A).** The one-time sweep was authorized and executed: 465 merged-PR heads deleted through `git push origin --delete` in 12 batches, each tip re-proven an ancestor of `origin/main` immediately before its batch, every open-PR head and open-PR base excluded, 0 failures; 45 merged-but-diverged, 27 closed-unmerged and 11 never-a-PR branches listed on `#2337` for a separate decision. Remote heads went from 573 to 108. The maintainer flipped `delete_branch_on_merge` in the second walkthrough pass the same day (read back `true`), so SC-11 is closed. +- **Billing (SC-3 q-5 = A, SC-9 q-4 = A).** The Actions budget reads $0 with "stop usage when budget limit is reached" confirmed on in the second pass, so it is a hard ceiling (J.7 residual closed); the Codex connector bills through the maintainer's OpenAI subscription and refreshes on 2026-09-07; Copilot is the unused Student plan. Fresh-context agent review is the accepted documentation gate whenever the connector is out of credits. - **Product batch (§K).** D-1 B (no column contract in v0.3; `#1984` closes on the truth slice, CF-20 owns targeting). D-2 (ii) A, (iii) B. **D-3 (a) B: the Legacy skin is frozen for v0.3** — `#2141` re-titled and closed on the Paper evidence, the Legacy halves of `#2215`, `#2214`, `#2591` and the queued `#1968` Legacy shortcut-map slice are dropped; (b) A, (c) **A: Paper transcript file upload seeded as `#2727`**, (d) A. **D-4 (a) B: batch execute widens to every Approved proposal** (authorization checks unchanged), **(b) B: single execute narrows to 404** for an unreadable board, (c) A (trust text beside GP-06); (d) the dogfooding week stays the maintainer's. **D-5 (a) B: read-only keys `P` and Space work under the revision-editor lock; (b) B: `newAutomation` is user-facing, ledger row built; (c) A: the Paper ledger is exhaustive**; (d) A bare `T`; (e) A; (f) A. D-6 (b) B, (c) B, (d) A, (e) A; (a) waits for candidates. **D-7 B with follow-up A: `#1284` stays open and chat-origin proposals claim the dispatched provider and model, stamped at dispatch.** D-8 A/A/B (`#2315` leaves the blocker set). D-9 (a) A; (b) (c) parked. D-10 A confirmed. D-11 A (`#2211` into v0.3). **D-13 B: Paper allows the Triaged-row edit and the backend gains a re-triage path for Completed rows.** D-14 A (the nine mirror defaults; `Chris0Jeky/taskdeck-release`, option (a), `ci/` and `scripts/ci/` stripped; GHCR visibility raised on `#2337`). **D-15 A: the shell fix, `.td-content` becomes the bounded scroller, own slice with a Playwright leg across routes.** `#2240` B (moved to v0.4 with `#2093`, `decision` label discharged). `#2004`: no ADR-split question remains; the 2026-09-04 ruling stands and the ADR draft is agent work. +- **Second pass (11 replies, `map:v1:cfe8e597c6d5bbb5fac7db58f3e09fa8b62dab8f624eb25f77af37ab971c0451`).** SC-3 stop-usage toggle on; GHCR images stay public when the repository goes private (D-14 item 8); `#2687` gets the no-publish preview rehearsal only; `#1770` caveat kept through v0.3; `#1821`, the second-machine key and the OneDrive checkout not now; BEN-1/DIST-1 still deferred; **CL-1 Stage 1 starts** with `docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md` (nine ordered steps, every human step marked). SC-5 unblocked by `#2502`; the PR body's flip command targets an endpoint without the field and the corrected command is on `#2335`. Control plane (SC-10, merged under the delegation): - **Smart CI planner accepts a merge ref regenerated against the live base tip (`#2506`, merge `79d7efdb7`, head `86e28dc9e` after update-branch; Refs `#2327`).** Closes the shadow false-red shape seen on `#2485`, `#2496`, `#2500`, `#2617`. Review re-check against the moved base: `main` had touched none of its six files nor any `scripts/ci/smart-ci/**`, `ci/**` or Smart CI workflow since the review base, so the 2026-09-04 review stands; hosted run green at the head. The SC-4 observation window restarts from the first clean planner run after this merge (recorded on `#2327`). diff --git a/docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md b/docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md index f8a52f005..c904e2a4d 100644 --- a/docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md +++ b/docs/ops/STAGE1_PRIVATE_INSTANCE_RUNBOOK.md @@ -81,11 +81,12 @@ docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile bas curl -fsS http://localhost:8080/health/ready ``` -Record the exact image identity the instance runs on, so the evidence names a digest and not a tag: +Record the exact image identity the instance runs on. The stack builds `taskdeck-api:local` locally, so +it has no registry digest; the content id is the identity: ```bash docker compose -f deploy/docker-compose.yml --env-file deploy/.env images -docker inspect --format '{{index .RepoDigests 0}} {{.Id}}' taskdeck-api:local +docker inspect --format '{{.Id}}' taskdeck-api:local ``` Done when: `/health/ready` answers 200 and the image id is written into the evidence record. @@ -143,10 +144,12 @@ the URL, the mechanism and the outside-check result in the evidence. ## 5. Restore drill into a fresh local container — [human runs; agent may review the evidence] Do this **before** inviting anyone, on the archive from step 3, into a throwaway volume, never the -live one: +live one. Prove the throwaway volume is the one mounted at `/app/data` before restoring (an empty +listing; the live volume would show `taskdeck.db`): ```bash docker volume create taskdeck-drill-data +docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline run --rm --no-deps \n -v taskdeck-drill-data:/app/data api sh -c 'ls -A /app/data' docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline run --rm --no-deps \ -v taskdeck-drill-data:/app/data \ -v taskdeck-backups:/backups:ro \ @@ -168,8 +171,20 @@ lines and the exit code in the evidence. Send the code to the collaborator over a channel you already trust; they register. 3. **Close registration:** set `TASKDECK_REGISTRATION_MODE=Closed` in `deploy/.env`, re-run the `up -d` command from step 2 (or the two-file command from step 7 if live providers are already on) - so the container is recreated, then prove it: `curl -s -o /dev/null -w '%{http_code}' -X POST https:///api/auth/register -H 'Content-Type: application/json' -d '{}'` - must not be a 2xx. + so the container is recreated, then prove it with a **syntactically valid** throwaway registration + that also carries the invite code minted in 6.2 (an empty body only proves model validation, which + answers 400 in every mode): + + ```bash + curl -s -w ' +%{http_code} +' -X POST https:///api/auth/register -H 'Content-Type: application/json' \n -d '{"username":"closure-probe","email":"closure-probe@example.invalid","password":"Closure-Probe-Passw0rd!","inviteCode":""}' + ``` + + Required: HTTP **403** and the body text `Registration is closed by this Taskdeck instance.` + (`RegistrationPolicyService.RegistrationClosedMessage`). `InviteOnly` answers a different forbidden + message (`A valid registration invite is required.`) or, with a live invite, succeeds; either means the + container was not recreated with `Closed`, and the invite can still create a third account. 4. Share a board: Boards → the board → Settings → Access → grant the collaborator `Editor`. Done when: exactly two users exist (`GET /api/users` while logged in), registration is refused,