Skip to content

Commit 1cc3466

Browse files
author
ancongui
committed
release: v26.5.10 — v1 endpoints + RetentionWorker + worker CLI + repo extraction + SDK helpers
Headlines - v1 history/billing/stats: GET /queries, /queries/{id}, /queries/{id}/result, /billing, /stats - RetentionWorker (TTL sweep + stuck-RUNNING reaper + orphan-PENDING republisher) - Worker CLI: flyquery worker {ingest|retention|all} - Repository extraction complete: 8 new repos, 0 raw SQL in controllers, CI gate enforces - Paginated[T] uniform envelope across 14 list endpoints - /datasets/{id}/files:async endpoint with Stage-1-only sync path - /datasets/{id}:purge endpoint with object-store walk - Agent surface: 8 → 28 paths - Idempotency-Key wired on 5 endpoints - OpenAPI spec ships header + security + SSE content surface - Java FlyqueryClient + Python FlyqueryClient with v1 helpers - 311 unit tests (+53 vs session start), lint clean, lockstep clean - Two new deep-dive docs: schema-detection.md (6.2k words), workers.md (4.3k words) - 16 existing docs refreshed for the release - Release runbook at docs/release.md Fixes - Production bug in IngestWorker._drain_inflight (with asyncio.timeout → wait_for) - Missing return-type annotations on 8 controllers (dropped schemas from spec) - Docker label was flycanon string - 4 documented endpoints (/audit, /billing, /stats, /queries) now live Spec: 64 → 95 paths · 51 → 85 schemas · 22 → 32 tags Routes: 129 → 142 mounted Tests: 258 → 311
1 parent 0a1c63c commit 1cc3466

730 files changed

Lines changed: 81979 additions & 50206 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-publish.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ jobs:
112112
type=raw,value=manual-${{ github.run_id }},enable=${{ github.event_name == 'workflow_dispatch' }}
113113
labels: |
114114
org.opencontainers.image.title=flyquery
115-
org.opencontainers.image.description=flyquery -- Operational Knowledge Repository. Universal ingestion (any file format), hybrid retrieval (Postgres BM25 + pgvector dense + RRF), grounded RAG answers with citations, append-only version chain + provenance graph + EDA outbox. Part of Firefly OperationOS, platform-agnostic.
115+
org.opencontainers.image.description=flyquery -- Operational Structured-Data Intelligence (upload-driven). Multi-tenant ingestion + Text-to-SQL service over user-uploaded structured files (CSV / TSV / XLSX / XLS / ODS / JSON / JSONL / Parquet / Avro / ORC / Arrow / Feather + compression variants). Materialises uploads to Parquet on object storage; indexes a long-lived schema knowledge base; answers natural-language questions via a multi-agent pipeline. Part of Firefly OperationOS.
116116
org.opencontainers.image.source=https://github.com/${{ github.repository }}
117117
org.opencontainers.image.licenses=Proprietary
118118
org.opencontainers.image.vendor=Firefly Software Solutions Inc

CHANGELOG.md

Lines changed: 541 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ JSONL, Parquet, Avro, ORC, Arrow and Feather, plus their `.gz`,
2525
[![OpenAPI](https://img.shields.io/badge/api-openapi%203.1-green)](docs/api-reference.md)
2626
[![DuckDB](https://img.shields.io/badge/query%20engine-duckdb-yellow)](docs/architecture.md)
2727
[![pgvector](https://img.shields.io/badge/vector--store-pgvector-336791)](docs/architecture.md)
28-
[![Version](https://img.shields.io/badge/version-26.5.4-green.svg)](#)
28+
[![Version](https://img.shields.io/badge/version-26.5.10-green.svg)](#)
2929
[![License](https://img.shields.io/badge/license-Proprietary-lightgrey.svg)](LICENSE)
3030

3131
</div>

Taskfile.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,27 +87,51 @@ tasks:
8787
cmds:
8888
- uv run python scripts/openapi_snapshot.py
8989

90+
sdk:all:
91+
desc: Refresh openapi.json snapshot and regenerate both SDKs
92+
cmds:
93+
- task: openapi-snapshot
94+
- task: sdk:python
95+
- task: sdk:java
96+
9097
sdk:python:
9198
desc: Regenerate the Python SDK from openapi.json
9299
cmds:
93100
# NOTE: pyproject.toml metadata (authors, description, urls) is
94101
# preserved across regeneration by .openapi-generator-ignore.
95102
# Only packageVersion + library are passed as additional-properties;
96103
# multi-word values are hand-curated in pyproject.toml.
104+
#
105+
# IMPORTANT: prune the generated api/ + models/ + docs/ trees
106+
# FIRST so files for OpenAPI tags that have since been split or
107+
# renamed (eg. SchemaApi -> SchemaChangesApi + SchemaObjectsApi)
108+
# don't accumulate. .openapi-generator-ignore preserves the
109+
# hand-curated README + pyproject + LICENSE + examples/.
110+
- rm -rf sdks/python/flyquery_sdk/api
111+
- rm -rf sdks/python/flyquery_sdk/models
112+
- rm -rf sdks/python/docs
97113
- npx -y @openapitools/openapi-generator-cli generate
98114
-i openapi.json
99115
-g python
100116
-o sdks/python
101117
--package-name flyquery_sdk
102-
--additional-properties=packageVersion=26.5.6,projectName=flyquery-sdk,library=asyncio
118+
--additional-properties=packageVersion=26.5.10,projectName=flyquery-sdk,library=asyncio
103119
--skip-validate-spec
104120
- cd sdks/python && uv pip install --system --quiet . || true
105121

106122
sdk:java:
107123
desc: Regenerate the Java SDK from openapi.json (Java 25 + Spring Boot 3.5.9 + WebFlux)
108124
cmds:
109-
# Remove stale package trees from earlier generator runs.
125+
# Remove stale package trees from earlier generator runs (older
126+
# versions used org.* / io.* package roots) AND the current
127+
# api/ + model/ + docs/ trees so renamed/split tags don't leave
128+
# zombie classes behind.
110129
- rm -rf sdks/java/src/main/java/org sdks/java/src/main/java/io
130+
- rm -rf sdks/java/src/main/java/com/firefly/flyquery/api
131+
- rm -rf sdks/java/src/main/java/com/firefly/flyquery/model
132+
- rm -rf sdks/java/src/test/java/com/firefly/flyquery/api
133+
- rm -rf sdks/java/src/test/java/com/firefly/flyquery/model
134+
- rm -rf sdks/java/docs sdks/java/api
111135
- npx -y @openapitools/openapi-generator-cli generate
112136
-i openapi.json
113137
-g java
@@ -116,5 +140,5 @@ tasks:
116140
--invoker-package com.firefly.flyquery
117141
--api-package com.firefly.flyquery.api
118142
--model-package com.firefly.flyquery.model
119-
--additional-properties=artifactId=flyquery-sdk,groupId=com.firefly,artifactVersion=26.5.6,library=webclient,dateLibrary=java8,java8=true
143+
--additional-properties=artifactId=flyquery-sdk,groupId=com.firefly,artifactVersion=26.5.10,library=webclient,dateLibrary=java8,java8=true,useJakartaEe=true
120144
--skip-validate-spec

docs/README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ Pick the entry point that matches what you're trying to do.
4040
durable Postgres outbox default.
4141
2. [**async-ingest.md**](async-ingest.md) — how `IngestWorker` consumes the
4242
`flyquery.ingest` topic, job lifecycle, cooperative cancel, retry + dead-letter.
43-
3. [**architecture.md**](architecture.md) — how `pyfly.eda.EventPublisher` is
43+
3. [**workers.md**](workers.md) — the worker fleet topology (IngestWorker +
44+
RetentionWorker), CLI subcommands, scaling, recovery, and deployment forms.
45+
4. [**architecture.md**](architecture.md) — how `pyfly.eda.EventPublisher` is
4446
injected into the ingestion and query services.
4547

4648
### "I want to understand how it works"
@@ -52,7 +54,10 @@ Pick the entry point that matches what you're trying to do.
5254
diagrams; mode coupling (sync vs async via EDA).
5355
3. [**ingestion.md**](ingestion.md) — stage-by-stage ingestion detail: each
5456
stage's inputs, outputs, skip policy, and SSE events.
55-
4. [**prompts.md**](prompts.md) — catalog of the 7 agent prompts: instruction
57+
4. [**schema-detection.md**](schema-detection.md) — XLSX/CSV/JSON parsing
58+
deep dive, type inference, drift detection, PII tagging, and the
59+
column-name proposer agent.
60+
5. [**prompts.md**](prompts.md) — catalog of the 7 agent prompts: instruction
5661
text, input shape, output schema.
5762

5863
### "I'm running this in production"
@@ -138,8 +143,10 @@ Pick the entry point that matches what you're trying to do.
138143
| [integration-with-firefly-os.md](integration-with-firefly-os.md) | You're wiring flyquery with flycanon or flyradar. |
139144
| [glossary.md](glossary.md) | You need a precise definition for a term the API or docs use. |
140145
| [scale-and-performance.md](scale-and-performance.md) | You're capacity-planning or tuning FLYQUERY_* knobs. |
141-
| [billing.md](billing.md) | You're tracking per-query and per-ingest LLM cost via `flyquery_cost_events`. |
146+
| [billing.md](billing.md) | You're tracking per-query and per-ingest LLM cost via `flyquery_cost_events` and `GET /api/v1/billing`. |
142147
| [stats.md](stats.md) | You're rendering a workspace dashboard via `GET /api/v1/stats`. |
148+
| [schema-detection.md](schema-detection.md) | You're debugging XLSX/CSV/JSON parsing, type inference, drift detection, or the column-name proposer. |
149+
| [workers.md](workers.md) | You're running, scaling, or recovering the `IngestWorker` + `RetentionWorker` fleet. |
143150
| [quality.md](quality.md) | You're running or extending the test suite — unit, integration, parser fixtures, pipeline tests. |
144151
| [firefly-intelligence-system.md](firefly-intelligence-system.md) | You want the three-pillar narrative (flycanon + flyradar + flyquery). |
145152
| [../sdks/python/README.md](../sdks/python/README.md) | You're integrating from Python (async-first SDK, Pydantic types). |
@@ -164,14 +171,16 @@ Pick the entry point that matches what you're trying to do.
164171
| Agent-token surface | [security-model.md § 4](security-model.md) | [api-reference.md](api-reference.md) |
165172
| EDA / typed events | [eda-events.md](eda-events.md) | [async-ingest.md](async-ingest.md) |
166173
| Snapshot versioning + pinning | [ingestion.md](ingestion.md) | [conversations.md](conversations.md) |
167-
| Schema drift + rename detection | [ingestion.md § RENAMED_CANDIDATE](ingestion.md) | [pipeline.md](pipeline.md) |
174+
| Schema drift + rename detection | [schema-detection.md](schema-detection.md) | [ingestion.md § RENAMED_CANDIDATE](ingestion.md) |
175+
| Format detection + parser dispatch | [schema-detection.md](schema-detection.md) | [file-formats.md](file-formats.md) |
176+
| Worker fleet + scaling | [workers.md](workers.md) | [async-ingest.md](async-ingest.md), [deployment-topology.md](deployment-topology.md) |
168177
| Object-store layout | [architecture.md § Storage architecture](architecture.md) | [deployment.md](deployment.md) |
169-
| Billing + cost stream | [billing.md](billing.md) | [api-reference.md](api-reference.md) |
178+
| Billing + cost stream | [billing.md](billing.md) | [api-reference.md](api-reference.md), [cost-tracking.md](cost-tracking.md) |
170179
| Workspace stats | [stats.md](stats.md) | [api-reference.md](api-reference.md) |
171180
| Lock-step modules | [architecture.md § Lock-step](architecture.md) | [cicd.md](cicd.md) |
172181
| GDPR purge | [operations-runbook.md](operations-runbook.md) | [security-model.md § Upload security](security-model.md) |
173182
| RFC 7807 error envelope | [payload-reference.md](payload-reference.md) | [api-reference.md](api-reference.md) |
174-
| Multi-replica concurrency | [concurrency.md](concurrency.md) | [async-ingest.md](async-ingest.md) |
183+
| Multi-replica concurrency | [concurrency.md](concurrency.md) | [workers.md](workers.md), [async-ingest.md](async-ingest.md) |
175184

176185
---
177186

docs/api-reference.md

Lines changed: 170 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,39 @@ SDK helpers:
292292

293293
---
294294

295+
#### `POST /api/v1/datasets/{id}/files:async` — async upload
296+
297+
Like `POST /datasets/{id}/files` but the response is a `202 Accepted`
298+
with a job id. Stage 1 (receive: caps check, hash, format detect,
299+
store bytes, write `flyquery_files` row, track workspace storage)
300+
runs **synchronously** before responding — `file_id` is final and the
301+
content hash provides natural per-file dedup. Stages 2-10 of the
302+
pipeline are queued as a `PARSE_AND_INGEST` ingest job that the
303+
`IngestWorker` consumes (the worker honours an `already_received`
304+
flag on the job so it does NOT re-run Stage 1; see
305+
[async-ingest.md](async-ingest.md) and
306+
[workers.md](workers.md) for the worker side).
307+
308+
**Request** `Content-Type: multipart/form-data` — one `file` part.
309+
310+
**Response** `202 Accepted` with `Location: /api/v1/ingest-jobs/{job_id}`
311+
```json
312+
{
313+
"job_id": "01906f2c-...",
314+
"file_id": "01906f2b-...",
315+
"dataset_id": "01906f2a-...",
316+
"status": "PENDING"
317+
}
318+
```
319+
320+
Use in place of the synchronous endpoint when a single file exceeds
321+
the request-timeout budget of the deployment (typically anything
322+
above a few MB once cold-cache LLM describe calls land).
323+
324+
Source: `files_controller.py:249` (upload_file_async).
325+
326+
---
327+
295328
#### `GET /api/v1/datasets/{id}/files`
296329

297330
List files for a dataset.
@@ -883,60 +916,163 @@ Add a drill-down turn. The prior turn's `executed_sql`, `table_qnames`, and
883916

884917
### 5.12 History and ops
885918

919+
#### `GET /api/v1/audit-events`
920+
921+
Paginated audit-event ledger. Filters: `event_type`, `actor`,
922+
`resource_kind`, `date_from`, `date_to`, `limit`, `offset`. Response
923+
is the standard `Paginated[AuditEventRead]` envelope. Newest first.
924+
925+
Today's writers (more callsites land over time):
926+
- Dataset CRUD (`dataset.created`, `dataset.updated`, `dataset.archived`, `dataset.purged`)
927+
- Workspace CRUD (`workspace.created`, `workspace.updated`)
928+
- Agent-token mint / revoke (`agent_token.minted`, `agent_token.revoked`)
929+
930+
Future scope gate: `flyquery.audit:read`.
931+
932+
---
933+
934+
#### `GET /api/v1/cost-events`
935+
936+
Paginated per-call LLM cost ledger. Filters: `actor`, `model`,
937+
`operation`, `date_from`, `date_to`, `limit`, `offset`. Response is
938+
`Paginated[CostEventRead]`. One row per LLM-bearing operation.
939+
940+
Today's writers cover the query pipeline (grounding / generation /
941+
critic / explainer) and the ingest pipeline (describe / column-name
942+
proposer / relation proposer / rename detector).
943+
944+
Future scope gate: `flyquery.billing:read`. The `GET /api/v1/billing`
945+
rollup below is the aggregation on top of this raw stream — see
946+
[billing.md](billing.md).
947+
948+
---
949+
950+
> **Live as of 26.5.10.** The endpoints below were placeholders in
951+
> earlier releases; the read-side query history + billing rollup +
952+
> stats endpoints are now shipped on top of the pre-existing
953+
> `flyquery_queries`, `flyquery_cost_events`, and workspace inventory
954+
> tables. See [`src/flyquery/web/controllers/queries_controller.py`](../src/flyquery/web/controllers/queries_controller.py),
955+
> [`billing_controller.py`](../src/flyquery/web/controllers/billing_controller.py),
956+
> [`stats_controller.py`](../src/flyquery/web/controllers/stats_controller.py).
957+
886958
#### `GET /api/v1/queries`
887959

888-
Paginated query history. Optional: `dataset_id`, `execution_status`,
889-
`semantic_path_taken`, `date_from`, `date_to`.
960+
Paginated query history scoped to the caller's workspace, newest first.
961+
962+
**Query params**`dataset_id`, `execution_status`,
963+
`semantic_path_taken`, `date_from` (inclusive), `date_to` (exclusive),
964+
`limit` (clamped to `[1, 200]`), `offset`.
965+
966+
Each row is the compact `QueryHistoryItem` shape — heavy JSONB columns
967+
(candidates, clarification, PII findings) are omitted; use the detail
968+
endpoint below for the full payload. Response is the standard
969+
`Paginated[QueryHistoryItem]` envelope.
970+
971+
Source: `queries_controller.py:52` (list_queries).
890972

891973
---
892974

893975
#### `GET /api/v1/queries/{id}`
894976

895-
Full query detail including all candidates, retries, cost, model identifiers.
977+
Full single-query payload including every candidate proposal, the AST
978+
classification, every model identifier used
979+
(`model_grounding` / `model_generation` / `model_critic` /
980+
`model_explainer`), PII findings, clarification frame, retries, and the
981+
final error envelope if any. Returns `QueryDetailRead`.
982+
983+
Cross-tenant probing returns 404 (the same response as a missing query),
984+
so the endpoint never leaks the existence of another tenant's row.
985+
986+
Source: `queries_controller.py:100` (get_query).
896987

897988
---
898989

899990
#### `GET /api/v1/queries/{id}/result`
900991

992+
Re-download the preview + a fresh presigned Parquet URL for a previously
993+
executed query.
994+
901995
```json
902996
{
997+
"query_id": "01906f40-...",
903998
"preview_json": [...],
904999
"parquet_presigned_url": "https://...",
9051000
"result_byte_size": 204800,
9061001
"ttl_expires_at": "2026-05-24T12:00:00Z"
9071002
}
9081003
```
9091004

910-
---
911-
912-
#### `GET /api/v1/audit`
1005+
`parquet_presigned_url` is `None` once `ttl_expires_at` has elapsed
1006+
(default 24h, controlled by `FLYQUERY_RESULT_TTL_HOURS`) — the consumer
1007+
must rerun the query to materialise a fresh URL. A presign-time failure
1008+
(object reclaimed, backend transient outage) also collapses the URL to
1009+
`None` while still returning the inline preview.
9131010

914-
Paginated audit events. Optional: `event_type`, `actor`, `date_from`, `date_to`.
1011+
Source: `queries_controller.py:124` (get_query_result).
9151012

9161013
---
9171014

9181015
#### `GET /api/v1/billing`
9191016

920-
Cost rollup by period.
1017+
Aggregates `flyquery_cost_events` into day / week / month buckets
1018+
scoped to the caller's workspace. Consume
1019+
[`GET /api/v1/cost-events`](#cost-events) for the raw per-call ledger;
1020+
this endpoint just rolls it up.
9211021

922-
**Query params**: `period` (day|week|month), `date_from`, `date_to`.
1022+
**Query params**: `period` = `day` (default) | `week` | `month`,
1023+
`date_from` (inclusive), `date_to` (exclusive).
1024+
1025+
Returns `BillingRollup` with the split `ingest_cost_cents` /
1026+
`query_cost_cents` / `other_cost_cents` per bucket plus the
1027+
`total_cost_cents` sum. Buckets with zero cost are omitted (no empty
1028+
days). An invalid `period` returns 400 `invalid_request`.
9231029

924-
**Response**
9251030
```json
9261031
{
1032+
"period": "day",
1033+
"date_from": "2026-05-01T00:00:00Z",
1034+
"date_to": "2026-05-24T00:00:00Z",
9271035
"total_cost_cents": 14230,
9281036
"breakdown": [
929-
{"date": "2026-05-23", "ingest_cost_cents": 200, "query_cost_cents": 14030}
1037+
{
1038+
"date": "2026-05-23T00:00:00Z",
1039+
"ingest_cost_cents": 200,
1040+
"query_cost_cents": 14030,
1041+
"other_cost_cents": 0,
1042+
"total_cost_cents": 14230
1043+
}
9301044
]
9311045
}
9321046
```
9331047

1048+
See [billing.md](billing.md) and [cost-tracking.md](cost-tracking.md)
1049+
for the cost model and which call sites currently write to the ledger.
1050+
Scope: `flyquery.billing:read`.
1051+
1052+
Source: `billing_controller.py:32` (rollup).
1053+
9341054
---
9351055

9361056
#### `GET /api/v1/stats`
9371057

938-
Workspace usage summary: `storage_used_bytes`, `dataset_count`, `table_count`,
939-
`query_count_last_30d`, `token_count_last_30d`.
1058+
Compact workspace summary, returned as `WorkspaceStats`. Six fields:
1059+
1060+
```json
1061+
{
1062+
"storage_used_bytes": 524288000,
1063+
"dataset_count": 5,
1064+
"table_count": 23,
1065+
"query_count_last_30d": 312,
1066+
"token_count_last_30d": 412380,
1067+
"ingest_job_count_pending": 0
1068+
}
1069+
```
1070+
1071+
Operator traffic, not hot-path — backed by a small set of `COUNT(*)`
1072+
queries on demand without caching. See [stats.md](stats.md) for field
1073+
semantics. Scope: `flyquery.billing:read` (reused).
1074+
1075+
Source: `stats_controller.py:32` (workspace_summary).
9401076

9411077
---
9421078

@@ -946,6 +1082,27 @@ Returns `{version, git_sha, build_time}`.
9461082

9471083
---
9481084

1085+
#### Health probes — `GET /actuator/health` and siblings
1086+
1087+
Pyfly's actuator is enabled and exposes Spring-Boot-style health
1088+
probes. **Use these instead of ad-hoc `/healthz` / `/readyz` paths**:
1089+
1090+
| Path | Purpose |
1091+
|---|---|
1092+
| `GET /actuator/health` | Aggregate of every registered `HealthIndicator`. Returns `{"status": "UP" \| "DOWN"}`. |
1093+
| `GET /actuator/health/liveness` | Kubernetes liveness probe (process alive?). |
1094+
| `GET /actuator/health/readiness` | Kubernetes readiness probe (deps reachable?). |
1095+
| `GET /actuator/info` | `{"app": {"name", "version", "description"}}` from `pyfly.yaml`. |
1096+
| `GET /actuator/metrics` | Prometheus-compatible counters. |
1097+
| `GET /actuator/env` | Runtime configuration view (gate behind auth in production). |
1098+
| `GET /admin/*` | Full admin UI surface for development. |
1099+
1100+
These are **not** under `/api/v1/*` (deliberate — they're operator
1101+
endpoints, not tenant-scoped business endpoints) and therefore don't
1102+
require `X-Tenant-Id` / `X-Workspace-Id`.
1103+
1104+
---
1105+
9491106
### 5.13 Agent tokens
9501107

9511108
Operator-only. Creates long-lived bearer tokens for agent-tier access.

0 commit comments

Comments
 (0)