@query-farm/vgi 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3174,7 +3174,7 @@ var arrowStateSerializer = {
3174
3174
  };
3175
3175
 
3176
3176
  // src/protocol/dispatch.ts
3177
- import { Protocol } from "@query-farm/vgi-rpc";
3177
+ import { Protocol as Protocol2 } from "@query-farm/vgi-rpc";
3178
3178
 
3179
3179
  // src/util/statistics.ts
3180
3180
  function serializeColumnStatistics(stats, _cacheMaxAgeSeconds) {
@@ -4955,6 +4955,8 @@ var ATTACH_AAD_PREFIX = _UTF8.encode("vgi.attach_opaque_data.v1\x00");
4955
4955
  var TRANSACTION_AAD_PREFIX = _UTF8.encode("vgi.transaction_opaque_data.v1\x00");
4956
4956
 
4957
4957
  class OpaqueDataRejectedError extends Error {
4958
+ errorCode = "INVALID_ARGUMENT";
4959
+ errorKind = "opaque_data_not_recognized";
4958
4960
  constructor(field = "opaque data") {
4959
4961
  super(`${field} not recognized`);
4960
4962
  this.name = "OpaqueDataRejectedError";
@@ -4990,8 +4992,11 @@ function attachAad(auth) {
4990
4992
  function transactionAad(auth, attachEnvelope) {
4991
4993
  return concat(TRANSACTION_AAD_PREFIX, identityTail(auth), Uint8Array.of(0), attachEnvelope);
4992
4994
  }
4993
- async function sealBytes(payload, key, aad, version) {
4994
- const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LEN));
4995
+ async function sealBytes(payload, key, aad, version, fixedNonce) {
4996
+ if (fixedNonce !== undefined && fixedNonce.length !== NONCE_LEN) {
4997
+ throw new Error(`nonce must be ${NONCE_LEN} bytes`);
4998
+ }
4999
+ const nonce = fixedNonce ?? crypto.getRandomValues(new Uint8Array(NONCE_LEN));
4995
5000
  const ciphertext = xchacha20poly1305(await normalizeKey(key), nonce, aad).encrypt(payload);
4996
5001
  const out = new Uint8Array(1 + NONCE_LEN + ciphertext.length);
4997
5002
  out[0] = version & 255;
@@ -6408,6 +6413,479 @@ var encodeCopyFromFormatInfo = (v) => encodeASD(CopyFromFormatInfoSchema, {
6408
6413
  ordered: v.ordered ?? false
6409
6414
  });
6410
6415
 
6416
+ // src/attach-ticket.ts
6417
+ import {
6418
+ Protocol,
6419
+ badRequest,
6420
+ errorInfo,
6421
+ preconditionFailure
6422
+ } from "@query-farm/vgi-rpc";
6423
+
6424
+ // src/catalog/attach-option.ts
6425
+ var RESERVED_ATTACH_OPTION = "vgi_attach_ticket";
6426
+ function assertNoReservedAttachOption(specs) {
6427
+ for (const spec of specs) {
6428
+ if (spec.name.toLowerCase() === RESERVED_ATTACH_OPTION) {
6429
+ throw new Error(`Attach option '${spec.name}' uses the reserved name '${RESERVED_ATTACH_OPTION}': the framework ` + "reads it as an attach ticket before any catalog code runs. Rename the option.");
6430
+ }
6431
+ }
6432
+ }
6433
+ var SPEC_SCHEMA = schema2([
6434
+ field2("name", utf82(), false),
6435
+ field2("description", utf82(), false),
6436
+ field2("type", binary2(), false),
6437
+ field2("default_value", binary2(), true),
6438
+ field2("required", bool2(), true),
6439
+ field2("secret", bool2(), true)
6440
+ ]);
6441
+ function deserializeAttachOptionSpec(bytes) {
6442
+ const row = [...iterRows(deserializeBatch(bytes))][0];
6443
+ if (row === undefined) {
6444
+ throw new Error("Cannot deserialize AttachOptionSpec from an empty batch");
6445
+ }
6446
+ const typeSchema = deserializeSchema(toUint8Array(row.type));
6447
+ const type = typeSchema.fields[0].type;
6448
+ let value;
6449
+ const defaultBytes = row.default_value;
6450
+ if (defaultBytes != null) {
6451
+ const asBytes = toUint8Array(defaultBytes);
6452
+ if (asBytes.length > 0) {
6453
+ const defaultRow = [...iterRows(deserializeBatch(asBytes))][0];
6454
+ value = defaultRow?.value;
6455
+ }
6456
+ }
6457
+ return {
6458
+ name: String(row.name),
6459
+ description: String(row.description ?? ""),
6460
+ type,
6461
+ ...value === undefined || value === null ? {} : { default: value },
6462
+ required: row.required === true,
6463
+ secret: row.secret === true
6464
+ };
6465
+ }
6466
+ function deserializeAttachOptionSpecs(specs) {
6467
+ return Array.from(specs, deserializeAttachOptionSpec);
6468
+ }
6469
+ class ReservedAttachOptionError extends Error {
6470
+ constructor(message) {
6471
+ super(message);
6472
+ this.name = "ReservedAttachOptionError";
6473
+ }
6474
+ }
6475
+ async function checkReservedAttachOptions(catalog) {
6476
+ if (!catalog?.catalogsInfo)
6477
+ return;
6478
+ let infos;
6479
+ try {
6480
+ infos = await catalog.catalogsInfo();
6481
+ } catch (e) {
6482
+ if (e instanceof Error && e.message.includes(`reserved name '${RESERVED_ATTACH_OPTION}'`)) {
6483
+ throw new ReservedAttachOptionError(e.message);
6484
+ }
6485
+ return;
6486
+ }
6487
+ for (const info of infos ?? []) {
6488
+ for (const raw of info.attach_option_specs ?? []) {
6489
+ let name;
6490
+ try {
6491
+ name = deserializeAttachOptionSpec(toUint8Array(raw)).name;
6492
+ } catch {
6493
+ continue;
6494
+ }
6495
+ try {
6496
+ assertNoReservedAttachOption([{ name }]);
6497
+ } catch (e) {
6498
+ throw new ReservedAttachOptionError(e.message);
6499
+ }
6500
+ }
6501
+ }
6502
+ }
6503
+
6504
+ // src/attach-ticket.ts
6505
+ var ATTACH_TICKET_PREFIX = "vgia1.";
6506
+ var ATTACH_TICKET_OPTION = RESERVED_ATTACH_OPTION;
6507
+ var ATTACH_TICKETS_PROTOCOL_NAME = "vgi.attach_tickets.v1";
6508
+ var ATTACH_TICKETS_PROTOCOL_VERSION = "1.0.0";
6509
+ var TICKET_ENVELOPE_VERSION = 1;
6510
+ var MAX_OPTIONS_BYTES = 16 * 1024;
6511
+ var MAX_TICKET_CHARS = 32 * 1024;
6512
+ var CLOCK_SKEW_SECONDS = 60;
6513
+ var UTF82 = new TextEncoder;
6514
+ var STRICT_UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
6515
+ var TICKET_AAD_DOMAIN = UTF82.encode("vgi.attach_ticket.v1\x00");
6516
+ var TICKET_ID = /^[0-9a-f]{32}$/;
6517
+ var B64URL = /^[A-Za-z0-9_-]+$/;
6518
+ var MAX_TEXT = 65535;
6519
+
6520
+ class AttachTicketInvalidError extends Error {
6521
+ static errorKind = "attach_ticket_invalid";
6522
+ errorKind = "attach_ticket_invalid";
6523
+ errorCode = "INVALID_ARGUMENT";
6524
+ errorDetails;
6525
+ constructor(detail = "attach ticket not accepted") {
6526
+ super(detail);
6527
+ this.name = "AttachTicketInvalidError";
6528
+ this.errorDetails = [badRequest([{ field: ATTACH_TICKET_OPTION, description: detail }])];
6529
+ }
6530
+ }
6531
+
6532
+ class AttachTicketExpiredError extends Error {
6533
+ static errorKind = "attach_ticket_expired";
6534
+ errorKind = "attach_ticket_expired";
6535
+ errorCode = "FAILED_PRECONDITION";
6536
+ errorDetails;
6537
+ constructor(detail = "attach ticket has expired") {
6538
+ super(detail);
6539
+ this.name = "AttachTicketExpiredError";
6540
+ this.errorDetails = [
6541
+ preconditionFailure([{ type: "ATTACH_TICKET", subject: ATTACH_TICKET_OPTION, description: detail }])
6542
+ ];
6543
+ }
6544
+ }
6545
+
6546
+ class AttachTicketRequestError extends Error {
6547
+ errorKind = "invalid_request";
6548
+ errorCode = "INVALID_ARGUMENT";
6549
+ errorDetails;
6550
+ constructor(message, violations) {
6551
+ super(message);
6552
+ this.name = "AttachTicketRequestError";
6553
+ this.errorDetails = [
6554
+ badRequest(violations.map(([f, description]) => ({ field: f, description })))
6555
+ ];
6556
+ }
6557
+ }
6558
+
6559
+ class SealAttachDeniedError extends Error {
6560
+ errorKind = "action_denied";
6561
+ errorCode = "PERMISSION_DENIED";
6562
+ errorDetails = [errorInfo({ action: "seal_attach" })];
6563
+ constructor(message) {
6564
+ super(message);
6565
+ this.name = "SealAttachDeniedError";
6566
+ }
6567
+ }
6568
+ function attachTicketAad(principal) {
6569
+ const p = UTF82.encode(principal);
6570
+ const out = new Uint8Array(TICKET_AAD_DOMAIN.length + p.length);
6571
+ out.set(TICKET_AAD_DOMAIN, 0);
6572
+ out.set(p, TICKET_AAD_DOMAIN.length);
6573
+ return out;
6574
+ }
6575
+ function packText(value, name) {
6576
+ const raw = UTF82.encode(value);
6577
+ if (raw.length > MAX_TEXT)
6578
+ throw new Error(`${name} is longer than 65535 bytes`);
6579
+ const out = new Uint8Array(2 + raw.length);
6580
+ new DataView(out.buffer).setUint16(0, raw.length, true);
6581
+ out.set(raw, 2);
6582
+ return out;
6583
+ }
6584
+ function encodePayload(c) {
6585
+ if (c.optionsIpc.length > MAX_OPTIONS_BYTES) {
6586
+ throw new Error(`options are ${c.optionsIpc.length} bytes; a ticket carries at most ${MAX_OPTIONS_BYTES}`);
6587
+ }
6588
+ const head = new Uint8Array(16);
6589
+ const hv = new DataView(head.buffer);
6590
+ hv.setBigInt64(0, BigInt(c.issuedAt), true);
6591
+ hv.setBigInt64(8, BigInt(c.expiresAt), true);
6592
+ const optLen = new Uint8Array(4);
6593
+ new DataView(optLen.buffer).setUint32(0, c.optionsIpc.length, true);
6594
+ const parts = [
6595
+ head,
6596
+ packText(c.ticketId, "ticket_id"),
6597
+ packText(c.catalogName, "catalog_name"),
6598
+ packText(c.dataVersionSpec, "data_version_spec"),
6599
+ packText(c.implementationVersion, "implementation_version"),
6600
+ optLen,
6601
+ c.optionsIpc
6602
+ ];
6603
+ let total = 0;
6604
+ for (const p of parts)
6605
+ total += p.length;
6606
+ const out = new Uint8Array(total);
6607
+ let off = 0;
6608
+ for (const p of parts) {
6609
+ out.set(p, off);
6610
+ off += p.length;
6611
+ }
6612
+ return out;
6613
+ }
6614
+ function decodePayload(payload) {
6615
+ const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
6616
+ let pos = 0;
6617
+ const need = (n) => {
6618
+ if (pos + n > payload.length)
6619
+ throw new AttachTicketInvalidError("attach ticket payload is truncated");
6620
+ };
6621
+ const text = () => {
6622
+ need(2);
6623
+ const len = view.getUint16(pos, true);
6624
+ pos += 2;
6625
+ need(len);
6626
+ const raw = payload.subarray(pos, pos + len);
6627
+ pos += len;
6628
+ try {
6629
+ return STRICT_UTF8.decode(raw);
6630
+ } catch {
6631
+ throw new AttachTicketInvalidError("attach ticket payload is not UTF-8");
6632
+ }
6633
+ };
6634
+ need(16);
6635
+ const issuedAt = Number(view.getBigInt64(0, true));
6636
+ const expiresAt = Number(view.getBigInt64(8, true));
6637
+ pos = 16;
6638
+ const ticketId = text();
6639
+ const catalogName = text();
6640
+ const dataVersionSpec = text();
6641
+ const implementationVersion = text();
6642
+ need(4);
6643
+ const optionsLen = view.getUint32(pos, true);
6644
+ pos += 4;
6645
+ if (optionsLen > MAX_OPTIONS_BYTES)
6646
+ throw new AttachTicketInvalidError("attach ticket options exceed 16 KiB");
6647
+ need(optionsLen);
6648
+ const optionsIpc = payload.slice(pos, pos + optionsLen);
6649
+ pos += optionsLen;
6650
+ if (pos !== payload.length)
6651
+ throw new AttachTicketInvalidError("attach ticket payload has trailing bytes");
6652
+ if (!TICKET_ID.test(ticketId))
6653
+ throw new AttachTicketInvalidError("attach ticket id is not 32 lowercase hex");
6654
+ if (!catalogName)
6655
+ throw new AttachTicketInvalidError("attach ticket names no catalog");
6656
+ if (expiresAt !== 0 && expiresAt <= issuedAt)
6657
+ throw new AttachTicketInvalidError("attach ticket lifetime is empty");
6658
+ return { issuedAt, expiresAt, ticketId, catalogName, dataVersionSpec, implementationVersion, optionsIpc };
6659
+ }
6660
+ function b64url(data) {
6661
+ let bin = "";
6662
+ for (let i = 0;i < data.length; i++)
6663
+ bin += String.fromCharCode(data[i]);
6664
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
6665
+ }
6666
+ function b64urlStrict(text) {
6667
+ if (!B64URL.test(text) || text.length % 4 === 1) {
6668
+ throw new AttachTicketInvalidError("attach ticket is not unpadded base64url");
6669
+ }
6670
+ const padded = text.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - text.length % 4) % 4);
6671
+ const bin = atob(padded);
6672
+ const raw = new Uint8Array(bin.length);
6673
+ for (let i = 0;i < bin.length; i++)
6674
+ raw[i] = bin.charCodeAt(i);
6675
+ if (b64url(raw) !== text)
6676
+ throw new AttachTicketInvalidError("attach ticket is not canonical base64url");
6677
+ return raw;
6678
+ }
6679
+ function randomHex(bytes) {
6680
+ const b = crypto.getRandomValues(new Uint8Array(bytes));
6681
+ let s = "";
6682
+ for (const x of b)
6683
+ s += x.toString(16).padStart(2, "0");
6684
+ return s;
6685
+ }
6686
+ async function mintAttachTicket(signingKey, opts) {
6687
+ if (!opts.principal)
6688
+ throw new Error("a ticket needs a principal");
6689
+ if (!opts.catalogName)
6690
+ throw new Error("a ticket needs a catalog name");
6691
+ const claims = {
6692
+ issuedAt: Math.trunc(opts.issuedAt),
6693
+ expiresAt: Math.trunc(opts.expiresAt),
6694
+ ticketId: opts.ticketId ?? randomHex(16),
6695
+ catalogName: opts.catalogName,
6696
+ dataVersionSpec: opts.dataVersionSpec ?? "",
6697
+ implementationVersion: opts.implementationVersion ?? "",
6698
+ optionsIpc: opts.optionsIpc
6699
+ };
6700
+ if (!TICKET_ID.test(claims.ticketId))
6701
+ throw new Error("ticket_id must be 32 lowercase hex");
6702
+ if (claims.expiresAt !== 0 && claims.expiresAt <= claims.issuedAt) {
6703
+ throw new Error("expires_at must be 0 or after issued_at");
6704
+ }
6705
+ const envelope = await sealBytes(encodePayload(claims), signingKey, attachTicketAad(opts.principal), TICKET_ENVELOPE_VERSION, opts.nonce);
6706
+ const token = ATTACH_TICKET_PREFIX + b64url(envelope);
6707
+ if (token.length > MAX_TICKET_CHARS) {
6708
+ throw new Error(`the ticket would be ${token.length} characters; at most ${MAX_TICKET_CHARS} are accepted`);
6709
+ }
6710
+ return { token, claims };
6711
+ }
6712
+ async function openAttachTicket(signingKey, token, principal, now) {
6713
+ if (!token.startsWith(ATTACH_TICKET_PREFIX))
6714
+ throw new AttachTicketInvalidError("not an attach ticket");
6715
+ if (token.length > MAX_TICKET_CHARS)
6716
+ throw new AttachTicketInvalidError("attach ticket is too long");
6717
+ const envelope = b64urlStrict(token.slice(ATTACH_TICKET_PREFIX.length));
6718
+ if (!principal)
6719
+ throw new AttachTicketInvalidError("an anonymous caller cannot redeem an attach ticket");
6720
+ let payload;
6721
+ try {
6722
+ payload = await openBytes(envelope, signingKey, attachTicketAad(principal), TICKET_ENVELOPE_VERSION);
6723
+ } catch (e) {
6724
+ if (e instanceof OpaqueDataRejectedError) {
6725
+ throw new AttachTicketInvalidError("attach ticket failed verification");
6726
+ }
6727
+ throw e;
6728
+ }
6729
+ const claims = decodePayload(payload);
6730
+ const current = now ?? Date.now() / 1000;
6731
+ if (claims.issuedAt > current + CLOCK_SKEW_SECONDS)
6732
+ throw new AttachTicketExpiredError("attach ticket is not yet valid");
6733
+ if (claims.expiresAt !== 0 && current >= claims.expiresAt + CLOCK_SKEW_SECONDS) {
6734
+ throw new AttachTicketExpiredError("attach ticket has expired");
6735
+ }
6736
+ return claims;
6737
+ }
6738
+ function callerPrincipal(auth) {
6739
+ if (!auth || !auth.authenticated)
6740
+ return null;
6741
+ return auth.principal || null;
6742
+ }
6743
+ async function redeemAttachTicket(options, signingKey, auth, now) {
6744
+ const keys = Object.keys(options);
6745
+ const ticketKeys = keys.filter((k) => k.toLowerCase() === ATTACH_TICKET_OPTION);
6746
+ if (ticketKeys.length === 0)
6747
+ return null;
6748
+ const others = keys.filter((k) => k !== ticketKeys[0]);
6749
+ if (others.length > 0) {
6750
+ throw new AttachTicketRequestError(`${ATTACH_TICKET_OPTION} must be the only attach option`, others.map((k) => [`options.${k}`, `not allowed alongside ${ATTACH_TICKET_OPTION}`]));
6751
+ }
6752
+ const token = options[ticketKeys[0]];
6753
+ if (typeof token !== "string")
6754
+ throw new AttachTicketInvalidError(`${ATTACH_TICKET_OPTION} must be a string`);
6755
+ if (!signingKey)
6756
+ throw new AttachTicketInvalidError("this worker does not redeem attach tickets");
6757
+ const claims = await openAttachTicket(signingKey, token, callerPrincipal(auth), now);
6758
+ let restored = {};
6759
+ if (claims.optionsIpc.length > 0) {
6760
+ try {
6761
+ restored = decodeOptionsBatch(claims.optionsIpc);
6762
+ } catch {
6763
+ throw new AttachTicketInvalidError("attach ticket options are not an Arrow IPC record");
6764
+ }
6765
+ }
6766
+ return {
6767
+ name: claims.catalogName,
6768
+ optionsIpc: claims.optionsIpc.length > 0 ? claims.optionsIpc : null,
6769
+ options: restored,
6770
+ dataVersionSpec: claims.dataVersionSpec || null,
6771
+ implementationVersion: claims.implementationVersion || null
6772
+ };
6773
+ }
6774
+ var SEAL_ATTACH_REQUEST_SCHEMA = schema2([
6775
+ field2("catalog_name", utf82(), false),
6776
+ field2("options", binary2(), true),
6777
+ field2("data_version_spec", utf82(), false),
6778
+ field2("implementation_version", utf82(), false),
6779
+ field2("ttl_seconds", int642(), false)
6780
+ ]);
6781
+ var ATTACH_TICKET_SCHEMA = schema2([
6782
+ field2("ticket", utf82(), false),
6783
+ field2("expires_at", float642(), false)
6784
+ ]);
6785
+ function resolveTicketMaxTtl(grantKeys, env) {
6786
+ if (grantKeys)
6787
+ return Math.trunc(grantKeys.maxTtlSeconds);
6788
+ const raw = (env?.VGI_RPC_GRANT_MAX_TTL_SECONDS ?? "").trim();
6789
+ if (!raw)
6790
+ return null;
6791
+ const value = /^\d+$/.test(raw) ? Number(raw) : 0;
6792
+ if (!(value > 0))
6793
+ throw new Error(`VGI_RPC_GRANT_MAX_TTL_SECONDS=${JSON.stringify(raw)} must be a positive integer`);
6794
+ return value;
6795
+ }
6796
+ async function declaredSpecs(catalog, catalogName) {
6797
+ if (!catalog)
6798
+ return null;
6799
+ const infos = catalog.catalogsInfo ? await catalog.catalogsInfo() : catalog.catalogs().map((name) => ({ name, attach_option_specs: [] }));
6800
+ for (const info of infos) {
6801
+ if (info.name === catalogName) {
6802
+ return deserializeAttachOptionSpecs((info.attach_option_specs ?? []).map((b) => toUint8Array(b)));
6803
+ }
6804
+ }
6805
+ return null;
6806
+ }
6807
+ async function sealAttach2(config, request, auth) {
6808
+ const principal = callerPrincipal(auth);
6809
+ if (principal === null)
6810
+ throw new SealAttachDeniedError("an anonymous caller cannot seal an attach ticket");
6811
+ const violations = [];
6812
+ const ttl = Number(request.ttl_seconds ?? 0);
6813
+ if (!Number.isFinite(ttl) || ttl < 0)
6814
+ violations.push(["ttl_seconds", "must be 0 (as long as allowed) or positive"]);
6815
+ const catalogName = String(request.catalog_name ?? "");
6816
+ let optionsIpc = new Uint8Array(0);
6817
+ let options = {};
6818
+ if (request.options != null) {
6819
+ const raw = toUint8Array(request.options);
6820
+ if (raw.length > 0) {
6821
+ const batch = deserializeBatch(raw);
6822
+ if (batch.numRows > 1) {
6823
+ violations.push(["options", "must be a one-row record"]);
6824
+ } else if (batch.numRows === 1) {
6825
+ options = decodeOptionsBatch(raw);
6826
+ optionsIpc = raw;
6827
+ }
6828
+ }
6829
+ }
6830
+ const specs = await declaredSpecs(config.catalogInterface, catalogName);
6831
+ if (specs === null) {
6832
+ violations.push(["catalog_name", `no catalog named ${JSON.stringify(catalogName)}`]);
6833
+ } else {
6834
+ const declared = new Set(specs.map((s) => s.name.toLowerCase()));
6835
+ for (const name of Object.keys(options)) {
6836
+ if (name.toLowerCase() === ATTACH_TICKET_OPTION) {
6837
+ violations.push([`options.${name}`, "a ticket cannot seal another ticket"]);
6838
+ } else if (!declared.has(name.toLowerCase())) {
6839
+ violations.push([`options.${name}`, "not an attach option this catalog declares"]);
6840
+ }
6841
+ }
6842
+ const supplied = new Set(Object.keys(options).map((n) => n.toLowerCase()));
6843
+ for (const spec of specs) {
6844
+ if (spec.required && !supplied.has(spec.name.toLowerCase()))
6845
+ violations.push([`options.${spec.name}`, "required"]);
6846
+ }
6847
+ }
6848
+ if (optionsIpc.length > MAX_OPTIONS_BYTES) {
6849
+ violations.push(["options", `${optionsIpc.length} bytes; a ticket carries at most ${MAX_OPTIONS_BYTES}`]);
6850
+ }
6851
+ if (violations.length > 0)
6852
+ throw new AttachTicketRequestError("seal_attach request is invalid", violations);
6853
+ const issuedAt = Math.trunc(config.now ? config.now() : Date.now() / 1000);
6854
+ const ceiling = config.maxTtlSeconds;
6855
+ const lifetime = ttl === 0 ? ceiling : ceiling === null ? Math.trunc(ttl) : Math.min(Math.trunc(ttl), ceiling);
6856
+ const expiresAt = lifetime === null ? 0 : issuedAt + lifetime;
6857
+ let token;
6858
+ try {
6859
+ ({ token } = await mintAttachTicket(config.signingKey, {
6860
+ principal,
6861
+ catalogName,
6862
+ optionsIpc,
6863
+ dataVersionSpec: String(request.data_version_spec ?? ""),
6864
+ implementationVersion: String(request.implementation_version ?? ""),
6865
+ issuedAt,
6866
+ expiresAt
6867
+ }));
6868
+ } catch (e) {
6869
+ throw new AttachTicketRequestError("seal_attach request is invalid", [["request", e.message]]);
6870
+ }
6871
+ return { ticket: token, expires_at: expiresAt === 0 ? Number.POSITIVE_INFINITY : expiresAt };
6872
+ }
6873
+ function buildAttachTicketsProtocol(config) {
6874
+ const p = new Protocol(ATTACH_TICKETS_PROTOCOL_NAME, { protocolVersion: ATTACH_TICKETS_PROTOCOL_VERSION });
6875
+ p.unary("seal_attach", {
6876
+ params: REQUEST_PARAMS_SCHEMA,
6877
+ result: RESULT_BINARY_SCHEMA,
6878
+ doc: "Seal the caller's attach of request.catalog_name into a ticket.",
6879
+ handler: async (params, ctx) => {
6880
+ const request = unwrapRequest(params.request);
6881
+ const sealed = await sealAttach2(config, request, ctx?.auth);
6882
+ const batch = batchFromColumns({ ticket: [sealed.ticket], expires_at: [sealed.expires_at] }, ATTACH_TICKET_SCHEMA);
6883
+ return { result: serializeBatch(batch) };
6884
+ }
6885
+ });
6886
+ return p;
6887
+ }
6888
+
6411
6889
  // src/protocol/handlers/catalog/admin.ts
6412
6890
  function registerCatalogAdminMethods(protocol, getCatalog, signingKey) {
6413
6891
  catalogUnary(protocol, signingKey, "catalog_catalogs", {
@@ -6432,7 +6910,15 @@ function registerCatalogAdminMethods(protocol, getCatalog, signingKey) {
6432
6910
  handler: async (params, ctx) => {
6433
6911
  const innerParams = unwrapRequest(params.request);
6434
6912
  const cat = getCatalog();
6435
- const optionsDict = decodeOptionsBatch(innerParams.options);
6913
+ let optionsDict = decodeOptionsBatch(innerParams.options);
6914
+ const restored = await redeemAttachTicket(optionsDict, signingKey, ctx?.auth ?? currentRequestAuth());
6915
+ if (restored !== null) {
6916
+ innerParams.name = restored.name;
6917
+ innerParams.options = restored.optionsIpc;
6918
+ innerParams.data_version_spec = restored.dataVersionSpec;
6919
+ innerParams.implementation_version = restored.implementationVersion;
6920
+ optionsDict = restored.options;
6921
+ }
6436
6922
  const result = await cat.attach(innerParams.name, optionsDict, innerParams.data_version_spec ?? null, innerParams.implementation_version ?? null);
6437
6923
  const sealedAttach = await sealAttach(toUint8Array(result.attach_opaque_data), ctx?.auth, signingKey);
6438
6924
  return wrapResult(buildCatalogAttachResult({
@@ -6885,10 +7371,42 @@ function registerCatalogMethods(protocol, catalog, _catalogName, signingKey) {
6885
7371
  registerCatalogIndexMethods(protocol, getCatalog, signingKey);
6886
7372
  }
6887
7373
 
7374
+ // src/protocol/handlers/unimplemented.ts
7375
+ import { MethodNotImplementedError } from "@query-farm/vgi-rpc";
7376
+ var UNIMPLEMENTED_METHODS = [
7377
+ { name: "aggregate_streaming_open", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
7378
+ { name: "aggregate_streaming_chunk", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
7379
+ { name: "aggregate_streaming_close", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
7380
+ { name: "aggregate_window_init", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
7381
+ { name: "aggregate_window", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
7382
+ { name: "aggregate_window_batch", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
7383
+ { name: "aggregate_window_destructor", params: REQUEST_PARAMS_SCHEMA, result: RESULT_BINARY_SCHEMA },
7384
+ { name: "catalog_index_create", params: CatalogIndexCreateParamsSchema, result: emptyResultSchema },
7385
+ { name: "catalog_index_drop", params: CatalogIndexDropParamsSchema, result: emptyResultSchema },
7386
+ { name: "catalog_table_column_comment_set", params: CatalogTableColumnCommentSetParamsSchema, result: emptyResultSchema },
7387
+ { name: "catalog_table_insert_function_get", params: CatalogTableInsertFunctionGetParamsSchema, result: RESULT_BINARY_SCHEMA },
7388
+ { name: "catalog_table_update_function_get", params: CatalogTableUpdateFunctionGetParamsSchema, result: RESULT_BINARY_SCHEMA },
7389
+ { name: "catalog_table_delete_function_get", params: CatalogTableDeleteFunctionGetParamsSchema, result: RESULT_BINARY_SCHEMA }
7390
+ ];
7391
+ function unimplementedMessage(method) {
7392
+ return `${method} is not implemented by this worker`;
7393
+ }
7394
+ function registerUnimplementedMethods(protocol) {
7395
+ for (const m of UNIMPLEMENTED_METHODS) {
7396
+ protocol.unary(m.name, {
7397
+ params: m.params,
7398
+ result: m.result,
7399
+ handler: async () => {
7400
+ throw new MethodNotImplementedError(unimplementedMessage(m.name));
7401
+ }
7402
+ });
7403
+ }
7404
+ }
7405
+
6888
7406
  // src/protocol/dispatch.ts
6889
7407
  var VGI_PROTOCOL_NAME = "vgi.v2";
6890
7408
  function buildVgiProtocol(config) {
6891
- const protocol = new Protocol(VGI_PROTOCOL_NAME, { protocolVersion: "2.1.0" });
7409
+ const protocol = new Protocol2(VGI_PROTOCOL_NAME, { protocolVersion: "2.1.0" });
6892
7410
  registerFunctionMethods(protocol, {
6893
7411
  registry: config.registry,
6894
7412
  signingKey: config.signingKey,
@@ -6897,6 +7415,7 @@ function buildVgiProtocol(config) {
6897
7415
  registerAggregateMethods(protocol, config.registry);
6898
7416
  registerTableBufferingMethods(protocol, config.registry, config.signingKey);
6899
7417
  registerCatalogMethods(protocol, config.catalogInterface, config.catalogName, config.signingKey);
7418
+ registerUnimplementedMethods(protocol);
6900
7419
  return protocol;
6901
7420
  }
6902
7421
 
@@ -7018,7 +7537,15 @@ function buildRpcServer(primary, hosting, options) {
7018
7537
  throw new Error(`Unknown transport ${JSON.stringify(options.transport)}; expected one of ${[...TRANSPORTS].join(", ")}.`);
7019
7538
  }
7020
7539
  const protocols = validatedHostedProtocols(hosting, primary);
7021
- const identity = IDENTITY_TRANSPORTS.has(options.transport) ? buildIdentity(hosting, options.env) : undefined;
7540
+ let identity;
7541
+ if (IDENTITY_TRANSPORTS.has(options.transport)) {
7542
+ const env = options.env ?? processEnv();
7543
+ const grantKeys = hosting.grantKeys === undefined ? GrantKeys.fromEnv(env) : hosting.grantKeys;
7544
+ identity = buildIdentity(hosting, grantKeys, options.env);
7545
+ const tickets = buildAttachTickets(hosting, grantKeys, options.attachTickets, env);
7546
+ if (tickets)
7547
+ protocols.push(tickets);
7548
+ }
7022
7549
  return new VgiRpcServer(primary, {
7023
7550
  serverId: options.serverId,
7024
7551
  protocols,
@@ -7064,9 +7591,8 @@ function validatedHostedProtocols(hosting, primary) {
7064
7591
  }
7065
7592
  return out;
7066
7593
  }
7067
- function buildIdentity(hosting, env) {
7594
+ function buildIdentity(hosting, grantKeys, env) {
7068
7595
  const { resolveToken, mintGrant } = hosting;
7069
- const grantKeys = hosting.grantKeys === undefined ? GrantKeys.fromEnv(env ?? processEnv()) : hosting.grantKeys;
7070
7596
  if (!resolveToken && !mintGrant && !grantKeys)
7071
7597
  return;
7072
7598
  return new IdentityImpl({
@@ -7077,6 +7603,17 @@ function buildIdentity(hosting, env) {
7077
7603
  maxAuthAge: hosting.maxAuthAge
7078
7604
  });
7079
7605
  }
7606
+ function buildAttachTickets(hosting, grantKeys, config, env) {
7607
+ if (!config || !config.signingKeyConfigured || !config.signingKey)
7608
+ return;
7609
+ if (!grantKeys && !hosting.mintGrant)
7610
+ return;
7611
+ return buildAttachTicketsProtocol({
7612
+ signingKey: config.signingKey,
7613
+ catalogInterface: config.catalogInterface,
7614
+ maxTtlSeconds: resolveTicketMaxTtl(grantKeys, env)
7615
+ });
7616
+ }
7080
7617
  function resolveIntrospectPrincipals(explicit, env) {
7081
7618
  let principals;
7082
7619
  if (explicit !== undefined) {
@@ -7152,7 +7689,17 @@ function createVgiFetch(opts) {
7152
7689
  signingKey: opts.signingKey,
7153
7690
  ...opts.protocol
7154
7691
  });
7155
- const server = buildRpcServer(protocol, opts, { transport: "http", serverId });
7692
+ const server = buildRpcServer(protocol, opts, {
7693
+ transport: "http",
7694
+ serverId,
7695
+ attachTickets: {
7696
+ signingKey: opts.signingKey,
7697
+ signingKeyConfigured: opts.signingKeyConfigured ?? true,
7698
+ catalogInterface: opts.protocol.catalogInterface
7699
+ }
7700
+ });
7701
+ const reservedCheck = checkReservedAttachOptions(opts.protocol.catalogInterface);
7702
+ reservedCheck.catch((e) => console.error(`VGI worker refused to serve: ${e.message}`));
7156
7703
  const handler = createHttpHandler(server, {
7157
7704
  prefix,
7158
7705
  serverId,
@@ -7170,6 +7717,7 @@ function createVgiFetch(opts) {
7170
7717
  enableLandingPage: false
7171
7718
  });
7172
7719
  return async (req) => {
7720
+ await reservedCheck;
7173
7721
  const storage = await ensureRequestAuthStorage();
7174
7722
  if (!storage)
7175
7723
  return handler(req);
@@ -7236,6 +7784,7 @@ function resolveTokenTtl(explicit, env) {
7236
7784
  function createVgiWorkerFetch(opts) {
7237
7785
  const env = opts.env ?? process.env;
7238
7786
  const signingKey = resolveSigningKey(opts.signingKey, env);
7787
+ const signingKeyConfigured = opts.signingKey !== undefined || !!env.VGI_SIGNING_KEY?.trim();
7239
7788
  const tokenTtl = resolveTokenTtl(opts.tokenTtl, env);
7240
7789
  const corsOrigins = opts.corsOrigins === null ? null : opts.corsOrigins ?? env.CORS_ORIGINS;
7241
7790
  const bridge = opts.irohBridge;
@@ -7246,6 +7795,7 @@ function createVgiWorkerFetch(opts) {
7246
7795
  return createVgiFetch({
7247
7796
  protocol: { registry: opts.registry, catalogInterface: opts.catalogInterface },
7248
7797
  signingKey,
7798
+ signingKeyConfigured,
7249
7799
  tokenTtl,
7250
7800
  prefix: opts.prefix ?? "",
7251
7801
  serverId: opts.serverId ?? opts.name,
@@ -7309,4 +7859,4 @@ export {
7309
7859
  serveVgiWorker
7310
7860
  };
7311
7861
 
7312
- //# debugId=9A6DC12C2CA522B864756E2164756E21
7862
+ //# debugId=6B882607A7F5F68164756E2164756E21