From 8fd2bce9c62d8cedf300c4441d36f8487fd34778 Mon Sep 17 00:00:00 2001 From: zendaya Date: Sat, 2 May 2026 23:20:32 +0200 Subject: [PATCH 1/2] feat: add export command for run events as JSONL - Introduced a new `flightdeck runs export` command to export filtered run events as JSONL, allowing users to specify a time window and various filters (e.g., `--env`, `--tenant`, `--task`, `--trace-id`). - Updated CLI documentation to reflect the new export functionality and its options. - Enhanced tests to verify the correct behavior of the export command, including handling of truncation warnings when the result exceeds the specified limit. - Bumped version to 1.1.2 to reflect the addition of this feature. This update enhances the forensics capabilities of the FlightDeck platform by enabling users to export run events for further analysis. --- .github/workflows/release-pypi.yml | 2 +- CHANGELOG.md | 7 ++- DEVELOPMENT.md | 2 +- RELEASE_NOTES.md | 4 ++ ROADMAP.md | 16 +++-- docs/cli.md | 12 +++- docs/http-api.md | 2 +- examples/README.md | 14 ++++- examples/ci/README.md | 2 +- .../ci/github-actions/policy-gate-pypi.yml | 2 +- examples/deploy/Dockerfile | 2 +- examples/deploy/README.md | 3 +- examples/deploy/chart/flightdeck/Chart.yaml | 2 +- pyproject.toml | 2 +- src/flightdeck/__init__.py | 2 +- src/flightdeck/cli/main.py | 63 ++++++++++++++++++- tests/test_phase1_features.py | 24 +++++++ 17 files changed, 142 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index ab975ca..db7ed8b 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -1,4 +1,4 @@ -# Publish sdist + wheel to PyPI when a SemVer tag is pushed (e.g. v1.1.1). +# Publish sdist + wheel to PyPI when a SemVer tag is pushed (e.g. v1.1.2). # Configure "trusted publishing" on PyPI for this workflow + repository + optional GitHub environment. # https://docs.pypi.org/trusted-publishers/ diff --git a/CHANGELOG.md b/CHANGELOG.md index df62567..d858f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,16 @@ All notable changes to FlightDeck will be documented in this file. This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0**, documented CLI behavior (**[README.md](https://github.com/flightdeckdev/flightdeck/blob/main/README.md)** on the canonical **`main`** branch), committed **`schemas/v1/`**, and **`POST /v1/events`** payloads with **`api_version` `v1`** are treated as stable public contracts unless a release notes a semver-major bump. -## Unreleased +## 1.1.2 - 2026-05-03 ### Added - **`trace_id` filter** on `GET /v1/runs`, `flightdeck runs list --trace-id`, and SDK `list_runs(trace_id=…)` — exact match on ingested `RunEvent.request.trace_id`. +- **`flightdeck runs export`** — write the same filtered run-event slice as **`runs list`** as **JSONL** (optional **`-o`** file; default stdout); **`--limit`** defaults to **500** (max **500**); prints a **stderr** warning when results are truncated. + +### Changed + +- **Examples / CI snippets:** **`flightdeck-ai>=1.1.2`** where version pins apply. ## 1.1.1 - 2026-05-02 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 7524de8..191273c 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -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.1`**) then **`git push origin vX.Y.Z`**. +4. **`git tag vX.Y.Z`** (must match **`pyproject.toml`** exactly, e.g. **`v1.1.2`**) 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. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bf5ce59..79da562 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -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.2 — Forensics filters, JSONL export, Phase 1 closure slice + +Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): optional **`trace_id`** filter on **`GET /v1/runs`**, **`flightdeck runs list --trace-id`**, and SDK **`list_runs(trace_id=…)`** (exact match on **`RunEvent.request.trace_id`**); **`flightdeck runs export`** writes the same filtered slice as JSONL (stdout or **`-o`**, **`--limit`** up to **500**, stderr warning when truncated). **Stable contracts:** additive HTTP query param and CLI command only. + ## 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. diff --git a/ROADMAP.md b/ROADMAP.md index f8f0dab..ac37800 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -13,7 +13,7 @@ This roadmap is meant to be clear from **what is already shipped** to **near-ter - **Release registry and verification:** versioned `release.yaml` artifacts with checksums, `flightdeck release verify`. - **Economic + operational governance:** immutable pricing imports, trusted `release diff`, policy-gated `promote` and `rollback`. - **Audit trail:** promotion/rollback history with stable sequencing (`audit_seq`) and integrity checks via `doctor`. -- **Evidence ingestion:** `runs ingest` from JSONL/JSON arrays plus stable `POST /v1/events` contracts (`schemas/v1/`). +- **Evidence ingestion:** `runs ingest` from JSONL/JSON arrays plus stable `POST /v1/events` contracts (`schemas/v1/`); **`GET /v1/runs`**, **`runs list`**, optional **`trace_id`** filter, and **`runs export`** (JSONL) for operator forensics. - **Local API + UI:** `flightdeck serve` routes and web UI (Overview with ledger metrics, Diff, Promote) in `src/flightdeck/server/static/`. - **SDK and tooling:** Python sync/async clients with retries/batching and `flightdeck-quickstart-verify`. @@ -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.1** (deeper forensics / replay UX, OTLP-oriented telemetry per gaps table). Track **[CHANGELOG.md](CHANGELOG.md)**. +Further work after **v1.1.2** — **OTLP-oriented** telemetry, **replay-style web** forensics, deeper **catalog lifecycle** governance, and **cross-workspace / fleet** product surfaces stay on **Phase 2 / mid-term** (see gaps table below). Track **[CHANGELOG.md](CHANGELOG.md)**. + +**v1.1.2** (patch, shipped): **`trace_id`** filter on **`GET /v1/runs`**, **`flightdeck runs list --trace-id`**, and SDK **`list_runs`**, plus **`flightdeck runs export`** (JSONL, stderr warning when truncated); **[examples/README.md](examples/README.md)** adds a **Phase 1 readiness checklist**; **Phase 1 status** (this document) records closure of the scoped productization tranche. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.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)**. @@ -92,13 +94,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**. **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. +**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. **v1.1.2** adds **CLI/HTTP forensics** filters and **JSONL export**; see **Phase 1 status** below. ### 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 and **`trace_id`-scoped listing** (replay-style views, JSONL exports), richer catalog lifecycle governance, OTLP-oriented telemetry — see gaps table above. +- **Forensics (v1.1.2):** **`trace_id`** filter and **`runs export`** (JSONL). **Still open** for replay-style **web** views and richer export semantics if needed; **OTLP** and **catalog lifecycle** depth remain mid-term (gaps table). ### Build in this phase @@ -115,6 +117,12 @@ Goal: move from solid local tooling to repeatable production usage patterns. - At least two provider pricing sources compare cleanly in one diff workflow. - Teams can stand up and operate `flightdeck serve` with documented deployment guidance. +### Phase 1 status + +**Phase 1 is closed** as of **v1.1.2** for the **scoped local-first productization tranche** shipped across **v1.1.0–v1.1.2**: catalog-aware diffs + hints, approval-gated promote (HTTP + CLI + web), read-only runs forensics (**`GET /v1/runs`**, **`runs list`**, **`trace_id`**, **`runs export`**), reference **Helm** / **Compose** / **fleet** examples, SQLite migration **v4**, **`GET /v1/workspace`**, and a discoverability **checklist** for the Phase-1 **readiness signals** in **[examples/README.md](examples/README.md)**. + +**Carried forward to Phase 2 / mid-term** (explicitly not claimed by this closure): **OTLP-oriented** correlated telemetry; **cross-workspace / fleet** governance products; **replay-style** forensics UI beyond CLI/HTTP listing; **enterprise-grade** identity beyond documented bearer + loopback patterns; deeper **catalog lifecycle** governance (for example version skew hints). + --- ## Phase 2: Scale and platform options (long term, conditional) diff --git a/docs/cli.md b/docs/cli.md index 0a17c2b..6baaa89 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -446,7 +446,7 @@ If no policy has been set, prints the default policy (all constraints `null`/dis ## `flightdeck runs` -Subgroup for ingesting and listing run events. +Subgroup for ingesting, listing, and exporting run events. ### `flightdeck runs list` @@ -458,6 +458,16 @@ flightdeck runs list RELEASE_ID --window WINDOW [--env ENV] [--tenant …] [--ta `--trace-id` filters to events whose ingested `request.trace_id` equals the given string (exact match), same as the `trace_id` query parameter on `GET /v1/runs`. +### `flightdeck runs export` + +Write the same filtered slice as `runs list` (newest first) as **JSONL** — one `RunEvent` JSON object per line. Default **`--limit`** is **500** (maximum **500**). If more events match the window and filters, only the first **`--limit`** lines are written and a **`WARNING:`** line is printed to **stderr** with `exported` / `matching` counts. + +```bash +flightdeck runs export RELEASE_ID --window WINDOW [-o export.jsonl] [--env ENV] [--tenant …] [--task …] [--trace-id ID] [--limit N] +``` + +With **`-o` / `--output`**, writes UTF-8 JSONL to that path; without it, writes to **stdout** (suitable for pipes). + ### `flightdeck runs ingest` Ingest `RunEvent` records from a JSONL or JSON array file. diff --git a/docs/http-api.md b/docs/http-api.md index e00d02b..8754fb6 100644 --- a/docs/http-api.md +++ b/docs/http-api.md @@ -106,7 +106,7 @@ Read-only flags derived from `flightdeck.yaml` plus the running package version. "kind": "WorkspacePublic", "promotion_requires_approval": false, "pricing_catalog_configured": false, - "server_version": "1.1.1" + "server_version": "1.1.2" } ``` diff --git a/examples/README.md b/examples/README.md index 2a142e5..4f76ac1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,9 +8,19 @@ This folder holds **copy-pasteable** references for wiring FlightDeck into a rea 2. **Ingest** evidence: `flightdeck runs ingest ` (or JSON array file), or HTTP `POST /v1/events` while `flightdeck serve` is running. 3. **Register** a release bundle: `flightdeck release register ` then **`flightdeck release verify`** against the same tree before you trust the checksum. 4. **Diff and gate** in CI: `flightdeck release diff …` with **`--fail-on-policy`** when you want a non-zero exit without mutating promotion — see [ci/](ci/README.md) and `ledger_gate.py` / GitHub Actions templates. Optional **`pricing_catalog_path`** in `flightdeck.yaml` adds **`pricing.catalog`** / **`pricing.hints`** on diffs (see [docs/pricing-catalog.md](../docs/pricing-catalog.md)). -5. **Promote or rollback** via CLI (`flightdeck release promote` / `rollback`) or HTTP `POST /v1/promote` and `POST /v1/rollback` (token + loopback rules apply). When **`promotion_requires_approval: true`**, use **`release promote-request`** / **`promote-confirm`** or **`POST /v1/promote/request`** then **`POST /v1/promote/confirm`** — see [ci/promote_with_approval.sh](ci/promote_with_approval.sh). +5. **Promote or rollback** via CLI (`flightdeck release promote` / `rollback`) or HTTP `POST /v1/promote` and `POST /v1/rollback` (token + loopback rules apply). When **`promotion_requires_approval: true`**, use **`release promote-request`** / **`promote-confirm`** or **`POST /v1/promote/request`** then **`POST /v1/promote/confirm`** — see [ci/promote_with_approval.sh](ci/promote_with_approval.sh) and [ci/README.md](ci/README.md) (GitHub Actions patterns). 6. **Run the server** in a container or compose stack — see [deploy/](deploy/README.md). The bundled UI calls **`GET /v1/workspace`** to choose direct promote vs request/confirm. -7. **Triage runs** with **`flightdeck runs list`** or **`GET /v1/runs`**, and **observe** aggregate ledger size with **`GET /v1/metrics`** (JSON counters; read-only, same access tier as other `GET /v1/*` routes). +7. **Triage runs** with **`flightdeck runs list`** / **`runs export`** or **`GET /v1/runs`**, and **observe** aggregate ledger size with **`GET /v1/metrics`** (JSON counters; read-only, same access tier as other `GET /v1/*` routes). + +## Phase 1 readiness (quick checklist) + +Use this as a **discoverability** pass for the **[ROADMAP.md](../ROADMAP.md)** Phase-1 readiness signals (not a product guarantee): + +| Signal | Where to start | +|--------|----------------| +| **Approval-gated promote in CI** | [ci/promote_with_approval.sh](ci/promote_with_approval.sh), [ci/README.md](ci/README.md), [ci/github-actions/promote-approval-twostep.yml](ci/github-actions/promote-approval-twostep.yml) | +| **Two-provider (or catalog) pricing on a diff** | [docs/pricing-catalog.md](../docs/pricing-catalog.md); tests **`test_diff_cross_provider_releases`** and **`test_catalog_comparable_cost_on_cross_provider_diff`** in `tests/` | +| **Operate `flightdeck serve` with deployment guidance** | [deploy/README.md](deploy/README.md) (**Compose healthcheck**, **`FLIGHTDECK_LOCAL_API_TOKEN`**, **SQLite backup** via **`flightdeck doctor --backup`**); optional **Helm** under [deploy/chart/flightdeck/](deploy/chart/flightdeck/) | ## Subfolders diff --git a/examples/ci/README.md b/examples/ci/README.md index 733a865..2fe7e31 100644 --- a/examples/ci/README.md +++ b/examples/ci/README.md @@ -37,7 +37,7 @@ uv run python examples/ci/ledger_gate.py Example (**PyPI** install): ```bash -pip install "flightdeck-ai>=1.1.1" +pip install "flightdeck-ai>=1.1.2" export WORKSPACE="$(mktemp -d)" export QUICKSTART_ROOT=/path/to/flightdeck/examples/quickstart python /path/to/flightdeck/examples/ci/ledger_gate.py diff --git a/examples/ci/github-actions/policy-gate-pypi.yml b/examples/ci/github-actions/policy-gate-pypi.yml index d8aa794..dde10b2 100644 --- a/examples/ci/github-actions/policy-gate-pypi.yml +++ b/examples/ci/github-actions/policy-gate-pypi.yml @@ -11,7 +11,7 @@ on: env: # Pin to a tag or SHA that matches your installed flightdeck-ai version when possible. FLIGHTDECK_REF: main - FLIGHTDECK_AI_SPEC: ">=1.1.1" + FLIGHTDECK_AI_SPEC: ">=1.1.2" jobs: ledger-gate: diff --git a/examples/deploy/Dockerfile b/examples/deploy/Dockerfile index a2788eb..823e9b7 100644 --- a/examples/deploy/Dockerfile +++ b/examples/deploy/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.14-slim RUN pip install --no-cache-dir --upgrade pip \ - && pip install --no-cache-dir "flightdeck-ai>=1.1.1" + && pip install --no-cache-dir "flightdeck-ai>=1.1.2" WORKDIR /workspace diff --git a/examples/deploy/README.md b/examples/deploy/README.md index 625cbf8..2c68c30 100644 --- a/examples/deploy/README.md +++ b/examples/deploy/README.md @@ -69,4 +69,5 @@ Compose sets a **`healthcheck`** on **`/health`** plus restart policies; for sys ## Related - **[examples/integration/README.md](../integration/README.md)** — emit `RunEvent` traffic into a running server. -- **[examples/ci/README.md](../ci/README.md)** — CI policy gates without `serve`. +- **[examples/ci/README.md](../ci/README.md)** — CI policy gates without `serve`; **approval-gated promote** script [promote_with_approval.sh](../ci/promote_with_approval.sh) and workflow samples. +- **[SECURITY.md](../../SECURITY.md)** — trust boundaries before exposing **`/v1/*`** beyond loopback. diff --git a/examples/deploy/chart/flightdeck/Chart.yaml b/examples/deploy/chart/flightdeck/Chart.yaml index dcffa04..b6e9a2e 100644 --- a/examples/deploy/chart/flightdeck/Chart.yaml +++ b/examples/deploy/chart/flightdeck/Chart.yaml @@ -3,4 +3,4 @@ name: flightdeck description: Optional Helm chart for flightdeck serve (single-replica reference) type: application version: 0.1.0 -appVersion: "1.1.1" +appVersion: "1.1.2" diff --git a/pyproject.toml b/pyproject.toml index 7cc2b06..4a9acb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "flightdeck-ai" -version = "1.1.1" +version = "1.1.2" description = "Ship AI agents safely with release diffs, runtime evidence, and policy gates." readme = "README.md" license = "Apache-2.0" diff --git a/src/flightdeck/__init__.py b/src/flightdeck/__init__.py index 26e2ccc..c91200d 100644 --- a/src/flightdeck/__init__.py +++ b/src/flightdeck/__init__.py @@ -1,3 +1,3 @@ """FlightDeck — ship AI agents safely with release diffs, runtime evidence, and policy gates.""" -__version__ = "1.1.1" +__version__ = "1.1.2" diff --git a/src/flightdeck/cli/main.py b/src/flightdeck/cli/main.py index 87ab906..5f8924e 100644 --- a/src/flightdeck/cli/main.py +++ b/src/flightdeck/cli/main.py @@ -4,6 +4,7 @@ import json import os +import sys from pathlib import Path from uuid import uuid4 @@ -312,7 +313,7 @@ def policy_show() -> None: @cli.group() def runs() -> None: - """Ingest run events.""" + """Ingest, list, and export run events.""" @runs.command("list") @@ -369,6 +370,66 @@ def runs_list( click.echo(json.dumps(ev, sort_keys=True)) +@runs.command("export") +@click.argument("release_id") +@click.option("--window", required=True, help="Time window like 7d, 24h, 30m.") +@click.option("--env", "environment", default=None) +@click.option("--tenant", "tenant_id", default=None) +@click.option("--task", "task_id", default=None) +@click.option("--trace-id", "trace_id", default=None, help="Filter to events whose request.trace_id matches (exact).") +@click.option("--limit", default=500, show_default=True, type=click.IntRange(1, 500)) +@click.option( + "-o", + "--output", + "output_path", + type=click.Path(path_type=Path), + default=None, + help="Write JSONL to this file; default is stdout.", +) +def runs_export( + release_id: str, + window: str, + environment: str | None, + tenant_id: str | None, + task_id: str | None, + trace_id: str | None, + limit: int, + output_path: Path | None, +) -> None: + """Export run events as JSONL (newest first), same filters as ``runs list`` (truncated to --limit, max 500).""" + cfg = load_config() + storage = Storage(cfg.db_path) + storage.migrate() + try: + payload = query_run_events_page( + cfg=cfg, + storage=storage, + release_id=release_id, + window=window, + environment=environment, + tenant_id=tenant_id, + task_id=task_id, + trace_id=trace_id, + limit=limit, + ) + except OperationError as e: + raise click.ClickException(str(e)) from e + stream = output_path.open("w", encoding="utf-8", newline="\n") if output_path else None + try: + out = stream or sys.stdout + for ev in payload["events"]: + out.write(json.dumps(ev, sort_keys=True) + "\n") + finally: + if stream is not None: + stream.close() + if payload["truncated"]: + click.echo( + f"WARNING: exported {payload['returned']} of {payload['matched_total']} matching events " + f"(cap --limit {limit}).", + err=True, + ) + + @runs.command("ingest") @click.argument("path", type=click.Path(exists=True, path_type=Path)) def runs_ingest(path: Path) -> None: diff --git a/tests/test_phase1_features.py b/tests/test_phase1_features.py index 9b0351a..70c0c49 100644 --- a/tests/test_phase1_features.py +++ b/tests/test_phase1_features.py @@ -301,6 +301,30 @@ def test_cli_runs_list_json(tmp_path: Path, monkeypatch) -> None: assert payload["matched_total"] == 1 +def test_cli_runs_export_jsonl_truncation_and_stderr(tmp_path: Path, monkeypatch) -> None: + monkeypatch.chdir(tmp_path) + runner = CliRunner() + assert runner.invoke(cli, ["init"]).exit_code == 0 + policy = write_policy(tmp_path, min_candidate_runs=0, min_baseline_runs=0, min_low_runs=0) + assert runner.invoke(cli, ["policy", "set", str(policy)]).exit_code == 0 + pricing = write_pricing(tmp_path, provider="openai", pricing_version="openai-2026-04-30") + assert runner.invoke(cli, ["pricing", "import", str(pricing)]).exit_code == 0 + rdir = write_release(tmp_path, agent_id="ag", version="1", pricing_provider="openai", pricing_version="openai-2026-04-30") + rid = runner.invoke(cli, ["release", "register", str(rdir)]).output.strip() + now = datetime.now(tz=timezone.utc) + assert runner.invoke(cli, ["runs", "ingest", str(write_events(tmp_path, release_id=rid, agent_id="ag", n=5, ts=now))]).exit_code == 0 + out_path = tmp_path / "exp.jsonl" + res = runner.invoke( + cli, + ["runs", "export", rid, "--window", "7d", "--limit", "2", "-o", str(out_path)], + ) + assert res.exit_code == 0 + lines = [ln for ln in out_path.read_text(encoding="utf-8").splitlines() if ln.strip()] + assert len(lines) == 2 + assert "WARNING" in (res.stderr or "") + assert "exported 2 of 5" in (res.stderr or "") + + def test_diff_survives_malformed_catalog_yaml_syntax(tmp_path: Path, monkeypatch) -> None: """Invalid YAML in pricing catalog must not crash diff (YAMLError → catalog warning).""" monkeypatch.chdir(tmp_path) From 21c19e4c836fe713fa42647691ed6806819845ea Mon Sep 17 00:00:00 2001 From: zendaya Date: Sat, 2 May 2026 23:20:39 +0200 Subject: [PATCH 2/2] Update flightdeck-ai version to 1.1.2 in uv.lock --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 7e98277..1ad8c4a 100644 --- a/uv.lock +++ b/uv.lock @@ -167,7 +167,7 @@ wheels = [ [[package]] name = "flightdeck-ai" -version = "1.1.1" +version = "1.1.2" source = { editable = "." } dependencies = [ { name = "aiosqlite" },