@sequenceholdings/orm 2.6.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -191,7 +191,42 @@ matching acknowledgement. Existing calls without that option are unchanged.
191
191
  - `@sequenceholdings/orm/client` — persisted-operation client utilities
192
192
  - `@sequenceholdings/orm/drizzle` — supported Drizzle helpers for views
193
193
  - `@sequenceholdings/orm/codegen` — GraphQL Code Generator preset used by a
194
- scaffolded namespace
194
+ scaffolded namespace. Custom scalars (`bigint`, `date`, `jsonb`, `numeric`,
195
+ `timestamptz`, `uuid`) and any other unmapped scalar are emitted as
196
+ TypeScript `unknown`, not `any`, so `generated/client.ts` and
197
+ `generated/server.ts` pass native `@typescript-eslint/no-explicit-any`.
198
+
199
+ ### Regenerating clients after upgrading
200
+
201
+ Existing scaffolds that already committed generated clients need a regenerate.
202
+ `any` on those scalars fails native lint; the files are checked, not ignored.
203
+
204
+ 1. Install the `@sequenceholdings/orm` release that contains this preset.
205
+ 2. Run `seq-studio orm generate .` in the namespace directory.
206
+ 3. Commit the rewritten `generated/client.ts` and `generated/server.ts`.
207
+ 4. Narrow call sites that treated those scalars as `any`. `unknown` has to be
208
+ checked before it is used.
209
+
210
+ To keep a concrete type, set `config.scalars` on the `./generated/` output in
211
+ `codegen.ts`. A consumer entry replaces the preset default for that scalar
212
+ only. Lossless `numeric` and `bigint` transport should use `string`:
213
+
214
+ ```ts
215
+ generates: {
216
+ './generated/': {
217
+ preset,
218
+ config: { scalars: { numeric: 'string', bigint: 'string' } },
219
+ },
220
+ },
221
+ ```
222
+
223
+ Then run `seq-studio orm generate .` again. Mapping a scalar to `any`, or
224
+ eslint-ignoring `generated/client.ts` and `generated/server.ts`, fails the
225
+ purpose of the check: an authored file that uses `any` must still fail.
226
+
227
+ A scaffold that cannot regenerate should replace each custom scalar's `any`
228
+ with `unknown` (or `string` for lossless numeric/bigint) in the `Scalars` map
229
+ and in every inlined field of both generated files.
195
230
 
196
231
  The `@sequenceholdings/orm/client` entry point contains the persisted-operation
197
232
  executor and error types without importing compiler, migration, Node builtins,
@@ -208,3 +243,11 @@ For a function invocation with no human caller (a Lattice step), use
208
243
  service account. The host adapters supply authentication; they do not grant namespace access or
209
244
  register operations. Do not extract CLI credentials or invent a browser token
210
245
  provider to wire a generated client.
246
+
247
+ ### Verified service policies (planned 2.7.0)
248
+
249
+ `servicePrincipal()` is a zero-argument Boolean row-policy predicate. Combine it with a private capability, for example `servicePrincipal() and capability('ticket-delivery')`. A role or a `service-account|` subject string does not establish this predicate. The executing runtime derives it afresh from verified service authentication or an attached service delegation; user, dev, console run-as, impersonated and external-borrower requests cannot satisfy it. It is not accepted through `claim()`, request variables or serialized data-token claims.
250
+
251
+ Affected namespaces require IR v6, including namespaces that only declare a write policy and have no operations. `orm apply` first checks the authenticated target's `/api/orm/runtime-capabilities` response; a missing or older endpoint blocks registration. The compiler and executor also reject an incompatible IR version. Publish the matching ORM and deploy its runtime before activating these policies. Service identity does not grant namespace access, operation capability, declared data scope or row access by itself.
252
+
253
+ ORM 2.7.0 and downstream Artifact Studio 0.10.0, Managed Functions 0.4.4, Pipeline Spec 0.2.6 and Studio CLI 0.6.6 remain prepared for the separate chain-release workflow and awaiting publication. Their workspace dependencies are pinned to this ORM version when packed; no candidate build constitutes registry evidence.
package/dist/cli.cjs CHANGED
@@ -1462,14 +1462,21 @@ function normalizeCanonical(definition) {
1462
1462
  var BUILTIN_SQL_READ_HASH = "sha256:a50c477c952a5e0fe68ed158a4530e4aab573c09bcbb1a8ac454dc88a39a6258";
1463
1463
 
1464
1464
  // src/ir/index.ts
1465
- var IR_VERSION = 5;
1465
+ var IR_VERSION = 6;
1466
1466
  var LEGACY_IR_VERSION = 1;
1467
1467
  var GUARDED_IR_VERSION = 2;
1468
1468
  var COMPOSED_IR_VERSION = 3;
1469
1469
  var SQL_IR_VERSION = 4;
1470
1470
  var RUNTIME_SQL_IR_VERSION = 5;
1471
- var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(GUARDED_IR_VERSION), zod.z.literal(COMPOSED_IR_VERSION), zod.z.literal(SQL_IR_VERSION), zod.z.literal(RUNTIME_SQL_IR_VERSION)]);
1471
+ var SERVICE_PRINCIPAL_IR_VERSION = 6;
1472
+ var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(GUARDED_IR_VERSION), zod.z.literal(COMPOSED_IR_VERSION), zod.z.literal(SQL_IR_VERSION), zod.z.literal(RUNTIME_SQL_IR_VERSION), zod.z.literal(SERVICE_PRINCIPAL_IR_VERSION)]);
1472
1473
  var operationVersionSchema = zod.z.object({ irVersion: irVersionSchema });
1474
+ function usesServicePrincipal(value) {
1475
+ if (value === null || typeof value !== "object") return false;
1476
+ if (Array.isArray(value)) return value.some(usesServicePrincipal);
1477
+ const row = value;
1478
+ return row.kind === "servicePrincipal" || Object.values(row).some(usesServicePrincipal);
1479
+ }
1473
1480
  function requiredArtifactIrVersion(operations) {
1474
1481
  let required = LEGACY_IR_VERSION;
1475
1482
  for (const operation of operations) {
@@ -1529,6 +1536,7 @@ var predIrSchema = zod.z.lazy(
1529
1536
  zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string(), namespace: zod.z.string().optional() }),
1530
1537
  zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string(), namespace: zod.z.string().optional() }),
1531
1538
  zod.z.object({ kind: zod.z.literal("dynamicFilter"), slot: zod.z.string() }),
1539
+ zod.z.object({ kind: zod.z.literal("servicePrincipal") }).strict(),
1532
1540
  zod.z.object({ kind: zod.z.literal("const"), value: zod.z.boolean() })
1533
1541
  ])
1534
1542
  );
@@ -1754,7 +1762,7 @@ var variableIrSchema = zod.z.object({
1754
1762
  /** Absent = no default declared; see {@link variableDefaultIrSchema}. */
1755
1763
  defaultValue: variableDefaultIrSchema.optional()
1756
1764
  });
1757
- var sqlColumnSchema = zod.z.object({ name: zod.z.string(), type: zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]), nullable: zod.z.boolean() });
1765
+ var sqlColumnSchema = zod.z.object({ name: zod.z.string(), type: zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz", "jsonb"]), nullable: zod.z.boolean() });
1758
1766
  var compiledOperationSchema = zod.z.object({
1759
1767
  language: zod.z.literal("sql").optional(),
1760
1768
  /** Server-compiled object catalog and permission ceiling, never caller SQL. */
@@ -1792,6 +1800,9 @@ var compiledOperationSchema = zod.z.object({
1792
1800
  tableSet: zod.z.array(zod.z.string()).min(1),
1793
1801
  roots: zod.z.array(rootIrSchema)
1794
1802
  }).superRefine((operation, ctx) => {
1803
+ if (operation.irVersion < SERVICE_PRINCIPAL_IR_VERSION && usesServicePrincipal(operation.roots)) {
1804
+ ctx.addIssue({ code: "custom", message: "servicePrincipal() requires IR v6" });
1805
+ }
1795
1806
  if (operation.language === "sql") {
1796
1807
  const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
1797
1808
  const builtin = operation.name === "BuiltinSqlRead" && operation.opHash === BUILTIN_SQL_READ_HASH && operation.requires === null;
@@ -1924,6 +1935,9 @@ var compiledArtifactSchema = zod.z.object({
1924
1935
  tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
1925
1936
  operations: zod.z.record(zod.z.string(), compiledOperationSchema)
1926
1937
  }).superRefine((artifact, ctx) => {
1938
+ if (artifact.irVersion < SERVICE_PRINCIPAL_IR_VERSION && usesServicePrincipal(artifact)) {
1939
+ ctx.addIssue({ code: "custom", message: "servicePrincipal() requires IR v6" });
1940
+ }
1927
1941
  if (Object.keys(artifact.dependencies ?? {}).length > 0 && artifact.irVersion < COMPOSED_IR_VERSION) {
1928
1942
  ctx.addIssue({ code: "custom", path: ["irVersion"], message: "cross-namespace dependencies require IR v3" });
1929
1943
  }
@@ -2232,6 +2246,12 @@ var Parser2 = class {
2232
2246
  }
2233
2247
  return node;
2234
2248
  }
2249
+ if (this.peekKeyword("servicePrincipal") && this.tokens[this.index + 1]?.kind === "lparen") {
2250
+ this.advance();
2251
+ this.expect("lparen", "'(' after servicePrincipal");
2252
+ this.expect("rparen", "')' closing servicePrincipal()");
2253
+ return { kind: "servicePrincipal" };
2254
+ }
2235
2255
  if (this.peekKeyword("exists")) return this.parseExists();
2236
2256
  if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
2237
2257
  return this.parseComparison();
@@ -2444,6 +2464,8 @@ var PolicyCompiler = class _PolicyCompiler {
2444
2464
  return { kind: "capability", key: node.key };
2445
2465
  case "role":
2446
2466
  return { kind: "role", key: node.key };
2467
+ case "servicePrincipal":
2468
+ return { kind: "servicePrincipal" };
2447
2469
  case "exists":
2448
2470
  return this.compileExists(node.path, node.where, table, depth);
2449
2471
  case "visible":
@@ -3108,7 +3130,10 @@ function platformUserMeta() {
3108
3130
  { name: "role", columnName: "role", type: "text", nullable: true },
3109
3131
  { name: "managerId", columnName: "manager_id", type: "text", nullable: true },
3110
3132
  // NOT NULL DEFAULT true on chat.users, so non-null through the view.
3111
- { name: "accountEnabled", columnName: "account_enabled", type: "boolean", nullable: false }
3133
+ { name: "accountEnabled", columnName: "account_enabled", type: "boolean", nullable: false },
3134
+ { name: "canonicalSub", columnName: "canonical_sub", type: "text", nullable: false },
3135
+ { name: "normalizedEmail", columnName: "normalized_email", type: "text", nullable: true },
3136
+ { name: "emailDomain", columnName: "email_domain", type: "text", nullable: true }
3112
3137
  ];
3113
3138
  return {
3114
3139
  table: "users_directory",
@@ -5095,7 +5120,7 @@ function compileNamespace({ canonical, sdl, documents }) {
5095
5120
  }
5096
5121
  }
5097
5122
  operations[restricted.name] = {
5098
- irVersion: canonical.imports?.length ? COMPOSED_IR_VERSION : roots.some((root) => root.kind === "mutation" && root.assertAffectedRows !== void 0) ? GUARDED_IR_VERSION : LEGACY_IR_VERSION,
5123
+ irVersion: usesServicePrincipal(roots) ? SERVICE_PRINCIPAL_IR_VERSION : canonical.imports?.length ? COMPOSED_IR_VERSION : roots.some((root) => root.kind === "mutation" && root.assertAffectedRows !== void 0) ? GUARDED_IR_VERSION : LEGACY_IR_VERSION,
5099
5124
  name: restricted.name,
5100
5125
  opHash: computeOpHash({ definition: flattened, schemaHash }),
5101
5126
  kind: restricted.kind,
@@ -5122,7 +5147,7 @@ function compileNamespace({ canonical, sdl, documents }) {
5122
5147
  }
5123
5148
  if (diagnostics.some((d) => d.severity === "error")) return { ok: false, diagnostics };
5124
5149
  const artifact = {
5125
- irVersion: canonical.imports?.length ? COMPOSED_IR_VERSION : requiredArtifactIrVersion(Object.values(operations)),
5150
+ irVersion: usesServicePrincipal([...ctx.readPolicies.values(), ...ctx.writePolicies.values(), ...ctx.insertPolicies.values(), ...ctx.updatePolicies.values(), ...Object.values(operations)]) ? SERVICE_PRINCIPAL_IR_VERSION : canonical.imports?.length ? COMPOSED_IR_VERSION : requiredArtifactIrVersion(Object.values(operations)),
5126
5151
  namespace: ctx.namespace,
5127
5152
  schemaHash,
5128
5153
  ownSchemaHash: computeSchemaHash(localContract(canonical)),
@@ -5176,6 +5201,8 @@ function compilePredIrToSql(pred, options = {}) {
5176
5201
  return `(NOT ${compilePredIrToSql(pred.operand, options)})`;
5177
5202
  case "cmp":
5178
5203
  return compileCmp(pred.op, pred.column, pred.value, options);
5204
+ case "servicePrincipal":
5205
+ return `coalesce(${claimExpression("servicePrincipal")} = 'true', false)`;
5179
5206
  case "capability":
5180
5207
  return `(${options.statementClaimChecks ? "SELECT " : ""}${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
5181
5208
  case "role":
@@ -5278,6 +5305,39 @@ function compileArrayValue(value, columnType, _options) {
5278
5305
  return columnType ? `${array}::${sqlTypeName(columnType)}[]` : array;
5279
5306
  }
5280
5307
 
5308
+ // src/compiler/static-policy-sql.ts
5309
+ function compileStaticPolicySql({ table, predicate, tables, namespace, alias, rootRelation, statementClaimChecks, onRead }) {
5310
+ let sequence = 0;
5311
+ const aliases = new Set([alias, rootRelation].filter((value) => value !== void 0));
5312
+ const column = (meta, qualifier, name2) => {
5313
+ const found = meta.columns.find((c) => c.name === name2 || c.columnName === name2);
5314
+ if (!found) throw new OrmDefinitionError("Static policy relationship column is unavailable");
5315
+ return `${quoteIdentifier(qualifier)}.${quoteIdentifier(found.columnName)}`;
5316
+ };
5317
+ const render = (meta, pred, qualifier, outerRelation) => compilePredIrToSql(pred, {
5318
+ alias: qualifier,
5319
+ namespace: meta.namespace ?? namespace,
5320
+ statementClaimChecks,
5321
+ columnNames: Object.fromEntries(meta.columns.map((c) => [c.name, c.columnName])),
5322
+ columnTypes: Object.fromEntries(meta.columns.map((c) => [c.name, c.type])),
5323
+ exists: (node) => {
5324
+ const link = meta.links.find((candidate) => candidate.name === node.link);
5325
+ const target = link?.target ? tables[link.target] : void 0;
5326
+ const parent = qualifier ?? outerRelation;
5327
+ if (!link || !target || !parent) throw new OrmDefinitionError("Static policy relationship metadata is unavailable");
5328
+ onRead?.(target);
5329
+ let child;
5330
+ do {
5331
+ child = `policy_${++sequence}`;
5332
+ } while (aliases.has(child));
5333
+ aliases.add(child);
5334
+ const join5 = link.kind === "toMany" ? `${column(target, child, link.on)} = ${column(meta, parent, link.references)}` : `${column(meta, parent, link.on)} = ${column(target, child, link.references)}`;
5335
+ return `EXISTS (SELECT 1 FROM ${quoteIdentifier(target.schema)}.${quoteIdentifier(target.table)} AS ${quoteIdentifier(child)} WHERE ${join5} AND (${render(target, node.where, child)}))`;
5336
+ }
5337
+ });
5338
+ return render(table, predicate, alias, rootRelation);
5339
+ }
5340
+
5281
5341
  // src/sql/catalog.ts
5282
5342
  function sqlHash(value) {
5283
5343
  return `sha256:${crypto.createHash("sha256").update(value).digest("hex")}`;
@@ -5298,27 +5358,6 @@ function createSqlCatalog({ canonical, publishedObjects, executionTarget }) {
5298
5358
  const meta = Object.values(ctx.tables).find((table) => refOf(table) === ref);
5299
5359
  if (!meta || internal.has(ref) || meta.namespace !== canonical.namespace && !meta.exportRead) throw new Error(`SQL object not published: ${ref}`);
5300
5360
  const reads = /* @__PURE__ */ new Set([ref]);
5301
- let sequence = 0;
5302
- const predicate = (table, pred, alias) => compilePredIrToSql(pred, {
5303
- alias,
5304
- namespace: table.namespace ?? canonical.namespace,
5305
- columnNames: Object.fromEntries(table.columns.map((c) => [c.name, c.columnName])),
5306
- columnTypes: Object.fromEntries(table.columns.map((c) => [c.name, c.type])),
5307
- exists: (node) => {
5308
- const link = table.links.find((candidate) => candidate.name === node.link);
5309
- const target = link?.target ? ctx.tables[link.target] : void 0;
5310
- if (!link || !target) throw new Error("SQL policy link is unavailable");
5311
- reads.add(refOf(target));
5312
- const child = `p${++sequence}`;
5313
- const col = (t2, a, name2) => {
5314
- const c = t2.columns.find((c2) => c2.name === name2 || c2.columnName === name2);
5315
- if (!c) throw new Error("SQL policy column is unavailable");
5316
- return `${quoteIdentifier(a)}.${quoteIdentifier(c.columnName)}`;
5317
- };
5318
- const join5 = link.kind === "toMany" ? `${col(target, child, link.on)} = ${col(table, alias, link.references)}` : `${col(table, alias, link.on)} = ${col(target, child, link.references)}`;
5319
- return `EXISTS (SELECT 1 FROM ${quoteIdentifier(target.schema)}.${quoteIdentifier(target.table)} AS ${quoteIdentifier(child)} WHERE ${join5} AND (${predicate(target, node.where, child)}))`;
5320
- }
5321
- });
5322
5361
  const columns = meta.columns.filter((c) => meta.filterable.includes(c.name)).map((c) => ({
5323
5362
  name: c.name,
5324
5363
  type: typeof c.type === "string" ? c.type : "text",
@@ -5329,7 +5368,7 @@ function createSqlCatalog({ canonical, publishedObjects, executionTarget }) {
5329
5368
  return `${quoteIdentifier("p0")}.${quoteIdentifier(physical.columnName)}${typeof physical.type === "object" ? "::text" : ""} AS ${quoteIdentifier(c.name)}`;
5330
5369
  }).join(", ");
5331
5370
  const policy = ctx.readPolicies.get(meta.table) ?? { kind: "const", value: true };
5332
- const scan = `SELECT ${projection} FROM ${quoteIdentifier(meta.schema)}.${quoteIdentifier(meta.table)} AS ${quoteIdentifier("p0")} WHERE ${predicate(meta, policy, "p0")}`;
5371
+ const scan = `SELECT ${projection} FROM ${quoteIdentifier(meta.schema)}.${quoteIdentifier(meta.table)} AS ${quoteIdentifier("p0")} WHERE ${compileStaticPolicySql({ table: meta, predicate: policy, tables: ctx.tables, namespace: canonical.namespace, alias: "p0", onRead: (target) => reads.add(refOf(target)) })}`;
5333
5372
  return { ref, table: meta.table, columns, scan, read: [...reads].sort() };
5334
5373
  });
5335
5374
  return { identity, objects };
@@ -5953,7 +5992,7 @@ async function managedViewDiagnostics(canonical) {
5953
5992
  }
5954
5993
 
5955
5994
  // src/sql/codegen.ts
5956
- var typeOf = (type) => ["integer", "double"].includes(type) ? "number" : type === "boolean" ? "boolean" : "string";
5995
+ var typeOf = (type) => ["integer", "double"].includes(type) ? "number" : type === "boolean" ? "boolean" : type === "jsonb" ? "unknown" : "string";
5957
5996
  function generateSqlOperations({ artifact }) {
5958
5997
  const dynamic = Object.values(artifact.operations).some((op) => op.sqlQuery);
5959
5998
  const lines = [
@@ -5990,6 +6029,15 @@ ${variables}
5990
6029
  return `${lines.join("\n").trimEnd()}
5991
6030
  `;
5992
6031
  }
6032
+
6033
+ // src/runtime-capabilities.ts
6034
+ function assertServicePrincipalRuntime(value) {
6035
+ const row = value !== null && typeof value === "object" ? value : {};
6036
+ const predicates = row.policyPredicates;
6037
+ if (typeof row.maxIrVersion !== "number" || row.maxIrVersion < 6 || !Array.isArray(predicates) || !predicates.includes("servicePrincipal")) {
6038
+ throw new Error("The target ORM runtime does not support servicePrincipal() / IR v6; deploy the reviewed runtime before applying this namespace");
6039
+ }
6040
+ }
5993
6041
  var ENUM_BRAND = "seq-enum";
5994
6042
  function isEnumRef(value) {
5995
6043
  return typeof value === "object" && value !== null && value.kind === ENUM_BRAND;
@@ -11965,6 +12013,10 @@ async function applyV2Command(rest, dir) {
11965
12013
  }
11966
12014
  const { loaded, sdl, documents, manifest } = compiled.project;
11967
12015
  const namespace = loaded.canonical.namespace;
12016
+ if (compiled.project.artifact.irVersion >= SERVICE_PRINCIPAL_IR_VERSION || usesServicePrincipal(compiled.project.artifact)) {
12017
+ const support = await apiRequestStrict("GET", "/api/orm/runtime-capabilities");
12018
+ assertServicePrincipalRuntime(support);
12019
+ }
11968
12020
  const workspace = await readMigrationWorkspace(dir, namespace);
11969
12021
  const migration = await loadCommittedTipMigrationV2({ dir, loaded, workspace });
11970
12022
  const registered = await apiRequestStrict("POST", "/api/orm/namespaces", {