Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions .github/workflows/reusable-frontend-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ permissions:
contents: read

jobs:
source-launcher:
name: Source Launcher (Linux)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ inputs.node-version }}

# Same Linux-only launcher contract as the former frontend matrix step.
# A launcher failure must not erase unrelated frontend test results (#2332).
# Preserve #2378's step timeout and the suite's own nine-minute watchdog.
# PowerShell cases remain local Windows evidence under SC-3; no Windows suite is added or removed.
- name: Run source launcher regression suite
if: runner.os == 'Linux'
timeout-minutes: 10
run: node --test --test-concurrency=1 --test-timeout=30000 scripts/ci/dev-up.test.mjs

frontend-unit:
name: Frontend Unit (${{ matrix.os }})
runs-on: ${{ matrix.os }}
Expand All @@ -34,18 +58,6 @@ jobs:
cache: npm
cache-dependency-path: frontend/taskdeck-web/package-lock.json

# #2378: this step launches real PowerShell/Bash launchers and their API/Vite children. When
# it hangs it used to consume the whole job budget (cancellations observed at 25:00), taking
# the job's remaining steps down with it. A passed windows-latest run of this step measured
# 6m31s, so 10 minutes fails a hang fast while leaving ~1.5x headroom on a slow runner. The
# suite's own watchdog trips at 9 minutes first and logs which launcher trees leaked.
# CI-07 #2331 under the SC-3 re-ruling (hosted minutes are Linux-only): the PowerShell launcher
# cases are local Windows evidence now, while the Bash launcher cases still run on the Linux leg.
- name: Run source launcher regression suite
if: runner.os == 'Linux'
timeout-minutes: 10
run: node --test --test-concurrency=1 --test-timeout=30000 scripts/ci/dev-up.test.mjs

- name: Install frontend dependencies
working-directory: frontend/taskdeck-web
run: npm ci
Expand Down
31 changes: 16 additions & 15 deletions ci/policy.v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"api-integration-linux": { "checkName": "API Integration / API Integration (ubuntu-latest)", "family": "backend", "runner": "selfHostedLinuxHeavy", "trustedOnly": true, "hostedFallback": "hostedLinux", "description": "API integration suite on Linux" },
"api-integration-windows": { "checkName": "API Integration / API Integration (windows-latest)", "family": "windows", "runner": "selfHostedWindowsCompat", "trustedOnly": true, "hostedFallback": "hostedWindows", "description": "API integration suite on Windows (full duplicate today; MCP/process/SQLite contract shard after CI-06/CI-07)" },
"migration-validation": { "checkName": "Migration Validation / Migration Validation", "family": "persistence", "runner": "hostedLinux", "trustedOnly": false, "description": "EF Core migration chain validation" },
"source-launcher-linux": { "checkName": "Frontend Unit / Source Launcher (Linux)", "family": "launchers", "runner": "hostedLinux", "trustedOnly": false, "description": "source launcher regression, isolated from frontend semantics; Linux only under SC-3" },
"frontend-unit-linux": { "checkName": "Frontend Unit / Frontend Unit (ubuntu-latest)", "family": "frontend", "runner": "selfHostedLinuxHeavy", "trustedOnly": true, "hostedFallback": "hostedLinux", "description": "lint + typecheck + build + coverage + bundle budget" },
"frontend-unit-windows": { "checkName": "Frontend Unit / Frontend Unit (windows-latest)", "family": "windows", "runner": "selfHostedWindowsCompat", "trustedOnly": true, "hostedFallback": "hostedWindows", "description": "full frontend duplicate today; launcher/platform subset after CI-08 (launcher suite runs on the Linux leg only since CI-07 #2331)" },
"paper-color-audit": { "checkName": "Paper Color Audit / Paper Color Audit", "family": "frontend", "runner": "hostedLinux", "trustedOnly": false, "description": "hex-literal regression gate" },
Expand Down Expand Up @@ -94,21 +95,21 @@
{ "id": "mcp-config", "patterns": [".mcp.json", "mcp*.example.json", "mcp-*.example.json"], "riskFloor": "R3", "lanes": ["api-integration-linux", "api-integration-windows"] },
{ "id": "worktree-helpers", "patterns": ["scripts/git/**", "scripts/worktree_guard.ps1", "scripts/check-codex-path-trust.ps1", "scripts/github/**"], "riskFloor": "R2", "lanes": ["worktree-helper-windows", "docs-governance"] },
{ "id": "governance-scripts", "patterns": ["scripts/check-*.mjs", "scripts/check-*.test.mjs"], "riskFloor": "R2", "lanes": ["docs-governance"] },
{ "id": "scripts-other", "patterns": ["scripts/**"], "riskFloor": "R2", "lanes": ["docs-governance"] },
{ "id": "backend-domain", "patterns": ["backend/src/Taskdeck.Domain/**"], "riskFloor": "R2", "lanes": ["backend-unit-linux", "backend-architecture", "api-integration-linux", "migration-validation"] },
{ "id": "backend-application", "patterns": ["backend/src/Taskdeck.Application/**"], "riskFloor": "R2", "lanes": ["backend-unit-linux", "backend-architecture", "api-integration-linux"] },
{ "id": "backend-infrastructure", "patterns": ["backend/src/Taskdeck.Infrastructure/**"], "riskFloor": "R2", "lanes": ["api-integration-linux", "backend-architecture", "migration-validation", "backend-unit-linux"] },
{ "id": "persistence-migrations", "patterns": ["backend/src/Taskdeck.Infrastructure/Migrations/**", "backend/src/Taskdeck.Infrastructure/Persistence/**"], "riskFloor": "R3", "lanes": ["migration-validation", "api-integration-linux", "api-integration-windows", "backend-architecture"] },
{ "id": "backend-api", "patterns": ["backend/src/Taskdeck.Api/**"], "riskFloor": "R2", "lanes": ["api-integration-linux", "backend-architecture", "e2e-smoke"] },
{ "id": "mcp-process", "patterns": ["backend/src/**/Mcp/**", "backend/src/**/*Mcp*", "backend/src/Taskdeck.Api/Program.cs", "backend/tests/Taskdeck.Api.Tests/**/*Mcp*", "scripts/mcp/**", ".mcp.json", "mcp*.example.json"], "riskFloor": "R3", "lanes": ["api-integration-linux", "api-integration-windows", "e2e-smoke"] },
{ "id": "auth-security", "patterns": ["backend/src/**/Auth/**", "backend/src/**/*Auth*", "backend/src/**/Security/**", "backend/src/**/*Security*", "backend/src/**/*Authorization*", "backend/src/**/*Identity*", "backend/src/**/*ApiKey*", "backend/src/**/*Mfa*", "backend/src/**/*Jwt*", "backend/src/**/*Password*", "backend/src/**/*Oidc*", "backend/src/**/*ExternalLogin*", "backend/src/**/*Registration*", "backend/src/**/*Token*", "backend/src/**/*Credential*", "backend/src/**/*Session*", "backend/src/**/Connectors/**", "backend/src/Taskdeck.Api/Middleware/**"], "riskFloor": "R3", "lanes": ["api-integration-linux", "backend-unit-linux", "e2e-smoke"] },
{ "id": "capture-proposal-executor", "patterns": ["backend/src/**/*Capture*", "backend/src/**/*Proposal*", "backend/src/**/*Provenance*", "backend/src/**/*Triage*", "backend/src/**/*Executor*", "backend/src/**/*Revision*", "backend/src/**/*Automation*", "backend/src/**/*Execution*", "backend/src/**/Pipeline/**", "backend/src/**/*Operation*"], "riskFloor": "R3", "lanes": ["backend-unit-linux", "api-integration-linux", "e2e-smoke"] },
{ "id": "backend-cli", "patterns": ["backend/src/Taskdeck.Cli/**"], "riskFloor": "R2", "lanes": ["backend-unit-linux", "backend-architecture"] },
{ "id": "backend-tests", "patterns": ["backend/tests/**"], "riskFloor": "R1", "lanes": ["backend-unit-linux", "api-integration-linux", "backend-architecture"] },
{ "id": "backend-project-files", "patterns": ["backend/**/*.csproj", "backend/Taskdeck.sln", "backend/stryker-config.json"], "riskFloor": "R3", "lanes": ["backend-unit-linux", "api-integration-linux", "backend-architecture", "migration-validation", "container-images", "dependency-security"] },
{ "id": "frontend-src", "patterns": ["frontend/taskdeck-web/src/**", "frontend/taskdeck-web/index.html", "frontend/taskdeck-web/public/**", "frontend/taskdeck-web/vite.config.ts", "frontend/taskdeck-web/vitest.config.ts", "frontend/taskdeck-web/tsconfig*.json", "frontend/taskdeck-web/tailwind.config.js", "frontend/taskdeck-web/postcss.config.js", "frontend/taskdeck-web/eslint.config.js", "frontend/taskdeck-web/stryker.config.mjs"], "riskFloor": "R2", "lanes": ["frontend-unit-linux", "paper-color-audit", "e2e-smoke"] },
{ "id": "frontend-e2e", "patterns": ["frontend/taskdeck-web/tests/**", "frontend/taskdeck-web/playwright*.ts"], "riskFloor": "R1", "lanes": ["e2e-smoke", "frontend-unit-linux"] },
{ "id": "launchers-windows", "patterns": ["frontend/taskdeck-web/scripts/**", "scripts/dev-up.ps1", "scripts/dev-up.sh", "scripts/backup.ps1", "scripts/restore.ps1", "scripts/clean-workspace.ps1", "backend/src/**/Desktop*", "backend/src/**/*Launcher*"], "riskFloor": "R3", "lanes": ["frontend-unit-windows", "frontend-unit-linux", "backend-unit-windows"] },
{ "id": "scripts-other", "patterns": ["scripts/**"], "riskFloor": "R2", "lanes": ["docs-governance", "source-launcher-linux"] },
{ "id": "backend-domain", "patterns": ["backend/src/Taskdeck.Domain/**"], "riskFloor": "R2", "lanes": ["backend-unit-linux", "backend-architecture", "api-integration-linux", "migration-validation", "source-launcher-linux"] },
{ "id": "backend-application", "patterns": ["backend/src/Taskdeck.Application/**"], "riskFloor": "R2", "lanes": ["backend-unit-linux", "backend-architecture", "api-integration-linux", "source-launcher-linux"] },
{ "id": "backend-infrastructure", "patterns": ["backend/src/Taskdeck.Infrastructure/**"], "riskFloor": "R2", "lanes": ["api-integration-linux", "backend-architecture", "migration-validation", "backend-unit-linux", "source-launcher-linux"] },
{ "id": "persistence-migrations", "patterns": ["backend/src/Taskdeck.Infrastructure/Migrations/**", "backend/src/Taskdeck.Infrastructure/Persistence/**"], "riskFloor": "R3", "lanes": ["migration-validation", "api-integration-linux", "api-integration-windows", "backend-architecture", "source-launcher-linux"] },
{ "id": "backend-api", "patterns": ["backend/src/Taskdeck.Api/**"], "riskFloor": "R2", "lanes": ["api-integration-linux", "backend-architecture", "e2e-smoke", "source-launcher-linux"] },
{ "id": "mcp-process", "patterns": ["backend/src/**/Mcp/**", "backend/src/**/*Mcp*", "backend/src/Taskdeck.Api/Program.cs", "backend/tests/Taskdeck.Api.Tests/**/*Mcp*", "scripts/mcp/**", ".mcp.json", "mcp*.example.json"], "riskFloor": "R3", "lanes": ["api-integration-linux", "api-integration-windows", "e2e-smoke", "source-launcher-linux"] },
{ "id": "auth-security", "patterns": ["backend/src/**/Auth/**", "backend/src/**/*Auth*", "backend/src/**/Security/**", "backend/src/**/*Security*", "backend/src/**/*Authorization*", "backend/src/**/*Identity*", "backend/src/**/*ApiKey*", "backend/src/**/*Mfa*", "backend/src/**/*Jwt*", "backend/src/**/*Password*", "backend/src/**/*Oidc*", "backend/src/**/*ExternalLogin*", "backend/src/**/*Registration*", "backend/src/**/*Token*", "backend/src/**/*Credential*", "backend/src/**/*Session*", "backend/src/**/Connectors/**", "backend/src/Taskdeck.Api/Middleware/**"], "riskFloor": "R3", "lanes": ["api-integration-linux", "backend-unit-linux", "e2e-smoke", "source-launcher-linux"] },
{ "id": "capture-proposal-executor", "patterns": ["backend/src/**/*Capture*", "backend/src/**/*Proposal*", "backend/src/**/*Provenance*", "backend/src/**/*Triage*", "backend/src/**/*Executor*", "backend/src/**/*Revision*", "backend/src/**/*Automation*", "backend/src/**/*Execution*", "backend/src/**/Pipeline/**", "backend/src/**/*Operation*"], "riskFloor": "R3", "lanes": ["backend-unit-linux", "api-integration-linux", "e2e-smoke", "source-launcher-linux"] },
{ "id": "backend-cli", "patterns": ["backend/src/Taskdeck.Cli/**"], "riskFloor": "R2", "lanes": ["backend-unit-linux", "backend-architecture", "source-launcher-linux"] },
{ "id": "backend-tests", "patterns": ["backend/tests/**"], "riskFloor": "R1", "lanes": ["backend-unit-linux", "api-integration-linux", "backend-architecture", "source-launcher-linux"] },
{ "id": "backend-project-files", "patterns": ["backend/**/*.csproj", "backend/Taskdeck.sln", "backend/stryker-config.json"], "riskFloor": "R3", "lanes": ["backend-unit-linux", "api-integration-linux", "backend-architecture", "migration-validation", "container-images", "dependency-security", "source-launcher-linux"] },
{ "id": "frontend-src", "patterns": ["frontend/taskdeck-web/src/**", "frontend/taskdeck-web/index.html", "frontend/taskdeck-web/public/**", "frontend/taskdeck-web/vite.config.ts", "frontend/taskdeck-web/vitest.config.ts", "frontend/taskdeck-web/tsconfig*.json", "frontend/taskdeck-web/tailwind.config.js", "frontend/taskdeck-web/postcss.config.js", "frontend/taskdeck-web/eslint.config.js", "frontend/taskdeck-web/stryker.config.mjs"], "riskFloor": "R2", "lanes": ["frontend-unit-linux", "paper-color-audit", "e2e-smoke", "source-launcher-linux"] },
{ "id": "frontend-e2e", "patterns": ["frontend/taskdeck-web/tests/**", "frontend/taskdeck-web/playwright*.ts"], "riskFloor": "R1", "lanes": ["e2e-smoke", "frontend-unit-linux", "source-launcher-linux"] },
{ "id": "launchers-windows", "patterns": ["frontend/taskdeck-web/scripts/**", "scripts/dev-up.ps1", "scripts/dev-up.sh", "scripts/backup.ps1", "scripts/restore.ps1", "scripts/clean-workspace.ps1", "backend/src/**/Desktop*", "backend/src/**/*Launcher*"], "riskFloor": "R3", "lanes": ["frontend-unit-windows", "frontend-unit-linux", "backend-unit-windows", "source-launcher-linux"] },
{ "id": "containers-deploy", "patterns": ["deploy/**", "**/Dockerfile*", "**/docker-compose*.yml", ".dockerignore", "scripts/deploy/**"], "riskFloor": "R3", "lanes": ["container-images", "dependency-security"] },
{ "id": "load-and-evals", "patterns": ["tests/**", "evals/**"], "riskFloor": "R1", "lanes": [] }
],
Expand Down
31 changes: 31 additions & 0 deletions docs/ci/continuation/LANES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Source-launcher and frontend verification boundaries

Date: 2026-09-10. Owners: #2331, #2332 and #2329. Parent: [engineering contract](README.md).

`reusable-frontend-unit.yml` defines two independent job families. `source-launcher` runs the existing regression once on hosted Linux; `frontend-unit` retains the complete Linux/Windows matrix and every previous frontend semantic check. No launcher source or test implementation is changed.

The exact launcher command remains `node --test --test-concurrency=1 --test-timeout=30000 scripts/ci/dev-up.test.mjs`. Its Linux guard and ten-minute step timeout are unchanged. The separate job has a fifteen-minute ceiling for checkout/Node setup around the suite's own watchdog. Node uses the caller's version input. The new checkout disables credential persistence; preserve concurrent checkout hardening during integration. PowerShell cases remain governed by SC-3; this slice does not change #2858's cleanup implementation.

## Independent results without lost qualification

Previously a launcher failure stopped the Linux frontend job before lint/typecheck/build/coverage, and backend changes affected that mixed job's input identity. Sibling jobs can finish independently. A launcher failure still fails the reusable call/E2E prerequisite; it does not erase an independently successful frontend result. The caller's `needs: frontend-unit` waits for both families.

Parallelism introduces one extra job's setup/rounding overhead. No measured savings are claimed. Full frontend coverage stays full; no partial threshold is substituted.

## Canonical and historical accounting

The existing shadow policy gains `source-launcher-linux`, context `Frontend Unit / Source Launcher (Linux)`, using hosted Linux. Its ownership is added to fifteen existing backend/frontend/launcher/script groups, without changing ANY existing group ID, path pattern or risk floor. Unknown paths therefore retain their original escalation and nightly suite mapping remains valid. No required check is registered.

The adapter gives the new lane the backend/frontend/script closure. Post-split frontend fingerprints no longer depend on backend sources. Historical policies without the new lane retain the original broad compound-job contract; old evidence is not reinterpreted retroactively. All Taskdeck contracts remain unreviewed/reuse-disabled.

Original policy blob: `8fc840fac37bef19790e3b4ec6e03280ea5c81ee`. Removing the new lane and its existing-group references reconstructs the original parsed policy exactly. Canonical receipt schema versions are unchanged.

## Hosted integration correction

The first hosted head `e66711b3` exposed nine self-test failures: a broad new ownership group lacked a nightly mapping and changed an overlap fixture; an inherited E2E test forbade the newly intended frontend barrier. The correction removes the broad group rather than weakening the nightly validator, inherits only existing ownership, and incorporates the parent E2E contract correction. Earlier failures remain recorded. Fresh hosted qualification is required.

Local continuation plus launcher-placement tests originally passed 264/264 on Node 22.16.0/Linux, but that was not the complete existing Smart CI suite. The corrected local overlay, including in-progress provider tests, passes 309/309; the same local limitations remain. The actual product launcher/frontend suites, full hosted self-tests, independent review and maintainer review are still required. Do not claim earlier fixture success qualified the failed hosted head.

## Rollback

Restore the original launcher step when removing the separate job; never remove the new job alone. Remove its lane and existing-group references, and restore the broad adapter contract together. Preserve concurrent launcher fixes/credential hardening. Canonical coordinator-owned documents are left unchanged; reconcile their topology prose with this scoped record after the stack lands.
Comment thread
Chris0Jeky marked this conversation as resolved.
1 change: 1 addition & 0 deletions scripts/ci/smart-ci/continuation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ import './continuation/tests/planner.test.mjs';
import './continuation/tests/adapter.test.mjs';
import './continuation/tests/repository.test.mjs';
import './continuation/tests/workflow.test.mjs';
import './continuation/tests/launcher-inputs.test.mjs';
5 changes: 3 additions & 2 deletions scripts/ci/smart-ci/continuation/adapters/taskdeck.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ export function taskdeckContracts(policy) {
let components = ['repository'];
if (id.startsWith('backend-') || id.startsWith('api-integration') || id === 'migration-validation') components = ['backend', 'scripts'];
if (id === 'frontend-unit-windows' || id === 'paper-color-audit') components = ['frontend', 'scripts'];
// Linux Frontend currently executes dev-up.test.mjs, including the API process.
if (id === 'frontend-unit-linux') components = ['launcher'];
// Preserve broad historical contracts when inspecting pre-split canonical plans.
if (id === 'frontend-unit-linux') components = Object.hasOwn(policy.lanes, 'source-launcher-linux') ? ['frontend', 'scripts'] : ['launcher'];
if (id === 'source-launcher-linux') components = ['launcher'];
if (id === 'e2e-smoke' || id === 'container-images') components = ['journeys'];
const isWindows = id.endsWith('-windows') || id === 'worktree-helper-windows';
graph.tasks[id] = {
Expand Down
Loading
Loading