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
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ jobs:
npx playwright install chromium
npm run test:e2e

- name: Playwright E2E (approval workspace)
env:
FD_E2E_FORCE_APPROVAL: "1"
run: |
cd web
npx playwright install chromium
npx playwright test e2e/actions-approval.spec.ts

- name: Lint
run: uv run python -m ruff check src tests

Expand Down Expand Up @@ -114,6 +122,15 @@ jobs:
npx playwright install chromium
npm run test:e2e

- name: Playwright E2E (approval workspace)
shell: bash
env:
FD_E2E_FORCE_APPROVAL: "1"
run: |
cd web
npx playwright install chromium
npx playwright test e2e/actions-approval.spec.ts

- name: Lint
run: uv run python -m ruff check src tests

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Publish sdist + wheel to PyPI when a SemVer tag is pushed (e.g. v1.1.0).
# Publish sdist + wheel to PyPI when a SemVer tag is pushed (e.g. v1.1.1).
# Configure "trusted publishing" on PyPI for this workflow + repository + optional GitHub environment.
# https://docs.pypi.org/trusted-publishers/

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0**

## Unreleased

## 1.1.1 - 2026-05-02

### Added

- **`GET /v1/workspace`:** read-only JSON for operators and the web UI — **`promotion_requires_approval`**, **`pricing_catalog_configured`**, **`server_version`** (normative schema + Python SDK helper).
- **Web Actions:** Promote flow uses workspace flags — direct **`POST /v1/promote`** when approval is off; **request → list pending → confirm** when **`promotion_requires_approval`** is on, with clearer errors.
- **Docs:** README / **release-artifact** / **examples** / **web-ui** / **http-api** / **sdk** updates for Phase 1 remainder; optional **`docs/pricing-catalog.md`**; **`examples/ci/promote_with_approval.sh`** and CI README **GitHub Actions** pattern for approval-gated promote.

### Changed

- **Examples / CI snippets:** **`flightdeck-ai>=1.1.1`** where version pins apply.

## 1.1.0 - 2026-05-03

### Added
Expand Down
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Merging to **`main` does not publish packages** — PyPI uploads are **tag-drive
1. **PyPI:** add a **trusted publisher** for **[github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)** — workflow **`release-pypi.yml`**. If PyPI offers **Environment name: (Any)**, you can still use a GitHub **Environment** named **`pypi`** for approval gates; otherwise match whatever you register on PyPI ([trusted publishers](https://docs.pypi.org/trusted-publishers/)).
2. **GitHub:** Settings → **Environments** → create **`pypi`** (optional: required reviewers / wait timer before OIDC publish).
3. Bump **`version`** in **`pyproject.toml`** and **`src/flightdeck/__init__.py`**, update **`CHANGELOG.md`**, merge to **`main`**.
4. **`git tag vX.Y.Z`** (must match **`pyproject.toml`** exactly, e.g. **`v1.1.0`**) then **`git push origin vX.Y.Z`**.
4. **`git tag vX.Y.Z`** (must match **`pyproject.toml`** exactly, e.g. **`v1.1.1`**) then **`git push origin vX.Y.Z`**.

The workflow runs **ruff**, **pytest**, schema drift, **`uv build`**, publishes **sdist + wheel** to **PyPI** via **OIDC** (no long-lived API token in repo secrets), enables **publish attestations**, and creates a **GitHub Release** with generated notes and **`dist/*`** assets.

Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ Current local spine:

FlightDeck is **local-first** and ships as a Python CLI backed by SQLite.

**v1.0.0** establishes **SemVer-stable public contracts** for the documented CLI
(**[README.md](https://github.com/flightdeckdev/flightdeck/blob/main/README.md)** on `main`),
committed **`schemas/v1/`**, and **`POST /v1/events`** with **`api_version` `v1`**. See
**[RELEASE_NOTES.md](RELEASE_NOTES.md)** (same narrative on
**[`main`](https://github.com/flightdeckdev/flightdeck/blob/main/RELEASE_NOTES.md)**).
**v1.0.0** froze **SemVer-stable public contracts** for the documented CLI, committed **`schemas/v1/`**,
and **`POST /v1/events`** with **`api_version` `v1`**. **v1.1.x** adds Phase 1 slices (optional pricing catalog on diffs,
promotion request/confirm, read-only runs listing, **`GET /v1/workspace`** for UI and automation, Helm/fleet examples)
without breaking those v1.0 shapes. See **[RELEASE_NOTES.md](RELEASE_NOTES.md)** and **[CHANGELOG.md](CHANGELOG.md)**.
The product scope is still intentionally narrow (release governance, not a hosted agent platform).

Not implemented yet:
Expand All @@ -46,10 +45,12 @@ Shipped locally:
- `flightdeck serve` + JSON routes under `/v1/*` (read + diff/promote/rollback + event ingest); see **Local HTTP API** below
- minimal Python SDK (`flightdeck.sdk.client`)
- `flightdeck release rollback` (policy-gated, audited)
- optional **`promotion_requires_approval`** in `flightdeck.yaml` with **`POST /v1/promote/request`** and **`POST /v1/promote/confirm`**

### Local HTTP API

With **`flightdeck serve`** (default bind **127.0.0.1**), the app exposes **`GET /health`**, **`GET /v1/releases`**, **`GET /v1/promoted`**, **`GET /v1/actions`**, **`POST /v1/events`**, **`POST /v1/diff`**, **`POST /v1/promote`**, and **`POST /v1/rollback`**. **`POST /v1/promote`** and **`POST /v1/rollback`** accept requests only from loopback clients unless **`FLIGHTDECK_LOCAL_API_TOKEN`** is set, in which case callers must send **`Authorization: Bearer <token>`** (same behavior as the **`web/`** dev UI via **`VITE_FLIGHTDECK_LOCAL_API_TOKEN`**). See **[SECURITY.md](SECURITY.md)**.
With **`flightdeck serve`** (default bind **127.0.0.1**), the app exposes **`GET /health`**, **`GET /v1/workspace`**
(read-only workspace flags for scripts and the bundled UI), **`GET /v1/metrics`**, **`GET /v1/releases`**, **`GET /v1/promoted`**, **`GET /v1/actions`**, **`GET /v1/promotion-requests`**, **`GET /v1/runs`**, **`POST /v1/events`**, **`POST /v1/diff`**, **`POST /v1/promote`**, **`POST /v1/promote/request`**, **`POST /v1/promote/confirm`**, and **`POST /v1/rollback`**. **`POST /v1/promote`**, **`POST /v1/promote/request`**, **`POST /v1/promote/confirm`**, and **`POST /v1/rollback`** accept requests only from loopback clients unless **`FLIGHTDECK_LOCAL_API_TOKEN`** is set, in which case callers must send **`Authorization: Bearer <token>`** (same behavior as the **`web/`** dev UI via **`VITE_FLIGHTDECK_LOCAL_API_TOKEN`**). See **[docs/http-api.md](docs/http-api.md)** and **[SECURITY.md](SECURITY.md)**.

## Quickstart

Expand Down Expand Up @@ -116,6 +117,7 @@ Substitute them before ingestion, or run **`uv run flightdeck-quickstart-verify`
- [Python SDK](docs/sdk.md) — `FlightdeckClient` / `AsyncFlightdeckClient` usage guide
- [Operations and policy](docs/operations-and-policy.md) — diff, promote, rollback internals; policy model and confidence tiers
- [Release artifacts and pricing](docs/release-artifact.md) — `release.yaml` format, bundle layout, checksum algorithm, workspace config, pricing tables
- [Pricing catalog](docs/pricing-catalog.md) — optional `pricing_catalog_path`, catalog vs imported tables, troubleshooting
- [JSON Schemas](schemas/v1/)
- [Release notes (maintainer)](RELEASE_NOTES.md)
- [Roadmap](ROADMAP.md)
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.1.1 — Workspace discovery, web approval UX, docs + CI cookbook

Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`GET /v1/workspace`** exposes non-secret workspace flags (**`promotion_requires_approval`**, **`pricing_catalog_configured`**, **`server_version`**) for scripting and the **Actions** page; web **Promote** follows the two-step request/confirm path when approval is required; expanded operator docs and **`examples/ci/promote_with_approval.sh`** + README workflow snippet. **Stable contracts:** additive HTTP and JSON Schema only.

## v1.1.0 — Phase 1 first slice (catalog, approval, runs, Helm, fleet)

Minor release (see **[CHANGELOG.md](CHANGELOG.md)**): optional **`pricing_catalog_path`** + **`PricingCatalog`** YAML for cross-vendor comparable **`pricing.catalog`** lines on diffs; **`pricing.hints`** for multi-version and model-name diagnostics; **`promotion_requires_approval`** with **`POST /v1/promote/request`** / **`POST /v1/promote/confirm`** / **`GET /v1/promotion-requests`** and matching CLI; **`GET /v1/runs`** and **`flightdeck runs list`**; SQLite migration **v4** (`promotion_requests`); reference **Helm** chart and **fleet** docs under **`examples/`**. **Stable contracts:** additive HTTP and CLI surfaces; existing **`v1`** event and release payloads unchanged.
Expand Down
12 changes: 10 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ This roadmap is meant to be clear from **what is already shipped** to **near-ter

## Next release

Further **Phase 1** work after **v1.1.0** (deeper forensics / replay UX, richer approval UI if needed, OTLP-oriented telemetry per gaps table). Track **[CHANGELOG.md](CHANGELOG.md)**.
Further **Phase 1** work after **v1.1.1** (deeper forensics / replay UX, OTLP-oriented telemetry per gaps table). Track **[CHANGELOG.md](CHANGELOG.md)**.

**v1.1.1** (patch, shipped): **`GET /v1/workspace`** (read-only flags + **`server_version`**); web **Actions** page uses it for **direct promote** vs **request / pending list / confirm**; operator docs refresh (**README**, **release-artifact**, **examples**, **web-ui**, **http-api**, **sdk**, **`docs/pricing-catalog.md`**, **SECURITY**); **`examples/ci/promote_with_approval.sh`** and **`github-actions/promote-approval-twostep.yml`**; CI runs a second Playwright pass with **`FD_E2E_FORCE_APPROVAL`**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**.

**v1.1.0** (minor, shipped): Phase 1 first slice — **`pricing_catalog_path`** + **`pricing.catalog`** / **`pricing.hints`** on diffs; **`promotion_requires_approval`** + promote **request/confirm** (HTTP + CLI) + **`GET /v1/promotion-requests`**; **`GET /v1/runs`** / **`runs list`**; **Helm** reference chart; **`examples/fleet/`**; SQLite migration **v4**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**.

Expand Down Expand Up @@ -90,7 +92,13 @@ Shipped on **`main`**:

Goal: move from solid local tooling to repeatable production usage patterns.

**v1.1.0** ships the first tranche: catalog + hints on diffs, approval-gated promote (HTTP + CLI), read-only runs listing, Helm + fleet reference docs, and migration **v4**. Remaining bullets below are still in scope for later minors/patches.
**v1.1.0** ships the first tranche: catalog + hints on diffs, approval-gated promote (HTTP + CLI), read-only runs listing, Helm + fleet reference docs, and migration **v4**. **v1.1.1** closes the **web + discovery** gap for approval (**`GET /v1/workspace`**, Actions UX) and refreshes team docs / CI examples. Remaining bullets below are still in scope for later minors/patches.

### Phase 1 progress (post–v1.1.0 / v1.1.1)

- **Approval workflow:** **v1.1.0** added HTTP + CLI + **`GET /v1/promotion-requests`**. **v1.1.1** adds **`GET /v1/workspace`** and a first-class **web** path (request → pending table → confirm) keyed off `promotion_requires_approval`, plus **`examples/ci/promote_with_approval.sh`** and a **`workflow_dispatch`** sample workflow.
- **Operator narrative:** README / **examples** index / **web-ui** / **release-artifact** / **http-api** / **sdk** / optional **`docs/pricing-catalog.md`** describe catalog fields, runs listing, and the two promote modes.
- **Still open:** **Forensics** beyond read-only runs list (replay/trace-style views, exports), richer catalog lifecycle governance, OTLP-oriented telemetry — see gaps table above.

### Build in this phase

Expand Down
4 changes: 3 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for a pre-push checklist aligned with

## Local HTTP API (`flightdeck serve`)

The bundled server is intended for **local development and demos**. **`POST /v1/promote`** and **`POST /v1/rollback`** are gated in server code so that, with no token configured, only **loopback** clients (`127.0.0.1`, `::1`, `localhost`) can invoke them. If you set **`FLIGHTDECK_LOCAL_API_TOKEN`**, every mutation request must include **`Authorization: Bearer <that value>`**; use a strong random value and treat it like a local secret.
The bundled server is intended for **local development and demos**. **`POST /v1/promote`**, **`POST /v1/promote/request`**, **`POST /v1/promote/confirm`**, and **`POST /v1/rollback`** are gated in server code so that, with no token configured, only **loopback** clients (`127.0.0.1`, `::1`, `localhost`) can invoke them. If you set **`FLIGHTDECK_LOCAL_API_TOKEN`**, every mutation request must include **`Authorization: Bearer <that value>`**; use a strong random value and treat it like a local secret.

**Human approval** (`promotion_requires_approval: true` in `flightdeck.yaml`) adds a **second actor step** before a promote is applied: **`POST /v1/promote/request`** creates a pending row; **`POST /v1/promote/confirm`** completes it. **Policy still runs on confirm** — approval is not a bypass; a request that fails policy remains blocked with the same HTTP **409** outcome as a direct promote. **`GET /v1/workspace`**, **`GET /v1/promotion-requests`**, and other read-only **`GET /v1/*`** routes stay on the read tier (no Bearer required unless you add external controls).

**`POST /v1/events`** and **`POST /v1/diff`** have **no server-side host or token check** in `server/routes/ingest.py` and `server/routes/actions.py`. They are open to any caller that can reach the server. When `flightdeck serve` binds to `127.0.0.1` (the default), this is safe by network topology. If you use `--host 0.0.0.0` or bind to a non-loopback address, event ingest and diff become reachable from any client. Protect them at the network layer (firewall / reverse proxy) if the server is exposed on a shared or public network.
20 changes: 19 additions & 1 deletion docs/http-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Two access tiers:
| Route | No token configured | `FLIGHTDECK_LOCAL_API_TOKEN` set |
|-------|--------------------|---------------------------------|
| `GET /health` | open | open |
| `GET /v1/*` (reads, including `GET /v1/metrics`, `GET /v1/runs`, `GET /v1/promotion-requests`) | open | open |
| `GET /v1/*` (reads, including `GET /v1/workspace`, `GET /v1/metrics`, `GET /v1/runs`, `GET /v1/promotion-requests`) | open | open |
| `POST /v1/events` | open† | open (no Bearer required) |
| `POST /v1/diff` | open | open |
| `POST /v1/promote` | loopback only | `Authorization: Bearer <token>` required |
Expand Down Expand Up @@ -94,6 +94,24 @@ Read-only JSON snapshot of aggregate counts in the local SQLite ledger (releases

---

## `GET /v1/workspace`

Read-only flags derived from `flightdeck.yaml` plus the running package version. Used by the web UI and automation to choose **direct promote** vs **request/confirm** without embedding workspace YAML in the client. No secrets and no catalog file contents — only whether a **non-empty** `pricing_catalog_path` is set (`pricing_catalog_configured`).

**Response** (`WorkspacePublic` — see `schemas/v1/workspace_public.schema.json`)

```json
{
"api_version": "v1",
"kind": "WorkspacePublic",
"promotion_requires_approval": false,
"pricing_catalog_configured": false,
"server_version": "1.1.1"
}
```

---

## `GET /v1/releases`

List all registered releases.
Expand Down
39 changes: 39 additions & 0 deletions docs/pricing-catalog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Pricing catalog (operator YAML)

FlightDeck can load an optional **operator-defined** [`PricingCatalog`](../schemas/v1/pricing_catalog.schema.json) YAML
referenced by **`pricing_catalog_path`** in [`flightdeck.yaml`](release-artifact.md#workspace-config-flightdeckyaml).

## Purpose

Imported **pricing tables** (`flightdeck pricing import …`) drive per-model token rates for runs that match the table’s
**`provider`** + **`pricing_version`**. A **catalog** adds **cross-vendor comparable** rows on diffs (`pricing.catalog` on
`POST /v1/diff` / `release diff`) and diagnostics in **`pricing.hints`** when multiple pricing table versions or naming
patterns appear in the evidence window.

## Relationship to `pricing.prices`

On a diff, **`pricing.prices`** (when present) reflects **per-side imported tables** for the resolved baseline/candidate
models. **`pricing.catalog`** is **additive**: slot/tariff lines from the catalog file, gated by whether the catalog is
enabled and resolvable. You can use **tables only**, **catalog only** for comparable lines, or **both** — they answer
different questions (strict table match vs operator normalization).

## Configuration

Set a non-empty path in `flightdeck.yaml`:

```yaml
pricing_catalog_path: pricing/catalog.yaml
```

Paths are relative to the workspace working directory or absolute. **`GET /v1/workspace`** reports
**`pricing_catalog_configured: true`** when this field is set to a non-empty string (the file is not opened for that probe).

## Failure modes

- **Missing file** — catalog features stay off; diff may note disabled catalog in the payload.
- **Malformed YAML** — syntax errors are treated as **non-fatal** for the diff: the request still returns **HTTP 200** with
catalog disabled and diagnostics where implemented; see [CHANGELOG.md](../CHANGELOG.md) for behavior on your version.

## Sample

See **[examples/pricing/catalog.sample.yaml](../examples/pricing/catalog.sample.yaml)**.
11 changes: 11 additions & 0 deletions docs/release-artifact.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,22 @@ diff:
min_candidate_runs: 500 # HIGH confidence threshold (candidate side)
min_baseline_runs: 500 # HIGH confidence threshold (baseline side)
min_low_runs: 50 # LOW confidence floor
# Optional: YAML PricingCatalog for cross-vendor comparable lines on diffs (see schemas/v1/pricing_catalog.schema.json)
# pricing_catalog_path: pricing/catalog.yaml
# Optional: when true, direct promote is rejected until a pending request is confirmed (HTTP/CLI request + confirm)
# promotion_requires_approval: false
```

All fields have defaults; an empty `flightdeck.yaml` is valid. `db_path` accepts any
relative or absolute path — the parent directory is created automatically on first use.

**`pricing_catalog_path`** — optional path to a [`PricingCatalog`](../schemas/v1/pricing_catalog.schema.json) YAML
(relative to the workspace cwd or absolute). When set, diffs include additive `pricing.catalog` / `pricing.hints`.
**`promotion_requires_approval`** — when `true`, `POST /v1/promote` and `flightdeck release promote` reject until a row is
completed via `POST /v1/promote/request` then `POST /v1/promote/confirm` (or CLI `release promote-request` / `promote-confirm`).
**`GET /v1/workspace`** exposes non-secret booleans for automation and the web UI (`promotion_requires_approval`,
`pricing_catalog_configured`, `server_version`).

`diff.*` thresholds are the **workspace defaults** used when the active policy does not
override them. The policy's `min_*` fields take precedence when set (including `0` for
"no minimum"). See [operations-and-policy.md § Confidence tiers](operations-and-policy.md).
Expand Down
4 changes: 4 additions & 0 deletions docs/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ See [SECURITY.md](../SECURITY.md) for the full access model.

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

### `get_workspace() -> dict`

`GET /v1/workspace` — returns `WorkspacePublic` JSON: `promotion_requires_approval`, `pricing_catalog_configured` (whether `pricing_catalog_path` is set to a non-empty string), and `server_version` (installed `flightdeck-ai` SemVer). Same method exists on `AsyncFlightdeckClient`. See [http-api.md § GET /v1/workspace](http-api.md#get-v1workspace).

### `GET /v1/metrics` (no SDK wrapper)

The metrics endpoint has no dedicated SDK method. Call it directly via `httpx` or `requests`:
Expand Down
Loading
Loading