Skip to content

Commit c172bcb

Browse files
feat: bqaa-revalidate-extractors CLI for local revalidation (#75 follow-up to C2.d) (#150)
* feat: bqaa-revalidate-extractors CLI for local revalidation (issue #75 follow-up to C2.d) Operationalizes revalidate_compiled_extractors so ops can run periodic revalidation without writing Python. **Local inputs only** this round; --events-bq-query is a deliberate follow-up so the auth/location/pagination surface stays isolated from the operational-loop contract. Public surface (src/bigquery_agent_analytics/extractor_compilation/cli_revalidate.py): * main(argv) — argparse-driven entry. Wired through pyproject.toml [project.scripts] as ``bqaa-revalidate-extractors``. * Flags: --bundles-root, --events-jsonl, --reference-extractors-module, --thresholds-json (optional), --report-out. * Exit codes: 0 pass / 1 threshold violation (report still written) / 2 usage-or-input error (report not written). Reference module contract: the dotted-path module passed to --reference-extractors-module must expose EXTRACTORS: dict[str, Callable[[dict, Any], StructuredExtractionResult]] RESOLVED_GRAPH: ResolvedGraph # from resolve(ontology, binding) SPEC: Any = None # optional The CLI deliberately carries no ontology / binding flags because the reference module is the operational contract that defined both the event_type-to-callable mapping AND the spec they validate against. One module, one contract. Implementation notes: * Fingerprint is auto-detected from the first bundle's manifest; mixed fingerprints under one --bundles-root fail-closed at exit 2 (a deployment mistake for revalidation). * JSONL parsing is strict — a malformed line aborts with exit 2 naming the line number. The harness's per-event skipped_events path is for legitimately-shaped events without coverage, not corrupted input. * Reference-module shape is checked at the CLI boundary (missing EXTRACTORS / RESOLVED_GRAPH; non-dict EXTRACTORS; non-string-or-empty keys; non-callable values) so the harness never sees a malformed registry. * Threshold JSON is validated via the existing RevalidationThresholds.__post_init__ bounds check; unknown fields also rejected so a typo doesn't silently produce a no-op gate. * Report JSON shape: {"report": ..., "threshold_check": null | {"ok": bool, "violations": [str, ...]}}. Tests (tests/test_extractor_compilation_cli_revalidate.py, 15 cases): * TestCliEndToEnd (3): happy path (exit 0, threshold_check=null); threshold pass (exit 0, ok=True); threshold violation (exit 1, report still written with violations listed). All run against real BKA fixtures plus hand-built bundles whose compiled source either agrees with or drifts from the reference. * TestCliUsageErrors (11): missing events file; malformed JSONL line; missing bundles root; mixed-fingerprint bundles root; empty bundles root; reference module not importable; reference module missing EXTRACTORS; reference module missing RESOLVED_GRAPH; bad EXTRACTORS shape; thresholds JSON with unknown field; thresholds JSON with out-of-range rate. * test_console_script_entry_point_registered (1): locks the pyproject.toml entry so a typo fails CI rather than breaking the binary at user-install time. Docs: docs/extractor_compilation_revalidate_cli.md describes the contract, exit codes, report shape, and reference-module surface. docs/README.md index entry + CHANGELOG entry added. 189 related extractor_compilation tests pass. * fix(revalidate-cli): preflight --report-out + wrap I/O exceptions Addresses PR #150 round-1 reviewer findings. P1 - --report-out parent-directory preflight + write wrap. The docs promise usage/input errors exit 2 with no report, but ``--report-out missing/report.json`` used to raise FileNotFoundError out of ``_write_report``. Now: * ``_load_config`` preflight checks ``report_out.parent.is_dir()`` before doing any work; missing parent surfaces as a clean ``_CliError`` with a "create it before running" message. * ``_write_report`` wraps ``path.write_text(...)`` in try/except OSError so the late-failure cases (permissions, disk full, parent removed between preflight and write) also surface as ``_CliError`` rather than a traceback. P2 - I/O exception wrapping on --events-jsonl and --thresholds-json. The previous code only caught JSONDecodeError; OSError (permission denied, file removed between is_file check and open) and UnicodeError (invalid UTF-8 in a tampered or wrong-encoding file) leaked as raw tracebacks. Both readers now catch both exception types and surface them as ``_CliError`` with the file path named. Tests: 15 -> 18. Three new cases in ``TestCliUsageErrors``: * test_report_out_parent_missing_exits_two_cleanly: ``--report-out tmp_path/does-not-exist/report.json`` exits 2 with "does not exist" in the message; the missing parent dir is NOT created. * test_events_jsonl_invalid_utf8_exits_two_cleanly: binary bytes (0xff 0xfe ...) in the JSONL file exits 2 with "UTF-8" in the message; no report leaked. * test_thresholds_json_invalid_utf8_exits_two_cleanly: same defensive shape on the thresholds reader. Docs: ``--report-out`` flag description spells out the preflight + late-failure-wrap behavior; ``TestCliUsageErrors`` test count bumped to 14 plus three new lines for the new cases. * fix(revalidate-cli): route argparse usage errors through _CliError Addresses PR #150 round-2 reviewer finding. P2 - argparse's default ArgumentParser.error() writes the usage line + error message to stderr and then calls sys.exit(2), which bypasses main()'s documented "returns an exit code" contract. Tests calling ``main([])`` would catch a raised SystemExit instead of receiving the documented ``EXIT_USAGE_ERROR`` return value. Fix: subclass argparse.ArgumentParser as ``_NonExitingArgumentParser`` whose ``error()`` raises ``_CliError`` instead of calling sys.exit. The usage line is still printed to stderr first (matches argparse's default UX); main() catches ``_CliError`` from ``parse_args()`` through the same boundary that handles every other usage failure and returns ``EXIT_USAGE_ERROR``. ``exit()`` is NOT overridden, so ``--help`` and ``--version`` still terminate via ``SystemExit(0)`` — that's the expected terminal behavior for those flags. The override only catches argument errors that argparse would otherwise route through ``sys.exit(2)``. Tests: 18 -> 20. Two new cases in ``TestCliUsageErrors``: * test_missing_required_flag_returns_two_not_systemexit: ``main([])`` returns 2 (no raised SystemExit); stderr contains the standard "the following arguments are required" wording plus the usage line. * test_unrecognized_flag_returns_two_not_systemexit: all required flags supplied plus a bogus ``--no-such-flag`` returns 2 with "unrecognized arguments" in stderr. Docs: exit-code table calls out that ``main(argv)`` returns ``EXIT_USAGE_ERROR`` rather than raising on bad flags; ``--help`` / ``--version`` still ``SystemExit(0)`` behavior is documented as intentional. * docs(revalidate-cli): drop stale --version claims from docstrings + exit-code table PR #150 round-3 P3 cleanup. The CLI's main() docstring, _NonExitingArgumentParser docstring, and the exit-code table in the doc page all claimed ``--version`` exited via SystemExit(0) — but the parser doesn't actually define a ``--version`` action. ``main(["--version"])`` falls through to the missing-required-args path and returns 2. Fix: remove the ``--version`` claims to match reality. The CLI inherits ``--help`` from argparse for free; ``--version`` is deliberately deferred until the package has a stable version-emission strategy (the existing pyproject.toml ``version`` field would need wiring through ``importlib.metadata.version("bigquery-agent-analytics")``, which is scope creep beyond the reviewer's actual finding). No code changes; no test changes. All 19 CLI tests + 1 entry-point skip still pass. --------- Co-authored-by: Haiyuan Cao <haiyuan@google.com>
1 parent e3edb3d commit c172bcb

6 files changed

Lines changed: 1714 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- **``bqaa-revalidate-extractors`` CLI** in
13+
`bigquery_agent_analytics.extractor_compilation.cli_revalidate`
14+
and
15+
[`docs/extractor_compilation_revalidate_cli.md`](docs/extractor_compilation_revalidate_cli.md).
16+
Issue [#75](https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/issues/75)
17+
follow-up to Milestone C2.d — operationalizes
18+
``revalidate_compiled_extractors`` so ops can run periodic
19+
revalidation without writing Python. **Local inputs
20+
only** this round; ``--events-bq-query`` lands in a
21+
follow-up so the auth/location/pagination surface gets
22+
isolated from the operational-loop contract.
23+
Flags: ``--bundles-root`` (auto-detects the fingerprint
24+
from the first bundle's manifest; mixed fingerprints
25+
fail-closed), ``--events-jsonl`` (one event per line,
26+
malformed lines abort with line number), ``--reference-
27+
extractors-module`` (dotted path; module exposes
28+
``EXTRACTORS: dict[str, callable]``, ``RESOLVED_GRAPH``
29+
from ``resolve(ontology, binding)``, and optionally
30+
``SPEC`` — the CLI carries no ontology/binding flags
31+
because the reference module owns the validator-input
32+
contract), ``--thresholds-json`` (optional; JSON object
33+
with any subset of ``RevalidationThresholds`` fields,
34+
bounds-checked via the existing ``__post_init__``),
35+
``--report-out`` (combined JSON of the raw
36+
``RevalidationReport`` plus the ``ThresholdCheckResult``).
37+
Exit codes are deliberately narrow: ``0`` pass / ``1``
38+
threshold violation (report still written) / ``2``
39+
usage-or-input error (report not written). Wired through
40+
``pyproject.toml [project.scripts]`` so
41+
``pip install`` exposes the binary.
1242
- **BigQuery-table bundle mirror** in
1343
`bigquery_agent_analytics.extractor_compilation.bq_bundle_mirror`
1444
and

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ architecture, rationale, and implementation plans behind key SDK features.
5252
| [extractor_compilation_runtime_registry.md](extractor_compilation_runtime_registry.md) | Runtime extractor-registry adapter (issue #75 PR C2.c.1): `build_runtime_extractor_registry(...)` glues C2.a's `discover_bundles` + C2.b's `run_with_fallback` into one call, returning a `WrappedRegistry` with an `extractors` dict ready for `run_structured_extractors` plus `bundles_without_fallback` (compiled-only, skipped) and `fallbacks_without_bundle` (no usable compiled registry entry — "never built" *and* "rejected by discovery"; cross-reference `discovery.failures` for the reason). Compiled-only event_types are skipped and recorded (fail-closed); fallback-only event_types pass through unchanged. Non-callable fallbacks are rejected at build time with `TypeError` naming the event_type. The `on_outcome(event_type, outcome)` callback fires on every wrapped invocation (denominator metric); callback exceptions propagate. Out of scope: actual orchestrator call-site swap (C2.c.2), BQ mirror (C2.c.3), revalidation (C2.d). |
5353
| [extractor_compilation_orchestrator_swap.md](extractor_compilation_orchestrator_swap.md) | Orchestrator call-site swap (issue #75 PR C2.c.2): `OntologyGraphManager.from_bundles_root(...)` classmethod that builds the runtime registry internally and constructs a manager whose `extractors` dict is the wrapped registry, so existing `run_structured_extractors` calls inside `extract_graph` pick up compiled-with-fallback behavior with no other code changes. Adds `manager.runtime_registry: WrappedRegistry | None` audit handle (non-None when bundle-wired). Mirrors `from_ontology_binding` arg shape; existing `__init__` and `from_ontology_binding` paths are unchanged. Compiled-only event_types without a matching fallback are NOT registered (fail-closed). Out of scope: BQ mirror (C2.c.3), revalidation (C2.d). |
5454
| [extractor_compilation_bq_bundle_mirror.md](extractor_compilation_bq_bundle_mirror.md) | BigQuery-table bundle mirror (issue #75 PR C2.c.3): `publish_bundles_to_bq(bundle_root, store, ...)` + `sync_bundles_from_bq(store, dest_dir, ...)`. Mirror is a publish/sync utility, NOT a runtime loader — the runtime path stays `sync_bundles_from_bq → discover_bundles → from_bundles_root`. Both functions call `load_bundle` as a gate: publish refuses bundles that wouldn't load at the runtime; sync writes to a side-by-side **staging directory** and `load_bundle`-validates the staged copy before performing a **staged replace** of the target (the rmtree+move pair is not strictly atomic — a crash between the two leaves the bundle absent on disk and is recoverable by re-sync — but the load-bundle-failure direction *is* atomic, so a bad mirror row never destroys a previously-good local bundle). Strict bundle-shape (exactly `manifest.json` + the manifest's `module_filename`) plus shape-check on the manifest's `module_filename` (bare filename only — no separators, no `..`, no NUL; otherwise `manifest_row_unreadable`). Path-safety rejects traversal / absolute / backslash / NUL. `duplicate_fingerprint` rejects publish-side cases where two subdirs claim the same fingerprint (neither published). `duplicate_row` rejects two rows sharing the same `(fingerprint, bundle_path)` at sync. `malformed_row` shape check. Idempotent republish via DELETE+INSERT in `BigQueryBundleStore.publish_rows` (NOT a single atomic transaction; a transient INSERT failure is recoverable by re-running publish). `publish_rows` raises `ValueError` on duplicate input pairs as defense in depth. `BundleStore` Protocol for testability; `BigQueryBundleStore` is the concrete impl. Stable `MirrorFailure` codes; per-bundle problems accumulate, store exceptions propagate. Out of scope: GCS signed URLs, caching, garbage collection, multi-region. |
55+
| [extractor_compilation_revalidate_cli.md](extractor_compilation_revalidate_cli.md) | `bqaa-revalidate-extractors` CLI (Phase C operationalization): one-shot binary that wraps `revalidate_compiled_extractors` for local inputs. Flags: `--bundles-root`, `--events-jsonl`, `--reference-extractors-module`, `--thresholds-json` (optional), `--report-out`. Reference module exposes `EXTRACTORS` dict + `RESOLVED_GRAPH` (+ optional `SPEC`) so the CLI doesn't need ontology/binding flags. Fingerprint auto-detected from the first bundle's manifest; mixed fingerprints fail-closed. Exit codes: `0` pass / `1` threshold violation / `2` usage-or-input error. Report JSON includes both the raw `RevalidationReport` and the `ThresholdCheckResult`. Out of scope: `--events-bq-query` (follow-up PR), scheduled execution, BQ persistence. |
5556
| [extractor_compilation_revalidation.md](extractor_compilation_revalidation.md) | Revalidation harness (issue #75 PR C2.d): `revalidate_compiled_extractors(events, compiled_extractors, reference_extractors, resolved_graph, ...)` drives `run_with_fallback` (with a no-op fallback) over a batch of events AND calls the reference extractor directly, aggregating outcomes into a `RevalidationReport` with **two orthogonal dimensions**: runtime decision (`compiled_unchanged` / `compiled_filtered` / `fallback_for_event`, plus `compiled_path_faults` split out so bundle bugs are distinguishable from ontology drift) and agreement against reference (`parity_match` / `parity_divergence` / `parity_not_checked`). Parity uses three comparators: `_compare_nodes` and `_compare_span_handling` from `measurement.py` plus `_compare_edges` in `revalidation.py` (same edge_id set with matching relationship_name / endpoints / property-set per shared edge; duplicate edge_ids on either side reported as a divergence rather than silently collapsed by dict keying). The parity dimension catches **schema-valid but semantically wrong** outputs the schema-only check would miss. **Every failure mode on the reference side becomes a parity divergence, never a batch abort**: exceptions, non-`StructuredExtractionResult` returns (including `None`), and comparator crashes all funnel into the divergence channel with a descriptive string. `check_thresholds(report, RevalidationThresholds(...))` evaluates policy gates; threshold rates are validated to `[0, 1]` at construction so a typo like `=5` (intended as 5%) fails loud. JSON-serializable for persistence; deterministic. Out of scope: scheduled orchestration, BQ persistence, CLI, sampling strategy. |
5657

5758
## Deployment Surfaces
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Compiled Structured Extractors — `bqaa-revalidate-extractors` CLI
2+
3+
**Status:** Implemented (Phase C operationalization, follow-up to issue #75 Milestone C2.d)
4+
**Parent epic:** [issue #75](https://github.com/GoogleCloudPlatform/BigQuery-Agent-Analytics-SDK/issues/75)
5+
**Builds on:** [`extractor_compilation_revalidation.md`](extractor_compilation_revalidation.md), [`extractor_compilation_bundle_loader.md`](extractor_compilation_bundle_loader.md)
6+
7+
---
8+
9+
## What this is
10+
11+
A one-shot CLI binary that runs `revalidate_compiled_extractors` against local inputs so operators can periodically check the compiled extractor path without writing Python. This first PR keeps the input surface deliberately small — **local inputs only**. A follow-up adds `--events-bq-query` once the CLI contract is stable; that path drags in auth / location / pagination / error handling and is worth isolating.
12+
13+
## Usage
14+
15+
```bash
16+
bqaa-revalidate-extractors \
17+
--bundles-root /var/bqaa/synced-bundles \
18+
--events-jsonl events.jsonl \
19+
--reference-extractors-module my_project.references \
20+
--thresholds-json thresholds.json \
21+
--report-out report.json
22+
```
23+
24+
## Flags
25+
26+
| Flag | Required | Description |
27+
|------|----------|-------------|
28+
| `--bundles-root` | yes | Directory containing one subdirectory per compiled bundle (the layout `discover_bundles` walks). Fingerprint is **auto-detected** from the first bundle's manifest; every other bundle must declare the same fingerprint or sync fails with exit 2. |
29+
| `--events-jsonl` | yes | Path to a JSONL file (one event JSON object per line). Empty lines are skipped; malformed lines abort with exit 2 naming the line number. |
30+
| `--reference-extractors-module` | yes | Dotted Python path to a module exposing the reference-module contract below. |
31+
| `--thresholds-json` | no | Optional JSON file mapping `RevalidationThresholds` field names to numeric rates in `[0, 1]`. When omitted, no threshold check is performed and exit is 0 on a successful run. |
32+
| `--report-out` | yes | Path to write the combined JSON report. Parent directories are NOT created automatically; a missing parent directory fails at preflight with exit 2 before any work runs (no report written). Other write errors (permissions, disk full) also surface as clean exit 2. |
33+
34+
## Reference module contract
35+
36+
The dotted-path module passed to `--reference-extractors-module` must expose, at module scope:
37+
38+
```python
39+
EXTRACTORS: dict[str, Callable[[dict, Any], StructuredExtractionResult]]
40+
RESOLVED_GRAPH: ResolvedGraph # output of resolve(ontology, binding)
41+
SPEC: Any = None # optional; forwarded to extractor calls
42+
```
43+
44+
- **`EXTRACTORS`** — same shape `revalidate_compiled_extractors` accepts (event_type → callable).
45+
- **`RESOLVED_GRAPH`** — the validator-input artifact. The CLI doesn't carry ontology / binding flags because the reference module is the operational contract that defined both the event_type-to-callable mapping AND the spec they validate against. One module, one contract.
46+
- **`SPEC`** — optional. Defaults to `None` to match the harness's keyword default.
47+
48+
A module missing either `EXTRACTORS` or `RESOLVED_GRAPH`, or with `EXTRACTORS` of the wrong shape, fails fast at the CLI boundary (exit 2) — the harness never sees a malformed registry.
49+
50+
## Exit codes
51+
52+
Intentionally narrow so cron / GitHub Actions can branch on them:
53+
54+
| Code | Meaning |
55+
|------|---------|
56+
| `0` | Revalidation completed; if thresholds were supplied, every threshold passed. |
57+
| `1` | Revalidation completed but at least one threshold was violated. The report JSON is still written; the caller inspects `threshold_check.violations`. |
58+
| `2` | Usage / load / input error: bad flags (missing required, unrecognized), missing files, malformed JSONL, missing reference module surface, mixed-fingerprint bundle root, threshold validation failure, etc. The report is **not** written. `main(argv)` *returns* this code rather than raising `SystemExit` (argparse's own `error()` is routed through the same `_CliError` boundary). `--help` still terminates via `SystemExit(0)` — that's the expected behavior. The CLI does not define a `--version` action today. |
59+
60+
## Report JSON shape
61+
62+
```json
63+
{
64+
"report": {
65+
"total_events": ...,
66+
"total_compiled_unchanged": ...,
67+
"total_compiled_filtered": ...,
68+
"total_fallback_for_event": ...,
69+
"total_compiled_path_faults": ...,
70+
"total_parity_matches": ...,
71+
"total_parity_divergences": ...,
72+
"total_parity_not_checked": ...,
73+
"skipped_events": ...,
74+
"counts_by_event_type": { ... },
75+
"sample_decision_divergences": [ ... ],
76+
"sample_parity_divergences": [ ... ],
77+
"started_at": "...",
78+
"finished_at": "..."
79+
},
80+
"threshold_check": null | {
81+
"ok": true|false,
82+
"violations": ["compiled_unchanged_rate 0.2500 < min 0.9500", ...]
83+
}
84+
}
85+
```
86+
87+
`threshold_check` is `null` when `--thresholds-json` wasn't supplied; the raw report is still written so an operator can inspect rates without committing to a gate.
88+
89+
## Thresholds JSON shape
90+
91+
Any subset of `RevalidationThresholds` fields, with numeric rates in `[0, 1]`:
92+
93+
```json
94+
{
95+
"min_compiled_unchanged_rate": 0.95,
96+
"max_compiled_filtered_rate": 0.05,
97+
"max_fallback_for_event_rate": 0.05,
98+
"max_compiled_path_fault_rate": 0.01,
99+
"min_parity_match_rate": 0.99
100+
}
101+
```
102+
103+
Unknown fields, out-of-range rates (`5.0` intended as 5%), NaN, and bool all fail at the CLI boundary with exit 2 — same `__post_init__` validation that `RevalidationThresholds` enforces in-process.
104+
105+
## What gets skipped
106+
107+
- **Events whose `event_type` isn't in `EXTRACTORS` or the compiled registry** land in `report.skipped_events`; they don't enter the rate denominators.
108+
- **Empty JSONL lines** are silently skipped; that's whitespace, not data.
109+
- **Malformed JSONL lines** are **not** skipped — they abort the run with exit 2 to distinguish corrupt input from legitimately-uncovered event_types.
110+
111+
## Tests
112+
113+
`tests/test_extractor_compilation_cli_revalidate.py` (20 cases):
114+
115+
- **`TestCliEndToEnd`** (3) — happy path (exit 0, report written, `threshold_check: null`); threshold pass (exit 0, `ok: true`); threshold violation (exit 1, report still written with violations listed).
116+
- **`TestCliUsageErrors`** (16) — missing events file; malformed JSONL line; missing bundles root; mixed-fingerprint bundle root; empty bundle root; reference module not importable; reference module missing `EXTRACTORS`; reference module missing `RESOLVED_GRAPH`; bad `EXTRACTORS` shape; thresholds JSON with unknown field; thresholds JSON with out-of-range rate; missing `--report-out` parent directory (preflight catches it before any work runs); invalid UTF-8 in `--events-jsonl`; invalid UTF-8 in `--thresholds-json`; **missing required flag returns 2 (not `SystemExit`)**; **unrecognized flag returns 2 (not `SystemExit`)** — argparse's default `error()` is overridden to route through `_CliError` so `main(argv)` reliably *returns* an exit code rather than raising `SystemExit` mid-call.
117+
- **`test_console_script_entry_point_registered`** (1) — locks the `pyproject.toml` `[project.scripts]` entry so a typo in the entry-point string fails CI rather than breaking the binary at user-install time.
118+
119+
## Out of scope (deferred)
120+
121+
- **`--events-bq-query`** — load events from a BigQuery query. Follow-up PR; brings auth + location + pagination + error handling.
122+
- **Scheduled execution** — operator owns cron / Cloud Scheduler / GitHub Actions; the CLI is a one-shot.
123+
- **BQ persistence of reports**`--report-out` writes a local file; pushing it elsewhere is the caller's concern.
124+
- **Multiple bundle roots** — one fingerprint per run; the harness is designed for "what's currently deployed."
125+
126+
## Related
127+
128+
- [`extractor_compilation_revalidation.md`](extractor_compilation_revalidation.md) — the underlying `revalidate_compiled_extractors` + `check_thresholds` API. The CLI is a thin operational wrapper around it.
129+
- [`extractor_compilation_bundle_loader.md`](extractor_compilation_bundle_loader.md)`discover_bundles` is what the CLI uses internally to load compiled extractors.
130+
- [`extractor_compilation_bq_bundle_mirror.md`](extractor_compilation_bq_bundle_mirror.md)`sync_bundles_from_bq` is the typical upstream of `--bundles-root` for Cloud-Run-style deployments.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ all = [
6262

6363
[project.scripts]
6464
bq-agent-sdk = "bigquery_agent_analytics.cli:main"
65+
bqaa-revalidate-extractors = "bigquery_agent_analytics.extractor_compilation.cli_revalidate:main"
6566
gm = "bigquery_ontology.cli:main"
6667

6768
[tool.hatch.build.targets.wheel]

0 commit comments

Comments
 (0)