@somewhere-cloud/cli 0.38.19 → 0.38.21

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.
@@ -67,11 +67,12 @@ var require_declared_data_contract = __commonJS({
67
67
  const normalizedOperations = o?.length === 4 ? void 0 : o;
68
68
  return { k: "m", m: { g: [...g], m, u, mg: [...mg], ...normalizedOperations !== void 0 ? { o: [...normalizedOperations] } : {}, ...og ? { og: true } : {} } };
69
69
  }
70
- if (value.k === "a" && keys === "k,p" && Array.isArray(value.p) && value.p.length === 2) {
70
+ if (value.k === "a" && (keys === "k,p" || keys === "k,p,reads" && edges === 0) && Array.isArray(value.p) && value.p.length === 2) {
71
71
  const left = policy(value.p[0], label), right = policy(value.p[1], label);
72
72
  if (left.k !== "o" || !["m", "g", "r"].includes(right.k)) fail(label);
73
73
  if (right.k === "m" && right.m.og && left.c !== "user_id") fail(label);
74
- return { k: "a", p: [left, right] };
74
+ const reads = value.reads === void 0 ? void 0 : branchReads(value.reads, label);
75
+ return { k: "a", p: [left, right], ...reads ? { reads } : {} };
75
76
  }
76
77
  if (value.k === "n" && keys === "g,k,w") {
77
78
  const gate = policy(value.g, label), who = policy(value.w, label);
@@ -99,6 +100,23 @@ var require_declared_data_contract = __commonJS({
99
100
  }
100
101
  fail(label);
101
102
  }
103
+ function branchReads(value, label) {
104
+ if (!Array.isArray(value) || value.length !== 2 || value[0] === null && value[1] === null) fail(label + ".reads");
105
+ return value.map((list) => {
106
+ if (list === null) return null;
107
+ if (!Array.isArray(list) || !list.length || list.some((name) => typeof name !== "string" || !identifier.test(name)) || new Set(list).size !== list.length) fail(label + ".reads");
108
+ return [...list].sort();
109
+ });
110
+ }
111
+ function hiddenByBranch(table) {
112
+ const reads = table.policy && table.policy.k === "a" ? table.policy.reads : void 0;
113
+ if (!reads) return /* @__PURE__ */ new Set();
114
+ const hidden = /* @__PURE__ */ new Set();
115
+ for (const list of reads) if (list) {
116
+ for (const column of table.columns) if (!list.includes(column.n)) hidden.add(column.n);
117
+ }
118
+ return hidden;
119
+ }
102
120
  function policyRoles(value, label) {
103
121
  record2(value, label + ".roles");
104
122
  if (Object.keys(value).sort().join(",") !== "create,delete,read,update") fail(label);
@@ -336,10 +354,10 @@ var require_declared_data_contract = __commonJS({
336
354
  const type = { integer: "number", bigint: "string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "Json", blob: "number[]" }[column.t];
337
355
  return column.nul ? `${type} | null` : type;
338
356
  }
339
- function shape(columns, names, mode, required = /* @__PURE__ */ new Set()) {
357
+ function shape(columns, names, mode, required = /* @__PURE__ */ new Set(), hidden = /* @__PURE__ */ new Set()) {
340
358
  const values = names.map((name) => {
341
359
  const column = columns.find((candidate) => candidate.n === name);
342
- const optional = mode === "update" || mode === "create" && !required.has(name) && (column.d || column.nul);
360
+ const optional = mode === "update" || mode === "create" && !required.has(name) && (column.d || column.nul) || mode === "read" && hidden.has(name);
343
361
  return `${JSON.stringify(name)}${optional ? "?" : ""}: ${columnType(column)}`;
344
362
  });
345
363
  return values.length ? `{ ${values.join("; ")} }` : "Record<string, never>";
@@ -503,13 +521,14 @@ async function intake(table, reference, values, extra) {
503
521
  export interface IntakeResult { readonly accepted: true; readonly idempotency: InsertReceipt; readonly request: InsertRequest<IntakeResult> }
504
522
  `;
505
523
  var ROW_CAN_DECLARATION = " /** Row capability hint (list/get with can: true): whether the signed-in caller may update or delete this row now, by the same rule the write enforces. A hint for UI; the write is still the final answer. */\n export interface RowCan { readonly update: boolean; readonly delete: boolean; }\n";
524
+ var ROW_HINTS_DECLARATION = ROW_CAN_DECLARATION + ' /** Row hints (list/get with can: true): can says what the signed-in caller may change; mine is true when this caller created the row (the owner or author the platform recorded). Show edit and delete controls, a "you" label or a "mine" filter from these; the identity column itself is never sent. */\n export interface RowHints { readonly can: RowCan; readonly mine: boolean; }\n';
506
525
  var INSERT_CLIENT_DECLARATIONS = `
507
526
  export interface InsertReceipt { readonly replayed: boolean; readonly created_at: number; readonly result_until: number; readonly refuse_until: number; }
508
527
  export interface InsertRequest<T> { readonly key: string; readonly createdAt: number; retry(): Promise<T>; }
509
528
  export type InsertResult<T> = T & { readonly idempotency: InsertReceipt; readonly request: InsertRequest<InsertResult<T>> };
510
529
  `;
511
- var CLIENT_GENERATION2 = 3;
512
- var ACCEPTED_CLIENT_GENERATIONS2 = Object.freeze([3, 2, 1]);
530
+ var CLIENT_GENERATION2 = 5;
531
+ var ACCEPTED_CLIENT_GENERATIONS2 = Object.freeze([5, 4, 3, 2, 1]);
513
532
  var PUBLIC_BOOTSTRAP_CLIENT_RUNTIME = 'const swCanon=(v,d)=>{if(v===null||typeof v==="string"||typeof v==="boolean"||(typeof v==="number"&&Number.isFinite(v)))return JSON.stringify(v);if(d===0||typeof v!=="object"||Array.isArray(v))return null;const parts=[];for(const k of Object.keys(v).sort()){if(v[k]===undefined)continue;const e=swCanon(v[k],d-1);if(e===null)return null;parts.push(JSON.stringify(k)+":"+e)}return"{"+parts.join(",")+"}"};let swBoot;const swBootTake=body=>{try{if(swBoot===undefined){swBoot=null;const el=typeof document!=="undefined"&&document.getElementById?document.getElementById("__sw_public_data"):null;if(el&&el.type==="application/json"&&(el.parentNode===document.head||el.parentNode===document.body)){const p=JSON.parse(el.textContent||"null");if(p&&p.v===1&&p.contract===contract&&Number.isSafeInteger(p.validated_at)&&p.validated_at<=Date.now()&&Number.isInteger(p.max_age)&&p.max_age>0&&p.max_age<=120&&p.entries&&typeof p.entries==="object"&&!Array.isArray(p.entries)&&typeof p.path==="string"&&typeof location!=="undefined")swBoot={until:p.validated_at+p.max_age*1000,path:p.path,entries:new Map(Object.entries(p.entries))}}if(el&&el.parentNode)el.parentNode.removeChild(el)}if(!swBoot)return undefined;if(Date.now()>=swBoot.until||location.pathname!==swBoot.path){swBoot=null;return undefined}const key=swCanon(body,2);if(key===null||!swBoot.entries.has(key))return undefined;const value=swBoot.entries.get(key);swBoot.entries.delete(key);return value}catch(error){swBoot=null;return undefined}};';
514
533
  var PUBLIC_BOOTSTRAP_INVOKE = 'if(operation==="list"||operation==="get"||operation==="aggregate"||operation==="relation_list"){const booted=swBootTake({...input,contract,table,operation});if(booted!==undefined)return booted}else swBoot=null;';
515
534
  function describeDataClient2(authority, contract_digest, insertReceiptCapability, localTypes = false, generation = CLIENT_GENERATION2) {
@@ -529,17 +548,19 @@ async function intake(table, reference, values, extra) {
529
548
  for (const table of tables) {
530
549
  const { name, columns, client, primaryKey } = table;
531
550
  const readable = client.read === true ? columns.filter((column) => column.n !== table.author).map((column) => column.n) : client.read || [];
532
- const row = shape(columns, readable, "read");
551
+ const hidden = generation >= 5 ? hiddenByBranch(table) : /* @__PURE__ */ new Set();
552
+ const row = shape(columns, readable, "read", /* @__PURE__ */ new Set(), hidden);
533
553
  const id = columnType(columns.find((column) => column.n === primaryKey));
534
554
  const operations = [];
535
555
  const methods = [];
536
556
  if (client.read !== false) {
537
- const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t));
557
+ const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t) && !hidden.has(field));
538
558
  if (generation >= 2) {
539
559
  const listOptions = `limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")}`;
540
- operations.push(`list<const L extends number = number>(options: { ${listOptions}; can: true }): Promise<{ data: (${row} & { can: RowCan })[]; next: ${id} | null; has_more: boolean }>`);
560
+ const hinted = generation >= 4 ? "RowHints" : "{ can: RowCan }";
561
+ operations.push(`list<const L extends number = number>(options: { ${listOptions}; can: true }): Promise<{ data: (${row} & ${hinted})[]; next: ${id} | null; has_more: boolean }>`);
541
562
  operations.push(`list<const L extends number = number>(options?: { ${listOptions}; can?: false }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
542
- operations.push(`get(id: ${id}, options: { can: true }): Promise<{ data: (${row} & { can: RowCan }) | null }>`);
563
+ operations.push(`get(id: ${id}, options: { can: true }): Promise<{ data: (${row} & ${hinted}) | null }>`);
543
564
  operations.push(`get(id: ${id}, options?: { can?: false }): Promise<{ data: ${row} | null }>`);
544
565
  } else {
545
566
  operations.push(`list<const L extends number = number>(options?: { limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")} }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
@@ -676,7 +697,7 @@ async function intake(table, reference, values, extra) {
676
697
  const declaration = `declare module ${JSON.stringify("somewhere:data")} {
677
698
  type PageSize = ${Array.from({ length: 100 }, (_, i) => i + 1).join(" | ")};
678
699
  export type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
679
- ${generation >= 2 ? ROW_CAN_DECLARATION : ""}${receiptCapable ? localTypes ? INSERT_CLIENT_DECLARATIONS.replace("readonly idempotency:", "readonly idempotency?:").replace("readonly request:", "readonly request?:") : INSERT_CLIENT_DECLARATIONS : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_DECLARATIONS : ""}${intakeTables.length ? INTAKE_CLIENT_DECLARATIONS : ""} export class DataError extends Error { readonly status: number; readonly code: string | null;${receiptCapable ? " readonly request?: InsertRequest<unknown>; readonly outcome?: string; readonly write_committed?: boolean; readonly retry?: false; readonly retryable?: false; readonly idempotency?: InsertReceipt;" : ""} constructor(status: number, payload: unknown); }
700
+ ${generation >= 4 ? ROW_HINTS_DECLARATION : generation >= 2 ? ROW_CAN_DECLARATION : ""}${receiptCapable ? localTypes ? INSERT_CLIENT_DECLARATIONS.replace("readonly idempotency:", "readonly idempotency?:").replace("readonly request:", "readonly request?:") : INSERT_CLIENT_DECLARATIONS : ""}${anonymousTables.length ? ANONYMOUS_CLIENT_DECLARATIONS : ""}${intakeTables.length ? INTAKE_CLIENT_DECLARATIONS : ""} export class DataError extends Error { readonly status: number; readonly code: string | null;${receiptCapable ? " readonly request?: InsertRequest<unknown>; readonly outcome?: string; readonly write_committed?: boolean; readonly retry?: false; readonly retryable?: false; readonly idempotency?: InsertReceipt;" : ""} constructor(status: number, payload: unknown); }
680
701
  export const data: { ${declarations.join(";\n ")} };
681
702
  }
682
703
  `;
@@ -957,6 +978,12 @@ interface SomewhereDbReadOptions {
957
978
  include?: readonly string[] | null;
958
979
  has?: Readonly<Record<string, SomewhereDbWhere>> | null;
959
980
  }
981
+ // Row hints on a caller read asked for { can: true }: what this signed-in caller may change now (the
982
+ // write still enforces) and whether this caller created the row. The owner column itself is never sent.
983
+ interface SomewhereDbRowHints {
984
+ readonly can: { readonly update: boolean; readonly delete: boolean };
985
+ readonly mine: boolean;
986
+ }
960
987
  interface SomewhereDbCountOptions { where?: SomewhereDbWhere | null }
961
988
  interface SomewhereDbPlainInsertOptions { onConflict?: 'ignore' | 'update' | null }
962
989
  // idempotencyKey: an explicit key (16-128 of A-Z a-z 0-9 - _) for a single plain insert by a
@@ -1076,8 +1103,11 @@ interface SomewhereCallerDb extends Omit<SomewhereServerDb, 'from' | 'count' | '
1076
1103
  insert<T extends SomewhereDbTable>(table: T, values: __SomewhereDeclared<T, 'insert', SomewhereDbValues>, options?: SomewhereDbCallerInsertOptions<T> | null): Promise<SomewhereDbInsertResult<SomewhereDbRow<T>>>;
1077
1104
  update<T extends SomewhereDbTable>(table: T, spec: SomewhereDbUpdateFor<T, 'set'>): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
1078
1105
  tx(intents: readonly SomewhereDbCallerWriteIntent[]): Promise<SomewhereDbResult[]>;
1106
+ // can: true (signed-in callers only; never with asServer): each row also carries the row hints.
1079
1107
  from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
1080
- table: T, options?: (SomewhereDbReadOptionsFor<T, C, I> & { asServer?: true }) | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
1108
+ table: T, options: SomewhereDbReadOptionsFor<T, C, I> & { can: true; asServer?: never }): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I> & SomewhereDbRowHints>>;
1109
+ from<T extends SomewhereDbTable, const C extends readonly SomewhereDbColumn<T>[] = readonly SomewhereDbColumn<T>[], const I extends readonly SomewhereDbRelationName<T>[] = []>(
1110
+ table: T, options?: (SomewhereDbReadOptionsFor<T, C, I> & { asServer?: true; can?: false }) | null): Promise<SomewhereDbResult<SomewhereDbReadRow<T, C, I>>>;
1081
1111
  count(table: SomewhereDbTable, options?: (SomewhereDbCountOptions & { asServer?: true }) | null): Promise<{ data: number; error: null }>;
1082
1112
  delete<T extends SomewhereDbTable>(table: T, spec?: SomewhereDbRemove | null): Promise<SomewhereDbResult<SomewhereDbRow<T>>>;
1083
1113
  readonly server: SomewhereServerDb;
@@ -4124,10 +4154,10 @@ function boolean(options?: SomewhereSchemaDeclaration.ColumnOptions<boolean>): S
4124
4154
  function timestamp(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
4125
4155
  function json(options?: SomewhereSchemaDeclaration.ColumnOptions<string>): SomewhereSchemaDeclaration.Column;
4126
4156
  function blob(options?: SomewhereSchemaDeclaration.ColumnOptions<never>): SomewhereSchemaDeclaration.Column;
4127
- function owner(options?: { column?: string; visitors?: boolean }): SomewhereSchemaDeclaration.OwnerScope;
4157
+ function owner(options?: { column?: string; visitors?: boolean; read?: string[] }): SomewhereSchemaDeclaration.OwnerScope;
4128
4158
  function shared(): SomewhereSchemaDeclaration.Scope;
4129
4159
  function serverOnly(): SomewhereSchemaDeclaration.Scope;
4130
- function member(options: { group: string | string[]; membership: string; member_user: string; member_group: string | string[]; operations?: Array<'read' | 'create' | 'update' | 'delete' | 'upload' | 'replace'> }): SomewhereSchemaDeclaration.MemberScope;
4160
+ function member(options: { group: string | string[]; membership: string; member_user: string; member_group: string | string[]; operations?: Array<'read' | 'create' | 'update' | 'delete' | 'upload' | 'replace'>; read?: string[] }): SomewhereSchemaDeclaration.MemberScope;
4131
4161
  function files(options: SomewhereSchemaDeclaration.FileCollectionOptions): SomewhereSchemaDeclaration.FileCollection;
4132
4162
  function hasMany(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
4133
4163
  function belongsTo(table: string, foreignKey: string): SomewhereSchemaDeclaration.Relation;
@@ -4135,13 +4165,13 @@ function removed(): SomewhereSchemaDeclaration.ColumnMarker;
4135
4165
  function removedTable(): SomewhereSchemaDeclaration.TableMarker;
4136
4166
  function exported(): SomewhereSchemaDeclaration.TableMarker;
4137
4167
  `;
4138
- var policySignatures = `function grant(options: { table: string; grantee: string; row?: string; operations: readonly ['read'] | readonly ['read', 'update'] }): SomewhereSchemaDeclaration.GrantScope;
4168
+ var policySignatures = `function grant(options: { table: string; grantee: string; row?: string; operations: readonly ['read'] | readonly ['read', 'update']; read?: string[] }): SomewhereSchemaDeclaration.GrantScope;
4139
4169
  function anyOf(owner: SomewhereSchemaDeclaration.OwnerScope, other: SomewhereSchemaDeclaration.GrantScope | SomewhereSchemaDeclaration.MemberScope | SomewhereSchemaDeclaration.GroupScope | SomewhereSchemaDeclaration.AppRoleScope): SomewhereSchemaDeclaration.AnyOfScope;
4140
4170
  function anyOf(other: SomewhereSchemaDeclaration.GrantScope | SomewhereSchemaDeclaration.MemberScope | SomewhereSchemaDeclaration.GroupScope | SomewhereSchemaDeclaration.AppRoleScope, owner: SomewhereSchemaDeclaration.OwnerScope): SomewhereSchemaDeclaration.AnyOfScope;
4141
4171
  function allOf(gate: SomewhereSchemaDeclaration.MemberScope | SomewhereSchemaDeclaration.GroupScope | SomewhereSchemaDeclaration.AppRoleScope, who: SomewhereSchemaDeclaration.OwnerScope | SomewhereSchemaDeclaration.AnyOfScope): SomewhereSchemaDeclaration.Scope;
4142
4172
  function parent(options: { via: string; table?: string }): SomewhereSchemaDeclaration.Scope;
4143
- function group(options?: { column?: string; roles?: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]>> }): SomewhereSchemaDeclaration.GroupScope;
4144
- function appRole(options: { roles: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]>> }): SomewhereSchemaDeclaration.AppRoleScope;
4173
+ function group(options?: { column?: string; roles?: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]>>; read?: string[] }): SomewhereSchemaDeclaration.GroupScope;
4174
+ function appRole(options: { roles: Partial<Record<'read' | 'create' | 'update' | 'delete', string[]>>; read?: string[] }): SomewhereSchemaDeclaration.AppRoleScope;
4145
4175
  `;
4146
4176
  var SCHEMA_DECLARATION2 = types + signatures.replace(/^function /gm, "declare function ") + `declare module 'somewhere/db' {
4147
4177
  ${(signatures + policySignatures).replace(/^function /gm, " export function ")}}
@@ -4312,19 +4342,131 @@ var require_generated_declarations_render = __commonJS({
4312
4342
  }
4313
4343
  });
4314
4344
 
4345
+ // worker/containers/compile/consumer-contract.cjs
4346
+ var require_consumer_contract = __commonJS({
4347
+ "worker/containers/compile/consumer-contract.cjs"(exports2, module2) {
4348
+ "use strict";
4349
+ var CONSUMER_CONTRACT2 = Object.freeze({
4350
+ version: 1,
4351
+ digest: "caa6986fab558d2d8ccb9157f3e7ffb5e94b179b4d22254dfeefe991468005e4"
4352
+ });
4353
+ var MEMBERSHIPS = "memberships: table({ id: id(), team_id: text(), member_id: text() }, { scope: owner(), client: { read: true } }),";
4354
+ var SHARES = "shares: table({ id: id(), grantee_id: text() }, { scope: owner(), client: { read: true, create: ['grantee_id'], delete: true }, unique: [['user_id', 'grantee_id']] }),";
4355
+ var MEMBER = "member({ group: ['team_id'], membership: 'memberships', member_user: 'member_id', member_group: ['team_id'], operations: ['read', 'create', 'update', 'delete'] })";
4356
+ var one = (imports, tables, options = "") => `import { schema, table, id, text, integer, ${imports} } from 'somewhere/db';
4357
+ export default schema({
4358
+ ${tables}
4359
+ }${options});
4360
+ `;
4361
+ var contacts = (scope, extra = "") => `contacts: table({ id: id(), team_id: text({ default: '' }), name: text()${extra} }, { scope: ${scope}, client: { read: true, create: ['name'], update: ['name'], delete: true } }),`;
4362
+ var CONSUMER_CONTRACT_FORMS = Object.freeze([
4363
+ ["owner()", one("owner", "notes: table({ id: id(), title: text() }, { scope: owner(), client: { read: true, create: ['title'], update: ['title'], delete: true } }),")],
4364
+ ["shared()", one("shared", "posts: table({ id: id(), body: text() }, { scope: shared(), client: { read: true, create: ['body'] } }),")],
4365
+ ["publicRead", one("owner", "cards: table({ id: id(), title: text() }, { scope: owner(), client: { read: ['id', 'title'], publicRead: true } }),")],
4366
+ ["member()", one("owner, member", `${MEMBERSHIPS}
4367
+ ${contacts(MEMBER)}`)],
4368
+ ["anyOf(owner(), member())", one("owner, member, anyOf", `${MEMBERSHIPS}
4369
+ ${contacts(`anyOf(owner(), ${MEMBER})`)}`)],
4370
+ ["anyOf(owner(), appRole())", one("owner, anyOf, appRole", "tickets: table({ id: id(), subject: text() }, { scope: anyOf(owner(), appRole({ roles: { read: ['staff'] } })), client: { read: true } }),", ", { appRoles: { staff: { grantableBy: [] } } }")],
4371
+ ["anyOf(owner(), grant())", one("owner, anyOf, grant", `${SHARES}
4372
+ ${contacts("anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['read'] }))")}`)],
4373
+ ["anyOf(owner(), grant({ row }))", one("owner, anyOf, grant", "doc_shares: table({ id: id(), doc_id: text({ references: 'docs', onDelete: 'cascade' }), grantee_id: text() }, { scope: owner(), unique: [['doc_id', 'grantee_id']] }),\n docs: table({ id: id(), title: text() }, { scope: anyOf(owner(), grant({ table: 'doc_shares', grantee: 'grantee_id', row: 'doc_id', operations: ['read', 'update'] })), client: { read: true, update: ['title'] } }),")],
4374
+ ["anyOf(owner({ read }), appRole())", one("owner, anyOf, appRole", "tickets: table({ id: id(), subject: text(), customer_email: text() }, { scope: anyOf(owner({ read: ['id', 'subject'] }), appRole({ roles: { read: ['staff'] } })), client: { read: ['id', 'subject', 'customer_email'] } }),", ", { appRoles: { staff: { grantableBy: [] } } }")],
4375
+ ["allOf(member(), owner())", one("owner, member, allOf", `${MEMBERSHIPS}
4376
+ ${contacts(`allOf(${MEMBER}, owner())`)}`)],
4377
+ ["parent()", one("owner, parent, hasMany", "tickets: table({ id: id(), subject: text() }, { scope: owner(), client: { read: true }, relations: { comments: hasMany('comments', 'ticket_id') } }),\n comments: table({ id: id(), ticket_id: text({ references: 'tickets' }), body: text() }, { scope: parent({ via: 'ticket_id' }), client: { read: true } }),")],
4378
+ // Refused by deploy (scripts/check-cli-vendor-parity.mjs asserts the same reasons).
4379
+ ["refused: grant() inside allOf", one("owner, member, anyOf, allOf, grant", `${SHARES}
4380
+ ${MEMBERSHIPS}
4381
+ ${contacts(`allOf(${MEMBER}, anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['read'] })))`)}`)],
4382
+ ["refused: grant() update without read", one("owner, anyOf, grant", `${SHARES}
4383
+ ${contacts("anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['update'] }))")}`)],
4384
+ ["refused: grant() delete", one("owner, anyOf, grant", `${SHARES}
4385
+ ${contacts("anyOf(owner(), grant({ table: 'shares', grantee: 'grantee_id', operations: ['read', 'update', 'delete'] }))")}`)],
4386
+ ["refused: grant() alone", one("owner, grant", `${SHARES}
4387
+ ${contacts("grant({ table: 'shares', grantee: 'grantee_id', operations: ['read'] })")}`)],
4388
+ ["refused: a column named mine", one("owner", "notes: table({ id: id(), mine: text() }, { scope: owner(), client: { read: true } }),")]
4389
+ ]);
4390
+ var DERIVED = ["generateFromFiles", "localDeclarationFromFiles", "declaredTablesFromFiles", "runtimeDeclarationFromFiles", "filesDeclarationFromFiles"];
4391
+ function consumerContractComponents2(data, sourceLintVersion, hash) {
4392
+ const forms = {};
4393
+ for (const [name, source] of CONSUMER_CONTRACT_FORMS) {
4394
+ const files = { "db/schema.ts": source };
4395
+ let verdict;
4396
+ try {
4397
+ verdict = { accepted: true, outputs: DERIVED.map((key) => typeof data[key] === "function" ? data[key](files) ?? null : `missing ${key}`) };
4398
+ } catch (error) {
4399
+ verdict = { accepted: false, refusal: String(error && error.message || error) };
4400
+ }
4401
+ forms[name] = hash(JSON.stringify(verdict));
4402
+ }
4403
+ return {
4404
+ schema_declaration: hash(String(data.SCHEMA_DECLARATION)),
4405
+ runtime_context_declaration: hash(String(data.RUNTIME_CONTEXT_DECLARATION)),
4406
+ endpoint_declaration: hash(String(data.ENDPOINT_DECLARATION)),
4407
+ files_declaration: hash(String(data.FILES_DECLARATION_FILE)),
4408
+ source_lint_version: String(sourceLintVersion),
4409
+ forms
4410
+ };
4411
+ }
4412
+ function consumerContractFingerprint2(components) {
4413
+ const sorted3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? Object.fromEntries(Object.keys(value).sort().map((k) => [k, sorted3(value[k])])) : value;
4414
+ return JSON.stringify(sorted3(components));
4415
+ }
4416
+ function consumerContractHeader(contract = CONSUMER_CONTRACT2) {
4417
+ return `${contract.version}; digest=${contract.digest}`;
4418
+ }
4419
+ function parseConsumerContractHeader2(value) {
4420
+ const m = /^\s*(\d{1,9}); digest=([0-9a-f]{64})\s*$/.exec(String(value || ""));
4421
+ return m ? { version: Number(m[1]), digest: m[2] } : null;
4422
+ }
4423
+ function consumerContractVerdict2(cli, seen, now = Date.now()) {
4424
+ if (!cli || !Number.isInteger(cli.version) || !/^[0-9a-f]{64}$/.test(cli.digest || "")) {
4425
+ return { status: "unknown", compatible: null, message: "This CLI does not record which platform consumer contract it implements." };
4426
+ }
4427
+ if (!seen) {
4428
+ return { status: "unknown", compatible: null, message: `The platform's consumer contract is not known on this machine yet (no platform response recorded). This CLI checks against contract ${cli.version}.` };
4429
+ }
4430
+ const age = Number.isFinite(seen.seen_at) ? ` (last seen ${Math.max(0, Math.round((now - seen.seen_at) / 36e5))} h ago${seen.origin ? ` from ${seen.origin}` : ""})` : "";
4431
+ if (seen.digest === cli.digest) return { status: "current", compatible: true, message: Number.isFinite(seen.seen_at) ? `This CLI implements consumer contract ${cli.version}, the one the platform last announced${age}. A platform release since then is not known until the CLI next talks to the platform.` : `This CLI implements consumer contract ${cli.version}.` };
4432
+ if (seen.version > cli.version) {
4433
+ return { status: "outdated", compatible: false, message: `This CLI implements consumer contract ${cli.version}; the platform is at ${seen.version}${age}. Its typecheck and lint judge your code by older platform rules, so a form the platform accepts may be reported as an error and newer forms are unknown to it. Update the CLI (somewhere update), then run this again.` };
4434
+ }
4435
+ if (seen.version < cli.version) {
4436
+ return { status: "ahead", compatible: false, message: `This CLI implements consumer contract ${cli.version}; the platform is still at ${seen.version}${age}. It may accept forms the platform does not yet deploy. Use the CLI version that matches the platform, or wait for the platform release.` };
4437
+ }
4438
+ return { status: "mismatch", compatible: false, message: `This CLI and the platform both say consumer contract ${cli.version} but with different digests${age}; one of them is not a released build. Reinstall the CLI from npm.` };
4439
+ }
4440
+ module2.exports = {
4441
+ CONSUMER_CONTRACT: CONSUMER_CONTRACT2,
4442
+ CONSUMER_CONTRACT_FORMS,
4443
+ consumerContractComponents: consumerContractComponents2,
4444
+ consumerContractFingerprint: consumerContractFingerprint2,
4445
+ consumerContractHeader,
4446
+ parseConsumerContractHeader: parseConsumerContractHeader2,
4447
+ consumerContractVerdict: consumerContractVerdict2
4448
+ };
4449
+ }
4450
+ });
4451
+
4315
4452
  // declared-data-vendor-entry.js
4316
4453
  var declared_data_vendor_entry_exports = {};
4317
4454
  __export(declared_data_vendor_entry_exports, {
4455
+ CONSUMER_CONTRACT: () => import_consumer_contract.CONSUMER_CONTRACT,
4318
4456
  ENDPOINT_DECLARATION: () => import_runtime_types3.ENDPOINT_DECLARATION,
4319
4457
  FILES_DECLARATION_FILE: () => import_typed_files2.CLIENT_FILE,
4320
4458
  RUNTIME_CONTEXT_DECLARATION: () => import_runtime_types3.RUNTIME_CONTEXT_DECLARATION,
4321
4459
  SCHEMA_DECLARATION: () => import_schema_types.SCHEMA_DECLARATION,
4322
4460
  checkerContract: () => checkerContract,
4461
+ consumerContractComponents: () => import_consumer_contract.consumerContractComponents,
4462
+ consumerContractFingerprint: () => import_consumer_contract.consumerContractFingerprint,
4463
+ consumerContractVerdict: () => import_consumer_contract.consumerContractVerdict,
4323
4464
  declaredTablesFromFiles: () => declaredTablesFromFiles,
4324
4465
  filesDeclarationFromFiles: () => filesDeclarationFromFiles,
4325
4466
  generateFromFiles: () => generateFromFiles,
4326
4467
  isRuntimeEndpointGlobal: () => isRuntimeEndpointGlobal,
4327
4468
  localDeclarationFromFiles: () => localDeclarationFromFiles,
4469
+ parseConsumerContractHeader: () => import_consumer_contract.parseConsumerContractHeader,
4328
4470
  referenceCheckDeclarations: () => referenceCheckDeclarations,
4329
4471
  runtimeDeclarationFromFiles: () => runtimeDeclarationFromFiles
4330
4472
  });
@@ -4339,6 +4481,9 @@ function isCanonicalBigintText(value) {
4339
4481
  return digits.length < limit.length || digits.length === limit.length && digits <= limit;
4340
4482
  }
4341
4483
 
4484
+ // worker/src/utils/db-schema-deploy/schema-ownership.ts
4485
+ var SHARED_AUTHOR_COLUMN = "_sw_author_id";
4486
+
4342
4487
  // worker/src/utils/db-schema-deploy/client-permissions.ts
4343
4488
  function tableAllowsClientWrites(table) {
4344
4489
  return Boolean(table.client && (table.client.create !== null || table.client.update !== null || table.client.delete || table.client.intake !== void 0));
@@ -4391,6 +4536,9 @@ function parseClientPermissions(value, columns, scope) {
4391
4536
  if (Array.isArray(read)) {
4392
4537
  const names = [];
4393
4538
  for (const entry of read) {
4539
+ if (typeof entry === "string" && ((scope.kind === "owner" || scope.kind === "policy") && entry.toLowerCase() === scope.column || entry.toLowerCase() === SHARED_AUTHOR_COLUMN)) {
4540
+ return fail(`client.read cannot list "${entry}": the platform assigns who created each row and never sends that identity to the browser. To show the signed-in user which rows are theirs, read with { can: true }: each row then carries mine (true for rows this user created) and can.update / can.delete.`);
4541
+ }
4394
4542
  if (typeof entry !== "string" || !fields.has(entry.toLowerCase())) return fail("client.read must contain only declared column names.");
4395
4543
  const name = entry.toLowerCase();
4396
4544
  if (names.includes(name)) return fail(`client.read declares field "${entry}" twice.`);
@@ -4651,9 +4799,6 @@ function bakeIntakePermissions(schema, declarations, intents, scopes) {
4651
4799
  }
4652
4800
  }
4653
4801
 
4654
- // worker/src/utils/db-schema-deploy/schema-ownership.ts
4655
- var SHARED_AUTHOR_COLUMN = "_sw_author_id";
4656
-
4657
4802
  // worker/src/services/jobs/limits.ts
4658
4803
  var STALE_JOB_WINDOW_MS = 45 * 60 * 1e3;
4659
4804
 
@@ -4901,6 +5046,11 @@ function validGroupRoles(value) {
4901
5046
  const roles = value;
4902
5047
  return exactKeys(roles, ["read", "create", "update", "delete"]) && ["read", "create", "update", "delete"].every((op) => Array.isArray(roles[op]) && roles[op].every((role) => typeof role === "string" && GROUP_ROLE_NAME.test(role)) && new Set(roles[op]).size === roles[op].length);
4903
5048
  }
5049
+ function validBranchReads(value) {
5050
+ if (!Array.isArray(value) || value.length !== 2) return false;
5051
+ const list = (entry) => entry === null || Array.isArray(entry) && entry.length > 0 && entry.every((name, i) => typeof name === "string" && SAFE_SCOPE_IDENTIFIER.test(name) && name === name.toLowerCase() && (i === 0 || entry[i - 1] < name));
5052
+ return list(value[0]) && list(value[1]) && !(value[0] === null && value[1] === null);
5053
+ }
4904
5054
  function exactKeys(value, keys) {
4905
5055
  const actual = Object.keys(value).sort();
4906
5056
  return actual.length === keys.length && actual.every((key, index) => key === [...keys].sort()[index]);
@@ -4942,7 +5092,7 @@ function validBakedPolicy(value, root = true, edges = 0) {
4942
5092
  }
4943
5093
  if (node.k === "a") {
4944
5094
  const second = node.p?.[1]?.k;
4945
- return exactKeys(node, ["k", "p"]) && Array.isArray(node.p) && node.p.length === 2 && node.p[0]?.k === "o" && validBakedPolicy(node.p[0], false) && (!(node.p[1]?.k === "m" && node.p[1].m?.og) || node.p[0].c === "user_id") && (second === "m" ? validBakedPolicy(node.p[1], false) : (second === "g" || second === "r") && validBakedPolicy(node.p[1], true));
5095
+ return (exactKeys(node, ["k", "p"]) || edges === 0 && exactKeys(node, ["k", "p", "reads"]) && validBranchReads(node.reads)) && Array.isArray(node.p) && node.p.length === 2 && node.p[0]?.k === "o" && validBakedPolicy(node.p[0], false) && (!(node.p[1]?.k === "m" && node.p[1].m?.og) || node.p[0].c === "user_id") && (second === "m" ? validBakedPolicy(node.p[1], false) : (second === "g" || second === "r") && validBakedPolicy(node.p[1], true));
4946
5096
  }
4947
5097
  if (root && node.k === "n") {
4948
5098
  if (!ALLOF_POLICY_ADMITTED || !exactKeys(node, ["k", "g", "w"])) return false;
@@ -5091,7 +5241,9 @@ function bakedPolicyFromDeclared(value, depth = 0, edges = 0, underParent = fals
5091
5241
  if (depth !== 0 && !underParent || node.policies[0]?.kind !== "owner" || second !== "member" && second !== "group" && second !== "app_role") return null;
5092
5242
  const left = bakedPolicyFromDeclared(node.policies[0], depth + 1);
5093
5243
  const right = bakedPolicyFromDeclared(node.policies[1], depth + 1, edges, second !== "member");
5094
- const baked = left && right ? { k: "a", p: [left, right] } : null;
5244
+ const reads = node.reads === void 0 || edges > 0 || underParent ? void 0 : Array.isArray(node.reads) && node.reads.length === 2 ? node.reads.map((list) => Array.isArray(list) ? [...new Set(list.map((name) => String(name).toLowerCase()))].sort() : null) : null;
5245
+ if (reads === null || reads !== void 0 && !validBranchReads(reads)) return null;
5246
+ const baked = left && right ? { k: "a", p: [left, right], ...reads ? { reads } : {} } : null;
5095
5247
  return baked && validBakedPolicy(pinShape(baked), true, edges) ? baked : null;
5096
5248
  }
5097
5249
  if (node.kind === "all_of") {
@@ -5195,6 +5347,7 @@ function readGrantScope(r, table, line, fail) {
5195
5347
  r.expectPunct("{", "in grant options");
5196
5348
  let membership, grantee, row;
5197
5349
  let operations;
5350
+ let read;
5198
5351
  const seen = /* @__PURE__ */ new Set();
5199
5352
  while (!r.tryPunct("}")) {
5200
5353
  const key = r.readKey("in grant options");
@@ -5222,7 +5375,9 @@ function readGrantScope(r, table, line, fail) {
5222
5375
  }
5223
5376
  if (!ops.includes("read")) fail(key.line, "grant operations require read; update alone is not allowed.");
5224
5377
  operations = ops.includes("update") ? ["read", "update"] : ["read"];
5225
- } else fail(key.line, `Unknown grant option "${key.name}". Allowed: table, grantee, row, operations.`);
5378
+ } else if (key.name === "read") {
5379
+ read = readBranchReadColumns(r, `grant({ read }) on table "${table}"`);
5380
+ } else fail(key.line, `Unknown grant option "${key.name}". Allowed: table, grantee, row, operations, read.`);
5226
5381
  if (!r.tryPunct(",")) {
5227
5382
  r.expectPunct("}", "closing grant options");
5228
5383
  break;
@@ -5232,7 +5387,7 @@ function readGrantScope(r, table, line, fail) {
5232
5387
  r.expectPunct(")", "closing grant");
5233
5388
  if (!membership || !grantee || !operations || grantee === "user_id") fail(line, `grant() on "${table}" requires table, a grantee column distinct from managed user_id, and explicit operations.`);
5234
5389
  if (row !== void 0 && (row === "user_id" || row === grantee)) fail(line, `grant() on "${table}": row names the shares column holding the shared row's id, distinct from user_id and the grantee.`);
5235
- return { kind: "member", group: ["user_id"], membership, memberUser: grantee, memberGroup: row === void 0 ? ["user_id"] : ["user_id", row], operations, ownerGrant: true };
5390
+ return { kind: "member", group: ["user_id"], membership, memberUser: grantee, memberGroup: row === void 0 ? ["user_id"] : ["user_id", row], operations, ownerGrant: true, ...read ? { read } : {} };
5236
5391
  }
5237
5392
  function resolveGrantRowKeys(scope, key, table, line, fail) {
5238
5393
  const visit = (node) => {
@@ -6182,6 +6337,7 @@ function readRoleList(r, fail, ctx) {
6182
6337
  function readGroupScope(r, tableName, line, fail) {
6183
6338
  r.expectPunct("(", 'after "group"');
6184
6339
  let column = DEFAULT_GROUP_COLUMN;
6340
+ let read;
6185
6341
  const explicit = {};
6186
6342
  if (!r.tryPunct(")")) {
6187
6343
  r.expectPunct("{", `in group() options for table "${tableName}"`);
@@ -6210,8 +6366,10 @@ function readGroupScope(r, tableName, line, fail) {
6210
6366
  break;
6211
6367
  }
6212
6368
  }
6369
+ } else if (key.name === "read") {
6370
+ read = readBranchReadColumns(r, `group({ read }) on table "${tableName}"`);
6213
6371
  } else {
6214
- fail(key.line, `group() on table "${tableName}" has an unknown option "${key.name}". Allowed: column, roles.`);
6372
+ fail(key.line, `group() on table "${tableName}" has an unknown option "${key.name}". Allowed: column, roles, read.`);
6215
6373
  }
6216
6374
  if (!r.tryPunct(",")) {
6217
6375
  r.expectPunct("}", `closing group() options for table "${tableName}"`);
@@ -6221,7 +6379,7 @@ function readGroupScope(r, tableName, line, fail) {
6221
6379
  r.tryPunct(",");
6222
6380
  r.expectPunct(")", "closing group()");
6223
6381
  }
6224
- const node = { kind: "group", column, roles: { read: [], create: [], update: [], delete: [] } };
6382
+ const node = { kind: "group", column, roles: { read: [], create: [], update: [], delete: [] }, ...read ? { read } : {} };
6225
6383
  declaredGroupRoles.set(node, { line, explicit });
6226
6384
  return node;
6227
6385
  }
@@ -6330,7 +6488,17 @@ function readAppRoleScope(r, tableName, line, fail) {
6330
6488
  let sawRoles = false;
6331
6489
  while (!r.tryPunct("}")) {
6332
6490
  const key = r.readKey(`for an option of ${where}`);
6333
- if (key.name !== "roles") fail(key.line, `${where} has an unknown option "${key.name}". Allowed: roles.`);
6491
+ if (key.name === "read") {
6492
+ if (node.read) fail(key.line, `${where} declares read twice.`);
6493
+ r.expectPunct(":", `after "read" in ${where}`);
6494
+ node.read = readBranchReadColumns(r, `appRole({ read }) on table "${tableName}"`);
6495
+ if (!r.tryPunct(",")) {
6496
+ r.expectPunct("}", `closing ${where}`);
6497
+ break;
6498
+ }
6499
+ continue;
6500
+ }
6501
+ if (key.name !== "roles") fail(key.line, `${where} has an unknown option "${key.name}". Allowed: roles, read.`);
6334
6502
  if (sawRoles) fail(key.line, `${where} declares roles twice.`);
6335
6503
  sawRoles = true;
6336
6504
  r.expectPunct(":", `after "roles" in ${where}`);
@@ -6622,6 +6790,7 @@ var SCHEMA_CALLABLE = /* @__PURE__ */ new Set([
6622
6790
  ]);
6623
6791
  var SAFE_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
6624
6792
  var RESERVED_PREFIXES = ["app_", "sqlite_", "d1_", "__sw_"];
6793
+ var ROW_HINT_FIELDS = ["can", "mine"];
6625
6794
  var MAX_TABLES = 100;
6626
6795
  var MAX_COLUMNS = 100;
6627
6796
  var MAX_INDEXES = 40;
@@ -6869,6 +7038,9 @@ function checkDeclaredName(kind, raw, line) {
6869
7038
  if (name.startsWith("_") || RESERVED_PREFIXES.some((p) => name.startsWith(p))) {
6870
7039
  throw new SchemaTsError(`line ${line}: ${kind} name "${raw}" uses a reserved platform prefix and cannot be declared: names starting with "app_" or "_" belong to the platform.`);
6871
7040
  }
7041
+ if (kind === "Column" && ROW_HINT_FIELDS.includes(name)) {
7042
+ throw new SchemaTsError(`line ${line}: column name "${raw}" is reserved for row hints: a read with { can: true } returns can and mine on every row, so a column cannot take either name.`);
7043
+ }
6872
7044
  return name;
6873
7045
  }
6874
7046
  function readColumn(r, tableName, rawName, line) {
@@ -7058,6 +7230,7 @@ function readMemberScope(r, tableName, scopeLine) {
7058
7230
  let memberUser = null;
7059
7231
  let memberGroup = null;
7060
7232
  let operations, operationsSeen = false;
7233
+ let read;
7061
7234
  while (!r.tryPunct("}")) {
7062
7235
  const key = r.readKey(`for a member() option of table "${tableName}"`);
7063
7236
  r.expectPunct(":", `after "${key.name}" in member() for table "${tableName}"`);
@@ -7073,9 +7246,12 @@ function readMemberScope(r, tableName, scopeLine) {
7073
7246
  if (operationsSeen) throw new SchemaTsError(`line ${key.line}: member() on table "${tableName}" declares operations more than once.`);
7074
7247
  operationsSeen = true;
7075
7248
  operations = readMemberOperations(r, tableName);
7249
+ } else if (key.name === "read") {
7250
+ if (read) throw new SchemaTsError(`line ${key.line}: member() on table "${tableName}" declares read more than once.`);
7251
+ read = readBranchReadColumns(r, `member({ read }) on table "${tableName}"`);
7076
7252
  } else {
7077
7253
  throw new SchemaTsError(
7078
- `line ${key.line}: member() on table "${tableName}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group, operations.`
7254
+ `line ${key.line}: member() on table "${tableName}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group, operations, read.`
7079
7255
  );
7080
7256
  }
7081
7257
  if (!r.tryPunct(",")) {
@@ -7105,7 +7281,7 @@ function readMemberScope(r, tableName, scopeLine) {
7105
7281
  `line ${scopeLine}: member() on table "${tableName}" has a ${group.length}-column group but a ${memberGroup.length}-column member_group. The group key must have the same number of columns on both sides.`
7106
7282
  );
7107
7283
  }
7108
- const node = { kind: "member", group, membership, memberUser, memberGroup, ...operations !== void 0 ? { operations } : {} };
7284
+ const node = { kind: "member", group, membership, memberUser, memberGroup, ...operations !== void 0 ? { operations } : {}, ...read ? { read } : {} };
7109
7285
  if (operationsSeen) explicitMemberOperations.add(node);
7110
7286
  return node;
7111
7287
  }
@@ -7113,21 +7289,56 @@ var explicitMemberOperations = /* @__PURE__ */ new WeakSet();
7113
7289
  function readPolicyOwner(r, tableName, line) {
7114
7290
  r.expectPunct("(", 'after "owner"');
7115
7291
  let column = DEFAULT_OWNER_COLUMN;
7292
+ let read;
7116
7293
  if (!r.tryPunct(")")) {
7117
- const opts = r.readLiteralObject(`in owner() inside anyOf() for table "${tableName}"`);
7118
- for (const [key, { value, line: optionLine }] of opts) {
7119
- if (key !== "column") {
7120
- throw new SchemaTsError(`line ${optionLine}: owner() inside anyOf() only allows column. Visitor ownership is not part of policy scope.`);
7294
+ r.expectPunct("{", `in owner() inside anyOf() for table "${tableName}"`);
7295
+ const seen = /* @__PURE__ */ new Set();
7296
+ while (!r.tryPunct("}")) {
7297
+ const key = r.readKey(`for an owner() option inside anyOf() for table "${tableName}"`);
7298
+ if (seen.has(key.name)) throw new SchemaTsError(`line ${key.line}: option "${key.name}" appears twice in owner() inside anyOf() for table "${tableName}".`);
7299
+ seen.add(key.name);
7300
+ r.expectPunct(":", `after "${key.name}" in owner() inside anyOf() for table "${tableName}"`);
7301
+ if (key.name === "column") {
7302
+ const value = r.readLiteral(`for "column" in owner() inside anyOf() for table "${tableName}"`);
7303
+ if (typeof value !== "string" || !SAFE_IDENT.test(value) || value.length > MAX_NAME_LENGTH) {
7304
+ throw new SchemaTsError(`line ${key.line}: owner({ column }) on table "${tableName}" must be a valid column name.`);
7305
+ }
7306
+ column = value.toLowerCase();
7307
+ } else if (key.name === "read") {
7308
+ read = readBranchReadColumns(r, `owner({ read }) on table "${tableName}"`);
7309
+ } else {
7310
+ throw new SchemaTsError(`line ${key.line}: owner() inside anyOf() only allows column and read. Visitor ownership is not part of policy scope.`);
7121
7311
  }
7122
- if (typeof value !== "string" || !SAFE_IDENT.test(value) || value.length > MAX_NAME_LENGTH) {
7123
- throw new SchemaTsError(`line ${optionLine}: owner({ column }) on table "${tableName}" must be a valid column name.`);
7312
+ if (!r.tryPunct(",")) {
7313
+ r.expectPunct("}", `closing owner() options inside anyOf() for table "${tableName}"`);
7314
+ break;
7124
7315
  }
7125
- column = value.toLowerCase();
7126
7316
  }
7127
7317
  r.tryPunct(",");
7128
7318
  r.expectPunct(")", "closing owner()");
7129
7319
  }
7130
- return { kind: "owner", column };
7320
+ return { kind: "owner", column, ...read ? { read } : {} };
7321
+ }
7322
+ function readBranchReadColumns(r, ctx) {
7323
+ const open = r.next();
7324
+ if (open.kind !== "punct" || open.value !== "[") throw new SchemaTsError(`line ${open.line}: ${ctx} must be a list of quoted column names, such as ['id', 'subject'].`);
7325
+ const out = [];
7326
+ while (!r.tryPunct("]")) {
7327
+ const t = r.next();
7328
+ if (t.kind !== "string" || !SAFE_IDENT.test(t.value) || t.value.length > MAX_NAME_LENGTH) throw new SchemaTsError(`line ${t.line}: ${ctx} must be a list of quoted column names; values must be literal strings.`);
7329
+ const name = t.value.toLowerCase();
7330
+ if (out.includes(name)) throw new SchemaTsError(`line ${t.line}: ${ctx} lists "${t.value}" twice.`);
7331
+ out.push(name);
7332
+ if (!r.tryPunct(",")) {
7333
+ r.expectPunct("]", `closing ${ctx}`);
7334
+ break;
7335
+ }
7336
+ }
7337
+ if (out.length === 0) throw new SchemaTsError(`line ${open.line}: ${ctx} must name at least one column; leave read out for every column.`);
7338
+ return out;
7339
+ }
7340
+ function refuseBranchRead(node, line, tableName, where) {
7341
+ if (node.read) throw new SchemaTsError(`line ${line}: read on ${where} of table "${tableName}" is accepted only on the branches of the table's own anyOf(); it names the columns that branch may read.`);
7131
7342
  }
7132
7343
  function readPolicyMember(r, tableName, line) {
7133
7344
  r.expectPunct("(", 'after "member"');
@@ -7162,7 +7373,11 @@ function readAnyOfScope(r, tableName, line) {
7162
7373
  const owner = owners[0];
7163
7374
  const other = firstPolicy.kind === "owner" ? secondPolicy : firstPolicy;
7164
7375
  if (other.kind === "member" && other.ownerGrant && owner.column !== DEFAULT_OWNER_COLUMN) throw new SchemaTsError(`line ${line}: grant() requires owner() with managed user_id.`);
7165
- return { kind: "policy", policy: { kind: "any_of", policies: [owner, other] } };
7376
+ const otherRead = other;
7377
+ const reads = [owner.read ?? null, otherRead.read ?? null];
7378
+ delete owner.read;
7379
+ delete otherRead.read;
7380
+ return { kind: "policy", policy: { kind: "any_of", policies: [owner, other], ...reads[0] || reads[1] ? { reads } : {} } };
7166
7381
  }
7167
7382
  var ALL_OF_FORMS = "allOf() takes a gate \u2014 member(), group() or appRole() \u2014 and then owner() or anyOf(owner(), ...).";
7168
7383
  var ALL_OF_EXPLICIT = "allOf needs explicit operations";
@@ -7188,6 +7403,9 @@ function readAllOfScope(r, tableName, line) {
7188
7403
  } else throw new SchemaTsError(`line ${whoTok.line}: ${ALL_OF_FORMS}`);
7189
7404
  r.tryPunct(",");
7190
7405
  r.expectPunct(")", "closing allOf()");
7406
+ refuseBranchRead(gate, line, tableName, "the gate of allOf()");
7407
+ refuseBranchRead(who, line, tableName, "who in allOf()");
7408
+ if (who.kind === "any_of" && who.reads) throw new SchemaTsError(`line ${line}: read on a branch of anyOf() inside allOf() on table "${tableName}" is not accepted; per-branch columns belong on the table's own anyOf().`);
7191
7409
  const x = who.kind === "any_of" ? who.policies[1] : null;
7192
7410
  if (x?.kind === "member" && x.ownerGrant) throw new SchemaTsError(`line ${line}: grant() belongs in anyOf(owner(), grant()), not allOf().`);
7193
7411
  for (const node of [gate, ...x ? [x] : []]) {
@@ -7279,13 +7497,17 @@ function readScope(r, tableName) {
7279
7497
  const failAt = (line, message) => {
7280
7498
  throw new SchemaTsError(`line ${line}: ${message}`);
7281
7499
  };
7282
- if (tok.value === "group") return { kind: "policy", policy: readGroupScope(r, tableName, tok.line, failAt) };
7283
- if (tok.value === "appRole") return { kind: "policy", policy: readAppRoleScope(r, tableName, tok.line, failAt) };
7500
+ if (tok.value === "group" || tok.value === "appRole") {
7501
+ const policy = tok.value === "group" ? readGroupScope(r, tableName, tok.line, failAt) : readAppRoleScope(r, tableName, tok.line, failAt);
7502
+ refuseBranchRead(policy, tok.line, tableName, `the ${tok.value}() scope`);
7503
+ return { kind: "policy", policy };
7504
+ }
7284
7505
  if (tok.value === "member") {
7285
7506
  r.expectPunct("(", 'after "member"');
7286
7507
  const scope = readMemberScope(r, tableName, tok.line);
7287
7508
  r.tryPunct(",");
7288
7509
  r.expectPunct(")", "closing member()");
7510
+ if (scope.kind === "member") refuseBranchRead(scope, tok.line, tableName, "the member() scope");
7289
7511
  return scope;
7290
7512
  }
7291
7513
  r.expectPunct("(", `after "${tok.value}"`);
@@ -7535,6 +7757,22 @@ function readTable(r, rawName, nameLine) {
7535
7757
  }
7536
7758
  uniques.push(entry.columns);
7537
7759
  }
7760
+ if (scope.kind === "policy" && scope.policy.kind === "any_of" && scope.policy.reads) {
7761
+ const key = columns.filter((column) => column.helper === "id").length === 1 ? columns.find((column) => column.helper === "id").name : null;
7762
+ const declared = new Set(columns.map((column) => column.name));
7763
+ scope.policy.reads.forEach((list, i) => {
7764
+ if (!list) return;
7765
+ const branch = scope.policy.kind === "any_of" ? scope.policy.policies[i] : null;
7766
+ const label = branch?.kind === "owner" ? "owner()" : branch?.kind === "member" ? branch.ownerGrant ? "grant()" : "member()" : branch?.kind === "group" ? "group()" : "appRole()";
7767
+ for (const column of list) {
7768
+ if (!declared.has(column)) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${label} read lists "${column}", which is not one of its declared columns.`);
7769
+ }
7770
+ if (key && !list.includes(key)) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${label} read must include the id column "${key}", which every reader needs to page and address rows.`);
7771
+ for (const relation of relations) {
7772
+ if (relation.kind === "belongsTo" && !list.includes(relation.fk)) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${label} read must include "${relation.fk}", the key relation "${relation.name}" joins by; a reader without it would get an empty relation.`);
7773
+ }
7774
+ });
7775
+ }
7538
7776
  let client;
7539
7777
  if (clientInput) {
7540
7778
  if (columns.filter((column) => column.helper === "id").length !== 1) {
@@ -7821,6 +8059,7 @@ function isRuntimeEndpointGlobal(diag, functions) {
7821
8059
  }
7822
8060
 
7823
8061
  // declared-data-vendor-entry.js
8062
+ var import_consumer_contract = __toESM(require_consumer_contract());
7824
8063
  function generateFromFiles(files) {
7825
8064
  const authority = clientAuthorityFromSource(files);
7826
8065
  return authority ? (0, import_typed_data.generateDataClient)(authority) : void 0;
@@ -7841,16 +8080,21 @@ function filesDeclarationFromFiles(files) {
7841
8080
  }
7842
8081
  // Annotate the CommonJS export names for ESM import in node:
7843
8082
  0 && (module.exports = {
8083
+ CONSUMER_CONTRACT,
7844
8084
  ENDPOINT_DECLARATION,
7845
8085
  FILES_DECLARATION_FILE,
7846
8086
  RUNTIME_CONTEXT_DECLARATION,
7847
8087
  SCHEMA_DECLARATION,
7848
8088
  checkerContract,
8089
+ consumerContractComponents,
8090
+ consumerContractFingerprint,
8091
+ consumerContractVerdict,
7849
8092
  declaredTablesFromFiles,
7850
8093
  filesDeclarationFromFiles,
7851
8094
  generateFromFiles,
7852
8095
  isRuntimeEndpointGlobal,
7853
8096
  localDeclarationFromFiles,
8097
+ parseConsumerContractHeader,
7854
8098
  referenceCheckDeclarations,
7855
8099
  runtimeDeclarationFromFiles
7856
8100
  });