Skip to content

feat: FlightDeck 1.0.1 package and v1 rollout readiness#1

Merged
Gsbreddy merged 6 commits into
mainfrom
chore/1.0.1-v1-rollout-readiness
May 1, 2026
Merged

feat: FlightDeck 1.0.1 package and v1 rollout readiness#1
Gsbreddy merged 6 commits into
mainfrom
chore/1.0.1-v1-rollout-readiness

Conversation

@Gsbreddy

@Gsbreddy Gsbreddy commented May 1, 2026

Copy link
Copy Markdown
Collaborator

Ship the local-first CLI, schemas, tests, and CI. Slim-repo docs link to canonical flightdeckdev/flightdeck main. OpenTelemetry is optional-only.

Also: pytest basetemp under .tmp/pytest for Windows, Python 3.13–3.14 in CI, ruff 0.15.12 aligned with ruff-pre-commit, pre-commit-hooks v5, .gitattributes LF for golden bundle, CHANGELOG 1.0.1 section and empty Unreleased, RELEASE_NOTES v1.0.1 patch notes, README quickstart_smoke first.

zendaya and others added 4 commits May 1, 2026 15:35
Ship the local-first CLI, schemas, tests, and CI. Slim-repo docs link to
canonical flightdeckdev/flightdeck main. OpenTelemetry is optional-only.

Also: pytest basetemp under .tmp/pytest for Windows, Python 3.13–3.14 in CI,
ruff 0.15.12 aligned with ruff-pre-commit, pre-commit-hooks v5, .gitattributes
LF for golden bundle, CHANGELOG 1.0.1 section and empty Unreleased,
RELEASE_NOTES v1.0.1 patch notes, README quickstart_smoke first.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
@cursor

cursor Bot commented May 1, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Medium risk because it introduces new release automation (tag-driven PyPI publish) and touches core CLI/storage integrity paths (bundle checksum, migrations/audit sequencing) that could affect upgrades and governance invariants.

Overview
This PR packages FlightDeck as flightdeck-ai v1.0.1 (Python 3.14 only), adds uv-based developer workflow, and introduces GitHub Actions for CI (lint/test/schema-drift/quickstart smoke on Linux+Windows) plus a tag-driven PyPI publish pipeline with version/tag consistency checks and GitHub Release creation.

It also tightens release integrity and local-ledger health checks by committing generated schemas/v1 with a generator script + drift guard, enforcing LF checkout for the golden bundle fixture, and expanding the CLI/storage surface with doctor checks (migrations/promoted pointers/audit_seq) and release verify checksum comparisons backed by new golden fixtures and cross-platform smoke/tests.

Reviewed by Cursor Bugbot for commit 295db21. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Non-atomic audit_seq in insert_promotion_record risks integrity
    • insert_promotion_record now uses the same immediate transaction path as promotion commits, with a regression test covering lock acquisition.
  • ✅ Fixed: Duplicate utc_now defined in two modules
    • Removed the storage-local duplicate and imported the canonical utc_now from models in both storage and CLI usage.

Create PR

Or push these changes by commenting:

@cursor push 55972ca243
Preview (55972ca243)
diff --git a/src/flightdeck/cli/main.py b/src/flightdeck/cli/main.py
--- a/src/flightdeck/cli/main.py
+++ b/src/flightdeck/cli/main.py
@@ -16,8 +16,17 @@
 from flightdeck.config import DEFAULT_CONFIG_FILENAME, load_config, write_default_config
 from flightdeck.doctor import run_doctor
 from flightdeck.ledger import diff_releases, parse_window
-from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseArtifact, ReleaseRecord, RunEvent
-from flightdeck.storage import Storage, utc_now
+from flightdeck.models import (
+    Policy,
+    PolicyResult,
+    PricingTable,
+    PromotionRecord,
+    ReleaseArtifact,
+    ReleaseRecord,
+    RunEvent,
+    utc_now,
+)
+from flightdeck.storage import Storage
 
 
 def read_release_artifact(path: Path) -> ReleaseArtifact:

diff --git a/src/flightdeck/storage.py b/src/flightdeck/storage.py
--- a/src/flightdeck/storage.py
+++ b/src/flightdeck/storage.py
@@ -5,18 +5,14 @@
 import sqlite3
 from contextlib import contextmanager
 from dataclasses import dataclass
-from datetime import datetime, timezone
+from datetime import datetime
 from pathlib import Path
 from typing import Any, Iterable
 from uuid import uuid4
 
-from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseRecord, RunEvent
+from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseRecord, RunEvent, utc_now
 
 
-def utc_now() -> datetime:
-    return datetime.now(timezone.utc)
-
-
 def ensure_parent_dir(db_path: str) -> None:
     Path(db_path).expanduser().resolve().parent.mkdir(parents=True, exist_ok=True)
 
@@ -506,7 +502,7 @@
         )
 
     def insert_promotion_record(self, record: PromotionRecord) -> None:
-        with self.connect() as conn:
+        with self.transaction() as conn:
             self._insert_release_action_conn(conn, record)
 
     def commit_promotion(self, record: PromotionRecord, *, new_promoted_release_id: str) -> None:

diff --git a/tests/test_doctor.py b/tests/test_doctor.py
--- a/tests/test_doctor.py
+++ b/tests/test_doctor.py
@@ -7,6 +7,8 @@
 from click.testing import CliRunner
 
 from flightdeck.cli.main import cli
+from flightdeck.models import PolicyResult, PromotionRecord
+from flightdeck.storage import Storage
 
 from tests.test_spine import write_events, write_policy, write_pricing, write_release
 
@@ -107,6 +109,45 @@
     assert "audit_seq" in res.output.lower()
 
 
+def test_insert_promotion_record_uses_immediate_transaction(tmp_path: Path) -> None:
+    storage = Storage(str(tmp_path / "flightdeck.db"))
+    storage.migrate()
+    with storage.connect() as conn:
+        conn.execute(
+            """
+            INSERT INTO releases
+              (release_id, agent_id, version, environment, checksum, artifact_json, created_at)
+            VALUES (?, ?, ?, ?, ?, ?, ?)
+            """,
+            ("rel_1", "agent_support", "1", "local", "sha256:abc", "{}", "2026-05-01T00:00:00+00:00"),
+        )
+
+    record = PromotionRecord(
+        action_id="act_1",
+        action="promote",
+        actor="tester",
+        release_id="rel_1",
+        agent_id="agent_support",
+        environment="local",
+        reason="test",
+        policy_result=PolicyResult(passed=True),
+        created_at=datetime.now(tz=timezone.utc),
+    )
+
+    competing_conn = storage.connect()
+    try:
+        competing_conn.execute("BEGIN IMMEDIATE;")
+        try:
+            storage.insert_promotion_record(record)
+        except sqlite3.OperationalError as exc:
+            assert "database is locked" in str(exc)
+        else:
+            raise AssertionError("insert_promotion_record did not request an immediate write lock")
+    finally:
+        competing_conn.rollback()
+        competing_conn.close()
+
+
 def test_doctor_fails_when_promoted_release_missing(tmp_path: Path, monkeypatch) -> None:
     monkeypatch.chdir(tmp_path)
     runner = CliRunner()

You can send follow-ups to the cloud agent here.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 295db21. Configure here.

Comment thread src/flightdeck/storage.py
Comment thread src/flightdeck/storage.py Outdated
Gsbreddy and others added 2 commits May 1, 2026 16:44
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
…cstrings (#4)

- CHANGELOG.md: add Fixed entry under Unreleased for PR #3 (zero policy
  sample thresholds were silently ignored due to falsy 'or' fallback;
  now uses 'is not None' checks)
- ledger.py: add docstrings to confidence_label, evaluate_policy, and
  diff_releases explaining threshold semantics (None vs 0 vs config default),
  confidence label tiers, constraint fields, and agent-id invariant
- models.py: add Policy class docstring explaining the None/0 distinction
  for min_* threshold fields versus constraint (max_*) fields
- schemas/v1/policy.schema.json: regenerated to pick up Policy docstring

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
@Gsbreddy Gsbreddy merged commit 8819ad8 into main May 1, 2026
2 checks passed
@Gsbreddy Gsbreddy deleted the chore/1.0.1-v1-rollout-readiness branch May 1, 2026 23:52
Gsbreddy added a commit that referenced this pull request May 31, 2026
Implements the #1 amazing-product-gap item — HMAC-signed outbound
webhooks for promote/rollback/policy-blocked events. Unblocks Slack,
Discord, Teams, PagerDuty, and Linear adoption without FlightDeck
owning any of those integrations.

New surface (all gated by the existing Bearer / loopback ledger-write
policy):

- HTTP routes (`src/flightdeck/server/routes/webhooks.py`):
  - POST   /v1/webhooks         create (returns the secret once)
  - GET    /v1/webhooks         list (secrets redacted to a preview)
  - DELETE /v1/webhooks/{id}    delete
- CLI (`flightdeck webhook …`):
  - `webhook add --url … --event … [--description …]`
  - `webhook list`  (rich table)
  - `webhook remove WEBHOOK_ID [--yes]`
  - `webhook test WEBHOOK_ID`  (synthetic test.ping payload)

Signing follows GitHub convention:
- Header: `X-FlightDeck-Signature: sha256=<hex_digest>`
- Header: `X-FlightDeck-Event: <event_name>`
- Header: `X-FlightDeck-Delivery: <uuid>`
- HMAC-SHA256 over the raw request body using the per-webhook secret.

Delivery (`src/flightdeck/webhooks.py`):
- Synchronous fan-out from the originating promote/rollback handler.
- Best-effort: 5 s per-request timeout, 3 attempts, exponential
  backoff (1 s / 2 s / 4 s), no redirects, TLS verified.
- Failures only logged; webhook errors NEVER break a promote/rollback.

Event payload (envelope):
{
  "event": "promote.succeeded|rollback.succeeded|promote.blocked",
  "delivery_id": uuid,
  "created_at": iso8601,
  "data": { release_id, agent_id, environment, window, actor, reason,
             baseline_release_id, action_id, [policy_reasons] }
}

Schema migration v5 adds the `webhooks` table on both SQLite and
PostgreSQL (id, url, events_json, secret, enabled, created_at,
description) plus an `idx_webhooks_enabled` index. Storage method
contract: insert_webhook / list_webhooks / get_webhook / delete_webhook.

Hook points in `operations.py`:
- On successful promote / rollback in `_evaluate_promotion_or_rollback`,
  after `commit_promotion`, fire `promote.succeeded` or
  `rollback.succeeded` via `_dispatch_webhook_safe` (try/except never
  re-raises).
- On policy-blocked direct promote (action='promote',
  not policy_result.passed), after `insert_promotion_record`, fire
  `promote.blocked` with policy_reasons in the payload. The approval
  workflow path is NOT instrumented to avoid duplicate fires.

No new runtime dependencies. `httpx` was already in `pyproject.toml`.

Verification:
- ruff: clean
- pytest --cov-fail-under=80: 167 passed, 4 skipped, 81.68% cov
  (23 new tests across signing, storage CRUD, route auth + secret
  redaction, and HTTP delivery with httpx.MockTransport — including
  retry-on-5xx, give-up-after-3, and event-name filtering)
- `flightdeck webhook --help` shows all 4 subcommands
- schema drift: clean
- static-bundle drift: clean (web/ not touched)

Docs:
- README: new Webhooks section with a one-paragraph quickstart.
- docs/http-api.md: route table for the three webhook endpoints +
  signature header format.
- docs/cli.md: `webhook` subcommand reference.
- CHANGELOG: Unreleased > Added bullet for v1.3.0.
Gsbreddy added a commit that referenced this pull request May 31, 2026
…ded-User -> audit actor

Closes the #1 rip-out risk from the product audit
(/Users/sai.gottam/flightdeck-launch/07a-product-gaps.md): mutations
previously logged actor='http' for every HTTP-originated promote /
rollback / promote-request / promote-confirm, because the body's
Pydantic default was the only signal available to the route handler.

New behaviour: the handlers consult headers first.
  1. X-FlightDeck-Actor   (explicit, for CI wrappers / scripts)
  2. X-Forwarded-User     (de-facto reverse-proxy / SSO convention --
                           oauth2-proxy, Pomerium, Authelia,
                           Cloudflare Access, nginx auth_request)
  3. body 'actor'         (last-resort fallback)
The first non-empty, non-whitespace value wins. This lets an upstream
auth layer authoritatively stamp the audit ledger without trusting the
caller-controlled JSON body, which is the foundation for the eventual
SSO / OIDC story without committing to a full identity model today.

- src/flightdeck/server/routes/actions.py: new resolve_actor() helper +
  _ACTOR_HEADERS constant; applied to all four mutating routes.
- tests/test_actor_passthrough.py: 7 tests covering precedence, body
  fallback, whitespace handling, and the documented constant order.
- docs/http-api.md: new 'Identity passthrough' subsection under
  Authentication with the precedence table.

Verification on this branch:
- ruff: clean
- pytest: 174 passed, 4 skipped (was 167; +7 new) -- coverage stays
  above the 80% floor.
Gsbreddy added a commit that referenced this pull request Jun 1, 2026
…cs improvements (#62)

* docs(deploy): add Fly.io fly.toml and deployment steps

Include optional persistent volume mount template, health check on /health,
and security notes for FLIGHTDECK_LOCAL_API_TOKEN and read-only UI builds.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* feat(deploy): Railway docs, railway.toml, and PORT-aware entrypoint

Railway injects PORT at runtime; entrypoint binds flightdeck serve to
PORT with 8765 fallback for local Compose. Dockerfile healthcheck follows
PORT. Add railway.toml (Dockerfile builder, /health check) and README
section with pricing caveat, volume, and token guidance.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Add flightdeck demo for one-command quickstart onboarding

Introduce `flightdeck demo` to run the examples/quickstart ledger flow in a
temp workspace without sed or fixture paths. Ship quickstart fixtures in the
wheel via Hatch force-include to `_bundled_quickstart` for PyPI installs.
Refactor quickstart_smoke to share demo_flow helpers; document FLIGHTDECK_QUICKSTART_ROOT.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* docs: align web-ui, cli, sdk-integrations, and ops-policy with shipped features

- web-ui.md: document refactored DiffPage component tree (DiffVerdictStack,
  DiffReleaseTwin, DiffPolicyPanel, DiffChangeImpact/DiffPricingExpand,
  DiffDecisionCard, diffPayload.tsx), URL deep-linking for all pages,
  OverviewPage focused-release hero (?release= param), ReleaseLifecycleStrip,
  CopyTextButton, urlSearch.ts helpers, updated routing table, new CSS classes
  for DiffPage and OverviewPage additions

- cli.md: add flightdeck pricing check subcommand (--max-age-days, --fail)
  with example output and CI usage pattern

- pricing-catalog.md: link flightdeck pricing check reference to cli.md

- operations-and-policy.md: add schema migration v4 (promotion_requests table);
  update storage schema table from 7 to 8 tables

- sdk-integrations.md: add Module reference section documenting make_run_end_event,
  temporal_labels, and per-integration public APIs (openai_chat, anthropic_messages,
  openai_agents, langchain_callback, crewai_bridge)

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* docs(repo): add Cursor Cloud specific instructions to AGENTS.md

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Polish web UI for release-quality UX and discoverability

Set per-route document titles, sync theme-color with light/dark, add
Open Graph meta and clearer sidebar landmark text. Refine controls
(touch-friendly buttons/inputs), layered card shadows, and striped
data tables. Extend Playwright coverage for titles and theme-color.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Port operator UI primitives from industry-standard polish branch

Add Button (loading spinner, aria-busy) and StatusChip components; render
API security as scannable Writes/Reads/UI token chips. Wire Diff, Runs,
and Actions flows to Button; improve promote reason field validation UX,
native-styled filters (fd-select), sticky overview tables, and fd-link on
promoted release focus. Rebuild shipped static bundle; align smoke test
with chip copy.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Add demo capture script and committed UI marketing artifacts

Include Playwright-generated MP4/WebM walkthrough and PNG stills under
artifacts/flightdeck-demo-share for easy clone/download; document
regeneration via web/scripts/capture-demo-artifacts.mjs.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Address PR review: a11y, validation UX, security strip, and e2e gaps

Actions: aria-busy tracks workspace load only; workspace error vs loading
copy for token hint; field-level invalid flags for reason and confirm
inputs; clear flags on API errors. Runs: mutual disable for load/export;
invalid styling when release ID missing. SecurityStatusBar: data-testid,
skip /health in read-only UI, unknown auth values with warn copy,
chained detail messages; theme-color meta updates all tags. Button:
preserve loading aria-busy over rest spread. useDocumentTitle: empty and
suffix rules. Demo capture script aligns with e2e CLI resolution and
closes browser in finally. Add security-strip e2e (bearer route mock);
scope smoke assertion. Rebuild static bundle.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Regenerate demo screenshots and walkthrough video

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Web UI: open Appearance from sidebar Settings popover

Replace the dedicated Settings route with a portal dialog next to the
footer control, keep theme persistence unchanged, redirect legacy #/settings
to home, refresh e2e and demo capture docs, and rebuild the shipped static bundle.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* docs: add GitHub Pages workflow and Ask AI affordances

- Add MkDocs Material config, docs landing page, and pinned docs/requirements.txt
- Deploy site/ to Pages on main via actions/upload-pages-artifact + deploy-pages
- Perplexity/ChatGPT banner, floating Ask AI (Perplexity), extra CSS
- Ignore local site/; document preview in DEVELOPMENT and Pages setup in CONTRIBUTING
- Link published docs from README and pyproject Documentation URL

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Regenerate FlightDeck UI demo screenshots and walkthrough video

Re-run capture-demo-artifacts against current serve UI; refresh README
listing for all numbered PNGs plus WebM/MP4 outputs.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* Web UI: icon-only Theme row in sidebar Settings popover

Use sun/moon/monitor controls next to a Theme label, rename the dialog to
Settings, tighten popover width, and refresh docs, e2e, demo captures, and the
shipped static bundle.

Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>

* chore(ci): expand Python matrix (3.11/3.12/3.13), add CodeQL + Dependabot

- ci.yml: collapse duplicate test/test-windows jobs into single 2D matrix
  (os × python-version); guard web-build, Playwright e2e, schema-drift to
  ubuntu+3.12 only to save CI minutes. Add explicit actions/setup-python@v5.
- codeql.yml: weekly + per-PR SAST for python and javascript-typescript.
- dependabot.yml: weekly version updates for pip, npm (web/), github-actions
  with minor+patch grouping and a 5-PR cap per ecosystem.

Addresses launch-readiness audit gaps (no SAST, no automated dep updates,
single Python version tested despite pyproject claiming 3.11-3.14 support).

* chore(repo): real maintainer identity; soften Ask AI to floating pill only

- pyproject.toml: replace generic 'FlightDeck' author with the real
  maintainer (Gottam Sai Bharath) and mirror to [maintainers]. Removes
  the 'looks like abandonware' signal on PyPI/HN.
- mkdocs.yml + docs/stylesheets/extra.css: remove the dismissible Ask AI
  banner (Material extra.announce). The unsolicited outbound to
  Perplexity/ChatGPT read as a dark pattern on a docs site.
- docs/index.md: rewrite Ask AI paragraph to describe the floating pill
  only. README/CHANGELOG already aligned.

* feat(community,supply-chain): industry-standard launch readiness

Community files:
- CODE_OF_CONDUCT.md: Contributor Covenant 2.1 adaptation with placeholder
  enforcement contact.
- GOVERNANCE.md: honest single-maintainer model with documented path to a
  steering committee once there are >=3 active maintainers, no single
  contributor >70% of commits over 6 months, published trademark policy,
  documented contribution license model.
- CITATION.cff: academic-citation metadata (CFF 1.2.0).
- README.md: badge row (PyPI version, Python versions, CI, license,
  GitHub stars, ruff).
- .github/ISSUE_TEMPLATE/config.yml: disable blank issues, link to
  Security Advisories, Discussions, and Documentation.
- .github/FUNDING.yml: GitHub Sponsors link for the maintainer.

Supply chain & security workflows:
- .github/workflows/codeql.yml [previous commit] — already landed.
- .github/workflows/scorecard.yml: OpenSSF Scorecard (weekly +
  branch_protection_rule + manual) -> SARIF -> GitHub Security tab.
- .github/workflows/sbom.yml: CycloneDX SBOM on every semver tag,
  attached as a Release asset.
- .github/workflows/trivy.yml: filesystem + container scan (weekly +
  per-PR), SARIF to Security tab; container scan builds the
  examples/deploy/ image.
- .github/workflows/docker-publish.yml: multi-arch (linux/amd64 +
  linux/arm64) container image to ghcr.io/flightdeckdev/flightdeck on
  semver tags, with provenance and SBOM attestations and a 'latest' tag
  on every release. (Fix: removed enable={{is_default_branch}} from the
  latest tag rule -- the workflow only runs on tag push, where
  is_default_branch is always false, so latest would have never been
  pushed.)
- .github/workflows/stale.yml: politely close stale issues/PRs with
  generous timeouts (60/14 issues, 45/14 PRs) and exempt labels
  (pinned, security, roadmap, keep-open).

These close every gap flagged in the launch-readiness audit (CodeQL,
Dependabot, OpenSSF Scorecard, SBOM, container scan, container image
distribution, COC, GOVERNANCE, badges, ISSUE_TEMPLATE polish, CFF).

* fix(ci): always tag GHCR image as 'latest' on semver tag push

docker-publish.yml only triggers on tag push, where is_default_branch is
always false; the enable guard meant ':latest' would never be pushed.
Drop the guard so every semver release moves ':latest'.

* feat(web): apply UI/UX audit P0+P1 fixes

P0 (must-fix-before-launch):
- ActionsPage: replace window.confirm() with typed-confirm guard
  (must type last 8 chars of release ID before destructive promote/
  rollback proceeds). Same path used for both actions.
- ActionsPage: user-settable Actor identity persisted in localStorage
  (was hardcoded 'react-ui' in every audit row).
- DiffPage / ActionsPage: drop hardcoded environment='local' default.
  Empty inputs disable the Compute / Promote action, with helpful
  placeholders ('e.g. staging').
- DiffPage: client-validate baseline / candidate inputs (Compute diff
  disabled while either is empty).
- OverviewPage: first-time-user empty-state CTA with the
  'flightdeck release register …' command + Quickstart docs link.

P1 (strong-before-launch):
- DiffPage: swap-baseline-and-candidate button.
- OverviewPage: 'Showing N of M' + Clear-filters chip on the filter row.
- RunsPage: Next/Previous pagination with returned/total summary.
- RunsPage: collapse 5 optional filters (Tenant, Task, Trace ID,
  Session ID, Span ID) into an Advanced disclosure; Release ID, Window,
  and Environment stay visible by default.
- RunsPage: type='number' + inputMode='numeric' on Offset/Limit.
- index.css: drop italic on empty cells (a11y antipattern); fix
  release-ID mid-character wrap on Diff twin; widen drawer panel to
  40rem (was 28rem) for JSON payloads.
- AppShell: aria-controls IDREFS compliance (single ID).
- ReleaseLifecycleStrip: pre-existing latent TS error fixed —
  Link → NavLink (Link does not accept the 'end' prop).

Deferred for a focused follow-up PR (broke e2e on the first pass and
deserve their own scrutiny):
- P1-8 React.lazy / Suspense route splitting (HashRouter + Suspense
  transient mount issue surfaced a strict-mode getByLabel match in the
  smoke deep-link spec).
- P1-11 'Promote (writes ledger)' button copy (smoke spec expects
  exact 'Promote' button name).

Verification on this branch:
- npx tsc --noEmit ✅
- npm run build ✅
- npm run test:e2e ✅ (21 passed, 2 skipped)
- pytest --cov-fail-under=80 ✅ (144 passed, 4 skipped, 83.01% cov)
- ruff ✅
- schema drift ✅

* fix(pr62-review): address all reviewer BLOCKERs + MAJORs + 2 high-leverage MINORs

Reviewer findings file: /Users/sai.gottam/flightdeck-launch/08-pr62-review.md
Verdict before: READY AFTER FIXING BLOCKERS (2 BLOCKER, 6 MAJOR, 9 MINOR, 4 NIT)

BLOCKERs
- trivy.yml: pin aquasecurity/trivy-action@master -> @0.28.0 in both jobs.
  Mutable @master was the lone outlier in the supply-chain workflow stack
  and would have failed OpenSSF Scorecard's Pinned-Dependencies check
  (Scorecard was added in the same PR).
- CODE_OF_CONDUCT.md: drop the conduct@flightdeck.dev placeholder and the
  trailing 'maintainers: update this' italic. Enforcement now routes to a
  GitHub private security advisory (Conduct category) and the maintainer's
  @Gsbreddy GitHub handle -- both work today, no DNS setup required.

MAJORs
- ActionsPage.runAction: explicit Release-ID check + non-empty typed-confirm
  match. Empty rid produced expected='', which an Enter-on-empty prompt
  satisfied -- the typed-confirm guard looked bypassable on the UI even
  though the server rejected. Now: '!rid -> Release ID is required', and
  '!typed' fails the guard.
- ActionsPage: actor defaults to '' (was 'react-ui'); the persistence
  effect only writes non-empty values. The audit-log 'actor=react-ui'
  problem P0-4 was meant to fix is now fully fixed -- nothing silently
  pre-populates the audit row.
- docker-publish.yml: gate ':latest' on github.event_name == 'push' || the
  new workflow_dispatch.inputs.push_latest input (default true). Tag push
  preserves current behavior; backports / hotfixes can opt out so
  ':latest' never moves backwards.
- Dockerfile: pin python:3.14-slim -> python:3.13-slim. CI matrix tests
  3.11/3.12/3.13 -- we now actually test the interpreter we ship.
- sbom.yml: pin cyclonedx-bom>=4.0 -> ==6.1.4. SBOM tooling itself must be
  reproducible; the whole point of this workflow is supply-chain provenance.
- docs/requirements.txt: add transitive pymdown-extensions==10.16 pin so
  mkdocs build --strict doesn't break on a silent transitive bump on the
  first GitHub Pages deploy.

MINORs (cheap wins worth bundling)
- dependabot.yml: register pip ecosystem for docs/ so the GitHub Pages
  build gets auto-bumped (mkdocs-material / pymdown-extensions have had
  CVEs before).
- fly.toml: rename app='flightdeck-demo' -> 'REPLACE-ME-your-fly-app-name'
  with an explanatory comment. The previous placeholder is squatted on
  Fly.io; users following the docs hit 'name already taken' on fly deploy.

Remaining MINOR/NIT findings (intentionally deferred to follow-up PRs):
- MINOR-1/2: SHA-pin ossf/scorecard-action and softprops/action-gh-release
- MINOR-3/4: docs-site Ask-AI footer note + pages.yml concurrency comment
- MINOR-6: document 'web is the only npm root' assumption in dependabot.yml
- MINOR-7: CITATION.cff name convention -- needs maintainer confirmation
- MINOR-9: post-merge: tag v1.3.0-rc1 to verify the release pipeline still
  ships a wheel with _bundled_quickstart/ included
- NIT-1: PR body file/line counts (cosmetic)
- NIT-2: changelog mention of bundled demo artifacts (cosmetic)

Verification on this branch:
- ruff: clean
- pytest --cov-fail-under=80: 144 passed, 4 skipped, 83.01% cov
- web tsc: clean
- npm run build: clean (74 modules, 322 KB js / 37 KB css)
- Playwright e2e: 21 passed, 2 skipped
- YAML lint (trivy/sbom/docker-publish/dependabot): all parse cleanly

* feat(webhooks): HMAC-signed outbound webhooks (v1.3.0)

Implements the #1 amazing-product-gap item — HMAC-signed outbound
webhooks for promote/rollback/policy-blocked events. Unblocks Slack,
Discord, Teams, PagerDuty, and Linear adoption without FlightDeck
owning any of those integrations.

New surface (all gated by the existing Bearer / loopback ledger-write
policy):

- HTTP routes (`src/flightdeck/server/routes/webhooks.py`):
  - POST   /v1/webhooks         create (returns the secret once)
  - GET    /v1/webhooks         list (secrets redacted to a preview)
  - DELETE /v1/webhooks/{id}    delete
- CLI (`flightdeck webhook …`):
  - `webhook add --url … --event … [--description …]`
  - `webhook list`  (rich table)
  - `webhook remove WEBHOOK_ID [--yes]`
  - `webhook test WEBHOOK_ID`  (synthetic test.ping payload)

Signing follows GitHub convention:
- Header: `X-FlightDeck-Signature: sha256=<hex_digest>`
- Header: `X-FlightDeck-Event: <event_name>`
- Header: `X-FlightDeck-Delivery: <uuid>`
- HMAC-SHA256 over the raw request body using the per-webhook secret.

Delivery (`src/flightdeck/webhooks.py`):
- Synchronous fan-out from the originating promote/rollback handler.
- Best-effort: 5 s per-request timeout, 3 attempts, exponential
  backoff (1 s / 2 s / 4 s), no redirects, TLS verified.
- Failures only logged; webhook errors NEVER break a promote/rollback.

Event payload (envelope):
{
  "event": "promote.succeeded|rollback.succeeded|promote.blocked",
  "delivery_id": uuid,
  "created_at": iso8601,
  "data": { release_id, agent_id, environment, window, actor, reason,
             baseline_release_id, action_id, [policy_reasons] }
}

Schema migration v5 adds the `webhooks` table on both SQLite and
PostgreSQL (id, url, events_json, secret, enabled, created_at,
description) plus an `idx_webhooks_enabled` index. Storage method
contract: insert_webhook / list_webhooks / get_webhook / delete_webhook.

Hook points in `operations.py`:
- On successful promote / rollback in `_evaluate_promotion_or_rollback`,
  after `commit_promotion`, fire `promote.succeeded` or
  `rollback.succeeded` via `_dispatch_webhook_safe` (try/except never
  re-raises).
- On policy-blocked direct promote (action='promote',
  not policy_result.passed), after `insert_promotion_record`, fire
  `promote.blocked` with policy_reasons in the payload. The approval
  workflow path is NOT instrumented to avoid duplicate fires.

No new runtime dependencies. `httpx` was already in `pyproject.toml`.

Verification:
- ruff: clean
- pytest --cov-fail-under=80: 167 passed, 4 skipped, 81.68% cov
  (23 new tests across signing, storage CRUD, route auth + secret
  redaction, and HTTP delivery with httpx.MockTransport — including
  retry-on-5xx, give-up-after-3, and event-name filtering)
- `flightdeck webhook --help` shows all 4 subcommands
- schema drift: clean
- static-bundle drift: clean (web/ not touched)

Docs:
- README: new Webhooks section with a one-paragraph quickstart.
- docs/http-api.md: route table for the three webhook endpoints +
  signature header format.
- docs/cli.md: `webhook` subcommand reference.
- CHANGELOG: Unreleased > Added bullet for v1.3.0.

* feat(server): identity passthrough from X-FlightDeck-Actor / X-Forwarded-User -> audit actor

Closes the #1 rip-out risk from the product audit
(/Users/sai.gottam/flightdeck-launch/07a-product-gaps.md): mutations
previously logged actor='http' for every HTTP-originated promote /
rollback / promote-request / promote-confirm, because the body's
Pydantic default was the only signal available to the route handler.

New behaviour: the handlers consult headers first.
  1. X-FlightDeck-Actor   (explicit, for CI wrappers / scripts)
  2. X-Forwarded-User     (de-facto reverse-proxy / SSO convention --
                           oauth2-proxy, Pomerium, Authelia,
                           Cloudflare Access, nginx auth_request)
  3. body 'actor'         (last-resort fallback)
The first non-empty, non-whitespace value wins. This lets an upstream
auth layer authoritatively stamp the audit ledger without trusting the
caller-controlled JSON body, which is the foundation for the eventual
SSO / OIDC story without committing to a full identity model today.

- src/flightdeck/server/routes/actions.py: new resolve_actor() helper +
  _ACTOR_HEADERS constant; applied to all four mutating routes.
- tests/test_actor_passthrough.py: 7 tests covering precedence, body
  fallback, whitespace handling, and the documented constant order.
- docs/http-api.md: new 'Identity passthrough' subsection under
  Authentication with the precedence table.

Verification on this branch:
- ruff: clean
- pytest: 174 passed, 4 skipped (was 167; +7 new) -- coverage stays
  above the 80% floor.

* feat: request-id middleware, version --json, Slack/Discord webhook recipes

Three small additive launch-readiness wins; no overlap with the
reviewer's current PR-62 scope.

1. **Request-context middleware (src/flightdeck/server/middleware.py).**
   Every response now carries two headers:
   - X-Request-Id: per-request UUID (echoes a caller-supplied value if
     present, generates a fresh uuid4().hex otherwise). Also stashed on
     request.state.request_id for downstream handlers / logs / future
     audit-row enrichment.
   - X-FlightDeck-Server-Version: package __version__. Lets clients
     detect server / client skew without hitting /health.
   Wired in src/flightdeck/server/app.py via app.add_middleware().
   Five tests in tests/test_server_middleware.py cover: generated id
   shape (32 hex chars), client-supplied id echoed, whitespace-only
   replaced, server version equals __version__, id unique per request.

2. **flightdeck version [--json] CLI command (src/flightdeck/cli/main.py).**
   Human form (default): 'flightdeck 1.2.0'. Machine form (--json):
   {"name": "flightdeck-ai", "version": "1.2.0"} — for CI scripts,
   chatops bots, and dashboards that want a single source of truth.
   click already exposes --version on the root group; this is the
   explicit subcommand for cases where --version isn't ergonomic.

3. **Slack / Discord / PagerDuty / Linear webhook recipes
   (docs/sdk-integrations.md).**
   New 'Outbound webhooks' section turns the generic JSON envelope
   into a concrete launch story: Cloudflare Worker example for Slack,
   one-line variations for Discord and PagerDuty, signature-verification
   snippet in Python (with the hmac.compare_digest reminder so users
   don't write timing-vulnerable comparisons). This is the 'webhooks
   are real' demo a first-time user can copy-paste in 5 minutes.

Verification:
- ruff: clean
- pytest tests/test_server_middleware.py tests/test_actor_passthrough.py
  tests/test_webhooks_*.py: 35 passed
- 'uv run flightdeck version' + 'flightdeck version --json' smoke OK

* fix(reviewer-pass-2): SSRF validation on webhook URL, identity-passthrough trust docs, workspace info CLI

Addresses both MAJORs from the second reviewer pass
(/Users/sai.gottam/flightdeck-launch/08-pr62-review.md "Final pass"):

MAJOR-7 — Webhook URL SSRF defence
  models.py: WebhookCreate.url now runs a validator that rejects
  - non-http(s) schemes (no file://, gopher://, ftp://, javascript:,
    data:);
  - link-local IPv4 and IPv6 literals (covers AWS IMDS 169.254.169.254,
    ECS 169.254.170.2, and IPv6 fe80::/10);
  - the canonical cloud-metadata hostnames (metadata.google.internal,
    metadata, instance-data, instance-data.ec2.internal).
  Loopback and RFC1918 private addresses are intentionally allowed —
  FlightDeck is local-first and self-hosted Slack/Discord receivers
  commonly live on private nets. 21 new tests in
  tests/test_webhooks_url_validation.py cover both accept and reject
  paths.

MAJOR-8 — Identity-passthrough trust posture
  SECURITY.md: new "Identity passthrough headers — when to trust them"
  subsection explicitly warns that X-Forwarded-User and
  X-FlightDeck-Actor are trivially forgeable without (1) all inbound
  through a trusted reverse proxy, (2) that proxy stripping any
  incoming copies of the header before injecting its own value, and
  (3) Bearer-gating mutating routes. Includes nginx, Caddy, and
  oauth2-proxy configuration shape.
  Also added a new "Outbound webhooks — SSRF defence" subsection
  documenting the validator above.

Bonus while the reviewer was running:
  CLI: new `flightdeck workspace info [--json]` — one-screen snapshot
  of workspace path + server version + db backend + schema version +
  ledger counters (releases / promoted / actions / run events) +
  configuration (default env, policy presence, pricing catalog,
  promotion-requires-approval) + webhooks count. JSON form is
  machine-parseable for CI dashboards / chatops. Restores the
  doctor-command "all passed" summary line that an earlier edit had
  inadvertently displaced.

Verification:
- ruff: clean
- pytest --cov-fail-under=80: 202 passed, 4 skipped, 82.15% cov
  (was 174; +28 new tests across SSRF and workspace info)
- Doctor smoke: 'all passed' restored
- 'flightdeck workspace info' + '--json' both smoke OK

* fix(ci): trivy-action tag 0.28.0 doesn't exist; use 0.35.0 (latest non-v stable)

The reviewer's suggested pin (0.28.0) referenced a version that aquasecurity/trivy-action never released. Marketplace tag actually
exists at 0.35.0 (and v-prefixed v0.36.0). 0.35.0 is the highest tag without the v-prefix variant in the release feed and matches what
the action's README documents.

* feat(ux+landing): live UI fixes, new screenshots, demo video, README overhaul

UX fixes (from live user simulation on running instance):
- DiffPage: auto-scroll to policy verdict on Compute diff (useRef +
  scrollIntoView after 50ms settle) — verdict was below the fold at
  1440x900.
- DiffPricingExpand: wrap pricing.warnings and pricing.hints in
  <details><summary> accordions ('Pricing warnings (N)' / 'Pricing
  hints (N)'), styled fd-muted. Were previously bold orange/blue alert
  boxes at the same visual weight as policy failures — startling to
  first-time users.
- ActionsPage: improve typed-confirm error — now says 'To confirm,
  type the last 8 characters of the release ID: "<expected>"' so
  users know exactly what to type.
- RunsPage: Release ID input now has placeholder 'type or paste a
  release ID' and a hint span 'Type to search known IDs, or paste from
  Overview' — the <datalist> had no affordance for discoverability.

Screenshots (9 screenshots captured with Playwright on the fixed build,
1440x900, against a seeded demo workspace):
- docs/screenshots/overview.png     — overview with promoted + release tables
- docs/screenshots/diff-result.png  — policy PASS verdict, auto-scrolled
- docs/screenshots/runs.png         — runs with datalist hint visible
- docs/screenshots/actions.png      — actions form pre-filled via deep-link
- docs/screenshots/dark-mode.png    — dark mode + settings popover
- docs/screenshots/flightdeck-demo.webm — 2.5MB full walkthrough recording

README overhaul:
- 'Try it now' callout (pip install flightdeck-ai + flightdeck demo)
  promoted to top, before product snapshot.
- 'Core loop' → 'How it works' (plain prose, not pipe-separated).
- Product snapshot caption updated to link demo + screenshots.
- Demo section: clickable overview thumbnail → demo video.
- Install section: 'User install' vs 'Contributor install' split.
- '~31%' stale claim removed; replaced with accurate description.
- Screenshots section: 2×2 gallery using new docs/screenshots/ assets.

Cleanup:
- Removed artifacts/flightdeck-demo-share/ (old screenshots + MP4/WebM
  replaced by docs/screenshots/ with better post-fix captures).

* fix(pages): enable GitHub Pages, improve docs homepage, add docs badge

GitHub Pages:
- Enabled via API (Settings → Pages → Source: GitHub Actions). The site
  will be live at https://flightdeckdev.github.io/flightdeck/ the
  moment this branch merges to main and the pages.yml workflow runs.
  Subsequent pushes to main auto-redeploy.
- Verified mkdocs build --strict passes locally (all 11 nav pages
  resolve, no broken internal links).

docs/index.md — complete rewrite for first-time visitors:
  - 'Try it' callout at the top: pip install flightdeck-ai &&
    flightdeck demo (was buried; developer uv install came first).
  - Plain-English 4-step 'How it works' before the reference table.
  - 'Who should use this?' section (mirrors README ICP — consistent
    messaging across landing and docs).
  - Install section split: user (pip) vs contributor (uv), with extras
    list (openai, anthropic, postgres, telemetry).
  - Ask AI pill moved to the bottom — not the first thing a first-time
    docs visitor should see.

README.md:
  - Added 'Docs' badge linking to https://flightdeckdev.github.io/flightdeck/
    in the badge row (between CI and License).

* fix(e2e): open pricing-warnings <details> before asserting visibility

The P3 UX fix wrapped pricing.hints and pricing.warnings in collapsed
<details> accordions (less alarming than bright alert boxes). The
diff-ui e2e spec expected the warning text to be immediately visible
after clicking the DiffPricingExpand panel, but the item is now inside
a closed <details> — hence 'received: hidden'. Add one click on the
Pricing-warnings summary before the assertion.

* fix(security): bump pymdown-extensions 10.16 -> 10.21.3

Fixes two Trivy CVEs reported on PR #62:
- CVE-2026-46338 (MEDIUM) — sibling-prefix path traversal bypass in
  pymdownx.snippets despite restrict_base_path. Fixed in 10.21.3.
- CVE-2025-68142 (LOW) — ReDoS in figure caption extension.
  Fixed in 10.16.1; 10.21.3 covers both.

Verified: mkdocs build --strict still passes with the patched version
(0.34s, no errors).

* chore(release): bump version 1.2.0 → 1.3.0; finalize CHANGELOG for v1.3.0

Version:
- pyproject.toml version = "1.3.0"
- src/flightdeck/__init__.py __version__ = "1.3.0"

The CHANGELOG already referenced v1.3.0 in the Webhooks entry; this
aligns pyproject + __init__ so the release-pypi workflow can tag
v1.3.0 immediately after merge without another commit.

CHANGELOG additions (all in Unreleased → moved to 1.3.0 on tag):
Added:
- Identity passthrough (X-FlightDeck-Actor / X-Forwarded-User)
- flightdeck workspace info [--json]
- flightdeck version [--json]
- Request-context middleware (X-Request-Id + X-FlightDeck-Server-Version)
- Webhook SSRF URL validation (21 tests)
- Web UI UX fixes (diff auto-scroll, typed-confirm, hints disclosure,
  datalist hint)
- Community/supply-chain files (COC, GOVERNANCE, CITATION, FUNDING,
  ISSUE_TEMPLATE, CodeQL, Scorecard, SBOM, Trivy, GHCR image, Dependabot)
- CI matrix 3.11/3.12/3.13 × Linux/Windows

Fixed:
- pymdown-extensions 10.16 → 10.21.3 (CVE-2026-46338, CVE-2025-68142)

Changed:
- README overhaul (user install first, demo video, screenshot gallery)

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants