@somewhere-cloud/cli 0.38.20 → 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.
@@ -112,11 +112,12 @@ var require_declared_data_contract = __commonJS({
112
112
  const normalizedOperations = o?.length === 4 ? void 0 : o;
113
113
  return { k: "m", m: { g: [...g], m, u, mg: [...mg], ...normalizedOperations !== void 0 ? { o: [...normalizedOperations] } : {}, ...og ? { og: true } : {} } };
114
114
  }
115
- if (value.k === "a" && keys === "k,p" && Array.isArray(value.p) && value.p.length === 2) {
115
+ if (value.k === "a" && (keys === "k,p" || keys === "k,p,reads" && edges === 0) && Array.isArray(value.p) && value.p.length === 2) {
116
116
  const left = policy(value.p[0], label), right = policy(value.p[1], label);
117
117
  if (left.k !== "o" || !["m", "g", "r"].includes(right.k)) fail(label);
118
118
  if (right.k === "m" && right.m.og && left.c !== "user_id") fail(label);
119
- return { k: "a", p: [left, right] };
119
+ const reads = value.reads === void 0 ? void 0 : branchReads(value.reads, label);
120
+ return { k: "a", p: [left, right], ...reads ? { reads } : {} };
120
121
  }
121
122
  if (value.k === "n" && keys === "g,k,w") {
122
123
  const gate = policy(value.g, label), who = policy(value.w, label);
@@ -144,6 +145,23 @@ var require_declared_data_contract = __commonJS({
144
145
  }
145
146
  fail(label);
146
147
  }
148
+ function branchReads(value, label) {
149
+ if (!Array.isArray(value) || value.length !== 2 || value[0] === null && value[1] === null) fail(label + ".reads");
150
+ return value.map((list) => {
151
+ if (list === null) return null;
152
+ if (!Array.isArray(list) || !list.length || list.some((name) => typeof name !== "string" || !identifier.test(name)) || new Set(list).size !== list.length) fail(label + ".reads");
153
+ return [...list].sort();
154
+ });
155
+ }
156
+ function hiddenByBranch(table) {
157
+ const reads = table.policy && table.policy.k === "a" ? table.policy.reads : void 0;
158
+ if (!reads) return /* @__PURE__ */ new Set();
159
+ const hidden = /* @__PURE__ */ new Set();
160
+ for (const list of reads) if (list) {
161
+ for (const column of table.columns) if (!list.includes(column.n)) hidden.add(column.n);
162
+ }
163
+ return hidden;
164
+ }
147
165
  function policyRoles(value, label) {
148
166
  record(value, label + ".roles");
149
167
  if (Object.keys(value).sort().join(",") !== "create,delete,read,update") fail(label);
@@ -381,10 +399,10 @@ var require_declared_data_contract = __commonJS({
381
399
  const type = { integer: "number", bigint: "string", number: "number", text: "string", timestamp: "string", boolean: "boolean", json: "Json", blob: "number[]" }[column.t];
382
400
  return column.nul ? `${type} | null` : type;
383
401
  }
384
- function shape(columns, names, mode, required = /* @__PURE__ */ new Set()) {
402
+ function shape(columns, names, mode, required = /* @__PURE__ */ new Set(), hidden = /* @__PURE__ */ new Set()) {
385
403
  const values = names.map((name) => {
386
404
  const column = columns.find((candidate) => candidate.n === name);
387
- const optional = mode === "update" || mode === "create" && !required.has(name) && (column.d || column.nul);
405
+ const optional = mode === "update" || mode === "create" && !required.has(name) && (column.d || column.nul) || mode === "read" && hidden.has(name);
388
406
  return `${JSON.stringify(name)}${optional ? "?" : ""}: ${columnType(column)}`;
389
407
  });
390
408
  return values.length ? `{ ${values.join("; ")} }` : "Record<string, never>";
@@ -548,13 +566,14 @@ async function intake(table, reference, values, extra) {
548
566
  export interface IntakeResult { readonly accepted: true; readonly idempotency: InsertReceipt; readonly request: InsertRequest<IntakeResult> }
549
567
  `;
550
568
  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";
569
+ 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';
551
570
  var INSERT_CLIENT_DECLARATIONS = `
552
571
  export interface InsertReceipt { readonly replayed: boolean; readonly created_at: number; readonly result_until: number; readonly refuse_until: number; }
553
572
  export interface InsertRequest<T> { readonly key: string; readonly createdAt: number; retry(): Promise<T>; }
554
573
  export type InsertResult<T> = T & { readonly idempotency: InsertReceipt; readonly request: InsertRequest<InsertResult<T>> };
555
574
  `;
556
- var CLIENT_GENERATION = 3;
557
- var ACCEPTED_CLIENT_GENERATIONS = Object.freeze([3, 2, 1]);
575
+ var CLIENT_GENERATION = 5;
576
+ var ACCEPTED_CLIENT_GENERATIONS = Object.freeze([5, 4, 3, 2, 1]);
558
577
  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}};';
559
578
  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;';
560
579
  function describeDataClient(authority, contract_digest, insertReceiptCapability, localTypes = false, generation = CLIENT_GENERATION) {
@@ -574,17 +593,19 @@ async function intake(table, reference, values, extra) {
574
593
  for (const table of tables) {
575
594
  const { name, columns, client, primaryKey } = table;
576
595
  const readable = client.read === true ? columns.filter((column) => column.n !== table.author).map((column) => column.n) : client.read || [];
577
- const row = shape(columns, readable, "read");
596
+ const hidden = generation >= 5 ? hiddenByBranch(table) : /* @__PURE__ */ new Set();
597
+ const row = shape(columns, readable, "read", /* @__PURE__ */ new Set(), hidden);
578
598
  const id = columnType(columns.find((column) => column.n === primaryKey));
579
599
  const operations = [];
580
600
  const methods = [];
581
601
  if (client.read !== false) {
582
- const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t));
602
+ const equality = readable.filter((field) => !["json", "blob"].includes(columns.find((column) => column.n === field).t) && !hidden.has(field));
583
603
  if (generation >= 2) {
584
604
  const listOptions = `limit?: L & (number extends L ? unknown : [L] extends [PageSize] ? unknown : never); after?: ${id}; where?: ${shape(columns, equality, "update")}`;
585
- operations.push(`list<const L extends number = number>(options: { ${listOptions}; can: true }): Promise<{ data: (${row} & { can: RowCan })[]; next: ${id} | null; has_more: boolean }>`);
605
+ const hinted = generation >= 4 ? "RowHints" : "{ can: RowCan }";
606
+ operations.push(`list<const L extends number = number>(options: { ${listOptions}; can: true }): Promise<{ data: (${row} & ${hinted})[]; next: ${id} | null; has_more: boolean }>`);
586
607
  operations.push(`list<const L extends number = number>(options?: { ${listOptions}; can?: false }): Promise<{ data: ${row}[]; next: ${id} | null; has_more: boolean }>`);
587
- operations.push(`get(id: ${id}, options: { can: true }): Promise<{ data: (${row} & { can: RowCan }) | null }>`);
608
+ operations.push(`get(id: ${id}, options: { can: true }): Promise<{ data: (${row} & ${hinted}) | null }>`);
588
609
  operations.push(`get(id: ${id}, options?: { can?: false }): Promise<{ data: ${row} | null }>`);
589
610
  } else {
590
611
  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 }>`);
@@ -721,7 +742,7 @@ async function intake(table, reference, values, extra) {
721
742
  const declaration = `declare module ${JSON.stringify("somewhere:data")} {
722
743
  type PageSize = ${Array.from({ length: 100 }, (_, i) => i + 1).join(" | ")};
723
744
  export type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
724
- ${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); }
745
+ ${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); }
725
746
  export const data: { ${declarations.join(";\n ")} };
726
747
  }
727
748
  `;
@@ -1279,104 +1300,6 @@ var SENSITIVE_PARAMS = [
1279
1300
  ];
1280
1301
  var SENSITIVE_PARAM_NAMES = new Set(SENSITIVE_PARAMS);
1281
1302
 
1282
- // shared/draft-execution-authority.ts
1283
- var DRAFT_PLATFORM_SERVICE_BINDING = "PLATFORM_API";
1284
-
1285
- // worker/src/runtime/draft-auth-capability.ts
1286
- var DRAFT_AUTH_CAPABILITY_JS = `
1287
- const __sw_authOwnDescriptor = Object.getOwnPropertyDescriptor;
1288
- const __sw_authBearerMatches = Function.prototype.call.bind(RegExp.prototype.test, /^Bearer [A-Za-z0-9_-]+[.][A-Za-z0-9_-]+[.][A-Za-z0-9_-]+$/);
1289
- function __sw_authOwnValue(object, key) {
1290
- if (!object || typeof object !== 'object') return undefined;
1291
- const descriptor = __sw_authOwnDescriptor(object, key);
1292
- return descriptor && descriptor.value;
1293
- }
1294
- function __sw_draftAuthRead(input, init, projectId) {
1295
- if (input !== 'https://api.somewhere.tech/v1/auth/me') return null;
1296
- const method = __sw_authOwnValue(init, 'method');
1297
- const headers = __sw_authOwnValue(init, 'headers');
1298
- const bearer = __sw_authOwnValue(headers, 'Authorization');
1299
- if ((method !== undefined && method !== 'GET') || __sw_authOwnValue(init, 'body') != null
1300
- || typeof bearer !== 'string' || !__sw_authBearerMatches(bearer)) {
1301
- const error = new Error('Draft identity verification requires an existing app access token.');
1302
- error.code = 'DRAFT_PLATFORM_CAPABILITY_DENIED';
1303
- throw error;
1304
- }
1305
- return {
1306
- __proto__: null, method: 'GET', redirect: 'manual',
1307
- headers: { __proto__: null, Authorization: bearer, 'X-Sw-Executing-Project': projectId, 'X-No-Auto-Refresh': '1' },
1308
- };
1309
- }
1310
- `;
1311
-
1312
- // worker/src/runtime/draft-platform-transport.ts
1313
- var DRAFT_PLATFORM_TRANSPORT_JS = `// Exact draft database/files/log calls use a separate service binding that
1314
- // native disallow_importable_env keeps outside customer module authority.
1315
- let __sw_platformService = null;
1316
- let __sw_draftPlatformProjectId = null;
1317
- function __sw_bindPlatformService(env) {
1318
- const binding = env && env.${DRAFT_PLATFORM_SERVICE_BINDING};
1319
- if (!binding || typeof binding.fetch !== 'function') {
1320
- throw new Error('Draft platform transport is unavailable.');
1321
- }
1322
- if (__sw_platformService && __sw_platformService !== binding) {
1323
- throw new Error('Draft platform transport changed within one function isolate.');
1324
- }
1325
- const projectId = env && env.PROJECT_ID;
1326
- if (typeof projectId !== 'string' || !projectId) {
1327
- throw new Error('Draft platform transport has no project identity.');
1328
- }
1329
- if (__sw_draftPlatformProjectId && __sw_draftPlatformProjectId !== projectId) {
1330
- throw new Error('Draft platform project identity changed within one function isolate.');
1331
- }
1332
- __sw_platformService = binding;
1333
- __sw_draftPlatformProjectId = projectId;
1334
- }
1335
- ${DRAFT_AUTH_CAPABILITY_JS}
1336
- function __sw_platformFetch(input, init) {
1337
- if (!__sw_platformService || !__sw_draftPlatformProjectId) {
1338
- throw new Error('Draft platform transport is unavailable.');
1339
- }
1340
- const authRead = __sw_draftAuthRead(input, init, __sw_draftPlatformProjectId);
1341
- if (authRead) return __sw_platformService.fetch('https://api.somewhere.tech/v1/auth/me', authRead);
1342
- let url;
1343
- let method;
1344
- try {
1345
- const request = input instanceof Request ? input : null;
1346
- url = new URL(request ? request.url : String(input));
1347
- method = String((init && init.method) || (request && request.method) || 'GET').toUpperCase();
1348
- } catch (_) {
1349
- const error = new Error('Draft platform capability denied an unreadable request.');
1350
- error.code = 'DRAFT_PLATFORM_CAPABILITY_DENIED';
1351
- throw error;
1352
- }
1353
- const fsRoot = '/v1/fs/' + encodeURIComponent(__sw_draftPlatformProjectId);
1354
- const allowed =
1355
- ((url.pathname === '/v1/db/query' || url.pathname === '/v1/db/batch' || url.pathname === '/v1/db/raw-read' || url.pathname === '/v1/fs/upload-url') && method === 'POST') ||
1356
- // Split storage: the structured managed endpoints. Each one requires this
1357
- // exact draft identity and the artifact's signed release, and resolves the
1358
- // ready clone of that draft and candidate itself (readManagedExecution).
1359
- ((url.pathname === '/v1/db/managed/user-memory' || url.pathname === '/v1/db/managed/user' || url.pathname === '/v1/db/managed/server' || url.pathname === '/v1/db/managed/public'
1360
- || url.pathname === '/v1/db/managed/visitor' || url.pathname === '/v1/db/managed/visitor-session') && method === 'POST') ||
1361
- (url.pathname === '/v1/logs' && (method === 'GET' || method === 'POST')) ||
1362
- (url.pathname === '/v1/traces' && method === 'POST') ||
1363
- // A preview job's signed delivery: verified only for a job bound to this draft.
1364
- (url.pathname === '/v1/jobs/verify-invocation' && method === 'POST') ||
1365
- ((url.pathname === fsRoot || url.pathname.startsWith(fsRoot + '/')) &&
1366
- (method === 'GET' || method === 'PUT' || method === 'POST' || method === 'DELETE'));
1367
- if (!allowed) {
1368
- const error = new Error('Draft platform capability denied ' + method + ' ' + url.pathname + '.');
1369
- error.code = 'DRAFT_PLATFORM_CAPABILITY_DENIED';
1370
- throw error;
1371
- }
1372
- return __sw_platformService.fetch(input, init);
1373
- }
1374
- function __sw_hasExactDraftIdentity(request) {
1375
- const draftId = request.headers.get('X-Sw-Draft-Id');
1376
- const candidate = request.headers.get('X-Sw-Draft-Candidate');
1377
- return !!(draftId && candidate);
1378
- }`;
1379
-
1380
1303
  // worker/src/utils/function-bundle-transport.ts
1381
1304
  var NATIVE_RUNTIME_SERVICE_BINDING = "RUNTIME_PLATFORM_API";
1382
1305
  var MANAGED_RUNTIME_SERVICE_BINDING = "RUNTIME_MANAGED_DATA";
@@ -2059,6 +1982,10 @@ async function __sw_hotConfigPull(env) {
2059
1982
  }
2060
1983
  `;
2061
1984
 
1985
+ // worker/src/utils/app-access-lifetime.ts
1986
+ var APP_ACCESS_MAX_SECONDS = 60;
1987
+ var APP_ACCESS_SELECTION_MARGIN_SECONDS = 5;
1988
+
2062
1989
  // worker/src/runtime/access-token.ts
2063
1990
  var ACCESS_TOKEN_JS = `const __sw_accessNow = Date.now.bind(Date);
2064
1991
  const __sw_accessInteger = Number.isSafeInteger.bind(Number);
@@ -2079,7 +2006,7 @@ async function __sw_verifyAccessToken(binding, token, audience) {
2079
2006
  || typeof p.email !== 'string' || typeof p.sid !== 'string' || !p.sid || p.session_binding_version !== 1
2080
2007
  || !__sw_accessInteger(p.tv) || (p.role !== 'user' && p.role !== 'admin')
2081
2008
  || !__sw_accessInteger(p.iat) || !__sw_accessInteger(p.exp) || p.iat * 1000 > now || p.exp * 1000 <= now
2082
- || p.exp <= p.iat || p.exp - p.iat > 60
2009
+ || p.exp <= p.iat || p.exp - p.iat > ${APP_ACCESS_MAX_SECONDS}
2083
2010
  || !p.user || Array.isArray(p.user) || p.user.id !== p.sub || p.user.email !== p.email || p.user.role !== p.role
2084
2011
  || 'impersonating' in p || 'impersonator_id' in p || 'impersonated_by' in p || 'registration_id' in p) return null;
2085
2012
  return p;
@@ -2116,7 +2043,7 @@ var CONTEXT_HEAD_JS = `// \u2500\u2500 Primordial freeze (tsk_327fe8a4, opiniona
2116
2043
  // JSON.parse = ... or Array.prototype.push = ... is a no-op in sloppy mode and
2117
2044
  // throws in strict; either way the platform guard is untouched. Freezing the
2118
2045
  // prototypes also blocks Object.prototype pollution. Monkey-patching core
2119
- // built-ins is a deprecated, rare practice (a handful of old polyfill packages);
2046
+ // built-ins is a rare practice (a handful of old polyfill packages);
2120
2047
  // modern code never needs it and it is explicitly UNSUPPORTED. We freeze only
2121
2048
  // what the guard depends on (Reflect/Date are deliberately NOT frozen \u2014
2122
2049
  // reflect-metadata / decorator libraries add to Reflect, and the guard no longer
@@ -2618,7 +2545,7 @@ function buildPlatformContext(env, request, runtimeFetch = fetch, outboundFetch
2618
2545
  }
2619
2546
  const isDraftExecution = draftId !== null;
2620
2547
  const projectEnv = isDraftExecution ? 'draft' : (env.PROJECT_ENV || 'dev');
2621
- const platformDomain = env.PLATFORM_DOMAIN || 'somewhere.tech';
2548
+ const platformDomain = env.PLATFORM_DOMAIN || 'somewhere.cloud';
2622
2549
  const tenantDomain = env.TENANT_DOMAIN || platformDomain;
2623
2550
  const platformBase = env.PLATFORM_API_BASE || ('https://api.' + platformDomain);
2624
2551
  const apiKey = env.PROJECT_API_KEY;
@@ -2658,8 +2585,7 @@ function buildPlatformContext(env, request, runtimeFetch = fetch, outboundFetch
2658
2585
  // WHERE A STATEMENT ACTUALLY WENT, said once per request (tsk_5037f8f1).
2659
2586
  // The per-statement number is the same 30-40 ms whether one statement runs
2660
2587
  // or thirty, so the question is never "what did we add" but "how far away is
2661
- // the database and which transport reached it" \u2014 and that was previously
2662
- // unanswerable from a response. sw_db_via names the transport (the binding,
2588
+ // the database and which transport reached it". sw_db_via names the transport (the binding,
2663
2589
  // or the platform data plane), sw_db_edge the colo the function ran in, and
2664
2590
  // sw_db_home where the database answered from. Same-value edge and home mean
2665
2591
  // the residue is ours; different values mean it is distance, and no amount
@@ -4343,7 +4269,7 @@ function createBoundedReplyReader(maxBytes) {
4343
4269
  } catch {
4344
4270
  return { ok: false, reason: "transport" };
4345
4271
  } finally {
4346
- clearTimeout(timer);
4272
+ if (timer !== void 0) clearTimeout(timer);
4347
4273
  }
4348
4274
  }
4349
4275
  };
@@ -5360,6 +5286,7 @@ function renderPolicySql(request) {
5360
5286
  const qualifier = r.qualifier;
5361
5287
  const proposed = r.proposed;
5362
5288
  const params = [];
5289
+ if (r.branch !== void 0 && (!policy || policy.k !== "a")) throw new Error("policy-sql: a branch admission needs an anyOf policy");
5363
5290
  if (policy && policy.k === "o") {
5364
5291
  if (proposed) return { sql: "1", params: [] };
5365
5292
  params.push(r.principal);
@@ -5436,7 +5363,10 @@ function renderPolicySql(request) {
5436
5363
  for (let i = 0; i < team.params.length; i++) gated.push(team.params[i]);
5437
5364
  left = { sql: "(" + left.sql + " AND " + team.sql + ")", params: gated };
5438
5365
  }
5366
+ if (r.branch === 0) return left;
5439
5367
  const right = renderPolicySql({ render: "predicate", principal: r.principal, emailDomain: r.emailDomain, table: r.table, physical: r.physical, policy: policy.p[1], operation: r.operation, qualifier, proposed, edge: r.edge });
5368
+ if (r.branch === 1) return right;
5369
+ if (r.branch !== void 0) throw new Error("policy-sql: an anyOf branch is 0 or 1");
5440
5370
  for (let i = 0; i < left.params.length; i++) params.push(left.params[i]);
5441
5371
  for (let i = 0; i < right.params.length; i++) params.push(right.params[i]);
5442
5372
  return { sql: "(" + left.sql + " OR " + right.sql + ")", params };
@@ -5801,8 +5731,10 @@ function renderQuerySql(ir, principalId, returning, context) {
5801
5731
  params.push(rendered.params[i]);
5802
5732
  return rendered.sql;
5803
5733
  },
5804
- policyPredicate(policy, qualifier, proposedValues, operation) {
5805
- return helpers.policySql(context.renderPolicy({ render: "predicate", principal: principalId, table: ir.table.name, physical: context.physicalMap(), policy, operation: operation || null, qualifier: qualifier || null, proposed: proposedValues || null }));
5734
+ policyPredicate(policy, qualifier, proposedValues, operation, branch) {
5735
+ const request = { render: "predicate", principal: principalId, table: ir.table.name, physical: context.physicalMap(), policy, operation: operation || null, qualifier: qualifier || null, proposed: proposedValues || null };
5736
+ if (branch !== void 0) request.branch = branch;
5737
+ return helpers.policySql(context.renderPolicy(request));
5806
5738
  },
5807
5739
  memberScopePredicate(member, qualifier, operation) {
5808
5740
  return helpers.policySql(context.renderPolicy({ render: "member", principal: principalId, table: ir.table.name, physical: context.physicalMap(), member, operation: operation || null, qualifier: qualifier || null }));
@@ -5926,7 +5858,7 @@ function renderQuerySql(ir, principalId, returning, context) {
5926
5858
  if (scope.mode === "member")
5927
5859
  return helpers.memberScopePredicate(scope.member, null, op);
5928
5860
  if (scope.mode === "policy")
5929
- return helpers.policyPredicate(scope.policy, null, null, op);
5861
+ return helpers.policyPredicate(scope.policy, null, null, op, scope.branch);
5930
5862
  if (scope.mode === "link")
5931
5863
  return helpers.linkGuard(scope.link, tableSql + "." + q(scope.link.column), null);
5932
5864
  return null;
@@ -6011,8 +5943,11 @@ function renderQuerySql(ir, principalId, returning, context) {
6011
5943
  let sql;
6012
5944
  if (ir.kind === "select") {
6013
5945
  const cols = ir.columns && ir.columns.length ? context.map(ir.columns, q).join(", ") : "*";
6014
- const canCols = ir.can ? ", " + helpers.canHint(ir.can.update, "update") + " AS " + q("__sw_can_update") + ", " + helpers.canHint(ir.can.delete, "delete") + " AS " + q("__sw_can_delete") : "";
6015
- sql = "SELECT " + cols + canCols + " FROM " + tableSql + helpers.whereSql();
5946
+ const canCols = ir.can ? ", " + helpers.canHint(ir.can.update, "update") + " AS " + q("__sw_can_update") + ", " + helpers.canHint(ir.can.delete, "delete") + " AS " + q("__sw_can_delete") + ", " + helpers.canHint(ir.can.mine, "read") + " AS " + q("__sw_mine") : "";
5947
+ let branchCols = "";
5948
+ for (let i = 0; i < (ir.branchReads || []).length; i++)
5949
+ branchCols += ", " + helpers.canHint(ir.branchReads[i], "read") + " AS " + q("__sw_branch_" + i);
5950
+ sql = "SELECT " + cols + canCols + branchCols + " FROM " + tableSql + helpers.whereSql();
6016
5951
  if (ir.order && ir.order.length) {
6017
5952
  sql += " ORDER BY " + context.map(ir.order, function(o) {
6018
5953
  return q(o.column) + " " + (o.dir === "desc" ? "DESC" : "ASC");
@@ -6371,7 +6306,7 @@ var DB_GENERATED_OWNER_READ_JS = ` function __sw_registerGeneratedOwnerRead
6371
6306
  if (!payload || payload.type !== 'app_user' || payload.project_id !== projectId
6372
6307
  || typeof payload.sub !== 'string' || !payload.sub || payload.sub.length > 200
6373
6308
  || typeof payload.exp !== 'number' || !__sw_generatedOwnerFinite(payload.exp)
6374
- || payload.exp - __sw_generatedOwnerNow() / 1000 < 120) return null;
6309
+ || payload.exp - __sw_generatedOwnerNow() / 1000 < ${APP_ACCESS_SELECTION_MARGIN_SECONDS}) return null;
6375
6310
  // A private header selector, never a principal/auth memo or live brand.
6376
6311
  const selected = { token: token, selector: payload.sub };
6377
6312
  return function (options) { return __sw_structFrom(table, options, undefined, selected); };
@@ -6446,7 +6381,11 @@ var DB_PUBLIC_READ_JS = DB_GENERATED_OWNER_READ_JS + ` function __sw_shared
6446
6381
  }
6447
6382
  if (canPrincipal === undefined) refuse();
6448
6383
  const sameCaller = function (resolved) { return resolved && resolved.principalId === canPrincipal ? resolved.scope : null; };
6449
- can = { update: sameCaller(forUpdate), delete: sameCaller(forDelete) };
6384
+ // mine (tsk_311011c1): the column that records who created a row \u2014 the owner column, or a
6385
+ // shared table's author column \u2014 when the table has one. The hint renders it as a boolean.
6386
+ const creatorShape = __sw_structManagedShape(table);
6387
+ const creatorColumn = __sw_structResolveOwnerColumn(table) || (creatorShape && creatorShape.author) || null;
6388
+ can = { update: sameCaller(forUpdate), delete: sameCaller(forDelete), mine: creatorColumn ? { mode: 'user', ownerColumn: creatorColumn } : null };
6450
6389
  }
6451
6390
  // Composed joins (tsk_1186c9a): resolve declared relations. Both absent \u2192
6452
6391
  // ir.include / ir.relationWhere stay unset and the IR (and its shape
@@ -6478,16 +6417,18 @@ var DB_PUBLIC_READ_JS = DB_GENERATED_OWNER_READ_JS + ` function __sw_shared
6478
6417
  return result;
6479
6418
  }
6480
6419
 
6481
- // The renderer returns the hints as two internal 0/1 columns; each row instead carries
6482
- // can: { update, delete } and the internal columns never reach the caller.
6420
+ // The renderer returns the hints as internal 0/1 columns; each row instead carries
6421
+ // can: { update, delete } and mine, and the internal columns never reach the caller.
6483
6422
  function __sw_structAttachCan(result) {
6484
6423
  const rows = result && __sw_arrayIsArray(result.data) ? result.data : [];
6485
6424
  for (let i = 0; i < rows.length; i++) {
6486
6425
  const row = rows[i];
6487
6426
  if (!row || typeof row !== 'object') continue;
6488
6427
  row.can = { update: row.__sw_can_update === 1 || row.__sw_can_update === true, delete: row.__sw_can_delete === 1 || row.__sw_can_delete === true };
6428
+ row.mine = row.__sw_mine === 1 || row.__sw_mine === true;
6489
6429
  delete row.__sw_can_update;
6490
6430
  delete row.__sw_can_delete;
6431
+ delete row.__sw_mine;
6491
6432
  }
6492
6433
  }
6493
6434
 
@@ -7006,11 +6947,28 @@ var DB_POLICY_HELPERS_JS = `${ROLE_DOMAIN_MAP_JS}
7006
6947
  }
7007
6948
  return true;
7008
6949
  }
6950
+ // Per-branch read lists (tsk_1ac01f6a): [owner list | null, other list | null], at the table's
6951
+ // own anyOf only; each list is a non-empty sorted set of lowercase column identifiers.
6952
+ function __sw_structPolicyReads(value) {
6953
+ if (!__sw_arrayIsArray(value) || value.length !== 2 || (value[0] === null && value[1] === null)) return false;
6954
+ for (let i = 0; i < 2; i++) {
6955
+ const list = value[i];
6956
+ if (list === null) continue;
6957
+ if (!__sw_arrayIsArray(list) || list.length === 0) return false;
6958
+ for (let j = 0; j < list.length; j++) {
6959
+ if (typeof list[j] !== 'string' || !__sw_reMatch(/^[a-z_][a-z0-9_]*$/, list[j]) || (j > 0 && !(list[j - 1] < list[j]))) return false;
6960
+ }
6961
+ }
6962
+ return true;
6963
+ }
7009
6964
  // anyOf(owner, member | group | app role); a group or app-role branch is
7010
- // a pinned leaf, exactly as at the root.
7011
- function __sw_structPolicyAny(value) {
7012
- if (!value || typeof value !== 'object' || __sw_keys(value).slice().sort().join(',') !== 'k,p' || value.k !== 'a'
7013
- || !__sw_arrayIsArray(value.p) || value.p.length !== 2 || !__sw_structPolicyLeaf(value.p[0]) || value.p[0].k !== 'o') return false;
6965
+ // a pinned leaf, exactly as at the root. root: the table's own rule may
6966
+ // carry per-branch read lists (reads); under allOf or a parent it may not.
6967
+ function __sw_structPolicyAny(value, root) {
6968
+ if (!value || typeof value !== 'object' || value.k !== 'a') return false;
6969
+ const anyKeys = __sw_keys(value).slice().sort().join(',');
6970
+ if (anyKeys !== 'k,p' && !(root === true && anyKeys === 'k,p,reads' && __sw_structPolicyReads(value.reads))) return false;
6971
+ if (!__sw_arrayIsArray(value.p) || value.p.length !== 2 || !__sw_structPolicyLeaf(value.p[0]) || value.p[0].k !== 'o') return false;
7014
6972
  const second = value.p[1];
7015
6973
  if (second && second.k === 'm' && second.m && second.m.og && value.p[0].c !== 'user_id') return false;
7016
6974
  return (__sw_structPolicyLeaf(second) && second.k === 'm') || __sw_structPolicyGroup(second) || __sw_structPolicyRole(second);
@@ -7041,7 +6999,7 @@ var DB_POLICY_HELPERS_JS = `${ROLE_DOMAIN_MAP_JS}
7041
6999
  }
7042
7000
  // A parent chain: at most four edges, each ending in a leaf, anyOf or allOf.
7043
7001
  function __sw_structPolicyValid(value, edges) {
7044
- if (!edges && (__sw_structPolicyAny(value) || __sw_structPolicyAllOf(value) || __sw_structPolicyGroup(value) || __sw_structPolicyRole(value))) return true;
7002
+ if (!edges && (__sw_structPolicyAny(value, true) || __sw_structPolicyAllOf(value) || __sw_structPolicyGroup(value) || __sw_structPolicyRole(value))) return true;
7045
7003
  const above = edges || 0;
7046
7004
  return value && typeof value === 'object' && __sw_keys(value).slice().sort().join(',') === 'k,p,pk,t,v' && value.k === 'p'
7047
7005
  && above + 1 <= 4
@@ -7752,7 +7710,7 @@ var DB_JS = ` db: (function () {
7752
7710
  // silently running the SQL as written \u2014 is what keeps every existing
7753
7711
  // scoped call from becoming an unscoped full-table read.
7754
7712
  function __sw_throwRawSqlCannotBePlatformScoped(api) {
7755
- const err = new Error(api + ' no longer applies platform scoping to raw SQL. ' +
7713
+ const err = new Error(api + ' does not apply platform scoping to raw SQL. ' +
7756
7714
  'Platform-proven scoping exists only on structured queries the platform composes. ' +
7757
7715
  'Use sw.db.from/count/insert/update/remove/tx for declared operations. ' +
7758
7716
  'For intentional server-authority SQL, authorize the caller first, then use sw.db.server.query/batch; your function is responsible for that authorization.');
@@ -9458,7 +9416,7 @@ ${DB_AGGREGATE_JS}
9458
9416
  // Structured query builder (tsk_5b91e0e4). The platform composes,
9459
9417
  // renders, and scopes these \u2014 see the __sw_struct* helpers above.
9460
9418
  // sw.db.delete is a trivial alias of sw.db.remove (both call the same
9461
- // composer). The retired scope argument fails loudly, never silently.
9419
+ // composer). A scope argument fails loudly, never silently.
9462
9420
  from(table, opts, legacyScope) { __sw_structNoScopeArg(legacyScope, 'from'); return __sw_structFrom(table, opts); },
9463
9421
  live(name, read) {
9464
9422
  if (arguments.length !== 2) __sw_structThrow('sw.db.live takes exactly (name, read).');
@@ -9469,9 +9427,9 @@ ${DB_AGGREGATE_JS}
9469
9427
  // group-by over the rows the caller is allowed to read.
9470
9428
  aggregate(table, opts, legacyScope) { __sw_structNoScopeArg(legacyScope, 'aggregate'); return __sw_structAggregate(table, opts); },
9471
9429
  // insert's third argument is the OPTIONS object ({ onConflict } \u2014
9472
- // tsk_3e9b033c). The retired scope-argument shapes ({ user }, {
9473
- // unscoped }) still fail loudly as before: they used to change
9474
- // semantics, so they must never be read as options.
9430
+ // tsk_3e9b033c). The scope-argument shapes ({ user }, { unscoped })
9431
+ // fail loudly: they would change semantics, so they are never read
9432
+ // as options.
9475
9433
  insert(table, values, opts) {
9476
9434
  if (opts !== undefined && opts !== null && typeof opts === 'object' && !__sw_arrayIsArray(opts) && (__sw_own(opts, 'user') || __sw_own(opts, 'unscoped'))) {
9477
9435
  __sw_structNoScopeArg(opts, 'insert');
@@ -9639,7 +9597,7 @@ var DB_RAW_JS = ` var __sw_rawReadDbInstance = null;
9639
9597
  __sw_assertSingleStatement(sql);
9640
9598
  __sw_assertNoDdl(sql);
9641
9599
  options = options || {};
9642
- // { user } is retired (tsk_fde5d117): the platform never rewrites raw
9600
+ // { user } is refused (tsk_fde5d117): the platform never rewrites raw
9643
9601
  // SQL to inject an owner filter. Preserve its specific loud failure.
9644
9602
  if (__sw_own(options, 'user')) {
9645
9603
  __sw_throwRawSqlCannotBePlatformScoped('sw.db.query');
@@ -11973,7 +11931,7 @@ var AI_JS = ` ai: (function () {
11973
11931
  // "undefined is not a function". Build the tool explicitly with
11974
11932
  // sw.db + sw.ai.chatWithTools, or use the manifest-based tool when it lands.
11975
11933
  function catalogTool() {
11976
- const err = new Error('sw.ai.catalogTool has been retired. Assemble the search tool explicitly from sw.db + sw.ai.chatWithTools, or use the manifest-based AI DB tool. See the platform docs (ai) for the migration.');
11934
+ const err = new Error('sw.ai.catalogTool is not part of the runtime. Assemble the search tool explicitly from sw.db + sw.ai.chatWithTools, or use the manifest-based AI DB tool. The platform docs (ai) show both.');
11977
11935
  err.code = 'CATALOG_TOOL_RETIRED';
11978
11936
  err.status = 400;
11979
11937
  throw __sw_markExpected(err);
@@ -14301,7 +14259,7 @@ ${AUTH_SERVER_JS}
14301
14259
  return { id: visitor.id, isAnon: true, expiresAt: visitor.expiresAt };
14302
14260
  },
14303
14261
  async migrateAnon() {
14304
- const err = new Error('Caller-selected anonymous row migrations are no longer supported. Use declared visitor ownership.');
14262
+ const err = new Error('Caller-selected anonymous row migrations are not supported. Use declared visitor ownership.');
14305
14263
  err.code = 'MIGRATE_ANON_REMOVED'; err.status = 400; err.__sw_expose = true;
14306
14264
  throw err;
14307
14265
  },
@@ -16009,7 +15967,7 @@ var CONTEXT_TAIL_JS = ` };
16009
15967
  // teaches nothing (pfb_3645abbfa717). One thrower, mounted on both the
16010
15968
  // standard sw and the draft proxy, so the answer is identical either way.
16011
15969
  function __sw_realtimeRetired() {
16012
- const err = new Error('sw.realtime is retired and has no replacement namespace. For browser live updates, declare the read in a function with sw.db.live(name, sw.db.from(...)) and subscribe with watchLive from /__sw/live/client.js. For durable events and history, write rows with sw.db.');
15970
+ const err = new Error('sw.realtime is not part of the runtime. For browser live updates, declare the read in a function with sw.db.live(name, sw.db.from(...)) and subscribe with watchLive from /__sw/live/client.js. For durable events and history, write rows with sw.db.');
16013
15971
  err.code = 'REALTIME_RETIRED';
16014
15972
  err.status = 410;
16015
15973
  throw __sw_markExpected(err);
@@ -16231,6 +16189,17 @@ var import_declared_data_contract = __toESM(require_declared_data_contract());
16231
16189
  var PUBLIC_RESULT_MAX_AGE_SECONDS = 120;
16232
16190
 
16233
16191
  // worker/src/runtime/declared-data-read-plan.ts
16192
+ function declaredBranchReads(shape) {
16193
+ const policy = shape.policy;
16194
+ if (!policy || policy.k !== "a" || !Array.isArray(policy.reads)) return null;
16195
+ const reads = policy.reads;
16196
+ const hidden = [];
16197
+ for (const list of reads) {
16198
+ if (!list) continue;
16199
+ for (const column of shape.columns) if (list.indexOf(column.n) === -1 && hidden.indexOf(column.n) === -1) hidden.push(column.n);
16200
+ }
16201
+ return { reads, hidden };
16202
+ }
16234
16203
  function declaredReadFields(shape) {
16235
16204
  const policy = shape.client;
16236
16205
  return policy.read === true ? shape.columns.filter((column) => column.n !== shape.author).map((column) => column.n) : Array.isArray(policy.read) ? policy.read : [];
@@ -16256,6 +16225,14 @@ function declaredListGetPlan(operation, input, shape, fields, fail) {
16256
16225
  };
16257
16226
  const primaryKey = shape.clientPrimaryKey;
16258
16227
  const primaryColumn = shape.columns.find((column) => column.n === primaryKey);
16228
+ const branchPolicy = shape.policy;
16229
+ const branchHidden = [];
16230
+ if (branchPolicy && branchPolicy.k === "a" && Array.isArray(branchPolicy.reads)) {
16231
+ for (const list of branchPolicy.reads) {
16232
+ if (!list) continue;
16233
+ for (const column of shape.columns) if (list.indexOf(column.n) === -1 && branchHidden.indexOf(column.n) === -1) branchHidden.push(column.n);
16234
+ }
16235
+ }
16259
16236
  const where = /* @__PURE__ */ Object.create(null);
16260
16237
  if (operation === "get") helpers.and(where, primaryKey, helpers.key(input.id, primaryColumn));
16261
16238
  else {
@@ -16265,6 +16242,9 @@ function declaredListGetPlan(operation, input, shape, fields, fail) {
16265
16242
  for (const field of Object.keys(filters)) {
16266
16243
  const column = shape.columns.find((item) => item.n === field);
16267
16244
  const value = filters[field];
16245
+ if (branchHidden.indexOf(field) !== -1) {
16246
+ fail(400, "DATA_INPUT_INVALID", 'where cannot name "' + field + '": an access branch of this table leaves it out, so only columns every reader may read can filter.');
16247
+ }
16268
16248
  if (!fields.includes(field) || !column || column.t === "json" || column.t === "blob" || !(value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value))) {
16269
16249
  fail(400, "DATA_INPUT_INVALID", "where accepts only equality values for readable scalar fields.");
16270
16250
  }
@@ -16285,9 +16265,12 @@ function declaredListGetPayload(operation, rows, fields, primaryKey, limit) {
16285
16265
  project(row) {
16286
16266
  if (row === null || row === void 0) return null;
16287
16267
  const result = /* @__PURE__ */ Object.create(null);
16288
- for (const field of fields) result[field] = row[field];
16268
+ for (const field of fields) if (Object.prototype.hasOwnProperty.call(row, field)) result[field] = row[field];
16289
16269
  const can = row.can;
16290
- if (can && typeof can === "object") result.can = { update: can.update === true, delete: can.delete === true };
16270
+ if (can && typeof can === "object") {
16271
+ result.can = { update: can.update === true, delete: can.delete === true };
16272
+ result.mine = row.mine === true;
16273
+ }
16291
16274
  return result;
16292
16275
  }
16293
16276
  };
@@ -16300,7 +16283,8 @@ function declaredListGetPayload(operation, rows, fields, primaryKey, limit) {
16300
16283
  has_more: hasMore
16301
16284
  };
16302
16285
  }
16303
- var DECLARED_DATA_READ_PLAN_SOURCE = `const __sw_dataReadFields = (${declaredReadFields.toString()});
16286
+ var DECLARED_DATA_READ_PLAN_SOURCE = `const __sw_dataBranchReads = (${declaredBranchReads.toString()});
16287
+ const __sw_dataReadFields = (${declaredReadFields.toString()});
16304
16288
  const __sw_dataListGetPlan = (${declaredListGetPlan.toString()});
16305
16289
  const __sw_dataListGetPayload = (${declaredListGetPayload.toString()});`;
16306
16290
 
@@ -16487,9 +16471,10 @@ function __sw_dataAnd(where, field, predicate) {
16487
16471
  // decoded once at the database seam (booleans, json, blob), so this only
16488
16472
  // narrows a row to the fields the client may read.
16489
16473
  function __sw_dataProject(row, fields) {
16474
+ // A column an access branch withheld from this row (tsk_1ac01f6a) is absent, not null.
16490
16475
  if (row === null || row === undefined) return null;
16491
16476
  const result = Object.create(null);
16492
- for (const field of fields) result[field] = row[field];
16477
+ for (const field of fields) if (Object.prototype.hasOwnProperty.call(row, field)) result[field] = row[field];
16493
16478
  return result;
16494
16479
  }
16495
16480
  // Named spans for this handler's own awaits (tsk_c276e2d8). The context's
@@ -16536,8 +16521,9 @@ async function __sw_dataRead(sw, table, policy, options, verb) {
16536
16521
  }
16537
16522
  }
16538
16523
  /** Whether this release's pinned contract grants the browser any operation (hasDeclaredDataAccess, at run time). */
16524
+ // Load-time Object.values and Array#some: customer code may replace globals in an earlier request (tsk_ab656f8e).
16539
16525
  function __sw_dataAnyClientGrant() {
16540
- return Object.values(__SW_DATA_SCHEMA).some(({ client }) => client && (client.read !== false || client.create !== null
16526
+ return __sw_generatedOwnerSome(__sw_generatedOwnerValues(__SW_DATA_SCHEMA), ({ client }) => client && (client.read !== false || client.create !== null
16541
16527
  || client.update !== null || client.delete || client.intake !== undefined || client.links !== undefined));
16542
16528
  }
16543
16529
  /** The exact current data contract digest (one computation per isolate). */
@@ -16644,7 +16630,7 @@ async function __sw_handleDeclaredData(request, sw, env) {
16644
16630
  }
16645
16631
  // Membership records are authority, even if a stale or forged bake exposed
16646
16632
  // them. Never let the generic browser writer grant membership in a group.
16647
- if (!reading && Object.values(__SW_DATA_SCHEMA).some(item => item.member && item.member.m === table || __sw_dataPolicyUsesMembership(item.policy, table))) {
16633
+ if (!reading && __sw_generatedOwnerSome(__sw_generatedOwnerValues(__SW_DATA_SCHEMA), item => item.member && item.member.m === table || __sw_dataPolicyUsesMembership(item.policy, table))) {
16648
16634
  __sw_dataFail(403, 'DATA_ACCESS_DENIED', 'Membership changes require a server function.');
16649
16635
  }
16650
16636
  if (policy.identity === 'visitor' && shape.visitors !== true) {
@@ -16674,12 +16660,16 @@ async function __sw_handleDeclaredData(request, sw, env) {
16674
16660
  const childFields = childPolicy.read === true ? child.columns.filter(column => column.n !== child.author).map(column => column.n) : childPolicy.read;
16675
16661
  const childPrimaryKey = child.clientPrimaryKey;
16676
16662
  const childPrimaryColumn = child.columns.find(column => column.n === childPrimaryKey);
16663
+ const childBranch = __sw_dataBranchReads(child);
16677
16664
  const where = Object.create(null);
16678
16665
  __sw_dataAnd(where, relation.fk, parentId);
16679
16666
  if (input.where !== undefined) {
16680
16667
  __sw_dataObject(input.where, 'where');
16681
16668
  for (const field of Object.keys(input.where)) {
16682
16669
  const column = child.columns.find(column => column.n === field), value = input.where[field];
16670
+ if (childBranch && childBranch.hidden.includes(field)) {
16671
+ __sw_dataFail(400, 'DATA_INPUT_INVALID', 'where cannot name "' + field + '": an access branch of this table leaves it out, so only columns every reader may read can filter.');
16672
+ }
16683
16673
  if (!childFields.includes(field) || !column || column.t === 'json' || column.t === 'blob'
16684
16674
  || !(value === null || typeof value === 'string' || typeof value === 'boolean'
16685
16675
  || typeof value === 'number' && Number.isFinite(value))) {
@@ -16709,9 +16699,11 @@ async function __sw_handleDeclaredData(request, sw, env) {
16709
16699
  // COUNT or MAX over a non-readable column would answer questions about
16710
16700
  // fields this client was never granted.
16711
16701
  const options = Object.create(null);
16702
+ // A column some access branch leaves out (tsk_1ac01f6a) never feeds a filter, grouping or aggregate.
16703
+ const branchReads = __sw_dataBranchReads(shape);
16712
16704
  const readableScalar = fields.filter(field => {
16713
16705
  const column = shape.columns.find(item => item.n === field);
16714
- return column && column.t !== 'json' && column.t !== 'blob';
16706
+ return column && column.t !== 'json' && column.t !== 'blob' && !(branchReads && branchReads.hidden.includes(field));
16715
16707
  });
16716
16708
  const aggregateField = (key) => {
16717
16709
  const field = input[key];
@@ -16836,7 +16828,7 @@ async function __sw_handleDeclaredData(request, sw, env) {
16836
16828
  // operation must be declared for the browser client, and membership tables are never written
16837
16829
  // from the browser. A hint only; the write itself still enforces.
16838
16830
  if (plan.options.can) {
16839
- const membership = Object.values(__SW_DATA_SCHEMA).some(item => item.member && item.member.m === table || __sw_dataPolicyUsesMembership(item.policy, table));
16831
+ const membership = __sw_generatedOwnerSome(__sw_generatedOwnerValues(__SW_DATA_SCHEMA), item => item.member && item.member.m === table || __sw_dataPolicyUsesMembership(item.policy, table));
16840
16832
  const declared = (op) => !membership && !!policy[op] && policy[op] !== null;
16841
16833
  for (const row of result.data || []) {
16842
16834
  if (row && row.can) row.can = { update: row.can.update === true && declared('update'), delete: row.can.delete === true && declared('delete') };
@@ -17803,7 +17795,7 @@ function __sw_postTrace(env, ctx, t, spans, status, platformFetch) {
17803
17795
  try {
17804
17796
  __sw_traceClose(t);
17805
17797
  if (__sw_traceWouldBeDropped(t.traceId, spans, status)) return;
17806
- const platformDomain = env.PLATFORM_DOMAIN || 'somewhere.tech';
17798
+ const platformDomain = env.PLATFORM_DOMAIN || 'somewhere.cloud';
17807
17799
  const platformBase = env.PLATFORM_API_BASE || ('https://api.' + platformDomain);
17808
17800
  const ingest = __sw_telemetryIngestActive(env);
17809
17801
  ctx.waitUntil(
@@ -17854,7 +17846,7 @@ function __sw_fitErrorData(data, ingest) {
17854
17846
  /** Best-effort function exception reporting correlated to the same trace. */
17855
17847
  async function reportFunctionError(env, request, code, err, responseStatus, requestId, routePath, platformFetch, platformCtx, responseCode) {
17856
17848
  try {
17857
- const platformDomain = env.PLATFORM_DOMAIN || 'somewhere.tech';
17849
+ const platformDomain = env.PLATFORM_DOMAIN || 'somewhere.cloud';
17858
17850
  const platformBase = env.PLATFORM_API_BASE || ('https://api.' + platformDomain);
17859
17851
  const apiKey = env.PROJECT_API_KEY;
17860
17852
  if (!apiKey || !env.PROJECT_ID) return;
@@ -18016,6 +18008,9 @@ function parseClientPermissions(value, columns, scope) {
18016
18008
  if (Array.isArray(read)) {
18017
18009
  const names = [];
18018
18010
  for (const entry of read) {
18011
+ if (typeof entry === "string" && ((scope.kind === "owner" || scope.kind === "policy") && entry.toLowerCase() === scope.column || entry.toLowerCase() === SHARED_AUTHOR_COLUMN)) {
18012
+ 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.`);
18013
+ }
18019
18014
  if (typeof entry !== "string" || !fields.has(entry.toLowerCase())) return fail("client.read must contain only declared column names.");
18020
18015
  const name = entry.toLowerCase();
18021
18016
  if (names.includes(name)) return fail(`client.read declares field "${entry}" twice.`);
@@ -24426,6 +24421,7 @@ function readGrantScope(r, table, line, fail) {
24426
24421
  r.expectPunct("{", "in grant options");
24427
24422
  let membership, grantee, row;
24428
24423
  let operations;
24424
+ let read;
24429
24425
  const seen = /* @__PURE__ */ new Set();
24430
24426
  while (!r.tryPunct("}")) {
24431
24427
  const key = r.readKey("in grant options");
@@ -24453,7 +24449,9 @@ function readGrantScope(r, table, line, fail) {
24453
24449
  }
24454
24450
  if (!ops.includes("read")) fail(key.line, "grant operations require read; update alone is not allowed.");
24455
24451
  operations = ops.includes("update") ? ["read", "update"] : ["read"];
24456
- } else fail(key.line, `Unknown grant option "${key.name}". Allowed: table, grantee, row, operations.`);
24452
+ } else if (key.name === "read") {
24453
+ read = readBranchReadColumns(r, `grant({ read }) on table "${table}"`);
24454
+ } else fail(key.line, `Unknown grant option "${key.name}". Allowed: table, grantee, row, operations, read.`);
24457
24455
  if (!r.tryPunct(",")) {
24458
24456
  r.expectPunct("}", "closing grant options");
24459
24457
  break;
@@ -24463,7 +24461,7 @@ function readGrantScope(r, table, line, fail) {
24463
24461
  r.expectPunct(")", "closing grant");
24464
24462
  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.`);
24465
24463
  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.`);
24466
- return { kind: "member", group: ["user_id"], membership, memberUser: grantee, memberGroup: row === void 0 ? ["user_id"] : ["user_id", row], operations, ownerGrant: true };
24464
+ return { kind: "member", group: ["user_id"], membership, memberUser: grantee, memberGroup: row === void 0 ? ["user_id"] : ["user_id", row], operations, ownerGrant: true, ...read ? { read } : {} };
24467
24465
  }
24468
24466
  function resolveGrantRowKeys(scope, key, table, line, fail) {
24469
24467
  const visit = (node) => {
@@ -25391,6 +25389,7 @@ function readRoleList(r, fail, ctx) {
25391
25389
  function readGroupScope(r, tableName, line, fail) {
25392
25390
  r.expectPunct("(", 'after "group"');
25393
25391
  let column = DEFAULT_GROUP_COLUMN;
25392
+ let read;
25394
25393
  const explicit = {};
25395
25394
  if (!r.tryPunct(")")) {
25396
25395
  r.expectPunct("{", `in group() options for table "${tableName}"`);
@@ -25419,8 +25418,10 @@ function readGroupScope(r, tableName, line, fail) {
25419
25418
  break;
25420
25419
  }
25421
25420
  }
25421
+ } else if (key.name === "read") {
25422
+ read = readBranchReadColumns(r, `group({ read }) on table "${tableName}"`);
25422
25423
  } else {
25423
- fail(key.line, `group() on table "${tableName}" has an unknown option "${key.name}". Allowed: column, roles.`);
25424
+ fail(key.line, `group() on table "${tableName}" has an unknown option "${key.name}". Allowed: column, roles, read.`);
25424
25425
  }
25425
25426
  if (!r.tryPunct(",")) {
25426
25427
  r.expectPunct("}", `closing group() options for table "${tableName}"`);
@@ -25430,7 +25431,7 @@ function readGroupScope(r, tableName, line, fail) {
25430
25431
  r.tryPunct(",");
25431
25432
  r.expectPunct(")", "closing group()");
25432
25433
  }
25433
- const node = { kind: "group", column, roles: { read: [], create: [], update: [], delete: [] } };
25434
+ const node = { kind: "group", column, roles: { read: [], create: [], update: [], delete: [] }, ...read ? { read } : {} };
25434
25435
  declaredGroupRoles.set(node, { line, explicit });
25435
25436
  return node;
25436
25437
  }
@@ -25539,7 +25540,17 @@ function readAppRoleScope(r, tableName, line, fail) {
25539
25540
  let sawRoles = false;
25540
25541
  while (!r.tryPunct("}")) {
25541
25542
  const key = r.readKey(`for an option of ${where}`);
25542
- if (key.name !== "roles") fail(key.line, `${where} has an unknown option "${key.name}". Allowed: roles.`);
25543
+ if (key.name === "read") {
25544
+ if (node.read) fail(key.line, `${where} declares read twice.`);
25545
+ r.expectPunct(":", `after "read" in ${where}`);
25546
+ node.read = readBranchReadColumns(r, `appRole({ read }) on table "${tableName}"`);
25547
+ if (!r.tryPunct(",")) {
25548
+ r.expectPunct("}", `closing ${where}`);
25549
+ break;
25550
+ }
25551
+ continue;
25552
+ }
25553
+ if (key.name !== "roles") fail(key.line, `${where} has an unknown option "${key.name}". Allowed: roles, read.`);
25543
25554
  if (sawRoles) fail(key.line, `${where} declares roles twice.`);
25544
25555
  sawRoles = true;
25545
25556
  r.expectPunct(":", `after "roles" in ${where}`);
@@ -25819,6 +25830,7 @@ var SCHEMA_CALLABLE = /* @__PURE__ */ new Set([
25819
25830
  ]);
25820
25831
  var SAFE_IDENT = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
25821
25832
  var RESERVED_PREFIXES = ["app_", "sqlite_", "d1_", "__sw_"];
25833
+ var ROW_HINT_FIELDS = ["can", "mine"];
25822
25834
  var MAX_TABLES = 100;
25823
25835
  var MAX_COLUMNS = 100;
25824
25836
  var MAX_INDEXES = 40;
@@ -26066,6 +26078,9 @@ function checkDeclaredName(kind, raw, line) {
26066
26078
  if (name.startsWith("_") || RESERVED_PREFIXES.some((p) => name.startsWith(p))) {
26067
26079
  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.`);
26068
26080
  }
26081
+ if (kind === "Column" && ROW_HINT_FIELDS.includes(name)) {
26082
+ 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.`);
26083
+ }
26069
26084
  return name;
26070
26085
  }
26071
26086
  function readColumn(r, tableName, rawName, line) {
@@ -26255,6 +26270,7 @@ function readMemberScope(r, tableName, scopeLine) {
26255
26270
  let memberUser = null;
26256
26271
  let memberGroup = null;
26257
26272
  let operations, operationsSeen = false;
26273
+ let read;
26258
26274
  while (!r.tryPunct("}")) {
26259
26275
  const key = r.readKey(`for a member() option of table "${tableName}"`);
26260
26276
  r.expectPunct(":", `after "${key.name}" in member() for table "${tableName}"`);
@@ -26270,9 +26286,12 @@ function readMemberScope(r, tableName, scopeLine) {
26270
26286
  if (operationsSeen) throw new SchemaTsError(`line ${key.line}: member() on table "${tableName}" declares operations more than once.`);
26271
26287
  operationsSeen = true;
26272
26288
  operations = readMemberOperations(r, tableName);
26289
+ } else if (key.name === "read") {
26290
+ if (read) throw new SchemaTsError(`line ${key.line}: member() on table "${tableName}" declares read more than once.`);
26291
+ read = readBranchReadColumns(r, `member({ read }) on table "${tableName}"`);
26273
26292
  } else {
26274
26293
  throw new SchemaTsError(
26275
- `line ${key.line}: member() on table "${tableName}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group, operations.`
26294
+ `line ${key.line}: member() on table "${tableName}" has an unknown option "${key.name}". Allowed: group, membership, member_user, member_group, operations, read.`
26276
26295
  );
26277
26296
  }
26278
26297
  if (!r.tryPunct(",")) {
@@ -26302,7 +26321,7 @@ function readMemberScope(r, tableName, scopeLine) {
26302
26321
  `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.`
26303
26322
  );
26304
26323
  }
26305
- const node = { kind: "member", group, membership, memberUser, memberGroup, ...operations !== void 0 ? { operations } : {} };
26324
+ const node = { kind: "member", group, membership, memberUser, memberGroup, ...operations !== void 0 ? { operations } : {}, ...read ? { read } : {} };
26306
26325
  if (operationsSeen) explicitMemberOperations.add(node);
26307
26326
  return node;
26308
26327
  }
@@ -26310,21 +26329,56 @@ var explicitMemberOperations = /* @__PURE__ */ new WeakSet();
26310
26329
  function readPolicyOwner(r, tableName, line) {
26311
26330
  r.expectPunct("(", 'after "owner"');
26312
26331
  let column = DEFAULT_OWNER_COLUMN;
26332
+ let read;
26313
26333
  if (!r.tryPunct(")")) {
26314
- const opts = r.readLiteralObject(`in owner() inside anyOf() for table "${tableName}"`);
26315
- for (const [key, { value, line: optionLine }] of opts) {
26316
- if (key !== "column") {
26317
- throw new SchemaTsError(`line ${optionLine}: owner() inside anyOf() only allows column. Visitor ownership is not part of policy scope.`);
26334
+ r.expectPunct("{", `in owner() inside anyOf() for table "${tableName}"`);
26335
+ const seen = /* @__PURE__ */ new Set();
26336
+ while (!r.tryPunct("}")) {
26337
+ const key = r.readKey(`for an owner() option inside anyOf() for table "${tableName}"`);
26338
+ if (seen.has(key.name)) throw new SchemaTsError(`line ${key.line}: option "${key.name}" appears twice in owner() inside anyOf() for table "${tableName}".`);
26339
+ seen.add(key.name);
26340
+ r.expectPunct(":", `after "${key.name}" in owner() inside anyOf() for table "${tableName}"`);
26341
+ if (key.name === "column") {
26342
+ const value = r.readLiteral(`for "column" in owner() inside anyOf() for table "${tableName}"`);
26343
+ if (typeof value !== "string" || !SAFE_IDENT.test(value) || value.length > MAX_NAME_LENGTH) {
26344
+ throw new SchemaTsError(`line ${key.line}: owner({ column }) on table "${tableName}" must be a valid column name.`);
26345
+ }
26346
+ column = value.toLowerCase();
26347
+ } else if (key.name === "read") {
26348
+ read = readBranchReadColumns(r, `owner({ read }) on table "${tableName}"`);
26349
+ } else {
26350
+ throw new SchemaTsError(`line ${key.line}: owner() inside anyOf() only allows column and read. Visitor ownership is not part of policy scope.`);
26318
26351
  }
26319
- if (typeof value !== "string" || !SAFE_IDENT.test(value) || value.length > MAX_NAME_LENGTH) {
26320
- throw new SchemaTsError(`line ${optionLine}: owner({ column }) on table "${tableName}" must be a valid column name.`);
26352
+ if (!r.tryPunct(",")) {
26353
+ r.expectPunct("}", `closing owner() options inside anyOf() for table "${tableName}"`);
26354
+ break;
26321
26355
  }
26322
- column = value.toLowerCase();
26323
26356
  }
26324
26357
  r.tryPunct(",");
26325
26358
  r.expectPunct(")", "closing owner()");
26326
26359
  }
26327
- return { kind: "owner", column };
26360
+ return { kind: "owner", column, ...read ? { read } : {} };
26361
+ }
26362
+ function readBranchReadColumns(r, ctx) {
26363
+ const open = r.next();
26364
+ 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'].`);
26365
+ const out = [];
26366
+ while (!r.tryPunct("]")) {
26367
+ const t = r.next();
26368
+ 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.`);
26369
+ const name = t.value.toLowerCase();
26370
+ if (out.includes(name)) throw new SchemaTsError(`line ${t.line}: ${ctx} lists "${t.value}" twice.`);
26371
+ out.push(name);
26372
+ if (!r.tryPunct(",")) {
26373
+ r.expectPunct("]", `closing ${ctx}`);
26374
+ break;
26375
+ }
26376
+ }
26377
+ if (out.length === 0) throw new SchemaTsError(`line ${open.line}: ${ctx} must name at least one column; leave read out for every column.`);
26378
+ return out;
26379
+ }
26380
+ function refuseBranchRead(node, line, tableName, where) {
26381
+ 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.`);
26328
26382
  }
26329
26383
  function readPolicyMember(r, tableName, line) {
26330
26384
  r.expectPunct("(", 'after "member"');
@@ -26359,7 +26413,11 @@ function readAnyOfScope(r, tableName, line) {
26359
26413
  const owner = owners[0];
26360
26414
  const other = firstPolicy.kind === "owner" ? secondPolicy : firstPolicy;
26361
26415
  if (other.kind === "member" && other.ownerGrant && owner.column !== DEFAULT_OWNER_COLUMN) throw new SchemaTsError(`line ${line}: grant() requires owner() with managed user_id.`);
26362
- return { kind: "policy", policy: { kind: "any_of", policies: [owner, other] } };
26416
+ const otherRead = other;
26417
+ const reads = [owner.read ?? null, otherRead.read ?? null];
26418
+ delete owner.read;
26419
+ delete otherRead.read;
26420
+ return { kind: "policy", policy: { kind: "any_of", policies: [owner, other], ...reads[0] || reads[1] ? { reads } : {} } };
26363
26421
  }
26364
26422
  var ALL_OF_FORMS = "allOf() takes a gate \u2014 member(), group() or appRole() \u2014 and then owner() or anyOf(owner(), ...).";
26365
26423
  var ALL_OF_EXPLICIT = "allOf needs explicit operations";
@@ -26385,6 +26443,9 @@ function readAllOfScope(r, tableName, line) {
26385
26443
  } else throw new SchemaTsError(`line ${whoTok.line}: ${ALL_OF_FORMS}`);
26386
26444
  r.tryPunct(",");
26387
26445
  r.expectPunct(")", "closing allOf()");
26446
+ refuseBranchRead(gate, line, tableName, "the gate of allOf()");
26447
+ refuseBranchRead(who, line, tableName, "who in allOf()");
26448
+ 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().`);
26388
26449
  const x = who.kind === "any_of" ? who.policies[1] : null;
26389
26450
  if (x?.kind === "member" && x.ownerGrant) throw new SchemaTsError(`line ${line}: grant() belongs in anyOf(owner(), grant()), not allOf().`);
26390
26451
  for (const node of [gate, ...x ? [x] : []]) {
@@ -26476,13 +26537,17 @@ function readScope(r, tableName) {
26476
26537
  const failAt = (line, message) => {
26477
26538
  throw new SchemaTsError(`line ${line}: ${message}`);
26478
26539
  };
26479
- if (tok.value === "group") return { kind: "policy", policy: readGroupScope(r, tableName, tok.line, failAt) };
26480
- if (tok.value === "appRole") return { kind: "policy", policy: readAppRoleScope(r, tableName, tok.line, failAt) };
26540
+ if (tok.value === "group" || tok.value === "appRole") {
26541
+ const policy = tok.value === "group" ? readGroupScope(r, tableName, tok.line, failAt) : readAppRoleScope(r, tableName, tok.line, failAt);
26542
+ refuseBranchRead(policy, tok.line, tableName, `the ${tok.value}() scope`);
26543
+ return { kind: "policy", policy };
26544
+ }
26481
26545
  if (tok.value === "member") {
26482
26546
  r.expectPunct("(", 'after "member"');
26483
26547
  const scope = readMemberScope(r, tableName, tok.line);
26484
26548
  r.tryPunct(",");
26485
26549
  r.expectPunct(")", "closing member()");
26550
+ if (scope.kind === "member") refuseBranchRead(scope, tok.line, tableName, "the member() scope");
26486
26551
  return scope;
26487
26552
  }
26488
26553
  r.expectPunct("(", `after "${tok.value}"`);
@@ -26732,6 +26797,22 @@ function readTable(r, rawName, nameLine) {
26732
26797
  }
26733
26798
  uniques.push(entry.columns);
26734
26799
  }
26800
+ if (scope.kind === "policy" && scope.policy.kind === "any_of" && scope.policy.reads) {
26801
+ const key = columns.filter((column) => column.helper === "id").length === 1 ? columns.find((column) => column.helper === "id").name : null;
26802
+ const declared = new Set(columns.map((column) => column.name));
26803
+ scope.policy.reads.forEach((list, i) => {
26804
+ if (!list) return;
26805
+ const branch = scope.policy.kind === "any_of" ? scope.policy.policies[i] : null;
26806
+ const label = branch?.kind === "owner" ? "owner()" : branch?.kind === "member" ? branch.ownerGrant ? "grant()" : "member()" : branch?.kind === "group" ? "group()" : "appRole()";
26807
+ for (const column of list) {
26808
+ if (!declared.has(column)) throw new SchemaTsError(`line ${nameLine}: table "${rawName}": ${label} read lists "${column}", which is not one of its declared columns.`);
26809
+ }
26810
+ 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.`);
26811
+ for (const relation of relations) {
26812
+ 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.`);
26813
+ }
26814
+ });
26815
+ }
26735
26816
  let client;
26736
26817
  if (clientInput) {
26737
26818
  if (columns.filter((column) => column.helper === "id").length !== 1) {