Skip to content

Commit e6a3a11

Browse files
rustyconoverclaude
andcommitted
feat: attach tickets (vgi.attach_tickets.v1), the reference implementation
A ticket seals a user's ATTACH -- catalog name, options (secret ones included), version specs -- so a runner holding that user's grant can reattach later without ever seeing an option. - Format: "vgia1." + canonical unpadded base64url of the attach-envelope construction (XChaCha20-Poly1305 under VGI_SIGNING_KEY, version 0x01), AAD "vgi.attach_ticket.v1\0" + principal. Principal only, not (domain, principal): sealed under the login domain, opened under "grant". Fixed little-endian payload; options are the opaque Arrow IPC bytes of CatalogAttachRequest.options, capped at 16 KiB. Verification: prefix, length, canonical base64url, open under the caller's principal, strict parse, lifetime with 60 s skew. - vgi.attach_tickets.v1 seal_attach: hosted on HTTP only when the signing key is configured explicitly (a key vgi-serve minted, incl. one exported to pre-fork children, never counts) and the worker can issue grants. Anonymous -> action_denied; options validated against the catalog's declared AttachOptionSpecs -> invalid_request + BadRequest; lifetime capped at the grant maximum, 0 = as long as allowed. - catalog_attach redeems vgi_attach_ticket before any catalog code: no other option beside it (invalid_request), attach_ticket_invalid / attach_ticket_expired, then the sealed request runs as typed. MetaWorker redeems before routing, so the sealed catalog picks the child. vgi_attach_ticket is a reserved attach-option name. - Cross-SDK: attach_ticket_vectors.json (mint/accept/reject/redeem, generated by scripts/gen_attach_ticket_vectors.py, drift-checked), the ticket_probe fixture catalog (region + secret required api_key, table main.probe reveals region and sha256(api_key)[:12]) in both fixture workers, and the normative spec docs/protocol/vgi-attach-tickets.md. The fixture HTTP server's test bearers now count as fresh logins and let vgig1. bearers fall through to grants. Mutation checks (each turns tests/test_attach_ticket.py red): principal dropped from the AAD (18 fail), (domain, principal) AAD (18), expiry check removed (2), no-other-options rule removed (6). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 1d50004 commit e6a3a11

16 files changed

Lines changed: 3111 additions & 12 deletions

‎CLAUDE.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,20 @@ one. Allowlist rules are unchanged. See `docs/authentication.md` and
503503
`tests/test_grant_auth.py`, which also runs the shared
504504
`grant_token_vectors.json` through `create_app`.
505505

506+
### Attach Tickets (`vgi.attach_tickets.v1`)
507+
508+
`seal_attach` seals a user's ATTACH (catalog name, options incl. secrets,
509+
version specs) into a `vgia1.` ticket under `VGI_SIGNING_KEY`, AAD
510+
`"vgi.attach_ticket.v1\0" + principal` (principal only, not domain). A runner
511+
holding the user's grant reattaches with the single option `vgi_attach_ticket`;
512+
`Worker.catalog_attach` (and `MetaWorker`, before routing) redeems it before any
513+
catalog code. Hosted on HTTP only, only with an explicitly configured signing key
514+
(`signing_key_configured` in `build_rpc_server`; a minted key never counts) and
515+
the ability to issue grants. `vgi_attach_ticket` is a reserved attach-option
516+
name. Code: `vgi/attach_ticket.py`; spec: `docs/protocol/vgi-attach-tickets.md`;
517+
vectors: `vgi/_test_fixtures/attach_ticket_vectors.json` (regenerate with
518+
`scripts/gen_attach_ticket_vectors.py`); fixture catalog: `ticket_probe`.
519+
506520
### Hosting Additional Protocols (`hosted_protocols`)
507521

508522
Every transport (stdio, unix/named pipe, TCP, Iroh, HTTP) builds its `RpcServer`

‎docs/api/worker.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ Identity stays framework-owned. You enable it by overriding
8686
only on HTTP, which authenticates callers. Its introspection allowlist names principals, and the
8787
other transports have no principals to check.
8888

89+
`vgi.attach_tickets.v1` is framework-owned too. It is hosted on HTTP only, and only when
90+
`VGI_SIGNING_KEY` is configured explicitly and the worker can issue grants (grant keys, or its own
91+
`mint_grant`). See [Attach tickets](../protocol/vgi-attach-tickets.md).
92+
8993
## Worker
9094

9195
::: vgi.worker
@@ -97,3 +101,7 @@ other transports have no principals to check.
97101
## Serving
98102

99103
::: vgi.serve
104+
105+
## Attach tickets
106+
107+
::: vgi.attach_ticket

‎docs/protocol/vgi-attach-tickets.md‎

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

‎mkdocs.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ nav:
149149
- "DuckDB filter adapter (informative)": protocol/vgi-duckdb-filter-adapter.md
150150
- "Catalog query pushdown (proposed design)": protocol/vgi-catalog-query-pushdown-design.md
151151
- "DuckDB 2.0 design audit (informative)": protocol/vgi-protocol-proposed-changes.md
152+
- "Attach tickets (normative)": protocol/vgi-attach-tickets.md
152153
- API Reference:
153154
- api/index.md
154155
- Functions: api/functions.md

0 commit comments

Comments
 (0)