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
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ src/flightdeck/server/static/** text eol=lf

# Post-build Node scripts committed under web/ (avoid CRLF churn on Windows checkouts).
web/scripts/** text eol=lf

# Shell entrypoints used by Docker (Linux images expect LF shebang lines).
examples/deploy/*.sh text eol=lf
examples/ci/*.sh text eol=lf
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ jobs:
- name: Quickstart smoke (cross-platform)
run: uv run flightdeck-quickstart-verify

- name: Example CI ledger gate
env:
FD_PROJECT: ${{ github.workspace }}
WORKSPACE: ${{ runner.temp }}/fd-ledger-gate-${{ github.run_id }}-${{ github.run_attempt }}
QUICKSTART_ROOT: ${{ github.workspace }}/examples/quickstart
run: uv run python examples/ci/ledger_gate.py

- name: CLI smoke
run: uv run flightdeck --help

Expand Down Expand Up @@ -121,5 +128,12 @@ jobs:
- name: Quickstart smoke (cross-platform)
run: uv run flightdeck-quickstart-verify

- name: Example CI ledger gate
env:
FD_PROJECT: ${{ github.workspace }}
WORKSPACE: ${{ runner.temp }}/fd-ledger-gate-${{ github.run_id }}-${{ github.run_attempt }}
QUICKSTART_ROOT: ${{ github.workspace }}/examples/quickstart
run: uv run python examples/ci/ledger_gate.py

- name: CLI smoke
run: uv run flightdeck --help
24 changes: 13 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,27 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0**

## Unreleased

## 1.0.2 - 2026-05-02

### Added

- **Quickstart:** **`flightdeck-quickstart-verify`**, **`flightdeck.quickstart_smoke`**, **`scripts/quickstart_smoke.py`**; **CI** and **PyPI release** run **`uv run flightdeck-quickstart-verify`** (release: after schema drift check).
- **HTTP SDK:** **`FlightdeckClient`** / **`AsyncFlightdeckClient`** — optional **`api_token`**, **`health`**, **`list_releases`** / **`list_promoted`** / **`list_actions`**, **`post_diff`** / **`post_promote`** / **`post_rollback`**, plus ingest batching and retries.
- **Web + E2E:** React/Vite **`web/`**, committed **`src/flightdeck/server/static/`**, FastAPI **`/assets`**; Vite dev proxy **`/v1`** / **`/health`**, optional **`VITE_FLIGHTDECK_LOCAL_API_TOKEN`**; **`.gitattributes`** LF on **`static/`**; **`web/e2e/`**, **`web/playwright.config.ts`**, **`web/scripts/e2e-server.mjs`**, **`@playwright/test`**; **CI** / **PyPI release**: **`npm ci`**, **`npm run build`**, **`git diff --exit-code static/`**, **`npx playwright install chromium`**, **`npm run test:e2e`**.
- **Tests:** CLI contracts (**`release verify`**, **`diff`**, **`history`**, **`rollback`**); invalid JSON fixtures (**`PricingTable`**, **`RunEvent`**, **`ReleaseArtifact`**).
- **Tooling:** **`uv.lock`** / **`uv sync --frozen --extra dev`** / **`astral-sh/setup-uv`**; **`.github/workflows/release-pypi.yml`** (tag ↔ **`pyproject.toml`** / **`__init__.py`**, ruff, pytest, schemas, **`uv build`**, OIDC **PyPI**, GitHub Release); **`tests/test_version_consistency.py`**.
- **CLI:** **`flightdeck release diff --fail-on-policy`** exits **1** when the active policy does not pass (after printing the diff), for CI gates without calling **`release promote`**.
- **HTTP `GET /health`:** response includes **`mutation_auth`** (`loopback` or `bearer`) — safe hint for whether **`FLIGHTDECK_LOCAL_API_TOKEN`** is configured (no secret material).
- **Web UI:** security status strip (server mode + whether **`VITE_FLIGHTDECK_LOCAL_API_TOKEN`** is set); optional read-only mode via **`VITE_FLIGHTDECK_UI_READ_ONLY=true`** (hides Promote nav and **`#/actions`**).
- **examples/ci:** **`ledger_gate.py`**, **`ledger-gate-policy.yaml`**, **`ledger-gate.sh`** (wrapper), **`github-actions/policy-gate-*.yml`**, **[examples/ci/README.md](examples/ci/README.md)**; **`.github/workflows/ci.yml`** runs **`uv run python examples/ci/ledger_gate.py`** on **Ubuntu** and **Windows** (no bash-only gate).
- **examples/deploy:** reference **`Dockerfile`**, **`docker-compose.yml`**, **`entrypoint.sh`**, **[examples/deploy/README.md](examples/deploy/README.md)**.
- **examples/integration:** **`emit_sample_events.py`**, **[examples/integration/README.md](examples/integration/README.md)**.
- **Tests:** **`tests/test_server_health.py`**; **`test_release_diff_fail_on_policy_exits_1`** in **`tests/test_cli_contract.py`**; Playwright smoke asserts **`/health`** includes **`mutation_auth`**.

### Fixed

- **`diff_releases` zero policy sample thresholds:** `Policy.min_candidate_runs`, `Policy.min_baseline_runs`, and `Policy.min_low_runs` set to **`0`** now correctly override workspace config defaults to `0` instead of being silently ignored. Previously, `or`-based fallback treated `0` as falsy and fell back to the config value (typically `500` / `50`). Fixed by using explicit `is not None` checks. A policy can now unconditionally accept any sample size by setting thresholds to `0` — for example, to allow diffs over empty event windows without a confidence downgrade.
- **examples/ci ledger gate:** **`ledger_gate.py`** invokes the CLI as **`python -m flightdeck.cli.main`** (avoids nested **`uv run`** / Windows **`flightdeck.exe`** locks); wipes **`WORKSPACE`** with **`shutil.rmtree`** before **`init`**; unique **`WORKSPACE`** per CI run (**`run_id`** + **`run_attempt`**). **`ledger-gate-policy.yaml`** caps cost high enough for quickstart candidate rollup (quickstart **`policy.yaml`** at \$4 rejects **~\$5/run** under **`--fail-on-policy`**).

### Changed

- **Docs:** README deduplicates **RELEASE_NOTES** links; **CHANGELOG** historic bullets use canonical **`https://github.com/flightdeckdev/flightdeck/...`** URLs for **`docs/*`** and **RESEARCH.md** (this slim tree does not ship those files).
- **`tests/conftest.py`:** create repo **`.tmp/`** at import time so **`pytest --basetemp=.tmp/pytest`** works on fresh checkouts and **Linux** CI (parent dir is no longer Windows-only).
- **`pyproject.toml` `[project] name`:** **`flightdeck-ai`** to match the **PyPI** trusted-publisher project; install with **`pip install flightdeck-ai`** / **`uv add flightdeck-ai`** (CLI remains **`flightdeck`**, imports **`flightdeck.*`**).
- **Contributor docs** (**`README.md`**, **`DEVELOPMENT.md`**, **`CONTRIBUTING.md`**, **`AGENTS.md`**, **`CLAUDE.md`**, **`.cursorrules`**, **`.github/PULL_REQUEST_TEMPLATE.md`**, **`web/README.md`**): prefer **uv**; document full CI parity (**`uv sync --frozen`**, **`flightdeck --help`**, **`schemas/`** + **`src/flightdeck/server/static/`** **`git diff --exit-code`** gates, Playwright when **`web/`** changes). Added **`.cursor/rules/flightdeck-ci-artifacts.mdc`** (Cursor **`alwaysApply`**). Keep **pip** / **`python -m venv`** as fallback.
- **Python:** **`requires-python >=3.14,<3.15`**, **`.python-version`**, PyPI classifiers, **Ruff** `target-version`, **`uv.lock`**, and **CI** matrices now target **CPython 3.14** only (replacing broader **3.11–3.14** testing).
- **Docs:** **`docs/cli.md`** documents **`--fail-on-policy`**; **`docs/http-api.md`** / **`docs/sdk.md`** document **`GET /health`** `mutation_auth`.
- **`.gitattributes`:** LF for **`examples/deploy/*.sh`** (Docker entrypoint) and **`examples/ci/*.sh`** (CI shell wrappers).

## 1.0.1 - 2026-05-01

Expand All @@ -37,7 +39,7 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0**
- **Slim distribution:** this repository omits the full in-tree **`docs/`** tree, org mirror scripts, and **`verify-repo-standards`** wrappers. Narrative docs and maintainer runbooks live on **[github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)**; in-repo links now point there where applicable.
- **`pyproject.toml`:** OpenTelemetry packages are **optional** only (**`telemetry`** / **`all`** extras); the default install matches the **1.0.0** dependency story (core does not import OpenTelemetry).
- **`.pre-commit-config.yaml`:** **ruff** replaces **black** / **isort**; **`ruff-pre-commit`** pinned to **v0.15.12** to match **`dev`** (**`ruff==0.15.12`**).
- **CI:** Python **3.13** and **3.14** added to the Ubuntu and Windows matrices (superseded by **3.14**-only policy; see **Unreleased**).
- **CI:** Python **3.13** and **3.14** added to the Ubuntu and Windows matrices (superseded by **3.14**-only policy as of **1.0.2**).
- **`pyproject.toml`:** default **`pytest --basetemp=.tmp/pytest`** so local runs avoid Windows **`PermissionError`** on **`%TEMP%\pytest-of-*`**.
- **`pre-commit-hooks`:** bumped to **v5.0.0**.

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ flightdeck release history --agent agent_support --env local
The static event files in `examples/quickstart` use placeholder release IDs so the repo can ship stable examples.
Substitute them before ingestion, or run **`uv run flightdeck-quickstart-verify`** / **`python -m flightdeck.quickstart_smoke`** (venv) or **`./scripts/smoke.sh`** from Git Bash/WSL on Windows.

**Examples:** [examples/quickstart/](examples/quickstart/) · [examples/ci/](examples/ci/) (policy gate + Actions) · [examples/deploy/](examples/deploy/) (`serve` via Docker/Compose) · [examples/integration/](examples/integration/) (HTTP event emitter).

## Documentation

- [CLI reference](docs/cli.md) — all commands, flags, arguments, and exit codes
Expand Down
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ High-level notes for **shipping FlightDeck**. Detailed history: **[CHANGELOG.md]

Narrative docs (including the CLI reference) are maintained on **[github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)** `main`; this file and **`schemas/`** ship in minimal clones.

## v1.0.2 — CI examples, serve packaging, and policy gate CLI

Minor release (see **[CHANGELOG.md](CHANGELOG.md)**): **`flightdeck release diff --fail-on-policy`** for CI gates; **`examples/ci/`** (`ledger-gate.sh`, GitHub Actions templates) exercised in root CI; **`examples/deploy/`** (Docker/Compose for **`flightdeck serve`**); **`examples/integration/`** (SDK sample emitter for **`POST /v1/events`**); **`GET /health`** adds non-secret **`mutation_auth`** (`loopback` vs `bearer`); web shell shows mutation/token ergonomics and optional read-only UI (**`VITE_FLIGHTDECK_UI_READ_ONLY`**). Fix: policy **`min_*_runs`** explicit **`0`** overrides workspace defaults ( **`is not None`** resolution in **`diff_releases`** ). **Stable contracts:** additive **`/health`** field only; CLI flag is backward-compatible.

## v1.0.1 — distribution and developer tooling

Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): canonical **`main`** URLs for narrative docs in slim clones, optional OpenTelemetry in extras only, CI on **CPython 3.14** (Ubuntu and Windows), repo-local **pytest** basetemp on Windows, **ruff** pinned consistently with **pre-commit**, **`.gitattributes`** LF for the golden bundle fixture, and removal of a test that depended on unpublished export scripts. **Public CLI / schema / HTTP contracts** are unchanged from **v1.0.0**.
Expand Down
1 change: 0 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,3 @@ Hosted control plane and in-path traffic routing remain opt-in long-term conside
- **Versioning:** `VERSIONING.md`
- **Contributors/org workflow:** `CONTRIBUTING.md`
- **Engineering rules and doctrine:** `AGENTS.md`
- **Canonical repo:** [github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)
3 changes: 3 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,12 @@ flightdeck release diff BASELINE_ID CANDIDATE_ID --window WINDOW [OPTIONS]
| `--env` | Filter events by environment (default: `WorkspaceConfig.default_environment`) |
| `--tenant` | Filter events by `tenant_id` |
| `--task` | Filter events by `task_id` |
| `--fail-on-policy` | After printing the diff, exit **1** when the active policy does not pass (for CI gates). |

Both releases must have the same `agent_id`. Cross-agent diffs are rejected with exit 1.

**Exit codes:** invalid input, missing pricing, or other `OperationError` → non-zero. With **`--fail-on-policy`**, a computed diff whose policy result is **FAIL** also exits **1** (after the usual stdout).

The diff is a **read-only computation** — it does not write to the audit ledger or update
any promoted pointers.

Expand Down
10 changes: 9 additions & 1 deletion docs/http-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,18 @@ All paths below are relative to the server base URL, e.g. `http://127.0.0.1:8765
Health probe. Always returns HTTP 200 while the server is up.

**Response**

```json
{"status": "ok"}
{"status": "ok", "mutation_auth": "loopback"}
```

`mutation_auth` is always present on current servers:

- `"loopback"` — `FLIGHTDECK_LOCAL_API_TOKEN` is not set; promote and rollback are allowed only from loopback clients (no Bearer gate).
- `"bearer"` — `FLIGHTDECK_LOCAL_API_TOKEN` is set; promote and rollback require `Authorization: Bearer <that value>`.

This field never includes secret material.

---

## `GET /v1/releases`
Expand Down
2 changes: 1 addition & 1 deletion docs/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ See [SECURITY.md](../SECURITY.md) for the full access model.

### `health() -> dict`

`GET /health` — returns `{"status": "ok"}` when the server is up.
`GET /health` — returns `{"status": "ok", "mutation_auth": "loopback"|"bearer"}` when the server is up (`mutation_auth` describes promote/rollback auth; see **HTTP API**).

### `list_releases() -> dict`

Expand Down
65 changes: 65 additions & 0 deletions examples/ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# CI / GitOps examples

These files show a **register → ingest → diff (policy gate) → verify** loop you can drop into a pipeline. They mirror `examples/quickstart` and the `flightdeck-quickstart-verify` script, but stop **before** promotion so a pull request can fail when the active policy would reject the candidate.

## Policy gate exit code

`flightdeck release diff` accepts **`--fail-on-policy`**: after printing the diff, the command exits **1** when the active policy does not pass (same semantics as a failed `release promote`, without writing the ledger).

Use this flag in CI so a red build means “unsafe to ship under current policy,” not only “CLI error.”

## `ledger_gate.py` (recommended)

Canonical cross-platform gate (used by **`.github/workflows/ci.yml`**). Runs the CLI as
`python -m flightdeck.cli.main` from the **same interpreter** that executes the script (the
`uv` devenv on CI, or `python` after `pip install flightdeck-ai`).

Environment:

| Variable | Required | Meaning |
|----------|----------|---------|
| `WORKSPACE` | yes | **Dedicated throwaway directory** for `flightdeck.yaml` + SQLite (**deleted and recreated** each run) |
| `QUICKSTART_ROOT` | yes | Path to `examples/quickstart` (or your own copy of those fixtures) |
| `FD_PROJECT` | — | **Ignored** by `ledger_gate.py` (kept on env in workflows for documentation only). |

Policy for the diff step is **`ledger-gate-policy.yaml`** next to this README (not `quickstart/policy.yaml`): quickstart candidate cost is **~\$5/run** while quickstart policy caps **\$4**, so `--fail-on-policy` would fail there by design.

`ledger-gate.sh` is a thin **`exec …/ledger_gate.py`** wrapper for local bash users.

Example (monorepo with **uv**):

```bash
export WORKSPACE="$(mktemp -d)"
export QUICKSTART_ROOT="$PWD/examples/quickstart"
uv run python examples/ci/ledger_gate.py
```

Example (**PyPI** install):

```bash
pip install "flightdeck-ai>=1.0.2"
export WORKSPACE="$(mktemp -d)"
export QUICKSTART_ROOT=/path/to/flightdeck/examples/quickstart
python /path/to/flightdeck/examples/ci/ledger_gate.py
```

## GitHub Actions

Copy a workflow from `github-actions/` into `.github/workflows/` in your repository and adjust paths, Python version, and FlightDeck version pins.

| File | Use when |
|------|----------|
| [`policy-gate-monorepo.yml`](github-actions/policy-gate-monorepo.yml) | This repository (or a fork): `uv sync` + `uv run python examples/ci/ledger_gate.py`. |
| [`policy-gate-pypi.yml`](github-actions/policy-gate-pypi.yml) | Another repo: install **`flightdeck-ai`** from PyPI and sparse-checkout upstream `examples/quickstart` for fixtures (pin the checkout ref to match your installed version when possible). |

### Promoting from CI

`flightdeck release promote` is intentionally **not** in the gate script: many teams run diff/verify on every PR and only promote from a protected branch or manual workflow with secrets and review. If you automate promote, reuse the same workspace (or a trusted replica), set policy explicitly, and pass a non-empty `--reason` (for example the Git run URL).

## Related

- [Quickstart fixtures](../quickstart/README.md)
- [Deploy `flightdeck serve`](../deploy/README.md)
- [Runtime event emitter](../integration/README.md)
- [CLI reference](../../docs/cli.md)
- [Operations and policy](../../docs/operations-and-policy.md)
35 changes: 35 additions & 0 deletions examples/ci/github-actions/policy-gate-monorepo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Reference workflow: policy-gated diff in the FlightDeck repository (or a fork).
# Copy to .github/workflows/ and tune triggers as needed.
name: FlightDeck policy gate (monorepo)

on:
pull_request:
paths:
- "examples/quickstart/**"
- "examples/ci/**"
- "src/flightdeck/**"
- "pyproject.toml"
- "uv.lock"
workflow_dispatch:

jobs:
ledger-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
python-version: "3.14"

- name: Sync Python env
run: uv sync --frozen --extra dev

- name: Run ledger gate (diff + verify, no promote)
env:
FD_PROJECT: ${{ github.workspace }}
WORKSPACE: ${{ runner.temp }}/fd-ledger-gate-${{ github.run_id }}-${{ github.run_attempt }}
QUICKSTART_ROOT: ${{ github.workspace }}/examples/quickstart
run: uv run python examples/ci/ledger_gate.py
41 changes: 41 additions & 0 deletions examples/ci/github-actions/policy-gate-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Reference workflow: install FlightDeck from PyPI and run the same gate as the monorepo
# example, using sparse-checkout for quickstart fixtures only.
#
# Copy into your application repository, pin FLIGHTDECK_REF / package version together,
# and replace ORG/REPO if you vendor fixtures elsewhere.
name: FlightDeck policy gate (PyPI)

on:
workflow_dispatch:

env:
# Pin to a tag or SHA that matches your installed flightdeck-ai version when possible.
FLIGHTDECK_REF: main
FLIGHTDECK_AI_SPEC: ">=1.0.2"

jobs:
ledger-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: flightdeckdev/flightdeck
ref: ${{ env.FLIGHTDECK_REF }}
path: _flightdeck_fixtures
sparse-checkout: |
examples/quickstart
examples/ci
sparse-checkout-cone-mode: true

- uses: actions/setup-python@v5
with:
python-version: "3.14"

- name: Install FlightDeck CLI
run: pip install "flightdeck-ai${FLIGHTDECK_AI_SPEC}"

- name: Run ledger gate (diff + verify, no promote)
env:
WORKSPACE: ${{ runner.temp }}/fd-ledger-gate-${{ github.run_id }}-${{ github.run_attempt }}
QUICKSTART_ROOT: ${{ github.workspace }}/_flightdeck_fixtures/examples/quickstart
run: python "${GITHUB_WORKSPACE}/_flightdeck_fixtures/examples/ci/ledger_gate.py"
11 changes: 11 additions & 0 deletions examples/ci/ledger-gate-policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Used only by examples/ci/ledger_gate.py (and ledger-gate.sh).
# Quickstart candidate pricing yields ~$5/run; quickstart/policy.yaml caps at $4 and is
# meant for promote demos, so this file loosens the ceiling for the CI diff gate.
policy_id: ledger-gate-ci
max_cost_per_run_usd: 10.0
max_latency_ms: 5000
max_error_rate: 0.99
min_candidate_runs: 1
min_baseline_runs: 1
min_low_runs: 1
require_high_diff_confidence: false
10 changes: 10 additions & 0 deletions examples/ci/ledger-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Thin wrapper: the canonical gate is ledger_gate.py (cross-platform; no CRLF/shebang issues).
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
if command -v python3 >/dev/null 2>&1; then
PY=python3
else
PY=python
fi
exec "$PY" "$ROOT/ledger_gate.py"
Loading
Loading