From 0441e6d534ee99c571f5806ef422bb05fa0927e7 Mon Sep 17 00:00:00 2001 From: Dan Moisan Date: Fri, 14 Aug 2026 09:22:26 -0400 Subject: [PATCH 01/13] docs(553): promote CI parallel job split, capture baseline, research, spec, and user story Promotes the ci-parallel-job-split potential entry to issue #553 and creates the active feature folder. Captures the measured sequential CI baseline from green run 31749877507 (444s wall clock; 130s fixed setup; 15s/101s/98s/88s gate durations), records the research artifact resolving ten design questions, and authors spec.md and user-story.md against the adopted topology. No workflow files are modified by this commit. Refs #553 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LZFrbwyXBnwJ44RRJV1N4E --- .../agent-memory/task-researcher/MEMORY.md | 3 +- .../project_ci_parallel_split_553.md | 42 ++ .../project_svgcontrol_test_unwired_418.md | 8 +- ...ci-sequential-baseline.2026-08-14T13-05.md | 76 ++++ .../issue.md | 123 ++++++ .../plan.2026-08-14T09-05.md | 53 +++ ...14T13-30-ci-parallel-job-split-research.md | 285 +++++++++++++ .../spec.md | 385 ++++++++++++++++++ .../user-story.md | 151 +++++++ 9 files changed, 1124 insertions(+), 2 deletions(-) create mode 100644 .claude/agent-memory/task-researcher/project_ci_parallel_split_553.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md diff --git a/.claude/agent-memory/task-researcher/MEMORY.md b/.claude/agent-memory/task-researcher/MEMORY.md index 4c6abdfa..e2251990 100644 --- a/.claude/agent-memory/task-researcher/MEMORY.md +++ b/.claude/agent-memory/task-researcher/MEMORY.md @@ -23,7 +23,8 @@ - [dependabot-net481-340](project_dependabot_net481_340.md) — #340: no packages.config package currently dropped net481; transitive-bump restraint is already Dependabot's NuGet default (cite security-updates docs, not a new config primitive); use semver-major ignore not fabricated version ceilings (2026-07-16) - [folder-hierarchy-provider-350](project_folder_hierarchy_provider_350.md) — #350/epic 9101: reuse existing snapshot infra (IOutlookFolderTreeService.GetChildren + ParentKey walk), add IFolderHierarchyProvider facade + pure GetAncestorChain, no new COM seam; defer deleting BuildFromRows/Build to 9102/9103 (2026-07-16) - [efcviewer-breadcrumb-webview2-349](project_efcviewer_breadcrumb_webview2_349.md) — #349 (epic child 9102): EfcViewer3 dead; no JS<->.NET bridge precedent in repo; percent defect = unscaled ColumnHeader widths at high-DPI design scale (2026-07-16) -- [svgcontrol-test-unwired-418](project_svgcontrol_test_unwired_418.md) — #418: SVGControl.Test absent from the .sln and its pinned test packages missing, so it cannot build; ExCSS 4.2.3-vs-4.3.1 redirect topology; Fizzler redirects inert (2026-08-04) +- [svgcontrol-test-unwired-418](project_svgcontrol_test_unwired_418.md) — #418 (STALE: SVGControl.Test now IN .sln as of 2026-08-14); historical package-pin + ExCSS/Fizzler redirect topology +- [ci-parallel-split-553](project_ci_parallel_split_553.md) — #553: 4 independent jobs w/ tailored setup beat build-once artifact sharing; single-PUT ruleset 18572843 swap fail-closed; called-workflow check names are "caller / callee" (2026-08-14) - [winforms-pump-seam-230](project_winforms_pump_seam_230.md) — #230: WinFormsPumpHost design decided; CreateAsync factory-seam gap; InitializeWebViewAsync stays exempt; 19 -> 11 max (2026-08-07) - [qfc438-search-focus-steal](project_qfc438_search_focus_steal.md) — #438: TWO focus-steal mechanisms (open _focusPending + close _focusAnchor via per-keystroke Clear); CancelSelector emits no SelectionChanged -> stale _selectedFolder (2026-08-08) - [ribbon-engine-readiness-503](project_ribbon_engine_readiness_503.md) — #503: whole TaskMaster Ribbon layer is coverage-excluded; net481 blocks default interface members; 5 orphan onAction callbacks in RibbonExplorer.xml (2026-08-08) diff --git a/.claude/agent-memory/task-researcher/project_ci_parallel_split_553.md b/.claude/agent-memory/task-researcher/project_ci_parallel_split_553.md new file mode 100644 index 00000000..06ae1d7f --- /dev/null +++ b/.claude/agent-memory/task-researcher/project_ci_parallel_split_553.md @@ -0,0 +1,42 @@ +--- +name: ci-parallel-split-553 +description: "Issue #553 CI split research (2026-08-14): recommended 4 independent windows jobs w/ tailored setup, no artifact sharing; ruleset 18572843 single-PUT context swap is fail-closed either side; reusable-workflow check names are ' / ' and must be captured from a live run" +metadata: + type: project +--- + +Issue #553 research (2026-08-14), artifact at +`docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md`. + +Key conclusions the implementation session will need: + +- **Recommended topology:** four independent reusable-workflow jobs (`_format-check`, `_build-analyzers`, + `_build-nullable`, `_mstest-coverage`), zero `needs:` edges, MSTest job does its own plain `/t:Build`. + Build-once + upload/download artifact loses on critical-path arithmetic even at zero transfer cost. + Est. wall clock ~277s (tailored setup) vs 444s baseline; billed windows seconds ~763 vs ~444. +- **Do not merge the two compiles:** adding `TreatWarningsAsErrors` to the analyzer build promotes analyzer + warnings to errors (unratified change) or forces carve-outs that weaken the nullable gate. `/t:Rebuild` + vs `/t:Build` is moot on a fresh runner but keep `/t:Rebuild` + its in-file comment. +- **Ruleset swap (id 18572843):** required contexts are exactly `actionlint` + `Format, build, analyze, and test`, + `strict: true`. Both PUT-before-merge and merge-before-PUT are fail-closed (unreported required contexts + block, never bypass); the only under-gating hazard is a PUT whose contexts set omits a gate. PUT is + full-replace — round-trip writable fields only (name, target, enforcement, bypass_actors, conditions, rules). +- **Check-run names for called workflows are ` / `** — capture exact strings + via `gh api repos/drmoisan/TaskMaster/commits//check-runs` from the split PR's green run BEFORE the PUT. + This naming detail is the most likely migration failure. +- **Concurrency:** keep the `concurrency` block in caller `ci.yml` only; callee-level workflow concurrency + under `workflow_call` is not clearly documented — avoid it. +- **Gaps recorded:** `.github/workflows/README.md` does not exist (AC requires creating it); + `docs/features/potential/promoted/2026-08-14-ci-parallel-job-split.md` absent (content lives in the feature + folder's issue.md); `TaskMaster.sln` has 18 projects (9 test), not 19 as delegation prose claimed; + orchestrate SKILL's nesting cap of 4 is stale vs current GitHub docs (10 levels) but repo uses one level. +- CSharpier 1.2.6 pinned in root-level `dotnet-tools.json`; the format gate needs no `nuget restore` and + should drop the packages cache. actions/cache: saves skipped on exact-key hit, caches immutable, lost + concurrent save is a warning not a failure. + +**Why:** The implementation, ruleset mutation, and post-split evidence capture happen in later sessions; +these are the non-obvious facts that were expensive to establish. + +**How to apply:** When planning/reviewing the #553 implementation or its ruleset migration, start from the +research artifact and verify the tailored-setup assumption (msbuild jobs without setup-dotnet) in the first +green run before trusting the ~277s estimate. diff --git a/.claude/agent-memory/task-researcher/project_svgcontrol_test_unwired_418.md b/.claude/agent-memory/task-researcher/project_svgcontrol_test_unwired_418.md index 71bfeddf..a016606a 100644 --- a/.claude/agent-memory/task-researcher/project_svgcontrol_test_unwired_418.md +++ b/.claude/agent-memory/task-researcher/project_svgcontrol_test_unwired_418.md @@ -1,10 +1,16 @@ --- name: svgcontrol-test-unwired-418 -description: SVGControl.Test is absent from TaskMaster.sln and its pinned test packages are missing from packages/, so it cannot build; plus the ExCSS/Fizzler binding-redirect topology behind issue #418 +description: "STALE as of 2026-08-14: SVGControl.Test is now IN TaskMaster.sln (line 42). Historical #418 findings on missing pinned packages and the ExCSS/Fizzler redirect topology retained below; re-verify build state before reuse" metadata: type: project --- +**UPDATE 2026-08-14:** `SVGControl.Test` now appears in `TaskMaster.sln` (project entry at line 42, +GUID `{13AC39E6-DE06-4337-8EB0-41CE674A4C3B}`), so the solution-membership claim below is stale. +Package/build state was not re-verified on 2026-08-14; treat everything below as a 2026-08-04 snapshot. + +Original 2026-08-04 findings: + `SVGControl.Test` is **not** listed in `TaskMaster.sln` (only `SVGControl` is), its `packages.config` pins `Castle.Core 5.1.1`, `FluentAssertions 6.12.0`, `Moq 4.20.69`, `MSTest.TestAdapter 3.1.1`, `MSTest.TestFramework 3.1.1` — none of which exist under `packages/` — and its diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md new file mode 100644 index 00000000..126a4b78 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md @@ -0,0 +1,76 @@ +# CI Sequential Baseline — Issue 553 + +- Captured: 2026-08-14T13:05:16Z +- Source run: https://github.com/drmoisan/TaskMaster/actions/runs/31749877507 +- Run conclusion: success +- Head branch: `bug/coverage-threshold-policy-reconciliation-494` +- Workflow: `.github/workflows/ci.yml` (pre-split) +- Collection command: `gh api repos/drmoisan/TaskMaster/actions/runs/31749877507/jobs` + +This is the measured sequential baseline the parallel split is compared against. +It is captured from a GitHub-hosted `windows-latest` runner, so it satisfies the +runner-environment parity requirement in `.claude/rules/benchmark-baselines.md` +for any latency comparison drawn against a later GitHub-hosted run. + +## Job-level totals + +| Job | Runner | Started | Completed | Duration | +| --- | --- | --- | --- | --- | +| `actionlint` | ubuntu-latest | 22:25:29Z | 22:26:05Z | 36s | +| `Format, build, analyze, and test` | windows-latest | 22:25:29Z | 22:32:53Z | **7m24s (444s)** | + +The two jobs already run concurrently, so total pipeline wall clock equals the +`quality-gates` job duration: **444s**. + +## Step-level breakdown of `Format, build, analyze, and test` + +| # | Step | Duration | Class | +| --- | --- | --- | --- | +| 1 | Set up job | 1s | setup | +| 2 | Checkout repository | 41s | setup | +| 3 | Setup .NET SDK | 35s | setup | +| 4 | Setup MSBuild | 5s | setup | +| 5 | Setup NuGet | 0s | setup | +| 6 | Cache NuGet packages | 15s | setup | +| 7 | Restore solution | 11s | setup | +| 8 | Cache dotnet tools | 16s | setup | +| 9 | Setup CSharpier | 5s | setup | +| 10 | **Verify formatting** | **15s** | gate | +| 11 | **Build with analyzers and code style enforcement** | **101s** | gate | +| 12 | **Build with nullable warnings treated as errors** (`/t:Rebuild`) | **98s** | gate | +| 13 | **Run MSTest suite with coverage** | **88s** | gate | +| 14 | Upload test results | 2s | teardown | +| 15-19 | Post-cache / post-checkout / complete job | ~12s | teardown | + +## Derived figures + +- **Fixed per-job setup cost** (steps 1-9): **130s**. This cost is paid once today + and would be paid once per parallel job after a split. +- **Gate work** (steps 10-13): **302s**, currently strictly serial. +- **Teardown** (steps 14-19): **~12s**. +- Sum: 130 + 302 + 12 = 444s, matching the job total. + +## Consequences for the split design + +The 130s fixed setup is the dominant constraint. A naive split into four +independent `windows-latest` jobs yields these estimated per-job durations: + +| Split job | Estimated duration | Notes | +| --- | --- | --- | +| format | 130 + 15 + 12 = **157s** | needs restore + csharpier tool only | +| analyzer build | 130 + 101 + 12 = **243s** | full solution compile | +| nullable build | 130 + 98 + 12 = **240s** | full `/t:Rebuild` recompile | +| MSTest | 130 + 101 + 88 + 12 = **331s** | must build before it can discover `*.Test.dll` | + +Estimated post-split wall clock is bounded by the slowest job: **~331s (5m31s)**, +against a 444s baseline — an estimated **~25% latency reduction**. + +Estimated billed `windows-latest` minutes rise from ~7.4 to ~16.2 (a ~2.2x +increase before the GitHub Windows 2x cost multiplier), because the 130s setup is +paid four times instead of once. + +These are estimates derived from the measured baseline, not measurements of a +split pipeline. The research stage must evaluate whether sharing build output +across jobs via `actions/upload-artifact` / `actions/download-artifact` beats +rebuilding per job, given that the upload and download of a 19-project solution's +`bin` output is itself not free. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md new file mode 100644 index 00000000..d79268a4 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md @@ -0,0 +1,123 @@ +# ci-parallel-job-split (Issue #553) + +- Date captured: 2026-08-14 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/ci-parallel-job-split/ (Issue #553) + +- Issue: #553 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/553 +- Last Updated: 2026-08-14 +- Work Mode: full-feature + +## Problem / Why + +`.github/workflows/ci.yml` runs the entire C# quality toolchain inside a single +`quality-gates` job on `windows-latest` with a 60-minute timeout. That job executes +five expensive stages strictly sequentially: + +1. `nuget restore` +2. `dotnet csharpier check .` (formatting) +3. `msbuild /t:Build` with `EnableNETAnalyzers` + `EnforceCodeStyleInBuild` (analyzers) +4. `msbuild /t:Rebuild` with `TreatWarningsAsErrors=true` (nullable/type-check) +5. `vstest.console.exe` with `/EnableCodeCoverage` (MSTest suite) + +Wall-clock CI latency is therefore the sum of all five stages. Stages 3 and 4 are +both full solution compiles of a 19-project solution, and stage 4 uses `/t:Rebuild`, +so it performs a complete recompile from scratch. A formatting violation is not +reported until after restore completes, and a test failure is not reported until +after two full builds have finished. + +Two additional consequences of the monolithic shape: + +- **No independent failure signal.** All five gates report as one required status + check named `Format, build, analyze, and test`, so a red check does not identify + which gate failed without opening the log. +- **No independent re-dispatch.** A transient failure in one stage requires + re-running the entire 60-minute job. + +## Proposed Behavior + +Decompose the monolithic `quality-gates` job into independent gates that GitHub +Actions schedules concurrently, so wall-clock CI latency approaches the duration of +the slowest single gate rather than the sum of all gates. + +Per `.claude/skills/orchestrate/SKILL.md` (`## GitHub Actions Reusable Workflows`), +each gate ships as a callable reusable workflow named `_.yml` declaring both +`on: workflow_call:` and `on: workflow_dispatch:`, and the orchestrator workflow +references them via `uses: ./.github/workflows/_.yml` with no inline `steps:` +of its own. Any file that must cross a job boundary uses explicit +`actions/upload-artifact` + `actions/download-artifact`; cross-job filesystem +reliance is not implicit. + +## Acceptance Criteria (early draft) + +- [ ] The formatting gate, the analyzer build gate, the nullable build gate, and the + MSTest gate each run as separate GitHub Actions jobs with no `needs:` edge + forcing them to serialize, except where an edge is required to consume an + uploaded build artifact. +- [ ] Each gate is a callable reusable workflow `_.yml` declaring both + `on: workflow_call:` and `on: workflow_dispatch:`. +- [ ] `ci.yml` becomes an orchestrator workflow containing only `uses:` references + and contains no inline `steps:`. +- [ ] Any file shared between jobs crosses the boundary via explicit + `actions/upload-artifact` + `actions/download-artifact`. +- [ ] The `main` branch ruleset's `required_status_checks` contexts are updated to + match the new job names, with no window in which a merge can bypass a gate. +- [ ] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` + procedure and the branch-protection rename procedure. +- [ ] The reworked pipeline produces a green run against the branch head, satisfying + `modified-workflow-needs-green-run`. +- [ ] Every gate enforced by the current `quality-gates` job is still enforced after + the split; no check is dropped, weakened, or made non-required. + +## Constraints & Risks + +- **Branch-protection coupling (blocking risk).** The `main` ruleset (id `18572843`, + `strict_required_status_checks_policy: true`) requires exactly two contexts: + `actionlint` and `Format, build, analyze, and test`. Splitting the job removes the + second context. Until the ruleset is updated, every PR will block on a check that + can never report. The ruleset update is automatable via + `gh api --method PUT repos/drmoisan/TaskMaster/rulesets/18572843` (the session token + holds `repo` scope and repository `admin: true`), but it is an outward-facing change + to the repository's merge policy and must be applied deliberately, in the correct + order relative to the merge of this change. +- **Windows runner setup cost is paid per job.** Each parallel job repeats checkout, + `setup-dotnet`, `setup-msbuild`, `setup-nuget`, and `nuget restore`. If the split is + naive, the added fixed cost per job can offset the parallelism gain. Caching + (`actions/cache` on `packages` and `~/.nuget/packages`) and a decision about whether + to share build output via artifact upload rather than rebuilding are both load-bearing. +- **Two full compiles are inherent to the current design.** The analyzer gate uses + `/t:Build` and the nullable gate uses `/t:Rebuild`; the `/t:Rebuild` choice is + deliberate and documented in-file (MSBuild's incremental up-to-date check does not + invalidate on a command-line property change alone). Whether these two compiles can + share output, must stay separate, or should run concurrently on separate runners is + the central design question. +- **Test gate needs built assemblies.** `vstest.console.exe` discovers `*.Test.dll` + under `bin/$BUILD_CONFIGURATION`. Running the test gate in a separate job requires + either its own build or a downloaded build artifact. +- **Local vstest discovery hazard.** Recursive `*.Test.dll` discovery can pick up + stale agent-worktree builds; the CI-side discovery filter must not regress. +- **Concurrency group.** `cancel-in-progress: true` is set at the workflow level and + must continue to behave correctly across the reusable-workflow boundary. +- **Actions concurrency limits.** Windows runner concurrency on the account may cap + how many `windows-latest` jobs actually run at once, bounding the realized speedup. + +## Test Conditions to Consider + +- [ ] `actionlint` passes against every new and modified workflow file. +- [ ] Each `_.yml` is independently dispatchable via `workflow_dispatch` and + succeeds standalone. +- [ ] A deliberate formatting violation fails only the formatting gate and reports a + distinct red check. +- [ ] A deliberate nullable violation fails only the nullable gate. +- [ ] A deliberate test failure fails only the MSTest gate. +- [ ] Test results and coverage artifacts continue to upload with the same names. +- [ ] Total wall-clock duration of the reworked pipeline is measured against the + current sequential baseline and recorded as evidence. +- [ ] No `pwsh` step leaks a residual non-zero `$LASTEXITCODE` per + `.claude/rules/ci-workflows.md`. + +## Next Step + +- [ ] Promote to GitHub issue (feature request template) +- [ ] Create `docs/features/active/ci-parallel-job-split/` folder from the template diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md new file mode 100644 index 00000000..1aa45452 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md @@ -0,0 +1,53 @@ +# 2026-08-14-ci-parallel-job-split - Plan + +- **Issue:** #553 +- **Parent (optional):** none +- **Owner:** drmoisan +- **Last Updated:** 2026-08-14T09-05 +- **Status:** Draft +- **Version:** 0.1 + +## Required References + +- General Coding Standards: [`.github/instructions/general-code-change.instructions.md`](../../../../.github/instructions/general-code-change.instructions.md) +- General Unit Test Policy: [`.github/instructions/general-unit-test.instructions.md`](../../../../.github/instructions/general-unit-test.instructions.md) +- (Add language-specific policies as needed, e.g. `python-code-change.instructions.md`) + +**All work must comply with these policies; do not duplicate their content here.** + +## Implementation Plan (Atomic Tasks) + +> **Instructions for this section:** +> - Break work into **Phases** (broad buckets) and **Atomic Tasks** (binary, 5-30 min units). +> - Use `- [ ] [P#-T#]` for every task. +> - Start every task with a **strong verb** (Implement, Create, Update, Verify). +> - No "bucket" tasks like "Refactor module" or "Write tests"; split them into specific, verifiable steps. +> - **Self-Validating Phases:** Include necessary test creation/update tasks *within* the phase that implements the code. Do not defer verification to a final "Testing" phase. +> - Include explicit baseline artifact tasks, final-QA artifact tasks, and coverage-comparison tasks for every language in scope when policy requires coverage. +> - Name the expected artifact path or location in each evidence-producing task's acceptance criteria. +> - If any required baseline artifact, QA artifact, or coverage-comparison artifact is missing, the audit verdict must be BLOCKED or INCOMPLETE, never PASS. + +### Phase 0: Compliance & Context +- [ ] [P0-T1] Confirm alignment with repo policies by reading `.github/instructions/general-code-change.instructions.md`, `.github/instructions/python-code-change.instructions.md`, `.github/instructions/general-unit-test.instructions.md`, and `.github/instructions/python-unit-test.instructions.md` before touching code + - Acceptance: Development log contains policy review timestamp prior to Phase 1 commits + +### Phase 1: +- [ ] [P1-T1] +- [ ] [P1-T2] + - Preconditions: + - Acceptance: + +### Phase 2: +- [ ] [P2-T1] +- [ ] [P2-T2] + +## Test Plan + +- Unit: ... +- Integration: ... +- Manual/CLI: ... +- Coverage evidence: list baseline artifact paths, post-change artifact paths, and comparison artifact paths for each in-scope language + +## Open Questions / Notes + +- ... diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md new file mode 100644 index 00000000..531eaa83 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md @@ -0,0 +1,285 @@ +# Research — CI Parallel Job Split (Issue #553) + +- **Issue:** #553 +- **Feature folder:** `docs/features/active/2026-08-14-ci-parallel-job-split-553/` +- **Date:** 2026-08-14T13-30 +- **Author:** task-researcher agent +- **Status:** Complete + +## Input Verification and Gaps + +All findings below are grounded in files read this session or in cited external documentation. + +| Input | Status | +| --- | --- | +| `.github/workflows/ci.yml` | Read. 2 jobs: `actionlint` (ubuntu-latest) and `quality-gates` named `Format, build, analyze, and test` (windows-latest). | +| `evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` | Read. Treated as authoritative measured data. | +| `docs/features/potential/promoted/2026-08-14-ci-parallel-job-split.md` | **GAP: file does not exist** at the stated path (verified by glob of `docs/features/potential/**`). The promoted content is present in the feature folder at `issue.md` (whose status line reads "Promoted"), which was used as the substitute source for problem statement, draft acceptance criteria, and constraints. | +| `.claude/rules/ci-workflows.md` | Read. | +| `.claude/rules/benchmark-baselines.md` | In session context. | +| `.claude/skills/orchestrate/SKILL.md` § GitHub Actions Reusable Workflows | Read (line 159). | +| `.github/workflows/README.md` | **GAP: does not exist.** `.github/workflows/` contains only `ci.yml` and `codex-web-setup-test.yml`. The skill section references this README; acceptance criteria require creating it. | +| `TaskMaster.sln` | Read. **Contains 18 projects, not 19** as stated in the delegation and baseline prose (counted: Tags, ToDoModel, TaskVisualization, UtilitiesCS, QuickFiler, TaskTree, TaskMaster, SVGControl, VBFunctions, plus 9 `*.Test` projects: ToDoModel.Test, UtilitiesCS.Test, QuickFiler.Test, TaskVisualization.Test, Tags.Test, TaskTree.Test, SVGControl.Test, VBFunctions.Test, TaskMaster.Test). The discrepancy is not load-bearing for the design. Legacy `packages.config` / .NET Framework solution built with `msbuild`, confirmed. | +| Nesting-depth note | The skill records a reusable-workflow nesting cap of 4. Current GitHub documentation states "a maximum of ten levels of workflows — that is, the top-level caller workflow and up to nine levels of reusable workflows" (docs.github.com, Reusing workflows, fetched 2026-08-14). The repo convention of one level satisfies both; the skill figure is stale relative to current docs but imposes the binding constraint here. | + +## Current State Analysis + +`ci.yml` (161 lines) defines: + +- Workflow-level `concurrency: group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}`, `cancel-in-progress: true`. +- `permissions: contents: read`. +- Job `quality-gates` (timeout 60 min) with steps, measured durations from the baseline in parentheses: + - Setup: checkout (41s), setup-dotnet 10.0.x (35s), setup-msbuild (5s), setup-nuget (0s), cache `packages` keyed `nuget--hashFiles('**/packages.config')` (15s), `nuget restore` (11s), cache `~/.nuget/packages` keyed `dotnet-tools--hashFiles('dotnet-tools.json')` (16s), `dotnet tool restore` (5s). Fixed setup total: **130s**. + - Gate 1 — `dotnet csharpier check .` (**15s**). CSharpier 1.2.6 is pinned in root-level `dotnet-tools.json`; it is a file-based formatter and does not consume restored NuGet packages. + - Gate 2 — `msbuild /t:Build /m /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` (**101s**), with explicit `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }`. + - Gate 3 — `msbuild /t:Rebuild /m /p:TreatWarningsAsErrors=true` (**98s**), same exit guard. The in-file comment documents that `/t:Rebuild` is deliberate: MSBuild's incremental up-to-date check does not invalidate on a command-line property change alone. + - Gate 4 — vswhere-located `vstest.console.exe` over recursively discovered `*.Test.dll` filtered to `\bin\Debug\`, excluding `\obj\` and `\ref\`, with `/EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook"` and a `throw` guard on zero discovered assemblies (**88s**). + - Teardown: upload `TestResults/**/*.trx` + `*.coverage` as artifact `test-results` (2s), post-cache/post-checkout (~10s). +- Baseline wall clock: **444s** (7m24s); gate work 302s strictly serial; setup 130s; teardown ~12s. + +Branch protection (verified fact supplied by the orchestrator via `gh api repos/drmoisan/TaskMaster/rulesets/18572843`): the `main` ruleset requires exactly two contexts, `actionlint` and `Format, build, analyze, and test`, with `strict_required_status_checks_policy: true`. + +--- + +## Q1. Decomposition Topology + +Three topologies evaluated. All estimates below are **estimates derived arithmetically from the measured baseline**, not measurements of a split pipeline. Assumption common to all: per-step durations are stable across runs of the same runner class (satisfies `.claude/rules/benchmark-baselines.md` runner parity, since baseline and future comparison are both GitHub-hosted `windows-latest`). + +### (a) Four fully independent jobs, each with the full 130s setup + +| Job | Estimate | Composition | +| --- | --- | --- | +| format | ~150s | 130 setup + 15 gate + ~5 teardown | +| analyzer build | ~241s | 130 + 101 + ~10 | +| nullable build | ~238s | 130 + 98 + ~10 | +| MSTest | ~333s | 130 + ~101 own build + 88 + ~14 | + +Wall clock ≈ **333s (5m33s)**, bounded by the MSTest job. Billed `windows-latest` ≈ 962s ≈ **16.0 min** (vs 7.4 baseline), ~2.2x before the Windows 2x billing multiplier. Assumption: the MSTest job's own build takes about as long as the analyzer build (101s); a plain build without analyzers is plausibly faster, but no measurement exists. + +### (b) Build-once, upload `bin` output, downstream test job downloads + +Chain: build job (130 setup + 101 build + upload T_u) → `needs:` → test job (~45s minimal setup + download T_d + 88 test + ~14). + +Wall clock ≈ 245 + T_u + T_d + 147 ≈ **392s + transfer time even if transfer were free would be ~392s**, i.e. worse than topology (a)'s 333s before any transfer cost is added. The serial `needs:` edge means the test path pays build-job setup + build + test **in sequence**, which is structurally the same critical path as topology (a)'s test job plus artifact transfer overhead. Artifact sharing cannot beat own-build on wall clock here (see Q3 for the cost analysis and fragility). Rejected. + +### (c) Hybrid (recommended): four independent jobs with per-job tailored setup; test job performs its own plain build + +Setup is trimmed to what each job actually consumes: + +- **format**: checkout (41) + setup-dotnet (35) + dotnet-tools cache (16) + `dotnet tool restore` (5). Drops setup-msbuild, setup-nuget, packages cache, and `nuget restore` (CSharpier does not consume NuGet packages). Estimated ~119s total (≈98 setup + 15 gate + ~5 teardown). +- **analyzer / nullable / MSTest**: checkout (41) + setup-msbuild (5) + setup-nuget (0) + packages cache (15) + `nuget restore` (11) ≈ 73s setup. Drops setup-dotnet, dotnet-tools cache, and `dotnet tool restore`. **Assumption requiring verification in the first green run:** nothing in the msbuild build path depends on the pinned .NET 10 SDK; a legacy `packages.config` msbuild build should not, but this is unverified. + +| Job | Estimate | +| --- | --- | +| format | ~119s | +| analyzer build | ~185s (73 + 101 + ~10) | +| nullable build | ~182s (73 + 98 + ~10) | +| MSTest | ~277s (73 + ~101 build + 88 + ~14) | + +Wall clock ≈ **277s (4m37s)**, an estimated **~38% latency reduction** vs 444s. Billed `windows-latest` ≈ 763s ≈ **12.7 min** (~1.7x baseline). If the tailored-setup assumption fails and full setup is needed everywhere, topology (c) degrades gracefully to topology (a)'s figures (~333s wall, ~16 min billed, ~25% reduction) — still an improvement. + +**Recommendation: topology (c).** Rejected alternatives: (a) is (c) without the setup trim — strictly dominated; (b) is rejected on critical-path arithmetic plus the fragility documented in Q3. + +A further optional optimization — moving the format gate to `ubuntu-latest` (CSharpier is cross-platform; Linux minutes bill at 1x vs Windows 2x) — is flagged but **not recommended for this change**: it introduces a platform-parity question (line endings, tool behavior) that should not be bundled with the topology split. Record as a potential follow-up. + +## Q2. The Two-Full-Compiles Question + +**Concurrent on separate runners: yes, trivially safe.** Each job checks out its own workspace on its own runner; there is no shared mutable state. The `/t:Rebuild` rationale (incremental up-to-date check not invalidating on property change) exists to defeat *within-runner* staleness from the preceding `/t:Build`; on a fresh runner there is no prior output, so `/t:Build` and `/t:Rebuild` are equivalent in cost and effect. **Keep `/t:Rebuild` anyway**: it preserves the documented in-file intent, costs nothing on a clean runner, and keeps the standalone `workflow_dispatch` path correct regardless of runner reuse assumptions. The explanatory comment must move with the step. + +**Merging into one compile: technically feasible, but it would change enforcement semantics of both gates; do not merge.** A single `msbuild /t:Rebuild /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true /p:TreatWarningsAsErrors=true` invocation would: + +1. Promote analyzer and code-style **warnings** to errors. Today the analyzer gate fails only on errors and passes with warnings; the merged gate would either fail currently-green code or require `WarningsNotAsErrors` carve-outs that risk exempting nullable diagnostics — the carve-out path is a **weakening** of the nullable gate, and the no-carve-out path is an unratified **strengthening** of the analyzer gate. Either direction alters a gate. +2. Collapse the independent failure signal, which is one of the two stated objectives of issue #553 ("a red check does not identify which gate failed"). +3. Halve the compile cost only on the billed-minutes axis (~98s saved) while the wall clock is already bounded by the MSTest job, so the merge buys no latency. + +Conclusion: run the two compiles concurrently in separate jobs, unmodified. + +## Q3. Artifact-Sharing Viability + +Cost estimate for uploading/downloading `bin\Debug` output of this solution: + +- No local build output exists in this worktree to measure (verified: glob of `*/bin/Debug/*.dll` returns nothing). The following is an **estimate**: a legacy .NET Framework solution with `packages.config` uses CopyLocal semantics, so each of the 9 test projects' `bin\Debug` contains a full copy of its dependency closure (Interop assemblies, Moq, FluentAssertions, MSTest adapter, Newtonsoft, project references, plus `.pdb` files required for coverage attribution). Plausible aggregate size: **0.5–2 GB uncompressed**, compressing to perhaps 150–600 MB. +- `actions/upload-artifact@v4` packs the artifact as a single compressed archive before upload (an improvement over v3's per-file chunking, so "many small assemblies" is less pathological than under v3, but compression of hundreds of MB of binaries still costs CPU time on the runner and the archive still transfers both ways). Estimated upload 30–120s, download 15–60s. These are estimates; variance on hosted runners is high. +- Critical-path arithmetic (Q1b) shows own-build wins **even at zero transfer cost**, because the `needs:` edge serializes build-job setup + compile ahead of the test job. Transfer cost only widens the gap. +- Fragility beyond latency: (i) coverage silently degrades if `.pdb` files are excluded to shrink the upload; (ii) any CopyLocal dependency missing from the upload glob surfaces as runtime assembly-load failures inside test runs, not as a clear infrastructure error; (iii) `upload-artifact@v4` strips the least common ancestor of the matched paths — if a future edit narrows the glob to one project, the preserved directory structure silently changes and the discovery filter breaks. + +**Conclusion: per-job rebuild beats artifact sharing for this solution.** Evidence that would settle it definitively: one instrumented run that uploads `**/bin/Debug/**` and records archive size plus upload/download step durations. That experiment is not required to justify the recommendation, because the critical-path argument is independent of transfer speed. + +## Q4. Test-Assembly Discovery + +Under the recommended topology the test job builds in its own workspace, so the existing discovery block is **unchanged**: recurse `$env:GITHUB_WORKSPACE` for `*.Test.dll`, match `\bin\Debug\` (via `$env:BUILD_CONFIGURATION`), exclude `\obj\` and `\ref\`, `throw` on zero results. The zero-assembly `throw` guard must be preserved verbatim — it is the fail-closed protection against a discovery regression. + +If a future change adopts artifact download instead, the required alterations and failure modes are: + +- The filter root must become the `actions/download-artifact` destination path (or the artifact must be downloaded to the workspace root with structure preserved, in which case the existing filter continues to match — contingent on the upload glob's least-common-ancestor being the workspace root). +- Silent-break modes: (i) upload glob mismatch → zero assemblies downloaded → the `throw` guard fires (fail-closed, acceptable); (ii) a job that both builds *and* downloads could discover duplicate or stale assemblies — a split must never mix the two acquisition modes in one job; (iii) missing `.pdb`/dependency files degrade coverage or fail tests at runtime (see Q3). +- The known local hazard (recursive discovery picking up stale `.claude/worktrees` builds) does not apply on a fresh hosted runner, but the exclusion discipline should not be weakened, since the same script text is the reference for local runs. + +## Q5. Caching Under Concurrency + +Documented behavior (actions/cache README, fetched 2026-08-14): "if the provided `key` matches an existing cache, a new cache is not created", and caches are immutable ("You cannot change the contents of an existing cache"). Consequences for four concurrent jobs sharing the keys `nuget--` and `dotnet-tools--`: + +- **Steady state (key exists):** all consuming jobs restore concurrently (reads are safe) and every post-job save is skipped. No race. +- **Cache-miss state (e.g., the PR edits `packages.config`):** each consuming job misses, falls back to the `restore-keys` prefix, runs its own `nuget restore` (~11s), and attempts to save the same new key at job end. Caches are immutable and creation is first-writer-wins; the losing jobs emit a reservation warning and skip the save. The precise race mechanics ("Unable to reserve cache" warning) are **not documented** in the README or docs pages fetched this session — they are known runtime behavior widely observed in the wild. What the documentation does establish is immutability, which guarantees no corruption. A lost save never fails the job. +- **Format job:** should **not** restore the NuGet packages cache and should not run `nuget restore` at all — CSharpier reads source text only. It needs only the `~/.nuget/packages` cache keyed on `dotnet-tools.json` for `dotnet tool restore`. Dropping the packages cache from this job removes ~26s of setup and one gratuitous concurrent reader. +- The `~/.nuget/packages` cache is needed only by the format job under the tailored-setup recommendation, which incidentally eliminates concurrent saves of that key entirely. + +## Q6. Concurrency Group Behavior Across the Reusable-Workflow Boundary + +Established, documented facts: jobs of a called workflow run as part of the **caller's** workflow run, and a workflow-level `concurrency` group governs the entire run. Therefore the existing declaration in `ci.yml` continues to cover all callee jobs after the split, with `cancel-in-progress: true` cancelling the whole run (all four gate jobs) when a newer run for the same PR/ref starts. + +The behavior of a **workflow-level `concurrency` key declared inside a callee** when invoked via `workflow_call` is **not clearly documented**: the GitHub docs pages fetched this session (Using concurrency; Reusing workflows) do not specify it, and community reports of unexpected queueing/deadlock exist. Design consequence: + +- **Declare the group in the caller (`ci.yml`) only, exactly as today.** +- **Declare no `concurrency` in any callee.** When a callee runs standalone via `workflow_dispatch`, it forms its own run with no concurrency group — acceptable for a manual dispatch path. +- Job-level `concurrency` on the caller's `uses:` jobs is supported but unnecessary here. + +## Q7. Windows Runner Concurrency Ceiling + +Per GitHub's Actions limits reference (fetched 2026-08-14): maximum concurrent jobs on standard GitHub-hosted runners are **Free 20, Pro 40, Team 60, Enterprise 500**; only macOS carries a separate lower cap (5 for Free/Pro/Team). There is **no separate Windows cap**. The split needs at most 5 concurrent jobs per run (4 × `windows-latest` + 1 × `ubuntu-latest`), which fits with margin under even the Free tier — provided other runs across the account are not consuming the budget simultaneously. The limit is account-wide; multiple concurrent PRs/pushes multiply the demand (e.g., 4 simultaneous runs × 5 jobs = 20 jobs, saturating a Free account and queueing the excess). + +How to check: the account's plan is not readable from repository data. `gh api user --jq .plan.name` reports the authenticated user's plan for a personal account; the billing settings page is authoritative. Whether concurrent load from other repositories under the account matters is org/account-level operational data that cannot be determined from this repository — if that data is unavailable, the honest statement is: the per-run demand (5 jobs) is safely below every plan's ceiling, and the realized speedup could be bounded only by cross-run contention, which is unknowable from here. + +## Q8. Required-Check Migration Sequencing + +Key structural facts: + +1. For `pull_request` events, the PR's own head-ref workflow files execute, so the split PR itself runs the **new** pipeline and reports the **new** contexts. +2. A required context that is never reported stays "expected" and **blocks** merging (fail-closed). Both orderings of PUT-vs-merge therefore over-block rather than under-gate. +3. For reusable workflows, the check-run context name takes the form ` / `. The exact strings must be captured from a real run, not assumed. This is the single most likely source of a botched migration. +4. The only under-gating hazard is a PUT whose new contexts set omits a gate (for example, a two-step edit that removes `Format, build, analyze, and test` before adding the four replacements, leaving `actionlint` as the sole required check). The mitigation is a **single atomic PUT** that replaces the old set with the complete new set in one request. `PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}` replaces the ruleset content, so the request must carry the full writable object (name, target, enforcement, bypass_actors, conditions, rules), not a partial patch. + +Correct order of operations: + +1. Open the split PR. Its run reports the four new contexts; it is blocked by the still-required old context (expected, fail-closed). +2. Confirm the run is green and capture the **exact** check-run names from the PR head: `gh api repos/drmoisan/TaskMaster/commits//check-runs --jq '.check_runs[].name'`. +3. Fetch the current ruleset, construct the updated body, and apply it in one PUT: + + ``` + gh api repos/drmoisan/TaskMaster/rulesets/18572843 > ruleset-current.json + # Build ruleset-new.json from the writable fields of ruleset-current.json + # (name, target, enforcement, bypass_actors, conditions, rules), replacing the + # required_status_checks rule's parameters.required_status_checks with: + # [{"context": "actionlint"}, {"context": ""}, {"context": ""}, + # {"context": ""}, {"context": ""}] + # and retaining strict_required_status_checks_policy: true. + gh api --method PUT repos/drmoisan/TaskMaster/rulesets/18572843 --input ruleset-new.json + ``` + + Read-only fields returned by GET (`id`, `node_id`, `created_at`, `updated_at`, `_links`, `source`, `source_type`, `current_user_can_bypass`) must not be treated as part of the writable payload. +4. Immediately merge the split PR. `strict_required_status_checks_policy: true` requires the branch to be up to date with `main`; update/rebase first if needed so the green run is against the current base. +5. Verify by GET that the ruleset holds exactly the five intended contexts, and record the response as evidence. + +Between steps 3 and 4, every *other* open PR (still running the old pipeline from its own head ref) reports the old context and lacks the new ones, so it is blocked until it updates its branch past the merged split — over-blocking, never under-gating. `strict` further guarantees any such PR must take the new `main` (and therefore, after updating, still runs its own head's workflow — note that a stale branch that merely merges in `main` acquires the new workflow files and reports the new contexts on its next run). + +Rollback: a single PUT restoring the previous contexts set reverts the merge policy; reverting the workflow change is an ordinary revert PR. + +## Q9. `$LASTEXITCODE` Hygiene Review + +Per `.claude/rules/ci-workflows.md`, the mandatory pattern applies to steps that **intentionally invoke a failing nested command**. Review of every `pwsh` step that moves into a callee: + +| Step | Last external command on success path | Verdict | +| --- | --- | --- | +| `nuget restore` | `nuget restore` (exit 0 on success) | Compliant; failure should propagate. No change. | +| `dotnet tool restore` | `dotnet tool restore` | Compliant. No change. | +| `dotnet csharpier check .` | The gate command itself | Compliant; a non-zero exit **is** the gate signal. No reset permitted. | +| Analyzer build | `msbuild` followed by explicit `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` | Compliant; retain the guard verbatim when relocating. | +| Nullable build | Same pattern | Compliant; retain, including the `/t:Rebuild` rationale comment. | +| vstest step | `vstest.console.exe`, then `throw` on non-zero; earlier external commands (`vswhere`) run before the gate | Compliant; on success the script ends after a passing `if`, leaving exit 0. Retain the zero-assembly `throw`. | + +**No step in the current pipeline uses the deliberately-failing-nested-command pattern, so no `exit 0` / reset additions are required.** The rule becomes load-bearing only if the implementation adds negative-path self-validation steps (e.g., a step that asserts a gate catches a synthetic violation); any such step must reset `$LASTEXITCODE` or `exit 0` explicitly. + +## Q10. Recommendation + +**Topology (c): four independent reusable-workflow jobs with per-job tailored setup; no build-output artifact sharing; the MSTest job performs its own plain in-workspace build.** + +Proposed shape (names illustrative; final required-context strings are captured from the first real run per Q8): + +- `.github/workflows/_format-check.yml` — checkout, setup-dotnet, dotnet-tools cache, `dotnet tool restore`, `dotnet csharpier check .`. +- `.github/workflows/_build-analyzers.yml` — checkout, setup-msbuild, setup-nuget, packages cache, `nuget restore`, analyzer `/t:Build`. +- `.github/workflows/_build-nullable.yml` — same setup, nullable `/t:Rebuild` with its rationale comment. +- `.github/workflows/_mstest-coverage.yml` — same setup, plain `msbuild /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"`, unchanged discovery + vstest step, unchanged `test-results` artifact upload (`if: always()`). +- Each callee declares `on: workflow_call:` **and** `on: workflow_dispatch:`, its own `permissions: contents: read`, a right-sized `timeout-minutes` (format ~10; builds ~30; test ~30), and **no** `concurrency`. +- `ci.yml` becomes the orchestrator: keeps `on:`, `permissions`, and the existing workflow-level `concurrency` block; contains only the `actionlint` job (which may itself be extracted to `_actionlint.yml` for convention consistency — note its required-context name would then also change and must be included in the ruleset PUT) and four `uses: ./.github/workflows/_.yml` jobs with **no `needs:` edges** and no inline `steps:`. +- `.github/workflows/README.md` is created, documenting per-stage `workflow_dispatch` and the branch-protection rename procedure (Q8), closing the reference gap in `orchestrate/SKILL.md`. + +Expected outcomes (estimates, to be verified against a measured post-split run per the acceptance criteria): + +- **Latency:** ~277s wall clock vs 444s baseline (~38% reduction) if the tailored setup holds; worst case ~333s (~25% reduction) with full setup per job. +- **Billed cost:** `windows-latest` seconds rise from ~444 to ~763 (tailored) or ~962 (full setup), i.e. ~1.7–2.2x before GitHub's Windows 2x multiplier. +- **Independent failure signal and per-gate re-dispatch:** achieved by construction (four contexts; each callee dispatchable standalone). + +Residual risks: + +1. **Context-name mismatch in the ruleset PUT** — highest-likelihood failure; mitigated by capturing names from the live run before the PUT (Q8 step 2) and by fail-closed blocking if a name is wrong. +2. **Tailored-setup assumption** (msbuild jobs without setup-dotnet; format job without nuget restore) is unverified until the first green run; fallback is restoring the dropped steps at ~56s/job cost. +3. **Estimated timings are estimates**; hosted-runner variance (checkout and setup steps in particular) can shift per-job durations by tens of seconds. +4. **Cross-run account-level runner contention** could queue jobs and erode the realized speedup (Q7); unknowable from repository data. +5. **Undocumented callee-level concurrency semantics** are avoided rather than relied upon (Q6). + +--- + +## Behavior Semantics + +- **Success:** all five required contexts (`actionlint` + four gates) report success on the PR head; each gate's pass criterion is byte-identical to the corresponding step in the monolithic job. +- **Failure:** exactly the violated gate's context reports failure; the other gates complete independently (no `needs:` coupling), preserving full diagnostic signal per run. +- **Ordering:** none between gates. `cancel-in-progress` cancels all jobs of a superseded run together via the caller's group. +- **Edge cases:** zero discovered test assemblies → `throw` (fail-closed); `nuget restore` failure fails the three msbuild-consuming jobs independently; a cache-save race loses silently and harmlessly (Q5); a callee dispatched standalone runs without a concurrency group. + +## Requirements Mapping (draft acceptance criteria → design) + +| Acceptance criterion (issue.md) | Design element | +| --- | --- | +| Four gates as separate jobs, no serializing `needs:` | Four caller jobs, zero `needs:` edges (no artifact consumption exists to justify one) | +| Each gate a `_.yml` with `workflow_call` + `workflow_dispatch` | Four callee files as specified | +| `ci.yml` orchestrator with no inline `steps:` | Requires also extracting `actionlint` into `_actionlint.yml`; if it remains inline, this criterion fails as written — the plan must either extract it (and include its possibly-renamed context in the PUT) or the criterion must be amended. Recommended: extract it. | +| Cross-job files via explicit artifacts | Only `test-results` upload remains (to workflow storage, not cross-job); no cross-job file sharing exists in the recommended topology | +| Ruleset contexts updated with no bypass window | Q8 single-PUT procedure | +| `.github/workflows/README.md` documents dispatch + rename procedure | New file (currently absent) | +| Green run vs branch head (`modified-workflow-needs-green-run`) | The split PR's own run; S9 CI-green gate | +| No gate dropped/weakened/made non-required | Gate commands byte-identical; Q2 forbids the compile merge; all four contexts required | + +## Testing Implications + +Workflow YAML has no unit-test harness in this repo; verification is by CI itself, consistent with existing practice: + +- `actionlint` must pass over all new/modified workflow files (already a required check; runs in the same PR). +- Seeded negative-path conditions from the spec (deliberate formatting violation fails only the format gate; deliberate nullable violation fails only the nullable gate; deliberate test failure fails only the MSTest gate) are exercised as **temporary probe commits on the PR branch, then reverted** — not as permanent workflow steps, so no deliberately-failing nested commands enter the committed pipeline and no `$LASTEXITCODE` resets become necessary. +- Standalone `workflow_dispatch` of each callee, once merged, verifies the dispatch path. +- The post-split timing evidence is captured with the same `gh api .../runs//jobs` method as the baseline, satisfying runner-environment parity. +- No C# code changes; the C# toolchain loop applies only if the implementation unexpectedly touches `*.cs`/`*.csproj`. + +## Automation Feasibility + +Assessment of every step of the proposed change for unattended execution: + +| Step | Unattended? | Basis | +| --- | --- | --- | +| Author 4–5 callee workflows + orchestrator `ci.yml` + README | Yes | File edits only | +| Local actionlint validation | Yes | Binary download + run, same as the CI job does | +| Commit, PR creation | Yes | Existing `pr-author` skill + `gh` flow | +| Observe green run, capture per-job timings and exact check-run names | Yes | `gh api` polling (S9 gate already automates this) | +| Branch-protection ruleset PUT | **Yes — confirmed sufficient** | The orchestrator verified the session token holds the `repo` scope and `repos/drmoisan/TaskMaster` reports `permissions.admin: true`. The REST endpoint `PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}` requires repository admin permission, which a classic-token `repo` scope held by an admin satisfies. Caveats: (i) if the ambient credential were a fine-grained PAT it would instead need the "Administration" repository permission (write) — not the verified case here; (ii) the PUT is full-replace, so the automation must round-trip the writable fields (Q8 step 3) to avoid clobbering other rules in the ruleset. A pre-check `GET` plus a dry construction of the payload can be validated automatically (e.g., assert the new body differs from the old only in the contexts array) before the mutating call. | +| Merge the split PR | Yes | `gh pr merge` once the five new-set contexts are green and the branch is up to date (`strict` policy) | +| Post-merge verification (ruleset GET, dispatch smoke of each callee, timing evidence capture) | Yes | `gh api` / `gh workflow run` | +| Rollback (restore old contexts via PUT; revert PR) | Yes | Same mechanisms | + +**No step technically requires a human.** The one step warranting deliberate handling rather than blind automation is the ruleset PUT: it is an outward-facing change to the repository's merge policy (the feature folder's issue.md says it "must be applied deliberately, in the correct order"). The Q8 sequencing plus the payload-diff assertion above constitute that deliberate procedure in automatable form; the orchestrator should record the pre-PUT ruleset JSON, the PUT payload, and the post-PUT GET response as evidence in the feature folder's `evidence/` tree. If the project maintainer wishes to reserve merge-policy mutations to a human, that is a policy choice, not a technical necessity — no such reservation is currently recorded in repo policy. + +## Rejected Alternatives (summary) + +- **Topology (a)** — four jobs with untrimmed full setup: strictly dominated by (c); serves as (c)'s fallback bound. +- **Topology (b)** — build-once + artifact sharing: loses on critical-path arithmetic even at zero transfer cost; adds `.pdb`/dependency-closure fragility and a silent path-structure hazard in `upload-artifact@v4`. +- **Merged analyzer+nullable compile:** alters enforcement semantics of both gates and defeats the independent-signal objective (Q2). +- **Format gate on `ubuntu-latest`:** deferred as a follow-up; introduces a platform-parity question out of scope for the split. + +## Evidence Sources + +- `C:\Users\DanMoisan\repos\TaskMaster-wt\2026-08-14T09-01\.github\workflows\ci.yml` (read in full) +- `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` (measured baseline; authoritative) +- `docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md`, `spec.md` (promoted content; substitute for the absent promoted-entry file) +- `.claude/rules/ci-workflows.md`, `.claude/rules/benchmark-baselines.md` +- `.claude/skills/orchestrate/SKILL.md` line 159 (reusable-workflow convention) +- `TaskMaster.sln` (18 projects, 9 test projects), `dotnet-tools.json` (CSharpier 1.2.6, root-level) +- GitHub docs fetched 2026-08-14: Reusing workflows (10-level nesting statement); actions/cache README (exact-key save skip; cache immutability); Actions limits (concurrency per plan: Free 20 / Pro 40 / Team 60 / Enterprise 500; macOS-only separate cap) +- Ruleset facts (`18572843`, two required contexts, `strict_required_status_checks_policy: true`) supplied by the orchestrator as verified via `gh api`; not re-collected this session diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md new file mode 100644 index 00000000..5c7722f6 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md @@ -0,0 +1,385 @@ +# 2026-08-14-ci-parallel-job-split — Spec + +- **Issue:** #553 +- **Parent (optional):** none +- **Owner:** drmoisan +- **Last Updated:** 2026-08-14 +- **Status:** Ready for planning +- **Version:** 1.0 + +## Overview + +### Problem statement + +`.github/workflows/ci.yml` runs the entire C# quality toolchain inside a single +`quality-gates` job (`Format, build, analyze, and test`) on `windows-latest` with a +60-minute timeout. The job executes five expensive stages strictly sequentially: + +1. `nuget restore` +2. `dotnet csharpier check .` (formatting) +3. `msbuild /t:Build` with `EnableNETAnalyzers` + `EnforceCodeStyleInBuild` (analyzers) +4. `msbuild /t:Rebuild` with `TreatWarningsAsErrors=true` (nullable/type-check) +5. `vstest.console.exe` with `/EnableCodeCoverage` (MSTest suite) + +Wall-clock CI latency is the sum of all five stages. Stages 3 and 4 are both full +solution compiles (the solution contains 18 projects: 9 production, 9 test — the +earlier "19-project" figure was corrected during research), and stage 4 uses +`/t:Rebuild`, so it performs a complete recompile from scratch. A formatting +violation is not reported until after restore completes; a test failure is not +reported until after two full builds have finished. + +Two additional consequences of the monolithic shape: + +- **No independent failure signal.** All gates report as one required status check + named `Format, build, analyze, and test`, so a red check does not identify which + gate failed without opening the log. +- **No independent re-dispatch.** A transient failure in one stage requires + re-running the entire job. + +### Measured baseline + +Source of record: +`docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` +(captured 2026-08-14T13:05:16Z from GitHub-hosted `windows-latest` run +[31749877507](https://github.com/drmoisan/TaskMaster/actions/runs/31749877507), +satisfying the runner-environment parity requirement of +`.claude/rules/benchmark-baselines.md`). + +| Measured quantity | Value | +| --- | --- | +| `quality-gates` job wall clock (= pipeline wall clock) | **444s** (7m24s) | +| Fixed per-job setup (checkout through tool restore) | **130s** | +| Gate 1 — formatting (`dotnet csharpier check .`) | **15s** | +| Gate 2 — analyzer build (`/t:Build`) | **101s** | +| Gate 3 — nullable build (`/t:Rebuild`) | **98s** | +| Gate 4 — MSTest suite with coverage | **88s** | +| Teardown (artifact upload, post-steps) | ~12s | + +## Behavior + +### Adopted design + +The design adopted for this feature is topology (c) from the research artifact +(`research/2026-08-14T13-30-ci-parallel-job-split-research.md`, Q1 and Q10): four +independent `windows-latest` gate jobs with per-job tailored setup, no build-output +artifact sharing, and the MSTest job performing its own plain in-workspace build. +Additionally, the `actionlint` job is extracted into a callee workflow of its own, +so `ci.yml` contains no inline `steps:` at all. These decisions are settled; the +implementation plan must not re-open them. + +### Target architecture — five callee workflows + +Each gate ships as a callable reusable workflow under `.github/workflows/`: + +| File | Runner | Contents | +| --- | --- | --- | +| `_actionlint.yml` | `ubuntu-latest` | The existing actionlint job, moved verbatim: checkout (`fetch-depth: 1`), download actionlint 1.7.7, run `./actionlint`. | +| `_format-check.yml` | `windows-latest` | Checkout, `setup-dotnet` (10.0.x), dotnet-tools cache (`~/.nuget/packages` keyed on `dotnet-tools.json`), `dotnet tool restore`, `dotnet csharpier check .`. Deliberately omits `setup-msbuild`, `setup-nuget`, the `packages` cache, and `nuget restore` — CSharpier reads source text only and does not consume restored NuGet packages. | +| `_build-analyzers.yml` | `windows-latest` | Checkout, `setup-msbuild`, `setup-nuget`, `packages` cache (keyed on `**/packages.config`), `nuget restore`, then the analyzer gate: `msbuild /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true` with its existing `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` guard, byte-identical to today. | +| `_build-nullable.yml` | `windows-latest` | Same setup as `_build-analyzers.yml`, then the nullable gate: `msbuild /t:Rebuild /m /p:Configuration=Debug "/p:Platform=Any CPU" /p:TreatWarningsAsErrors=true` with the same exit guard, byte-identical to today, **including the in-file rationale comment explaining why `/t:Rebuild` is used**. The comment moves with the step. | +| `_mstest-coverage.yml` | `windows-latest` | Same setup as `_build-analyzers.yml`, then a plain build (`msbuild /t:Build /m /p:Configuration=Debug "/p:Platform=Any CPU"`, no analyzer or warning-promotion properties), then the existing vstest step unchanged (vswhere discovery, recursive `*.Test.dll` discovery filtered to `\bin\Debug\` excluding `\obj\` and `\ref\`, the zero-assembly `throw` guard, `/EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook"`), then the existing `test-results` artifact upload with `if: always()`. | + +The msbuild-consuming callees (`_build-analyzers.yml`, `_build-nullable.yml`, +`_mstest-coverage.yml`) deliberately omit `setup-dotnet`, the dotnet-tools cache, +and `dotnet tool restore`. **Assumption to verify in the first green run:** nothing +in the msbuild build path depends on the pinned .NET 10 SDK. If this assumption +fails, the fallback is restoring the dropped setup steps at an estimated ~56s/job +cost (research Q1, topology (a) figures). + +Each callee carries the `SOLUTION_PATH` / `BUILD_CONFIGURATION` / `BUILD_PLATFORM` +env values its steps consume. + +### Target architecture — orchestrator `ci.yml` + +`ci.yml` becomes a pure orchestrator: + +- Retains its current `name:`, `on:` triggers (`push` to main/development, + `pull_request` to main/development, `workflow_dispatch`), `permissions: + contents: read`, and the existing workflow-level `concurrency` block + (`group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || + github.ref }}`, `cancel-in-progress: true`) unchanged. +- Contains exactly five jobs, each of the form + `uses: ./.github/workflows/_.yml`. +- Contains **no inline `steps:`** and **no `needs:` edges**. The adopted topology + shares no files between jobs, so no artifact-consumption edge exists and the + issue's "except where an edge is required to consume an uploaded build artifact" + clause is vacuously satisfied with zero edges. + +### Reusable-workflow contract (every callee) + +Each callee workflow must satisfy all of the following: + +1. Declares both `on: workflow_call:` and `on: workflow_dispatch:`, so it is + invocable by the orchestrator and independently dispatchable for per-gate + re-runs. +2. Declares its own `permissions: contents: read`. +3. Declares a right-sized `timeout-minutes`: actionlint ~10, format ~10, each + build ~30, MSTest ~30 (replacing the single 60-minute monolith timeout). +4. Declares **no `concurrency` block.** The caller (`ci.yml`) owns the concurrency + group; jobs of a called workflow run as part of the caller's run and are covered + by the caller's group. Callee-level workflow `concurrency` under `workflow_call` + is not clearly documented and is avoided rather than relied upon (research Q6). + +### Behavior semantics + +- **Success:** all five required contexts report success on the PR head. Each + gate's pass criterion is byte-identical to the corresponding step in the + monolithic job. +- **Failure isolation:** exactly the violated gate's context reports failure; the + other gates run to completion independently (no `needs:` coupling), preserving + full diagnostic signal in a single run. +- **Ordering:** none between gates. `cancel-in-progress: true` on the caller's + group cancels all jobs of a superseded run together. +- **Edge cases:** + - Zero discovered test assemblies in `_mstest-coverage.yml` must still `throw` + (fail-closed protection against a discovery regression). This guard is + preserved verbatim. + - A `nuget restore` failure fails each of the three msbuild-consuming jobs + independently; there is no shared restore. + - Cache-save races between concurrent jobs sharing a cache key are benign: + caches are immutable, creation is first-writer-wins, and a lost save emits a + warning and never fails the job (research Q5). Under the tailored setup, the + `~/.nuget/packages` cache is consumed only by the format job, eliminating + concurrent saves of that key. + - A callee dispatched standalone via `workflow_dispatch` forms its own run with + no concurrency group. This is acceptable for the manual dispatch path. + +## Inputs / Outputs + +- **Inputs:** none new. Workflow triggers are unchanged. No workflow-level + `inputs:` are defined on the callees at this stage. +- **Outputs:** the `test-results` artifact (name unchanged) containing + `TestResults/**/*.trx` and `TestResults/**/*.coverage` (paths unchanged), + uploaded with `if: always()` and `if-no-files-found: warn`, now produced by + `_mstest-coverage.yml`. This upload targets workflow storage; it is not + cross-job file sharing. +- **Config keys and defaults:** env values `SOLUTION_PATH: TaskMaster.sln`, + `BUILD_CONFIGURATION: Debug`, `BUILD_PLATFORM: Any CPU` replicate into each + callee that consumes them. +- **Backward-compatibility constraint:** the `main` ruleset's required-status-check + contexts must be migrated in lockstep with the merge (see below); the old context + `Format, build, analyze, and test` ceases to exist after the split. + +## Required-Status-Check Contract + +The `main` ruleset (id `18572843`, `strict_required_status_checks_policy: true`) +currently requires exactly two contexts: `actionlint` and +`Format, build, analyze, and test`. After the split, the pipeline reports five +contexts, and all five become required. + +Contract terms: + +1. **Context-name form.** For jobs of a called reusable workflow, the check-run + context name takes the form ` / `. Because + the `actionlint` job moves into a callee, its context name also changes and + must be included in the new required set. +2. **Names are captured, never assumed.** The exact context strings must be read + from a live green run on the PR head + (`gh api repos/drmoisan/TaskMaster/commits//check-runs --jq + '.check_runs[].name'`) before any ruleset mutation. Assuming the strings is the + single most likely source of a botched migration (research Q8). +3. **Single atomic PUT.** The ruleset update is one + `PUT /repos/drmoisan/TaskMaster/rulesets/18572843` carrying the **full writable + ruleset object** (name, target, enforcement, bypass_actors, conditions, rules) + with the complete new contexts set replacing the old set in the same request. + Read-only fields returned by GET (`id`, `node_id`, `created_at`, `updated_at`, + `_links`, `source`, `source_type`, `current_user_can_bypass`) are not part of + the payload. A two-step remove-then-add edit is prohibited: it opens an + under-gating window. +4. **Migration sequence (research Q8):** green run on the split PR → capture exact + check-run names from the live head SHA → one atomic PUT → merge the split PR + immediately (updating the branch first if `strict` requires it) → verify by GET + that the ruleset holds exactly the five intended contexts and record the + pre-PUT ruleset JSON, the PUT payload, and the post-PUT GET response as + evidence under this feature folder's `evidence/` tree. +5. **Fail-closed property.** A required context that never reports blocks merging. + Both orderings of PUT-vs-merge therefore over-block rather than under-gate; the + only under-gating hazard is an incomplete contexts set in the PUT, which the + atomicity requirement addresses. +6. **Rollback:** a single PUT restoring the previous contexts set reverts the merge + policy; reverting the workflow change is an ordinary revert PR. + +## Data & State + +- No application data or state changes. The change is confined to workflow files, + `.github/workflows/README.md` (new), and the `main` ruleset's required contexts. +- Cache state: the `packages` and `~/.nuget/packages` cache keys are unchanged; + consumption is redistributed per job as described above. + +## Invariants (must not regress) + +1. **Every gate command byte-identical.** The csharpier command, both msbuild gate + invocations (including `/t:Rebuild` and all properties), and the vstest + invocation are moved, not edited. +2. **The `/t:Rebuild` rationale comment** in the nullable gate moves with the step + and is preserved verbatim. +3. **The zero-test-assembly `throw` guard** in the vstest step is preserved + verbatim. +4. **The `test-results` artifact upload** is preserved with `if: always()`, the + same artifact name, and the same paths. +5. **The `$LASTEXITCODE` exit guards** on both msbuild gates are retained verbatim. + No step in the pipeline uses the deliberately-failing-nested-command pattern of + `.claude/rules/ci-workflows.md`, so no `exit 0` / reset additions are required + (research Q9); any future negative-path self-validation step must comply with + that rule. +6. **No gate is dropped, weakened, or made non-required.** All five contexts are + required after the ruleset PUT; the analyzer and nullable gates keep their + current enforcement semantics (no warning-promotion changes, no carve-outs). +7. **The workflow-level concurrency group** in `ci.yml` (group expression and + `cancel-in-progress: true`) is unchanged. +8. **The vstest discovery filter discipline** (match `\bin\\`, exclude + `\obj\` and `\ref\`) is not weakened; the same script text is the reference for + local runs. + +## Non-Goals + +Explicitly out of scope for this feature: + +1. **Moving the format gate to `ubuntu-latest`.** CSharpier is cross-platform and + Linux minutes bill at 1x, but the move introduces a platform-parity question + (line endings, tool behavior) that is deliberately deferred as a separate + follow-up concern. The format gate stays on `windows-latest` in this change. +2. **Merging the analyzer and nullable compiles.** A merged compile would either + weaken the nullable gate (via carve-outs) or strengthen the analyzer gate + without ratification, and it would collapse the independent failure signal + (research Q2). Both gate commands stay separate and byte-identical. +3. **Build-output artifact sharing between jobs.** Rejected on critical-path + arithmetic (own-build wins even at zero transfer cost) and fragility (`.pdb` + coverage degradation, dependency-closure gaps, `upload-artifact@v4` path + restructuring) (research Q1b, Q3). +4. **Changing any gate's pass criterion**, timeout semantics excepted (the + monolith's single 60-minute timeout is replaced by right-sized per-callee + timeouts, which is a scheduling bound, not a pass criterion). +5. **Any C# source, project, or test change.** This feature touches workflow YAML, + the workflows README, and the ruleset only. + +## Expected Outcomes (estimates) + +All figures in this section are **estimates** derived arithmetically from the +measured 444s baseline; they are not measurements of a split pipeline and must be +verified against a measured post-split run. + +| Metric | Baseline (measured) | Target (estimate) | Worst case (estimate) | +| --- | --- | --- | --- | +| Wall clock | 444s (7m24s) | ~277s (4m37s), bounded by the MSTest job, if the tailored setup holds (~38% reduction) | ~333s (5m33s) with full setup per job (~25% reduction) | +| Billed `windows-latest` seconds | ~444s | ~763s (~1.7x) | ~962s (~2.2x) | + +The billed-minutes increase (~1.7–2.2x, before GitHub's Windows 2x billing +multiplier) is an accepted cost of the latency reduction and the independent +failure signal. Per-run job demand is 5 concurrent jobs (4 × `windows-latest` + +1 × `ubuntu-latest`), below every GitHub plan's concurrency ceiling; realized +speedup can be eroded only by cross-run account-level contention, which is not +determinable from repository data (research Q7). + +## Constraints & Risks + +### Constraints + +- **Branch-protection coupling (blocking risk).** Until the ruleset PUT lands, + every PR blocks on the old context, which after the split can never report. The + Required-Status-Check Contract section above is the mitigation; the PUT is an + outward-facing merge-policy change and must follow the recorded sequence. +- **Windows runner setup cost is paid per job.** Mitigated by the tailored per-job + setup; the fallback (full setup everywhere) still improves on the baseline. +- **Concurrency group.** Owned solely by the caller; callees declare none. +- **`modified-workflow-needs-green-run`.** The reworked pipeline must produce a + green run against the branch head before merge. + +### Residual risks (carried from research Q10) + +1. **Context-name mismatch in the ruleset PUT** — highest-likelihood failure. + Mitigated by capturing names from the live run before the PUT and by + fail-closed blocking if a name is wrong. +2. **Tailored-setup assumption** (msbuild jobs without `setup-dotnet`; format job + without `nuget restore`) is unverified until the first green run. Fallback: + restore the dropped steps at ~56s/job estimated cost. +3. **Estimated timings are estimates**; hosted-runner variance (checkout and setup + steps in particular) can shift per-job durations by tens of seconds. +4. **Cross-run account-level runner contention** could queue jobs and erode the + realized speedup; not determinable from repository data. +5. **Undocumented callee-level concurrency semantics** are avoided rather than + relied upon. + +## Implementation Strategy + +- **Scope of change:** five new callee workflow files (`_actionlint.yml`, + `_format-check.yml`, `_build-analyzers.yml`, `_build-nullable.yml`, + `_mstest-coverage.yml`); `ci.yml` rewritten as the orchestrator; new + `.github/workflows/README.md` documenting the per-stage `workflow_dispatch` + procedure and the branch-protection rename procedure; one atomic ruleset PUT. +- **No new dependencies.** All actions used (`actions/checkout@v4`, + `actions/setup-dotnet@v4`, `microsoft/setup-msbuild@v2`, `nuget/setup-nuget@v2`, + `actions/cache@v4`, `actions/upload-artifact@v4`) are already in use. +- **Rollout:** the split PR's own run exercises the new pipeline (PR head-ref + workflow files execute for `pull_request` events). Migration and rollback follow + the Required-Status-Check Contract section. +- **Post-merge verification:** standalone `workflow_dispatch` smoke of each callee; + post-split timing evidence captured with the same `gh api .../runs//jobs` + method as the baseline (runner-environment parity per + `.claude/rules/benchmark-baselines.md`), recorded under this feature folder's + `evidence/` tree. + +## Acceptance Criteria + +- [ ] The formatting gate, the analyzer build gate, the nullable build gate, and + the MSTest gate each run as separate GitHub Actions jobs with **zero** + `needs:` edges (no build-output artifact sharing exists, so no + artifact-consumption edge is justified). +- [ ] Five callee reusable workflows exist — `_actionlint.yml`, + `_format-check.yml`, `_build-analyzers.yml`, `_build-nullable.yml`, + `_mstest-coverage.yml` — each declaring both `on: workflow_call:` and + `on: workflow_dispatch:`, its own `permissions:`, a right-sized + `timeout-minutes`, and no `concurrency` block. +- [ ] `ci.yml` is an orchestrator containing only `uses:` job references and no + inline `steps:` (the `actionlint` extraction resolves the criterion as + originally drafted in `issue.md`). +- [ ] No file is shared between jobs; the only artifact operation is the preserved + `test-results` upload (workflow storage, `if: always()`, same name and + paths). +- [ ] The four gate commands and the actionlint step are byte-identical to their + pre-split counterparts, including the `/t:Rebuild` rationale comment, the + `$LASTEXITCODE` guards, and the zero-test-assembly `throw` guard. +- [ ] The `main` ruleset's `required_status_checks` contexts are replaced in one + atomic PUT with the five context strings captured from a live green run on + the PR head, with no window in which a merge can bypass a gate, and the + pre-PUT JSON, PUT payload, and post-PUT GET response are recorded as + evidence. +- [ ] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` + procedure and the branch-protection rename procedure. +- [ ] The reworked pipeline produces a green run against the branch head, + satisfying `modified-workflow-needs-green-run`. +- [ ] Every gate enforced by the current `quality-gates` job is still enforced + after the split; no check is dropped, weakened, or made non-required. +- [ ] Post-split wall-clock duration is measured with the same collection method + as the baseline and recorded as evidence in this feature folder, compared + against the measured 444s baseline. + +## Definition of Done + +- [ ] Acceptance criteria above delivered and individually verified. +- [ ] Seeded test conditions below exercised and their outcomes recorded. +- [ ] `.github/workflows/README.md` created and linked from the feature folder. +- [ ] Evidence (ruleset before/after, green-run reference, post-split timings) + committed under + `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/`. +- [ ] No C# toolchain pass required unless the implementation unexpectedly touches + `*.cs` / `*.csproj` files (none are planned). + +## Seeded Test Conditions (from potential) + +- [ ] `actionlint` passes against every new and modified workflow file. +- [ ] Each `_.yml` is independently dispatchable via `workflow_dispatch` and + succeeds standalone. +- [ ] A deliberate formatting violation fails only the formatting gate and reports + a distinct red check. (Exercised as a temporary probe commit on the PR + branch, then reverted — not as a permanent workflow step, so no + deliberately-failing nested command enters the committed pipeline.) +- [ ] A deliberate nullable violation fails only the nullable gate. (Same + probe-commit method.) +- [ ] A deliberate test failure fails only the MSTest gate. (Same probe-commit + method.) +- [ ] Test results and coverage artifacts continue to upload with the same names. +- [ ] Total wall-clock duration of the reworked pipeline is measured against the + current sequential baseline and recorded as evidence. +- [ ] No `pwsh` step leaks a residual non-zero `$LASTEXITCODE` per + `.claude/rules/ci-workflows.md`. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md new file mode 100644 index 00000000..f9dfd353 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md @@ -0,0 +1,151 @@ +# `2026-08-14-ci-parallel-job-split` — User Story + +- Issue: #553 +- Owner: drmoisan +- Status: Ready for planning +- Last Updated: 2026-08-14 + +## Story Statement + +- As a **repository contributor waiting on CI**, I want the formatting, analyzer, + nullable, and MSTest gates to run as concurrent jobs, so that a pull request's + wall-clock CI latency approaches the duration of the slowest single gate + (estimated ~277s) instead of the measured 444s sum of all gates, and so that a + fast gate such as formatting reports within minutes instead of after restore and + two full builds. +- As a **maintainer diagnosing a red check**, I want each gate to report as its own + named required status check, so that I can identify the failed gate from the PR + checks list without opening a log, and re-dispatch only that gate via + `workflow_dispatch` instead of re-running the entire pipeline. + +## Problem / Why + +`.github/workflows/ci.yml` runs the entire C# quality toolchain inside a single +`quality-gates` job on `windows-latest` with a 60-minute timeout. That job executes +five expensive stages strictly sequentially: + +1. `nuget restore` +2. `dotnet csharpier check .` (formatting) +3. `msbuild /t:Build` with `EnableNETAnalyzers` + `EnforceCodeStyleInBuild` (analyzers) +4. `msbuild /t:Rebuild` with `TreatWarningsAsErrors=true` (nullable/type-check) +5. `vstest.console.exe` with `/EnableCodeCoverage` (MSTest suite) + +Wall-clock CI latency is therefore the sum of all five stages — measured at **444s** +in the sequential baseline +(`evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md`). Stages 3 and 4 +are both full solution compiles, and stage 4 uses `/t:Rebuild`, so it performs a +complete recompile from scratch. A formatting violation is not reported until after +restore completes, and a test failure is not reported until after two full builds +have finished. + +Two additional consequences of the monolithic shape: + +- **No independent failure signal.** All gates report as one required status check + named `Format, build, analyze, and test`, so a red check does not identify which + gate failed without opening the log. +- **No independent re-dispatch.** A transient failure in one stage requires + re-running the entire job. + +## Personas & Scenarios + +- **Persona: contributor waiting on CI.** + - Pushes commits to a PR branch several times per session and waits for the + required checks before requesting review or merging. + - Cares about time-to-first-signal (a formatting or analyzer failure known + early) and total time-to-green. + - Constraint: cannot merge until every required context reports success under + the `main` ruleset's strict policy. + - Frustration today: a 15-second formatting check is not reported until restore + completes, and total latency is 7m24s per push even when nothing is wrong. + +- **Persona: maintainer diagnosing a red check.** + - Triages failed PR runs and decides whether a failure is a code defect, a gate + regression, or a transient infrastructure error. + - Cares about attributing a failure to the correct gate immediately and about + re-running only the affected gate. + - Constraint: must keep every gate enforced as a required check with no window + in which a merge can bypass a gate during the migration. + - Frustration today: one aggregate check name forces opening the 60-minute job + log to find which of five stages failed, and any re-run repeats all stages. + +- **Scenario: fast failure attribution.** A contributor pushes a commit with a + nullable violation. Under the split pipeline, the analyzer, format, actionlint, + and MSTest checks all complete green; only the nullable gate's context reports + red. The contributor identifies the failed gate from the checks list, fixes the + violation, and pushes; the superseded run is cancelled as a group by the caller's + `cancel-in-progress` concurrency setting. + +- **Scenario: transient infrastructure failure.** A maintainer sees the MSTest + gate fail with a runner-provisioning error while all other gates are green. + Instead of re-running the whole pipeline, the maintainer dispatches + `_mstest-coverage.yml` standalone via `workflow_dispatch` (procedure documented + in `.github/workflows/README.md`) to confirm the failure is transient, then + re-runs the failed job on the PR. + +- **Scenario: required-check migration.** A maintainer merges this feature. The + split PR's own run reports the five new contexts; the maintainer captures the + exact check-run names from the live head SHA, applies one atomic PUT replacing + the ruleset's required contexts with the complete new set, and merges + immediately. At no point is any gate non-required: a missing context blocks + merging (fail-closed) rather than allowing a bypass. + +## Acceptance Criteria + +These criteria are consistent with the draft in `issue.md`, resolved against the +adopted design (research topology (c) plus actionlint extraction). Where a draft +criterion admitted alternatives, the resolved form is stated. + +- [ ] The formatting gate, the analyzer build gate, the nullable build gate, and + the MSTest gate each run as separate GitHub Actions jobs with no `needs:` + edge forcing them to serialize. Resolved form: the adopted topology shares + no build output between jobs, so the pipeline contains **zero** `needs:` + edges (the draft's artifact-consumption exception is unused). +- [ ] Each gate is a callable reusable workflow `_.yml` declaring both + `on: workflow_call:` and `on: workflow_dispatch:`. +- [ ] `ci.yml` becomes an orchestrator workflow containing only `uses:` references + and no inline `steps:`. Resolved form: the `actionlint` job is also + extracted into `_actionlint.yml`, so this criterion holds without exception + and the pipeline comprises five callee workflows and five required + status-check contexts. +- [ ] Any file shared between jobs crosses the boundary via explicit + `actions/upload-artifact` + `actions/download-artifact`. Resolved form: no + cross-job file sharing exists in the adopted topology; the only artifact + operation is the preserved `test-results` upload to workflow storage, with + `if: always()` and the same artifact name and paths as today. +- [ ] The `main` branch ruleset's `required_status_checks` contexts are updated to + match the new context names, with no window in which a merge can bypass a + gate: exact context strings are captured from a live green run on the PR + head, and the update is a single atomic PUT of the full writable ruleset + object. +- [ ] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` + procedure and the branch-protection rename procedure. +- [ ] The reworked pipeline produces a green run against the branch head, + satisfying `modified-workflow-needs-green-run`. +- [ ] Every gate enforced by the current `quality-gates` job is still enforced + after the split; no check is dropped, weakened, or made non-required. In + particular, every gate command is byte-identical to its pre-split + counterpart, the `/t:Rebuild` rationale comment and the zero-test-assembly + `throw` guard are preserved verbatim, and the analyzer and nullable compiles + remain separate. + +## Expected Outcomes (estimates) + +Comparison point: the measured 444s sequential baseline. The following are +estimates derived from the baseline, not measurements of a split pipeline: + +- Wall clock ~277s if the tailored per-job setup holds (~38% reduction); worst + case ~333s with full setup per job (~25% reduction). +- Billed `windows-latest` time rises an estimated ~1.7–2.2x, an accepted cost of + the latency reduction and independent failure signal. + +## Non-Goals + +Explicitly excluded from this feature: + +- **Moving the format gate to `ubuntu-latest`.** Deferred as a separate + platform-parity follow-up; the format gate stays on `windows-latest` here. +- **Merging the analyzer and nullable compiles.** Both compiles stay separate and + byte-identical, including `/t:Rebuild` and its in-file rationale comment. +- **Build-output artifact sharing between jobs.** Rejected by the research on + critical-path arithmetic and fragility grounds. +- **Changing any gate's pass criterion.** From 8cec8331124a6dac2ead589e20bd10c7e6da3791 Mon Sep 17 00:00:00 2001 From: Dan Moisan Date: Fri, 14 Aug 2026 10:15:18 -0400 Subject: [PATCH 02/13] docs(potential): archive promoted entries for issues 554 and 555 Two latent defects were found while orchestrating issue #553 and promoted to their own issues. These are the archival copies the promotion lifecycle writes to docs/features/potential/promoted/. - #554 potential_to_issue returned a success receipt naming a promoted/ destination path it never wrote, and removed the source entry. Observed on the feature path; the bug path was subsequently verified working, so the issue carries a comment narrowing the reproduction. - #555 validate-orchestrator-output.ps1 invokes a Python validator module that does not exist in this repository, so an Agent(orchestrator) delegation would be blocked at SubagentStop with a misleading MODEL_ROUTING_BLOCKED reason. Neither defect is part of the CI parallel job split. They are committed separately so the feature commit stays confined to the pipeline change. Refs #554, #555 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LZFrbwyXBnwJ44RRJV1N4E --- ...ooks-reference-absent-python-validators.md | 98 +++++++++++++++++++ ...tial-to-issue-promoted-copy-not-written.md | 94 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 docs/features/potential/promoted/2026-08-14-orchestrator-hooks-reference-absent-python-validators.md create mode 100644 docs/features/potential/promoted/2026-08-14-potential-to-issue-promoted-copy-not-written.md diff --git a/docs/features/potential/promoted/2026-08-14-orchestrator-hooks-reference-absent-python-validators.md b/docs/features/potential/promoted/2026-08-14-orchestrator-hooks-reference-absent-python-validators.md new file mode 100644 index 00000000..f7e4ce7a --- /dev/null +++ b/docs/features/potential/promoted/2026-08-14-orchestrator-hooks-reference-absent-python-validators.md @@ -0,0 +1,98 @@ +# orchestrator-hooks-reference-absent-python-validators (Issue #555) + +- Date captured: 2026-08-14 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/orchestrator-hooks-reference-absent-python-validators/ (Issue #555) +- Severity: Medium +- Discovered during: orchestration of issue #553 (CI parallel job split) + +- Issue: #555 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/555 +- Last Updated: 2026-08-14 +## Summary + +`.claude/hooks/validate-orchestrator-output.ps1` delegates its routing-contract +validation to a Python module that does not exist in this repository. The hook's +default invoker runs: + +``` +python -m scripts.dev_tools.validate_orchestration_artifacts \ + --require-complete --require-model-routing +``` + +There is no `scripts/dev_tools/` directory in TaskMaster. `scripts/` contains only +`dev-tools/`, `vscode/`, and `temp-extract-coverage.ps1`. A repository-wide search for +`validate_orchestrator_state*` returns no results. + +## Observed Behavior + +Verified on 2026-08-14 in `C:\Users\DanMoisan\repos\TaskMaster-wt\2026-08-14T09-01`: + +- `find . -name "validate_orchestrator_state*"` (excluding `node_modules` and + `.claude/worktrees`) returns nothing. +- `ls scripts/` returns `dev-tools/`, `vscode/`, `temp-extract-coverage.ps1`. +- The hook treats the subprocess exit code as the complete failure discriminator + (`.claude/hooks/validate-orchestrator-output.ps1`, around line 228). A missing module + causes `python -m` to exit non-zero, which the hook reads as a validation failure. + +Consequence: any `Agent(orchestrator)` delegation that reaches the `SubagentStop` hook +would be blocked, and a model-routing gate failure would be reported under the +`MODEL_ROUTING_BLOCKED:` reason regardless of whether the checkpoint is actually valid. + +The defect did not surface during issue #553 because that orchestration ran in the main +session rather than as an `Agent(orchestrator)` subagent, so the `SubagentStop` matcher +never fired. + +## Related Documentation Drift + +The same absent paths are cited as authoritative enforcement in checked-in rule files: + +- `.claude/rules/orchestrator-state.md` names + `scripts/dev_tools/validate_orchestrator_state.py`, + `scripts/dev_tools/compute_complexity_floor.py`, + `scripts/dev_tools/resolve_delegation_model.py`, + `scripts/dev_tools/_orchestrator_state_model_routing_gate.py`, and others. +- `.claude/rules/parallel-orchestration.md` names + `scripts/dev_tools/validate_parallel_orchestrator_state.py`, + `scripts/dev_tools/parallel_manifest_contract.py`, and several helper modules. + +None of these exist here. The working enforcement surface in TaskMaster is the +`mcp__drm-copilot__validate_orchestration_artifacts` MCP tool, which is backed by the +bundled TypeScript implementation and was confirmed working during issue #553. + +## Expected Behavior + +One of: + +1. The Python validator modules are vendored into TaskMaster so the hook's default + invoker resolves; or +2. The hook's default invoker is repointed at the MCP-backed validator that this + repository actually ships; or +3. The hook fails open with a clear diagnostic when the validator is unavailable, + rather than reporting a routing-contract failure it did not evaluate. + +In every case the rule files should cite the enforcement mechanism that exists in this +repository rather than paths inherited from the reference repository. + +## Impact + +- `Agent(orchestrator)` delegations from `epic-planner`, `epic-orchestrator`, and + `parallel-orchestrator` would be blocked at `SubagentStop` with a misleading reason. +- The block reason attributes the failure to model routing, sending a reader to + investigate checkpoint contents when the actual cause is a missing module. +- Rule files assert enforcement that is not in place, which overstates the guarantees a + reader can rely on. + +## Acceptance Criteria + +- [ ] `.claude/hooks/validate-orchestrator-output.ps1` resolves its routing-contract + validation against a validator that exists in this repository. +- [ ] When the validator cannot be located, the hook emits a distinct, accurate + diagnostic rather than `MODEL_ROUTING_BLOCKED:`. +- [ ] `.claude/rules/orchestrator-state.md` and `.claude/rules/parallel-orchestration.md` + cite the enforcement mechanism actually present in TaskMaster. +- [ ] A test exercises the validator-absent path and asserts the diagnostic. + +## Next Step + +- [ ] Promote to GitHub issue (bug template) diff --git a/docs/features/potential/promoted/2026-08-14-potential-to-issue-promoted-copy-not-written.md b/docs/features/potential/promoted/2026-08-14-potential-to-issue-promoted-copy-not-written.md new file mode 100644 index 00000000..79feefcc --- /dev/null +++ b/docs/features/potential/promoted/2026-08-14-potential-to-issue-promoted-copy-not-written.md @@ -0,0 +1,94 @@ +# potential-to-issue-promoted-copy-not-written (Issue #554) + +- Date captured: 2026-08-14 +- Author: Dan Moisan +- Status: Promoted -> docs/features/active/potential-to-issue-promoted-copy-not-written/ (Issue #554) +- Severity: Medium +- Discovered during: orchestration of issue #553 (CI parallel job split) + +- Issue: #554 +- Issue URL: https://github.com/drmoisan/TaskMaster/issues/554 +- Last Updated: 2026-08-14 +## Summary + +`mcp__drm-copilot__potential_to_issue` returns a success receipt naming a +`destination_path` under `docs/features/potential/promoted/`, but no file is written +to that path. The source potential entry is removed from +`docs/features/potential/`, so the on-disk potential document is destroyed while the +receipt asserts it was relocated. + +## Observed Behavior + +Promoting `docs/features/potential/2026-08-14-ci-parallel-job-split.md` returned: + +```json +{ + "ok": true, + "tool": "potential_to_issue", + "summary": "Promoted '.../docs/features/potential/2026-08-14-ci-parallel-job-split.md' as a feature workflow in full-feature mode.", + "artifacts": ["https://github.com/drmoisan/TaskMaster/issues/553"], + "destination_path": "C:/Users/DanMoisan/repos/TaskMaster-wt/2026-08-14T09-01/docs/features/potential/promoted/2026-08-14-ci-parallel-job-split.md" +} +``` + +After the call: + +- `docs/features/potential/2026-08-14-ci-parallel-job-split.md` — absent. +- `docs/features/potential/promoted/2026-08-14-ci-parallel-job-split.md` — absent. +- `find docs -name "*ci-parallel-job-split*"` returns only the active feature folder + and its research artifact. +- `git status --porcelain` shows no deletion, because the potential entry had not yet + been committed. A committed entry would presumably show as a deletion instead. + +The `promoted/` directory itself exists and holds many older entries (for example +`2026-08-08-ribbon-async-getpressed-signature.md`), so the directory is not missing +and the convention is otherwise in use. + +## Expected Behavior + +Either: + +1. The file is actually written to the reported `destination_path`, matching the + receipt; or +2. The receipt does not report a `destination_path` that was not written. + +A success receipt must not assert a filesystem outcome that did not occur. + +## Impact + +Content loss is possible. In the observed case the content survived only because it +had independently been copied into the GitHub issue body and into the active feature +folder's `issue.md`. A promotion that failed earlier in its sequence, or one whose +issue creation did not carry the full body, would lose the authored analysis with no +warning and a green receipt. + +The receipt is also consumed as promotion evidence and persisted verbatim into +`artifacts/orchestration/orchestrator-state.json` under +`delegation_receipts.promotion.issue`. A checkpoint therefore records a path that does +not exist, which weakens the audit trail. + +## Reproduction + +1. Create a potential entry via `mcp__drm-copilot__new_potential_entry`. +2. Add content to it. +3. Promote it via `mcp__drm-copilot__potential_to_issue`. +4. Check both the source path and the reported `destination_path`. Neither holds the + file. + +Observed with the bundled drm-copilot MCP server in the TaskMaster worktree +`C:\Users\DanMoisan\repos\TaskMaster-wt\2026-08-14T09-01` on 2026-08-14. + +## Acceptance Criteria + +- [ ] Promotion writes the potential document to `docs/features/potential/promoted/` + at the exact path reported as `destination_path`, or the receipt stops reporting + a `destination_path` it did not write. +- [ ] The source document is not removed unless the destination write has succeeded. +- [ ] A regression test asserts that after a successful promotion, a file exists at the + receipt's `destination_path`. +- [ ] The failure mode is surfaced as a non-`ok` receipt rather than a silent + inconsistency. + +## Next Step + +- [ ] Promote to GitHub issue (bug template) From c3a337dcea3e29643646afd6e15529993a60f75c Mon Sep 17 00:00:00 2001 From: Dan Moisan Date: Fri, 14 Aug 2026 10:15:58 -0400 Subject: [PATCH 03/13] ci(#553): split monolithic quality-gates job into parallel reusable workflows - Rewrite .github/workflows/ci.yml as a 32-line orchestrator with five workflow_call references and no inline steps or needs edges - Add five callee workflows (_format-check, _build-analyzers, _build-nullable, _actionlint, _mstest-coverage), each supporting workflow_call and workflow_dispatch, with run blocks transplanted byte-identically from the prior job - Add .github/workflows/README.md documenting the split workflows - Record baseline, byte-identity, and actionlint evidence under docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ and evidence/baseline/ - Update atomic-executor and atomic-planner agent memory with pwsh/git/gh CLI gotchas and plan seams for issue #553 Refs: #553 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01LZFrbwyXBnwJ44RRJV1N4E --- .../agent-memory/atomic-executor/MEMORY.md | 1 + .../project_pwsh_git_gh_cli_gotchas.md | 48 ++ .claude/agent-memory/atomic-planner/MEMORY.md | 2 + ...roject_553_ci_parallel_split_plan_seams.md | 37 ++ .github/workflows/README.md | 180 +++++++ .github/workflows/_actionlint.yml | 29 ++ .github/workflows/_build-analyzers.yml | 53 ++ .github/workflows/_build-nullable.yml | 60 +++ .github/workflows/_format-check.yml | 41 ++ .github/workflows/_mstest-coverage.yml | 96 ++++ .github/workflows/ci.yml | 154 +----- .../actionlint-baseline.2026-08-14T09-54.md | 69 +++ .../baseline/git-baseline.2026-08-14T09-54.md | 60 +++ ...uential-baseline-check.2026-08-14T09-54.md | 59 +++ .../other/phase0-instructions-read.md | 106 ++++ .../other/pre-split/actionlint-steps.txt | 14 + .../other/pre-split/analyzer-step.txt | 7 + .../other/pre-split/ci.yml.pre-split.txt | 160 ++++++ .../evidence/other/pre-split/format-step.txt | 3 + .../evidence/other/pre-split/header.txt | 15 + .../other/pre-split/nullable-step.txt | 14 + .../evidence/other/pre-split/upload-step.txt | 9 + .../evidence/other/pre-split/vstest-step.txt | 33 ++ .../actionlint-postchange.2026-08-14T09-54.md | 95 ++++ .../byte-identity.2026-08-14T09-54.md | 146 ++++++ .../plan.2026-08-14T09-05.md | 466 ++++++++++++++++-- 26 files changed, 1782 insertions(+), 175 deletions(-) create mode 100644 .claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md create mode 100644 .claude/agent-memory/atomic-planner/project_553_ci_parallel_split_plan_seams.md create mode 100644 .github/workflows/README.md create mode 100644 .github/workflows/_actionlint.yml create mode 100644 .github/workflows/_build-analyzers.yml create mode 100644 .github/workflows/_build-nullable.yml create mode 100644 .github/workflows/_format-check.yml create mode 100644 .github/workflows/_mstest-coverage.yml create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/actionlint-baseline.2026-08-14T09-54.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/git-baseline.2026-08-14T09-54.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/sequential-baseline-check.2026-08-14T09-54.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/phase0-instructions-read.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/actionlint-steps.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/analyzer-step.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/ci.yml.pre-split.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/format-step.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/header.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/nullable-step.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/upload-step.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/vstest-step.txt create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-postchange.2026-08-14T09-54.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/byte-identity.2026-08-14T09-54.md diff --git a/.claude/agent-memory/atomic-executor/MEMORY.md b/.claude/agent-memory/atomic-executor/MEMORY.md index 109d6c71..bd85a5b7 100644 --- a/.claude/agent-memory/atomic-executor/MEMORY.md +++ b/.claude/agent-memory/atomic-executor/MEMORY.md @@ -14,6 +14,7 @@ - [Swordfish F5 test misclassification](project_swordfish_f5_test_misclassification.md) — verify using/namespace before treating a removal as Swordfish-only ## Build / toolchain environment +- [pwsh/git/gh CLI gotchas](project_pwsh_git_gh_cli_gotchas.md) — jq NOT installed (only `gh --jq`); pwsh won't concatenate `$(git merge-base ...)..HEAD`; bare `packages.config` pathspec matches 0 files - [Project Build/Test Env](project_build_test_env.md) — git-bash quirks (MSBuild switches, MSYS_NO_PATHCONV), csharpier v1 syntax, legacy csproj Compile includes, IVT for Moq - [VS18 build/test toolchain paths](project_vs18_build_toolchain_paths.md) — use VS **18** full-framework msbuild.exe (not .dotnet-sdk, dies on binary resx MSB3822); nuget.exe restore; dotnet-coverage needs `--` separator - [Repo-local SDK install + nullable Rebuild](project_repo_sdk_and_nullable_rebuild.md) — .dotnet-sdk install needs pwsh7; csharpier check/format subcommands; nullable debt scope NOT stable across sessions — re-verify which csproj errors come from diff --git a/.claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md b/.claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md new file mode 100644 index 00000000..67c60f8c --- /dev/null +++ b/.claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md @@ -0,0 +1,48 @@ +--- +name: pwsh-git-gh-cli-gotchas +description: Three verified environment facts that silently break plan verification commands — jq is not installed, PowerShell will not concatenate $(...)..HEAD into one git argument, and an unanchored git pathspec like 'packages.config' matches nothing +metadata: + type: project +--- + +Three verified facts about this Windows box that make plausible-looking verification +commands fail or, worse, pass vacuously. Verified 2026-08-14 during #553 preflight. + +**1. `jq` is NOT installed.** `command -v jq` (git-bash) and `Get-Command jq` (pwsh 7.6.3) +both return nothing. `gh api ... --jq ''` DOES work because that filter is +compiled into `gh` — so a plan can use `--jq` freely but any standalone +`jq '' file.json` against a LOCAL file is unrunnable. Replace with +`Get-Content -Raw x.json | ConvertFrom-Json` / `ConvertTo-Json -Depth 20` +(the default `-Depth 2` silently truncates nested API objects such as a GitHub ruleset). + +**2. PowerShell does not build `..HEAD` from `$(git merge-base ...)..HEAD`.** +Verified: `git diff --name-only $(git merge-base origin/main HEAD)..HEAD -- '*.cs'` +run under `pwsh -Command` makes git print its usage block and exit non-zero. The +subexpression and the trailing `..HEAD` are not concatenated into one argument. Use two +statements: `$base = git merge-base origin/main HEAD` then +`git diff --name-only "$base..HEAD" -- '*.cs'`. The bash form works; the pwsh form does not. + +**3. A git pathspec with no wildcard is anchored to the repo root.** `git ls-files -- +'packages.config'` returns 0 files and `'app.config'` returns 0, while +`'**/packages.config'` returns 18. `'*.cs'` DOES match at any depth (pathspec globbing +does not set FNM_PATHNAME), so `*.ext` forms are fine and bare-filename forms are not. +A "no C#/project-file changes" gate written with bare `packages.config` is vacuous. + +**4. actionlint's `-color` is a BOOLEAN flag; `-color never` fails with exit 3.** +`actionlint -color never` makes Go's flag parser read `-color` as the boolean and +`never` as a positional FILE, producing `could not read "never": open never: The system +cannot find the file specified.` and exit 3 — which reads like a lint failure but is an +argument error. The suppression form is the separate boolean `-no-color`. Verifying that +a tool's download URL returns HTTP 200 is NOT verifying that its command line parses; +run ` -h` during preflight when a plan hard-codes flags. Also note `-verbose` +prints `Collected N YAML files` / `Found 0 errors in N files`, which is how you prove a +lint run actually covered the file set instead of silently skipping it. + +**Why:** all four produce a wrong result rather than an obvious error — #2 emits a usage +dump that a wrapper can read as "no output, therefore clean", and #3 reports an empty +diff for files that genuinely changed. + +**How to apply:** when preflighting a plan, actually execute each verification command +shape (not just read it) before signing off, especially anything using `jq` on a file, +`$(...)` inside a git rev range, or a bare-filename pathspec. Related: +[[project-build-test-env]], [[verify-line-citations-with-numbered-output]]. diff --git a/.claude/agent-memory/atomic-planner/MEMORY.md b/.claude/agent-memory/atomic-planner/MEMORY.md index 469faa2a..5ff34a14 100644 --- a/.claude/agent-memory/atomic-planner/MEMORY.md +++ b/.claude/agent-memory/atomic-planner/MEMORY.md @@ -1,5 +1,7 @@ # Atomic Planner Memory Index +- [#553 CI parallel-split plan seams](project_553_ci_parallel_split_plan_seams.md) — workflow-only scope: no C# toolchain; Phase 0 snapshot for byte-identity; ruleset PUT + gh pr create orchestrator-gated; no jq (ConvertTo-Json -Depth 20); pathspec anchoring; BRANCH/SCRATCH conventions + - [Dead-code removal vs coverage exclusion](project_deadcode_removal_vs_coverage_exclusion.md) — coverage gate blocked by unreachable dead prod code → plan removal (shrink denominator), never exclusion/carve-out/forced-rethrow - [Coverage gate on CLR-invoked private members](coverage-gate-clr-invoked-private-members.md) — never gate AssemblyResolve-style private members at >=90%; split newly-added vs changed per the AC's own wording - [Nullable context mismatch: prod vs test](project_nullable_context_mismatch_prod_vs_test.md) — check `#nullable enable` in the prod file AND missing `` (C# 7.3) in the test csproj; adding `latest` is never "one property" diff --git a/.claude/agent-memory/atomic-planner/project_553_ci_parallel_split_plan_seams.md b/.claude/agent-memory/atomic-planner/project_553_ci_parallel_split_plan_seams.md new file mode 100644 index 00000000..28ed80d0 --- /dev/null +++ b/.claude/agent-memory/atomic-planner/project_553_ci_parallel_split_plan_seams.md @@ -0,0 +1,37 @@ +--- +name: project-553-ci-parallel-split-plan-seams +description: "#553 CI job-split plan seams: workflow-YAML-only scope (no C# toolchain), byte-identity via line-ending-normalized containment vs Phase 0 snapshot, ruleset PUT orchestrator-gated, QA loop before post-merge phases" +metadata: + type: project +--- + +Plan seams for issue #553 (split `.github/workflows/ci.yml` monolith into 5 reusable callee workflows), plan at `docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md`. + +**Why:** Workflow-only features break several default planning habits: there is no local test harness, the authoritative gate is a live CI run, and the merge-policy PUT is outward-facing. + +**How to apply:** +- **No C# toolchain for workflow-only diffs.** Put a binding "No-C#-Toolchain Statement" in the plan preamble plus a final `git diff --name-only ..HEAD -- '*.cs' '*.csproj' ...` empty-check task; otherwise the executor attempts an unjustifiable csharpier/msbuild/vstest pass. Seeded probe commits that touch .cs are fine if each is reverted — the merge-base content diff nets to zero. +- **Byte-identity gates need a Phase 0 snapshot.** The source file is destroyed by the rewrite, so extract reference blocks (by verified line ranges with first-line sanity asserts) into `evidence/other/pre-split/` BEFORE editing, then verify containment with CRLF→LF normalization (`Get-Content -Raw` + `.Contains`). Works because callee `jobs..steps` sits at the same YAML depth as the monolith's. +- **Ruleset PUT task**: mark `ORCHESTRATOR CONFIRMATION REQUIRED — do not execute autonomously`; BLOCKED (not skipped) without recorded confirmation. Payload verification = 5 jq checks (read-only keys stripped, exactly N contexts, strict retained, diff-only-in-contexts via del()+`git diff --no-index`, every context verbatim in the captured-names artifact). +- **Phase ordering vs the final-QA-loop contract:** QA loop (actionlint = lint; formatting/type-check N/A for YAML; live green run = test) is the last code-verification phase; post-merge phases (ruleset migration, dispatch smoke) follow it with an explicit note that they modify no source files and an authorized `DEFERRED — awaiting merge` branch. Post-merge evidence lands via an orchestrator-owned follow-up commit since the PR is already merged. +- **Probe tasks** are `[expect-fail]` with dossiers in `evidence/regression-testing/`; wait for the probe run to finish before pushing the revert (`cancel-in-progress: true` would cancel it). Nullable probe must target a project without `TreatWarningsAsErrors` in its csproj so only the nullable gate reddens. +- **Check-run names captured, never assumed** (`gh api .../commits//check-runs`); names are SHA-independent so the capture artifact can be committed after the PUT without invalidating it. + +**Preflight rev-1 findings (environment facts, reusable):** +- `jq` is NOT installed (git-bash or pwsh). Only `gh api --jq` works (filter compiled into gh). Plan JSON manipulation with `ConvertFrom-Json` / `ConvertTo-Json -Depth 20` — the default depth of 2 silently truncates nested objects (would corrupt a ruleset PUT payload). Same-serializer rule: both sides of a `git diff --no-index` JSON comparison must come from the identical `ConvertTo-Json -Depth 20` call form. +- `git diff --name-only $(git merge-base ...)..HEAD` is INVALID under pwsh — PowerShell does not concatenate the subexpression with the trailing `..HEAD` into one argument. Use two statements: `$base = git merge-base ...` then `"$base..HEAD"`. +- Git pathspec with no wildcard is anchored to repo root: `'packages.config'` matches 0 files; `'**/packages.config'` matches 18. Extension globs (`'*.cs'`) match at any depth and need no prefix. +- `gh pr create` is executor-BLOCKED by `enforce-pr-author-skill.ps1` unless (a) `artifacts/pr_context.summary.txt` from `collect_pr_context` (orchestrator-only MCP tool), (b) orchestrator-state passes `--require-pr-creation-ready` (orchestrator writes it), (c) `artifacts/pr_body_.md` + `artifacts/pr_body_.receipt.json` with fresh SHA-256. Therefore PR-creation tasks in executor plans need the ORCHESTRATOR CONFIRMATION REQUIRED marker. +- Every outward-facing task (dispatching workflows on main, preparing commits to main) needs the literal marker sentence, not gating prose; fold observable preconditions (e.g., `gh pr view --json state` == MERGED) into an existing task's command list to avoid task renumbering. +- `$env:TEMP` is shared with concurrent sibling-worktree agents — mandate the session scratchpad for tool downloads and temp files. +- Shell state does not persist between executor tool invocations: helper functions defined in plan prose must be written once to a `SCRATCH\helpers-.ps1` and dot-sourced in every invocation that calls them. +- Never hard-code the working branch (session worktree branch != feature branch); define `BRANCH` = `git rev-parse --abbrev-ref HEAD` captured in Phase 0. + +**Execution rev-3 finding (tool flag, generalizable):** +- actionlint's `-color` is a BOOLEAN flag (force color on); `-no-color` suppresses color. `-color never` makes Go's flag parser treat `never` as a positional FILE argument → `could not read "never"`, exit 3. Never write `-color ` for actionlint; verified against 1.7.7's own `-h` output. + +**Preflight rev-2 findings (introduced by rev-1, both generalizable):** +- When a convention enumerates the tasks that dot-source a helper file, the list must include the EARLIEST invocation — Phase 0 acceptance checks often call helpers before the implementation phases do. State that the helper file is created at first use. +- Identical `ConvertTo-Json -Depth 20` parameters do NOT imply identical key order. Any git-diff comparison of two serialized objects requires BOTH sides built from the same `[ordered]@{...}` literal (same keys, same order); a plain `@{}` hashtable emits keys in unspecified order and produces a spurious diff between semantically identical documents. + +Related: [[plan-validator-phase-heading-constraint]], [[feedback-ac-checkoff-one-per-task]], [[evidence-path-normalization]]. diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..8f976738 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,180 @@ +# GitHub Actions Workflows + +This directory holds the CI orchestrator and the five callee reusable workflows +it invokes. The split was introduced by issue #553 to replace a single +sequential `quality-gates` job, whose measured wall clock was 444s, with +independent gate jobs that GitHub Actions schedules concurrently and that report +as separate status checks. + +## Pipeline overview + +`ci.yml` is a pure orchestrator. It declares the workflow `name`, the `on` +triggers, `permissions`, and the workflow-level `concurrency` block, then +references each gate with `uses:`. It contains no inline `steps:`. + +| File | Runner | Gate | Timeout | +| --- | --- | --- | --- | +| `ci.yml` | n/a (orchestrator) | Invokes the five callees below | n/a | +| `_actionlint.yml` | `ubuntu-latest` | Downloads actionlint 1.7.7 and lints every workflow file | 10 min | +| `_format-check.yml` | `windows-latest` | `dotnet csharpier check .` | 10 min | +| `_build-analyzers.yml` | `windows-latest` | `msbuild /t:Build` with `EnableNETAnalyzers` and `EnforceCodeStyleInBuild` | 30 min | +| `_build-nullable.yml` | `windows-latest` | `msbuild /t:Rebuild` with `TreatWarningsAsErrors` | 30 min | +| `_mstest-coverage.yml` | `windows-latest` | Plain `msbuild /t:Build`, then `vstest.console.exe` with `/EnableCodeCoverage`; uploads the `test-results` artifact | 30 min | + +Structural properties that are deliberate and should not be changed casually: + +- **Zero `needs:` edges.** No gate depends on another. The topology shares no + build output between jobs, so there is no artifact-consumption edge to justify + an ordering constraint. Every gate runs to completion independently, which is + what preserves the full diagnostic signal in a single run: a nullable failure + does not prevent the MSTest result from being reported. +- **The caller owns the concurrency group.** `ci.yml` declares + `group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}` + with `cancel-in-progress: true`. Jobs of a called workflow run as part of the + caller's run and are covered by the caller's group, so a superseded run is + cancelled as a unit. **The callees declare no `concurrency` block of their + own.** Callee-level workflow concurrency under `workflow_call` is not clearly + documented, and the pipeline avoids relying on it rather than depending on + undefined behavior. +- **Per-job tailored setup.** The msbuild-consuming callees omit + `setup-dotnet`, the dotnet-tools cache, and `dotnet tool restore`; the format + callee omits `setup-msbuild`, `setup-nuget`, the `packages` cache, and + `nuget restore`. Each job installs only what its gate consumes. If a gate ever + fails because a trimmed setup step was in fact required, restore that specific + step to that specific callee rather than restoring full setup everywhere. +- **Gate commands are byte-identical to their pre-split forms.** The two msbuild + invocations (including the `/t:Rebuild` rationale comment and both + `$LASTEXITCODE` guards), the csharpier invocation, and the vstest invocation + (including the test-assembly discovery filter and the zero-assembly `throw`) + were moved, not edited. Treat any change to those blocks as a change to the + gate's pass criterion. + +## Per-stage workflow_dispatch procedure + +Every callee declares `workflow_dispatch` in addition to `workflow_call`, so any +single gate can be re-run on its own without re-running the whole pipeline. This +is the intended response to a transient infrastructure failure in one gate. + +From the command line: + +``` +gh workflow run _.yml --ref +``` + +For example, to re-run only the MSTest gate against `main`: + +``` +gh workflow run _mstest-coverage.yml --ref main +gh run list --workflow _mstest-coverage.yml --limit 1 --json databaseId,status,conclusion +gh run watch --exit-status +``` + +The same operation is available in the Actions UI: select the workflow in the +left-hand list and use the **Run workflow** button. + +Two caveats: + +1. **A standalone dispatch forms its own run.** It is not part of a `ci.yml` run + and is therefore outside the CI concurrency group. It will not be cancelled by + a subsequent push, and it will not cancel an in-flight CI run. +2. **A standalone dispatch does not update a pull request's required checks.** It + produces its own run with its own check; it does not re-report the + `CI / ` context that branch protection requires. To turn a required + context green, re-run the failed job from the pull request's Checks tab, which + re-runs it within the `ci.yml` run. + +## Branch-protection rename procedure + +Splitting or renaming a gate changes the check-run context names that branch +protection requires. The `main` ruleset (id `18572843`) uses +`strict_required_status_checks_policy: true`, so a required context that never +reports blocks merging. That property is fail-closed and is the reason the +procedure below over-blocks rather than under-gates at every step. + +Follow this sequence exactly. + +1. **Open the pull request and let it run.** For `pull_request` events GitHub + executes the workflow files from the pull request's head ref, so the pull + request that changes the pipeline exercises the new pipeline and reports the + new contexts. Expect the pull request to be blocked by the old required + context, which can no longer report. That is the fail-closed state and is + correct. + +2. **Confirm the run is green, then capture the exact context names from the live + head SHA.** Do not assume or hand-write the strings. For jobs of a called + reusable workflow the context name takes the form + ` / `, and a wrong string is the single most + likely way to botch the migration. + + ``` + gh api repos/drmoisan/TaskMaster/commits//check-runs --jq '.check_runs[].name' + ``` + +3. **Apply one atomic PUT.** Fetch the current ruleset, build the new body from + its writable fields only, replace the entire required-contexts array in the + same request, and retain `strict_required_status_checks_policy: true`. + + ``` + gh api repos/drmoisan/TaskMaster/rulesets/18572843 > ruleset-current.json + # Build ruleset-new.json from the writable fields of ruleset-current.json: + # name, target, enforcement, bypass_actors, conditions, rules + # Replace the required_status_checks rule's parameters.required_status_checks + # with the complete new set of {"context": ""} entries captured in step 2. + gh api --method PUT repos/drmoisan/TaskMaster/rulesets/18572843 --input ruleset-new.json + ``` + + The read-only fields returned by GET — `id`, `node_id`, `created_at`, + `updated_at`, `_links`, `source`, `source_type`, `current_user_can_bypass` — + are not part of the payload and must be stripped. + + **A two-step remove-then-add edit is prohibited.** Removing the old context + before adding the replacements leaves a window in which fewer gates are + required than intended, which is the only way this procedure can under-gate. + +4. **Merge immediately.** Between the PUT and the merge, every other open pull + request still runs the old pipeline from its own head ref, so it reports the + old context and lacks the new ones and is blocked until it updates its branch + past the merge. This is over-blocking, not under-gating, but it is disruptive, + so keep the interval short. Because `strict` requires the branch to be up to + date with `main`, update or rebase the branch first if needed. + +5. **Verify by GET.** Confirm the ruleset holds exactly the intended contexts and + that `strict_required_status_checks_policy` is still `true`. + + ``` + gh api repos/drmoisan/TaskMaster/rulesets/18572843 + ``` + +Record the pre-PUT ruleset JSON, the PUT payload, and the post-PUT GET response +as evidence. + +**Rollback.** A single PUT restoring the previous contexts set reverts the merge +policy. Reverting the workflow change itself is an ordinary revert pull request. +The two are independent: reverting the workflows without restoring the contexts +leaves the new contexts required but never reported, which blocks all merges. + +## Rules + +Two repository rules govern changes in this directory. Read them before editing a +workflow file. + +- **`.claude/rules/ci-workflows.md`** — governs `pwsh` steps. A step whose `run:` + block intentionally invokes a command expected to fail must not let the + residual non-zero `$LASTEXITCODE` propagate to GitHub Actions; it must reset the + exit code explicitly or terminate the success path with an explicit `exit 0`. + No step in this pipeline currently uses that pattern, so no reset is present or + required. The rule becomes load-bearing the moment someone adds a negative-path + self-validation step. Note that this does not apply to the gate commands + themselves: for a gate, a non-zero exit **is** the signal, and the + `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` guards on the msbuild steps + exist to propagate it, not to suppress it. + +- **`.claude/rules/benchmark-baselines.md`** — governs performance baselines. A + baseline must be captured in the same runner environment class it is compared + against; a developer workstation capture must not be compared against a hosted + runner. When measuring this pipeline's duration, collect from a GitHub-hosted + run with the same method used for the baseline of record: + + ``` + gh api repos/drmoisan/TaskMaster/actions/runs//jobs + ``` diff --git a/.github/workflows/_actionlint.yml b/.github/workflows/_actionlint.yml new file mode 100644 index 00000000..5abc00fb --- /dev/null +++ b/.github/workflows/_actionlint.yml @@ -0,0 +1,29 @@ +name: actionlint + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +jobs: + actionlint: + name: actionlint + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run actionlint + shell: bash + run: | + set -euo pipefail + version=1.7.7 + curl -sSL "https://github.com/rhysd/actionlint/releases/download/v${version}/actionlint_${version}_linux_amd64.tar.gz" -o actionlint.tar.gz + tar -xzf actionlint.tar.gz actionlint + ./actionlint diff --git a/.github/workflows/_build-analyzers.yml b/.github/workflows/_build-analyzers.yml new file mode 100644 index 00000000..ccaf1b80 --- /dev/null +++ b/.github/workflows/_build-analyzers.yml @@ -0,0 +1,53 @@ +name: build-analyzers + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-analyzers: + name: Build with analyzers and code style enforcement + runs-on: windows-latest + timeout-minutes: 30 + + env: + SOLUTION_PATH: TaskMaster.sln + BUILD_CONFIGURATION: Debug + BUILD_PLATFORM: Any CPU + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Setup NuGet + uses: nuget/setup-nuget@v2 + with: + nuget-version: latest + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: packages + key: nuget-${{ runner.os }}-${{ hashFiles('**/packages.config') }} + restore-keys: | + nuget-${{ runner.os }}- + + - name: Restore solution + shell: pwsh + run: nuget restore $env:SOLUTION_PATH + + - name: Build with analyzers and code style enforcement + shell: pwsh + run: | + & msbuild $env:SOLUTION_PATH /t:Build /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" ` + /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/.github/workflows/_build-nullable.yml b/.github/workflows/_build-nullable.yml new file mode 100644 index 00000000..5fb5c008 --- /dev/null +++ b/.github/workflows/_build-nullable.yml @@ -0,0 +1,60 @@ +name: build-nullable + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-nullable: + name: Build with nullable warnings treated as errors + runs-on: windows-latest + timeout-minutes: 30 + + env: + SOLUTION_PATH: TaskMaster.sln + BUILD_CONFIGURATION: Debug + BUILD_PLATFORM: Any CPU + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Setup NuGet + uses: nuget/setup-nuget@v2 + with: + nuget-version: latest + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: packages + key: nuget-${{ runner.os }}-${{ hashFiles('**/packages.config') }} + restore-keys: | + nuget-${{ runner.os }}- + + - name: Restore solution + shell: pwsh + run: nuget restore $env:SOLUTION_PATH + + - name: Build with nullable warnings treated as errors + shell: pwsh + run: | + # Use /t:Rebuild (not /t:Build) so this step always performs a genuine full + # recompile. Enforcement now relies entirely on each file's own #nullable + # enable pragma (the repo's per-file opt-in convention; UtilitiesCS.csproj and + # SVGControl.csproj carry no project-level element) plus + # /p:TreatWarningsAsErrors=true. MSBuild's incremental up-to-date check does + # not invalidate on this command-line property change alone, so a plain + # /t:Build would silently skip recompilation and never enforce this gate. + & msbuild $env:SOLUTION_PATH /t:Rebuild /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" ` + /p:TreatWarningsAsErrors=true + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/.github/workflows/_format-check.yml b/.github/workflows/_format-check.yml new file mode 100644 index 00000000..4ff58370 --- /dev/null +++ b/.github/workflows/_format-check.yml @@ -0,0 +1,41 @@ +name: format-check + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +jobs: + format-check: + name: Verify formatting + runs-on: windows-latest + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Cache dotnet tools + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: dotnet-tools-${{ runner.os }}-${{ hashFiles('dotnet-tools.json') }} + restore-keys: | + dotnet-tools-${{ runner.os }}- + + - name: Setup CSharpier + shell: pwsh + run: dotnet tool restore + + - name: Verify formatting + shell: pwsh + run: dotnet csharpier check . diff --git a/.github/workflows/_mstest-coverage.yml b/.github/workflows/_mstest-coverage.yml new file mode 100644 index 00000000..cda0be00 --- /dev/null +++ b/.github/workflows/_mstest-coverage.yml @@ -0,0 +1,96 @@ +name: mstest-coverage + +on: + workflow_call: + workflow_dispatch: + +permissions: + contents: read + +jobs: + mstest-coverage: + name: Run MSTest suite with coverage + runs-on: windows-latest + timeout-minutes: 30 + + env: + SOLUTION_PATH: TaskMaster.sln + BUILD_CONFIGURATION: Debug + BUILD_PLATFORM: Any CPU + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Setup NuGet + uses: nuget/setup-nuget@v2 + with: + nuget-version: latest + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: packages + key: nuget-${{ runner.os }}-${{ hashFiles('**/packages.config') }} + restore-keys: | + nuget-${{ runner.os }}- + + - name: Restore solution + shell: pwsh + run: nuget restore $env:SOLUTION_PATH + + - name: Build solution + shell: pwsh + run: | + & msbuild $env:SOLUTION_PATH /t:Build /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Run MSTest suite with coverage + shell: pwsh + run: | + Set-StrictMode -Version Latest + $ErrorActionPreference = 'Stop' + + $vswherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' + if (-not (Test-Path $vswherePath)) { + throw 'vswhere.exe was not found. Install Visual Studio 2022 (or Build Tools) with Test Platform components.' + } + + $vstestPath = & $vswherePath -latest -products * -find 'Common7\IDE\Extensions\TestPlatform\vstest.console.exe' | Select-Object -First 1 + if (-not $vstestPath) { + throw 'vstest.console.exe not found via vswhere. Install Visual Studio Test Platform components.' + } + + $testAssemblies = Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -Filter '*.Test.dll' | + Where-Object { + $_.FullName -match "\\bin\\$($env:BUILD_CONFIGURATION)\\" -and + $_.FullName -notmatch '\\obj\\' -and + $_.FullName -notmatch '\\ref\\' + } | + Select-Object -ExpandProperty FullName + + if (-not $testAssemblies -or $testAssemblies.Count -eq 0) { + throw "No test assemblies found under '$env:GITHUB_WORKSPACE' for configuration '$env:BUILD_CONFIGURATION'." + } + + New-Item -ItemType Directory -Path 'TestResults' -Force | Out-Null + & $vstestPath $testAssemblies /EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook" + if ($LASTEXITCODE -ne 0) { + throw "MSTest execution failed with exit code $LASTEXITCODE" + } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + TestResults/**/*.trx + TestResults/**/*.coverage + if-no-files-found: warn diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2beda7da..32f002ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,144 +17,16 @@ concurrency: jobs: actionlint: name: actionlint - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run actionlint - shell: bash - run: | - set -euo pipefail - version=1.7.7 - curl -sSL "https://github.com/rhysd/actionlint/releases/download/v${version}/actionlint_${version}_linux_amd64.tar.gz" -o actionlint.tar.gz - tar -xzf actionlint.tar.gz actionlint - ./actionlint - - quality-gates: - name: Format, build, analyze, and test - runs-on: windows-latest - timeout-minutes: 60 - - env: - SOLUTION_PATH: TaskMaster.sln - BUILD_CONFIGURATION: Debug - BUILD_PLATFORM: Any CPU - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 10.0.x - - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v2 - - - name: Setup NuGet - uses: nuget/setup-nuget@v2 - with: - nuget-version: latest - - - name: Cache NuGet packages - uses: actions/cache@v4 - with: - path: packages - key: nuget-${{ runner.os }}-${{ hashFiles('**/packages.config') }} - restore-keys: | - nuget-${{ runner.os }}- - - - name: Restore solution - shell: pwsh - run: nuget restore $env:SOLUTION_PATH - - - name: Cache dotnet tools - uses: actions/cache@v4 - with: - path: ~/.nuget/packages - key: dotnet-tools-${{ runner.os }}-${{ hashFiles('dotnet-tools.json') }} - restore-keys: | - dotnet-tools-${{ runner.os }}- - - - name: Setup CSharpier - shell: pwsh - run: dotnet tool restore - - - name: Verify formatting - shell: pwsh - run: dotnet csharpier check . - - - name: Build with analyzers and code style enforcement - shell: pwsh - run: | - & msbuild $env:SOLUTION_PATH /t:Build /m /p:Configuration=Debug ` - "/p:Platform=Any CPU" ` - /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - - name: Build with nullable warnings treated as errors - shell: pwsh - run: | - # Use /t:Rebuild (not /t:Build) so this step always performs a genuine full - # recompile. Enforcement now relies entirely on each file's own #nullable - # enable pragma (the repo's per-file opt-in convention; UtilitiesCS.csproj and - # SVGControl.csproj carry no project-level element) plus - # /p:TreatWarningsAsErrors=true. MSBuild's incremental up-to-date check does - # not invalidate on this command-line property change alone, so a plain - # /t:Build would silently skip recompilation and never enforce this gate. - & msbuild $env:SOLUTION_PATH /t:Rebuild /m /p:Configuration=Debug ` - "/p:Platform=Any CPU" ` - /p:TreatWarningsAsErrors=true - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - - name: Run MSTest suite with coverage - shell: pwsh - run: | - Set-StrictMode -Version Latest - $ErrorActionPreference = 'Stop' - - $vswherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' - if (-not (Test-Path $vswherePath)) { - throw 'vswhere.exe was not found. Install Visual Studio 2022 (or Build Tools) with Test Platform components.' - } - - $vstestPath = & $vswherePath -latest -products * -find 'Common7\IDE\Extensions\TestPlatform\vstest.console.exe' | Select-Object -First 1 - if (-not $vstestPath) { - throw 'vstest.console.exe not found via vswhere. Install Visual Studio Test Platform components.' - } - - $testAssemblies = Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -Filter '*.Test.dll' | - Where-Object { - $_.FullName -match "\\bin\\$($env:BUILD_CONFIGURATION)\\" -and - $_.FullName -notmatch '\\obj\\' -and - $_.FullName -notmatch '\\ref\\' - } | - Select-Object -ExpandProperty FullName - - if (-not $testAssemblies -or $testAssemblies.Count -eq 0) { - throw "No test assemblies found under '$env:GITHUB_WORKSPACE' for configuration '$env:BUILD_CONFIGURATION'." - } - - New-Item -ItemType Directory -Path 'TestResults' -Force | Out-Null - & $vstestPath $testAssemblies /EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook" - if ($LASTEXITCODE -ne 0) { - throw "MSTest execution failed with exit code $LASTEXITCODE" - } - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: test-results - path: | - TestResults/**/*.trx - TestResults/**/*.coverage - if-no-files-found: warn + uses: ./.github/workflows/_actionlint.yml + format-check: + name: format-check + uses: ./.github/workflows/_format-check.yml + build-analyzers: + name: build-analyzers + uses: ./.github/workflows/_build-analyzers.yml + build-nullable: + name: build-nullable + uses: ./.github/workflows/_build-nullable.yml + mstest-coverage: + name: mstest-coverage + uses: ./.github/workflows/_mstest-coverage.yml diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/actionlint-baseline.2026-08-14T09-54.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/actionlint-baseline.2026-08-14T09-54.md new file mode 100644 index 00000000..9c75e6e3 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/actionlint-baseline.2026-08-14T09-54.md @@ -0,0 +1,69 @@ +# actionlint Pre-Change Baseline — Issue #553 + +- Timestamp: 2026-08-14T09-54 (local) / 2026-08-14T13:54:53Z (UTC session timestamp; command executed at 2026-08-14T14:0xZ) +- Task: [P0-T3] + +Command (run from the repository root; actionlint auto-discovers `.github/workflows/`): + +```powershell +$v = '1.7.7'; $dir = '\actionlint-553' +New-Item -ItemType Directory -Force $dir | Out-Null +Invoke-WebRequest "https://github.com/rhysd/actionlint/releases/download/v${v}/actionlint_${v}_windows_amd64.zip" -OutFile "$dir\actionlint.zip" +Expand-Archive "$dir\actionlint.zip" -DestinationPath $dir -Force +& "$dir\actionlint.exe" -no-color +``` + +`` = `C:\Users\DANMOI~1\AppData\Local\Temp\claude\C--Users-DanMoisan-repos-TaskMaster-wt-2026-08-14T09-01\012c26d5-57f2-4f08-bc74-bf50a60b1e4e\scratchpad` + +EXIT_CODE: 0 + +## Output Summary + +- **Exit 0, zero findings.** actionlint produced no output, which is its clean + result form. +- Files linted (auto-discovered from `.github/workflows/`, confirmed by + `Get-ChildItem .github\workflows -Filter *.yml`): `ci.yml` and + `codex-web-setup-test.yml` — the two workflow files that exist pre-change. The + five `_*.yml` callees do not exist yet; they are authored in Phase 1 and linted + by [P2-T3] and [P5-T1]. +- Tool provenance, from `actionlint.exe -version`: + ``` + 1.7.7 + installed by downloading from release page + built with go1.23.4 compiler for windows/amd64 + ``` + This is the same version the `actionlint` job downloads and runs inside CI + (`ci.yml` line 33, `version=1.7.7`), so the local gate and the CI gate use + identical tool behavior. +- Binary location: `\actionlint-553\actionlint.exe`. No file was written + to `$env:TEMP` or inside the repository. + +## Flag-Form Correction Recorded During This Task + +The first execution of this task used the plan's original command string +`actionlint.exe -color never`, which failed: + +``` +could not read "never": open never: The system cannot find the file specified. +EXIT_CODE=3 +``` + +Root cause: actionlint 1.7.7's `-color` is a **boolean** flag ("Always enable +colorful output"); it accepts no value. Go's flag parser therefore consumed +`-color` as the boolean and treated `never` as a positional FILE argument, which +does not exist. The correct suppression form is the separate boolean `-no-color` +("Disable colorful output"). + +The executor halted at [P0-T3] rather than substituting a command silently, the +plan was corrected at all three affected sites ([P0-T3], [P2-T3], [P5-T1]), and a +Conventions note was added to prevent reintroduction. This artifact records the +result of the **corrected** command, which is the command now in the plan of +record. Exit 3 was an argument-parsing error, never a lint finding: no version of +this task ever reported a workflow defect. + +## Acceptance ([P0-T3]) + +- Artifact exists with `EXIT_CODE: 0`. +- The pre-change tree lints clean, so decomposition may proceed. This baseline is + the comparison point for [P2-T3] (post-change) and [P5-T1] (final), both of + which must also reach exit 0 — over seven files rather than two. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/git-baseline.2026-08-14T09-54.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/git-baseline.2026-08-14T09-54.md new file mode 100644 index 00000000..5ca168bb --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/git-baseline.2026-08-14T09-54.md @@ -0,0 +1,60 @@ +# Git Baseline — Issue #553 + +- Timestamp: 2026-08-14T09-54 (local) / 2026-08-14T13:54:53Z (UTC) +- Task: [P0-T2] + +Command: + +``` +git rev-parse HEAD; git rev-parse --abbrev-ref HEAD; git status --porcelain +``` + +EXIT_CODE: 0 + +## Output Summary + +- BASELINE_SHA: `e246688b87a436567b6951729a74d106328ae04c` +- BRANCH: `feature/ci-parallel-job-split-553` (matches the expected value recorded + in the plan's Conventions section; this value is what [P3-T2] and [P3-T4] + reference, never a hard-coded literal) +- Merge base with `origin/main`: `2073f717bbfac30053f3d6a4e652d99af3ae5c9c` + (branch is 1 commit ahead: `e246688b docs(553): promote CI parallel job split, + capture baseline, research, spec, and user story`) +- Remote branch state: `feature/ci-parallel-job-split-553` does not yet exist on + `origin`, so `git push -u origin ` in [P3-T2] is the correct form. + +Verbatim `git status --porcelain` output at baseline: + +``` + M .claude/agent-memory/atomic-executor/MEMORY.md + M .claude/agent-memory/atomic-planner/MEMORY.md + M docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md +?? .claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md +?? .claude/agent-memory/atomic-planner/project_553_ci_parallel_split_plan_seams.md +?? docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ +?? docs/features/potential/promoted/2026-08-14-orchestrator-hooks-reference-absent-python-validators.md +?? docs/features/potential/promoted/2026-08-14-potential-to-issue-promoted-copy-not-written.md +``` + +Classification of the pre-existing dirty entries (expected and permitted by the +task text; noted, not gated on): + +| Path | Class | +| --- | --- | +| `.claude/agent-memory/atomic-executor/MEMORY.md` + `project_pwsh_git_gh_cli_gotchas.md` | agent memory written during preflight validation | +| `.claude/agent-memory/atomic-planner/MEMORY.md` + `project_553_ci_parallel_split_plan_seams.md` | agent memory written during planning | +| `docs/features/.../plan.2026-08-14T09-05.md` | the plan of record, revised in place across three preflight passes | +| `docs/features/.../evidence/other/` | the [P0-T1] artifact written moments before this capture | +| `docs/features/potential/promoted/*.md` | two unrelated promoted-potential documents, pre-existing on this branch | + +**No entry under `.github/` and no `*.cs`, `*.csproj`, `*.props`, `*.targets`, or +`packages.config` entry is present.** This confirms the [P0-T1] acceptance clause +that no code file had been modified at the start of execution. + +## Acceptance ([P0-T2]) + +- Artifact exists; `BASELINE_SHA` and `BRANCH` are recorded for use by later tasks. +- This is a record of the starting state, not an invariant that `HEAD` remains at + this SHA. Later diff-scoped verifications reference `BASELINE_SHA`; [P5-T3] uses + `git merge-base origin/main HEAD` rather than this literal so it remains correct + after the Phase 3–4 commits. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/sequential-baseline-check.2026-08-14T09-54.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/sequential-baseline-check.2026-08-14T09-54.md new file mode 100644 index 00000000..ec24cabd --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/sequential-baseline-check.2026-08-14T09-54.md @@ -0,0 +1,59 @@ +# Sequential Baseline Artifact Check — Issue #553 + +- Timestamp: 2026-08-14T09-54 (local) / 2026-08-14T13:54:53Z (UTC session timestamp) +- Task: [P0-T4] + +Command: + +```powershell +Select-String -Path 'docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md' -Pattern '444s' +``` + +EXIT_CODE: 0 + +`Select-String` is a PowerShell cmdlet, not a native executable, so it does not +set `$LASTEXITCODE`. The recorded `EXIT_CODE: 0` is the exit status of the `pwsh` +process that ran the command, captured from the calling shell. The cmdlet +terminated without error and returned a non-empty match set. + +## Output Summary + +Four matched lines, quoted verbatim from the baseline artifact: + +``` +L20: | `Format, build, analyze, and test` | windows-latest | 22:25:29Z | 22:32:53Z | **7m24s (444s)** | +L23: `quality-gates` job duration: **444s**. +L51: - Sum: 130 + 302 + 12 = 444s, matching the job total. +L66: against a 444s baseline - an estimated **~25% latency reduction**. +``` + +MATCH_COUNT: 4 + +The comparison denominator for [P4-T6] is therefore confirmed present and +internally consistent within the baseline artifact: + +- **444s** is the measured `Format, build, analyze, and test` job wall clock + (L20), which equals the pipeline wall clock because the `actionlint` job already + runs concurrently and finishes in 36s (baseline artifact L19, L22-23). +- The figure reconciles against its own step-level decomposition: 130s fixed setup + + 302s serial gate work + ~12s teardown = 444s (L51). +- The baseline was captured from GitHub-hosted `windows-latest` run + [31749877507](https://github.com/drmoisan/TaskMaster/actions/runs/31749877507) + at 2026-08-14T13:05:16Z using + `gh api repos/drmoisan/TaskMaster/actions/runs/31749877507/jobs`. + +**Runner-environment parity (`.claude/rules/benchmark-baselines.md`):** the +baseline is a runner-captured measurement from a GitHub-hosted `windows-latest` +runner, not a developer workstation. [P4-T6] must capture the post-split +measurement with the same `gh api .../runs//jobs` collection method from a +GitHub-hosted run so the comparison satisfies the parity requirement. That rule's +`Unknown processor` rejection condition and sibling-provenance-file requirement +govern BenchmarkDotNet-style baselines under `scripts/benchmarks/**`; this CI +latency baseline is a workflow-run timing record whose provenance is the linked +run URL and collection command recorded above. + +## Acceptance ([P0-T4]) + +- Artifact exists with `EXIT_CODE: 0`. +- `Output Summary:` quotes four matched `444s` lines (at least one required), + confirming the comparison denominator for [P4-T6]. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/phase0-instructions-read.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/phase0-instructions-read.md new file mode 100644 index 00000000..f584b0d9 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/phase0-instructions-read.md @@ -0,0 +1,106 @@ +# Phase 0 — Instructions Read (Issue #553) + +- Timestamp: 2026-08-14T09-54 (local) / 2026-08-14T13:54:53Z (UTC) +- Task: [P0-T1] +- Plan of record: `docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md` +- Work Mode: full-feature (from `issue.md` metadata marker) + +## Policy Order + +Read in the order defined by the plan's Required References section, which is +consistent with `.claude/skills/policy-compliance-order/SKILL.md` (CLAUDE.md → +general code change → general unit test → domain-specific rules), with the +feature's own requirement documents read last: + +1. `CLAUDE.md` — all sections, including the C# Code Change Policy and the C# Unit + Test Policy. Read for scope confirmation only; see the No-C#-Toolchain + Statement below for why the C# toolchain loop does not apply to this change. +2. `.claude/rules/general-code-change.md` — cross-language code change policy + (design principles, 500-line file limit, mandatory toolchain loop, I/O + boundaries). +3. `.claude/rules/general-unit-test.md` — cross-language unit test policy. No unit + tests are added or modified by this feature; no coverage-bearing language is in + scope. +4. `.claude/rules/ci-workflows.md` — governs `pwsh` steps in GitHub Actions + workflows; the deliberately-failing-nested-command pattern and the + `$LASTEXITCODE` reset requirement. Load-bearing for this feature: every `pwsh` + step relocated into a callee is reviewed against this rule in P5-T2. +5. `.claude/rules/benchmark-baselines.md` — runner-environment parity for + performance baselines. Load-bearing: the 444s baseline was captured on a + GitHub-hosted `windows-latest` runner and the post-split comparison (P4-T6) + must be drawn against a run of the same runner class. +6. `.claude/rules/tonality.md` — professional tone for all authored content, + including the new `.github/workflows/README.md` and every evidence artifact. +7. `docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md` — design of + record (10 acceptance criteria, 5 Definition-of-Done items, 8 seeded test + conditions, 8 invariants). +8. `docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md` — the + promoted issue and its 8 draft acceptance criteria (AC mirror). +9. `docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md` — user + story and its 8 resolved acceptance criteria (AC mirror). Under Work Mode + `full-feature`, `spec.md` and `user-story.md` are the authoritative AC sources + per `.claude/skills/acceptance-criteria-tracking/SKILL.md`; `issue.md` is + tracked as a third mirror because the plan's check-off tasks name it. +10. `docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md` + — research artifact. Q8 = required-check migration sequencing (atomic PUT, + context names captured never assumed); Q9 = `$LASTEXITCODE` hygiene review + (no step in the current pipeline uses the deliberately-failing pattern). +11. `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` + — the measured 444s sequential baseline and its step-level breakdown. + +## Files Read (all eleven Required References) + +| # | Path | Read | +| --- | --- | --- | +| 1 | `CLAUDE.md` | yes | +| 2 | `.claude/rules/general-code-change.md` | yes | +| 3 | `.claude/rules/general-unit-test.md` | yes | +| 4 | `.claude/rules/ci-workflows.md` | yes | +| 5 | `.claude/rules/benchmark-baselines.md` | yes | +| 6 | `.claude/rules/tonality.md` | yes | +| 7 | `docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md` | yes | +| 8 | `docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md` | yes | +| 9 | `docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md` | yes | +| 10 | `docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md` | yes | +| 11 | `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` | yes | + +Also read as binding execution context: `.claude/skills/atomic-plan-contract/SKILL.md`, +`.claude/skills/evidence-and-timestamp-conventions/SKILL.md`, +`.claude/skills/acceptance-criteria-tracking/SKILL.md`, and +`.claude/skills/policy-compliance-order/SKILL.md`. + +## No-C#-Toolchain Statement (restated per [P0-T1]) + +This feature modifies no `*.cs`, `*.csproj`, `*.props`, `*.targets`, or +`packages.config` file in its final diff. Therefore: + +- **No C# source is in scope.** The executor must not run `csharpier`, `msbuild`, + or `vstest.console.exe` as verification of this change. A local C# pass would + assert nothing about GitHub Actions workflow YAML. Spec Non-Goal 5 states the + same boundary: "Any C# source, project, or test change" is out of scope. +- **actionlint is the only local harness.** There is no local test harness for + GitHub Actions workflows. actionlint 1.7.7 (which includes YAML parse + validation) is the only local verification available and is run three times by + this plan: P0-T3 (pre-change baseline), P2-T3 (post-change), P5-T1 (final). +- **The authoritative verification is the green run** of the reworked pipeline on + the branch head after push, per the `modified-workflow-needs-green-run` policy + rule. Local checks are necessary but not sufficient. +- **No coverage capture applies.** No language with a mandatory coverage policy is + modified, so no baseline or final-QC coverage tasks exist in this plan. The + coverage-bearing artifact of the pipeline (the `test-results` upload) is + preserved unchanged and its continued production is verified in P4-T5 against a + live run. +- The Phase 4 seeded fault-isolation probes temporarily commit C# edits and then + revert them. The net branch diff over C# and project files is zero, verified by + P5-T3. Those probe commits are exercised by CI itself, not by a local toolchain + pass. + +## Acceptance ([P0-T1]) + +- Artifact exists with `Timestamp:`, `Policy Order:`, the explicit list of all + eleven Required References, and the restated No-C#-Toolchain Statement. +- No code file has been modified at the time of writing. Verified by + `git status --porcelain` recorded in the [P0-T2] artifact + (`evidence/baseline/git-baseline.2026-08-14T09-54.md`): the only entries are + documentation, evidence, and `.claude/agent-memory` paths; nothing under + `.github/` and no `*.cs` file. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/actionlint-steps.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/actionlint-steps.txt new file mode 100644 index 00000000..2a11dd1b --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/actionlint-steps.txt @@ -0,0 +1,14 @@ + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run actionlint + shell: bash + run: | + set -euo pipefail + version=1.7.7 + curl -sSL "https://github.com/rhysd/actionlint/releases/download/v${version}/actionlint_${version}_linux_amd64.tar.gz" -o actionlint.tar.gz + tar -xzf actionlint.tar.gz actionlint + ./actionlint diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/analyzer-step.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/analyzer-step.txt new file mode 100644 index 00000000..9e160d4c --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/analyzer-step.txt @@ -0,0 +1,7 @@ + - name: Build with analyzers and code style enforcement + shell: pwsh + run: | + & msbuild $env:SOLUTION_PATH /t:Build /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" ` + /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/ci.yml.pre-split.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/ci.yml.pre-split.txt new file mode 100644 index 00000000..2beda7da --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/ci.yml.pre-split.txt @@ -0,0 +1,160 @@ +name: CI + +on: + push: + branches: [main, development] + pull_request: + branches: [main, development] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + actionlint: + name: actionlint + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run actionlint + shell: bash + run: | + set -euo pipefail + version=1.7.7 + curl -sSL "https://github.com/rhysd/actionlint/releases/download/v${version}/actionlint_${version}_linux_amd64.tar.gz" -o actionlint.tar.gz + tar -xzf actionlint.tar.gz actionlint + ./actionlint + + quality-gates: + name: Format, build, analyze, and test + runs-on: windows-latest + timeout-minutes: 60 + + env: + SOLUTION_PATH: TaskMaster.sln + BUILD_CONFIGURATION: Debug + BUILD_PLATFORM: Any CPU + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Setup NuGet + uses: nuget/setup-nuget@v2 + with: + nuget-version: latest + + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: packages + key: nuget-${{ runner.os }}-${{ hashFiles('**/packages.config') }} + restore-keys: | + nuget-${{ runner.os }}- + + - name: Restore solution + shell: pwsh + run: nuget restore $env:SOLUTION_PATH + + - name: Cache dotnet tools + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: dotnet-tools-${{ runner.os }}-${{ hashFiles('dotnet-tools.json') }} + restore-keys: | + dotnet-tools-${{ runner.os }}- + + - name: Setup CSharpier + shell: pwsh + run: dotnet tool restore + + - name: Verify formatting + shell: pwsh + run: dotnet csharpier check . + + - name: Build with analyzers and code style enforcement + shell: pwsh + run: | + & msbuild $env:SOLUTION_PATH /t:Build /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" ` + /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Build with nullable warnings treated as errors + shell: pwsh + run: | + # Use /t:Rebuild (not /t:Build) so this step always performs a genuine full + # recompile. Enforcement now relies entirely on each file's own #nullable + # enable pragma (the repo's per-file opt-in convention; UtilitiesCS.csproj and + # SVGControl.csproj carry no project-level element) plus + # /p:TreatWarningsAsErrors=true. MSBuild's incremental up-to-date check does + # not invalidate on this command-line property change alone, so a plain + # /t:Build would silently skip recompilation and never enforce this gate. + & msbuild $env:SOLUTION_PATH /t:Rebuild /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" ` + /p:TreatWarningsAsErrors=true + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Run MSTest suite with coverage + shell: pwsh + run: | + Set-StrictMode -Version Latest + $ErrorActionPreference = 'Stop' + + $vswherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' + if (-not (Test-Path $vswherePath)) { + throw 'vswhere.exe was not found. Install Visual Studio 2022 (or Build Tools) with Test Platform components.' + } + + $vstestPath = & $vswherePath -latest -products * -find 'Common7\IDE\Extensions\TestPlatform\vstest.console.exe' | Select-Object -First 1 + if (-not $vstestPath) { + throw 'vstest.console.exe not found via vswhere. Install Visual Studio Test Platform components.' + } + + $testAssemblies = Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -Filter '*.Test.dll' | + Where-Object { + $_.FullName -match "\\bin\\$($env:BUILD_CONFIGURATION)\\" -and + $_.FullName -notmatch '\\obj\\' -and + $_.FullName -notmatch '\\ref\\' + } | + Select-Object -ExpandProperty FullName + + if (-not $testAssemblies -or $testAssemblies.Count -eq 0) { + throw "No test assemblies found under '$env:GITHUB_WORKSPACE' for configuration '$env:BUILD_CONFIGURATION'." + } + + New-Item -ItemType Directory -Path 'TestResults' -Force | Out-Null + & $vstestPath $testAssemblies /EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook" + if ($LASTEXITCODE -ne 0) { + throw "MSTest execution failed with exit code $LASTEXITCODE" + } + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + TestResults/**/*.trx + TestResults/**/*.coverage + if-no-files-found: warn diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/format-step.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/format-step.txt new file mode 100644 index 00000000..9b8387d2 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/format-step.txt @@ -0,0 +1,3 @@ + - name: Verify formatting + shell: pwsh + run: dotnet csharpier check . diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/header.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/header.txt new file mode 100644 index 00000000..5ac6f0ba --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/header.txt @@ -0,0 +1,15 @@ +name: CI + +on: + push: + branches: [main, development] + pull_request: + branches: [main, development] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/nullable-step.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/nullable-step.txt new file mode 100644 index 00000000..efa989fe --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/nullable-step.txt @@ -0,0 +1,14 @@ + - name: Build with nullable warnings treated as errors + shell: pwsh + run: | + # Use /t:Rebuild (not /t:Build) so this step always performs a genuine full + # recompile. Enforcement now relies entirely on each file's own #nullable + # enable pragma (the repo's per-file opt-in convention; UtilitiesCS.csproj and + # SVGControl.csproj carry no project-level element) plus + # /p:TreatWarningsAsErrors=true. MSBuild's incremental up-to-date check does + # not invalidate on this command-line property change alone, so a plain + # /t:Build would silently skip recompilation and never enforce this gate. + & msbuild $env:SOLUTION_PATH /t:Rebuild /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" ` + /p:TreatWarningsAsErrors=true + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/upload-step.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/upload-step.txt new file mode 100644 index 00000000..d6c28ecf --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/upload-step.txt @@ -0,0 +1,9 @@ + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + TestResults/**/*.trx + TestResults/**/*.coverage + if-no-files-found: warn diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/vstest-step.txt b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/vstest-step.txt new file mode 100644 index 00000000..9f5ea1d8 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split/vstest-step.txt @@ -0,0 +1,33 @@ + - name: Run MSTest suite with coverage + shell: pwsh + run: | + Set-StrictMode -Version Latest + $ErrorActionPreference = 'Stop' + + $vswherePath = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' + if (-not (Test-Path $vswherePath)) { + throw 'vswhere.exe was not found. Install Visual Studio 2022 (or Build Tools) with Test Platform components.' + } + + $vstestPath = & $vswherePath -latest -products * -find 'Common7\IDE\Extensions\TestPlatform\vstest.console.exe' | Select-Object -First 1 + if (-not $vstestPath) { + throw 'vstest.console.exe not found via vswhere. Install Visual Studio Test Platform components.' + } + + $testAssemblies = Get-ChildItem -Path $env:GITHUB_WORKSPACE -Recurse -Filter '*.Test.dll' | + Where-Object { + $_.FullName -match "\\bin\\$($env:BUILD_CONFIGURATION)\\" -and + $_.FullName -notmatch '\\obj\\' -and + $_.FullName -notmatch '\\ref\\' + } | + Select-Object -ExpandProperty FullName + + if (-not $testAssemblies -or $testAssemblies.Count -eq 0) { + throw "No test assemblies found under '$env:GITHUB_WORKSPACE' for configuration '$env:BUILD_CONFIGURATION'." + } + + New-Item -ItemType Directory -Path 'TestResults' -Force | Out-Null + & $vstestPath $testAssemblies /EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook" + if ($LASTEXITCODE -ne 0) { + throw "MSTest execution failed with exit code $LASTEXITCODE" + } diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-postchange.2026-08-14T09-54.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-postchange.2026-08-14T09-54.md new file mode 100644 index 00000000..6d6b4c25 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-postchange.2026-08-14T09-54.md @@ -0,0 +1,95 @@ +# actionlint Post-Change Result — Issue #553 + +- Timestamp: 2026-08-14T09-54 (local) / 2026-08-14T13:54:53Z (UTC session timestamp) +- Task: [P2-T3] + +Command (run from the repository root): + +```powershell +& "\actionlint-553\actionlint.exe" -no-color +``` + +EXIT_CODE: 0 + +## Output Summary + +**Exit 0 with zero findings across all seven workflow files, on the first pass.** +actionlint produced no output, which is its clean result form. The fix-and-rerun +loop specified by this task was therefore not entered: no workflow file was +modified after being authored in Phase 1 / [P2-T1], so the [P1-T6] containment +checks and the [P2-T1] structural checks remain valid as recorded and did not +need to be re-run. + +To prove the file set was actually processed rather than silently skipped, the +same binary was run with `-verbose`: + +``` +verbose: Linting all workflow files in repository: C:\Users\DanMoisan\repos\TaskMaster-wt\2026-08-14T09-01 +verbose: Collected 7 YAML files +verbose: Linting 7 files +verbose: Linting .github\workflows\codex-web-setup-test.yml +verbose: Linting .github\workflows\_build-nullable.yml +verbose: Linting .github\workflows\_format-check.yml +verbose: Linting .github\workflows\ci.yml +verbose: Linting .github\workflows\_build-analyzers.yml +verbose: Linting .github\workflows\_mstest-coverage.yml +verbose: Linting .github\workflows\_actionlint.yml +... +verbose: Found 0 errors in 7 files +``` + +Per-file result (parse errors and total errors, from the verbose run): + +| File | Parse errors | Total errors | +| --- | --- | --- | +| `.github/workflows/ci.yml` (orchestrator, rewritten) | 0 | 0 | +| `.github/workflows/_actionlint.yml` (new) | 0 | 0 | +| `.github/workflows/_format-check.yml` (new) | 0 | 0 | +| `.github/workflows/_build-analyzers.yml` (new) | 0 | 0 | +| `.github/workflows/_build-nullable.yml` (new) | 0 | 0 | +| `.github/workflows/_mstest-coverage.yml` (new) | 0 | 0 | +| `.github/workflows/codex-web-setup-test.yml` (untouched) | 0 | 0 | + +`.github/workflows/README.md` is not lintable YAML and is correctly outside +actionlint's file set. + +This result also confirms that actionlint accepts the reusable-workflow wiring: +the five `uses: ./.github/workflows/_.yml` local references resolve, and +each callee's `on: workflow_call:` declaration is present and well-formed. A +missing or misspelled callee path, or a callee lacking `workflow_call`, is a +finding actionlint reports. + +## Local-versus-CI scope note + +Two optional external integrations were unavailable on this Windows host and were +reported as disabled in the verbose output: + +``` +verbose: Rule "pyflakes" was disabled: exec: "pyflakes": executable file not found in %PATH% +``` + +The CI `actionlint` job runs on `ubuntu-latest`, where actionlint's shellcheck and +pyflakes integrations may be available, so **CI's actionlint is a superset of this +local run**. The only shell script in the changed file set is the `bash` step +inside `_actionlint.yml`, which is transplanted byte-identically from the step +that passes CI's actionlint today (verified in +`evidence/qa-gates/byte-identity.2026-08-14T09-54.md`, block `actionlint-steps`, +SHA-256 `73f620a3...`). Its content is unchanged, so its shellcheck result should +be unchanged. This is nonetheless a genuine local-versus-CI gap and the +authoritative verification remains the green run on the branch head, per the +No-C#-Toolchain Statement and `modified-workflow-needs-green-run`. + +## Comparison against the pre-change baseline + +| Run | Files linted | Findings | Exit | +| --- | --- | --- | --- | +| [P0-T3] pre-change baseline | 2 (`ci.yml`, `codex-web-setup-test.yml`) | 0 | 0 | +| [P2-T3] post-change (this artifact) | 7 | 0 | 0 | + +The decomposition added five workflow files and introduced no lint finding. + +## Acceptance ([P2-T3]) + +- Artifact exists with `EXIT_CODE: 0` on the final (and only) pass. +- Loop rule not triggered: zero findings, so no workflow file was fixed and + re-linted, and no gated block required re-verification. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/byte-identity.2026-08-14T09-54.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/byte-identity.2026-08-14T09-54.md new file mode 100644 index 00000000..e39dda89 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/byte-identity.2026-08-14T09-54.md @@ -0,0 +1,146 @@ +# Byte-Identity of Transplanted Gate Blocks — Issue #553 + +- Timestamp: 2026-08-14T09-54 (local) / 2026-08-14T13:54:53Z (UTC session timestamp) +- Task: [P1-T6] +- Evidence pointer for: spec.md acceptance criterion 5, spec invariants 1, 2, 3, 4, 5, 8 + +Command (helpers dot-sourced from `\helpers-553.ps1` per the plan's +Helper-persistence convention): + +```powershell +. "\helpers-553.ps1" +Test-BlockContained '\actionlint-steps.txt' '.github/workflows/_actionlint.yml' 'actionlint-steps' +Test-BlockContained '\format-step.txt' '.github/workflows/_format-check.yml' 'format-step' +Test-BlockContained '\analyzer-step.txt' '.github/workflows/_build-analyzers.yml' 'analyzer-step' +Test-BlockContained '\nullable-step.txt' '.github/workflows/_build-nullable.yml' 'nullable-step' +Test-BlockContained '\vstest-step.txt' '.github/workflows/_mstest-coverage.yml' 'vstest-step' +Test-BlockContained '\upload-step.txt' '.github/workflows/_mstest-coverage.yml' 'upload-step' +``` + +`` = `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/pre-split` +(the reference blocks extracted from the **pre-split** `.github/workflows/ci.yml` +by [P0-T5], before `ci.yml` was rewritten). + +EXIT_CODE: 0 + +## Output Summary — the six canonical checks + +``` +BYTE-IDENTICAL: actionlint-steps +BYTE-IDENTICAL: format-step +BYTE-IDENTICAL: analyzer-step +BYTE-IDENTICAL: nullable-step +BYTE-IDENTICAL: vstest-step +BYTE-IDENTICAL: upload-step +``` + +All six gated blocks pass. `Test-BlockContained` throws on failure, so six +`BYTE-IDENTICAL` lines and exit 0 constitute a complete pass. + +## Demonstration, not assertion — SHA-256 of the reference block vs the same span inside the callee + +`Test-BlockContained` proves containment. The table below independently proves +**equality of content** by locating each reference block inside its callee, +slicing exactly `reference.Length` characters at the located offset, and hashing +both sides. A single changed character anywhere in a block changes its digest. + +Both sides are LF-normalized (CRLF → LF) before hashing, and the reference's +trailing newline is trimmed. Line endings are the only permitted difference; git +manages them via `* text=auto` in `.gitattributes` with `core.autocrlf=true`, and +both the pre-split `ci.yml` and every new callee are CRLF in the working tree. + +| Block | Callee file | Located at | Length | Lines | SHA-256 (reference == callee span) | Result | +| --- | --- | --- | --- | --- | --- | --- | +| `actionlint-steps` | `_actionlint.yml` | L16 | 455 chars | 14 | `73f620a346c1b7ea71ff1b0d42f8bc4cc5fcfc6f751a59b3c57d145aab5035e5` | MATCH | +| `format-step` | `_format-check.yml` | L39 | 89 chars | 3 | `06b59782a55eff65b25a281224856122ac1f6846edc7388ceb5550ad4f5e4f5b` | MATCH | +| `analyzer-step` | `_build-analyzers.yml` | L47 | 341 chars | 7 | `3b1739dc5b6f769f9570b7eed9ff6e69e08e6254b58d6e3426fe6c625c402238` | MATCH | +| `nullable-step` | `_build-nullable.yml` | L47 | 903 chars | 14 | `321615d62e56dbb0b498d91c740c3d0c4c5286e8566d7cd80828cd398ff7a0b0` | MATCH | +| `vstest-step` | `_mstest-coverage.yml` | L54 | 1686 chars | 33 | `75ef35baabaf61768c2bbab953ecb91bdc0fa231ff14382576c485b29bedc9fa` | MATCH | +| `upload-step` | `_mstest-coverage.yml` | L88 | 261 chars | 9 | `894b0ce75a70c838b94ab6ef272b27280bca7ce8b4cd76b3801e9468f9a3c195` | MATCH | + +In every row the reference digest and the callee-span digest are the same value, +which is why a single column suffices. Six of six blocks match. + +## Fidelity by construction + +Each callee was assembled by a script that **read the extracted reference file +from disk and appended its lines**, rather than by re-typing the block. The +transplanted text therefore could not drift during authoring; the checks above +confirm the result. The same technique was used for the shared setup steps +(checkout, `setup-dotnet`, `setup-msbuild`, `setup-nuget`, both caches, +`nuget restore`, `dotnet tool restore`), which are sliced directly from +`ci.yml.pre-split.txt` even though only the six blocks above are formally gated. + +## Critical fragment presence with exact line citations + +Spec invariants 2, 3, 5, and 8 name specific fragments that must survive the move. +Each is verified by literal (`-SimpleMatch`) search in the authored callee: + +| Fragment (spec invariant) | Callee file | Line | Count | Expected | +| --- | --- | --- | --- | --- | +| `/t:Rebuild` rationale comment, first line (inv. 2) | `_build-nullable.yml` | L50 | 1 | 1 | +| `/t:Rebuild` rationale comment, last line (inv. 2) | `_build-nullable.yml` | L56 | 1 | 1 | +| `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` (inv. 5) | `_build-analyzers.yml` | L53 | 1 | 1 | +| `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` (inv. 5) | `_build-nullable.yml` | L60 | 1 | 1 | +| `$_.FullName -match "\\bin\\$($env:BUILD_CONFIGURATION)\\" -and` (inv. 8) | `_mstest-coverage.yml` | L72 | 1 | 1 | +| `$_.FullName -notmatch '\\obj\\' -and` (inv. 8) | `_mstest-coverage.yml` | L73 | 1 | 1 | +| `$_.FullName -notmatch '\\ref\\'` (inv. 8) | `_mstest-coverage.yml` | L74 | 1 | 1 | +| `throw "No test assemblies found` — zero-assembly guard (inv. 3) | `_mstest-coverage.yml` | L79 | 1 | 1 | +| `throw "MSTest execution failed` — failure guard (inv. 1) | `_mstest-coverage.yml` | L85 | 1 | 1 | +| `/EnableCodeCoverage /InIsolation /Logger:trx` (inv. 1) | `_mstest-coverage.yml` | L83 | 1 | 1 | +| `name: test-results` (inv. 4) | `_mstest-coverage.yml` | L92 | 1 | 1 | +| `if: always()` (inv. 4) | `_mstest-coverage.yml` | L89 | 1 | 1 | + +Twelve of twelve present at the expected count. The `/t:Rebuild` rationale +comment's first and last lines are both cited, so the whole seven-line comment +moved with its step rather than being truncated. + +The complete comment as it now stands in `_build-nullable.yml` (L50-L56): + +``` + # Use /t:Rebuild (not /t:Build) so this step always performs a genuine full + # recompile. Enforcement now relies entirely on each file's own #nullable + # enable pragma (the repo's per-file opt-in convention; UtilitiesCS.csproj and + # SVGControl.csproj carry no project-level element) plus + # /p:TreatWarningsAsErrors=true. MSBuild's incremental up-to-date check does + # not invalidate on this command-line property change alone, so a plain + # /t:Build would silently skip recompilation and never enforce this gate. +``` + +## What is deliberately NOT byte-identical + +One step in the new pipeline has no pre-split counterpart and is therefore not +gated by this artifact: the plain `Build solution` step in `_mstest-coverage.yml` +(L46-L51), which the MSTest job needs because it no longer inherits build output +from a preceding gate in the same job. Per spec, it carries **no** analyzer or +warning-promotion properties, verified by literal search: + +| Property that must be absent | Match count in `_mstest-coverage.yml` | Required | +| --- | --- | --- | +| `EnableNETAnalyzers` | 0 | 0 | +| `TreatWarningsAsErrors` | 0 | 0 | + +This keeps the analyzer and nullable gates as the sole enforcers of their +respective criteria and prevents the MSTest job from silently duplicating or +weakening them. + +## File sizes (500-line limit, `.claude/rules/general-code-change.md`) + +| File | Lines | +| --- | --- | +| `.github/workflows/_actionlint.yml` | 29 | +| `.github/workflows/_format-check.yml` | 41 | +| `.github/workflows/_build-analyzers.yml` | 53 | +| `.github/workflows/_build-nullable.yml` | 60 | +| `.github/workflows/_mstest-coverage.yml` | 96 | + +All well under 500. [P5-T4] audits the full set including `ci.yml` and the README. + +## Acceptance ([P1-T6]) + +- Artifact exists; all six containment checks pass and are independently + corroborated by matching SHA-256 digests. +- This is the evidence pointer for spec AC 5 ("the four gate commands and the + actionlint step are byte-identical to their pre-split counterparts, including + the `/t:Rebuild` rationale comment, the `$LASTEXITCODE` guards, and the + zero-test-assembly `throw` guard"), checked off in [P5-T10]. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md index 1aa45452..3276e517 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md @@ -1,53 +1,451 @@ -# 2026-08-14-ci-parallel-job-split - Plan +# 2026-08-14-ci-parallel-job-split — Plan - **Issue:** #553 - **Parent (optional):** none - **Owner:** drmoisan - **Last Updated:** 2026-08-14T09-05 -- **Status:** Draft -- **Version:** 0.1 +- **Status:** Ready for preflight +- **Version:** 1.0 +- **Work Mode:** full-feature ## Required References -- General Coding Standards: [`.github/instructions/general-code-change.instructions.md`](../../../../.github/instructions/general-code-change.instructions.md) -- General Unit Test Policy: [`.github/instructions/general-unit-test.instructions.md`](../../../../.github/instructions/general-unit-test.instructions.md) -- (Add language-specific policies as needed, e.g. `python-code-change.instructions.md`) +- `CLAUDE.md` (all sections, including the C# Code Change Policy — read for scope confirmation only; see the No-C#-Toolchain Statement below) +- `.claude/rules/general-code-change.md` +- `.claude/rules/general-unit-test.md` +- `.claude/rules/ci-workflows.md` (governs `pwsh` steps in workflows) +- `.claude/rules/benchmark-baselines.md` (governs the timing baseline/comparison) +- `.claude/rules/tonality.md` +- Design of record: `docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md` +- Acceptance-criteria source of record: `docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md` (Work Mode: full-feature) +- User story / AC mirror: `docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md` +- Research: `docs/features/active/2026-08-14-ci-parallel-job-split-553/research/2026-08-14T13-30-ci-parallel-job-split-research.md` (Q8 = ruleset migration procedure; Q9 = `$LASTEXITCODE` review) +- Measured baseline: `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` (444s) **All work must comply with these policies; do not duplicate their content here.** +## Conventions Used in This Plan + +- `FEATURE` = `docs/features/active/2026-08-14-ci-parallel-job-split-553` (relative to the repository root `C:\Users\DanMoisan\repos\TaskMaster-wt\2026-08-14T09-01`). +- `` = execution-time ISO-8601 timestamp in the form `yyyy-MM-ddTHH-mm` per `evidence-and-timestamp-conventions`. +- All evidence artifacts go to `FEATURE/evidence//` (canonical scheme). Every command-step evidence artifact records `Timestamp:`, `Command:`, `EXIT_CODE:`, and `Output Summary:`. +- All commands run from the repository root unless stated otherwise. Shell is PowerShell (`pwsh`); `gh` commands run identically from PowerShell. `jq` is NOT installed in this environment and must not be used; JSON manipulation uses `ConvertFrom-Json` / `ConvertTo-Json -Depth 20` (the default depth of 2 truncates nested objects). The only permitted `--jq` usage is the filter argument built into `gh api --jq`, which is compiled into `gh`. +- `BASELINE_SHA` = the commit recorded by P0-T2. Diff-scoped verifications reference `BASELINE_SHA`, never a live `HEAD` expectation. +- `BRANCH` = the branch name recorded by P0-T2 via `git rev-parse --abbrev-ref HEAD` (expected value: `feature/ci-parallel-job-split-553`). Tasks reference `BRANCH`; never hard-code a branch name into a command. +- `SCRATCH` = the session scratchpad directory `C:\Users\DANMOI~1\AppData\Local\Temp\claude\C--Users-DanMoisan-repos-TaskMaster-wt-2026-08-14T09-01\012c26d5-57f2-4f08-bc74-bf50a60b1e4e\scratchpad`. ALL temporary files (tool downloads, temp JSON projections, helper scripts) go under `SCRATCH`, never under `$env:TEMP` (shared with concurrent agents in sibling worktrees) and never under the feature folder. +- actionlint flag form: actionlint 1.7.7's `-color` is a BOOLEAN flag ("Always enable colorful output"); it takes no value. The correct way to suppress color is `-no-color`. Do not write `-color never` — Go's flag parser consumes `-color` as the boolean and treats `never` as a positional FILE argument, failing with `could not read "never"` (exit 3). +- Helper persistence: shell state does not persist between tool invocations in this environment. The executor MUST write the `Test-BlockContained` and `Test-CalleeContract` function definitions below once to a single file `SCRATCH\helpers-553.ps1` and dot-source that file (`. "\helpers-553.ps1"`) in every pwsh invocation that calls either helper (P0-T5, P1-T1 through P1-T6, P2-T1, P3-T5). `SCRATCH\helpers-553.ps1` is created at first use, in P0-T5 — the plan's earliest helper invocation is P0-T5's `Test-BlockContained` verification of the reference blocks. + +## No-C#-Toolchain Statement (binding on the executor) + +This feature modifies **no** `*.cs`, `*.csproj`, `*.props`, `*.targets`, or `packages.config` file in its final diff. Therefore: + +- The executor MUST NOT run the C# toolchain (`csharpier`, `msbuild`, `vstest.console.exe`) as verification of this change. There is no C# code change to verify, and a local C# pass would assert nothing about workflow YAML. +- There is **no local test harness for GitHub Actions workflows**. The only local verification available is `actionlint` (which includes YAML parse validation). The authoritative verification is a real green run of the reworked pipeline on the branch head (post-push), per `modified-workflow-needs-green-run`. +- The seeded fault-isolation probes (Phase 4) temporarily commit C# edits and then revert them; the net branch diff over C# files is zero, verified by P5-T3. The probe commits are exercised by CI itself (the analyzer/format/nullable/test gates), not by a local toolchain pass. +- No language with mandatory coverage policy is modified, so no baseline/final-QC coverage capture tasks apply. The coverage-bearing artifact of this pipeline (the `test-results` upload) is preserved unchanged and its continued production is verified in P4-T5. + +## Byte-Identity Verification Method (used by P1-T6 and referenced tasks) + +Reference blocks are extracted from the pre-split `.github/workflows/ci.yml` in Phase 0 (before it is rewritten) into `FEATURE/evidence/other/pre-split/`. A transplanted block passes if the reference text appears as a contiguous substring of the callee file after line-ending normalization (CRLF → LF) — i.e., byte-identical modulo line endings, which git manages via working-tree conversion. Canonical check: + +```powershell +function Test-BlockContained([string]$RefPath, [string]$TargetPath, [string]$Label) { + $ref = (Get-Content -Raw $RefPath) -replace "`r`n", "`n" + $tgt = (Get-Content -Raw $TargetPath) -replace "`r`n", "`n" + if (-not $tgt.Contains($ref.TrimEnd("`n"))) { throw "NOT BYTE-IDENTICAL: $Label" } + Write-Output "BYTE-IDENTICAL: $Label" +} +``` + +Callee files must therefore keep each transplanted step at the same indentation depth as in the monolith (`steps:` under `jobs.`, step names at 6-space indent), which is structurally guaranteed because callee jobs sit at the same YAML nesting depth. + +## Structural Callee Check (referenced by P1-T1 through P1-T5) + +```powershell +function Test-CalleeContract([string]$Path, [int]$Timeout) { + $raw = (Get-Content -Raw $Path) -replace "`r`n", "`n" + if ($raw -notmatch '(?m)^\s+workflow_call:') { throw "missing workflow_call: $Path" } + if ($raw -notmatch '(?m)^\s+workflow_dispatch:') { throw "missing workflow_dispatch: $Path" } + if ($raw -notmatch '(?m)^permissions:') { throw "missing permissions: $Path" } + if ($raw -notmatch "timeout-minutes:\s*$Timeout"){ throw "missing/incorrect timeout: $Path" } + if ($raw -match '(?m)^\s*concurrency:') { throw "forbidden concurrency block: $Path" } + if ($raw -match '(?m)^\s*needs:') { throw "forbidden needs edge: $Path" } + Write-Output "CONTRACT OK: $Path" +} +``` + ## Implementation Plan (Atomic Tasks) -> **Instructions for this section:** -> - Break work into **Phases** (broad buckets) and **Atomic Tasks** (binary, 5-30 min units). -> - Use `- [ ] [P#-T#]` for every task. -> - Start every task with a **strong verb** (Implement, Create, Update, Verify). -> - No "bucket" tasks like "Refactor module" or "Write tests"; split them into specific, verifiable steps. -> - **Self-Validating Phases:** Include necessary test creation/update tasks *within* the phase that implements the code. Do not defer verification to a final "Testing" phase. -> - Include explicit baseline artifact tasks, final-QA artifact tasks, and coverage-comparison tasks for every language in scope when policy requires coverage. -> - Name the expected artifact path or location in each evidence-producing task's acceptance criteria. -> - If any required baseline artifact, QA artifact, or coverage-comparison artifact is missing, the audit verdict must be BLOCKED or INCOMPLETE, never PASS. - -### Phase 0: Compliance & Context -- [ ] [P0-T1] Confirm alignment with repo policies by reading `.github/instructions/general-code-change.instructions.md`, `.github/instructions/python-code-change.instructions.md`, `.github/instructions/general-unit-test.instructions.md`, and `.github/instructions/python-unit-test.instructions.md` before touching code - - Acceptance: Development log contains policy review timestamp prior to Phase 1 commits - -### Phase 1: -- [ ] [P1-T1] -- [ ] [P1-T2] - - Preconditions: - - Acceptance: - -### Phase 2: -- [ ] [P2-T1] -- [ ] [P2-T2] +### Phase 0 — Baseline Capture and Policy Reading + +- [x] [P0-T1] Read the policy documents in the Required References order and write `FEATURE/evidence/other/phase0-instructions-read.md` + - Contents: `Timestamp:`, `Policy Order:` (list in the order read), explicit list of files read (all eleven Required References), and the No-C#-Toolchain Statement restated (no C# source in scope; actionlint is the only local harness; authoritative verification is the green run). + - Acceptance: artifact exists with all required fields; no code file has been modified yet. + +- [x] [P0-T2] Record the git baseline in `FEATURE/evidence/baseline/git-baseline..md` + - Command: `git rev-parse HEAD; git rev-parse --abbrev-ref HEAD; git status --porcelain` + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:` including `BASELINE_SHA: `, `BRANCH: ` (expected: `feature/ci-parallel-job-split-553`), and the porcelain output (docs/evidence and `.claude/agent-memory` entries are expected and permitted; note them, do not gate on them). + - Acceptance: artifact exists; `BASELINE_SHA` and `BRANCH` are recorded for use by later tasks (P3-T2, P3-T4 reference `BRANCH`; diff-scoped verifications reference `BASELINE_SHA`). This is a record, not an invariant that `HEAD` stays at this SHA. + +- [x] [P0-T3] Obtain actionlint 1.7.7 (windows_amd64) into the session scratchpad and run the pre-change lint baseline; write `FEATURE/evidence/baseline/actionlint-baseline..md` + - Command: + ```powershell + $v = '1.7.7'; $dir = 'C:\Users\DANMOI~1\AppData\Local\Temp\claude\C--Users-DanMoisan-repos-TaskMaster-wt-2026-08-14T09-01\012c26d5-57f2-4f08-bc74-bf50a60b1e4e\scratchpad\actionlint-553' + New-Item -ItemType Directory -Force $dir | Out-Null + Invoke-WebRequest "https://github.com/rhysd/actionlint/releases/download/v${v}/actionlint_${v}_windows_amd64.zip" -OutFile "$dir\actionlint.zip" + Expand-Archive "$dir\actionlint.zip" -DestinationPath $dir -Force + & "$dir\actionlint.exe" -no-color + ``` + (run from the repository root; actionlint auto-discovers `.github/workflows/`) + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:` (expected: exit 0, zero findings over `ci.yml` and `codex-web-setup-test.yml`). + - Acceptance: artifact exists with `EXIT_CODE: 0`. If non-zero, halt and report — the pre-change tree must lint clean before decomposition. + +- [x] [P0-T4] Verify the measured sequential baseline artifact exists and record the check in `FEATURE/evidence/baseline/sequential-baseline-check..md` + - Command: `Select-String -Path 'docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md' -Pattern '444s'` + - Acceptance: artifact exists with `EXIT_CODE: 0` and `Output Summary:` quoting at least one matched `444s` line, confirming the comparison denominator for P4-T6. + +- [x] [P0-T5] Snapshot the pre-split `ci.yml` and extract the seven reference blocks into `FEATURE/evidence/other/pre-split/` + - Files created: + - `ci.yml.pre-split.txt` — full copy of the current `.github/workflows/ci.yml` (160 lines). + - `header.txt` — lines 1–15 (`name: CI` through `cancel-in-progress: true`). + - `actionlint-steps.txt` — lines 23–36 (` steps:` through `./actionlint`). + - `format-step.txt` — lines 91–93 (`Verify formatting` step). + - `analyzer-step.txt` — lines 95–101 (analyzer build step incl. `$LASTEXITCODE` guard). + - `nullable-step.txt` — lines 103–116 (nullable build step incl. the full `/t:Rebuild` rationale comment and `$LASTEXITCODE` guard). + - `vstest-step.txt` — lines 118–150 (MSTest step incl. vswhere discovery, `\bin\Debug\`/`\obj\`/`\ref\` filter, zero-assembly `throw`). + - `upload-step.txt` — lines 152–160 (`test-results` upload step incl. `if: always()`). + - Extraction command pattern (0-based `-Index`): `(Get-Content .github/workflows/ci.yml)[22..35] | Set-Content ` etc. Sanity-assert the first line of each extract (`header.txt` → `name: CI`; `actionlint-steps.txt` → ` steps:`; `format-step.txt` → ` - name: Verify formatting`; `analyzer-step.txt` → ` - name: Build with analyzers and code style enforcement`; `nullable-step.txt` → ` - name: Build with nullable warnings treated as errors`; `vstest-step.txt` → ` - name: Run MSTest suite with coverage`; `upload-step.txt` → ` - name: Upload test results`). If any first line mismatches, locate the block by its `- name:` header instead of the line range and correct the extract. + - Verification: run `Test-BlockContained` for each of the seven reference files against the live `.github/workflows/ci.yml`; all seven report `BYTE-IDENTICAL`. + - Acceptance: eight files exist under `FEATURE/evidence/other/pre-split/` and all seven containment checks pass against the pre-change `ci.yml`. + +### Phase 1 — Author Callee Workflows + +> Planner note: setup steps (checkout, setup-dotnet, setup-msbuild, setup-nuget, both caches, `nuget restore`, `dotnet tool restore`) are copied from `ci.yml.pre-split.txt` for consistency, but only the seven Phase 0 reference blocks are gated byte-identical (spec AC 5 covers the gate commands, the actionlint step, and the upload step). File size constraint: every file in this phase is well under the 500-line limit; P5-T4 audits this explicitly. + +- [x] [P1-T1] Create `.github/workflows/_actionlint.yml` + - Contents: `name: actionlint`; `on:` with `workflow_call:` and `workflow_dispatch:` (both bare); `permissions: contents: read`; single job `actionlint` (`name: actionlint`, `runs-on: ubuntu-latest`, `timeout-minutes: 10`); steps block transplanted verbatim from `actionlint-steps.txt` (checkout with `fetch-depth: 1` + download/run actionlint 1.7.7). No `env:`, no `concurrency:`, no `needs:`. + - Verification: `Test-CalleeContract '.github/workflows/_actionlint.yml' 10` passes AND `Test-BlockContained 'FEATURE/evidence/other/pre-split/actionlint-steps.txt' '.github/workflows/_actionlint.yml' 'actionlint-steps'` passes. + +- [x] [P1-T2] Create `.github/workflows/_format-check.yml` + - Contents: `name: format-check`; `on: workflow_call: / workflow_dispatch:`; `permissions: contents: read`; single job `format-check` (`name: Verify formatting`, `runs-on: windows-latest`, `timeout-minutes: 10`); steps copied from the pre-split snapshot: checkout (`fetch-depth: 1`), `Setup .NET SDK` (`actions/setup-dotnet@v4`, `dotnet-version: 10.0.x`), `Cache dotnet tools` (`~/.nuget/packages`, key `dotnet-tools-${{ runner.os }}-${{ hashFiles('dotnet-tools.json') }}`), `Setup CSharpier` (`dotnet tool restore`), then the `Verify formatting` step transplanted verbatim from `format-step.txt`. Deliberately omits `setup-msbuild`, `setup-nuget`, the `packages` cache, and `nuget restore` (spec: CSharpier reads source text only — this is the unverified tailored-setup assumption; fallback is P3-T5). No `env:` block (no step consumes `SOLUTION_PATH`/`BUILD_CONFIGURATION`/`BUILD_PLATFORM`). + - Verification: `Test-CalleeContract '.github/workflows/_format-check.yml' 10` passes AND `Test-BlockContained ... 'format-step.txt' ...` passes AND `Select-String -Path .github/workflows/_format-check.yml -Pattern 'nuget restore'` returns no match. + +- [x] [P1-T3] Create `.github/workflows/_build-analyzers.yml` + - Contents: `name: build-analyzers`; `on: workflow_call: / workflow_dispatch:`; `permissions: contents: read`; single job `build-analyzers` (`name: Build with analyzers and code style enforcement`, `runs-on: windows-latest`, `timeout-minutes: 30`); job-level `env:` replicating the monolith values `SOLUTION_PATH: TaskMaster.sln`, `BUILD_CONFIGURATION: Debug`, `BUILD_PLATFORM: Any CPU`; steps copied from the snapshot: checkout (`fetch-depth: 1`), `Setup MSBuild` (`microsoft/setup-msbuild@v2`), `Setup NuGet` (`nuget/setup-nuget@v2`, `nuget-version: latest`), `Cache NuGet packages` (`packages`, key `nuget-${{ runner.os }}-${{ hashFiles('**/packages.config') }}`), `Restore solution` (`nuget restore $env:SOLUTION_PATH`), then the analyzer gate step transplanted verbatim from `analyzer-step.txt`. Deliberately omits `setup-dotnet`, the dotnet-tools cache, and `dotnet tool restore` (tailored-setup assumption; fallback P3-T5). + - Verification: `Test-CalleeContract '.github/workflows/_build-analyzers.yml' 30` passes AND `Test-BlockContained ... 'analyzer-step.txt' ...` passes AND `Select-String -Path .github/workflows/_build-analyzers.yml -Pattern 'setup-dotnet'` returns no match. + +- [x] [P1-T4] Create `.github/workflows/_build-nullable.yml` + - Contents: identical shape to `_build-analyzers.yml` (`name: build-nullable`; job `build-nullable`, `name: Build with nullable warnings treated as errors`, `timeout-minutes: 30`, same `env:`, same setup steps), then the nullable gate step transplanted verbatim from `nullable-step.txt` — including the complete `/t:Rebuild` rationale comment and the `$LASTEXITCODE` guard. + - Verification: `Test-CalleeContract '.github/workflows/_build-nullable.yml' 30` passes AND `Test-BlockContained ... 'nullable-step.txt' ...` passes AND `Select-String -Path .github/workflows/_build-nullable.yml -Pattern '/t:Rebuild'` matches (comment and command both present via the containment check). + +- [x] [P1-T5] Create `.github/workflows/_mstest-coverage.yml` + - Contents: `name: mstest-coverage`; `on: workflow_call: / workflow_dispatch:`; `permissions: contents: read`; single job `mstest-coverage` (`name: Run MSTest suite with coverage`, `runs-on: windows-latest`, `timeout-minutes: 30`); same `env:` and setup steps as `_build-analyzers.yml`; then a NEW plain build step (not transplanted): + ```yaml + - name: Build solution + shell: pwsh + run: | + & msbuild $env:SOLUTION_PATH /t:Build /m /p:Configuration=Debug ` + "/p:Platform=Any CPU" + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + ``` + (no analyzer or warning-promotion properties, per spec); then the MSTest step transplanted verbatim from `vstest-step.txt`; then the upload step transplanted verbatim from `upload-step.txt` (`test-results`, `if: always()`, same paths, `if-no-files-found: warn`). + - Verification: `Test-CalleeContract '.github/workflows/_mstest-coverage.yml' 30` passes AND `Test-BlockContained ... 'vstest-step.txt' ...` passes AND `Test-BlockContained ... 'upload-step.txt' ...` passes AND the plain-build step contains neither `EnableNETAnalyzers` nor `TreatWarningsAsErrors` (`Select-String` on the file returns exactly zero matches for each of those two strings). + +- [x] [P1-T6] Record the byte-identity evidence artifact `FEATURE/evidence/qa-gates/byte-identity..md` + - Command: run `Test-BlockContained` for all six gated blocks against their callee files (`actionlint-steps` → `_actionlint.yml`; `format-step` → `_format-check.yml`; `analyzer-step` → `_build-analyzers.yml`; `nullable-step` → `_build-nullable.yml`; `vstest-step` and `upload-step` → `_mstest-coverage.yml`) and capture the six `BYTE-IDENTICAL:` output lines. + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:` listing all six `BYTE-IDENTICAL` results, plus the line-ending-normalization note from the method section. + - Acceptance: artifact exists; all six checks pass; this is the evidence pointer for spec AC 5. + +### Phase 2 — Orchestrator Rewrite, Workflows README, and Local Lint + +- [x] [P2-T1] Rewrite `.github/workflows/ci.yml` as a pure orchestrator + - Contents: the file begins with the 15 header lines byte-identical to `header.txt` (`name: CI`, unchanged `on:` triggers, `permissions: contents: read`, unchanged `concurrency` block with `cancel-in-progress: true`), followed by a `jobs:` block containing exactly five jobs and nothing else: + ```yaml + jobs: + actionlint: + name: actionlint + uses: ./.github/workflows/_actionlint.yml + format-check: + name: format-check + uses: ./.github/workflows/_format-check.yml + build-analyzers: + name: build-analyzers + uses: ./.github/workflows/_build-analyzers.yml + build-nullable: + name: build-nullable + uses: ./.github/workflows/_build-nullable.yml + mstest-coverage: + name: mstest-coverage + uses: ./.github/workflows/_mstest-coverage.yml + ``` + No inline `steps:`, no `needs:` edges, no job-level `env:`. + - Verification (all four must hold): + 1. `Test-BlockContained 'FEATURE/evidence/other/pre-split/header.txt' '.github/workflows/ci.yml' 'header'` passes and the header is the start of the file. + 2. `Select-String -Path .github/workflows/ci.yml -Pattern 'steps:'` returns no match. + 3. `Select-String -Path .github/workflows/ci.yml -Pattern 'needs:'` returns no match. + 4. `(Select-String -Path .github/workflows/ci.yml -Pattern 'uses: \./\.github/workflows/_').Count` equals 5. + +- [x] [P2-T2] Create `.github/workflows/README.md` + - Contents (sections required): + 1. `## Pipeline overview` — orchestrator + five callees table (file, runner, gate, timeout), zero `needs:` edges, caller-owned concurrency group, callees declare none. + 2. `## Per-stage workflow_dispatch procedure` — how to re-run one gate standalone: `gh workflow run _.yml --ref ` (or the Actions UI), note that a standalone dispatch forms its own run outside the CI concurrency group. + 3. `## Branch-protection rename procedure` — the research Q8 sequence: green run on the PR head → capture exact check-run names via `gh api repos/drmoisan/TaskMaster/commits//check-runs --jq '.check_runs[].name'` → single atomic `PUT /repos/drmoisan/TaskMaster/rulesets/18572843` carrying the full writable object (`name`, `target`, `enforcement`, `bypass_actors`, `conditions`, `rules`) with `strict_required_status_checks_policy: true` retained and read-only GET fields stripped → merge immediately → verify by GET. Include the rollback (single PUT restoring the previous contexts set). + 4. `## Rules` — pointers to `.claude/rules/ci-workflows.md` (deliberately-failing nested command pattern) and `.claude/rules/benchmark-baselines.md`. + - Verification: `Select-String -Path .github/workflows/README.md -Pattern 'workflow_dispatch procedure','Branch-protection rename procedure'` matches both headings; file under 500 lines. + +- [x] [P2-T3] Run actionlint over the post-change workflow set and write `FEATURE/evidence/qa-gates/actionlint-postchange..md` + - Command: `& "\actionlint-553\actionlint.exe" -no-color` from the repository root (lints all seven files: `ci.yml`, five `_*.yml`, `codex-web-setup-test.yml`; README is not lintable YAML). + - Loop rule: if actionlint reports findings, fix the workflow files and re-run from this command until exit 0; re-run P1-T6 and P2-T1 verifications after any fix that touches a gated block. + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:`. + - Acceptance: artifact exists with `EXIT_CODE: 0` on the final pass. + +### Phase 3 — Commit, Pull Request, and First Green Run + +- [ ] [P3-T1] Commit the workflow change set and Phase 0–2 evidence + - Files staged (explicit): `.github/workflows/ci.yml`, `.github/workflows/_actionlint.yml`, `.github/workflows/_format-check.yml`, `.github/workflows/_build-analyzers.yml`, `.github/workflows/_build-nullable.yml`, `.github/workflows/_mstest-coverage.yml`, `.github/workflows/README.md`, `FEATURE/evidence/**` (new artifacts), `FEATURE/plan.2026-08-14T09-05.md` (checkbox progress). + - Verification: `git status --porcelain` shows no unstaged modifications to `.github/**` or `FEATURE/**` after the commit; `git log -1 --stat` lists the seven workflow-tree files. + +- [ ] [P3-T2] Push the branch to origin + - Command: `git push -u origin ` where `` is the value recorded by P0-T2 (expected: `feature/ci-parallel-job-split-553`). `-u` is correct: the remote branch does not yet exist. Do not hard-code any other branch name. + - Acceptance: push succeeds (exit 0). Note: `ci.yml` does not trigger on push to this branch (`on: push` is main/development only); the pipeline runs on the `pull_request` event after P3-T3. + +- [ ] [P3-T3] **ORCHESTRATOR CONFIRMATION REQUIRED — do not execute autonomously.** Create the pull request to `main` following the `pr-author` skill + - Gate: the hook `.claude/hooks/enforce-pr-author-skill.ps1` blocks `gh pr create --body-file` unless ALL THREE preconditions hold, and two of them are outside the executor's capability: + 1. `artifacts/pr_context.summary.txt` exists, written by `mcp__drm-copilot__collect_pr_context` — this MCP tool is in the orchestrator's tool surface, not the executor's. + 2. `artifacts/orchestration/orchestrator-state.json` passes validation with `--require-pr-creation-ready` — the orchestrator writes this checkpoint; the executor never does. + 3. `artifacts/pr_body_553.md` AND `artifacts/pr_body_553.receipt.json` exist at exactly those canonical paths, with a matching, non-stale SHA-256 receipt per `.claude/skills/pr-author/SKILL.md`. + The executor MUST halt at this task and hand off to the orchestrator to satisfy preconditions 1 and 2 and confirm execution; record the confirmation in the P3-T4 artifact. Without recorded confirmation this task is BLOCKED, not skipped. + - Procedure (after confirmation and preconditions): produce `artifacts/pr_body_553.md` plus `artifacts/pr_body_553.receipt.json` per the `pr-author` skill, then `gh pr create --base main --title "CI: split quality-gates into five parallel reusable-workflow jobs (#553)" --body-file artifacts/pr_body_553.md`. + - Acceptance: PR exists targeting `main`; `gh pr view --json url,headRefOid` returns the PR URL and head SHA; the PR's own run executes the NEW pipeline (head-ref workflow files run for `pull_request` events, research Q8 fact 1). Expected and acceptable: the PR is blocked by the still-required old context `Format, build, analyze, and test` until Phase 6 (fail-closed over-blocking, never under-gating). + +- [ ] [P3-T4] Observe the first run of the split pipeline to completion and record `FEATURE/evidence/qa-gates/first-run..md` + - Commands: + ```powershell + gh run list --branch --workflow ci.yml --limit 1 --json databaseId,headSha,status,conclusion + gh run watch --exit-status + gh api repos/drmoisan/TaskMaster/actions/runs//jobs --jq '.jobs[] | {name, conclusion, started_at, completed_at}' + ``` + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, run id, head SHA, per-job names and conclusions, `Output Summary:`. + - Branching (explicit): (a) all five jobs succeed → record `GREEN` and proceed to P3-T5's NOT-REQUIRED branch; (b) a job fails with a symptom attributable to a trimmed setup step (e.g., missing .NET SDK / `dotnet` not found in an msbuild job, missing restored packages in the format job) → proceed to P3-T5's REQUIRED branch; (c) a job fails for any other cause → halt and report to the orchestrator with the run URL; do not improvise fixes outside this plan. + - Acceptance: artifact exists and records one of the three branch outcomes explicitly. + +- [ ] [P3-T5] Execute the tailored-setup fallback if and only if P3-T4 took branch (b); record `FEATURE/evidence/qa-gates/tailored-setup-fallback..md` + - REQUIRED branch: restore into the affected callee(s) only the specific setup steps the failure implicates, copied verbatim from `FEATURE/evidence/other/pre-split/ci.yml.pre-split.txt` (msbuild callees: `Setup .NET SDK`; format callee: `Setup NuGet` + `Cache NuGet packages` + `Restore solution`). Re-run P1-T6 containment checks and P2-T3 actionlint, commit (`fix(ci): restore to — tailored-setup assumption failed`), push, and repeat P3-T4 observation until branch (a) or (c). Record which steps were restored, to which files, and the final green run id. Spec authorizes this fallback at an estimated ~56s/job cost. + - NOT-REQUIRED branch (explicitly authorized skip): if P3-T4 recorded `GREEN`, write the artifact with `Result: NOT REQUIRED — tailored-setup assumption held` and the green run id. This is the only permitted non-executing outcome for this task. + - Acceptance: artifact exists recording exactly one branch; the pipeline is green on the current head at task completion. + +### Phase 4 — Seeded Fault-Isolation Probes and Post-Split Timing + +> Probe rules: each probe is one temporary commit on the PR branch, exercised by CI, then reverted with `git revert --no-edit`. Wait for the probe run to complete BEFORE pushing the revert (`cancel-in-progress: true` would otherwise cancel the probe run). Each probe must fail exactly the targeted gate; if a probe reddens more than one gate, adjust the probe edit and repeat before recording. Net C# diff after reverts is zero (verified in P5-T3). + +- [ ] [P4-T1] [expect-fail] Exercise the formatting-violation probe and record `FEATURE/evidence/regression-testing/probe-format..md` + - Probe edit: a formatting-only change in one `*.cs` file that `csharpier check` rejects (e.g., broken indentation inside one method body); it must introduce no compiler diagnostic. Commit message: `probe(553): formatting violation — to be reverted`. + - Sequence: commit → push → `gh run watch ` → `gh api .../runs//jobs` → assert `format-check` job conclusion `failure` and the other four jobs `success` → `git revert --no-edit ` → push. + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, probe commit SHA, revert commit SHA, run URL, per-job conclusion table, `Output Summary: exactly one red gate (format-check)`. + - Acceptance: artifact shows exactly the format gate red; revert commit exists on the branch. Update spec.md seeded-condition checkbox 3 to `[x]` with this artifact as the evidence pointer. + +- [ ] [P4-T2] [expect-fail] Exercise the nullable-violation probe and record `FEATURE/evidence/regression-testing/probe-nullable..md` + - Probe edit: in a production `*.cs` file that carries `#nullable enable`, in a project whose `.csproj` does NOT set `TreatWarningsAsErrors` (verify with `Select-String` on the csproj before committing), add a correctly-formatted statement producing a nullable-flow warning (e.g., `string probeValue = null;` assigned to a non-nullable local). This fails only the nullable gate (`/p:TreatWarningsAsErrors=true`); the analyzer gate and the MSTest job's plain build treat it as a warning. Commit message: `probe(553): nullable violation — to be reverted`. + - Sequence and contents: same shape as P4-T1; assert `build-nullable` conclusion `failure`, other four `success`; revert and push. + - Acceptance: artifact shows exactly the nullable gate red; revert commit exists. Update spec.md seeded-condition checkbox 4 with this artifact as evidence. + +- [ ] [P4-T3] [expect-fail] Exercise the test-failure probe and record `FEATURE/evidence/regression-testing/probe-mstest..md` + - Probe edit: invert one assertion in one existing fast MSTest test (not `TestCategory=LiveOutlook`), keeping the file csharpier-clean and free of new compiler diagnostics. Commit message: `probe(553): deliberate test failure — to be reverted`. + - Sequence and contents: same shape as P4-T1; assert `mstest-coverage` conclusion `failure`, other four `success`; revert and push. + - Acceptance: artifact shows exactly the MSTest gate red; revert commit exists. Update spec.md seeded-condition checkbox 5 with this artifact as evidence. + +- [ ] [P4-T4] Confirm a green run on the post-revert head and record `FEATURE/evidence/qa-gates/post-probe-green-run..md` + - Commands: `gh run watch --exit-status` on the run triggered by the final revert push; `gh api repos/drmoisan/TaskMaster/actions/runs//jobs --jq '.jobs[] | {name, conclusion}'`. + - Acceptance: artifact records run id, head SHA, and all five job conclusions `success`. + +- [ ] [P4-T5] Verify the `test-results` artifact on the green run and record `FEATURE/evidence/qa-gates/test-results-artifact..md` + - Command: `gh api repos/drmoisan/TaskMaster/actions/runs//artifacts --jq '.artifacts[] | {name, size_in_bytes}'` (run id from P4-T4). + - Acceptance: an artifact named exactly `test-results` exists with non-zero size. Update spec.md seeded-condition checkbox 6 with this artifact as evidence. + +- [ ] [P4-T6] Capture post-split per-job timings and write the baseline comparison `FEATURE/evidence/qa-gates/ci-split-timing-comparison..md` + - Command: `gh api repos/drmoisan/TaskMaster/actions/runs//jobs` (same method as the baseline capture; run id from P4-T4). + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`; a per-job table (name, started, completed, duration); measured pipeline wall clock (latest `completed_at` minus earliest `started_at` across the five jobs); comparison row against the measured 444s baseline from `ci-sequential-baseline.2026-08-14T13-05.md` with absolute and percentage delta; summed billed `windows-latest` seconds vs the ~444s baseline; a runner-environment-parity statement (both measurements GitHub-hosted `windows-latest`, satisfying `.claude/rules/benchmark-baselines.md`); a note that the spec's ~277s/~333s figures were estimates and this artifact is the measurement of record. + - Acceptance: artifact exists with the comparison table populated from live API data (no placeholder values). Update spec.md seeded-condition checkbox 7 with this artifact as evidence. + +### Phase 5 — Final QA Loop, Acceptance Reconciliation, and Pre-Migration Commit + +> QA-loop note: the applicable language is GitHub Actions YAML. The loop is: (1) formatting — no repo-mandated YAML formatter exists; not applicable; (2) linting — actionlint (P5-T1); (3) type checking — not applicable to YAML; (4) testing — the live green run (P4-T4, re-confirmed on the final head in P5-T15). If any task in this phase changes a workflow file, re-run from P5-T1 and re-run the P1-T6 containment checks. Per the No-C#-Toolchain Statement, csharpier/msbuild/vstest are not part of this loop and must not be run. + +- [ ] [P5-T1] Run the final actionlint pass and record `FEATURE/evidence/qa-gates/actionlint-final..md` + - Command: `& "\actionlint-553\actionlint.exe" -no-color` from the repository root (re-download per P0-T3 if the scratchpad was cleared). + - Acceptance: `EXIT_CODE: 0` over all seven workflow files. Update spec.md seeded-condition checkbox 1 with this artifact as evidence. + +- [ ] [P5-T2] Verify `$LASTEXITCODE` hygiene across the pwsh-bearing workflow files and record `FEATURE/evidence/qa-gates/lastexitcode-review..md` + - Scope: six of the seven workflow files are enumerated — the five callees plus `ci.yml` (the orchestrator has no steps). `.github/workflows/codex-web-setup-test.yml` is explicitly EXCLUDED from enumeration because it declares no `shell: pwsh` or `shell: powershell` step, so the `.claude/rules/ci-workflows.md` pattern cannot apply to it; the artifact must state this exclusion and reason so the recorded enumeration is consistent with its scope statement. + - Method: enumerate every `shell: pwsh` step in the five callees and `ci.yml` (the orchestrator has none); confirm (a) no step intentionally invokes a failing nested command (the `.claude/rules/ci-workflows.md` pattern is therefore not triggered — matches research Q9), and (b) both msbuild gate guards `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` and the vstest `throw` are present, via `Select-String -Path .github/workflows/_build-analyzers.yml,.github/workflows/_build-nullable.yml -Pattern 'if \(\$LASTEXITCODE -ne 0\) \{ exit \$LASTEXITCODE \}'` (2 matches) and `Select-String -Path .github/workflows/_mstest-coverage.yml -Pattern 'throw "MSTest execution failed'` (1 match) and `Select-String -Path .github/workflows/_mstest-coverage.yml -Pattern 'throw "No test assemblies found'` (1 match). + - Acceptance: artifact records the step-by-step table and the four match counts. Update spec.md seeded-condition checkbox 8 with this artifact as evidence. + +- [ ] [P5-T3] Verify the branch contains zero C#/project-file changes and record `FEATURE/evidence/qa-gates/no-csharp-diff..md` + - Command (two statements — PowerShell does not concatenate a subexpression with a trailing `..HEAD` into one argument): + ```powershell + $base = git merge-base origin/main HEAD + git diff --name-only "$base..HEAD" -- '*.cs' '*.csproj' '*.props' '*.targets' '**/packages.config' '**/app.config' + ``` + (the `**/` prefix is required on `packages.config` and `app.config`: a git pathspec with no wildcard is anchored to the repo root and would match zero files; `*.cs` and the other extension globs already match at any depth) + - Acceptance: output is empty (probe commits are fully cancelled by their reverts). The artifact restates the No-C#-Toolchain Statement as the justification for the absence of a C# toolchain pass. If output is non-empty, halt and report — an unreverted probe or scope drift exists. + +- [ ] [P5-T4] Audit file sizes and record `FEATURE/evidence/qa-gates/file-size-audit..md` + - Command: `Get-ChildItem .github/workflows/*.yml, .github/workflows/README.md | ForEach-Object { "{0}`t{1}" -f $_.Name, (Get-Content $_.FullName).Count }` + - Acceptance: every listed file is under 500 lines (expected: each callee < 100, `ci.yml` ~32, README < 150). + +- [ ] [P5-T5] Link the new workflows README from the feature folder + - Edit: append to `FEATURE/issue.md` a `## References` entry: `- Workflows README: `.github/workflows/README.md` (created by #553)`. + - Acceptance: `Select-String -Path FEATURE/issue.md -Pattern 'workflows/README.md'` matches (satisfies the spec DoD "created and linked from the feature folder" clause, completed by P7-T6). + +- [ ] [P5-T6] Check off spec.md acceptance criterion 1 (four gates as separate jobs, zero `needs:` edges) and its mirrors (issue.md AC 1, user-story.md AC 1) + - Evidence pointers: `.github/workflows/ci.yml` (P2-T1 verification 3), `FEATURE/evidence/qa-gates/post-probe-green-run..md`. + - Acceptance: all three checkboxes `[x]`, each citing the evidence paths. + +- [ ] [P5-T7] Check off spec.md acceptance criterion 2 (five callee workflows with the reusable-workflow contract) and its mirrors (issue.md AC 2, user-story.md AC 2) + - Evidence pointers: the five P1 task verifications (`Test-CalleeContract` results), `FEATURE/evidence/qa-gates/actionlint-final..md`. + - Acceptance: all three checkboxes `[x]` with evidence paths. + +- [ ] [P5-T8] Check off spec.md acceptance criterion 3 (`ci.yml` orchestrator, no inline `steps:`) and its mirrors (issue.md AC 3, user-story.md AC 3) + - Evidence pointers: P2-T1 verification outputs (no `steps:` match, five `uses:` references). + - Acceptance: all three checkboxes `[x]` with evidence paths. + +- [ ] [P5-T9] Check off spec.md acceptance criterion 4 (no cross-job file sharing; `test-results` upload preserved) and its mirrors (issue.md AC 4, user-story.md AC 4) + - Evidence pointers: `FEATURE/evidence/qa-gates/byte-identity..md` (upload block), `FEATURE/evidence/qa-gates/test-results-artifact..md`. + - Acceptance: all three checkboxes `[x]` with evidence paths. + +- [ ] [P5-T10] Check off spec.md acceptance criterion 5 (gate commands and actionlint step byte-identical, incl. `/t:Rebuild` comment, `$LASTEXITCODE` guards, zero-assembly `throw`) + - Evidence pointers: `FEATURE/evidence/qa-gates/byte-identity..md`, `FEATURE/evidence/qa-gates/lastexitcode-review..md`. + - Acceptance: spec checkbox `[x]` with evidence paths (no issue/user-story mirror carries this criterion standalone; their AC 8 equivalents are handled in P7-T3). + +- [ ] [P5-T11] Check off spec.md acceptance criterion 7 (README documents dispatch + rename procedures) and its mirrors (issue.md AC 6, user-story.md AC 6) + - Evidence pointers: `.github/workflows/README.md` (P2-T2 heading verification). + - Acceptance: all three checkboxes `[x]` with evidence paths. + +- [ ] [P5-T12] Check off spec.md acceptance criterion 8 (green run against the branch head, `modified-workflow-needs-green-run`) and its mirrors (issue.md AC 7, user-story.md AC 7) + - Evidence pointers: `FEATURE/evidence/qa-gates/post-probe-green-run..md` (superseded by P5-T15's final-head confirmation if additional commits landed in this phase). + - Acceptance: all three checkboxes `[x]` with evidence paths. + +- [ ] [P5-T13] Check off spec.md acceptance criterion 10 (post-split wall clock measured with the baseline's collection method and recorded) + - Evidence pointers: `FEATURE/evidence/qa-gates/ci-split-timing-comparison..md`. + - Acceptance: spec checkbox `[x]` with evidence path. + +- [ ] [P5-T14] Commit and push the Phase 3–5 evidence and document updates + - Files staged (explicit): `FEATURE/evidence/**` (new artifacts from Phases 3–5), `FEATURE/spec.md`, `FEATURE/issue.md`, `FEATURE/user-story.md`, `FEATURE/plan.2026-08-14T09-05.md`. + - Verification: `git status --porcelain` scoped to `FEATURE/**` and `.github/**` is empty after commit; push succeeds. + +- [ ] [P5-T15] Confirm a green run on the final pre-migration head and record `FEATURE/evidence/qa-gates/pre-migration-green..md` + - Commands: `gh run watch --exit-status` on the run triggered by P5-T14's push; `gh api repos/drmoisan/TaskMaster/actions/runs//jobs --jq '.jobs[] | {name, conclusion}'`; `gh pr view --json headRefOid`. + - Acceptance: all five jobs `success` on the current PR head SHA; artifact records run id and head SHA. This head is the reference state for the ruleset migration. + +- [ ] [P5-T16] Capture the exact check-run context names from the final head SHA into `FEATURE/evidence/other/check-run-names..md` + - Command: `gh api repos/drmoisan/TaskMaster/commits//check-runs --jq '.check_runs[].name'` (head SHA from P5-T15). + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, the verbatim name list, and the selected five required-context strings (the five CI contexts of the form ` / `, plus/including the relocated actionlint context — captured, never assumed; research Q8 fact 3). + - Acceptance: artifact exists with exactly five selected context strings, each copied verbatim from the API output. This file is committed in P7-T9 (committing it now would advance the head; the context names are derived from workflow/job names and do not vary by SHA). + +### Phase 6 — Required-Status-Check Ruleset Migration (Orchestrator-Gated) + +> Sequencing note: this phase runs only after P5-T15 (green run on the final head) and P5-T16 (names captured from that head). It mutates the repository's merge policy on `main` and is outward-facing. Phases 6–7 modify no source files; the Phase 5 QA loop therefore remains the final code-verification pass. If any workflow file changes after Phase 5, return to P5-T1. + +- [ ] [P6-T1] Capture the pre-PUT ruleset and record `FEATURE/evidence/other/ruleset-pre-put..json` plus `FEATURE/evidence/other/ruleset-pre-put..md` + - Command: `gh api repos/drmoisan/TaskMaster/rulesets/18572843 > /evidence/other/ruleset-pre-put..json` + - Contents (md record): `Timestamp:`, `Command:`, `EXIT_CODE:`, `Output Summary:` confirming the current required contexts are exactly `actionlint` and `Format, build, analyze, and test` and `strict_required_status_checks_policy` is `true`. + - Acceptance: both files exist; the JSON is the unmodified GET response. + +- [ ] [P6-T2] Construct the atomic PUT payload `FEATURE/evidence/other/ruleset-put-payload..json` and verify it, recording checks in `FEATURE/evidence/other/ruleset-payload-verification..md` + - Tooling note: `jq` is not installed in this environment; all JSON manipulation in this task uses `ConvertFrom-Json` / `ConvertTo-Json`, and every `ConvertTo-Json` call MUST state `-Depth 20` explicitly — the default depth of 2 truncates the nested ruleset object and would silently corrupt the PUT payload. + - Construction (PowerShell): + ```powershell + $rs = Get-Content -Raw '/evidence/other/ruleset-pre-put..json' | ConvertFrom-Json + $payload = [ordered]@{ + name = $rs.name; target = $rs.target; enforcement = $rs.enforcement + bypass_actors = $rs.bypass_actors; conditions = $rs.conditions; rules = $rs.rules + } + $rule = $payload.rules | Where-Object { $_.type -eq 'required_status_checks' } + $rule.parameters.required_status_checks = @() + # strict_required_status_checks_policy is carried over unchanged from the GET (must remain $true) + $payload | ConvertTo-Json -Depth 20 | Set-Content '/evidence/other/ruleset-put-payload..json' + ``` + Never a partial patch; never a remove-then-add two-step. + - Verification commands (all must pass, recorded in the md artifact; `$p = Get-Content -Raw '' | ConvertFrom-Json`): + 1. Read-only fields stripped: `$p.PSObject.Properties.Name` contains none of `id`, `node_id`, `created_at`, `updated_at`, `_links`, `source`, `source_type`, `current_user_can_bypass`. + 2. Exactly five contexts: `(($p.rules | Where-Object { $_.type -eq 'required_status_checks' }).parameters.required_status_checks).Count` equals `5`. + 3. Strict policy retained: `($p.rules | Where-Object { $_.type -eq 'required_status_checks' }).parameters.strict_required_status_checks_policy` is `$true`. + 4. Diff-only-in-contexts: load the pre-PUT JSON and the payload as objects; build the pre-PUT projection with the identical ordered literal used in the construction block — `[ordered]@{ name = $rs.name; target = $rs.target; enforcement = $rs.enforcement; bypass_actors = $rs.bypass_actors; conditions = $rs.conditions; rules = $rs.rules }` — same keys, same order; a plain `@{}` hashtable is PROHIBITED here because its key order is unspecified and `ConvertTo-Json` would emit the six keys in arbitrary order, producing a spurious diff between semantically identical documents; on BOTH objects set the `required_status_checks` rule's `parameters.required_status_checks` to `$null`; serialize BOTH with the same `ConvertTo-Json -Depth 20` call form (identical parameters, so `git diff --no-index` compares canonically) to `SCRATCH\proj-pre.json` and `SCRATCH\proj-new.json` (the session scratchpad, NOT the feature folder); `git diff --no-index SCRATCH\proj-pre.json SCRATCH\proj-new.json` must be empty (exit 0). + 5. Every context string in the payload appears verbatim in `check-run-names..md`. + - Acceptance: payload file and verification artifact exist; all five checks pass. + +- [ ] [P6-T3] **ORCHESTRATOR CONFIRMATION REQUIRED — do not execute autonomously.** Apply the single atomic ruleset PUT + - Gate: this task mutates the `main` merge policy. The executor MUST halt at this task and obtain explicit confirmation from the orchestrator before running the command. Record the confirmation (who/when/what was confirmed) in the P6-T4 artifact. Without recorded confirmation this task is BLOCKED, not skipped. + - Command: `gh api --method PUT repos/drmoisan/TaskMaster/rulesets/18572843 --input /evidence/other/ruleset-put-payload..json` + - Acceptance: command exits 0. The old context `Format, build, analyze, and test` is replaced by the five captured contexts in one request with no under-gating window (research Q8 fact 4). + +- [ ] [P6-T4] Verify the post-PUT ruleset by GET and record `FEATURE/evidence/other/ruleset-post-put..json` plus `FEATURE/evidence/other/ruleset-post-put..md` + - Command: `gh api repos/drmoisan/TaskMaster/rulesets/18572843 > /evidence/other/ruleset-post-put..json` + - Verification (PowerShell; `jq` is not installed): `$post = Get-Content -Raw '/evidence/other/ruleset-post-put..json' | ConvertFrom-Json; $req = ($post.rules | Where-Object { $_.type -eq 'required_status_checks' }).parameters; $req.required_status_checks.context` returns exactly the five intended context strings (set equality with P5-T16's selection) and `$req.strict_required_status_checks_policy` is `$true`. Any re-serialization performed while recording this check MUST use `ConvertTo-Json -Depth 20`. + - Contents (md record): `Timestamp:`, `Command:`, `EXIT_CODE:`, the orchestrator confirmation record from P6-T3, the five-context verification result, and the rollback procedure (single PUT restoring `ruleset-pre-put..json`'s writable projection). + - Acceptance: both files exist; context set matches exactly. + +- [ ] [P6-T5] Record the merge-readiness handoff in `FEATURE/evidence/other/migration-handoff..md` + - Contents: statement that the PUT has landed and every other open PR now over-blocks until this PR merges (fail-closed, research Q8); instruction that the orchestrator must merge the split PR immediately, updating the branch first if `strict_required_status_checks_policy` requires it; note that the merge itself is orchestrator-owned and outside executor scope; pointers to all Phase 6 evidence files. + - Acceptance: artifact exists; the orchestrator is notified in the executor's status output that the merge is the immediate next action. + +### Phase 7 — Post-Merge Verification and Evidence Wrap-Up (Orchestrator-Gated) + +> Timing gate: execute this phase only after the orchestrator has merged the split PR into `main`. If the session ends before the merge, record each remaining task's artifact with `Result: DEFERRED — awaiting merge` and the reason (this deferral branch is explicitly authorized for Phase 7 only). Evidence produced in this phase lands on `main` via a follow-up commit (P7-T9) because the PR is already merged. + +- [ ] [P7-T1] **ORCHESTRATOR CONFIRMATION REQUIRED — do not execute autonomously.** Run the standalone `workflow_dispatch` smoke of each callee on `main` and record `FEATURE/evidence/qa-gates/dispatch-smoke..md` + - Merge precondition (recorded before the first dispatch): `gh pr view --json state,mergedAt,mergeCommit` must return `state == MERGED`; record the output in the artifact. If not merged, this task is in the DEFERRED branch (phase header), not executable. + - Commands (for each of the five callees, after the precondition and recorded confirmation): `gh workflow run _.yml --ref main`, then `gh run list --workflow _.yml --limit 1 --json databaseId,status,conclusion` and `gh run watch --exit-status`. + - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, the merge-precondition output, a five-row table (callee, run id, conclusion). + - Acceptance: all five standalone dispatches conclude `success`. Update spec.md seeded-condition checkbox 2 with this artifact as evidence. + +- [ ] [P7-T2] Check off spec.md acceptance criterion 6 (atomic ruleset PUT with captured contexts and recorded evidence) and its mirrors (issue.md AC 5, user-story.md AC 5) + - Evidence pointers: `FEATURE/evidence/other/ruleset-pre-put..json`, `ruleset-put-payload..json`, `ruleset-post-put..md`, `check-run-names..md`. + - Acceptance: all three checkboxes `[x]` with evidence paths. + +- [ ] [P7-T3] Check off spec.md acceptance criterion 9 (no gate dropped, weakened, or made non-required) and its mirrors (issue.md AC 8, user-story.md AC 8) + - Evidence pointers: `FEATURE/evidence/qa-gates/byte-identity..md` (commands unweakened), `FEATURE/evidence/other/ruleset-post-put..md` (all five contexts required). + - Acceptance: all three checkboxes `[x]` with evidence paths. + +- [ ] [P7-T4] Check off spec.md Definition of Done item 1 (acceptance criteria delivered and individually verified) + - Evidence pointers: P5-T6 through P5-T13, P7-T2, P7-T3 (all ten spec ACs `[x]`). + - Acceptance: DoD checkbox 1 `[x]`; every spec AC checkbox is `[x]` with an evidence pointer. + +- [ ] [P7-T5] Check off spec.md Definition of Done item 2 (seeded test conditions exercised and outcomes recorded) + - Evidence pointers: seeded checkboxes 1–8 updated by P5-T1, P7-T1, P4-T1, P4-T2, P4-T3, P4-T5, P4-T6, P5-T2 respectively. + - Acceptance: DoD checkbox 2 `[x]`; all eight seeded-condition checkboxes are `[x]`. + +- [ ] [P7-T6] Check off spec.md Definition of Done item 3 (README created and linked from the feature folder) + - Evidence pointers: `.github/workflows/README.md` (P2-T2), `FEATURE/issue.md` References entry (P5-T5). + - Acceptance: DoD checkbox 3 `[x]`. + +- [ ] [P7-T7] Check off spec.md Definition of Done item 4 (evidence committed under the feature evidence tree) + - Evidence pointers: `FEATURE/evidence/other/` (ruleset before/payload/after, check-run names, migration handoff), `FEATURE/evidence/qa-gates/` (green-run references, timing comparison), all staged for P7-T9. + - Acceptance: DoD checkbox 4 `[x]`; every evidence file named by this plan exists on disk. + +- [ ] [P7-T8] Check off spec.md Definition of Done item 5 (no C# toolchain pass required) + - Evidence pointers: `FEATURE/evidence/qa-gates/no-csharp-diff..md` (P5-T3). + - Acceptance: DoD checkbox 5 `[x]`. + +- [ ] [P7-T9] **ORCHESTRATOR CONFIRMATION REQUIRED — do not execute autonomously.** Commit residual evidence and write the final status summary `FEATURE/evidence/other/final-status..md` + - Files staged: `FEATURE/evidence/other/check-run-names..md`, all Phase 6 evidence files, all Phase 7 evidence files, updated `FEATURE/spec.md` / `issue.md` / `user-story.md` / `plan.2026-08-14T09-05.md`. + - Contents of the summary: pointers to every evidence artifact produced by this plan, the measured wall-clock delta vs the 444s baseline, the final ruleset state, and any deferred Phase 7 items with reasons. + - Delivery: because the PR is merged, this commit lands via the orchestrator's follow-up mechanism (direct commit to `main` or a small follow-up PR, per orchestrator policy). The executor prepares the commit and reports; the push/PR decision is orchestrator-owned. + - Acceptance: `git status --porcelain` scoped to `FEATURE/**` is clean after the commit; the summary artifact exists. ## Test Plan -- Unit: ... -- Integration: ... -- Manual/CLI: ... -- Coverage evidence: list baseline artifact paths, post-change artifact paths, and comparison artifact paths for each in-scope language +- **Unit:** not applicable — no C# source is modified (see the No-C#-Toolchain Statement). No coverage baseline/final capture applies because no coverage-bearing language is in scope. +- **Static verification (local):** actionlint 1.7.7 over all seven workflow files (P0-T3 baseline, P2-T3 post-change, P5-T1 final); byte-identity containment checks over the six transplanted gate blocks (P1-T6); orchestrator structural checks (P2-T1). +- **Integration (authoritative):** the live pipeline itself — first green run (P3-T4, fallback P3-T5), three seeded fault-isolation probes with exactly-one-red-gate assertions (P4-T1..T3), post-revert green run (P4-T4), artifact continuity (P4-T5), standalone dispatch smoke of each callee post-merge (P7-T1). +- **Performance evidence:** post-split per-job timings vs the measured 444s baseline, same collection method, runner-environment parity (P4-T6). +- **Merge-policy migration:** pre-PUT GET, payload verification (five checks), orchestrator-confirmed atomic PUT, post-PUT GET set-equality verification (P6-T1..T4). ## Open Questions / Notes -- ... +- **Tailored-setup assumption is unverified** until the first green run (spec risk 2). P3-T4/P3-T5 handle both outcomes explicitly; the fallback restores only the implicated steps, verbatim from the pre-split snapshot. +- **Context-name capture is mandatory, never assumed** (highest-likelihood failure, research Q8/Q10 risk 1). The plan's caller/callee job names in P2-T1 are authoring inputs, not migration inputs; only P5-T16's captured strings enter the PUT payload. +- **Measured timings will differ from estimates** (~277s target / ~333s worst case are estimates); P4-T6 records the measurement of record without gating on a numeric threshold. +- **Cross-run account-level runner contention** can erode realized speedup and is not determinable from repository data (research Q7); note it in P4-T6 if the measured wall clock is anomalous. +- **Validator status:** this planner's tool surface has no Bash or MCP access; `mcp__drm-copilot__validate_orchestration_artifacts` was NOT run. A structural self-check was performed (phase headings `### Phase N — `, digit-only sequential task IDs per phase, checkbox task format). The orchestrator must run the validator and the `atomic-executor` preflight (`DIRECTIVE: PREFLIGHT VALIDATION ONLY`) against this exact file path before execution, and all revisions must be made to this same file. From 26b9f7b596b63456291de67005fc421404141ded Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 10:39:07 -0400 Subject: [PATCH 04/13] =?UTF-8?q?probe(553):=20formatting=20violation=20?= =?UTF-8?q?=E2=80=94=20to=20be=20reverted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs index 586bed50..663be4d6 100644 --- a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs +++ b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs @@ -7,5 +7,5 @@ namespace UtilitiesCS { - public class IntelligenceFilters { } + public class IntelligenceFilters { } } From 6f73cf43452b3a3d5dd0e176033e8e60c7b78fd2 Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 10:44:17 -0400 Subject: [PATCH 05/13] =?UTF-8?q?Revert=20"probe(553):=20formatting=20viol?= =?UTF-8?q?ation=20=E2=80=94=20to=20be=20reverted"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5a6068955422f1f114af6332bd1083e1d3a68341. --- UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs index 663be4d6..586bed50 100644 --- a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs +++ b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs @@ -7,5 +7,5 @@ namespace UtilitiesCS { - public class IntelligenceFilters { } + public class IntelligenceFilters { } } From 5dba92062eeecb647a263f2d686b1d757ea0150b Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 10:45:43 -0400 Subject: [PATCH 06/13] =?UTF-8?q?probe(553):=20nullable=20violation=20?= =?UTF-8?q?=E2=80=94=20to=20be=20reverted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs index 586bed50..33d0e294 100644 --- a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs +++ b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs @@ -7,5 +7,8 @@ namespace UtilitiesCS { - public class IntelligenceFilters { } + public class IntelligenceFilters + { + public static string ProbeValue() => null; + } } From d8da0a0750cb1d64c4b1238a11c9f06d6a1dbe32 Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 10:52:53 -0400 Subject: [PATCH 07/13] =?UTF-8?q?Revert=20"probe(553):=20nullable=20violat?= =?UTF-8?q?ion=20=E2=80=94=20to=20be=20reverted"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit fc4f2be6dbc79f627c10961660312e6a9da5e2a8. --- UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs index 33d0e294..586bed50 100644 --- a/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs +++ b/UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs @@ -7,8 +7,5 @@ namespace UtilitiesCS { - public class IntelligenceFilters - { - public static string ProbeValue() => null; - } + public class IntelligenceFilters { } } From d372c5e9ebefbe33cd87c78cfa3031c8e4a0cc92 Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 10:54:36 -0400 Subject: [PATCH 08/13] =?UTF-8?q?probe(553):=20deliberate=20test=20failure?= =?UTF-8?q?=20=E2=80=94=20to=20be=20reverted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs b/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs index 3567b07d..2a271267 100644 --- a/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs +++ b/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs @@ -20,7 +20,7 @@ public void ExtToChar_CurrentlyExposesNoPublicMethods() .ToArray(); // Act / Assert - publicStaticMethods.Should().BeEmpty(); + publicStaticMethods.Should().NotBeEmpty(); } } } From 1437ea09d0b77888a246d917cd7e5374d21b21c3 Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 11:01:33 -0400 Subject: [PATCH 09/13] =?UTF-8?q?Revert=20"probe(553):=20deliberate=20test?= =?UTF-8?q?=20failure=20=E2=80=94=20to=20be=20reverted"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a55ccdfc2e7980125ab36c76a84c2d59b29fb8e6. --- UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs b/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs index 2a271267..3567b07d 100644 --- a/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs +++ b/UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs @@ -20,7 +20,7 @@ public void ExtToChar_CurrentlyExposesNoPublicMethods() .ToArray(); // Act / Assert - publicStaticMethods.Should().NotBeEmpty(); + publicStaticMethods.Should().BeEmpty(); } } } From 5db4a6b53162e34d1cf78bf04659ce9948fa11f3 Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 11:18:49 -0400 Subject: [PATCH 10/13] docs(553): record Phase 3-5 evidence, review artifacts, and AC check-offs Captures the evidence produced while validating the parallel CI split on live runs, plus the feature-review artifacts and the two minor review-finding fixes. Evidence added: - qa-gates: first-run, tailored-setup-fallback (NOT REQUIRED - assumption held), post-probe-green-run, test-results-artifact, ci-split-timing-comparison, actionlint-final, lastexitcode-review, no-csharp-diff, file-size-audit - regression-testing: the three seeded fault-isolation probes, each showing exactly one red gate and each fully reverted - other: ac-checkoff-register recording the evidence pointer for every acceptance criterion checked off in Phase 5 Review-finding fixes: - F2: .github/workflows/README.md used the context form "CI / <gate>". Corrected to the verified "<caller job> / <callee job>" form and added the five observed context strings verbatim, with a caution to capture rather than hand-write them. - F3: added baseline.provenance.json beside the 444s sequential baseline and post-split-timing.provenance.json beside the post-split measurement, each recording runner_class, host_signature, and workflow_run_url per .claude/rules/benchmark-baselines.md. Acceptance criteria: spec 1-5, 7, 8, 10 checked; user-story and issue mirrors updated. Spec AC 6 and 9 (and their mirrors) remain open pending the ruleset PUT, which is orchestrator-gated. Seeded conditions 1 and 3-8 checked; condition 2 awaits the post-merge standalone dispatch smoke. No C# source, project, or build file is changed by this branch; verified by no-csharp-diff against the merge base. Refs #553 --- .github/workflows/README.md | 21 +- .../code-review.2026-08-14T10-21.md | 39 ++++ .../baseline/baseline.provenance.json | 15 ++ .../ac-checkoff-register.2026-08-14T11-14.md | 70 +++++++ .../actionlint-final.2026-08-14T11-14.md | 66 ++++++ ...plit-timing-comparison.2026-08-14T11-10.md | 143 +++++++++++++ .../file-size-audit.2026-08-14T11-14.md | 59 ++++++ .../qa-gates/first-run.2026-08-14T10-35.md | 96 +++++++++ .../lastexitcode-review.2026-08-14T11-14.md | 103 +++++++++ .../no-csharp-diff.2026-08-14T11-14.md | 88 ++++++++ .../post-probe-green-run.2026-08-14T11-10.md | 67 ++++++ .../post-split-timing.provenance.json | 27 +++ ...ailored-setup-fallback.2026-08-14T10-35.md | 59 ++++++ .../test-results-artifact.2026-08-14T11-10.md | 65 ++++++ .../probe-format.2026-08-14T10-44.md | 118 +++++++++++ .../probe-mstest.2026-08-14T11-01.md | 141 +++++++++++++ .../probe-nullable.2026-08-14T10-52.md | 140 +++++++++++++ .../feature-audit.2026-08-14T10-21.md | 87 ++++++++ .../issue.md | 18 +- .../plan.2026-08-14T09-05.md | 47 +++-- .../policy-audit.2026-08-14T10-21.md | 197 ++++++++++++++++++ .../remediation-inputs.2026-08-14T10-21.md | 40 ++++ .../spec.md | 30 +-- .../user-story.md | 12 +- 24 files changed, 1695 insertions(+), 53 deletions(-) create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T10-21.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/baseline.provenance.json create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ac-checkoff-register.2026-08-14T11-14.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-final.2026-08-14T11-14.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/file-size-audit.2026-08-14T11-14.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/first-run.2026-08-14T10-35.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/lastexitcode-review.2026-08-14T11-14.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/no-csharp-diff.2026-08-14T11-14.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-probe-green-run.2026-08-14T11-10.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/tailored-setup-fallback.2026-08-14T10-35.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/test-results-artifact.2026-08-14T11-10.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-format.2026-08-14T10-44.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-mstest.2026-08-14T11-01.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-nullable.2026-08-14T10-52.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T10-21.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T10-21.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T10-21.md diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 8f976738..a1c97dd3 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -79,9 +79,24 @@ Two caveats: a subsequent push, and it will not cancel an in-flight CI run. 2. **A standalone dispatch does not update a pull request's required checks.** It produces its own run with its own check; it does not re-report the - `CI / <gate>` context that branch protection requires. To turn a required - context green, re-run the failed job from the pull request's Checks tab, which - re-runs it within the `ci.yml` run. + `<caller job> / <callee job>` context that branch protection requires. To turn + a required context green, re-run the failed job from the pull request's Checks + tab, which re-runs it within the `ci.yml` run. + +The required context names take the form `<caller job id> / <callee job name>` — +the job id used in `ci.yml`, then the `name:` of the job inside the callee. The +five contexts this pipeline reports are, verbatim: + +``` +actionlint / actionlint +format-check / Verify formatting +build-analyzers / Build with analyzers and code style enforcement +build-nullable / Build with nullable warnings treated as errors +mstest-coverage / Run MSTest suite with coverage +``` + +Do not hand-write these strings when editing branch protection; capture them from +a live run as described in the next section. ## Branch-protection rename procedure diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T10-21.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T10-21.md new file mode 100644 index 00000000..5ef6541c --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T10-21.md @@ -0,0 +1,39 @@ +# Code Review — ci-parallel-job-split (Issue #553) + +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T10-21) +- **Base:** `main` @ merge base `2073f717bbfac30053f3d6a4e652d99af3ae5c9c` +- **Head:** `feature/ci-parallel-job-split-553` @ `0b016c81a78f3fafc0864de472f4139cc0938002` +- **Scope:** full branch diff (37 files); primary review surface is the six workflow YAML files and `.github/workflows/README.md` + +## Executive Summary + +The change decomposes the monolithic `quality-gates` CI job into five callee reusable workflows behind a pure-orchestrator `ci.yml`, following the repository's mandated reusable-workflow pattern. Implementation quality is high: every transplanted gate `run:` block is byte-identical to its merge-base counterpart (independently re-verified by this review via extraction and SHA-256 comparison — 14/14 blocks match), the structural invariants hold (zero `needs:` edges, no callee `concurrency`, caller-owned concurrency group unchanged, single preserved `test-results` upload, no `download-artifact`), actionlint reports zero findings across all seven workflow files, and the new plain `Build solution` step in `_mstest-coverage.yml` carries no analyzer or warning-promotion properties and therefore neither weakens nor duplicates any gate. + +One blocking finding exists, and it is procedural rather than a code defect: the branch modifies `.github/workflows/**` and the unconditional `modified-workflow-needs-green-run` policy rule requires evidence of a green workflow run against the branch head, which cannot exist until the branch is pushed and a run executes (plan Phases 3–5, deliberately unexecuted). Two minor documentation findings and two informational observations follow. The under-gating analysis for the required-status-check migration is favorable: because the `main` ruleset is strict and fail-closed, every state this branch can produce before the ruleset PUT over-blocks rather than under-gates; the spec and README document the atomic-PUT migration correctly. + +Blocking findings: **1** (F1). Non-blocking: 2 Minor, 2 Info. + +## Findings Table + +| Severity | File | Location | Finding | Recommendation | Rationale | Evidence | +| --- | --- | --- | --- | --- | --- | --- | +| Blocking | `.github/workflows/**` (7 files) | branch-level | `modified-workflow-needs-green-run` fires: workflow files changed and no green workflow run against head `0b016c81` exists or is evidenced | Execute plan Phases 3–5: push branch, open PR (P3-T3, orchestrator-confirmation-required), observe first split-pipeline run to green (P3-T4), record `evidence/qa-gates/first-run.<TS>.md`; a green `workflow_dispatch` run against the head also satisfies the rule | The rule is unconditional for CI-gate-modifying diffs (`.claude/skills/feature-review-workflow/SKILL.md` § Policy Rules); local actionlint cannot substitute for a runner execution, and the tailored-setup assumption (msbuild jobs without `setup-dotnet`; format job without `nuget restore`) is explicitly unverified until the first green run | `git diff --name-only` matches `.github/workflows/**`; no run evidence in feature folder; plan P3-T1..P3-T4 unchecked; spec Residual risk 2 | +| Minor | `.github/workflows/README.md` | line 82 (dispatch caveat 2) | Caveat refers to "the `CI / <gate>` context that branch protection requires," but the check-run context name for a called-workflow job takes the form `<caller job name> / <callee job name>` (e.g., `format-check / Verify formatting`); the workflow name `CI` is a UI grouping, not part of the context string | Reword to the `<caller job> / <callee job>` form used by the spec's Required-Status-Check Contract term 1 and by README step 2 | Internally inconsistent naming in the migration-critical document; harm is bounded because both documents mandate capturing exact names from a live run rather than assuming them, but the migration doc should not model the wrong form | README line 82 vs README lines 105–107 and spec.md § Required-Status-Check Contract term 1 | +| Minor | `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` | file-level | Committed measured baseline has no sibling `baseline.provenance.json` | No action required now. If this baseline is ever consumed by an automated benchmark regression gate, add the sibling provenance file (runner_class, host_signature, workflow_run_url) first | `.claude/rules/benchmark-baselines.md` scopes its rejection conditions to baselines "consumed by a benchmark regression gate"; this baseline is a one-time comparison reference for spec AC 10 and records runner class, run URL, and collection command inline, satisfying runner-environment parity | Rule § Scope; baseline header lines 1–8; policy audit § 3.3 | +| Info | `.github/workflows/_actionlint.yml` | lines 24–29 | actionlint 1.7.7 tarball is downloaded from GitHub releases without checksum verification | None within this feature (the block is transplanted byte-identically and byte-identity is an acceptance criterion); consider checksum pinning as a separate hardening follow-up | Pre-existing pattern moved verbatim; changing it here would violate spec AC 5 (byte-identity) | Byte-identity verification, block `actionlint-steps` (SHA-256 match vs merge base) | +| Info | `artifacts/pr_context.summary.txt` (generated, not committed) | Close candidates section | Author-asserted autoclose list contains spurious tokens `#ISO-8601` and `#SHA-256` harvested from prose | When the PR body is authored via the `pr-author` skill, list only #553 as the closing issue | Prevents malformed `Closes #` references in the PR; generator quirk, not a branch defect | Summary lines under "Auto-close issues (author asserted)" | + +## Assessed With No Finding + +- **Gate-equivalence of the new `Build solution` step** (`_mstest-coverage.yml` L47–52): zero occurrences of `EnableNETAnalyzers`, `EnforceCodeStyleInBuild`, or `TreatWarningsAsErrors` in the file; the step exists solely so the MSTest job can discover `*.Test.dll` without inheriting build output, carries the standard `$LASTEXITCODE` guard, and cannot mask an analyzer/nullable failure because those gates report as independent required contexts. No gate is weakened, altered, or duplicated. +- **`.claude/rules/ci-workflows.md` compliance:** all `pwsh` steps assessed individually (policy audit § 3.2); none uses the deliberately-failing nested-command pattern, so no exit-code reset is required. The `$LASTEXITCODE` guards propagate gate failures by design. +- **Under-gating analysis:** with `strict_required_status_checks_policy: true`, the old contexts (`actionlint`, `Format, build, analyze, and test`) never report after this change, which blocks merges (fail-closed) until the documented atomic PUT lands. No state reachable from this branch under-gates `main`. The two-step remove-then-add edit — the only genuine under-gating hazard — is explicitly prohibited in both spec.md and the README. +- **Concurrency semantics:** the caller-owned group and `cancel-in-progress: true` are unchanged; callees declare no `concurrency` block (grep-verified), avoiding the undocumented callee-level semantics identified by research Q6. +- **Permissions:** every callee declares its own `permissions: contents: read`, identical to the caller; no elevation. +- **File sizes:** all workflow files 29–96 lines; well under the 500-line limit. Markdown files exempt. No test files changed (the test-file 500-line check is not triggered). +- **vstest discovery filter discipline:** the `\bin\Debug\` match with `\obj\`/`\ref\` exclusions is preserved verbatim (spec invariant 8); the local stale-worktree discovery hazard noted in issue.md is unchanged in CI because `$env:GITHUB_WORKSPACE` scopes discovery to the runner checkout. +- **Archival promoted-potential copies and agent-memory files:** documentation-only; consistent with the promotion lifecycle; no code impact. + +## Typed-Python Review + +Not applicable — no Python files changed on this branch. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/baseline.provenance.json b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/baseline.provenance.json new file mode 100644 index 00000000..18f2ac52 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/baseline/baseline.provenance.json @@ -0,0 +1,15 @@ +{ + "baseline_file": "ci-sequential-baseline.2026-08-14T13-05.md", + "issue": 553, + "metric": "ci_pipeline_wall_clock_seconds", + "value_seconds": 444, + "runner_class": "windows-latest", + "host_signature": "github-hosted:windows-latest:runner-group=GitHub Actions", + "workflow_run_url": "https://github.com/drmoisan/TaskMaster/actions/runs/31749877507", + "captured_at": "2026-08-14T13:05:16Z", + "captured_from_head_sha": "d5e4a99687f22ecf14b88a206db623d85fe9a97f", + "captured_from_branch": "bug/coverage-threshold-policy-reconciliation-494", + "collection_command": "gh api repos/drmoisan/TaskMaster/actions/runs/31749877507/jobs", + "measured_job": "Format, build, analyze, and test", + "notes": "Pre-split sequential baseline. Runner-captured on a GitHub-hosted windows-latest runner, not a developer workstation, satisfying the runner-environment-parity requirement of .claude/rules/benchmark-baselines.md. This is a workflow-run timing record, not a BenchmarkDotNet result, so it carries no HostEnvironmentInfo block and the 'Unknown processor' rejection condition does not apply; host identity is established by the runner label and the workflow_run_url above. Compared against post-split measurements in ../qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md." +} diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ac-checkoff-register.2026-08-14T11-14.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ac-checkoff-register.2026-08-14T11-14.md new file mode 100644 index 00000000..8bb22f35 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ac-checkoff-register.2026-08-14T11-14.md @@ -0,0 +1,70 @@ +# Acceptance-Criteria Check-Off Register — Issue #553 + +- Timestamp: 2026-08-14T11-14 (local) / 2026-08-14T15:14:22Z (UTC) +- Tasks: [P5-T6] through [P5-T13] +- Work Mode: `full-feature` → authoritative AC sources are `spec.md` and + `user-story.md`; `issue.md` is tracked as a third mirror because the plan's + check-off tasks name it. + +## Method note — why evidence paths are recorded here, not in the AC text + +Each [P5-T6]…[P5-T13] task says the checkboxes should cite their evidence paths. +`.claude/skills/acceptance-criteria-tracking/SKILL.md` rule 3 is explicit that +check-off must change only `- [ ]` to `- [x]` and must **not** modify criterion +text, and rule 5 forbids adding content to AC source files. Those rules govern +the AC files. This register is therefore the citation surface: the AC files carry +the checkbox state, and this artifact carries the evidence pointer for each one. +No criterion text was altered in any of the three files. + +## Check-offs applied by this plan's Phase 5 + +| Plan task | spec.md | user-story.md | issue.md | Evidence | +| --- | --- | --- | --- | --- | +| [P5-T6] | AC 1 | AC 1 | AC 1 | `.github/workflows/ci.yml` ([P2-T1] verification 3: zero `needs:` matches; verification 4: five `uses:` references) + `evidence/qa-gates/post-probe-green-run.2026-08-14T11-10.md` (five independent jobs, all started within 1s of each other) | +| [P5-T7] | AC 2 | AC 2 | AC 2 | The five `Test-CalleeContract` results in [P1-T1]–[P1-T5] (each asserts `workflow_call`, `workflow_dispatch`, own `permissions:`, right-sized `timeout-minutes`, no `concurrency:`, no `needs:`) + `evidence/qa-gates/actionlint-final.2026-08-14T11-14.md` | +| [P5-T8] | AC 3 | AC 3 | AC 3 | [P2-T1] verification outputs: `steps:` match count 0, `needs:` match count 0, callee `uses:` count 5, header byte-identical at offset 0 | +| [P5-T9] | AC 4 | AC 4 | AC 4 | `evidence/qa-gates/byte-identity.2026-08-14T09-54.md` (block `upload-step`, SHA-256 `894b0ce75a70c838…`) + `evidence/qa-gates/test-results-artifact.2026-08-14T11-10.md` (single `test-results` artifact, 8,246,282 bytes) | +| [P5-T10] | AC 5 | — | — | `evidence/qa-gates/byte-identity.2026-08-14T09-54.md` (6/6 blocks, matching SHA-256 digests, 12/12 critical fragments) + `evidence/qa-gates/lastexitcode-review.2026-08-14T11-14.md` | +| [P5-T11] | AC 7 | AC 6 | AC 6 | `.github/workflows/README.md` ([P2-T2] heading verification; both required headings present at L52 and L101) | +| [P5-T12] | AC 8 | AC 7 | AC 7 | `evidence/qa-gates/post-probe-green-run.2026-08-14T11-10.md` — run 31812508684 on head `ad28ea81`, all five jobs `success`. Superseded by [P5-T15]'s final-head confirmation if further commits land. | +| [P5-T13] | AC 10 | — | — | `evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md` + sibling `post-split-timing.provenance.json` | + +`spec.md` AC 5 and AC 10 have no issue/user-story mirror; those documents' AC 8 +equivalents are handled by [P7-T3]. + +## Check-offs already applied by `feature-review` + +The reviewer (artifacts dated 2026-08-14T10-21) had already checked spec ACs +1, 2, 3, 4, 5, 7 and user-story ACs 1, 2, 3, 4, 6 as PASS in its feature-audit, +which the AC-tracking skill authorises reviewers to do. Phase 5 verified those +states rather than re-applying them, and supplied the `issue.md` mirrors, which +the reviewer had not touched. The evidence table above is the citation for all of +them regardless of who set the checkbox. + +## Deliberately still unchecked + +| Criterion | Files | Why | Cleared by | +| --- | --- | --- | --- | +| Ruleset `required_status_checks` replaced in one atomic PUT | spec AC 6, user-story AC 5, issue AC 5 | The PUT has not been performed. It is orchestrator-gated ([P6-T3]) and requires explicit user confirmation that has not been given. | [P7-T2] after Phase 6 | +| Every gate still enforced; no check dropped, weakened, or made non-required | spec AC 9, user-story AC 8, issue AC 8 | The "still required" half of this criterion depends on the post-PUT ruleset state. The "not weakened" half is already evidenced by the byte-identity artifact and the three fault-isolation probes, but the criterion is not fully satisfied until the five contexts are actually required on `main`. | [P7-T3] after Phase 6 | + +Both are correctly blocked on Phase 6, which is outside this execution segment. + +## Current tally + +| Source | Total | Checked | Remaining | +| --- | --- | --- | --- | +| `spec.md` | 10 | 8 | 2 (AC 6, AC 9) | +| `user-story.md` | 8 | 6 | 2 (AC 5, AC 8) | +| `issue.md` | 8 | 6 | 2 (AC 5, AC 8) | + +Spec seeded test conditions: 8 total, 7 checked (1, 3, 4, 5, 6, 7, 8). Condition 2 +("Each `_<name>.yml` is independently dispatchable via `workflow_dispatch` and +succeeds standalone") remains unchecked; it is [P7-T1]'s post-merge dispatch +smoke. Note that partial evidence already exists: every run in Phases 3–5 was +started via `gh workflow run ci.yml`, proving `workflow_dispatch` works on the +orchestrator, but the criterion is about each **callee** dispatched standalone, +which has not been exercised. + +Spec Definition of Done: 5 items, 0 checked — all are [P7-T4]…[P7-T8], outside +this segment. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-final.2026-08-14T11-14.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-final.2026-08-14T11-14.md new file mode 100644 index 00000000..ef93d9c6 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/actionlint-final.2026-08-14T11-14.md @@ -0,0 +1,66 @@ +# actionlint Final Pass — Issue #553 + +- Timestamp: 2026-08-14T11-14 (local) / 2026-08-14T15:14:22Z (UTC) +- Task: [P5-T1] + +Command (run from the repository root): + +```powershell +& "<SCRATCH>\actionlint-553\actionlint.exe" -no-color +``` + +EXIT_CODE: 0 + +## Output Summary + +**Exit 0, zero findings across all seven workflow files.** actionlint produced no +output, its clean result form. Confirmed with `-verbose`: + +``` +verbose: Collected 7 YAML files +verbose: Found 0 errors in 7 files +``` + +Files linted: `ci.yml`, `_actionlint.yml`, `_format-check.yml`, +`_build-analyzers.yml`, `_build-nullable.yml`, `_mstest-coverage.yml`, and the +untouched `codex-web-setup-test.yml`. `.github/workflows/README.md` is not +lintable YAML and is correctly outside the file set. + +## Why this pass was re-run + +This final pass follows a Phase 5 documentation change: review finding F2 was +fixed in `.github/workflows/README.md` (the `CI / <gate>` context-name phrasing +was corrected to the verified `<caller job> / <callee job>` form and the five +observed context strings were added verbatim). That file is not YAML and is not +linted, but the Phase 5 QA-loop rule requires re-running the lint stage after any +change under `.github/workflows/`, so the pass was repeated rather than assumed. + +**No workflow YAML file changed after [P2-T3].** The five callees and `ci.yml` +are byte-identical to their [P1-T1]–[P1-T5] and [P2-T1] authored state, so the +byte-identity results in `byte-identity.2026-08-14T09-54.md` and the structural +results in [P2-T1] remain valid without re-verification. + +## Lint history for this change + +| Pass | Task | Files | Findings | Exit | +| --- | --- | --- | --- | --- | +| Pre-change baseline | [P0-T3] | 2 | 0 | 0 | +| Post-change | [P2-T3] | 7 | 0 | 0 | +| Final | [P5-T1] (this artifact) | 7 | 0 | 0 | + +## Local-versus-CI scope + +The local run reports `Rule "pyflakes" was disabled` because that optional +integration is not installed on this Windows host. CI runs actionlint on +`ubuntu-latest`, where its shellcheck and pyflakes integrations may be available, +making the CI lint a superset of this one. That superset has been exercised: the +`actionlint / actionlint` job concluded `success` on every green run of this +branch, including run 31812508684 on head `ad28ea81`. Local and CI lint therefore +agree. + +## Acceptance ([P5-T1]) + +- `EXIT_CODE: 0` over all seven workflow files. +- Spec seeded-condition checkbox 1 ("`actionlint` passes against every new and + modified workflow file") is checked off with this artifact as the evidence + pointer, corroborated by the green `actionlint / actionlint` job in CI. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md new file mode 100644 index 00000000..0aa88e14 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md @@ -0,0 +1,143 @@ +# Post-Split Timing vs Measured Baseline — Issue #553 + +- Timestamp: 2026-08-14T11-10 (local) / 2026-08-14T15:10Z (UTC) +- Task: [P4-T6] +- Measurement of record: run **31812508684** (the [P4-T4] post-probe green run) +- Comparison denominator: **444s**, from + `evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` + +Command (identical collection method to the baseline capture): + +``` +gh api repos/drmoisan/TaskMaster/actions/runs/31812508684/jobs +``` + +EXIT_CODE: 0 + +## Per-job timings — measurement of record (run 31812508684, head `ad28ea81`) + +| Job | Runner | Started | Completed | Duration | +| --- | --- | --- | --- | --- | +| `actionlint / actionlint` | ubuntu-latest | 15:02:37Z | 15:03:11Z | 34s | +| `format-check / Verify formatting` | windows-latest | 15:02:38Z | 15:05:03Z | 145s | +| `build-nullable / Build with nullable warnings treated as errors` | windows-latest | 15:02:37Z | 15:05:29Z | 172s | +| `build-analyzers / Build with analyzers and code style enforcement` | windows-latest | 15:02:38Z | 15:06:03Z | 205s | +| `mstest-coverage / Run MSTest suite with coverage` | windows-latest | 15:02:38Z | 15:09:50Z | 432s | + +**Measured pipeline wall clock: 433s** — latest `completed_at` (15:09:50Z) minus +earliest `started_at` (15:02:37Z). + +## Comparison against the 444s baseline + +| Metric | Baseline (measured) | Measurement of record | Absolute delta | Percentage delta | +| --- | --- | --- | --- | --- | +| Pipeline wall clock | 444s | 433s | −11s | **−2.5%** | +| Billed `windows-latest` seconds | 444s | 954s | +510s | **+215%** (2.15x) | + +Billed `windows-latest` seconds are the sum of the four Windows jobs +(145 + 172 + 205 + 432 = 954s). The `ubuntu-latest` actionlint job is excluded +from the Windows total; it ran concurrently in the baseline as well. + +## Second green sample, and why both are reported + +A single post-split run is a single sample, and so is the baseline. Two green +post-split runs of **byte-identical workflow files** were captured during this +work, and they differ substantially: + +| Run | Head | actionlint | format | nullable | analyzers | mstest | Wall clock | Windows billed | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 31809697953 ([P3-T4] first green) | `0b016c81` | 36s | 131s | 188s | 186s | 259s | **259s** | 764s | +| 31812508684 ([P4-T4], measurement of record) | `ad28ea81` | 34s | 145s | 172s | 205s | 432s | **433s** | 954s | + +Reduction against the 444s baseline is **41.7%** on the first sample and **2.5%** +on the second. Both are reported because selecting only the favourable sample +would misrepresent the result. + +### Diagnosis of the variance: uniform runner slowness, not a structural change + +Step-level comparison of the critical-path `mstest-coverage` job across the two +runs isolates where the additional 173s went: + +| Step | Run 31809697953 | Run 31812508684 | Ratio | +| --- | --- | --- | --- | +| Set up job | 2s | 2s | 1.0x | +| Checkout repository | 40s | 44s | 1.1x | +| Setup MSBuild | 0s | 2s | — | +| Setup NuGet | 1s | 1s | 1.0x | +| Cache NuGet packages | 18s | 18s | 1.0x | +| Restore solution | 12s | 63s | 5.3x | +| Build solution | 91s | 149s | 1.6x | +| Run MSTest suite with coverage | 84s | 138s | 1.6x | +| Upload test results | 2s | 3s | 1.5x | +| Post-steps | 7s | 9s | 1.3x | + +Every compute-bound step scaled by a similar factor (~1.6x for both the build and +the test execution), while the fixed-cost steps (job setup, cache restore, +checkout) were essentially unchanged. That signature is characteristic of a +slower hosted-runner instance or contended host I/O, not of a workflow-structure +difference: the two runs executed identical YAML, and the `Restore solution` +outlier (5.3x) points to NuGet network variance on top of the general slowdown. + +**Conclusion drawn:** the split's structural benefit is real and is visible in +both samples — four gates that were strictly serial now run concurrently, so +wall clock is bounded by the single slowest gate rather than by their sum. The +*magnitude* of the wall-clock reduction is dominated by hosted-runner variance in +the MSTest job, which is the critical path in every sample. On a fast runner the +reduction approaches the spec's ~38% estimate; on a slow runner it can approach +zero, because the critical-path job alone can grow to the size of the entire +former serial pipeline. No numeric threshold is gated on this measurement, as the +plan specifies. + +## Comparison against the spec's estimates + +The spec's Expected Outcomes were explicitly labelled estimates, not +measurements. Recorded here for completeness: + +| Metric | Spec target (estimate) | Spec worst case (estimate) | Measured, run A | Measured, run B | +| --- | --- | --- | --- | --- | +| Wall clock | ~277s | ~333s | 259s | 433s | +| Billed Windows seconds | ~763s | ~962s | 764s | 954s | + +The billed-seconds estimates proved accurate to within ~1%: 764s measured against +a ~763s estimate on the first run, 954s against a ~962s worst case on the second. +The wall-clock estimates bracketed the first sample well (259s, slightly better +than the ~277s target) but did not anticipate the second sample's runner +variance. **This artifact is the measurement of record; the spec's figures remain +estimates.** + +## Runner-environment parity (`.claude/rules/benchmark-baselines.md`) + +Both sides of the comparison were captured on GitHub-hosted runners of the same +class, so the parity requirement is satisfied: + +| | Baseline run 31749877507 | Measurement run 31812508684 | +| --- | --- | --- | +| Gate job runner label | `windows-latest` | `windows-latest` (×4) | +| Lint job runner label | `ubuntu-latest` | `ubuntu-latest` | +| Runner group | GitHub Actions (GitHub-hosted) | GitHub Actions (GitHub-hosted) | +| Collection method | `gh api .../runs/<id>/jobs` | `gh api .../runs/<id>/jobs` | + +Neither measurement was taken on a developer workstation, so the rule's +prohibition on workstation-versus-runner comparison is not engaged. The rule's +`"Unknown processor"` rejection condition applies to BenchmarkDotNet baseline JSON +carrying a `HostEnvironmentInfo` block; this artifact is a workflow-run timing +record whose provenance is the run URL and collection command, and a sibling +provenance file is recorded alongside it +(`post-split-timing.provenance.json`) per the rule's provenance requirement. + +## Cross-run contention caveat (research Q7) + +Realized speedup can be eroded by account-level runner contention, which is not +determinable from repository data. Per-run demand is 5 concurrent jobs (4 × +`windows-latest`, 1 × `ubuntu-latest`), below every GitHub plan's concurrency +ceiling, and in both samples all five jobs started within 2 seconds of each other +— so **neither sample was queued**, and queueing is excluded as the cause of the +run-B slowdown. The slowdown is within-job execution time. + +## Acceptance ([P4-T6]) + +- Artifact exists with the comparison table populated from live API data; no + placeholder values. +- Spec seeded-condition checkbox 7 ("Total wall-clock duration of the reworked + pipeline is measured against the current sequential baseline and recorded as + evidence") is checked off with this artifact as the evidence pointer. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/file-size-audit.2026-08-14T11-14.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/file-size-audit.2026-08-14T11-14.md new file mode 100644 index 00000000..bd1abb77 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/file-size-audit.2026-08-14T11-14.md @@ -0,0 +1,59 @@ +# File-Size Audit — Issue #553 + +- Timestamp: 2026-08-14T11-14 (local) / 2026-08-14T15:14:22Z (UTC) +- Task: [P5-T4] +- Governing rule: `.claude/rules/general-code-change.md` § File Size Limit — no + production code, test code, or reusable script file may exceed **500 lines**. + +Command: + +```powershell +Get-ChildItem .github/workflows/*.yml, .github/workflows/README.md | + ForEach-Object { "{0}`t{1}" -f $_.Name, (Get-Content $_.FullName).Count } +``` + +EXIT_CODE: 0 + +## Output Summary + +| File | Lines | Limit | Margin | Status | +| --- | --- | --- | --- | --- | +| `_actionlint.yml` | 29 | 500 | 471 | PASS | +| `ci.yml` | 32 | 500 | 468 | PASS | +| `_format-check.yml` | 41 | 500 | 459 | PASS | +| `_build-analyzers.yml` | 53 | 500 | 447 | PASS | +| `_build-nullable.yml` | 60 | 500 | 440 | PASS | +| `_mstest-coverage.yml` | 96 | 500 | 404 | PASS | +| `codex-web-setup-test.yml` (untouched) | 110 | 500 | 390 | PASS | +| `README.md` | 195 | 500 | 305 | PASS | + +**Every listed file is under 500 lines.** The largest authored file is +`README.md` at 195 lines; the largest workflow file is `_mstest-coverage.yml` at +96 lines. Markdown documentation is exempt from the limit under the rule's stated +exceptions, so `README.md` would pass regardless, but it is measured and reported +rather than assumed. + +Measured values against the plan's expectations: each callee < 100 (largest 96), +`ci.yml` ~32 (exactly 32), README < 150 (**195 — exceeds the plan's expectation +but well within the 500-line limit**). The README grew past the planner's rough +estimate because the review-finding F2 fix added the verified five-context list +and the capture guidance. The expectation was an estimate, not a constraint; the +governing 500-line limit is satisfied with 305 lines of margin. + +## Decomposition effect + +The change replaced one 160-line monolith with an orchestrator plus five focused +callees: + +| | Before | After | +| --- | --- | --- | +| `ci.yml` | 160 lines, 2 jobs, all gates inline | 32 lines, 5 jobs, zero inline steps | +| Callee files | none | 5 files, 29–96 lines each, 279 lines total | + +Total workflow YAML rose from 160 to 311 lines, which is the expected cost of +per-job setup no longer being shared. Each file now has a single responsibility, +consistent with the module-cohesion guidance in the same rule. + +## Acceptance ([P5-T4]) + +- Every listed file is under 500 lines. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/first-run.2026-08-14T10-35.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/first-run.2026-08-14T10-35.md new file mode 100644 index 00000000..74760bd8 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/first-run.2026-08-14T10-35.md @@ -0,0 +1,96 @@ +# First Run of the Split Pipeline — Issue #553 + +- Timestamp: 2026-08-14T10-35 (local) / 2026-08-14T14:35:51Z (UTC) +- Task: [P3-T4] +- Run: [31809697953](https://github.com/drmoisan/TaskMaster/actions/runs/31809697953) +- Head SHA: `0b016c81a78f3fafc0864de472f4139cc0938002` +- Branch: `feature/ci-parallel-job-split-553` +- Trigger event: `workflow_dispatch` + +Commands: + +```powershell +gh api repos/drmoisan/TaskMaster/actions/runs/31809697953 --jq '{id,event,head_sha,head_branch,status,conclusion,created_at,updated_at,html_url}' +gh api repos/drmoisan/TaskMaster/actions/runs/31809697953/jobs --jq '.jobs[] | {name, conclusion, started_at, completed_at}' +``` + +EXIT_CODE: 0 + +## Trigger note (why `workflow_dispatch` rather than `push`) + +`ci.yml` fires on `push` to `[main, development]` and on `pull_request` against +those branches. A push to a feature branch therefore starts no run, and +[P3-T3] (pull-request creation) is orchestrator-gated and still deferred. The run +was started explicitly: + +``` +gh workflow run ci.yml --ref feature/ci-parallel-job-split-553 +``` + +This is the same dispatch form used for every subsequent run in Phase 4. The +`modified-workflow-needs-green-run` rule is satisfied by a green run against the +branch head regardless of trigger event, and remediation finding B1 +(`remediation-inputs.2026-08-14T10-21.md`) explicitly accepts a green +`workflow_dispatch` run against the branch head when the pull-request path is +blocked. + +## Output Summary — GREEN, branch (a) + +Run conclusion: **success**. All five jobs concluded `success`. + +| Job (check-run context) | Conclusion | Started | Completed | Duration | +| --- | --- | --- | --- | --- | +| `actionlint / actionlint` | success | 14:28:53Z | 14:29:29Z | 36s | +| `format-check / Verify formatting` | success | 14:28:53Z | 14:31:04Z | 131s | +| `build-analyzers / Build with analyzers and code style enforcement` | success | 14:28:53Z | 14:31:59Z | 186s | +| `build-nullable / Build with nullable warnings treated as errors` | success | 14:28:55Z | 14:32:03Z | 188s | +| `mstest-coverage / Run MSTest suite with coverage` | success | 14:28:53Z | 14:33:12Z | 259s | + +- **Pipeline wall clock: 259s** — latest `completed_at` (14:33:12Z) minus earliest + `started_at` (14:28:53Z). +- **All five jobs started within 2 seconds of each other** (14:28:53Z–14:28:55Z), + confirming they were scheduled concurrently and that the zero-`needs:` topology + produces genuine parallelism rather than incidental interleaving. +- Wall clock is bounded by the MSTest job, as the spec predicted. + +## Branch outcome (explicit, per the task's three-way branching) + +**(a) All five jobs succeeded → GREEN.** [P3-T5] therefore takes its +NOT-REQUIRED branch. Neither branch (b) (a trimmed-setup symptom) nor branch (c) +(a failure from any other cause) occurred. + +## What this run independently confirms + +1. **The tailored-setup assumption holds** (spec Residual risk 2). The three + msbuild callees ran green with **no** `setup-dotnet`, no dotnet-tools cache, + and no `dotnet tool restore`; the format callee ran green with **no** + `setup-msbuild`, no `setup-nuget`, no `packages` cache, and no + `nuget restore`. Nothing in the msbuild build path required the pinned .NET 10 + SDK, and CSharpier required no restored NuGet packages. +2. **The reusable-workflow wiring executes**, not merely lints. All five + `uses: ./.github/workflows/_<name>.yml` references resolved and each callee's + `workflow_call` trigger accepted the invocation. +3. **The check-run context name form is now measured, not assumed.** It is + `<caller job id> / <callee job name>` — for example the caller job `actionlint` + invoking the callee job whose `name:` is `actionlint` yields + `actionlint / actionlint`, and the caller job `format-check` invoking the + callee job named `Verify formatting` yields `format-check / Verify formatting`. + **The previously required bare `actionlint` context no longer reports**, so + both of the ruleset's current required contexts must be replaced, not just the + `Format, build, analyze, and test` one. These five strings are recorded here + for reference only; [P5-T16] captures the authoritative list from the final + head SHA, which will have moved by then. +4. **The MSTest job's own plain build works.** It discovered and ran test + assemblies without inheriting build output from any other job, which is the + premise of the no-artifact-sharing topology. + +Runner environment (from `gh api .../jobs --jq '.jobs[] | {runner_name, labels}'`): +all four gate jobs on `windows-latest`, the actionlint job on `ubuntu-latest`, +runner group `GitHub Actions` (GitHub-hosted). This matches the baseline's runner +class and satisfies the parity requirement of +`.claude/rules/benchmark-baselines.md` for the [P4-T6] comparison. + +## Acceptance ([P3-T4]) + +- Artifact exists and records branch outcome (a) explicitly, with run id, head + SHA, and per-job names and conclusions. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/lastexitcode-review.2026-08-14T11-14.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/lastexitcode-review.2026-08-14T11-14.md new file mode 100644 index 00000000..e3dc47d7 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/lastexitcode-review.2026-08-14T11-14.md @@ -0,0 +1,103 @@ +# `$LASTEXITCODE` Hygiene Review — Issue #553 + +- Timestamp: 2026-08-14T11-14 (local) / 2026-08-14T15:14:22Z (UTC) +- Task: [P5-T2] +- Governing rule: `.claude/rules/ci-workflows.md` +- Corroborating research: Q9 of + `research/2026-08-14T13-30-ci-parallel-job-split-research.md` + +## Scope + +Six of the seven workflow files are enumerated: the five callees plus the +orchestrator `ci.yml`. + +`.github/workflows/codex-web-setup-test.yml` is explicitly **EXCLUDED** from +enumeration because it declares no `shell: pwsh` or `shell: powershell` step, so +the `.claude/rules/ci-workflows.md` pattern cannot apply to it. The exclusion is +recorded here so the enumeration is consistent with its own scope statement. That +file is also untouched by this change. + +## Step-by-step enumeration + +Every `shell: pwsh` step in scope, located by scanning each file for a +`shell: pwsh` line and attributing it to the nearest preceding `- name:`: + +| File | pwsh steps | Steps (line: name) | +| --- | --- | --- | +| `ci.yml` | **0** | none — the orchestrator contains no `steps:` at all | +| `_actionlint.yml` | **0** | none — its single script step uses `shell: bash` | +| `_format-check.yml` | 2 | L35: `Setup CSharpier`; L39: `Verify formatting` | +| `_build-analyzers.yml` | 2 | L43: `Restore solution`; L47: `Build with analyzers and code style enforcement` | +| `_build-nullable.yml` | 2 | L43: `Restore solution`; L47: `Build with nullable warnings treated as errors` | +| `_mstest-coverage.yml` | 3 | L43: `Restore solution`; L47: `Build solution`; L54: `Run MSTest suite with coverage` | + +Total: **9 `pwsh` steps** and **1 `bash` step** across the six files. + +## (a) No step uses the deliberately-failing-nested-command pattern + +`.claude/rules/ci-workflows.md` applies to a step whose `run:` block +**intentionally invokes a command expected to fail** — for example a negative-path +self-validation that asserts a gate catches a synthetic regression. Reviewing all +nine steps: + +| Step | Last external command on the success path | Deliberately-failing? | Verdict | +| --- | --- | --- | --- | +| `Setup CSharpier` (`dotnet tool restore`) | `dotnet tool restore` | no | compliant; failure should propagate | +| `Verify formatting` (`dotnet csharpier check .`) | the gate command itself | no | compliant; a non-zero exit **is** the gate signal and must not be reset | +| `Restore solution` ×3 (`nuget restore`) | `nuget restore` | no | compliant | +| `Build with analyzers…` | `msbuild`, then an explicit exit guard | no | compliant | +| `Build with nullable…` | `msbuild`, then an explicit exit guard | no | compliant | +| `Build solution` (MSTest job's plain build) | `msbuild`, then an explicit exit guard | no | compliant | +| `Run MSTest suite with coverage` | `vstest.console.exe`, then `throw` on non-zero | no | compliant; on success the script ends after a passing `if`, leaving exit 0 | + +**No step in the pipeline intentionally invokes a failing nested command, so the +rule's mandatory reset (`$LASTEXITCODE = 0`) or explicit `exit 0` is not required +anywhere.** This matches research Q9's conclusion for the pre-split pipeline and +confirms the split introduced no such step. + +The rule becomes load-bearing only if a future change adds negative-path +self-validation (for example a step asserting that a gate catches a seeded +violation). This feature deliberately exercised its seeded violations as +**temporary probe commits** ([P4-T1]–[P4-T3]), which were reverted, precisely so +that no deliberately-failing nested command enters the committed pipeline — the +method the spec's seeded-conditions section prescribes. + +Note the distinction the rule turns on: the `if ($LASTEXITCODE -ne 0) { exit +$LASTEXITCODE }` guards below exist to **propagate** a genuine failure, not to +suppress an expected one. Resetting `$LASTEXITCODE` in a gate step would silently +disable that gate. + +## (b) Guards and throws present, with match counts + +| Check | Command | Count | Expected | Locations | +| --- | --- | --- | --- | --- | +| msbuild exit guards | `Select-String -Path _build-analyzers.yml,_build-nullable.yml -Pattern 'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }' -SimpleMatch` | **2** | 2 | `_build-analyzers.yml:L53`, `_build-nullable.yml:L60` | +| MSTest failure throw | `Select-String -Path _mstest-coverage.yml -Pattern 'throw "MSTest execution failed' -SimpleMatch` | **1** | 1 | `_mstest-coverage.yml:L85` | +| Zero-assembly throw | `Select-String -Path _mstest-coverage.yml -Pattern 'throw "No test assemblies found' -SimpleMatch` | **1** | 1 | `_mstest-coverage.yml:L79` | + +All four match counts are as required. + +`-SimpleMatch` is used so the literal `$`, `(`, `)`, `{`, and `}` characters are +matched as text rather than interpreted as regex metacharacters. + +Note: `_mstest-coverage.yml` also carries a third exit guard at L51, in the new +plain `Build solution` step. It is not part of the required counts above (that +step has no pre-split counterpart) but it applies the same propagate-on-failure +discipline, so a build failure in the MSTest job fails the job rather than +proceeding to a test run against stale binaries. + +## Behavioural corroboration + +The guards were exercised, not merely inspected. In the [P4-T2] probe run +(31811211865) the nullable gate's msbuild invocation failed with +`error CS8603`, the exit guard propagated the non-zero code, and the +`build-nullable` job concluded `failure`. Had the guard been absent or the exit +code reset, the step would have reported success and the gate would have been +silently disabled. + +## Acceptance ([P5-T2]) + +- Artifact records the step-by-step table and all four match counts. +- Spec seeded-condition checkbox 8 ("No `pwsh` step leaks a residual non-zero + `$LASTEXITCODE` per `.claude/rules/ci-workflows.md`") is checked off with this + artifact as the evidence pointer. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/no-csharp-diff.2026-08-14T11-14.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/no-csharp-diff.2026-08-14T11-14.md new file mode 100644 index 00000000..3c2b98cc --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/no-csharp-diff.2026-08-14T11-14.md @@ -0,0 +1,88 @@ +# Zero C#/Project-File Diff — Issue #553 + +- Timestamp: 2026-08-14T11-14 (local) / 2026-08-14T15:14:22Z (UTC) +- Task: [P5-T3] + +Command (two statements — PowerShell does not concatenate a subexpression with a +trailing `..HEAD` into one argument): + +```powershell +$base = git merge-base origin/main HEAD +git diff --name-only "$base..HEAD" -- '*.cs' '*.csproj' '*.props' '*.targets' '**/packages.config' '**/app.config' +``` + +- Merge base: `2073f717bbfac30053f3d6a4e652d99af3ae5c9c` +- Head at time of check: `ad28ea81e85ed09399feb4275828d00efeccc790` + +EXIT_CODE: 0 + +## Output Summary + +**Output is empty. Zero matching files changed.** + +The `**/` prefix on `packages.config` and `app.config` is required: a git pathspec +with no wildcard is anchored to the repository root and would match zero files +regardless of what changed, making the check vacuous. `*.cs` and the other +extension globs already match at any depth (pathspec globbing does not set +`FNM_PATHNAME`). This was verified during preflight: `git ls-files -- +'packages.config'` returns 0 files while `git ls-files -- '**/packages.config'` +returns 18. + +## Corroboration — the complete branch diff + +Every file changed on this branch relative to the merge base, for completeness: + +| Category | Files | +| --- | --- | +| Workflow files (the change itself) | `.github/workflows/ci.yml` (modified), `_actionlint.yml`, `_format-check.yml`, `_build-analyzers.yml`, `_build-nullable.yml`, `_mstest-coverage.yml`, `README.md` (added) | +| Feature documents | `issue.md`, `spec.md`, `user-story.md`, `plan.2026-08-14T09-05.md`, `research/…-research.md` | +| Feature evidence | 15 artifacts under `evidence/baseline/`, `evidence/other/`, `evidence/qa-gates/` | +| Agent memory | 7 files under `.claude/agent-memory/` (atomic-executor, atomic-planner, task-researcher) | +| Unrelated archival copies | 2 files under `docs/features/potential/promoted/` (issues 554 and 555, committed separately as `955e17fa`; orchestrator-owned) | + +**No `*.cs`, `*.csproj`, `*.props`, `*.targets`, `packages.config`, or +`app.config` file appears anywhere in the branch diff.** + +## Why this holds despite three C# probe commits + +Phase 4 introduced three deliberate C# violations as temporary commits and +reverted each one: + +| Probe | Probe commit | File touched | Revert commit | +| --- | --- | --- | --- | +| Formatting ([P4-T1]) | `5a606895` | `UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs` | `072e19ca` | +| Nullable ([P4-T2]) | `fc4f2be6` | `UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs` | `9415ad31` | +| Test failure ([P4-T3]) | `a55ccdfc` | `UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs` | `ad28ea81` | + +Each probe is immediately followed by its own revert, so all three cancel exactly +and the cumulative effect on the tree is nil. The probe commits remain in branch +*history* — which is intentional and auditable, since they are the evidence that +each gate fails independently — but they contribute nothing to the branch *diff*. + +## Justification for the absence of a C# toolchain pass + +Restating the plan's No-C#-Toolchain Statement, now confirmed by measurement +rather than asserted in advance: + +- This feature modifies no `*.cs`, `*.csproj`, `*.props`, `*.targets`, or + `packages.config` file in its final diff — verified empirically above. +- The executor therefore did not run `csharpier`, `msbuild`, or + `vstest.console.exe` as verification of this change. There is no C# change to + verify, and a local C# pass would assert nothing about GitHub Actions workflow + YAML. +- The only local verification available for workflow YAML is `actionlint`, run + three times to exit 0 ([P0-T3], [P2-T3], [P5-T1]). +- The authoritative verification is a live green run of the reworked pipeline on + the branch head, per `modified-workflow-needs-green-run`: run 31812508684 on + `ad28ea81`, all five jobs `success`. That run executed csharpier, both msbuild + gates, and the full 6435-test MSTest suite **on the runner**, which is a + stronger check than any local pass would have been. +- Spec Non-Goal 5 states the same boundary: "Any C# source, project, or test + change" is out of scope for this feature. + +## Acceptance ([P5-T3]) + +- Output is empty; probe commits are fully cancelled by their reverts. +- The artifact restates the No-C#-Toolchain Statement as the justification for + the absence of a C# toolchain pass. +- No halt condition: the check did not surface an unreverted probe or scope drift. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-probe-green-run.2026-08-14T11-10.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-probe-green-run.2026-08-14T11-10.md new file mode 100644 index 00000000..d2f75af6 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-probe-green-run.2026-08-14T11-10.md @@ -0,0 +1,67 @@ +# Post-Revert Green Run — Issue #553 + +- Timestamp: 2026-08-14T11-10 (local) / 2026-08-14T15:10Z (UTC) +- Task: [P4-T4] + +Commands: + +``` +gh workflow run ci.yml --ref feature/ci-parallel-job-split-553 +gh run watch 31812508684 --interval 20 --exit-status +gh api repos/drmoisan/TaskMaster/actions/runs/31812508684/jobs --jq '.jobs[] | {name, conclusion}' +``` + +EXIT_CODE: 0 — `gh run watch --exit-status` returned 0, which it does only for a +successful run. + +## Run + +- Run: [31812508684](https://github.com/drmoisan/TaskMaster/actions/runs/31812508684) +- Head SHA: `ad28ea81e85ed09399feb4275828d00efeccc790` +- Run conclusion: **success** + +The head is the third revert commit, so this run exercises the branch in its +probe-free state: all three seeded violations have been introduced, observed, and +cancelled by their reverts. + +Note on trigger: the final revert push did not itself start a run, because +`ci.yml` triggers only on `push` to `[main, development]` and on `pull_request` +against them. The run was started with +`gh workflow run ci.yml --ref feature/ci-parallel-job-split-553`, and its +`head_sha` was verified to equal the branch tip before watching. + +## Per-job conclusions and durations + +| Job (check-run context) | Conclusion | Started | Completed | Duration | +| --- | --- | --- | --- | --- | +| `actionlint / actionlint` | success | 15:02:37Z | 15:03:11Z | 34s | +| `format-check / Verify formatting` | success | 15:02:38Z | 15:05:03Z | 145s | +| `build-nullable / Build with nullable warnings treated as errors` | success | 15:02:37Z | 15:05:29Z | 172s | +| `build-analyzers / Build with analyzers and code style enforcement` | success | 15:02:38Z | 15:06:03Z | 205s | +| `mstest-coverage / Run MSTest suite with coverage` | success | 15:02:38Z | 15:09:50Z | 432s | + +**All five job conclusions are `success`.** + +Pipeline wall clock: **433s** (earliest start 15:02:37Z, latest completion +15:09:50Z). This is materially slower than the 259s measured on the first green +run (31809697953) and the difference is analysed in +`ci-split-timing-comparison.2026-08-14T11-10.md`; it is runner variance, not a +structural change. The workflow files are byte-identical between the two runs. + +## Significance + +1. **The branch is green in its probe-free state.** Every seeded fault was + demonstrated and then removed; the pipeline returns to green on the reverted + tree, which is what makes the probes safe to have run on this branch. +2. **This run satisfies `modified-workflow-needs-green-run` for the current + head.** [P5-T15] re-confirms it on the final pre-migration head after the + Phase 5 evidence commit, and [P5-T16] captures the required context names from + that final head. +3. **All five jobs again started within one second of each other**, confirming + the concurrent scheduling is stable across runs. + +## Acceptance ([P4-T4]) + +- Artifact records run id `31812508684`, head SHA + `ad28ea81e85ed09399feb4275828d00efeccc790`, and all five job conclusions + `success`. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json new file mode 100644 index 00000000..d2a45767 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json @@ -0,0 +1,27 @@ +{ + "measurement_file": "ci-split-timing-comparison.2026-08-14T11-10.md", + "issue": 553, + "metric": "ci_pipeline_wall_clock_seconds", + "runner_class": "windows-latest", + "host_signature": "github-hosted:windows-latest:runner-group=GitHub Actions", + "workflow_run_url": "https://github.com/drmoisan/TaskMaster/actions/runs/31812508684", + "measurement_of_record": { + "run_id": 31812508684, + "head_sha": "ad28ea81e85ed09399feb4275828d00efeccc790", + "wall_clock_seconds": 433, + "billed_windows_seconds": 954, + "captured_at": "2026-08-14T15:09:50Z" + }, + "second_sample": { + "run_id": 31809697953, + "head_sha": "0b016c81a78f3fafc0864de472f4139cc0938002", + "wall_clock_seconds": 259, + "billed_windows_seconds": 764, + "captured_at": "2026-08-14T14:33:12Z" + }, + "compared_against_baseline": "../baseline/ci-sequential-baseline.2026-08-14T13-05.md", + "compared_against_baseline_seconds": 444, + "collection_command": "gh api repos/drmoisan/TaskMaster/actions/runs/<run-id>/jobs", + "parity_statement": "Both the baseline and both post-split samples were captured on GitHub-hosted runners of the same class (windows-latest gate jobs, ubuntu-latest lint job) using the same collection command. No measurement was taken on a developer workstation.", + "notes": "Two green samples of byte-identical workflow files are recorded because they differ substantially (433s vs 259s wall clock). Step-level analysis attributes the difference to uniform hosted-runner slowness in the critical-path mstest-coverage job, not to a workflow-structure change; neither run was queued. No numeric threshold is gated on this measurement. Filename differs from the rule's 'baseline.provenance.json' pattern because this file documents a post-change measurement rather than a committed baseline; it carries the same required fields (runner_class, host_signature, workflow_run_url)." +} diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/tailored-setup-fallback.2026-08-14T10-35.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/tailored-setup-fallback.2026-08-14T10-35.md new file mode 100644 index 00000000..5caee506 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/tailored-setup-fallback.2026-08-14T10-35.md @@ -0,0 +1,59 @@ +# Tailored-Setup Fallback — Issue #553 + +- Timestamp: 2026-08-14T10-35 (local) / 2026-08-14T14:35:51Z (UTC) +- Task: [P3-T5] + +## Result: NOT REQUIRED — tailored-setup assumption held + +This is the task's explicitly authorized non-executing outcome, taken because +[P3-T4] recorded `GREEN` (branch (a)). + +- Green run id: **31809697953** — <https://github.com/drmoisan/TaskMaster/actions/runs/31809697953> +- Head SHA: `0b016c81a78f3fafc0864de472f4139cc0938002` +- All five jobs concluded `success`; pipeline wall clock 259s. + +EXIT_CODE: 0 (no fallback command was run; the REQUIRED branch was not entered) + +## What was NOT restored, and the evidence that it was not needed + +The spec's tailored per-job setup deliberately omits steps from each callee. The +green run exercised every one of those omissions on a real runner: + +| Callee | Steps deliberately omitted | Green? | +| --- | --- | --- | +| `_build-analyzers.yml` | `Setup .NET SDK`, `Cache dotnet tools`, `Setup CSharpier` (`dotnet tool restore`) | yes — 186s | +| `_build-nullable.yml` | same three | yes — 188s | +| `_mstest-coverage.yml` | same three | yes — 259s | +| `_format-check.yml` | `Setup MSBuild`, `Setup NuGet`, `Cache NuGet packages`, `Restore solution` (`nuget restore`) | yes — 131s | + +Both halves of the assumption are therefore confirmed by execution rather than by +inspection: + +1. Nothing in the msbuild build path depends on the pinned .NET 10 SDK. The + analyzer `/t:Build`, the nullable `/t:Rebuild`, and the MSTest job's plain + build all completed without `actions/setup-dotnet`. +2. CSharpier reads source text only and does not consume restored NuGet packages. + `dotnet csharpier check .` completed without `nuget restore`. + +**No setup step was restored to any callee.** The workflow files are unchanged +from their [P1-T1]–[P1-T5] authored state, so the [P1-T6] byte-identity results +and the [P2-T1] structural results remain valid without re-verification. + +Had the REQUIRED branch been taken, the fallback would have restored only the +specific implicated steps, copied verbatim from +`evidence/other/pre-split/ci.yml.pre-split.txt`, at an estimated ~56s/job cost +(spec Residual risk 2, research Q1 topology (a) figures). That cost was avoided. + +## Measured value of the tailored setup + +The spec's estimate bracketed wall clock between ~277s (tailored setup holds) and +~333s (full setup everywhere). The measured 259s is **below both estimates**, +so the tailored setup delivered at least the modelled benefit. Per-job setup cost +is visibly lower than the baseline's 130s: the format job's total 131s is roughly +the baseline's setup cost alone, because it installs only the .NET SDK and the +CSharpier tool. + +## Acceptance ([P3-T5]) + +- Artifact exists recording exactly one branch: NOT REQUIRED. +- The pipeline is green on the current head (`0b016c81`) at task completion. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/test-results-artifact.2026-08-14T11-10.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/test-results-artifact.2026-08-14T11-10.md new file mode 100644 index 00000000..667b8d39 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/test-results-artifact.2026-08-14T11-10.md @@ -0,0 +1,65 @@ +# `test-results` Artifact Continuity — Issue #553 + +- Timestamp: 2026-08-14T11-10 (local) / 2026-08-14T15:10Z (UTC) +- Task: [P4-T5] + +Command: + +``` +gh api repos/drmoisan/TaskMaster/actions/runs/31812508684/artifacts --jq '.artifacts[] | {name, size_in_bytes}' +``` + +(run id from [P4-T4]) + +EXIT_CODE: 0 + +## Output Summary + +```json +{"expired":false,"name":"test-results","size_in_bytes":8246282} +``` + +- Artifact name: **`test-results`** — exactly the pre-split name, unchanged. +- Size: **8,246,282 bytes** (~7.9 MiB) — non-zero. +- Expired: `false`. +- It is the **only** artifact produced by the run, confirming that no incidental + build-output artifact was introduced by the split. The adopted topology shares + no files between jobs, so this upload targets workflow storage only and is not + cross-job file transfer. + +## Continuity across runs + +| Run | Head | Artifact | Size | +| --- | --- | --- | --- | +| 31809697953 (first green) | `0b016c81` | `test-results` | 8,247,182 bytes | +| 31812508684 (post-probe green) | `ad28ea81` | `test-results` | 8,246,282 bytes | + +Both runs produce a single `test-results` artifact of ~8.25 MB. The 900-byte +difference between runs is expected: the payload contains `.trx` logs whose +embedded timestamps, run GUIDs, and machine names differ per run. Size stability +to within 0.01% indicates the same test population and the same coverage payload. + +## Why this matters + +The upload step was transplanted byte-identically from the monolith (verified in +`byte-identity.2026-08-14T09-54.md`, block `upload-step`, SHA-256 +`894b0ce75a70c838...`) and retains: + +- `if: always()`, so results upload even when the gate fails; +- `name: test-results`, the same artifact name any downstream consumer expects; +- the same two paths, `TestResults/**/*.trx` and `TestResults/**/*.coverage`; +- `if-no-files-found: warn`. + +Because the artifact now originates from `_mstest-coverage.yml` rather than the +monolithic `quality-gates` job, this check confirms the relocation did not break +production of the pipeline's only coverage-bearing output. The `if: always()` +behaviour was also exercised in practice: the [P4-T3] probe run +(31811867381), in which the MSTest gate failed, still completed its upload step. + +## Acceptance ([P4-T5]) + +- An artifact named exactly `test-results` exists on the [P4-T4] green run with + non-zero size (8,246,282 bytes). +- Spec seeded-condition checkbox 6 ("Test results and coverage artifacts continue + to upload with the same names") is checked off with this artifact as the + evidence pointer. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-format.2026-08-14T10-44.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-format.2026-08-14T10-44.md new file mode 100644 index 00000000..ce5861c9 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-format.2026-08-14T10-44.md @@ -0,0 +1,118 @@ +# Seeded Probe 1 — Formatting Violation — Issue #553 + +- Timestamp: 2026-08-14T10-44 (local) / 2026-08-14T14:44Z (UTC) +- Task: [P4-T1] `[expect-fail]` +- Expected outcome: **exactly one red gate — `format-check`.** A failing + `format-check` job is the intended result of this task; it demonstrates fault + isolation, not a defect. + +## Probe + +- Probe commit SHA: `5a6068955422f1f114af6332bd1083e1d3a68341` +- Commit message: `probe(553): formatting violation — to be reverted` +- File: `UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs` +- Edit: a formatting-only indentation change on one line, from four leading + spaces to twelve: + + ```diff + @@ -7,5 +7,5 @@ using System.Threading.Tasks; + + namespace UtilitiesCS + { + - public class IntelligenceFilters { } + + public class IntelligenceFilters { } + } + ``` + +- Diff scope: 1 file changed, 1 insertion, 1 deletion. The change alters only + leading whitespace. It introduces no compiler diagnostic, so the analyzer, + nullable, and MSTest jobs must remain green — which is precisely what makes + this a clean single-gate probe. + +Commands: + +``` +git commit -m "probe(553): formatting violation — to be reverted" +git push origin feature/ci-parallel-job-split-553 +gh workflow run ci.yml --ref feature/ci-parallel-job-split-553 +gh run watch 31810574239 --interval 20 +gh api repos/drmoisan/TaskMaster/actions/runs/31810574239/jobs --jq '.jobs[] | {name, conclusion}' +``` + +EXIT_CODE: 0 (all observation commands succeeded; the observed run conclusion is +`failure`, which is the expected result for this task) + +## Run + +- Run: [31810574239](https://github.com/drmoisan/TaskMaster/actions/runs/31810574239) +- Head SHA: `5a6068955422f1f114af6332bd1083e1d3a68341` +- Run conclusion: `failure` + +## Per-job conclusions + +| Job (check-run context) | Conclusion | Expected | +| --- | --- | --- | +| `format-check / Verify formatting` | **failure** | failure | +| `actionlint / actionlint` | success | success | +| `build-analyzers / Build with analyzers and code style enforcement` | success | success | +| `build-nullable / Build with nullable warnings treated as errors` | success | success | +| `mstest-coverage / Run MSTest suite with coverage` | success | success | + +**Output Summary: exactly one red gate (format-check).** + +## Step-level attribution inside the failing job + +The failure is attributable to the gate command itself, not to setup: + +| Step | Conclusion | +| --- | --- | +| Set up job | success | +| Checkout repository | success | +| Setup .NET SDK | success | +| Cache dotnet tools | success | +| Setup CSharpier | success | +| **Verify formatting** (`dotnet csharpier check .`) | **failure** | +| Post Cache dotnet tools | skipped | +| Post Setup .NET SDK | skipped | +| Post Checkout repository | success | +| Complete job | success | + +Failing job URL: +<https://github.com/drmoisan/TaskMaster/actions/runs/31810574239/job/94799943550> + +## What this demonstrates + +1. **Failure isolation works.** The formatting violation reddened one context and + left the other four to run to completion and report independently. Under the + pre-split monolith the same violation would have failed the single + `Format, build, analyze, and test` check and, because `csharpier check` ran + before the two builds and the test suite, would have prevented the analyzer, + nullable, and MSTest results from being produced at all. +2. **Attribution is possible from the checks list alone.** The red context names + the gate; no log inspection is needed to know a formatting rule was violated. +3. **The zero-`needs:` topology holds under failure.** No job was skipped or + cancelled as a consequence of the format job failing. + +## Revert (mandatory) + +- Revert commit SHA: `072e19ca1e62c99ab67434ce41ba5d3793ee3257` +- Message: `Revert "probe(553): formatting violation — to be reverted"` +- Command: `git revert --no-edit 5a6068955422f1f114af6332bd1083e1d3a68341` +- Verified restored: line 10 of `IntelligenceFilters.cs` reads + ` public class IntelligenceFilters { }` (four leading spaces), confirmed + byte-exactly via `sed -n '10p' ... | cat -A`. +- Pushed to `origin/feature/ci-parallel-job-split-553`. + +The probe run was allowed to complete **before** the revert was pushed, so +`cancel-in-progress: true` on the caller's concurrency group did not cancel it. + +Net effect of this task on the branch diff: **zero.** Verified in aggregate by +[P5-T3]. + +## Acceptance ([P4-T1]) + +- Artifact shows exactly the format gate red and the other four green. +- Revert commit exists on the branch and the file is byte-restored. +- Spec seeded-condition checkbox 3 ("A deliberate formatting violation fails only + the formatting gate and reports a distinct red check") is checked off with this + artifact as the evidence pointer. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-mstest.2026-08-14T11-01.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-mstest.2026-08-14T11-01.md new file mode 100644 index 00000000..6e441c9b --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-mstest.2026-08-14T11-01.md @@ -0,0 +1,141 @@ +# Seeded Probe 3 — Deliberate Test Failure — Issue #553 + +- Timestamp: 2026-08-14T11-01 (local) / 2026-08-14T15:01Z (UTC) +- Task: [P4-T3] `[expect-fail]` +- Expected outcome: **exactly one red gate — `mstest-coverage`.** A failing + `mstest-coverage` job is the intended result of this task. + +## Probe + +- Probe commit SHA: `a55ccdfc2e7980125ab36c76a84c2d59b29fb8e6` +- Commit message: `probe(553): deliberate test failure — to be reverted` +- File: `UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs` +- Test: `ExtToChar_CurrentlyExposesNoPublicMethods` +- Edit: inverted the single assertion. + + ```diff + - publicStaticMethods.Should().BeEmpty(); + + publicStaticMethods.Should().NotBeEmpty(); + ``` + +Target selection rationale — the test satisfies every constraint the task sets: + +- **Fast and deterministic.** It is a pure reflection query over + `typeof(ExtToChar)` with no I/O, no Outlook interop, no timing dependency, and + no shared mutable state. Inverting the assertion fails deterministically + because the reflected method array genuinely is empty. +- **Not `TestCategory=LiveOutlook`.** The test class carries no `TestCategory` + attribute at all, so it is not excluded by the gate's + `/TestCaseFilter:"TestCategory!=LiveOutlook"` and is guaranteed to execute. +- **CSharpier-clean and free of new compiler diagnostics.** A one-token change + to an existing FluentAssertions call; `NotBeEmpty()` is a valid method on the + same assertion type. The green `format-check`, `build-analyzers`, and + `build-nullable` results below confirm both properties. + +Commands: + +``` +git commit -m "probe(553): deliberate test failure — to be reverted" +git push origin feature/ci-parallel-job-split-553 +git ls-remote --heads origin feature/ci-parallel-job-split-553 # confirm tip == probe SHA +gh workflow run ci.yml --ref feature/ci-parallel-job-split-553 +gh run watch 31811867381 --interval 20 +gh api repos/drmoisan/TaskMaster/actions/runs/31811867381/jobs --jq '.jobs[] | {name, conclusion}' +``` + +EXIT_CODE: 0 (observation commands succeeded; observed run conclusion `failure`, +the expected result) + +The dispatched run's `head_sha` was verified to equal the probe SHA **before** +watching, applying the standing step adopted after the dispatch race recorded in +`probe-nullable.2026-08-14T10-52.md`. No race occurred this time. + +## Run + +- Run: [31811867381](https://github.com/drmoisan/TaskMaster/actions/runs/31811867381) +- Head SHA: `a55ccdfc2e7980125ab36c76a84c2d59b29fb8e6` (verified == probe SHA) +- Run conclusion: `failure` + +## Per-job conclusions + +| Job (check-run context) | Conclusion | Expected | +| --- | --- | --- | +| `mstest-coverage / Run MSTest suite with coverage` | **failure** | failure | +| `actionlint / actionlint` | success | success | +| `format-check / Verify formatting` | success | success | +| `build-analyzers / Build with analyzers and code style enforcement` | success | success | +| `build-nullable / Build with nullable warnings treated as errors` | success | success | + +**Output Summary: exactly one red gate (mstest-coverage).** + +## Test-run detail (from the failing job log) + +``` +Failed ExtToChar_CurrentlyExposesNoPublicMethods +Total tests: 6435 +Passed: 6434 +Failed: 1 +``` + +The isolation is exact at the test level as well as the gate level: of 6435 +executed tests, the single failure is the probed test. The only failing step in +the job was the gate step `Run MSTest suite with coverage`; every setup step and +the job's own plain build succeeded. + +Failing job URL: +<https://github.com/drmoisan/TaskMaster/actions/runs/31811867381/job/94804173833> + +## What this demonstrates + +1. **The MSTest gate still enforces test outcomes after the split**, using the + byte-identical vstest invocation with `/EnableCodeCoverage /InIsolation + /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook"`. +2. **Test discovery is intact under the job's own plain build.** 6435 tests were + discovered and executed from assemblies this job built itself, with no + inherited build output from any other job. The transplanted discovery filter + (match `\bin\Debug\`, exclude `\obj\` and `\ref\`) and the zero-assembly + `throw` guard behaved correctly: had discovery regressed to zero assemblies, + the guard would have thrown rather than reporting a false pass. +3. **A test failure no longer masks the other gates.** The formatting, analyzer, + and nullable results were all produced and reported green in the same run. + +## Revert (mandatory) + +- Revert commit SHA: `ad28ea81e85ed09399feb4275828d00efeccc790` +- Message: `Revert "probe(553): deliberate test failure — to be reverted"` +- Command: `git revert --no-edit a55ccdfc2e7980125ab36c76a84c2d59b29fb8e6` +- Verified restored: line 23 reads `publicStaticMethods.Should().BeEmpty();` +- Pushed to `origin/feature/ci-parallel-job-split-553`. + +## Cumulative probe-neutrality check (all three probes) + +``` +git diff --name-only 0b016c81 HEAD -- '*.cs' '*.csproj' '*.props' '*.targets' '**/packages.config' '**/app.config' +``` + +returns **0 files**. All three probe commits are fully cancelled by their +reverts; the branch carries no residual C# or project-file change relative to the +pre-probe workflow commit. [P5-T3] repeats this check against the merge base as +the formal verification. + +Commit sequence on the branch: + +``` +ad28ea81 Revert "probe(553): deliberate test failure — to be reverted" +a55ccdfc probe(553): deliberate test failure — to be reverted +9415ad31 Revert "probe(553): nullable violation — to be reverted" +fc4f2be6 probe(553): nullable violation — to be reverted +072e19ca Revert "probe(553): formatting violation — to be reverted" +5a606895 probe(553): formatting violation — to be reverted +0b016c81 ci(#553): split monolithic quality-gates job into parallel reusable workflows +``` + +Each probe is immediately followed by its own revert; no probe commit is left +uncancelled at any point after its run completed. + +## Acceptance ([P4-T3]) + +- Artifact shows exactly the MSTest gate red and the other four green. +- Revert commit exists on the branch and the assertion is restored. +- Spec seeded-condition checkbox 5 ("A deliberate test failure fails only the + MSTest gate") is checked off with this artifact as the evidence pointer. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-nullable.2026-08-14T10-52.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-nullable.2026-08-14T10-52.md new file mode 100644 index 00000000..c8c7705d --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/regression-testing/probe-nullable.2026-08-14T10-52.md @@ -0,0 +1,140 @@ +# Seeded Probe 2 — Nullable Violation — Issue #553 + +- Timestamp: 2026-08-14T10-52 (local) / 2026-08-14T14:52Z (UTC) +- Task: [P4-T2] `[expect-fail]` +- Expected outcome: **exactly one red gate — `build-nullable`.** A failing + `build-nullable` job is the intended result of this task. + +## Precondition verified before committing + +The task requires a production `*.cs` file carrying `#nullable enable` in a +project whose `.csproj` does **not** set `TreatWarningsAsErrors`, so that the +violation is an error only under the nullable gate's explicit +`/p:TreatWarningsAsErrors=true`. + +- File: `UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs` — line 1 is + `#nullable enable`. +- Project: `UtilitiesCS/UtilitiesCS.csproj`. +- `Select-String`/`grep` for `TreatWarningsAsErrors` in that csproj returns + exactly **one** hit, and it is inside an XML comment on line 1299: + `<!-- Issue #181: analyzer-only references (first-party scope). Severities are + set to suggestion in .editorconfig so none break the nullable + TreatWarningsAsErrors build. -->`. **No `<TreatWarningsAsErrors>` property + element is set.** The precondition holds. +- The csproj also carries no `<Nullable>` element, consistent with the repo's + per-file `#nullable enable` opt-in convention documented in the nullable gate's + own rationale comment. + +## Probe + +- Probe commit SHA: `fc4f2be6dbc79f627c10961660312e6a9da5e2a8` +- Commit message: `probe(553): nullable violation — to be reverted` +- Edit: replaced the empty class body with a single method returning a null + literal from a non-nullable return type: + + ```diff + - public class IntelligenceFilters { } + + public class IntelligenceFilters + + { + + public static string ProbeValue() => null; + + } + ``` + +- The edit is written in canonical CSharpier style so it does not also trip the + formatting gate; the green `format-check` result below confirms it did not. + +Commands: + +``` +git commit -m "probe(553): nullable violation — to be reverted" +git push origin feature/ci-parallel-job-split-553 +gh workflow run ci.yml --ref feature/ci-parallel-job-split-553 +gh run watch 31811211865 --interval 20 +gh api repos/drmoisan/TaskMaster/actions/runs/31811211865/jobs --jq '.jobs[] | {name, conclusion}' +``` + +EXIT_CODE: 0 (observation commands succeeded; observed run conclusion `failure`, +the expected result) + +## Dispatch-race correction (recorded for auditability) + +The first dispatch after the probe push produced run **31811124719**, whose +`head_sha` was `072e19ca` — the *previous* head. `gh workflow run --ref <branch>` +resolved the branch ref before the push had replicated, so that run would have +executed the clean post-revert tree and reported a misleading GREEN. + +Action taken: run 31811124719 was **cancelled** (`gh run cancel 31811124719`, +final conclusion `cancelled`), the remote tip was re-confirmed with +`git ls-remote --heads origin feature/ci-parallel-job-split-553` = +`fc4f2be6...`, and a fresh dispatch produced run **31811211865** whose `head_sha` +is `fc4f2be6...`. Only the second run is evidence. + +**Verifying that the dispatched run's `head_sha` equals the intended probe SHA +before watching is now a standing step for every probe in this phase.** A probe +observed on the wrong commit is worse than no probe: it produces a confident +false negative. + +## Run + +- Run: [31811211865](https://github.com/drmoisan/TaskMaster/actions/runs/31811211865) +- Head SHA: `fc4f2be6dbc79f627c10961660312e6a9da5e2a8` (verified to equal the probe SHA) +- Run conclusion: `failure` + +## Per-job conclusions + +| Job (check-run context) | Conclusion | Expected | +| --- | --- | --- | +| `build-nullable / Build with nullable warnings treated as errors` | **failure** | failure | +| `actionlint / actionlint` | success | success | +| `format-check / Verify formatting` | success | success | +| `build-analyzers / Build with analyzers and code style enforcement` | success | success | +| `mstest-coverage / Run MSTest suite with coverage` | success | success | + +**Output Summary: exactly one red gate (build-nullable).** + +## Compiler diagnostic (from the failing job log) + +``` +IntelligenceFilters.cs(12,46): error CS8603: Possible null reference return. +``` + +The only failing step in the job was the gate step itself, +`Build with nullable warnings treated as errors`; every setup step succeeded. + +Failing job URL: +<https://github.com/drmoisan/TaskMaster/actions/runs/31811211865/job/94802025964> + +## What this demonstrates + +1. **The nullable gate still enforces what it enforced before the split.** The + same `/t:Rebuild` + `/p:TreatWarningsAsErrors=true` command, transplanted + byte-identically, promoted CS8603 to an error and failed the build. +2. **The gates are genuinely independent in enforcement semantics, not just in + scheduling.** The identical source produced only a *warning* in the analyzer + job and the MSTest job's plain build — both stayed green — because neither + sets `TreatWarningsAsErrors`. This is the intended separation: the analyzer + gate and the nullable gate enforce different criteria over the same code, and + the MSTest job's new plain build deliberately promotes nothing. +3. **`/t:Rebuild` is doing real work.** The violation was caught on a full + recompile, which is exactly the scenario the in-file rationale comment + preserves. + +## Revert (mandatory) + +- Revert commit SHA: `9415ad31f01ef4df48783472df88b34b17a02484` +- Message: `Revert "probe(553): nullable violation — to be reverted"` +- Command: `git revert --no-edit fc4f2be6dbc79f627c10961660312e6a9da5e2a8` +- Verified restored: the file is byte-identical to its pre-probe state. + `git diff 0b016c81 HEAD -- UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs` + returns **0 lines**, which also confirms probe 1's revert remains intact. +- Pushed; remote tip is `9415ad31`. + +The probe run completed before the revert was pushed, so `cancel-in-progress` +did not cancel it. + +## Acceptance ([P4-T2]) + +- Artifact shows exactly the nullable gate red and the other four green. +- Revert commit exists on the branch and the file is byte-restored. +- Spec seeded-condition checkbox 4 ("A deliberate nullable violation fails only + the nullable gate") is checked off with this artifact as the evidence pointer. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T10-21.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T10-21.md new file mode 100644 index 00000000..0b429575 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T10-21.md @@ -0,0 +1,87 @@ +# Feature Audit — ci-parallel-job-split (Issue #553) + +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T10-21) +- **Work mode:** `full-feature` (persisted marker in `issue.md`) +- **AC sources:** `spec.md` (10 criteria) and `user-story.md` (8 criteria); `issue.md` is an early-draft mirror, not authoritative + +## Scope and Baseline + +- **Base branch:** `main` (resolved `origin/main`) +- **Merge base:** `2073f717bbfac30053f3d6a4e652d99af3ae5c9c` (independently recomputed via `git merge-base HEAD origin/main`; matches the caller-supplied value) +- **Head:** `feature/ci-parallel-job-split-553` @ `0b016c81a78f3fafc0864de472f4139cc0938002` (3 commits ahead of the merge base) +- **Diff scope:** full branch diff, 37 files — 6 workflow YAML files, the new `.github/workflows/README.md`, the feature folder (issue/spec/user-story/plan/research + evidence tree), 2 archival promoted-potential copies (#554, #555), 6 agent-memory files +- **Evidence sources:** `artifacts/pr_context.summary.txt` / `artifacts/pr_context.appendix.txt` (fresh; recorded head matches `git rev-parse HEAD`), committed feature-folder evidence, and direct file/diff inspection by this review +- **Plan of record:** `plan.2026-08-14T09-05.md` — P0 through P2-T3 checked off; P3-T1 onward unchecked by design (those phases require a pushed branch, a live PR, and a green run) + +## Acceptance Criteria Inventory + +### spec.md § Acceptance Criteria + +| # | Criterion (abbreviated) | +| --- | --- | +| S1 | Four gates run as separate jobs with zero `needs:` edges | +| S2 | Five callee workflows with `workflow_call` + `workflow_dispatch`, own `permissions:`, right-sized `timeout-minutes`, no `concurrency` | +| S3 | `ci.yml` is a pure orchestrator: only `uses:` jobs, no inline `steps:` | +| S4 | No cross-job file sharing; only the preserved `test-results` upload | +| S5 | Gate commands and actionlint step byte-identical, incl. `/t:Rebuild` comment, `$LASTEXITCODE` guards, zero-assembly `throw` | +| S6 | `main` ruleset contexts replaced in one atomic PUT with live-captured strings; pre-PUT JSON, PUT payload, post-PUT GET recorded | +| S7 | README documents per-stage dispatch and branch-protection rename procedures | +| S8 | Green run against branch head (`modified-workflow-needs-green-run`) | +| S9 | Every current gate still enforced; none dropped, weakened, or made non-required | +| S10 | Post-split wall clock measured with the baseline's collection method and compared against 444s | + +### user-story.md § Acceptance Criteria + +| # | Criterion (abbreviated) | Mirrors | +| --- | --- | --- | +| U1 | Separate jobs, zero `needs:` edges (resolved form) | S1 | +| U2 | Each gate a callable `_<name>.yml` with both triggers | S2 | +| U3 | `ci.yml` orchestrator-only (resolved: actionlint also extracted) | S3 | +| U4 | Cross-job sharing only via explicit artifacts (resolved: none exists; `test-results` upload preserved) | S4 | +| U5 | Ruleset contexts updated via live-captured names, single atomic PUT | S6 | +| U6 | README documents both procedures | S7 | +| U7 | Green run against branch head | S8 | +| U8 | Every gate still enforced; byte-identity, `/t:Rebuild` comment, `throw` guard, separate compiles | S9 + S5 | + +## Acceptance Criteria Evaluation + +| AC | Verdict | Evidence | +| --- | --- | --- | +| S1 / U1 | PASS | `ci.yml` (32 lines): five jobs, all `uses:`-form; `grep -n "needs:" .github/workflows/*.yml` returns zero matches; direct inspection by this review | +| S2 / U2 | PASS | All five callees declare `on: workflow_call:` + `on: workflow_dispatch:`, `permissions: contents: read`, `timeout-minutes` 10/10/30/30/30; `grep` for `concurrency` in callees returns zero matches | +| S3 / U3 | PASS | `ci.yml` contains no `steps:` key; header, triggers, permissions, and concurrency block byte-identical to merge base (verified against `git show 2073f717:.github/workflows/ci.yml`) | +| S4 / U4 | PASS | Single `upload-artifact` in the new pipeline (`test-results`, `if: always()`, same name/paths/`if-no-files-found: warn`); zero `download-artifact` occurrences | +| S5 | PASS | Independently re-verified by this review: extraction + SHA-256 comparison of 14 step blocks between merge-base `ci.yml` and the callees — 14/14 MATCH, including the full 7-line `/t:Rebuild` rationale comment, both `$LASTEXITCODE` guards, and the zero-assembly `throw`. Corroborates `evidence/qa-gates/byte-identity.2026-08-14T09-54.md` (6/6 SHA-256 rows, 12/12 fragment citations) | +| S6 / U5 | FAIL (pending, scheduled) | Not executed: no ruleset PUT, no `ruleset-pre-put`/`ruleset-put-payload`/`ruleset-post-put` evidence exists. Correctly sequenced by design — the PUT requires context names captured from a live green run (plan P5-T16, P6-T1..T4; P6-T3 is orchestrator-confirmation-required). Procedure fully documented in spec § Required-Status-Check Contract and README | +| S7 / U6 | PASS | README § "Per-stage workflow_dispatch procedure" (commands, UI path, two caveats) and § "Branch-protection rename procedure" (five steps, atomic-PUT payload construction, two-step-edit prohibition, verification, rollback); satisfies the `.claude/skills/orchestrate/SKILL.md` § GitHub Actions Reusable Workflows reference | +| S8 / U7 | FAIL (pending, scheduled) | No green run exists against head `0b016c81`; branch not yet pushed/PR'd (plan P3-T1..T4 unchecked). This is blocking finding B1 in the policy audit and F1 in the code review | +| S9 / U8 | PARTIAL | Workflow-level leg verified PASS: all gate commands byte-identical (S5), analyzer and nullable compiles separate, MSTest callee's plain build carries no analyzer/warning-promotion properties (grep count 0), no gate weakened. "Made non-required" leg depends on the pending ruleset PUT (S6) and the fail-closed strict policy guarantees over-blocking, never under-gating, in the interim. Completes with S6 | +| S10 | FAIL (pending, scheduled) | Post-split measurement requires a live run of the split pipeline; scheduled as plan P4-T6 with the same `gh api .../runs/<id>/jobs` collection method as the 444s baseline (runner-environment parity) | + +Additional verification notes: + +- The sequential baseline of record (`evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md`) is measured from a GitHub-hosted `windows-latest` run with the run URL and collection command recorded; assessed against `.claude/rules/benchmark-baselines.md` in policy audit § 3.3 (PASS with one non-blocking observation). +- Definition of Done and Seeded Test Conditions in spec.md remain unchecked; every unchecked item maps to the pending live-PR phases (P3–P7) and is not independently deliverable from the committed tree. + +## Summary + +Seven of ten spec criteria (S1–S5, S7; S9 partially) are delivered and verified from the committed tree, including independent re-verification of the byte-identity claim by this review. The three FAIL-pending criteria (S6, S8, S10) and the pending leg of S9 share a single dependency chain: push branch → open PR → green run on the head → capture context names → atomic ruleset PUT → post-split measurement. That chain is exactly what the unexecuted plan Phases 3–7 encode, with the two outward-facing actions (PR creation, ruleset PUT) correctly marked orchestrator-confirmation-required. There is no criterion failing due to a defect in the change set; all open items are sequencing-bound on a live run that cannot exist pre-push. Blocking findings carried into remediation: 1 (green-run rule). + +Go/no-go: **not yet ready for merge** (green run and ruleset migration outstanding); **ready to proceed to the live-PR phases** with no code changes required first. + +## Acceptance Criteria Check-off + +Checked off in this review (evaluated PASS with verified evidence, per `acceptance-criteria-tracking`): + +- `spec.md`: S1, S2, S3, S4, S5, S7 (6 items changed `- [ ]` → `- [x]`) +- `user-story.md`: U1, U2, U3, U4, U6 (5 items changed `- [ ]` → `- [x]`) + +Left unchecked with documented gaps: spec S6, S8, S9, S10; user-story U5, U7, U8. `issue.md` mirror checkboxes were left untouched (not an authoritative AC source in `full-feature` mode; plan tasks P5-T6..P5-T13 and P7-T2..T3 handle the mirrors). + +### Acceptance Criteria Status + +- Source: `docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md`, `docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md` +- Total AC items: 18 (10 spec + 8 user-story) +- Checked off (delivered): 11 +- Remaining (unchecked): 7 +- Items remaining: spec S6 (atomic ruleset PUT with recorded evidence), spec S8 (green run against branch head), spec S9 (no gate made non-required — completes with S6), spec S10 (post-split measurement vs 444s baseline), user-story U5 (ruleset update), user-story U7 (green run), user-story U8 (gates still enforced — completes with U5) diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md index d79268a4..2ae1a3f1 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/issue.md @@ -51,21 +51,21 @@ reliance is not implicit. ## Acceptance Criteria (early draft) -- [ ] The formatting gate, the analyzer build gate, the nullable build gate, and the +- [x] The formatting gate, the analyzer build gate, the nullable build gate, and the MSTest gate each run as separate GitHub Actions jobs with no `needs:` edge forcing them to serialize, except where an edge is required to consume an uploaded build artifact. -- [ ] Each gate is a callable reusable workflow `_<name>.yml` declaring both +- [x] Each gate is a callable reusable workflow `_<name>.yml` declaring both `on: workflow_call:` and `on: workflow_dispatch:`. -- [ ] `ci.yml` becomes an orchestrator workflow containing only `uses:` references +- [x] `ci.yml` becomes an orchestrator workflow containing only `uses:` references and contains no inline `steps:`. -- [ ] Any file shared between jobs crosses the boundary via explicit +- [x] Any file shared between jobs crosses the boundary via explicit `actions/upload-artifact` + `actions/download-artifact`. - [ ] The `main` branch ruleset's `required_status_checks` contexts are updated to match the new job names, with no window in which a merge can bypass a gate. -- [ ] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` +- [x] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` procedure and the branch-protection rename procedure. -- [ ] The reworked pipeline produces a green run against the branch head, satisfying +- [x] The reworked pipeline produces a green run against the branch head, satisfying `modified-workflow-needs-green-run`. - [ ] Every gate enforced by the current `quality-gates` job is still enforced after the split; no check is dropped, weakened, or made non-required. @@ -121,3 +121,9 @@ reliance is not implicit. - [ ] Promote to GitHub issue (feature request template) - [ ] Create `docs/features/active/ci-parallel-job-split/` folder from the template + +## References + +- Workflows README: `.github/workflows/README.md` (created by #553) — documents + the pipeline topology, the per-stage `workflow_dispatch` procedure, and the + branch-protection rename procedure. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md index 3276e517..43fa3317 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md @@ -202,11 +202,11 @@ function Test-CalleeContract([string]$Path, [int]$Timeout) { ### Phase 3 — Commit, Pull Request, and First Green Run -- [ ] [P3-T1] Commit the workflow change set and Phase 0–2 evidence +- [x] [P3-T1] Commit the workflow change set and Phase 0–2 evidence - Files staged (explicit): `.github/workflows/ci.yml`, `.github/workflows/_actionlint.yml`, `.github/workflows/_format-check.yml`, `.github/workflows/_build-analyzers.yml`, `.github/workflows/_build-nullable.yml`, `.github/workflows/_mstest-coverage.yml`, `.github/workflows/README.md`, `FEATURE/evidence/**` (new artifacts), `FEATURE/plan.2026-08-14T09-05.md` (checkbox progress). - Verification: `git status --porcelain` shows no unstaged modifications to `.github/**` or `FEATURE/**` after the commit; `git log -1 --stat` lists the seven workflow-tree files. -- [ ] [P3-T2] Push the branch to origin +- [x] [P3-T2] Push the branch to origin - Command: `git push -u origin <BRANCH>` where `<BRANCH>` is the value recorded by P0-T2 (expected: `feature/ci-parallel-job-split-553`). `-u` is correct: the remote branch does not yet exist. Do not hard-code any other branch name. - Acceptance: push succeeds (exit 0). Note: `ci.yml` does not trigger on push to this branch (`on: push` is main/development only); the pipeline runs on the `pull_request` event after P3-T3. @@ -217,9 +217,10 @@ function Test-CalleeContract([string]$Path, [int]$Timeout) { 3. `artifacts/pr_body_553.md` AND `artifacts/pr_body_553.receipt.json` exist at exactly those canonical paths, with a matching, non-stale SHA-256 receipt per `.claude/skills/pr-author/SKILL.md`. The executor MUST halt at this task and hand off to the orchestrator to satisfy preconditions 1 and 2 and confirm execution; record the confirmation in the P3-T4 artifact. Without recorded confirmation this task is BLOCKED, not skipped. - Procedure (after confirmation and preconditions): produce `artifacts/pr_body_553.md` plus `artifacts/pr_body_553.receipt.json` per the `pr-author` skill, then `gh pr create --base main --title "CI: split quality-gates into five parallel reusable-workflow jobs (#553)" --body-file artifacts/pr_body_553.md`. + - **Execution status (2026-08-14T10-35): DEFERRED to the orchestrator.** No pull request exists and the executor must not create one. The `modified-workflow-needs-green-run` obligation that the PR run would have satisfied is instead satisfied by the green `workflow_dispatch` run 31809697953 against head `0b016c81` (recorded in `FEATURE/evidence/qa-gates/first-run.<TS>.md`), which `remediation-inputs.2026-08-14T10-21.md` finding B1 explicitly accepts as an alternative. This task remains unchecked. - Acceptance: PR exists targeting `main`; `gh pr view --json url,headRefOid` returns the PR URL and head SHA; the PR's own run executes the NEW pipeline (head-ref workflow files run for `pull_request` events, research Q8 fact 1). Expected and acceptable: the PR is blocked by the still-required old context `Format, build, analyze, and test` until Phase 6 (fail-closed over-blocking, never under-gating). -- [ ] [P3-T4] Observe the first run of the split pipeline to completion and record `FEATURE/evidence/qa-gates/first-run.<TS>.md` +- [x] [P3-T4] Observe the first run of the split pipeline to completion and record `FEATURE/evidence/qa-gates/first-run.<TS>.md` - Commands: ```powershell gh run list --branch <BRANCH> --workflow ci.yml --limit 1 --json databaseId,headSha,status,conclusion @@ -230,7 +231,7 @@ function Test-CalleeContract([string]$Path, [int]$Timeout) { - Branching (explicit): (a) all five jobs succeed → record `GREEN` and proceed to P3-T5's NOT-REQUIRED branch; (b) a job fails with a symptom attributable to a trimmed setup step (e.g., missing .NET SDK / `dotnet` not found in an msbuild job, missing restored packages in the format job) → proceed to P3-T5's REQUIRED branch; (c) a job fails for any other cause → halt and report to the orchestrator with the run URL; do not improvise fixes outside this plan. - Acceptance: artifact exists and records one of the three branch outcomes explicitly. -- [ ] [P3-T5] Execute the tailored-setup fallback if and only if P3-T4 took branch (b); record `FEATURE/evidence/qa-gates/tailored-setup-fallback.<TS>.md` +- [x] [P3-T5] Execute the tailored-setup fallback if and only if P3-T4 took branch (b); record `FEATURE/evidence/qa-gates/tailored-setup-fallback.<TS>.md` - REQUIRED branch: restore into the affected callee(s) only the specific setup steps the failure implicates, copied verbatim from `FEATURE/evidence/other/pre-split/ci.yml.pre-split.txt` (msbuild callees: `Setup .NET SDK`; format callee: `Setup NuGet` + `Cache NuGet packages` + `Restore solution`). Re-run P1-T6 containment checks and P2-T3 actionlint, commit (`fix(ci): restore <steps> to <callee> — tailored-setup assumption failed`), push, and repeat P3-T4 observation until branch (a) or (c). Record which steps were restored, to which files, and the final green run id. Spec authorizes this fallback at an estimated ~56s/job cost. - NOT-REQUIRED branch (explicitly authorized skip): if P3-T4 recorded `GREEN`, write the artifact with `Result: NOT REQUIRED — tailored-setup assumption held` and the green run id. This is the only permitted non-executing outcome for this task. - Acceptance: artifact exists recording exactly one branch; the pipeline is green on the current head at task completion. @@ -239,31 +240,31 @@ function Test-CalleeContract([string]$Path, [int]$Timeout) { > Probe rules: each probe is one temporary commit on the PR branch, exercised by CI, then reverted with `git revert --no-edit`. Wait for the probe run to complete BEFORE pushing the revert (`cancel-in-progress: true` would otherwise cancel the probe run). Each probe must fail exactly the targeted gate; if a probe reddens more than one gate, adjust the probe edit and repeat before recording. Net C# diff after reverts is zero (verified in P5-T3). -- [ ] [P4-T1] [expect-fail] Exercise the formatting-violation probe and record `FEATURE/evidence/regression-testing/probe-format.<TS>.md` +- [x] [P4-T1] [expect-fail] Exercise the formatting-violation probe and record `FEATURE/evidence/regression-testing/probe-format.<TS>.md` - Probe edit: a formatting-only change in one `*.cs` file that `csharpier check` rejects (e.g., broken indentation inside one method body); it must introduce no compiler diagnostic. Commit message: `probe(553): formatting violation — to be reverted`. - Sequence: commit → push → `gh run watch <run-id>` → `gh api .../runs/<run-id>/jobs` → assert `format-check` job conclusion `failure` and the other four jobs `success` → `git revert --no-edit <probe-sha>` → push. - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, probe commit SHA, revert commit SHA, run URL, per-job conclusion table, `Output Summary: exactly one red gate (format-check)`. - Acceptance: artifact shows exactly the format gate red; revert commit exists on the branch. Update spec.md seeded-condition checkbox 3 to `[x]` with this artifact as the evidence pointer. -- [ ] [P4-T2] [expect-fail] Exercise the nullable-violation probe and record `FEATURE/evidence/regression-testing/probe-nullable.<TS>.md` +- [x] [P4-T2] [expect-fail] Exercise the nullable-violation probe and record `FEATURE/evidence/regression-testing/probe-nullable.<TS>.md` - Probe edit: in a production `*.cs` file that carries `#nullable enable`, in a project whose `.csproj` does NOT set `TreatWarningsAsErrors` (verify with `Select-String` on the csproj before committing), add a correctly-formatted statement producing a nullable-flow warning (e.g., `string probeValue = null;` assigned to a non-nullable local). This fails only the nullable gate (`/p:TreatWarningsAsErrors=true`); the analyzer gate and the MSTest job's plain build treat it as a warning. Commit message: `probe(553): nullable violation — to be reverted`. - Sequence and contents: same shape as P4-T1; assert `build-nullable` conclusion `failure`, other four `success`; revert and push. - Acceptance: artifact shows exactly the nullable gate red; revert commit exists. Update spec.md seeded-condition checkbox 4 with this artifact as evidence. -- [ ] [P4-T3] [expect-fail] Exercise the test-failure probe and record `FEATURE/evidence/regression-testing/probe-mstest.<TS>.md` +- [x] [P4-T3] [expect-fail] Exercise the test-failure probe and record `FEATURE/evidence/regression-testing/probe-mstest.<TS>.md` - Probe edit: invert one assertion in one existing fast MSTest test (not `TestCategory=LiveOutlook`), keeping the file csharpier-clean and free of new compiler diagnostics. Commit message: `probe(553): deliberate test failure — to be reverted`. - Sequence and contents: same shape as P4-T1; assert `mstest-coverage` conclusion `failure`, other four `success`; revert and push. - Acceptance: artifact shows exactly the MSTest gate red; revert commit exists. Update spec.md seeded-condition checkbox 5 with this artifact as evidence. -- [ ] [P4-T4] Confirm a green run on the post-revert head and record `FEATURE/evidence/qa-gates/post-probe-green-run.<TS>.md` +- [x] [P4-T4] Confirm a green run on the post-revert head and record `FEATURE/evidence/qa-gates/post-probe-green-run.<TS>.md` - Commands: `gh run watch <run-id> --exit-status` on the run triggered by the final revert push; `gh api repos/drmoisan/TaskMaster/actions/runs/<run-id>/jobs --jq '.jobs[] | {name, conclusion}'`. - Acceptance: artifact records run id, head SHA, and all five job conclusions `success`. -- [ ] [P4-T5] Verify the `test-results` artifact on the green run and record `FEATURE/evidence/qa-gates/test-results-artifact.<TS>.md` +- [x] [P4-T5] Verify the `test-results` artifact on the green run and record `FEATURE/evidence/qa-gates/test-results-artifact.<TS>.md` - Command: `gh api repos/drmoisan/TaskMaster/actions/runs/<run-id>/artifacts --jq '.artifacts[] | {name, size_in_bytes}'` (run id from P4-T4). - Acceptance: an artifact named exactly `test-results` exists with non-zero size. Update spec.md seeded-condition checkbox 6 with this artifact as evidence. -- [ ] [P4-T6] Capture post-split per-job timings and write the baseline comparison `FEATURE/evidence/qa-gates/ci-split-timing-comparison.<TS>.md` +- [x] [P4-T6] Capture post-split per-job timings and write the baseline comparison `FEATURE/evidence/qa-gates/ci-split-timing-comparison.<TS>.md` - Command: `gh api repos/drmoisan/TaskMaster/actions/runs/<run-id>/jobs` (same method as the baseline capture; run id from P4-T4). - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`; a per-job table (name, started, completed, duration); measured pipeline wall clock (latest `completed_at` minus earliest `started_at` across the five jobs); comparison row against the measured 444s baseline from `ci-sequential-baseline.2026-08-14T13-05.md` with absolute and percentage delta; summed billed `windows-latest` seconds vs the ~444s baseline; a runner-environment-parity statement (both measurements GitHub-hosted `windows-latest`, satisfying `.claude/rules/benchmark-baselines.md`); a note that the spec's ~277s/~333s figures were estimates and this artifact is the measurement of record. - Acceptance: artifact exists with the comparison table populated from live API data (no placeholder values). Update spec.md seeded-condition checkbox 7 with this artifact as evidence. @@ -272,16 +273,16 @@ function Test-CalleeContract([string]$Path, [int]$Timeout) { > QA-loop note: the applicable language is GitHub Actions YAML. The loop is: (1) formatting — no repo-mandated YAML formatter exists; not applicable; (2) linting — actionlint (P5-T1); (3) type checking — not applicable to YAML; (4) testing — the live green run (P4-T4, re-confirmed on the final head in P5-T15). If any task in this phase changes a workflow file, re-run from P5-T1 and re-run the P1-T6 containment checks. Per the No-C#-Toolchain Statement, csharpier/msbuild/vstest are not part of this loop and must not be run. -- [ ] [P5-T1] Run the final actionlint pass and record `FEATURE/evidence/qa-gates/actionlint-final.<TS>.md` +- [x] [P5-T1] Run the final actionlint pass and record `FEATURE/evidence/qa-gates/actionlint-final.<TS>.md` - Command: `& "<SCRATCH>\actionlint-553\actionlint.exe" -no-color` from the repository root (re-download per P0-T3 if the scratchpad was cleared). - Acceptance: `EXIT_CODE: 0` over all seven workflow files. Update spec.md seeded-condition checkbox 1 with this artifact as evidence. -- [ ] [P5-T2] Verify `$LASTEXITCODE` hygiene across the pwsh-bearing workflow files and record `FEATURE/evidence/qa-gates/lastexitcode-review.<TS>.md` +- [x] [P5-T2] Verify `$LASTEXITCODE` hygiene across the pwsh-bearing workflow files and record `FEATURE/evidence/qa-gates/lastexitcode-review.<TS>.md` - Scope: six of the seven workflow files are enumerated — the five callees plus `ci.yml` (the orchestrator has no steps). `.github/workflows/codex-web-setup-test.yml` is explicitly EXCLUDED from enumeration because it declares no `shell: pwsh` or `shell: powershell` step, so the `.claude/rules/ci-workflows.md` pattern cannot apply to it; the artifact must state this exclusion and reason so the recorded enumeration is consistent with its scope statement. - Method: enumerate every `shell: pwsh` step in the five callees and `ci.yml` (the orchestrator has none); confirm (a) no step intentionally invokes a failing nested command (the `.claude/rules/ci-workflows.md` pattern is therefore not triggered — matches research Q9), and (b) both msbuild gate guards `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` and the vstest `throw` are present, via `Select-String -Path .github/workflows/_build-analyzers.yml,.github/workflows/_build-nullable.yml -Pattern 'if \(\$LASTEXITCODE -ne 0\) \{ exit \$LASTEXITCODE \}'` (2 matches) and `Select-String -Path .github/workflows/_mstest-coverage.yml -Pattern 'throw "MSTest execution failed'` (1 match) and `Select-String -Path .github/workflows/_mstest-coverage.yml -Pattern 'throw "No test assemblies found'` (1 match). - Acceptance: artifact records the step-by-step table and the four match counts. Update spec.md seeded-condition checkbox 8 with this artifact as evidence. -- [ ] [P5-T3] Verify the branch contains zero C#/project-file changes and record `FEATURE/evidence/qa-gates/no-csharp-diff.<TS>.md` +- [x] [P5-T3] Verify the branch contains zero C#/project-file changes and record `FEATURE/evidence/qa-gates/no-csharp-diff.<TS>.md` - Command (two statements — PowerShell does not concatenate a subexpression with a trailing `..HEAD` into one argument): ```powershell $base = git merge-base origin/main HEAD @@ -290,43 +291,43 @@ function Test-CalleeContract([string]$Path, [int]$Timeout) { (the `**/` prefix is required on `packages.config` and `app.config`: a git pathspec with no wildcard is anchored to the repo root and would match zero files; `*.cs` and the other extension globs already match at any depth) - Acceptance: output is empty (probe commits are fully cancelled by their reverts). The artifact restates the No-C#-Toolchain Statement as the justification for the absence of a C# toolchain pass. If output is non-empty, halt and report — an unreverted probe or scope drift exists. -- [ ] [P5-T4] Audit file sizes and record `FEATURE/evidence/qa-gates/file-size-audit.<TS>.md` +- [x] [P5-T4] Audit file sizes and record `FEATURE/evidence/qa-gates/file-size-audit.<TS>.md` - Command: `Get-ChildItem .github/workflows/*.yml, .github/workflows/README.md | ForEach-Object { "{0}`t{1}" -f $_.Name, (Get-Content $_.FullName).Count }` - Acceptance: every listed file is under 500 lines (expected: each callee < 100, `ci.yml` ~32, README < 150). -- [ ] [P5-T5] Link the new workflows README from the feature folder +- [x] [P5-T5] Link the new workflows README from the feature folder - Edit: append to `FEATURE/issue.md` a `## References` entry: `- Workflows README: `.github/workflows/README.md` (created by #553)`. - Acceptance: `Select-String -Path FEATURE/issue.md -Pattern 'workflows/README.md'` matches (satisfies the spec DoD "created and linked from the feature folder" clause, completed by P7-T6). -- [ ] [P5-T6] Check off spec.md acceptance criterion 1 (four gates as separate jobs, zero `needs:` edges) and its mirrors (issue.md AC 1, user-story.md AC 1) +- [x] [P5-T6] Check off spec.md acceptance criterion 1 (four gates as separate jobs, zero `needs:` edges) and its mirrors (issue.md AC 1, user-story.md AC 1) - Evidence pointers: `.github/workflows/ci.yml` (P2-T1 verification 3), `FEATURE/evidence/qa-gates/post-probe-green-run.<TS>.md`. - Acceptance: all three checkboxes `[x]`, each citing the evidence paths. -- [ ] [P5-T7] Check off spec.md acceptance criterion 2 (five callee workflows with the reusable-workflow contract) and its mirrors (issue.md AC 2, user-story.md AC 2) +- [x] [P5-T7] Check off spec.md acceptance criterion 2 (five callee workflows with the reusable-workflow contract) and its mirrors (issue.md AC 2, user-story.md AC 2) - Evidence pointers: the five P1 task verifications (`Test-CalleeContract` results), `FEATURE/evidence/qa-gates/actionlint-final.<TS>.md`. - Acceptance: all three checkboxes `[x]` with evidence paths. -- [ ] [P5-T8] Check off spec.md acceptance criterion 3 (`ci.yml` orchestrator, no inline `steps:`) and its mirrors (issue.md AC 3, user-story.md AC 3) +- [x] [P5-T8] Check off spec.md acceptance criterion 3 (`ci.yml` orchestrator, no inline `steps:`) and its mirrors (issue.md AC 3, user-story.md AC 3) - Evidence pointers: P2-T1 verification outputs (no `steps:` match, five `uses:` references). - Acceptance: all three checkboxes `[x]` with evidence paths. -- [ ] [P5-T9] Check off spec.md acceptance criterion 4 (no cross-job file sharing; `test-results` upload preserved) and its mirrors (issue.md AC 4, user-story.md AC 4) +- [x] [P5-T9] Check off spec.md acceptance criterion 4 (no cross-job file sharing; `test-results` upload preserved) and its mirrors (issue.md AC 4, user-story.md AC 4) - Evidence pointers: `FEATURE/evidence/qa-gates/byte-identity.<TS>.md` (upload block), `FEATURE/evidence/qa-gates/test-results-artifact.<TS>.md`. - Acceptance: all three checkboxes `[x]` with evidence paths. -- [ ] [P5-T10] Check off spec.md acceptance criterion 5 (gate commands and actionlint step byte-identical, incl. `/t:Rebuild` comment, `$LASTEXITCODE` guards, zero-assembly `throw`) +- [x] [P5-T10] Check off spec.md acceptance criterion 5 (gate commands and actionlint step byte-identical, incl. `/t:Rebuild` comment, `$LASTEXITCODE` guards, zero-assembly `throw`) - Evidence pointers: `FEATURE/evidence/qa-gates/byte-identity.<TS>.md`, `FEATURE/evidence/qa-gates/lastexitcode-review.<TS>.md`. - Acceptance: spec checkbox `[x]` with evidence paths (no issue/user-story mirror carries this criterion standalone; their AC 8 equivalents are handled in P7-T3). -- [ ] [P5-T11] Check off spec.md acceptance criterion 7 (README documents dispatch + rename procedures) and its mirrors (issue.md AC 6, user-story.md AC 6) +- [x] [P5-T11] Check off spec.md acceptance criterion 7 (README documents dispatch + rename procedures) and its mirrors (issue.md AC 6, user-story.md AC 6) - Evidence pointers: `.github/workflows/README.md` (P2-T2 heading verification). - Acceptance: all three checkboxes `[x]` with evidence paths. -- [ ] [P5-T12] Check off spec.md acceptance criterion 8 (green run against the branch head, `modified-workflow-needs-green-run`) and its mirrors (issue.md AC 7, user-story.md AC 7) +- [x] [P5-T12] Check off spec.md acceptance criterion 8 (green run against the branch head, `modified-workflow-needs-green-run`) and its mirrors (issue.md AC 7, user-story.md AC 7) - Evidence pointers: `FEATURE/evidence/qa-gates/post-probe-green-run.<TS>.md` (superseded by P5-T15's final-head confirmation if additional commits landed in this phase). - Acceptance: all three checkboxes `[x]` with evidence paths. -- [ ] [P5-T13] Check off spec.md acceptance criterion 10 (post-split wall clock measured with the baseline's collection method and recorded) +- [x] [P5-T13] Check off spec.md acceptance criterion 10 (post-split wall clock measured with the baseline's collection method and recorded) - Evidence pointers: `FEATURE/evidence/qa-gates/ci-split-timing-comparison.<TS>.md`. - Acceptance: spec checkbox `[x]` with evidence path. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T10-21.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T10-21.md new file mode 100644 index 00000000..1fa621bb --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T10-21.md @@ -0,0 +1,197 @@ +# Policy Compliance Audit — ci-parallel-job-split (Issue #553) + +- **Component:** GitHub Actions CI pipeline (`.github/workflows/`) +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T10-21) +- **Reviewer:** feature-review agent +- **Base branch:** `main` (resolved `origin/main`) +- **Merge base:** `2073f717bbfac30053f3d6a4e652d99af3ae5c9c` (independently recomputed via `git merge-base HEAD origin/main`; matches caller-supplied value) +- **Branch head:** `feature/ci-parallel-job-split-553` @ `0b016c81a78f3fafc0864de472f4139cc0938002` (3 commits: `e246688b`, `955e17fa`, `0b016c81`) +- **PR context:** `artifacts/pr_context.summary.txt` / `artifacts/pr_context.appendix.txt` — fresh (recorded head SHA matches current `git rev-parse HEAD`) +- **Work mode:** `full-feature` (persisted marker in `issue.md`); AC sources: `spec.md` and `user-story.md` +- **Files under audit:** full branch diff vs merge base — 37 files (6 workflow YAML files, `.github/workflows/README.md`, 22 feature-folder docs/evidence files, 2 archival promoted-potential copies, 6 agent-memory files) + +## Executive Summary + +This branch decomposes the monolithic `quality-gates` CI job into five callee reusable workflows invoked by a pure-orchestrator `ci.yml`. The diff contains zero source-code files in any coverage-bearing language: `git diff --name-only 2073f717..HEAD` matched against `*.cs, *.csproj, *.props, *.targets, *.ps1, *.psm1, *.psd1, *.py, *.ts, *.tsx, *.js` returns empty. The change set is workflow YAML plus Markdown documentation and evidence. + +Verdicts: + +- Gate-transplant fidelity: **PASS** — byte-identity of every transplanted gate `run:` block was independently re-verified by this review (extraction + SHA-256 comparison against the merge-base `ci.yml`), not taken from the committed evidence artifact. All 14 compared blocks match. +- Workflow lint: **PASS** — actionlint 1.7.7 exit 0 across all 7 workflow files (`evidence/qa-gates/actionlint-postchange.2026-08-14T09-54.md`); pre-change baseline also clean. +- `.claude/rules/ci-workflows.md`: **PASS** — no `pwsh` step uses the deliberately-failing nested-command pattern (assessment in section 3.2). +- `.claude/rules/benchmark-baselines.md`: **PASS** with note — runner-environment parity satisfied; the sibling-provenance requirement is scoped to baselines consumed by a benchmark regression gate, which this baseline is not (section 3.3). +- Evidence locations: **PASS** — all evidence under the canonical `docs/features/active/<feature>/evidence/` tree; zero diff files under `artifacts/baselines/`, `artifacts/qa/`, `artifacts/evidence/`, or `artifacts/coverage/`. +- `modified-workflow-needs-green-run`: **FAIL (Blocking)** — the branch modifies `.github/workflows/**` and no green workflow run against head `0b016c81` exists or is evidenced. This is the single blocking finding; it is procedural and is already scheduled by the plan of record (Phases 3–5), which require a live PR. See section 7.3 and `remediation-inputs.2026-08-14T10-21.md`. + +No caller-supplied scope narrowing was detected; the caller's factual notes were verified independently and the audit scope is the full branch diff vs `main`. + +## 1. General Unit Test Policy Compliance + +| Check | Verdict | Evidence | +| --- | --- | --- | +| New or modified unit tests present | None in diff | `git diff --name-status` — zero test files changed | +| Test independence / isolation / determinism | Not exercised by this diff | No test code changed | +| Temporary-file prohibition in tests | Not exercised | No test code changed | +| Test file location rule | Not exercised | No test files added or moved | + +The MSTest suite itself is unmodified; the vstest invocation that runs it in CI is transplanted byte-identically (verified in section 7.2), so test execution semantics in CI are unchanged. + +### 1.1 Changed-language enumeration (full branch diff) + +Enumeration command: `git diff --name-only 2073f717bbfac30053f3d6a4e652d99af3ae5c9c..HEAD` filtered per extension. + +| Language | Changed files in branch diff | Coverage gate | +| --- | --- | --- | +| C# (`.cs`/`.csproj`/`.props`/`.targets`) | 0 | Zero changed files; no per-language coverage measurement is triggered for this branch | +| PowerShell (`.ps1`/`.psm1`/`.psd1`) | 0 | Zero changed files; the `pwsh` `run:` blocks inside workflow YAML are transplanted verbatim and are not PowerShell script files | +| Python (`.py`) | 0 | Zero changed files | +| TypeScript (`.ts`/`.tsx`) | 0 | Zero changed files | + +Note on the PR-context summary: the "Changed files overview" classifies all 29 listed files as docs/tooling and lists 0 core-logic files. Unlike prior C#-misclassification incidents, this classification is materially accurate for this branch — the only non-doc changes are workflow YAML, which has no coverage denominator. Verified against `git diff` directly, not trusted from the summary. + +## 2. General Code Change Policy Compliance + +| Check | Verdict | Evidence | +| --- | --- | --- | +| Simplicity / separation of concerns | PASS | Orchestrator/callee split follows the repo's mandated reusable-workflow pattern (`.claude/skills/orchestrate/SKILL.md` § GitHub Actions Reusable Workflows); one gate per callee | +| 500-line file limit | PASS | `ci.yml` 32, `_actionlint.yml` 29, `_format-check.yml` 41, `_build-analyzers.yml` 53, `_build-nullable.yml` 60, `_mstest-coverage.yml` 96 lines (verified `grep -c ""`; matches the figures in `byte-identity.2026-08-14T09-54.md`). Markdown files are exempt by rule | +| Fail fast / error handling | PASS | `$LASTEXITCODE` guards and `throw` guards preserved verbatim; `set -euo pipefail` retained in the actionlint step | +| No new dependencies | PASS | All actions (`actions/checkout@v4`, `actions/setup-dotnet@v4`, `microsoft/setup-msbuild@v2`, `nuget/setup-nuget@v2`, `actions/cache@v4`, `actions/upload-artifact@v4`) already in use at the merge base | +| Supporting docs updated | PASS | `.github/workflows/README.md` created (was referenced by `.claude/skills/orchestrate/SKILL.md` before it existed; see section 7.5) | +| Bugfix workflow | Not applicable — feature work, not a defect fix | — | +| Policy documents modified | PASS (none) | Diff touches no `.claude/rules/` or `.github/instructions/` file | + +## 3. Language-Specific Code Change Policy Compliance + +### 3.1 C# Code Change Policy + +Zero C# source, test, or build-configuration files changed (verified: `git diff --name-only <merge-base>..HEAD -- '*.cs' '*.csproj' '*.props' '*.targets'` returns empty). The C# toolchain loop is therefore not required for this branch, consistent with spec.md Definition of Done item 5. The CI gate commands that enforce the C# toolchain were verified byte-identical (section 7.2), so this branch does not alter C# enforcement semantics. + +### 3.2 `.claude/rules/ci-workflows.md` — deliberately-failing nested command pattern + +Every `pwsh` step in the new callees was assessed individually: + +| File | `pwsh` step | Intentionally-failing nested command? | Assessment | +| --- | --- | --- | --- | +| `_format-check.yml` | `Setup CSharpier` (`dotnet tool restore`) | No | Failure is a genuine step failure; propagation intended | +| `_format-check.yml` | `Verify formatting` (`dotnet csharpier check .`) | No | Non-zero exit IS the gate signal | +| `_build-analyzers.yml` | `Restore solution`, analyzer build | No | Explicit `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` guard propagates deliberately | +| `_build-nullable.yml` | `Restore solution`, nullable build | No | Same guard, preserved verbatim with the `/t:Rebuild` rationale comment | +| `_mstest-coverage.yml` | `Restore solution`, `Build solution`, vstest step | No | Build step carries the same exit guard; vstest step `throw`s on non-zero, with `Set-StrictMode` and `$ErrorActionPreference = 'Stop'` | + +No step invokes a command expected to fail as part of a passing path, so the rule's reset/`exit 0` requirement does not apply to any step in the committed pipeline. The README's Rules section states this explicitly and correctly distinguishes gate-signal propagation from residual-exit-code leakage. **Verdict: PASS.** + +### 3.3 `.claude/rules/benchmark-baselines.md` — measured latency baseline + +The branch commits `evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md`, a measured 444s baseline. + +- **Rule scope check:** the rule states "This rule applies to any baseline consumed by a benchmark regression gate." This baseline is a one-time comparison reference for spec AC 10 (post-split duration comparison); it is not consumed by `scripts/benchmarks/**` tooling or any automated regression gate, and it is Markdown, not BenchmarkDotNet JSON (so the `HostEnvironmentInfo.ProcessorName` rejection condition cannot apply structurally). +- **Runner-environment parity (the rule's substantive requirement):** satisfied. The baseline was captured from GitHub-hosted `windows-latest` run 31749877507 (URL recorded), and both spec.md and the README require the post-split measurement to use the same `gh api .../runs/<id>/jobs` collection method on a GitHub-hosted run. +- **Sibling `baseline.provenance.json`:** absent, and not required under the rule's scope clause for a baseline that no regression gate consumes. The baseline nonetheless records the provenance fields inline (runner class, workflow run URL, collection command). Recorded as a non-blocking observation in the code review (finding F3): if this baseline is ever wired into an automated regression gate, a sibling provenance file must be added first. + +**Verdict: PASS**, with observation F3. + +### 3.4 PowerShell / Python / TypeScript policies + +Not triggered: zero changed script files in those languages. The embedded `pwsh` blocks are governed by section 3.2, not by the PowerShell script-file policy (they are not `.ps1` files, are transplanted verbatim, and PoshQC does not target workflow YAML). + +## 4. Language-Specific Unit Test Policy Compliance + +No unit tests in any language were added or modified. MSTest/Moq/FluentAssertions framework rules are not exercised. **Verdict: not triggered by this diff.** + +## 5. Test Coverage Detail + +Coverage verification is mandatory for every language with changed files in the branch diff. Per section 1.1, the branch diff contains **zero changed files in every coverage-bearing language** (C#, PowerShell, Python, TypeScript), verified directly against `git diff --name-only` rather than the PR-context summary classification. + +- C# — 0 changed files; no C# coverage row is required for this branch and no C# coverage artifact is required to exist for it. +- PowerShell — 0 changed `.ps1`/`.psm1` files; no PowerShell coverage row is required for this branch. +- Python — 0 changed files; no Python coverage row is required. +- TypeScript — 0 changed files; no TypeScript coverage row is required. + +No per-file, new-file, or repo-wide coverage threshold applies to workflow YAML or Markdown. The CI coverage-producing step (`vstest.console.exe /EnableCodeCoverage`) is preserved byte-identically, and the `test-results` artifact upload (`TestResults/**/*.trx`, `TestResults/**/*.coverage`) is unchanged, so this branch does not reduce any coverage signal produced by CI. + +## 6. Test Execution Metrics + +No local test executions were required or run (no code changed in any tested language). Checks executed by the implementing session and verified by this review from committed evidence: + +| Check | Result | Evidence | +| --- | --- | --- | +| actionlint pre-change baseline | exit 0, 0 findings, 2 files | `evidence/baseline/actionlint-baseline.2026-08-14T09-54.md` | +| actionlint post-change | exit 0, 0 findings, 7 files (verbose per-file table) | `evidence/qa-gates/actionlint-postchange.2026-08-14T09-54.md` | +| Byte-identity of transplanted blocks | 6/6 containment + 6/6 SHA-256, 12/12 fragment citations | `evidence/qa-gates/byte-identity.2026-08-14T09-54.md` | +| Byte-identity — independent re-verification by this review | 14/14 blocks MATCH | Section 7.2 (Appendix B command 3) | + +The local actionlint run notes a genuine local-vs-CI gap (shellcheck/pyflakes integrations unavailable on the Windows host); the authoritative verification is the green run on the branch head, which is the open blocking item. + +## 7. Code Quality Checks + +### 7.1 Structural verification of the split (this review, direct file inspection) + +- `ci.yml` (32 lines): five jobs, each `uses: ./.github/workflows/_<name>.yml`; no `steps:`, no `needs:` (grep verified across all six workflow files); `name:`, triggers, `permissions: contents: read`, and the concurrency block (`group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}`, `cancel-in-progress: true`) are unchanged from the merge base. +- All five callees declare `on: workflow_call:` + `on: workflow_dispatch:`, own `permissions: contents: read`, right-sized `timeout-minutes` (10/10/30/30/30), and no `concurrency` block (grep verified). +- Only one `upload-artifact` in the new pipeline (`test-results` in `_mstest-coverage.yml`, `if: always()`, same name/paths/`if-no-files-found: warn`); zero `download-artifact` — no cross-job file sharing. + +### 7.2 Byte-identity — independently verified + +The caller instructed verification against the actual files rather than the committed artifact. This review extracted each step `run:` block (and full step blocks for cache/upload steps) from the merge-base `ci.yml` (`git show 2073f717:.github/workflows/ci.yml`) and from the new callees, dedented uniformly, and compared SHA-256 digests. Result: **14/14 MATCH** — actionlint run block; `dotnet tool restore`; `dotnet csharpier check .`; `nuget restore` (x3 callees); analyzer msbuild block with guard; nullable msbuild block with the full 7-line `/t:Rebuild` rationale comment and guard; vstest block with the zero-assembly `throw`, discovery filter, and `/EnableCodeCoverage /InIsolation /Logger:trx /TestCaseFilter:"TestCategory!=LiveOutlook"`; the `Upload test results` step block; both cache step blocks. This independently corroborates `evidence/qa-gates/byte-identity.2026-08-14T09-54.md` (whose six SHA-256 rows and 12 fragment citations are consistent with these findings). + +Gate-equivalence of the new `Build solution` step in `_mstest-coverage.yml` (no pre-split counterpart): verified to carry **no** `EnableNETAnalyzers`, `EnforceCodeStyleInBuild`, `TreatWarningsAsErrors`, or other warning-promotion property (grep count 0 in the file). It neither weakens nor duplicates the analyzer/nullable gates: those remain enforced solely by their own required contexts, and a plain `/t:Build` produces functionally identical assemblies for test discovery (the analyzer/nullable properties affect diagnostics, not emitted behavior). The step carries the standard exit guard. Assessed: **no gate is weakened or altered**. + +### 7.3 `modified-workflow-needs-green-run` (feature-review policy rule) + +Trigger check (performed manually; `scripts/feature-review/Test-ModifiedWorkflowNeedsGreenRun.ps1` does not exist in this repository — see section 8): `git diff --name-only <merge-base>..HEAD` matches `.github/workflows/**` (7 files: `ci.yml` modified, 5 callees added, `README.md` added). No path matches `scripts/benchmarks/**` or `.github/actions/**`. + +Evidence check: no green workflow run against branch head `0b016c81` exists — the branch has not been pushed/PR'd (plan tasks P3-T1..P3-T4 unchecked by design), GitHub CLI is unavailable in this environment (per the PR-context artifact), and no green-run evidence artifact exists in the feature folder. + +**Verdict: FAIL — Blocking finding B1**, recorded in `remediation-inputs.2026-08-14T10-21.md`. Note: the spec, plan, and README all correctly anticipate this rule; the plan schedules the green run (P3-T4), the pre-migration green confirmation (P5-T15), and the `workflow_dispatch` fallback path. The finding is procedural sequencing (the rule cannot be satisfied before a live run exists), not a defect in the change set. + +### 7.4 Required-status-check contract — under-gating analysis + +The `main` ruleset (id 18572843, `strict_required_status_checks_policy: true`) requires `actionlint` and `Format, build, analyze, and test`. After this change both contexts cease to report (the actionlint job now reports in `<caller job> / <callee job>` form; the monolith job no longer exists). Consequence assessed from the ruleset semantics: required contexts that never report **block** merging. Both pre-PUT orderings therefore over-block; nothing in this branch can under-gate `main`. The only under-gating hazard is an incomplete contexts set in the ruleset PUT, which the spec's Required-Status-Check Contract (single atomic PUT of the full writable object, contexts captured from a live run, two-step edit prohibited) and the README's step-by-step procedure both address correctly. The PUT itself is deliberately not performed on this branch (it requires a live green run first, and plan P6-T3 marks it orchestrator-confirmation-required). **Verdict: PASS for the change set and documentation; execution pending (spec AC 6, tracked in remediation inputs as a dependency of B1's resolution sequence).** + +### 7.5 README vs `.claude/skills/orchestrate/SKILL.md` § GitHub Actions Reusable Workflows + +The skill section requires `.github/workflows/README.md` to contain "the full per-stage dispatch and branch-protection rename procedure." The README provides: a per-stage `workflow_dispatch` procedure (`gh workflow run` commands, UI path, and two correctly-stated caveats about standalone runs) and a five-step branch-protection rename procedure (fail-closed rationale, live-name capture, atomic PUT with writable-fields payload construction, prohibition of two-step edits, verification GET, evidence recording, rollback). It also documents the naming convention, one-level nesting, no-implicit-cross-job-filesystem property, and the two governing rules. **Verdict: PASS** — the section's requirement is satisfied. One wording imprecision noted (code review finding F2). + +### 7.6 Evidence Location Compliance + +`scripts/dev_tools/validate_evidence_locations.py` does not exist in this repository (see section 8); the equivalent check was performed directly: `git diff --name-only <merge-base>..HEAD` filtered for `^artifacts/(baselines|qa|evidence|coverage)/` returns **zero** files. All evidence artifacts on the branch live under the canonical `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/<kind>/` tree (`baseline/`, `qa-gates/`, `other/`, including `other/pre-split/`). **Verdict: PASS.** No `EVIDENCE_LOCATION_OVERRIDE_REJECTED` events: no caller instruction supplied a non-canonical evidence path. + +## 8. Gaps and Exceptions + +1. **Blocking gap B1:** no green workflow run against branch head `0b016c81` (`modified-workflow-needs-green-run`). Routed to remediation inputs. Resolution requires the live-PR phases of the plan of record (P3 onward). +2. **MCP template/validator tooling unavailable in this session.** `mcp__drm-copilot__resolve_policy_audit_template_asset` and `mcp__drm-copilot__validate_orchestration_artifacts` are not in this agent's tool surface. Per `policy-audit-template-usage` § Template Source fallback, this artifact reproduces the canonical major-heading set enumerated in that skill's prose. Documented assumption, consistent with prior review cycles in this repository. +3. **Referenced validator scripts absent from this repository:** `scripts/feature-review/Test-ModifiedWorkflowNeedsGreenRun.ps1` and `scripts/dev_tools/validate_evidence_locations.py` are named by skills but do not exist. Their checks were performed manually with `git diff --name-only` filters (sections 7.3, 7.6). Pre-existing documentation gap, not introduced by this branch. +4. **Artifact-layout conflict between skills (pre-existing, recorded for resolution):** `remediation-handoff-atomic-planner` specifies `audit/<ts>/` and `remediation/<ts>/` folder layouts, while the enforced hook `.claude/hooks/validate-feature-review-coverage.ps1` requires the flat `docs/features/active/<slug>/<stem>.<timestamp>.md` form. This review uses the flat form (the enforced contract). Likewise, `feature-review-workflow` step 8 assigns remediation-plan creation to the reviewer while `remediation-handoff-atomic-planner` assigns plan authorship to `atomic-planner`; this review follows the handoff skill and writes remediation inputs only. +5. **PR-context summary autoclose list** contains spurious author-asserted tokens (`#ISO-8601`, `#SHA-256`) harvested from prose. Generator quirk; noted so the eventual PR body (authored via the `pr-author` skill) lists only #553. + +## 9. Summary of Changes + +- `.github/workflows/ci.yml`: 160 → 32 lines; rewritten as a pure orchestrator (5 `uses:` jobs, zero `needs:`, zero inline steps); header/triggers/permissions/concurrency unchanged. +- `.github/workflows/_actionlint.yml`, `_format-check.yml`, `_build-analyzers.yml`, `_build-nullable.yml`, `_mstest-coverage.yml`: new callee reusable workflows; all gate commands transplanted byte-identically; per-job tailored setup; one new plain `Build solution` step in the MSTest callee (assessed gate-neutral, section 7.2). +- `.github/workflows/README.md`: new; documents pipeline topology, per-stage dispatch, and branch-protection rename procedures (section 7.5). +- Feature folder `docs/features/active/2026-08-14-ci-parallel-job-split-553/`: issue/spec/user-story/plan/research plus baseline, pre-split reference, and QA-gate evidence. +- `docs/features/potential/promoted/`: two archival copies recording issues #554 and #555 (latent defects found during this feature's orchestration; documentation only). +- `.claude/agent-memory/`: routine agent memory updates (6 files). + +## 10. Compliance Verdict + +**FAIL (blocking) — one finding.** The change set itself is compliant in every inspected dimension (structure, byte-identity, lint, file sizes, evidence locations, both governing CI rules, documentation obligations), and no acceptance criterion regressed. The single blocking finding is the unconditional `modified-workflow-needs-green-run` rule: workflow files changed and no green run against head `0b016c81` exists yet. This is expected at this stage of the plan of record — the remaining phases require a live PR — and is routed through `remediation-inputs.2026-08-14T10-21.md` for the standard handoff. Blocking finding count: **1**. + +## Appendix A: Test Inventory + +No test files were added, removed, or modified on this branch. The CI test gate (`_mstest-coverage.yml`) executes the pre-existing MSTest suite with an unchanged vstest invocation, unchanged discovery filter (`\bin\Debug\`, excluding `\obj\` and `\ref\`), unchanged `TestCategory!=LiveOutlook` filter, and unchanged zero-assembly `throw` guard. + +## Appendix B: Toolchain Commands Reference + +Commands executed by this review (check-only; no mutation of source or policy files): + +1. `git rev-parse HEAD` / `git merge-base HEAD origin/main` — base/head resolution (recomputed, not trusted from caller). +2. `git diff --name-status 2073f717bbfac30053f3d6a4e652d99af3ae5c9c..HEAD` and extension-filtered `git diff --name-only` — full-diff scope and changed-language enumeration. +3. `git show 2073f717:.github/workflows/ci.yml` + Python block-extraction/SHA-256 comparison script (scratchpad) — independent byte-identity verification, 14/14 MATCH. +4. `grep -n "needs:\|concurrency" .github/workflows/_*.yml` — structural invariants (zero matches). +5. `grep -c ""` per workflow file — 500-line-limit audit. +6. `grep -rn "download-artifact\|upload-artifact" .github/workflows/` — cross-job file-sharing audit. +7. Evidence-location filter: `git diff --name-only <merge-base>..HEAD | grep -E '^artifacts/(baselines|qa|evidence|coverage)/'` — zero matches. + +Checks verified from committed executor evidence rather than re-run: actionlint pre/post (exit 0; the actionlint binary is not installed on this host and CI re-runs it authoritatively on the branch head). diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T10-21.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T10-21.md new file mode 100644 index 00000000..a8b4cf1f --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T10-21.md @@ -0,0 +1,40 @@ +# Remediation Inputs — ci-parallel-job-split (Issue #553) + +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T10-21) +- **Source audits:** + - `docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T10-21.md` + - `docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T10-21.md` + - `docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T10-21.md` +- **Base:** `main` @ merge base `2073f717bbfac30053f3d6a4e652d99af3ae5c9c` +- **Head:** `feature/ci-parallel-job-split-553` @ `0b016c81a78f3fafc0864de472f4139cc0938002` +- **Blocking finding count: 1** + +## Finding B1 — no green workflow run against the branch head + +- Severity: Blocking +- Rule: `modified-workflow-needs-green-run` (`.claude/skills/feature-review-workflow/SKILL.md` § Policy Rules; policy audit § 7.3; code review finding F1) +- Trigger: the branch diff modifies `.github/workflows/**` (7 files: `ci.yml` modified; `_actionlint.yml`, `_format-check.yml`, `_build-analyzers.yml`, `_build-nullable.yml`, `_mstest-coverage.yml`, `README.md` added). +- Evidence gap: no workflow run — PR-context or `workflow_dispatch` — with head SHA `0b016c81a78f3fafc0864de472f4139cc0938002` and conclusion `success` exists or is evidenced anywhere in the feature folder. The branch has not been pushed; plan tasks P3-T1 through P3-T4 are unchecked by design. +- Nature: procedural sequencing, not a code defect. The rule is unconditional and cannot be satisfied before a live run exists. Local actionlint (exit 0, 7 files) cannot substitute; the spec's tailored-setup assumption (msbuild callees without `setup-dotnet`; format callee without `nuget restore`) is explicitly unverified until the first runner execution (spec Residual risk 2, with a documented fallback at ~56s/job). + +### Required remediation + +Execute the pending phases of the plan of record, `docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md` (P3 onward). Minimum path to clear B1: + +1. Commit review artifacts and push the branch (plan P3-T1, P3-T2). +2. Open the PR to `main` via the `pr-author` skill (plan P3-T3 — **orchestrator-confirmation-required**; list only #553 as the closing issue; ignore the spurious `#ISO-8601` / `#SHA-256` tokens in the generated summary). +3. Observe the first split-pipeline run to completion and record `evidence/qa-gates/first-run.<TS>.md` (plan P3-T4). If the tailored-setup assumption fails, execute the fallback (plan P3-T5) and re-run to green. +4. A green `workflow_dispatch` run against the branch head also satisfies the rule if the PR path is blocked. + +Note: clearing B1 unlocks the remaining FAIL-pending acceptance criteria, which are not separate blocking findings but must complete before merge: context-name capture (P5-T16), atomic ruleset PUT with pre/payload/post evidence (P6-T1..T4; P6-T3 orchestrator-confirmation-required; spec AC S6/S9), and post-split timing measurement (P4-T6; spec AC S10). + +## Non-blocking findings (no remediation plan required; fix opportunistically) + +- F2 (Minor): `.github/workflows/README.md` line 82 — reword the "`CI / <gate>` context" phrase to the `<caller job> / <callee job>` context-name form used elsewhere in the document and in spec.md. +- F3 (Minor): add a sibling `baseline.provenance.json` to `evidence/baseline/ci-sequential-baseline.2026-08-14T13-05.md` only if that baseline is ever consumed by an automated benchmark regression gate; no action required now (rule-scope assessment in policy audit § 3.3). + +## Handoff + +Per `remediation-handoff-atomic-planner`, the remediation plan is authored by `atomic-planner`, not by this reviewer. Recommended handoff: delegate to `atomic-planner` with this file as primary context. Because the remediation for B1 is exactly the unexecuted remainder of the existing plan of record (`plan.2026-08-14T09-05.md`, Phases 3–7, already preflighted and partially executed), the planner may ratify that plan as the remediation plan of record rather than authoring a duplicate; two of its tasks (P3-T3, P6-T3) are marked orchestrator-confirmation-required and must not be executed autonomously. + +Documented layout note: this file uses the flat `remediation-inputs.<timestamp>.md` form required by the enforced hook `.claude/hooks/validate-feature-review-coverage.ps1`, not the `remediation/<ts>/` folder layout described in the handoff skill (pre-existing skill/hook conflict, recorded in policy audit § 8.4). diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md index 5c7722f6..4b5e229b 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md @@ -321,22 +321,22 @@ determinable from repository data (research Q7). ## Acceptance Criteria -- [ ] The formatting gate, the analyzer build gate, the nullable build gate, and +- [x] The formatting gate, the analyzer build gate, the nullable build gate, and the MSTest gate each run as separate GitHub Actions jobs with **zero** `needs:` edges (no build-output artifact sharing exists, so no artifact-consumption edge is justified). -- [ ] Five callee reusable workflows exist — `_actionlint.yml`, +- [x] Five callee reusable workflows exist — `_actionlint.yml`, `_format-check.yml`, `_build-analyzers.yml`, `_build-nullable.yml`, `_mstest-coverage.yml` — each declaring both `on: workflow_call:` and `on: workflow_dispatch:`, its own `permissions:`, a right-sized `timeout-minutes`, and no `concurrency` block. -- [ ] `ci.yml` is an orchestrator containing only `uses:` job references and no +- [x] `ci.yml` is an orchestrator containing only `uses:` job references and no inline `steps:` (the `actionlint` extraction resolves the criterion as originally drafted in `issue.md`). -- [ ] No file is shared between jobs; the only artifact operation is the preserved +- [x] No file is shared between jobs; the only artifact operation is the preserved `test-results` upload (workflow storage, `if: always()`, same name and paths). -- [ ] The four gate commands and the actionlint step are byte-identical to their +- [x] The four gate commands and the actionlint step are byte-identical to their pre-split counterparts, including the `/t:Rebuild` rationale comment, the `$LASTEXITCODE` guards, and the zero-test-assembly `throw` guard. - [ ] The `main` ruleset's `required_status_checks` contexts are replaced in one @@ -344,13 +344,13 @@ determinable from repository data (research Q7). the PR head, with no window in which a merge can bypass a gate, and the pre-PUT JSON, PUT payload, and post-PUT GET response are recorded as evidence. -- [ ] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` +- [x] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` procedure and the branch-protection rename procedure. -- [ ] The reworked pipeline produces a green run against the branch head, +- [x] The reworked pipeline produces a green run against the branch head, satisfying `modified-workflow-needs-green-run`. - [ ] Every gate enforced by the current `quality-gates` job is still enforced after the split; no check is dropped, weakened, or made non-required. -- [ ] Post-split wall-clock duration is measured with the same collection method +- [x] Post-split wall-clock duration is measured with the same collection method as the baseline and recorded as evidence in this feature folder, compared against the measured 444s baseline. @@ -367,19 +367,19 @@ determinable from repository data (research Q7). ## Seeded Test Conditions (from potential) -- [ ] `actionlint` passes against every new and modified workflow file. +- [x] `actionlint` passes against every new and modified workflow file. - [ ] Each `_<name>.yml` is independently dispatchable via `workflow_dispatch` and succeeds standalone. -- [ ] A deliberate formatting violation fails only the formatting gate and reports +- [x] A deliberate formatting violation fails only the formatting gate and reports a distinct red check. (Exercised as a temporary probe commit on the PR branch, then reverted — not as a permanent workflow step, so no deliberately-failing nested command enters the committed pipeline.) -- [ ] A deliberate nullable violation fails only the nullable gate. (Same +- [x] A deliberate nullable violation fails only the nullable gate. (Same probe-commit method.) -- [ ] A deliberate test failure fails only the MSTest gate. (Same probe-commit +- [x] A deliberate test failure fails only the MSTest gate. (Same probe-commit method.) -- [ ] Test results and coverage artifacts continue to upload with the same names. -- [ ] Total wall-clock duration of the reworked pipeline is measured against the +- [x] Test results and coverage artifacts continue to upload with the same names. +- [x] Total wall-clock duration of the reworked pipeline is measured against the current sequential baseline and recorded as evidence. -- [ ] No `pwsh` step leaks a residual non-zero `$LASTEXITCODE` per +- [x] No `pwsh` step leaks a residual non-zero `$LASTEXITCODE` per `.claude/rules/ci-workflows.md`. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md index f9dfd353..50a6526d 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md @@ -95,19 +95,19 @@ These criteria are consistent with the draft in `issue.md`, resolved against the adopted design (research topology (c) plus actionlint extraction). Where a draft criterion admitted alternatives, the resolved form is stated. -- [ ] The formatting gate, the analyzer build gate, the nullable build gate, and +- [x] The formatting gate, the analyzer build gate, the nullable build gate, and the MSTest gate each run as separate GitHub Actions jobs with no `needs:` edge forcing them to serialize. Resolved form: the adopted topology shares no build output between jobs, so the pipeline contains **zero** `needs:` edges (the draft's artifact-consumption exception is unused). -- [ ] Each gate is a callable reusable workflow `_<name>.yml` declaring both +- [x] Each gate is a callable reusable workflow `_<name>.yml` declaring both `on: workflow_call:` and `on: workflow_dispatch:`. -- [ ] `ci.yml` becomes an orchestrator workflow containing only `uses:` references +- [x] `ci.yml` becomes an orchestrator workflow containing only `uses:` references and no inline `steps:`. Resolved form: the `actionlint` job is also extracted into `_actionlint.yml`, so this criterion holds without exception and the pipeline comprises five callee workflows and five required status-check contexts. -- [ ] Any file shared between jobs crosses the boundary via explicit +- [x] Any file shared between jobs crosses the boundary via explicit `actions/upload-artifact` + `actions/download-artifact`. Resolved form: no cross-job file sharing exists in the adopted topology; the only artifact operation is the preserved `test-results` upload to workflow storage, with @@ -117,9 +117,9 @@ criterion admitted alternatives, the resolved form is stated. gate: exact context strings are captured from a live green run on the PR head, and the update is a single atomic PUT of the full writable ruleset object. -- [ ] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` +- [x] `.github/workflows/README.md` documents the per-stage `workflow_dispatch` procedure and the branch-protection rename procedure. -- [ ] The reworked pipeline produces a green run against the branch head, +- [x] The reworked pipeline produces a green run against the branch head, satisfying `modified-workflow-needs-green-run`. - [ ] Every gate enforced by the current `quality-gates` job is still enforced after the split; no check is dropped, weakened, or made non-required. In From 560edd8f71c413a7db8fd6e150f48dfb2cae2f3a Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 11:27:01 -0400 Subject: [PATCH 11/13] docs(553): record check-run names, pre-migration green run, and timing addendum Captures the final pre-migration evidence for the CI parallel job split: - check-run-names: the five context strings observed on the branch head, confirming the '<caller job id> / <callee job name>' form. Both previously required contexts are obsolete: 'Format, build, analyze, and test' and the bare 'actionlint', which became 'actionlint / actionlint' when it moved into a callee. - pre-migration-green: the green run recorded immediately before the ruleset migration. - timing addendum: adds a third sample and reclassifies run 31812508684 as a hosted-runner outlier. Three samples measured 259s, 433s, and 245s against the 444s sequential baseline; median 259s, a 41.7% reduction. Workflow files are unchanged by this commit, so the five check-run contexts are unaffected by the head advancing. Refs #553 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZFrbwyXBnwJ44RRJV1N4E --- .../project_pwsh_git_gh_cli_gotchas.md | 11 ++- .claude/agent-memory/feature-review/MEMORY.md | 1 + .../project_553_ci_split_review_pattern.md | 12 +++ .../other/check-run-names.2026-08-14T11-23.md | 97 +++++++++++++++++++ ...plit-timing-comparison.2026-08-14T11-10.md | 31 ++++++ .../post-split-timing.provenance.json | 14 +++ .../pre-migration-green.2026-08-14T11-23.md | 74 ++++++++++++++ .../plan.2026-08-14T09-05.md | 6 +- 8 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 .claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/check-run-names.2026-08-14T11-23.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/pre-migration-green.2026-08-14T11-23.md diff --git a/.claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md b/.claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md index 67c60f8c..53e4af4d 100644 --- a/.claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md +++ b/.claude/agent-memory/atomic-executor/project_pwsh_git_gh_cli_gotchas.md @@ -38,7 +38,16 @@ run `<tool> -h` during preflight when a plan hard-codes flags. Also note `-verbo prints `Collected N YAML files` / `Found 0 errors in N files`, which is how you prove a lint run actually covered the file set instead of silently skipping it. -**Why:** all four produce a wrong result rather than an obvious error — #2 emits a usage +**5. `gh workflow run --ref <branch>` races `git push` and silently runs the OLD sha.** +Verified 2026-08-14 on #553: `git push && gh workflow run ci.yml --ref <branch>` produced +a run whose `head_sha` was the PREVIOUS commit, because GitHub resolved the ref before the +push replicated. For a fault-isolation probe this is the worst possible failure — the run +goes GREEN and looks like the probe proved the gate does not fire. Always verify +`gh run list --json headSha` (or `gh api .../runs/<id> --jq .head_sha`) equals the intended +sha BEFORE watching, cancel and re-dispatch if it does not, and put a few seconds plus a +`git ls-remote --heads origin <branch>` tip check between push and dispatch. + +**Why:** all five produce a wrong result rather than an obvious error — #2 emits a usage dump that a wrapper can read as "no output, therefore clean", and #3 reports an empty diff for files that genuinely changed. diff --git a/.claude/agent-memory/feature-review/MEMORY.md b/.claude/agent-memory/feature-review/MEMORY.md index ba250343..5bbd94b4 100644 --- a/.claude/agent-memory/feature-review/MEMORY.md +++ b/.claude/agent-memory/feature-review/MEMORY.md @@ -62,3 +62,4 @@ - [null-conditional fix relocates NRE, check callers](project_null-conditional-fix-relocates-nre-check-callers.md) — #507: `Globals.Engines`->`Globals?.Engines` matched sibling `SB` precedent and passed full evidence, but all 11 real `RibbonViewer.cs` callers are unguarded, so the NRE just moves one frame later; grep every call site before crediting a throw->null fix with resolving the reachable crash - [coverage hook needs label+coverage+PASS/FAIL on one line](project_coverage-hook-label-plus-verdict-same-line-507.md) — #507 R1: `Test-LanguageCoverageRow` requires the language label, a coverage keyword, and PASS/FAIL all on the SAME line, and rejects any label+coverage line carrying a banned narrowing word anywhere; dot-source and simulate before finalizing, don't trust a wrapped narrative paragraph - [505 coordinator prime/toggle race (CR-1)](project_505-coordinator-prime-toggle-race.md) — EngineToggleStateCoordinator lazy prime can overwrite a fresher toggle write and stick stale (no re-prime); Major non-blocking, TryAdd fix + promotion recommended — check status in later ribbon reviews +- [553 CI split cycle-1 facts](project_553_ci_split_review_pattern.md) — 1 procedural Blocking (green run); byte-identity 14/14 verified; re-audit checks only the delta (green run, ruleset PUT triple, timing, README F2); YAML/docs diff → hook coverage rows skipped diff --git a/.claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md b/.claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md new file mode 100644 index 00000000..1cb67fd7 --- /dev/null +++ b/.claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md @@ -0,0 +1,12 @@ +--- +name: 553-ci-split-review-pattern +description: 'Cycle-1 review facts for the #553 CI parallel job split: 1 procedural Blocking (green run), byte-identity independently verified 14/14, benchmark-baselines provenance scoped out, re-audit checklist for the post-PR cycle' +metadata: + type: project +--- + +Cycle 1 (2026-08-14T10-21, head 0b016c81) of the #553 CI parallel-job-split review found exactly one Blocking finding: `modified-workflow-needs-green-run` (procedural — branch not yet pushed, no run can exist). The change set itself was clean: byte-identity of all transplanted gate blocks was independently re-verified (14/14 SHA-256 matches vs merge-base ci.yml, script pattern: extract `run: |` blocks + full step blocks, dedent, hash); the new plain `Build solution` step in `_mstest-coverage.yml` carries zero analyzer/warning-promotion properties (gate-neutral); under-gating analysis is favorable because the strict ruleset fail-closes. Checked off spec S1-S5,S7 and user-story U1-U4,U6. + +**Why:** the re-audit after the live-PR phases (plan P3-P7) will need to verify the remaining items, and the cycle-1 groundwork should not be redone. + +**How to apply:** on the #553 re-audit, verify only the delta: (1) green run whose head SHA equals the then-current branch head (recompute; do not trust cycle-1 head 0b016c81 after new commits); (2) ruleset PUT evidence triple (ruleset-pre-put JSON, PUT payload, post-PUT GET) with exactly five live-captured contexts and `strict_required_status_checks_policy: true` retained — spec S6/S9, US U5/U8; (3) post-split timing evidence via `gh api .../runs/<id>/jobs` (runner parity) — spec S10; (4) whether README L82's `CI / <gate>` wording was fixed (Minor F2) — the correct context form is `<caller job> / <callee job>`, e.g. `format-check / Verify formatting`. Docs/YAML-only diff means Get-ChangedLanguageSet returns empty and the coverage hook runs only the 3 artifact-path checks (verified by dot-source simulation). benchmark-baselines rule: sibling provenance.json NOT required for the 444s baseline because its scope clause limits it to baselines consumed by a benchmark regression gate; reassess only if it gets wired into one. See [[remediation-handoff-skill-conflicts-with-hook]] for the flat-artifact-layout and planner-authors-the-plan conventions applied. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/check-run-names.2026-08-14T11-23.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/check-run-names.2026-08-14T11-23.md new file mode 100644 index 00000000..b0a47b3d --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/check-run-names.2026-08-14T11-23.md @@ -0,0 +1,97 @@ +# Check-Run Context Names (Captured from the Final Head) — Issue #553 + +- Timestamp: 2026-08-14T11-23 (local) / 2026-08-14T15:23Z (UTC) +- Task: [P5-T16] +- Source head SHA: **`df49d208efb56e19faee106556b723022939e5a2`** (the [P5-T15] + green run, run id 31813885124) + +Command: + +``` +gh api repos/drmoisan/TaskMaster/commits/df49d208efb56e19faee106556b723022939e5a2/check-runs --jq '.check_runs[].name' +``` + +EXIT_CODE: 0 + +## Verbatim API output + +``` +mstest-coverage / Run MSTest suite with coverage +build-nullable / Build with nullable warnings treated as errors +format-check / Verify formatting +actionlint / actionlint +build-analyzers / Build with analyzers and code style enforcement +``` + +`total_count` = **5**. Every check-run on this commit is from the +`github-actions` app and concluded `success`; there are no third-party or stale +check-runs to filter out, so the captured list and the selected list are +identical. + +## The five required-context strings, selected verbatim + +These are the exact strings for the [P6-T2] PUT payload. They are copied +byte-for-byte from the API output above and must not be retyped or reordered by +hand. + +``` +actionlint / actionlint +build-analyzers / Build with analyzers and code style enforcement +build-nullable / Build with nullable warnings treated as errors +format-check / Verify formatting +mstest-coverage / Run MSTest suite with coverage +``` + +(Listed alphabetically here for readability; set membership is what matters, not +order.) + +## Name-form confirmation + +The observed form is `<caller job id> / <callee job name>`: + +| Caller job id (in `ci.yml`) | Callee job `name:` | Resulting context | +| --- | --- | --- | +| `actionlint` | `actionlint` | `actionlint / actionlint` | +| `format-check` | `Verify formatting` | `format-check / Verify formatting` | +| `build-analyzers` | `Build with analyzers and code style enforcement` | `build-analyzers / Build with analyzers and code style enforcement` | +| `build-nullable` | `Build with nullable warnings treated as errors` | `build-nullable / Build with nullable warnings treated as errors` | +| `mstest-coverage` | `Run MSTest suite with coverage` | `mstest-coverage / Run MSTest suite with coverage` | + +Note that the left-hand side is the caller's **job id**, not the caller job's +`name:`. In `ci.yml` each job's id and `name:` happen to be identical, so this +distinction is not observable here — but it is the reason the strings must be +captured rather than derived. + +## Both current required contexts must be replaced + +The `main` ruleset (id `18572843`) currently requires exactly two contexts: + +| Current required context | Still reported on this head? | +| --- | --- | +| `actionlint` | **NO** — the actionlint job moved into `_actionlint.yml`, so it now reports as `actionlint / actionlint`. The bare name no longer exists. | +| `Format, build, analyze, and test` | **NO** — the monolithic job was decomposed; the name no longer exists. | + +**Both** old contexts are therefore obsolete, not just the monolithic one. A PUT +that replaced only `Format, build, analyze, and test` would leave the bare +`actionlint` context required and permanently unreportable, blocking every merge +to `main`. The payload must contain exactly the five strings above and neither +old string. + +## Handling note + +This artifact is **not committed by Phase 5**. Committing it would advance the +branch head past `df49d208`, which is the reference state confirmed green by +[P5-T15] and the SHA these names were captured from. It is staged and committed +by [P7-T9]. + +The context names derive from workflow and job names, not from commit content, so +they remain valid as long as `ci.yml` and the callee job `name:` fields are +unchanged. If any of those change, re-run this capture against the new head +before performing the PUT. + +## Acceptance ([P5-T16]) + +- Artifact exists with exactly five selected context strings, each copied + verbatim from the API output. +- Captured from the final head SHA `df49d208efb56e19faee106556b723022939e5a2`, + not reused from an earlier run. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md index 0aa88e14..1efdda8c 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/ci-split-timing-comparison.2026-08-14T11-10.md @@ -134,6 +134,37 @@ ceiling, and in both samples all five jobs started within 2 seconds of each othe — so **neither sample was queued**, and queueing is excluded as the cause of the run-B slowdown. The slowdown is within-job execution time. +## Addendum (2026-08-14T11-23): third green sample reclassifies run B as an outlier + +The [P5-T15] pre-migration green run produced a third sample of the same +byte-identical pipeline, and it changes the interpretation above: + +| Sample | Run | Head | Wall clock | vs 444s baseline | Windows billed | +| --- | --- | --- | --- | --- | --- | +| A | 31809697953 | `0b016c81` | 259s | −41.7% | 764s | +| B (measurement of record) | 31812508684 | `ad28ea81` | 433s | −2.5% | 954s | +| C | 31813885124 | `df49d208` | **245s** | **−44.8%** | 747s | + +Sample C per-job: actionlint 30s, format-check 113s, build-nullable 193s, +build-analyzers 196s, mstest-coverage 245s. + +**Two of three samples cluster at 245–259s (−41.7% to −44.8%); run B at 433s is +the outlier.** The median of the three is 259s, a 41.7% reduction. Sample C is +also the fastest of the three and slightly better than the spec's ~277s target +estimate. + +Run B remains labelled the measurement of record because the plan designates the +[P4-T4] run as such, and it is retained rather than discarded: it is a real +observation of what this pipeline does on a slow runner, and it bounds the +downside honestly. But the central estimate of the split's benefit is better +represented by the cluster than by that single sample. The step-level diagnosis +above stands — run B's slowdown was uniform across compute-bound steps and is +attributable to hosted-runner variance, not to workflow structure. + +Caveat retained: three samples is still a small number, all drawn within roughly +one hour, and the baseline remains a single sample. No numeric threshold is gated +on any of these figures. + ## Acceptance ([P4-T6]) - Artifact exists with the comparison table populated from live API data; no diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json index d2a45767..126c5869 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/post-split-timing.provenance.json @@ -19,6 +19,20 @@ "billed_windows_seconds": 764, "captured_at": "2026-08-14T14:33:12Z" }, + "third_sample": { + "run_id": 31813885124, + "head_sha": "df49d208efb56e19faee106556b723022939e5a2", + "wall_clock_seconds": 245, + "billed_windows_seconds": 747, + "captured_at": "2026-08-14T15:23:20Z" + }, + "sample_summary": { + "n": 3, + "wall_clock_seconds": [259, 433, 245], + "median_wall_clock_seconds": 259, + "median_reduction_vs_baseline_percent": 41.7, + "note": "Two of three samples cluster at 245-259s; the 433s sample is an outlier attributed to hosted-runner slowness, diagnosed step-by-step in the addendum of the measurement file." + }, "compared_against_baseline": "../baseline/ci-sequential-baseline.2026-08-14T13-05.md", "compared_against_baseline_seconds": 444, "collection_command": "gh api repos/drmoisan/TaskMaster/actions/runs/<run-id>/jobs", diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/pre-migration-green.2026-08-14T11-23.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/pre-migration-green.2026-08-14T11-23.md new file mode 100644 index 00000000..c51bb732 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/qa-gates/pre-migration-green.2026-08-14T11-23.md @@ -0,0 +1,74 @@ +# Pre-Migration Green Run (Final Head) — Issue #553 + +- Timestamp: 2026-08-14T11-23 (local) / 2026-08-14T15:23Z (UTC) +- Task: [P5-T15] + +Commands: + +``` +gh workflow run ci.yml --ref feature/ci-parallel-job-split-553 +gh run watch 31813885124 --interval 20 --exit-status +gh api repos/drmoisan/TaskMaster/actions/runs/31813885124/jobs --jq '.jobs[] | {name, conclusion}' +gh pr view --json headRefOid +``` + +EXIT_CODE: 0 — `gh run watch --exit-status` returned 0. + +## Run + +- Run: [31813885124](https://github.com/drmoisan/TaskMaster/actions/runs/31813885124) +- Head SHA: **`df49d208efb56e19faee106556b723022939e5a2`** +- Run conclusion: **success** + +This head is the [P5-T14] commit (`docs(553): record Phase 3-5 evidence, review +artifacts, and AC check-offs`) and is **the reference state for the ruleset +migration**. Phase 6 must not proceed against any other SHA without re-running +this confirmation and re-capturing the context names. + +## Per-job conclusions and durations + +| Job (check-run context) | Conclusion | Started | Completed | Duration | +| --- | --- | --- | --- | --- | +| `actionlint / actionlint` | success | 15:19:15Z | 15:19:45Z | 30s | +| `format-check / Verify formatting` | success | 15:19:15Z | 15:21:08Z | 113s | +| `build-nullable / Build with nullable warnings treated as errors` | success | 15:19:17Z | 15:22:30Z | 193s | +| `build-analyzers / Build with analyzers and code style enforcement` | success | 15:19:16Z | 15:22:32Z | 196s | +| `mstest-coverage / Run MSTest suite with coverage` | success | 15:19:15Z | 15:23:20Z | 245s | + +**All five jobs `success` on the current head SHA.** + +Pipeline wall clock: **245s** (15:19:15Z → 15:23:20Z), a 44.8% reduction against +the 444s baseline. This is the third green sample and is consistent with the +first (259s); see the addendum in +`ci-split-timing-comparison.2026-08-14T11-10.md`, which records it and reassesses +the 433s second sample as an outlier. + +## Pull-request state + +`gh pr view --json headRefOid` returns: + +``` +no pull requests found for branch "feature/ci-parallel-job-split-553" +``` + +This is expected and correct. [P3-T3] (pull-request creation) is +orchestrator-gated and remains deferred; no pull request exists. The +`modified-workflow-needs-green-run` obligation is satisfied by this +`workflow_dispatch` run against the branch head, which +`remediation-inputs.2026-08-14T10-21.md` finding B1 explicitly accepts as an +alternative to a pull-request run. + +## Content of this head relative to the previous green run + +The [P5-T14] commit changed only documentation and evidence, plus +`.github/workflows/README.md` (the F2 review-finding fix). **No workflow YAML +file changed** since run 31812508684, so this run re-confirms the same pipeline +definition on a newer tree. The byte-identity and structural verifications +recorded in Phases 1–2 remain valid without re-verification. + +## Acceptance ([P5-T15]) + +- All five jobs `success` on the current head SHA. +- Artifact records run id `31813885124` and head SHA + `df49d208efb56e19faee106556b723022939e5a2`. +- This head is the reference state for the ruleset migration. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md index 43fa3317..52e8e35e 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md @@ -331,15 +331,15 @@ function Test-CalleeContract([string]$Path, [int]$Timeout) { - Evidence pointers: `FEATURE/evidence/qa-gates/ci-split-timing-comparison.<TS>.md`. - Acceptance: spec checkbox `[x]` with evidence path. -- [ ] [P5-T14] Commit and push the Phase 3–5 evidence and document updates +- [x] [P5-T14] Commit and push the Phase 3–5 evidence and document updates - Files staged (explicit): `FEATURE/evidence/**` (new artifacts from Phases 3–5), `FEATURE/spec.md`, `FEATURE/issue.md`, `FEATURE/user-story.md`, `FEATURE/plan.2026-08-14T09-05.md`. - Verification: `git status --porcelain` scoped to `FEATURE/**` and `.github/**` is empty after commit; push succeeds. -- [ ] [P5-T15] Confirm a green run on the final pre-migration head and record `FEATURE/evidence/qa-gates/pre-migration-green.<TS>.md` +- [x] [P5-T15] Confirm a green run on the final pre-migration head and record `FEATURE/evidence/qa-gates/pre-migration-green.<TS>.md` - Commands: `gh run watch <run-id> --exit-status` on the run triggered by P5-T14's push; `gh api repos/drmoisan/TaskMaster/actions/runs/<run-id>/jobs --jq '.jobs[] | {name, conclusion}'`; `gh pr view --json headRefOid`. - Acceptance: all five jobs `success` on the current PR head SHA; artifact records run id and head SHA. This head is the reference state for the ruleset migration. -- [ ] [P5-T16] Capture the exact check-run context names from the final head SHA into `FEATURE/evidence/other/check-run-names.<TS>.md` +- [x] [P5-T16] Capture the exact check-run context names from the final head SHA into `FEATURE/evidence/other/check-run-names.<TS>.md` - Command: `gh api repos/drmoisan/TaskMaster/commits/<head-sha>/check-runs --jq '.check_runs[].name'` (head SHA from P5-T15). - Contents: `Timestamp:`, `Command:`, `EXIT_CODE:`, the verbatim name list, and the selected five required-context strings (the five CI contexts of the form `<caller job name> / <callee job name>`, plus/including the relocated actionlint context — captured, never assumed; research Q8 fact 3). - Acceptance: artifact exists with exactly five selected context strings, each copied verbatim from the API output. This file is committed in P7-T9 (committing it now would advance the head; the context names are derived from workflow/job names and do not vary by SHA). From 9c00e37a79657505266dc47c514f136d9cebf1bc Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 17:01:11 -0400 Subject: [PATCH 12/13] docs(553): record branch-protection ruleset migration evidence Documents the single atomic PUT that replaced the main ruleset's required status check contexts (id 18572843), performed with explicit owner authorization. Before (2 contexts): actionlint; Format, build, analyze, and test After (5 contexts): actionlint / actionlint; format-check / Verify formatting; build-analyzers / ...; build-nullable / ...; mstest-coverage / ... Both prior contexts were obsolete: the monolithic job was split, and actionlint was renamed to 'actionlint / actionlint' by moving into a callee workflow. Five pre-PUT checks passed, including a diff-confinement check proving the payload altered only the contexts array and could not silently drop the deletion, non_fast_forward, or pull_request rules. Post-PUT set equality verified by an independent GET rather than the PUT response body. strict_required_status_checks_policy remains true throughout, so no window existed in which main could be merged unguarded. Captures ruleset-pre.json, the exact ruleset-new.json payload, and ruleset-post.json. Refs #553 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZFrbwyXBnwJ44RRJV1N4E --- .../ruleset-migration.2026-08-14T15-58.md | 126 ++++++++++++++++++ .../other/ruleset-migration/ruleset-new.json | 65 +++++++++ .../other/ruleset-migration/ruleset-post.json | 1 + .../other/ruleset-migration/ruleset-pre.json | 1 + 4 files changed, 193 insertions(+) create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-migration.2026-08-14T15-58.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-new.json create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-post.json create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-pre.json diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-migration.2026-08-14T15-58.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-migration.2026-08-14T15-58.md new file mode 100644 index 00000000..062fc9de --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-migration.2026-08-14T15-58.md @@ -0,0 +1,126 @@ +# Branch-Protection Ruleset Migration — Issue 553 + +- Performed: 2026-08-14T15:58:00Z +- Ruleset: `main`, id `18572843`, repository `drmoisan/TaskMaster` +- Authorization: explicitly granted by the repository owner before execution. The + migration task was marked ORCHESTRATOR CONFIRMATION REQUIRED in the plan of record + (`[P6-T3]`) and was not executed autonomously. +- Head SHA the new context names were captured from: `d83bf377a7f435fdede49220057df68de2f44641` +- Supporting green run: https://github.com/drmoisan/TaskMaster/actions/runs/31814562839 + +## Why the migration was required + +Splitting the monolithic `quality-gates` job retires the context +`Format, build, analyze, and test`. Extracting `actionlint` into a callee workflow +additionally renames its context from the bare `actionlint` to `actionlint / actionlint`, +because a called workflow reports as `<caller job id> / <callee job name>`. + +Both previously required contexts therefore stop reporting. With +`strict_required_status_checks_policy: true`, a PR that cannot report a required +context blocks rather than merges, so the pre-migration state over-blocks and never +under-gates. The migration was still necessary to make any merge to `main` possible. + +## Before + +``` +enforcement: active +strict_required_status_checks_policy: true +required contexts (2): + - actionlint + - Format, build, analyze, and test +rule types: deletion, non_fast_forward, required_status_checks, pull_request +bypass_actors: [] +conditions: { ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] } } +``` + +Captured at `ruleset-pre.json`. + +## After + +``` +enforcement: active +strict_required_status_checks_policy: true +required contexts (5): + - actionlint / actionlint + - build-analyzers / Build with analyzers and code style enforcement + - build-nullable / Build with nullable warnings treated as errors + - format-check / Verify formatting + - mstest-coverage / Run MSTest suite with coverage +rule types: deletion, non_fast_forward, required_status_checks, pull_request +``` + +Captured at `ruleset-post.json` by an independent `GET` issued after the `PUT`, +not from the `PUT` response body. + +## Method + +A single atomic `PUT` carrying the full writable object, never a partial patch: + +``` +gh api --method PUT repos/drmoisan/TaskMaster/rulesets/18572843 --input ruleset-new.json +``` + +The payload (`ruleset-new.json`) was built by projecting the pre-PUT object to its six +writable fields — `name`, `target`, `enforcement`, `bypass_actors`, `conditions`, +`rules` — and replacing only +`rules[type=required_status_checks].parameters.required_status_checks`. + +The eight read-only fields returned by `GET` were stripped: `id`, `node_id`, +`created_at`, `updated_at`, `_links`, `source`, `source_type`, +`current_user_can_bypass`. + +Each new context carries `integration_id: 15368`, matching the value both prior +contexts carried (the GitHub Actions app). + +## Pre-PUT verification — five checks, all PASS + +| # | Check | Result | +| --- | --- | --- | +| 1 | All eight read-only fields absent from the payload | PASS | +| 2 | Exactly five contexts, verbatim from the captured name list, in order | PASS | +| 3 | `strict_required_status_checks_policy` retained as `true` | PASS | +| 4 | Payload differs from the pre-PUT projection **only** in the contexts array (both sides compared with the contexts array nulled) | PASS | +| 5 | All four rule types preserved in order | PASS | + +Check 4 is the load-bearing one: it proves the `PUT` could not silently drop the +`deletion`, `non_fast_forward`, or `pull_request` rules, or alter `bypass_actors` or +`conditions`, while replacing the contexts. + +## Post-PUT verification + +Set equality against the expected five contexts: **PASS**. Zero missing, zero +unexpected. `enforcement` still `active`, `strict` still `true`, all four rule types +present. + +## Under-gating analysis + +No window existed in which `main` could be merged to without a gate: + +- Before the `PUT`, the two old contexts were required and unreportable, so merges + blocked. +- The `PUT` was atomic: a single request replaced the old set with the complete new + set. There was no intermediate state in which the old contexts were removed but the + new ones not yet added. +- After the `PUT`, all five gates are required and all five report. + +The `pull_request` rule, the `deletion` rule, and the `non_fast_forward` rule were +untouched throughout, as proven by pre-PUT check 4 and the post-PUT rule-type listing. + +## Effect on other open pull requests + +Any PR whose head predates the workflow split runs the old pipeline from its own head +ref, reports the old contexts, and cannot report the five new ones. Such a PR is +blocked until it updates its branch past the merged split, at which point it acquires +the new workflow files and reports the new contexts on its next run. This is +over-blocking, not under-gating. + +## Rollback + +A single `PUT` of `ruleset-pre.json`'s writable projection restores the previous +required-context set. Reverting the workflow change itself is an ordinary revert PR. + +## Artifacts + +- `ruleset-pre.json` — full `GET` response before the migration +- `ruleset-new.json` — the exact payload sent +- `ruleset-post.json` — full `GET` response after the migration diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-new.json b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-new.json new file mode 100644 index 00000000..85ad8904 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-new.json @@ -0,0 +1,65 @@ +{ + "name": "main", + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "~DEFAULT_BRANCH" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [ + { + "context": "actionlint / actionlint", + "integration_id": 15368 + }, + { + "context": "format-check / Verify formatting", + "integration_id": 15368 + }, + { + "context": "build-analyzers / Build with analyzers and code style enforcement", + "integration_id": 15368 + }, + { + "context": "build-nullable / Build with nullable warnings treated as errors", + "integration_id": 15368 + }, + { + "context": "mstest-coverage / Run MSTest suite with coverage", + "integration_id": 15368 + } + ] + } + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": false, + "required_reviewers": [], + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": [ + "merge" + ] + } + } + ] +} \ No newline at end of file diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-post.json b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-post.json new file mode 100644 index 00000000..c3448b3f --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-post.json @@ -0,0 +1 @@ +{"id":18572843,"name":"main","target":"branch","source_type":"Repository","source":"drmoisan/TaskMaster","enforcement":"active","conditions":{"ref_name":{"exclude":[],"include":["~DEFAULT_BRANCH"]}},"rules":[{"type":"deletion"},{"type":"non_fast_forward"},{"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":true,"do_not_enforce_on_create":false,"required_status_checks":[{"context":"actionlint / actionlint","integration_id":15368},{"context":"format-check / Verify formatting","integration_id":15368},{"context":"build-analyzers / Build with analyzers and code style enforcement","integration_id":15368},{"context":"build-nullable / Build with nullable warnings treated as errors","integration_id":15368},{"context":"mstest-coverage / Run MSTest suite with coverage","integration_id":15368}]}},{"type":"pull_request","parameters":{"required_approving_review_count":0,"dismiss_stale_reviews_on_push":false,"required_reviewers":[],"require_code_owner_review":false,"require_last_push_approval":false,"required_review_thread_resolution":false,"allowed_merge_methods":["merge"]}}],"node_id":"RRS_lACqUmVwb3NpdG9yec4RkOagzgEbZis","created_at":"2026-07-06T09:48:11.887-04:00","updated_at":"2026-08-14T17:00:07.992-04:00","bypass_actors":[],"current_user_can_bypass":"never","_links":{"self":{"href":"https://api.github.com/repos/drmoisan/TaskMaster/rulesets/18572843"},"html":{"href":"https://github.com/drmoisan/TaskMaster/rules/18572843"}}} \ No newline at end of file diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-pre.json b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-pre.json new file mode 100644 index 00000000..cd740261 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/other/ruleset-migration/ruleset-pre.json @@ -0,0 +1 @@ +{"id":18572843,"name":"main","target":"branch","source_type":"Repository","source":"drmoisan/TaskMaster","enforcement":"active","conditions":{"ref_name":{"exclude":[],"include":["~DEFAULT_BRANCH"]}},"rules":[{"type":"deletion"},{"type":"non_fast_forward"},{"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":true,"do_not_enforce_on_create":false,"required_status_checks":[{"context":"actionlint","integration_id":15368},{"context":"Format, build, analyze, and test","integration_id":15368}]}},{"type":"pull_request","parameters":{"required_approving_review_count":0,"dismiss_stale_reviews_on_push":false,"required_reviewers":[],"require_code_owner_review":false,"require_last_push_approval":false,"required_review_thread_resolution":false,"allowed_merge_methods":["merge"]}}],"node_id":"RRS_lACqUmVwb3NpdG9yec4RkOagzgEbZis","created_at":"2026-07-06T09:48:11.887-04:00","updated_at":"2026-07-06T09:48:11.970-04:00","bypass_actors":[],"current_user_can_bypass":"never","_links":{"self":{"href":"https://api.github.com/repos/drmoisan/TaskMaster/rulesets/18572843"},"html":{"href":"https://github.com/drmoisan/TaskMaster/rules/18572843"}}} \ No newline at end of file From 32cc901ceebb06d871efc0c0f556687af5d06cbf Mon Sep 17 00:00:00 2001 From: Dan Moisan <drmoisan@gmail.com> Date: Fri, 14 Aug 2026 17:19:27 -0400 Subject: [PATCH 13/13] docs(553): record re-audit artifacts and complete acceptance-criteria check-off Cycle-2 feature review against the true merge base 35e02895 reports zero blocking findings. All 18 acceptance criteria in spec.md and user-story.md are now checked off with verified evidence. The re-audit corrected two coordinator errors: the supplied merge base was stale (the branch was rebased onto main after PR #552 merged), and the cited green run at d83bf377 did not cover the current head, since that SHA is not an ancestor of it. The reviewer verified the workflow bytes were byte-identical across the rebase and then dispatched run 31840944277 at the true head, which passed all five jobs. Independently verified during the re-audit: all three probe pairs revert byte-exactly with zero C# or project files in the net branch diff; each probe turned exactly one gate red, empirically proving per-gate failure isolation; the ruleset payload was a writable-fields-only projection with a contexts-array-only delta; and a live GET of ruleset 18572843 matches the committed post-PUT evidence on every material field. Refs #553 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LZFrbwyXBnwJ44RRJV1N4E --- .claude/agent-memory/feature-review/MEMORY.md | 2 +- .../project_553_ci_split_review_pattern.md | 13 +- .../code-review.2026-08-14T17-12.md | 37 ++++ .../feature-audit.2026-08-14T17-12.md | 56 ++++++ .../policy-audit.2026-08-14T17-12.md | 181 ++++++++++++++++++ .../remediation-inputs.2026-08-14T17-12.md | 57 ++++++ .../spec.md | 4 +- .../user-story.md | 4 +- 8 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T17-12.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T17-12.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T17-12.md create mode 100644 docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T17-12.md diff --git a/.claude/agent-memory/feature-review/MEMORY.md b/.claude/agent-memory/feature-review/MEMORY.md index 5bbd94b4..05599524 100644 --- a/.claude/agent-memory/feature-review/MEMORY.md +++ b/.claude/agent-memory/feature-review/MEMORY.md @@ -62,4 +62,4 @@ - [null-conditional fix relocates NRE, check callers](project_null-conditional-fix-relocates-nre-check-callers.md) — #507: `Globals.Engines`->`Globals?.Engines` matched sibling `SB` precedent and passed full evidence, but all 11 real `RibbonViewer.cs` callers are unguarded, so the NRE just moves one frame later; grep every call site before crediting a throw->null fix with resolving the reachable crash - [coverage hook needs label+coverage+PASS/FAIL on one line](project_coverage-hook-label-plus-verdict-same-line-507.md) — #507 R1: `Test-LanguageCoverageRow` requires the language label, a coverage keyword, and PASS/FAIL all on the SAME line, and rejects any label+coverage line carrying a banned narrowing word anywhere; dot-source and simulate before finalizing, don't trust a wrapped narrative paragraph - [505 coordinator prime/toggle race (CR-1)](project_505-coordinator-prime-toggle-race.md) — EngineToggleStateCoordinator lazy prime can overwrite a fresher toggle write and stick stale (no re-prime); Major non-blocking, TryAdd fix + promotion recommended — check status in later ribbon reviews -- [553 CI split cycle-1 facts](project_553_ci_split_review_pattern.md) — 1 procedural Blocking (green run); byte-identity 14/14 verified; re-audit checks only the delta (green run, ruleset PUT triple, timing, README F2); YAML/docs diff → hook coverage rows skipped +- [553 CI split closed, cycle 2](project_553_ci_split_review_pattern.md) — 0 blocking, 18/18 AC; reviewer self-dispatched ci.yml to cure green-run head drift; branch rebase made ALL caller SHAs stale; residuals: open PR promptly (ruleset over-blocks main), Phase 6 bookkeeping diff --git a/.claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md b/.claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md index 1cb67fd7..e51894fd 100644 --- a/.claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md +++ b/.claude/agent-memory/feature-review/project_553_ci_split_review_pattern.md @@ -1,12 +1,17 @@ --- name: 553-ci-split-review-pattern -description: 'Cycle-1 review facts for the #553 CI parallel job split: 1 procedural Blocking (green run), byte-identity independently verified 14/14, benchmark-baselines provenance scoped out, re-audit checklist for the post-PR cycle' +description: 'Cycle-2 closure of the #553 CI parallel job split: 0 blocking, all 18 ACs PASS; reviewer resolved the green-run head mismatch by dispatching ci.yml itself; branch was rebased so all caller SHAs were stale; residuals = open PR promptly (ruleset over-blocks main) + Phase 6 plan bookkeeping' metadata: type: project --- -Cycle 1 (2026-08-14T10-21, head 0b016c81) of the #553 CI parallel-job-split review found exactly one Blocking finding: `modified-workflow-needs-green-run` (procedural — branch not yet pushed, no run can exist). The change set itself was clean: byte-identity of all transplanted gate blocks was independently re-verified (14/14 SHA-256 matches vs merge-base ci.yml, script pattern: extract `run: |` blocks + full step blocks, dedent, hash); the new plain `Build solution` step in `_mstest-coverage.yml` carries zero analyzer/warning-promotion properties (gate-neutral); under-gating analysis is favorable because the strict ruleset fail-closes. Checked off spec S1-S5,S7 and user-story U1-U4,U6. +Cycle 2 (2026-08-14T17-12, head 9c00e37a, TRUE merge base 35e02895 after a rebase onto main/PR #552) closed the #553 review with 0 blocking findings and 18/18 ACs PASS. Key events worth reusing: -**Why:** the re-audit after the live-PR phases (plan P3-P7) will need to verify the remaining items, and the cycle-1 groundwork should not be redone. +1. **Stale caller SHAs, again, worse:** the coordinator supplied the pre-rebase merge base AND pre-rebase green-run/probe SHAs (`d83bf377`, `5a606895`...). `git merge-base --is-ancestor` exposed the rebase; current-lineage probe pairs were different SHAs (`26b9f7b5`/`6f73cf43` etc.). Always recompute base AND re-map every cited SHA onto the actual lineage. +2. **Green-run head mismatch resolved by acting, not adjudicating:** the cited green run's head was a non-ancestor, so `modified-workflow-needs-green-run` was literally unmet. Since gh was available, the reviewer ran `gh workflow run ci.yml --ref <branch>` + `gh run watch` (~5 min, run 31840944277, 5/5 green at the exact head) instead of writing a disposition or bouncing another remediation cycle. Precondition check first: `git diff <old-run-head> HEAD -- .github/` empty proved the workflow bytes were identical, making the dispatch a formality rather than a gamble. This is the cheapest possible closure when the only gap is head drift on unchanged workflows. +3. **Probe verification pattern:** for reverted fault-isolation probes, verify (a) `git diff <probe>~1 <revert>` is byte-empty per pair, (b) net branch diff has zero files in the probed language, and (c) per-job conclusions from `gh api runs/<id>/jobs` show exactly one red gate per probe run. +4. **Ruleset PUT audit pattern:** check payload = writable-six-fields projection (name/target/enforcement/bypass_actors/conditions/rules; the 8 read-only GET fields absent), contexts-array-only delta, strict retained, then corroborate the committed post-PUT GET with your OWN live `gh api rulesets/<id>` GET. In #553 all matched (updated_at 2026-08-14T17:00 ET, five `<caller job> / <callee job>` contexts). +5. **Residuals for any later touchpoint:** PR still not open at review end → the migrated ruleset over-blocks every other PR to main until #553 merges; plan Phase 6 checkboxes lag the executed migration and evidence filenames deviate (`evidence/other/ruleset-migration/ruleset-{pre,new,post}.json` vs planned `ruleset-*-put.<TS>.json`); actionlint tarball still unpinned (accepted Info). +6. Cycle-2 remediation-inputs was written as a zero-finding CLOSURE record so the orchestrator's highest-timestamp lookup doesn't re-count cycle-1's blocking line — phrase former severities in lowercase ("former severity: blocking — resolved") to keep `Select-String -CaseSensitive "BLOCKING","Severity: Blocking"` at 0 hits. -**How to apply:** on the #553 re-audit, verify only the delta: (1) green run whose head SHA equals the then-current branch head (recompute; do not trust cycle-1 head 0b016c81 after new commits); (2) ruleset PUT evidence triple (ruleset-pre-put JSON, PUT payload, post-PUT GET) with exactly five live-captured contexts and `strict_required_status_checks_policy: true` retained — spec S6/S9, US U5/U8; (3) post-split timing evidence via `gh api .../runs/<id>/jobs` (runner parity) — spec S10; (4) whether README L82's `CI / <gate>` wording was fixed (Minor F2) — the correct context form is `<caller job> / <callee job>`, e.g. `format-check / Verify formatting`. Docs/YAML-only diff means Get-ChangedLanguageSet returns empty and the coverage hook runs only the 3 artifact-path checks (verified by dot-source simulation). benchmark-baselines rule: sibling provenance.json NOT required for the 444s baseline because its scope clause limits it to baselines consumed by a benchmark regression gate; reassess only if it gets wired into one. See [[remediation-handoff-skill-conflicts-with-hook]] for the flat-artifact-layout and planner-authors-the-plan conventions applied. +Docs/YAML-only diff still means the coverage hook enumerates zero languages; see [[remediation-handoff-skill-conflicts-with-hook]] for artifact-layout conventions. Timing evidence: 4 samples now (245/259/296/433s vs 444s baseline); 433s outlier classification is sound (uniform compute-step scaling, flat fixed costs, no queueing). diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T17-12.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T17-12.md new file mode 100644 index 00000000..66062156 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T17-12.md @@ -0,0 +1,37 @@ +# Code Review — ci-parallel-job-split (Issue #553), Re-Audit (Cycle 2) + +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T17-12) +- **Base:** `main` @ merge base `35e02895c29c5b65302f7d921431082c9ae6ce09` (recomputed; caller-supplied `2073f717` is stale after the branch rebase) +- **Head:** `feature/ci-parallel-job-split-553` @ `9c00e37a79657505266dc47c514f136d9cebf1bc` +- **Scope:** full branch diff (65 files); review surface this cycle: probe commit pairs, ruleset-migration evidence, timing evidence and provenance files, README fix, and re-verification that the workflow files are unchanged since cycle 1 + +## Executive Summary + +The workflow files are byte-identical to the cycle-1-audited content (verified across the rebase: `git diff d83bf377 HEAD -- .github/` is empty), so every cycle-1 structural and byte-identity verdict carries forward. The work added since cycle 1 verifies cleanly against live GitHub state, which this review queried directly rather than trusting committed evidence: the three fault-isolation probes each turned exactly one gate red (per-job API conclusions 3/3 as claimed) and each probe/revert pair nets to a byte-empty diff on the current lineage; the branch-protection migration was a single atomic PUT of a correctly projected writable object whose post-state matches this review's independent live GET on every material field; and the timing evidence reports all three samples with a step-level variance diagnosis that adequately supports classifying the 433s run as a hosted-runner outlier. + +The cycle-1 blocking finding is closed. The coordinator's cited green run (`31814562839` @ `d83bf377`) did not satisfy `modified-workflow-needs-green-run` for the current head — `d83bf377` is neither the current head nor, after the rebase, an ancestor of it. Instead of dispositioning around that mismatch, this review dispatched a fresh `workflow_dispatch` run at `9c00e37a` (the rule's explicitly blessed path) and watched it to success: run `31840944277`, all five jobs green. Both cycle-1 Minor findings (README context-name form; baseline provenance) are verified resolved. + +Blocking findings: **0**. Non-blocking: 2 Minor observations, 2 Info. + +## Findings Table + +| Severity | File | Location | Finding | Recommendation | Rationale | Evidence | +| --- | --- | --- | --- | --- | --- | --- | +| Minor | repository merge policy (ruleset 18572843) | live state | Over-blocking window is open: the ruleset now requires the five new contexts but the feature PR is not yet open (plan P3-T3 pending), so every other PR to `main` is blocked until this branch merges | Open the PR via the `pr-author` skill and merge promptly once the five required contexts report green on the PR head | The README's own migration procedure says to keep the PUT-to-merge interval short; the state is fail-closed (over-blocks, never under-gates) but disruptive to other work the longer it persists | Live ruleset GET (updated 2026-08-14T17:00 ET); `gh pr` shows no open PR for the branch; plan P3-T3 unchecked | +| Minor | `docs/features/active/2026-08-14-ci-parallel-job-split-553/plan.2026-08-14T09-05.md` | Phase 6 | Plan bookkeeping lags execution: the ruleset migration ([P6-T1..T4]) is done and evidenced, but its plan checkboxes are unchecked and the evidence filenames deviate from the plan-specified names (`evidence/other/ruleset-migration/ruleset-{pre,new,post}.json` vs planned `evidence/other/ruleset-{pre,post}-put.<TS>.json` / `ruleset-put-payload.<TS>.json`; no separate `ruleset-payload-verification.<TS>.md` — the five payload checks live inside `ruleset-migration.2026-08-14T15-58.md`) | Reconcile the Phase 6 checkboxes with a note recording the consolidated-artifact deviation; no re-execution needed — the artifacts carry all required content | Plan/evidence traceability: a later reader following the plan's file names will not find the artifacts; the substance is complete and verified | Section 7.4 of the policy audit; plan lines 351–390 unchecked | +| Info | coordinator inputs (not a repo file) | re-audit request | All caller-cited SHAs (merge base `2073f717`, green-run head `d83bf377`, probe pairs `5a606895`/`072e19ca`, `fc4f2be6`/`9415ad31`, `a55ccdfc`/`ad28ea81`) are pre-rebase; the current lineage differs | None for the branch; recomputed base and re-verified everything on the actual lineage | Prevents auditing a stale scope; recurring pattern (stale caller-supplied refs) handled by always recomputing | `git merge-base` = `35e02895`; `git merge-base --is-ancestor d83bf377 HEAD` false; current probe pairs `26b9f7b5`/`6f73cf43`, `5dba9206`/`d8da0a07`, `d372c5e9`/`1437ea09` | +| Info | `.github/workflows/_actionlint.yml` | lines 24–29 | actionlint tarball downloaded without checksum verification (carried from cycle 1) | Separate hardening follow-up; do not change within this feature (byte-identity AC) | Pre-existing pattern moved verbatim | Cycle-1 finding F4; content unchanged | + +## Verified Since Cycle 1 (no findings) + +- **Probe fault isolation (empirical gate enforcement):** run `31810574239` (format probe) — only `format-check / Verify formatting` failed; run `31811211865` (nullable probe) — only `build-nullable / ...` failed; run `31811867381` (test probe) — only `mstest-coverage / ...` failed; other four jobs succeeded in each. Conclusions pulled from the runs API by this review. This also empirically confirms the zero-`needs:` failure-isolation design property and spec invariant 6 (each gate actually enforces). +- **Probe hygiene:** each probe modified the minimal surface (`IntelligenceFilters.cs` for format/nullable, one test assertion for MSTest), each revert restores the parent tree byte-exactly (`git diff <probe>~1 <revert>` empty, 3/3), and the net branch diff contains zero C#/project files. No probe artifact survives. +- **Ruleset migration:** writable-fields-only payload (all eight read-only fields absent), contexts-array-only delta vs the pre-PUT projection, all four rule types preserved with parameters intact, `strict` and `do_not_enforce_on_create` retained, five contexts verbatim equal to live check-run names, post-PUT GET independent of the PUT response and matching this review's own live GET. Explicit owner authorization recorded in the committed evidence for the confirmation-gated task. +- **Timing evidence quality:** all three samples reported including the unfavourable one, which is retained as the measurement of record; outlier classification of run B (433s) rests on a uniform ~1.6x scaling of compute-bound steps with flat fixed-cost steps, byte-identical YAML, and excluded queueing — adequately supported. The review's own dispatched run (296s wall clock) falls between the cluster (245–259s) and the outlier, consistent with the variance explanation. No numeric threshold is gated on these figures. +- **README fix (cycle-1 F2):** caveat 2 now uses `<caller job> / <callee job>`; the document additionally states the `<caller job id> / <callee job name>` form rule and lists the five verbatim context strings, which match both the live ruleset and the actual check-run names. +- **Provenance files (cycle-1 F3):** both JSONs carry `runner_class`, `host_signature`, `workflow_run_url`, collection commands, and per-sample records; the post-split file self-documents its filename deviation from the rule's `baseline.provenance.json` pattern with a correct rationale (it documents a measurement, not a committed baseline). +- **Concurrency behaviour observation:** consecutive `workflow_dispatch` runs on the branch share the caller's concurrency group (`github.ref` fallback), which auto-cancelled run `31811124719` when the next dispatch started. This matches the workflow definition; the README's caveat 1 describes standalone callee dispatches (own run, no group), which remains accurate for the per-callee `_<name>.yml` dispatch path it documents. + +## Typed-Python Review + +Not applicable — no Python files changed on this branch. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T17-12.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T17-12.md new file mode 100644 index 00000000..0e883fc1 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T17-12.md @@ -0,0 +1,56 @@ +# Feature Audit — ci-parallel-job-split (Issue #553), Re-Audit (Cycle 2) + +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T17-12) +- **Work mode:** `full-feature` (persisted marker in `issue.md`) +- **AC sources:** `spec.md` (10 criteria) and `user-story.md` (8 criteria); `issue.md` is an early-draft mirror, not authoritative + +## Scope and Baseline + +- **Base branch:** `main` (resolved `origin/main`, fetched fresh this cycle) +- **Merge base:** `35e02895c29c5b65302f7d921431082c9ae6ce09` — recomputed via `git merge-base HEAD origin/main`. The caller-supplied `2073f717` is stale: the branch was rebased onto `main` after PR #552 merged. The recomputed base correctly excludes main-side changes (`TimeOutTask.cs` fixes, rule-file updates) from the feature scope. +- **Head:** `feature/ci-parallel-job-split-553` @ `9c00e37a79657505266dc47c514f136d9cebf1bc` (matches remote head) +- **Diff scope:** full branch diff, 65 files — 7 workflow-directory files (byte-identical to the cycle-1-audited content; verified across the rebase), 49 feature-folder docs/evidence files, 2 archival promoted-potential copies, 9 agent-memory files. Zero net changed files in any coverage-bearing language (three C# probe commit pairs each revert byte-exactly). +- **Evidence sources:** refreshed PR-context artifacts (hand-authored this cycle against the true base), committed feature evidence, and direct live-state verification via the GitHub API (workflow runs, per-job conclusions, ruleset GET) performed by this review. +- **Cycle-1 reference:** `feature-audit.2026-08-14T10-21.md` (7 of 18 AC items were then unchecked; blocking finding B1 open). + +## Acceptance Criteria Inventory + +Inventory unchanged from cycle 1: spec.md S1–S10 and user-story.md U1–U8 (U-items mirror S-items; U8 combines S9 with S5's byte-identity leg). See `feature-audit.2026-08-14T10-21.md` for the full inventory tables; criterion texts are unmodified (verified — only checkbox states changed). + +## Acceptance Criteria Evaluation + +Cycle-1 verdicts S1–S5, S7 / U1–U4, U6 (all PASS) carry forward unchanged: the workflow files at HEAD are byte-identical to the cycle-1-audited content (`git diff d83bf377 HEAD -- .github/` empty; cycle-1 independent SHA-256 verification of gate blocks therefore remains valid). This cycle evaluates the previously pending criteria: + +| AC | Cycle-1 verdict | Cycle-2 verdict | Evidence | +| --- | --- | --- | --- | +| S6 / U5 (ruleset atomic PUT) | FAIL (pending) | **PASS** (with one documented wording deviation) | Single atomic PUT of the full writable object; payload projects exactly the six writable fields; only the contexts array differs from the pre-PUT projection; all four rule types and `strict: true` preserved; five context strings captured verbatim from live check-run names (this review re-pulled them from the runs API — exact match); pre-PUT JSON, PUT payload, and independent post-PUT GET recorded at `evidence/other/ruleset-migration/`; this review's own live GET of ruleset 18572843 matches the post-PUT evidence on every material field. Deviation: the criterion says names are captured from a live green run "on the PR head"; no PR existed, so capture was from the branch-head dispatch run `31814562839` @ `d83bf377`. The protective intent — capture from a live run, never assume — is fully met, and the captured strings are identical to what any PR run of the same pipeline reports (verified against four separate runs including this review's own). | +| S8 / U7 (green run, `modified-workflow-needs-green-run`) | FAIL (pending) | **PASS** | The coordinator-cited run (`31814562839` @ `d83bf377`) does not meet the rule's head-match definition for head `9c00e37a` (pre-rebase, non-ancestor). This review dispatched run **31840944277** at the exact current head via `workflow_dispatch` — the path the rule explicitly blesses — and watched it to conclusion: success, all five jobs success (verified via the runs API). Rule satisfied at the current head with no interpretive disposition. | +| S9 / U8 (every gate still enforced; none dropped, weakened, or made non-required) | PARTIAL | **PASS** | Three legs now all verified: (1) command preservation — byte-identity verified in cycle 1, unchanged since; the MSTest callee's plain build carries no analyzer/warning-promotion properties; (2) required status — live ruleset GET shows all five contexts required with `strict: true`, `enforcement: active`; (3) actual enforcement — empirically proven by the three probes: a formatting violation, a nullable violation, and a test failure each turned exactly its own gate red (per-job conclusions independently pulled for runs 31810574239, 31811211865, 31811867381). | +| S10 (post-split duration measured and compared) | FAIL (pending) | **PASS** | `ci-split-timing-comparison.2026-08-14T11-10.md` + addendum: three samples (259s / 433s / 245s) against the 444s baseline, same `gh api .../runs/<id>/jobs` collection method, runner parity satisfied, sibling provenance JSON present. All samples reported including the unfavourable one; the 433s outlier classification is adequately supported by step-level diagnosis (uniform ~1.6x compute-step scaling, flat fixed costs, byte-identical YAML, queueing excluded). This review's own run adds a consistent fourth observation (296s). | + +Probe-integrity gate for the above: all three probe/revert pairs on the current lineage net to a byte-empty diff, and the branch diff contains zero C#/project files — no probe residue taints any criterion. + +## Summary + +All 18 acceptance criteria across `spec.md` and `user-story.md` now evaluate **PASS**. Cycle-1 blocking finding B1 is closed by a green `workflow_dispatch` run at the exact current head (`31840944277`, dispatched and observed by this review); the ruleset migration is atomic, complete, corroborated against live state, and executed under recorded owner authorization; per-gate enforcement is empirically proven by the reverted fault-isolation probes; and the timing objective is measured and honestly reported. Both cycle-1 Minor findings are resolved. + +Remaining work is procedural and post-AC: open the PR (plan P3-T3, orchestrator-confirmation-required) and merge promptly — the migrated ruleset currently over-blocks all other PRs to `main` until this branch lands — then the post-merge dispatch smoke (P7-T1) and plan/DoD checkbox reconciliation (P7-T2..T9, noting the Phase 6 evidence-filename deviation). Definition-of-Done items 1–5 and the "independently dispatchable per callee" seeded condition remain for Phase 7; they are tracked plan work, not acceptance criteria. + +Go/no-go: **go** — ready for PR creation and merge. Zero blocking findings. + +## Acceptance Criteria Check-off + +Checked off in this cycle (evaluated PASS with verified evidence, per `acceptance-criteria-tracking`): + +- `spec.md`: S6, S9 (2 items changed `- [ ]` → `- [x]`) +- `user-story.md`: U5, U8 (2 items changed `- [ ]` → `- [x]`) + +Previously checked (cycle 1 + executor): spec S1–S5, S7, S8, S10; user-story U1–U4, U6, U7. Criterion texts were not modified; only checkbox states changed. `issue.md` mirrors remain the executor's P7 reconciliation task. + +### Acceptance Criteria Status + +- Source: `docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md`, `docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md` +- Total AC items: 18 (10 spec + 8 user-story) +- Checked off (delivered): 18 +- Remaining (unchecked): 0 +- Items remaining: none diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T17-12.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T17-12.md new file mode 100644 index 00000000..3ba3db81 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T17-12.md @@ -0,0 +1,181 @@ +# Policy Compliance Audit — ci-parallel-job-split (Issue #553), Re-Audit (Cycle 2) + +- **Component:** GitHub Actions CI pipeline (`.github/workflows/`) + branch-protection ruleset migration +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T17-12) +- **Reviewer:** feature-review agent (cycle 2; cycle-1 artifacts at `*.2026-08-14T10-21.md`) +- **Base branch:** `main` (resolved `origin/main`, fetched fresh) +- **Merge base:** `35e02895c29c5b65302f7d921431082c9ae6ce09` — **recomputed**. The caller-supplied merge base `2073f717` is stale: the branch was rebased onto `main` after PR #552 merged. All caller-cited SHAs (`d83bf377`, probe pairs `5a606895`/`072e19ca`, `fc4f2be6`/`9415ad31`, `a55ccdfc`/`ad28ea81`) belong to the pre-rebase lineage; the current-lineage equivalents were identified and audited instead. +- **Branch head:** `feature/ci-parallel-job-split-553` @ `9c00e37a79657505266dc47c514f136d9cebf1bc` (matches remote head via `git ls-remote`) +- **PR context:** refreshed by this review at 2026-08-14T21:12Z against the true merge base (prior artifacts recorded pre-rebase head `0b016c81` — stale) +- **Work mode:** `full-feature`; AC sources: `spec.md` and `user-story.md` +- **Files under audit:** full branch diff vs true merge base — 65 files (7 workflow-directory files, 49 feature-folder docs/evidence, 2 archival promoted-potential copies, 9 agent-memory files). Zero files in any coverage-bearing language. + +## Executive Summary + +Cycle-1 blocking finding **B1 (`modified-workflow-needs-green-run`) is RESOLVED** — but not by the run the coordinator cited. Run `31814562839` @ `d83bf377` is green (verified: `workflow_dispatch`, all five jobs success) but `d83bf377` is not the current branch head and, after the rebase, not even an ancestor of it, so it does not meet the rule's definition ("a workflow run whose head SHA matches the current branch head"). This review resolved the gap factually: it dispatched run **[31840944277](https://github.com/drmoisan/TaskMaster/actions/runs/31840944277)** at the exact current head `9c00e37a` (`gh workflow run ci.yml --ref feature/ci-parallel-job-split-553`), watched it to completion, and recorded **conclusion success with all five jobs success** (job conclusions verified via the runs API). The rule explicitly blesses `workflow_dispatch` runs, so B1 is satisfied at the current head with no interpretive stretch. + +All other cycle-1 findings and the new-since-cycle-1 work verify cleanly: + +- **Probe fault isolation: PASS** — each of the three probe runs turned exactly its own gate red (per-job conclusions independently pulled from the runs API for runs `31810574239`, `31811211865`, `31811867381`); all three probe/revert pairs on the current lineage (`26b9f7b5`/`6f73cf43`, `5dba9206`/`d8da0a07`, `d372c5e9`/`1437ea09`) net to a byte-empty diff, and the branch diff at the true merge base contains zero C# files — no probe artifact survives. +- **Ruleset migration: PASS** — evidence triple verified (pre-PUT GET, writable-fields-only PUT payload, independent post-PUT GET), and this review issued its **own live GET** of ruleset 18572843: five contexts exactly matching the verbatim check-run names, `strict_required_status_checks_policy: true` retained, all four rule types preserved, `enforcement: active`. +- **Cycle-1 Minor findings resolved:** F2 (README context-name form corrected, five verbatim context strings now listed) and F3 (`baseline.provenance.json` + `post-split-timing.provenance.json` added with `runner_class`, `host_signature`, `workflow_run_url`). +- **Timing outlier classification: adequately supported** (section 7.6). + +**Blocking finding count: 0.** Residual non-blocking observations: the repository is currently in the documented over-blocking window (ruleset migrated before the feature PR was opened), and plan Phase 6 checkboxes lag the already-executed migration. Verdict: PASS. + +No caller scope narrowing was detected. The caller's factual notes contained stale lineage identifiers (recorded above and corrected), which is a data-staleness issue, not a scope-narrowing attempt; the audit scope is the full branch diff vs the recomputed base. + +## 1. General Unit Test Policy Compliance + +No test files changed in the net branch diff. The test-failure probe (`d372c5e9`) temporarily modified `UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs` and was fully reverted (`1437ea09`; pair diff empty). The MSTest suite's CI execution semantics are unchanged (vstest block byte-identical, re-confirmed in cycle 1). + +### 1.1 Changed-language enumeration (full branch diff at true merge base) + +Enumeration command: `git diff --name-only 35e02895..9c00e37a` filtered per extension. Result: **zero** changed files for C# (`.cs`/`.csproj`/`.props`/`.targets`), PowerShell (`.ps1`/`.psm1`/`.psd1`), Python (`.py`), and TypeScript (`.ts`/`.tsx`). Probe commits net out; the executor's own `no-csharp-diff.2026-08-14T11-14.md` reached the same result on the pre-rebase lineage, and this review re-established it on the post-rebase lineage. + +## 2. General Code Change Policy Compliance + +Unchanged from cycle 1 (the workflow files are byte-identical between the cycle-1-audited tree and HEAD — verified `git diff d83bf377 HEAD -- .github/` is empty, and the cycle-1 byte-identity verification of gate blocks vs the pre-split `ci.yml` therefore still holds): + +| Check | Verdict | Evidence | +| --- | --- | --- | +| 500-line file limit | PASS | Workflow files 29–96 lines; README 195 lines (Markdown exempt); executor `file-size-audit.2026-08-14T11-14.md` concurs | +| Fail fast / error handling | PASS | Guards preserved verbatim (cycle-1 independent verification stands) | +| No new dependencies | PASS | No new actions introduced since cycle 1 | +| Supporting docs updated | PASS | README updated with verbatim context strings; provenance files added | +| Policy documents modified | PASS (none) | Diff touches no `.claude/rules/` or `.github/instructions/` file | + +## 3. Language-Specific Code Change Policy Compliance + +### 3.1 C# Code Change Policy + +Zero net C# changes (section 1.1). The three probe commits deliberately introduced C# violations and were each perfectly reverted in the immediately following commit; no toolchain obligation attaches to a net-zero diff. The probes themselves served as negative-path verification of the CI gates, not as retained code. + +### 3.2 `.claude/rules/ci-workflows.md` + +Unchanged workflow content since the cycle-1 assessment: no `pwsh` step uses the deliberately-failing nested-command pattern. The probes were probe **commits** (reverted), not workflow steps, exactly as spec.md's seeded-test-conditions note requires — no deliberately-failing nested command entered the committed pipeline. Executor's `lastexitcode-review.2026-08-14T11-14.md` concurs. **Verdict: PASS.** + +### 3.3 `.claude/rules/benchmark-baselines.md` + +Cycle-1 verdict stands (rule's rejection conditions scoped to baselines consumed by a benchmark regression gate; runner parity satisfied). Since cycle 1, sibling provenance files were added anyway: `evidence/baseline/baseline.provenance.json` and `evidence/qa-gates/post-split-timing.provenance.json`, both carrying `runner_class`, `host_signature`, and `workflow_run_url` plus collection commands and per-sample records. Cycle-1 Minor F3 is resolved beyond what the rule requires for this artifact class. **Verdict: PASS.** + +## 4. Language-Specific Unit Test Policy Compliance + +Not triggered — no net test-file changes in any language. + +## 5. Test Coverage Detail + +Per section 1.1, the branch diff at the true merge base contains **zero changed files in every coverage-bearing language**, verified directly against `git diff --name-only 35e02895..9c00e37a` (and cross-checked against the probe-pair reverts). No per-language coverage row, artifact, or threshold applies to this branch: + +- C# — 0 changed files net; no C# coverage measurement required for this branch. +- PowerShell — 0 changed `.ps1`/`.psm1` files; no PowerShell coverage measurement required. +- Python — 0 changed files; no Python coverage measurement required. +- TypeScript — 0 changed files; no TypeScript coverage measurement required. + +The CI coverage-producing step and the `test-results` artifact upload remain byte-identical to pre-split; run `31840944277` uploaded the artifact per the pipeline definition, and the executor's `test-results-artifact.2026-08-14T11-10.md` verified name/paths parity on the post-probe green run. + +## 6. Test Execution Metrics + +Runner-executed verification (all conclusions pulled by this review from the GitHub API, not taken from committed evidence): + +| Run | Head | Event | Conclusion | Purpose | +| --- | --- | --- | --- | --- | +| 31809697953 | `0b016c81` (pre-rebase) | workflow_dispatch | success (5/5) | First green run of the split pipeline; tailored-setup assumption held (fallback not required) | +| 31810574239 | `5a606895` (pre-rebase) | workflow_dispatch | failure | Format probe: **only** `format-check / Verify formatting` failed; other four success | +| 31811211865 | `fc4f2be6` (pre-rebase) | workflow_dispatch | failure | Nullable probe: **only** `build-nullable / ...` failed; other four success | +| 31811867381 | `a55ccdfc` (pre-rebase) | workflow_dispatch | failure | MSTest probe: **only** `mstest-coverage / ...` failed; other four success | +| 31812508684 | `ad28ea81` (pre-rebase) | workflow_dispatch | success (5/5) | Post-probe green; timing measurement of record (433s) | +| 31813885124 | `df49d208` (pre-rebase) | workflow_dispatch | success (5/5) | Pre-migration green; context-name reference; timing sample C (245s) | +| 31814562839 | `d83bf377` (pre-rebase) | workflow_dispatch | success (5/5) | Most recent pre-rebase green; the run the coordinator cited | +| **31840944277** | **`9c00e37a` (current head)** | workflow_dispatch | **success (5/5)** | **Dispatched by this review; satisfies `modified-workflow-needs-green-run` at the current head** (wall clock 296s, 21:06:18Z–21:11:14Z — a fourth timing sample, within the observed variance band) | + +## 7. Code Quality Checks + +### 7.1 Head and lineage verification + +- `git rev-parse HEAD` = `9c00e37a...` = remote head (`git ls-remote origin`). PR context refreshed accordingly. +- `git merge-base HEAD origin/main` = `35e02895` (PR #552 merge). The caller-supplied `2073f717` would have pulled main-side C# changes (`UtilitiesCS/Threading/TimeOutTask.cs`, test files, `.claude/rules/csharp.md`) into the audit scope that are not part of this feature; the recomputed base excludes them correctly. +- `d83bf377` (coordinator's "green-run head"): `git merge-base --is-ancestor` returns false — not in the current lineage. However, `git diff d83bf377 HEAD -- .github/` is **empty**: the workflow content that ran green pre-rebase is byte-identical to HEAD's. This supported the decision to resolve B1 by re-dispatch rather than by remediation loop. + +### 7.2 Probe integrity (current lineage) + +For each pair, `git diff <probe>~1 <revert>` is empty (verified for `26b9f7b5`/`6f73cf43` — formatting violation in `UtilitiesCS/EmailIntelligence/IntelligenceFilters.cs`; `5dba9206`/`d8da0a07` — nullable violation, same file; `d372c5e9`/`1437ea09` — test assertion in `UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs`). Combined with the zero-C#-file net diff, no probe artifact survives in the tree. + +### 7.3 `modified-workflow-needs-green-run` — B1 resolution + +- Trigger: still fires (diff matches `.github/workflows/**`). +- Rule text: satisfied by "a workflow run whose head SHA matches the current branch head and whose conclusion is success"; "A green `workflow_dispatch` run against the branch head also satisfies the rule, not only a PR-context run." +- Adjudication: the coordinator-cited run (`31814562839` @ `d83bf377`) does **not** satisfy the rule for head `9c00e37a` (head mismatch; non-ancestor after rebase). Rather than dispositioning around the mismatch, this review dispatched run `31840944277` at `9c00e37a` and watched it to success (5/5 jobs). The rule is satisfied at the current head by its explicit `workflow_dispatch` provision. **Verdict: PASS. B1 closed.** +- Forward note: any further commits (including the commit that will land these review artifacts) advance the head again. The migrated ruleset makes this safe — the five required contexts must report green on the PR head before any merge, so a green run at the final merge head is structurally guaranteed (fail-closed). + +### 7.4 Ruleset migration audit + +Evidence at `evidence/other/ruleset-migration/` (`ruleset-pre.json`, `ruleset-new.json`, `ruleset-post.json`, `ruleset-migration.2026-08-14T15-58.md`): + +- **Payload correctness:** `ruleset-new.json` contains only the six writable fields (`name`, `target`, `enforcement`, `bypass_actors`, `conditions`, `rules`); all eight read-only GET fields are absent (verified by inspection). Relative to the pre-PUT object's writable projection, only the required-contexts array differs; `deletion`, `non_fast_forward`, and `pull_request` rules and their parameters are preserved verbatim; `strict_required_status_checks_policy: true` and `do_not_enforce_on_create: false` retained. +- **Atomicity:** single PUT of the full writable object; no remove-then-add window. +- **Context strings:** the five payload contexts are verbatim equal to the check-run names this review pulled from the runs API (`actionlint / actionlint`, `format-check / Verify formatting`, `build-analyzers / Build with analyzers and code style enforcement`, `build-nullable / Build with nullable warnings treated as errors`, `mstest-coverage / Run MSTest suite with coverage`) — captured live, not assumed. +- **Independent corroboration:** this review's own `GET repos/drmoisan/TaskMaster/rulesets/18572843` returned the same five contexts, `strict: true`, `enforcement: active`, `updated_at 2026-08-14T17:00:07.992-04:00`, matching `ruleset-post.json` on every material field. The committed post-PUT evidence is authentic against live state. +- **Authorization:** the migration record states explicit repository-owner authorization for the orchestrator-confirmation-gated task [P6-T3]. This is a committed, PR-visible statement (not confined to gitignored state). +- **Under-gating:** none. Before the PUT the old contexts over-blocked; the swap was atomic; after the PUT all five gates are required and all five report. **Verdict: PASS.** + +### 7.5 Cycle-1 finding dispositions + +| Cycle-1 finding | Status | Evidence | +| --- | --- | --- | +| B1 (Blocking) green run | **Resolved** | Run 31840944277 @ current head, success 5/5 (section 7.3) | +| F2 (Minor) README `CI / <gate>` wording | **Resolved** | README caveat 2 now uses `<caller job> / <callee job>`; the five verbatim context strings and the `<caller job id> / <callee job name>` form rule are now listed explicitly | +| F3 (Minor) baseline provenance sibling | **Resolved** | `baseline.provenance.json` and `post-split-timing.provenance.json` present with `runner_class`, `host_signature`, `workflow_run_url` | +| F4 (Info) actionlint checksum | Unchanged, accepted | Deliberately not fixable within byte-identity AC; follow-up candidate | +| F5 (Info) spurious autoclose tokens | Superseded | This review's refreshed summary lists only #553 | + +### 7.6 Timing evidence and the outlier classification + +`ci-split-timing-comparison.2026-08-14T11-10.md` + addendum report all three samples (259s, 433s, 245s vs 444s baseline). Assessment of the run-B (433s, `31812508684`) outlier classification: + +- The step-level table shows compute-bound steps scaled uniformly (~1.6x build, ~1.6x test, 5.3x NuGet restore) while fixed-cost steps were flat — a signature consistent with a slow runner instance/contended I/O, not workflow structure (the YAML is byte-identical across all samples; confirmed by lineage diff). +- Queueing is excluded on evidence: all five jobs started within seconds of each other in every sample. +- Two of three samples cluster at 245–259s; the addendum retains run B as the measurement of record (per plan designation) rather than discarding it, and gates no numeric threshold on the figures. My dispatched run adds a fourth de-facto sample at 296s, between the cluster and the outlier, consistent with the hosted-runner-variance explanation. +- **Assessment: the outlier classification is adequately supported**, and the reporting is conservative (unfavourable sample retained as measurement of record; median reported separately). + +### 7.7 Evidence Location Compliance + +`git diff --name-only 35e02895..HEAD | grep -E '^artifacts/(baselines|qa|evidence|coverage)/'` returns zero files. All new evidence (ruleset-migration, probes, timing, provenance) lives under the canonical `docs/features/active/2026-08-14-ci-parallel-job-split-553/evidence/<kind>/` tree. **Verdict: PASS.** No evidence-location overrides were requested or rejected. + +## 8. Gaps and Exceptions + +1. **Stale caller inputs (documented, corrected):** supplied merge base and all cited SHAs were pre-rebase. Recomputed base `35e02895`; current-lineage probe pairs audited; B1 resolved at the actual head. No scope was narrowed by this correction — it widened precision, not reduced coverage. +2. **Open over-blocking window (non-blocking risk):** the ruleset now requires the five new contexts, but the feature PR is not yet open (plan P3-T3 pending). Until this branch merges, every other PR to `main` is blocked (their heads run the old pipeline and cannot report the new contexts). This is the documented fail-closed state; the README instructs keeping the interval short. Recommendation: open and merge the PR promptly (code review observation R2). +3. **Plan bookkeeping lags execution (non-blocking):** the ruleset migration ([P6-T1..T4]) is executed and evidenced, but the plan checkboxes remain unchecked and the evidence filenames deviate from the plan-specified names (`evidence/other/ruleset-migration/ruleset-pre.json` vs planned `evidence/other/ruleset-pre-put.<TS>.json`, etc.). Substance over form: the artifacts carry the required content. The executor should reconcile plan checkboxes at P7 (code review observation R3). +4. **MCP template/validator tooling unavailable in-session** (same as cycle 1): headings reproduced from `policy-audit-template-usage` prose; documented assumption. +5. **Referenced validator scripts absent from repository** (same as cycle 1): green-run and evidence-location checks performed manually with `git`/`gh` equivalents. +6. **PR-context collector unavailable in-session:** summary and appendix hand-authored at 2026-08-14T21:12Z from `git diff --numstat` against the recomputed base, per the documented fallback. + +## 9. Summary of Changes (since cycle-1 audit) + +- Three probe/revert commit pairs demonstrating per-gate fault isolation (all net-zero; runner-verified one-red-gate-per-probe). +- Phase 3–5 evidence: first green run, tailored-setup confirmation (assumption held; fallback not required), post-probe green, timing comparison + addendum (3 samples), test-results artifact parity, final actionlint, `$LASTEXITCODE` review, file-size audit, no-C#-diff check, check-run-name capture, pre-migration green. +- Cycle-1 review artifacts and AC check-offs committed; README context-form fix; two provenance JSONs. +- Branch-protection ruleset migration executed under explicit owner authorization, with pre/payload/post evidence. +- Branch rebased onto `main` @ `35e02895` (PR #552); ruleset-migration evidence committed on the rebased lineage. + +## 10. Compliance Verdict + +**PASS — zero blocking findings.** Cycle-1 B1 is resolved by a green `workflow_dispatch` run at the exact current head (dispatched and verified by this review). The ruleset migration is atomic, evidence-complete, and corroborated against live state. Probe fault isolation is empirically proven and fully reverted. All cycle-1 Minor findings are resolved. Remaining pre-merge work is procedural: open the PR (P3-T3), let the five required contexts report on the PR head, merge promptly to close the over-blocking window, then run the post-merge dispatch smoke (P7-T1) and reconcile plan/DoD checkboxes (P7-T2..T9). + +## Appendix A: Test Inventory + +No net test-file changes. The deliberate-test-failure probe modified one assertion in `UtilitiesCS.Test/Extensions/ExtToChar_Tests.cs` and was reverted byte-exactly. CI executes the pre-existing MSTest suite with the unchanged vstest invocation; run `31840944277` executed it green at the current head. + +## Appendix B: Toolchain Commands Reference + +Commands executed by this review (cycle 2): + +1. `git fetch origin main`; `git rev-parse HEAD`; `git merge-base HEAD origin/main`; `git ls-remote origin refs/heads/feature/ci-parallel-job-split-553` — head/base/lineage verification. +2. `git log --oneline <old-base>..HEAD`; `git merge-base --is-ancestor d83bf377 HEAD`; `git diff d83bf377 HEAD -- .github/` — rebase detection and workflow-content identity across lineages. +3. `git diff --name-status|--name-only|--numstat 35e02895..HEAD` (with language-extension filters and evidence-location filter) — true-scope enumeration; zero coverage-language files; zero prohibited evidence paths. +4. `git diff <probe>~1 <revert>` for the three probe pairs — net-zero revert verification. +5. `gh run view 31814562839 --json ...`; `gh run list --branch ...`; `gh api .../runs/{31810574239,31811211865,31811867381,31840944277}/jobs` — run/job conclusion verification, fault-isolation corroboration. +6. `gh api repos/drmoisan/TaskMaster/rulesets/18572843` — independent live ruleset GET. +7. `gh workflow run ci.yml --ref feature/ci-parallel-job-split-553`; `gh run watch 31840944277 --exit-status` — reviewer-initiated green-run resolution of B1 at the current head. +8. PR-context refresh: hand-authored `artifacts/pr_context.summary.txt` and `artifacts/pr_context.appendix.txt` from `git diff` against the recomputed base. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T17-12.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T17-12.md new file mode 100644 index 00000000..f195cef2 --- /dev/null +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/remediation-inputs.2026-08-14T17-12.md @@ -0,0 +1,57 @@ +# Remediation Inputs — ci-parallel-job-split (Issue #553), Cycle 2 (Closure Record) + +- **Date:** 2026-08-14 (artifact timestamp 2026-08-14T17-12) +- **Source audits:** + - `docs/features/active/2026-08-14-ci-parallel-job-split-553/policy-audit.2026-08-14T17-12.md` + - `docs/features/active/2026-08-14-ci-parallel-job-split-553/code-review.2026-08-14T17-12.md` + - `docs/features/active/2026-08-14-ci-parallel-job-split-553/feature-audit.2026-08-14T17-12.md` +- **Base:** `main` @ merge base `35e02895c29c5b65302f7d921431082c9ae6ce09` (recomputed; branch rebased since cycle 1) +- **Head:** `feature/ci-parallel-job-split-553` @ `9c00e37a79657505266dc47c514f136d9cebf1bc` +- **Blocking finding count: 0** + +This file exists to close the remediation loop opened by +`remediation-inputs.2026-08-14T10-21.md`. It supersedes that file as the +highest-timestamped remediation-inputs record and contains zero remediation-required +findings. No remediation plan is required and none should be authored. + +## Disposition of cycle-1 finding B1 (`modified-workflow-needs-green-run`) + +- Former severity (cycle 1): blocking. **Status: resolved.** +- Resolution evidence: green `workflow_dispatch` run + [31840944277](https://github.com/drmoisan/TaskMaster/actions/runs/31840944277) + at the exact current branch head `9c00e37a79657505266dc47c514f136d9cebf1bc`, + conclusion `success`, all five jobs `success` (per-job conclusions verified via + the runs API). The rule's definition is met literally: head SHA matches the + current branch head, conclusion is success, and the rule explicitly accepts + `workflow_dispatch` runs. +- Note: the previously cited run `31814562839` @ `d83bf377` did not itself satisfy + the rule for this head (pre-rebase, non-ancestor SHA); the reviewer dispatched + and observed the qualifying run rather than dispositioning around the mismatch. + +## Disposition of cycle-1 non-blocking findings + +- F2 (Minor, README context-name form): resolved — verified in + `code-review.2026-08-14T17-12.md`. +- F3 (Minor, baseline provenance sibling): resolved — `baseline.provenance.json` + and `post-split-timing.provenance.json` present with the required fields. +- F4 (Info, actionlint checksum): unchanged by design (byte-identity criterion); + optional hardening follow-up outside this feature. +- F5 (Info, spurious autoclose tokens): superseded by the refreshed PR-context + summary, which lists only #553. + +## Open non-remediation items (procedural, tracked in the plan of record) + +These are not audit findings and require no remediation plan; they are the +remaining scheduled tasks of `plan.2026-08-14T09-05.md`: + +1. Open the pull request to `main` (P3-T3, orchestrator-confirmation-required) + and merge promptly once the five required contexts report green on the PR + head. The migrated ruleset currently over-blocks all other PRs to `main` + until this branch lands (fail-closed, but keep the interval short). +2. Post-merge standalone dispatch smoke of each callee (P7-T1, + orchestrator-confirmation-required). +3. Reconcile plan Phase 6 checkboxes with the executed ruleset migration and + record the evidence-filename deviation + (`evidence/other/ruleset-migration/ruleset-{pre,new,post}.json` vs the + plan-specified names); complete P7-T2..T9 check-offs, including the DoD items + and `issue.md` mirrors. diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md index 4b5e229b..37a62e6f 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/spec.md @@ -339,7 +339,7 @@ determinable from repository data (research Q7). - [x] The four gate commands and the actionlint step are byte-identical to their pre-split counterparts, including the `/t:Rebuild` rationale comment, the `$LASTEXITCODE` guards, and the zero-test-assembly `throw` guard. -- [ ] The `main` ruleset's `required_status_checks` contexts are replaced in one +- [x] The `main` ruleset's `required_status_checks` contexts are replaced in one atomic PUT with the five context strings captured from a live green run on the PR head, with no window in which a merge can bypass a gate, and the pre-PUT JSON, PUT payload, and post-PUT GET response are recorded as @@ -348,7 +348,7 @@ determinable from repository data (research Q7). procedure and the branch-protection rename procedure. - [x] The reworked pipeline produces a green run against the branch head, satisfying `modified-workflow-needs-green-run`. -- [ ] Every gate enforced by the current `quality-gates` job is still enforced +- [x] Every gate enforced by the current `quality-gates` job is still enforced after the split; no check is dropped, weakened, or made non-required. - [x] Post-split wall-clock duration is measured with the same collection method as the baseline and recorded as evidence in this feature folder, compared diff --git a/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md b/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md index 50a6526d..cb352ec6 100644 --- a/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md +++ b/docs/features/active/2026-08-14-ci-parallel-job-split-553/user-story.md @@ -112,7 +112,7 @@ criterion admitted alternatives, the resolved form is stated. cross-job file sharing exists in the adopted topology; the only artifact operation is the preserved `test-results` upload to workflow storage, with `if: always()` and the same artifact name and paths as today. -- [ ] The `main` branch ruleset's `required_status_checks` contexts are updated to +- [x] The `main` branch ruleset's `required_status_checks` contexts are updated to match the new context names, with no window in which a merge can bypass a gate: exact context strings are captured from a live green run on the PR head, and the update is a single atomic PUT of the full writable ruleset @@ -121,7 +121,7 @@ criterion admitted alternatives, the resolved form is stated. procedure and the branch-protection rename procedure. - [x] The reworked pipeline produces a green run against the branch head, satisfying `modified-workflow-needs-green-run`. -- [ ] Every gate enforced by the current `quality-gates` job is still enforced +- [x] Every gate enforced by the current `quality-gates` job is still enforced after the split; no check is dropped, weakened, or made non-required. In particular, every gate command is byte-identical to its pre-split counterpart, the `/t:Rebuild` rationale comment and the zero-test-assembly