Skip to content
Open
43 changes: 18 additions & 25 deletions .github/workflows/cd-staging-gate.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# =============================================================================
# CD Staging Gate — Automated staging deployment verification and production
# promotion gate. Implements Phase 1-2 of the staged deployment workflow
# (docs/ops/DEPLOYMENT_WORKFLOW.md) with a manual approval gate for Phase 3.
# CD Staging Gate — Automated staging deployment verification with a parked
# production-environment handoff. Implements Phase 1-2 of the staged deployment
# workflow (docs/ops/DEPLOYMENT_WORKFLOW.md). Environment protection is external
# repository state and must not be inferred from this workflow definition.
#
# Triggers:
# - Release published (automatic)
# - Manual workflow dispatch (for re-runs or pre-release validation)
#
# ADR: ADR-0028 (Staged Deployment — Blue/Green with Canary Verification)
Expand All @@ -25,9 +25,6 @@ on:
required: false
type: boolean
default: false
release:
types:
- published

permissions:
contents: read
Expand Down Expand Up @@ -58,18 +55,13 @@ jobs:
id: resolve-tag
env:
INPUT_TAG: ${{ inputs.image_tag }}
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
if [[ -n "$INPUT_TAG" ]]; then
TAG="$INPUT_TAG"
elif [[ "$EVENT_NAME" == "release" ]]; then
TAG="$RELEASE_TAG"
else
TAG="$(git describe --tags --always)"
if [[ ! "$INPUT_TAG" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]]; then
echo "::error::image_tag must be a valid container tag (1-128 letters, digits, underscores, periods, or hyphens)."
exit 1
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "Resolved image tag: $TAG"
printf 'tag=%s\n' "$INPUT_TAG" >> "$GITHUB_OUTPUT"
echo "Resolved image tag: $INPUT_TAG"

- name: Setup .NET
uses: actions/setup-dotnet@v6
Expand Down Expand Up @@ -189,25 +181,26 @@ jobs:
EOF

# -----------------------------------------------------------------------
# Production Promotion Gate (manual approval)
# Production Environment Handoff (protection rules are external repository state)
# -----------------------------------------------------------------------
promotion-gate:
name: "Phase 3: Production Promotion Gate"
name: "Phase 3: Production Environment Handoff"
needs: [build-verification, staging-smoke]
if: always() && needs.build-verification.result == 'success' && (needs.staging-smoke.result == 'success' || needs.staging-smoke.result == 'skipped')
runs-on: ubuntu-latest
environment: production
steps:
- name: Promotion approved
- name: Record production environment handoff
run: |
cat <<EOF >> "$GITHUB_STEP_SUMMARY"
## Phase 3: Production Promotion Gate -- APPROVED
## Phase 3: Production Environment Handoff -- REACHED

- **Image tag**: \`${{ needs.build-verification.outputs.image_tag }}\`
- Build verification: passed
- Staging smoke: ${{ needs.staging-smoke.result }}
- Manual approval: granted
- **Next step**: Execute Phase 3 (canary deployment) and Phase 4 (promotion) per \`docs/ops/DEPLOYMENT_WORKFLOW.md\`
- Environment binding: \`production\`
- Approval protection: external repository setting; not asserted by this workflow
- **Next step**: Keep the parked deployment path inactive until #1504 is maintainer-resolved
EOF
echo "Production promotion approved for tag: ${{ needs.build-verification.outputs.image_tag }}"
echo "Follow the deployment workflow in docs/ops/DEPLOYMENT_WORKFLOW.md for Phases 3-4."
echo "Production environment handoff reached for tag: ${{ needs.build-verification.outputs.image_tag }}"
echo "This workflow does not assert that environment approval protection is configured."
4 changes: 3 additions & 1 deletion .github/workflows/reusable-docs-governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,6 @@ jobs:
run: node scripts/check-golden-principles.mjs

- name: Validate GitHub operations governance invariants
run: node scripts/check-github-ops-governance.mjs
run: |
node --test scripts/check-github-ops-governance.test.mjs
Comment thread
Chris0Jeky marked this conversation as resolved.
node scripts/check-github-ops-governance.mjs
1 change: 1 addition & 0 deletions OUTSTANDING_TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Analysis docs: `docs/PROJECT_TRAJECTORY.md` (strengths + path) and `docs/COURSE_

## E. Revival overnight deferrals (added 2026-07-13)

- [ ] **#1504 — protect the production environment before staging-gate reactivation.** Configure required reviewers in repository settings, prove the protection with a manual rehearsal, and only then reconsider automatic promotion. This is maintainer-only; the parked workflow does not assert that external protection exists.
- [ ] **#1173** — Apply and verify the required branch-protection settings in GitHub. This is repository-settings work and remains maintainer-only.
- [ ] **#1303** — Push the `v0.1.0` release tag only after the maintainer accepts the release deck and its pipeline evidence. Agents may repair the pipeline but must not push the tag.
- [ ] **#1482 — Taskdeck name, pre-commercial legal residuals.** *(Supersedes the old "#1299 trademark check" line: #1299 shipped with PR #1337 on 2026-07-25 and its AC4 search + cost + recommendation are recorded on the issue.)* What remains is genuinely legal and none of it blocks the free beta: an **attorney registrability opinion** for Classes 9/42 asking about *descriptiveness* (not just conflict), a read on **TASKADE** (live, incontestable US Cl. 42 mark covering task management), the UKIPO/EUIPO coverage gaps (UKIPO hard-blocked automated access, so all GB data is indirect), perishable namespace reservations, and the **keep-or-rename decision before `v0.1.0` (#1303)** — renaming is nearly free pre-tag and expensive after.
Expand Down
4 changes: 4 additions & 0 deletions docs/IMPLEMENTATION_MASTERPLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Companion Active Docs:
- **Post-`#1373` diagnostic successor (`#1512`):** structured, bounded correlation plus outer/last-inspected exception type, explicit truncation, aggregate-branch, and SQLite-code evidence is now available when the concurrent-card assertion fails. Normal middleware logs are metadata-only, the client still receives the generic GP-03 500, and no request/user/credential/exception-message content enters the diagnostic sink. Pre/post exact stress, the five-case historical/current concurrency matrix, and five CI-equivalent API runs stayed green and did not reproduce the original 500; therefore no causal exception, `SQLITE_BUSY` classification, retry, quarantine, or product fix is claimed.
- **Still open:** retain `#1512` until a real recurrence identifies the cause and the narrow repair passes the full serialized backend suite plus exact-head Windows/Ubuntu CI.

## Delivery update (2026-07-27, release workflow truth)

- **Manual-only parked staging gate (`#1228`):** `.github/workflows/cd-staging-gate.yml` no longer subscribes to `release: published`; its required-input `workflow_dispatch` path, jobs, permissions, and `production` environment binding remain defined for a future deliberate operator run. That parent workflow is not runnable on a clean hosted worker yet because it does not supply the Compose-required `TASKDECK_CONNECTORS_ENCRYPTION_KEY`; `#1506` owns the no-environment, no-deploy rehearsal and current-doc correction. The repository does not currently configure that environment or required reviewers, so the workflow no longer calls the binding an approval gate; maintainer-owned activation protection is tracked in `#1504`. Active beta release lanes (`release-desktop.yml`, `ci-release.yml`, and `release-security.yml`) are unchanged, so publishing a release cannot start this parked staging path.

## Delivery update (2026-07-26, agentic governance)

- **Failure-ledger projection gate (`#1492`):** Required Docs Governance now pins Python 3.12 and runs the existing JSONL↔Markdown synchronization unittest before the governance checks, so a JSONL-only change with stale generated Markdown fails Required CI without regeneration masking it. Local agentic update workflows use the distinct render-then-test order so hook-appended JSONL can be projected, and the smoke contract pins both sides of that distinction.
Expand Down
4 changes: 2 additions & 2 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ Eight parallel worktree agents delivered new features, security infrastructure,
- **CLD-03 OAuth PKCE and account linking** (`#676`/`#812`): DB-backed auth code store replacing in-memory `ConcurrentDictionary` — `OAuthAuthCode` entity with EF migration, `IOAuthAuthCodeRepository` with atomic `TryConsumeAtomicAsync` (raw SQL `UPDATE WHERE IsConsumed = 0 AND ExpiresAt > now`); PKCE support via `UsePkce = true` in ASP.NET Core 8 OAuth middleware; account linking endpoints (`POST /api/auth/github/link`, `DELETE /api/auth/github/link`, `GET /api/auth/linked-accounts`) with conflict detection and session verification; frontend Linked Accounts section in `ProfileSettingsView` with Link/Unlink buttons and avatar display; 24+ backend tests; adversarial review fixed CSRF on account linking, TOCTOU in expiry check, JWT plaintext in DB, DoS via full-table load, and unbounded table growth

**Ops & Observability:**
- **OPS-09 Staged deployment workflow** (`#101`/`#806`): ADR-0028 documents blue/green + canary deployment strategy with rollback criteria; `docs/ops/DEPLOYMENT_WORKFLOW.md` canonical 4-phase workflow (build verification → staging → production canary → production promotion) with rollback procedures, database migration safety, emergency hotfix override, and ownership/escalation model; `docs/ops/RELEASE_CHECKLIST.md` versioned smoke verification (7 pre-deploy + 9 automated staging + 7 manual staging + 7 canary + 6 post-promotion + 5 post-release checks) with failure response matrix; `scripts/deploy/smoke-test.sh` portable smoke test (9 automated checks: health, API, auth, board auth gate, frontend, SignalR, static assets, security headers, container restart detection); `.github/workflows/cd-staging-gate.yml` with `production` environment manual approval gate; adversarial review fixed script injection in CI workflow and unscoped container checks. _(⚠️ Parked by the 2026-06-13 archive pivot — staged cloud rollout de-scoped, see the parked-cloud note below. The workflow still auto-triggers on `release: published` and would hang on the non-existent `production` environment for the personal build; disabling that trigger is tracked in **#1228**.)_
- **OPS-09 Staged deployment workflow** (`#101`/`#806`): ADR-0028 documents blue/green + canary deployment strategy with rollback criteria; `docs/ops/DEPLOYMENT_WORKFLOW.md` canonical 4-phase workflow (build verification → staging → production canary → production promotion) with rollback procedures, database migration safety, emergency hotfix override, and ownership/escalation model; `docs/ops/RELEASE_CHECKLIST.md` versioned smoke verification (7 pre-deploy + 9 automated staging + 7 manual staging + 7 canary + 6 post-promotion + 5 post-release checks) with failure response matrix; `scripts/deploy/smoke-test.sh` portable smoke test (9 automated checks: health, API, auth, board auth gate, frontend, SignalR, static assets, security headers, container restart detection); `.github/workflows/cd-staging-gate.yml` retains a `production` environment binding but no live environment protection is configured. _(⚠️ Parked by the 2026-06-13 archive pivot — staged cloud rollout de-scoped, see the parked-cloud note below. The workflow is manual-dispatch-only after **#1228**, so publishing a beta release cannot invoke it; required-reviewer protection before any reactivation is maintainer-owned in **#1504**.)_
- **OBS-02 Error tracking and product analytics** (`#549`/`#811`): config-gated Sentry SDK for backend (`Sentry.AspNetCore` with `BeforeSend` PII scrubbing for emails/JWTs, `ServerName` blanked) and frontend; opt-in product telemetry service (`TelemetryEventService`) aligned with `docs/product/TELEMETRY_TAXONOMY.md` — property key allowlist (15 safe keys), max 10 properties, 200-char value truncation; `TelemetryController` with anonymous config endpoint and authenticated events endpoint; Plausible/Umami analytics script injection (`useAnalyticsScript`) with HTTPS-only URL validation; Pinia `telemetryStore` with consent management, event buffering, and flush; DNT/GPC privacy signal detection prevents auto-restore of consent; telemetry consent toggle in `ProfileSettingsView`; `docs/ops/OBSERVABILITY_SETUP.md` configuration guide; all telemetry opt-in and disabled by default; 38 backend + 25 frontend tests; adversarial review fixed Sentry PII leak, arbitrary properties injection, XSS via script URL, and DNT non-compliance

## Post-Merge Housekeeping (2026-04-12)
Expand Down Expand Up @@ -1252,7 +1252,7 @@ Mutation testing workflow: `.github/workflows/mutation-testing.yml`
- Backend Stryker.NET (Domain) + Frontend Stryker JS (captureStore/boardStore)
- Non-blocking; HTML/JSON reports uploaded as 30-day artifacts

> _(Historical archive-period caveat, 2026-06-13–2026-07-10: release/tag-triggered distribution lanes were parked during the archive pivot. ADR-0044 supersedes that hold and reactivates only the public-beta release work admitted by `docs/REVIVAL_PLAN.md` (REVIVAL-07/14). Tag/release events still **auto-fire** these workflows: `release-desktop.yml` builds the cross-platform self-contained exe + publishes a GitHub Release on any `v*` tag push, and `ci-release.yml`/`release-security.yml` run on `v*` tags / `release: published`. The `release: published` event also triggers `cd-staging-gate.yml`, which then hangs on a missing `production` environment — disabling that trigger is tracked in **#1228**. SBOM/provenance (`reusable-sbom-provenance.yml`) remains a release-security artifact.)_
> _(Historical archive-period caveat, 2026-06-13–2026-07-10: release/tag-triggered distribution lanes were parked during the archive pivot. ADR-0044 supersedes that hold and reactivates only the public-beta release work admitted by `docs/REVIVAL_PLAN.md` (REVIVAL-07/14). Tag/release events still **auto-fire** these workflows: `release-desktop.yml` builds the cross-platform self-contained exe + publishes a GitHub Release on any `v*` tag push, and `ci-release.yml`/`release-security.yml` run on `v*` tags / `release: published`. `cd-staging-gate.yml` is manual-dispatch-only after **#1228**, so those release events no longer enter its parked `production` environment gate. SBOM/provenance (`reusable-sbom-provenance.yml`) remains a release-security artifact.)_

Release workflow: `.github/workflows/ci-release.yml` _(release-triggered; container-image build/export is active only through REVIVAL-07's bounded beta-release scope; SBOM/provenance remains required)_

Expand Down
6 changes: 6 additions & 0 deletions docs/TESTING_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ py -3 -B scripts/agent_hooks/render_failure_ledger.py; if ($LASTEXITCODE -ne 0)
py -3 -B -m unittest discover -s scripts/agent_hooks -p "test_render_failure_ledger.py"; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node scripts\check-docs-governance.mjs; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node scripts\check-golden-principles.mjs; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node --test scripts\check-github-ops-governance.test.mjs; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
node scripts\check-github-ops-governance.mjs; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
```

Expand All @@ -153,9 +154,14 @@ python3 -B scripts/agent_hooks/render_failure_ledger.py
python3 -B -m unittest discover -s scripts/agent_hooks -p 'test_render_failure_ledger.py'
node scripts/check-docs-governance.mjs
node scripts/check-golden-principles.mjs
node --test scripts/check-github-ops-governance.test.mjs
node scripts/check-github-ops-governance.mjs
```

The staging-gate governance regression pins the complete parked workflow after normalizing line
endings. Any intentional edit to that workflow requires a reviewed digest and fixture update plus
Actionlint; substring checks are not treated as proof of effective YAML semantics.

The native-Windows hook smoke test executes the configured `.claude/settings.json` command handlers with `CLAUDE_PROJECT_DIR` set, including PowerShell-hosted handlers, representative dangerous Bash-command denials, missing-launcher and missing-policy fail-closed probes, failure-ledger redaction, and pre-commit no-op behavior. Its payloads identify the `Bash` tool; it does not prove native PowerShell-tool interception. That T4 policy gap is tracked by [#1497](https://github.com/Chris0Jeky/Taskdeck/issues/1497).

When MCP availability itself is part of the change, also run the active runtime's MCP listing/auth command if available. Do not claim remote MCP connectivity unless the current session actually verified it.
Expand Down
6 changes: 3 additions & 3 deletions docs/ops/DEPLOYMENT_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,12 @@ Emergency overrides must still pass Phase 1 (CI build) and Phase 2 (staging smok

The `cd-staging-gate.yml` workflow automates Phase 1 and Phase 2 gates:

> ⚠️ _(Parked by the 2026-06-13 archive pivot — staged cloud deployment de-scoped. The workflow **still auto-triggers on `release: published`** and then waits on a `production` environment that does not exist for the personal build, so the optional archival release would **hang** it. Disabling/gating that trigger is tracked in **#1228** — see `docs/ops/README.md`.)_
> ⚠️ _(Parked by the 2026-06-13 archive pivot — staged cloud deployment de-scoped. After **#1228**, the workflow is manual-dispatch-only, so release publication cannot invoke its parked `production` environment gate. See `docs/ops/README.md`.)_

- Triggers on release publish or manual dispatch
- Triggers only on manual dispatch with an explicit image tag
- Builds and verifies container images
- Runs the smoke test suite against a CI-hosted staging environment
- Requires manual approval (GitHub environment protection) before Phase 3 can proceed
- Binds the final handoff job to `production`, but does **not** itself prove approval protection; the live repository has no protected `production` environment, and #1504 must be maintainer-resolved before reactivation
- See `.github/workflows/cd-staging-gate.yml` for the workflow definition

## Ownership and Escalation
Expand Down
2 changes: 1 addition & 1 deletion docs/ops/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This folder contains deployment, observability, and human-operator runbooks.

## Deployment

- `DEPLOYMENT_WORKFLOW.md` — Staged blue/green + canary workflow (ADR-0028) — **parked by the archive pivot** (hosted/multi-instance; not the personal run path). The personal release path is the self-contained executable build + smoke in `.github/workflows/release-desktop.yml`. ⚠️ **Caveat:** the implementing workflow `.github/workflows/cd-staging-gate.yml` still auto-triggers on `release: published` and then waits on a `production` environment that doesn't exist for the personal build — so the optional `v0.1.0` archival release would hang it. Disabling/gating that trigger is tracked in **#1228** (a workflow change, out of this docs PR's scope).
- `DEPLOYMENT_WORKFLOW.md` — Staged blue/green + canary workflow (ADR-0028) — **parked by the archive pivot** (hosted/multi-instance; not the personal run path). The beta release path is the self-contained executable build + smoke in `.github/workflows/release-desktop.yml`. The implementing `.github/workflows/cd-staging-gate.yml` is manual-dispatch-only after **#1228**; release publication no longer invokes its parked `production` environment gate.
- `RELEASE_CHECKLIST.md` — Smoke checklist for the OPS-09 staged (blue/green/canary) deployment — **parked by the archive pivot** (hosted/multi-instance; requires staging/prod URLs, container images, rollback slots — not the personal run path).
- `DEPLOYMENT_CONTAINERS.md` — Container baseline (Dockerfiles, compose, nginx)
- `DEPLOYMENT_HARDENING_MATRIX.md` — Container hardening verification matrix
Expand Down
Loading
Loading