|
| 1 | +# Implementing the reporting protocols |
| 2 | + |
| 3 | +The build plan for the protocols in this directory, written so an agent with no |
| 4 | +other context can pick up where the last one stopped. Read this file, then the |
| 5 | +[README](README.md), [credentials.md](credentials.md) and the spec of the phase |
| 6 | +you're on. **Update the status table when a phase lands**, in the same commit. |
| 7 | + |
| 8 | +## Status |
| 9 | + |
| 10 | +| Phase | Status | Landed in | |
| 11 | +| --- | --- | --- | |
| 12 | +| P0 Specs committed, HTML builder in the repo | done | this commit | |
| 13 | +| P1 Unlimited default grant lifetime | not started | | |
| 14 | +| P2 SQL-binding framework (vgi-python) | not started | | |
| 15 | +| P3 `vgi.reports.v1` | not started | | |
| 16 | +| P4 `vgi.delegations.v1` | not started | | |
| 17 | +| P5 Run engine and `vgi.schedules.v1` (`run_query`) | not started | | |
| 18 | +| P6 `vgi.sql_tasks.v1` | not started | | |
| 19 | +| P7 `vgi.notify.v1` reference and deliveries | not started | | |
| 20 | +| P8 `vgi.alerts.v1` | not started | | |
| 21 | +| P9 `vgi.report_render.v1` and `render_report` | not started | | |
| 22 | +| P10 Cupola client | not started (can start after P3) | | |
| 23 | + |
| 24 | +Already shipped and not part of this plan: multi-protocol hosting, the gRPC |
| 25 | +error model, `Identity.v1`, grants as bearers, attach tickets |
| 26 | +(`vgi.attach_tickets.v1`, `vgi_export_session()`, the `attach_ticket` ATTACH |
| 27 | +option), full `vgi.v2` parity across the seven SDKs. |
| 28 | + |
| 29 | +## Decisions |
| 30 | + |
| 31 | +Settled; don't reopen them without the user. The README's Decisions table has |
| 32 | +the protocol-level ones; these are the build-level ones and late changes. |
| 33 | + |
| 34 | +| # | Decision | |
| 35 | +| --- | --- | |
| 36 | +| D1 | The protocol is the unit of optionality; every method is required. | |
| 37 | +| D2 | `vgi.delegations.v1` (not "held attachments"): `Delegation`, `put_delegations`, `list_delegations`, `revoke_delegation`. | |
| 38 | +| D3 | `vgi.sql_tasks.v1` is its own protocol on the shared run engine, for SQL that changes data. Both destination kinds (`catalog`, `host`) and both watermark kinds (`derived`, `stored`). Scripts run statements in one transaction by default; `CALL` is a statement. | |
| 39 | +| D4 | `vgi.schedules.v1`'s `run_query` stays as specified (it may write). The overlap with SQL tasks is accepted. | |
| 40 | +| D5 | The delegation field stays `grant`. DuckDB accepts it unquoted (the extension's `attach_ticket/round_trip.test` does `SELECT grant FROM …`). | |
| 41 | +| D6 | The SQL binding is normative and **derived** from the RPC definition by a generic adapter plus a per-protocol annotation; never hand-written ([README](README.md#sql-binding)). | |
| 42 | +| D7 | Protocol-table writes are refused inside an explicit multi-statement transaction (each row is an RPC that `ROLLBACK` can't undo). Revisit only if a real use needs it. | |
| 43 | +| D8 | Specs stay in `docs/design/reporting-protocols/` until each protocol ships; a shipped protocol's spec then moves to `docs/protocol/`. | |
| 44 | +| D9 | These are service protocols. The reference implementation is Python; other SDKs implement one only when someone wants to host it natively. The extension tests the SQL bindings cross-language. | |
| 45 | +| D10 | Build order is by dependency, P1 to P9. Each phase ends usable, with conformance over both surfaces. | |
| 46 | + |
| 47 | +## Working rules |
| 48 | + |
| 49 | +**Where work happens** |
| 50 | + |
| 51 | +- Build and test on the EC2 host `ec2-user@13.217.25.2` in a directory you |
| 52 | + create and own, not the shared Mac. Debug builds, no LTO, at most 8 jobs. |
| 53 | + Kill only processes you started, by PID; never a broad `pkill`. |
| 54 | +- Never edit `~/Development/vgi` or `~/Development/vgi-typescript` in place; |
| 55 | + use a git worktree. vgi-python's main checkout may hold the user's |
| 56 | + uncommitted files: never stage what you didn't write. |
| 57 | +- Implement, never skip: missing SDK functionality is ported, not excluded |
| 58 | + from a cross-SDK test. |
| 59 | + |
| 60 | +**Testing** |
| 61 | + |
| 62 | +- vgi-python: `uv run ruff check --fix . && uv run ruff format . && uv run mypy vgi/`, |
| 63 | + then `uv run pytest -n auto`. |
| 64 | +- Feature tests that cross languages go in the extension as sqllogictests |
| 65 | + (`~/Development/vgi/test/sql/integration/<protocol>/`). Every local |
| 66 | + `unittest` run passes `--test-dir <tree>` (otherwise it silently runs its |
| 67 | + compiled-in tree) and `--test-config test/configs/no_error_skip.json` |
| 68 | + (otherwise HTTP-lane errors become skips). |
| 69 | +- Anything a test starts in the background, the test stops: record PIDs in the |
| 70 | + parent shell, never inside `$( … )`. |
| 71 | + |
| 72 | +**Commits and releases** |
| 73 | + |
| 74 | +- Agents commit on a branch and report; they don't push or tag. |
| 75 | +- Docs and script-only changes go straight to `main`, no PR. |
| 76 | +- A package release is CI-gated: push the release commit to `main`, wait for |
| 77 | + **every** push-triggered workflow on that SHA to succeed (zero runs is a |
| 78 | + failure: check the repo name from `git remote`), then create the GitHub |
| 79 | + release or push the tag, whichever that repo publishes on. Retry GitHub 5xx. |
| 80 | +- Commit messages are findings-style (what was wrong or missing, how it was |
| 81 | + found, what changed, how it was verified) and end with the session's |
| 82 | + `Co-Authored-By` trailer. |
| 83 | + |
| 84 | +## Phases |
| 85 | + |
| 86 | +### P0 Specs committed (done) |
| 87 | + |
| 88 | +`docs/design/reporting-protocols/` committed, with |
| 89 | +`scripts/build_reporting_protocols_html.py`, which regenerates |
| 90 | +`reporting-protocols.html` from the markdown. Rerun it after editing a spec. |
| 91 | + |
| 92 | +### P1 Unlimited default grant lifetime |
| 93 | + |
| 94 | +The design says a grant's lifetime is the issuing worker's policy and unlimited |
| 95 | +unless set. vgi-rpc still defaults to 7 days (`DEFAULT_MAX_TTL_SECONDS` in |
| 96 | +`vgi_rpc/grants.py`), so every unattended job would stop a week after its |
| 97 | +owner's last login. |
| 98 | + |
| 99 | +- Decide the wire form of "no expiry" in the grant spec first (an absent or |
| 100 | + zero `exp`), with test vectors. |
| 101 | +- Unset `VGI_RPC_GRANT_MAX_TTL_SECONDS` means no maximum, in vgi-rpc and all |
| 102 | + six ports. Attach tickets follow the worker's grant maximum, so check the |
| 103 | + SDKs' ticket expiry the same way. |
| 104 | +- **Done when** the shared vectors pass in every port, `expires_at` is `+inf` |
| 105 | + (or `NULL` in `vgi_export_session()`) for an unlimited worker, and the ports |
| 106 | + are released. |
| 107 | + |
| 108 | +P1 doesn't block P2 to P4; development can set an explicit TTL meanwhile. |
| 109 | + |
| 110 | +### P2 SQL-binding framework (vgi-python) |
| 111 | + |
| 112 | +The generic adapter that serves a hosted RPC protocol as a VGI catalog schema, |
| 113 | +built before any protocol so each ships both surfaces. |
| 114 | + |
| 115 | +- Protocol classes for every protocol: Arrow-typed records and the `Protocol` |
| 116 | + class, plus the SQL annotation (tables, get methods for fetched columns, DML |
| 117 | + methods). Proposed home `vgi/reporting/`; adjust if the codebase suggests |
| 118 | + better. |
| 119 | +- The adapter, implementing the [README's derivation rules](README.md#derivation-rules) |
| 120 | + 1 to 9 and the semantics (conflict, `request_id`, autocommit, error text). |
| 121 | +- `scripts/regen_generated.py --check` renders each spec's SQL-binding table |
| 122 | + from the annotations. |
| 123 | +- **Done when** each derivation rule and semantic has a test against a toy |
| 124 | + protocol, and a protocol with an annotation that names a missing method or a |
| 125 | + column the row lacks fails at startup. |
| 126 | + |
| 127 | +### P3 `vgi.reports.v1` |
| 128 | + |
| 129 | +- The store on `FunctionStorage`, `AccessPolicy` (by principal: grant callers |
| 130 | + have no claims), `ReadOnlyReportStore`, `vgi-report-serve`. |
| 131 | +- Pin every row schema in [reports.md](reports.md) exactly; the SQL binding |
| 132 | + makes them a contract. |
| 133 | +- A conformance suite that runs against any implementation over RPC and over |
| 134 | + SQL. |
| 135 | +- Extension sqllogictests `test/sql/integration/reports/` against the Python |
| 136 | + service. |
| 137 | +- **Done when** the conformance suite passes over both surfaces and Cupola |
| 138 | + could save, list, publish and read history with no localStorage. |
| 139 | + |
| 140 | +### P4 `vgi.delegations.v1` |
| 141 | + |
| 142 | +- `put_delegations`, `list_delegations`, `revoke_delegation`; grants and tickets |
| 143 | + encrypted under a key outside the store, never returned. |
| 144 | +- **Done when** this round trip passes as a sqllogictest: `vgi_export_session()`, |
| 145 | + `INSERT INTO … delegations BY NAME …`, then a runner reattaches each source |
| 146 | + with `ATTACH … (bearer_token, attach_ticket)` from the stored delegation and |
| 147 | + reads data, and `revoke_delegation` makes the next reattach fail. |
| 148 | + |
| 149 | +### P5 Run engine and `vgi.schedules.v1` |
| 150 | + |
| 151 | +- The shared engine from [schedules.md](schedules.md#reference-scheduler-non-normative): |
| 152 | + `tick` / `advance_run`, `vgi.scheduler_driver.v1` (allowlisted), leases, |
| 153 | + idempotent steps, hardened per-run sessions built from delegations. |
| 154 | +- `vgi.schedules.v1` with `run_query`; deliveries wait for P7. |
| 155 | +- **Done when** a schedule runs as its owner from delegations, runs are unique |
| 156 | + on `(schedule_id, scheduled_for)` across two replicas, overlap is `skipped`, |
| 157 | + and a missing delegation is `grant_required`. |
| 158 | + |
| 159 | +### P6 `vgi.sql_tasks.v1` |
| 160 | + |
| 161 | +- `load` (four modes), `script` (three transaction kinds), both watermark |
| 162 | + kinds, `lookback`, the `host` store (DuckLake by default), health and |
| 163 | + transition notifications (deliveries once P7 lands). |
| 164 | +- **Done when** `derived` loads exactly once across a crash injected between |
| 165 | + write and commit, `stored` replays at most the last window, a script that |
| 166 | + writes two databases in `single` fails with `transaction_failed`, and |
| 167 | + `test_run` leaves the target untouched. |
| 168 | + |
| 169 | +### P7 `vgi.notify.v1` reference and deliveries |
| 170 | + |
| 171 | +- A reference service with a recording fake channel and a webhook channel; |
| 172 | + destination policy and suppression list. Wire schedule deliveries and task |
| 173 | + notifications to it. The production service is the separate `vgi-notify` |
| 174 | + repo, later. |
| 175 | + |
| 176 | +### P8 `vgi.alerts.v1` |
| 177 | + |
| 178 | +- Rules, instances and states, notifications on transitions, acknowledge, |
| 179 | + snooze, subscriptions, on the shared engine. Pin the instance row schema |
| 180 | + (it's loose in [alerts.md](alerts.md) today). |
| 181 | + |
| 182 | +### P9 `vgi.report_render.v1` and `render_report` |
| 183 | + |
| 184 | +- Depends on a headless Cupola renderer. Then enable the `render_report` |
| 185 | + schedule action. |
| 186 | + |
| 187 | +### P10 Cupola client |
| 188 | + |
| 189 | +- Starts after P3: reports client and migration off localStorage, then |
| 190 | + delegation on save and renewal on login (P4), schedule and task editors (P5, |
| 191 | + P6), "Alert me when…" (P8). TypeScript types and clients come from codegen. |
| 192 | + |
| 193 | +## Known gaps to close while building |
| 194 | + |
| 195 | +- Row schemas are prose in places ("instances with state and times"). Each |
| 196 | + phase pins its protocol's rows; the regen check keeps the spec in step. |
| 197 | +- `get_*_info` limits (sizes, minimum intervals) have no values yet; choose |
| 198 | + them in the phase and record them in the spec. |
0 commit comments