Skip to content

Commit e4aa440

Browse files
rustyconoverclaude
andcommitted
docs(design): reporting protocols, SQL tasks, SQL binding, and a resumable build plan
The reporting-protocol design had lived only in an untracked directory and in one long session, so nothing an agent could resume from was in the repo. This commits it, with the plan that makes it resumable: - Seven protocol specs: vgi.reports.v1, vgi.report_render.v1, vgi.schedules.v1, vgi.sql_tasks.v1 (new: scheduled SQL that changes data: load with replace/append/merge/snapshot, scripts in one transaction with CALL as a statement, derived or stored watermarks, task health and transition notifications), vgi.alerts.v1, vgi.notify.v1, and vgi.delegations.v1 (renamed from held_attachments). - A normative SQL binding: every protocol is also a schema in the host's VGI catalog, derived from the RPC definition by a generic adapter plus a small per-protocol annotation, never hand-written, so the two surfaces cannot drift. - IMPLEMENTATION.md: phases P1-P10 in dependency order with deliverables and acceptance criteria, a status table, the decisions log, and the working rules (EC2 builds, CI-gated releases, test-dir and no_error_skip for integration lanes). The README's old plan now points at it. - scripts/build_reporting_protocols_html.py renders the single-page HTML (uv run --script; dependencies inline). Every .md in the directory must be listed, and bare file links resolve to each file's H1 anchor, so a new spec can't silently drop off the page. mkdocs build --strict passes; the design pages are outside the nav. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent aeb037b commit e4aa440

13 files changed

Lines changed: 6319 additions & 0 deletions
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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

Comments
 (0)