@sequenceholdings/orm 2.1.1 → 2.3.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.
Files changed (68) hide show
  1. package/README.md +3 -1
  2. package/dist/authoring.cjs +284 -99
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +31 -8
  5. package/dist/authoring.d.ts +31 -8
  6. package/dist/authoring.js +282 -100
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +51 -3
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +838 -12
  11. package/dist/canonical.d.ts +838 -12
  12. package/dist/canonical.js +47 -4
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +5876 -4198
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +5876 -4198
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +49 -32
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +49 -6
  21. package/dist/client.d.ts +49 -6
  22. package/dist/client.js +49 -32
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +1275 -264
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +57 -3
  27. package/dist/compiler.d.ts +57 -3
  28. package/dist/compiler.js +1268 -265
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/{constants-YoFGjIBM.d.cts → constants-C6EG6Wq7.d.cts} +1 -1
  31. package/dist/{constants-YoFGjIBM.d.ts → constants-C6EG6Wq7.d.ts} +1 -1
  32. package/dist/definition-DjfC7Lll.d.cts +91 -0
  33. package/dist/definition-DjfC7Lll.d.ts +91 -0
  34. package/dist/dynamic-input.cjs +2 -1
  35. package/dist/dynamic-input.cjs.map +1 -1
  36. package/dist/dynamic-input.d.cts +9 -2
  37. package/dist/dynamic-input.d.ts +9 -2
  38. package/dist/dynamic-input.js +2 -1
  39. package/dist/dynamic-input.js.map +1 -1
  40. package/dist/index.cjs +1103 -322
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +479 -19
  43. package/dist/index.d.ts +479 -19
  44. package/dist/index.js +1090 -323
  45. package/dist/index.js.map +1 -1
  46. package/dist/ir.cjs +93 -11
  47. package/dist/ir.cjs.map +1 -1
  48. package/dist/ir.d.cts +226 -15
  49. package/dist/ir.d.ts +226 -15
  50. package/dist/ir.js +88 -12
  51. package/dist/ir.js.map +1 -1
  52. package/dist/predicate-v2.cjs +116 -10
  53. package/dist/predicate-v2.cjs.map +1 -1
  54. package/dist/predicate-v2.d.cts +11 -3
  55. package/dist/predicate-v2.d.ts +11 -3
  56. package/dist/predicate-v2.js +116 -10
  57. package/dist/predicate-v2.js.map +1 -1
  58. package/dist/sdl.cjs +158 -22
  59. package/dist/sdl.cjs.map +1 -1
  60. package/dist/sdl.d.cts +8 -1
  61. package/dist/sdl.d.ts +8 -1
  62. package/dist/sdl.js +158 -22
  63. package/dist/sdl.js.map +1 -1
  64. package/package.json +5 -4
  65. package/src/codegen-gql/server-fns.cjs +2 -2
  66. package/templates/orm-namespace/AGENTS.md +10 -0
  67. package/templates/orm-namespace/CLAUDE.md +1 -84
  68. package/templates/orm-namespace/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  import { ClaimKey, PredIr } from './ir.cjs';
2
2
  import { P as PolicyExpression } from './policy-1iZbGnZZ.cjs';
3
3
  import 'zod';
4
- import './constants-YoFGjIBM.cjs';
4
+ import './constants-C6EG6Wq7.cjs';
5
5
 
6
6
  declare const POLICY_COMPARE_OPS: readonly ["=", "!=", "<", "<=", ">", ">="];
7
7
  type PolicyCompareOp = (typeof POLICY_COMPARE_OPS)[number];
@@ -11,7 +11,7 @@ type PolicyOperand =
11
11
  kind: 'path';
12
12
  segments: readonly string[];
13
13
  }
14
- /** `claim('sub')` / `claim('org')` — the only claim() keys in CLAIMS_CONTRACT. */
14
+ /** `claim('sub')` / `claim('org')` / `claim('ext_subject')` / `claim('ext_email')` — the scalar keys in CLAIMS_CONTRACT. */
15
15
  | {
16
16
  kind: 'claim';
17
17
  key: ClaimKey;
@@ -63,7 +63,7 @@ declare class PolicyParseError extends Error {
63
63
  readonly name = "PolicyParseError";
64
64
  constructor(message: string, position: number);
65
65
  }
66
- type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only';
66
+ type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only' | 'claim-type-mismatch';
67
67
  /**
68
68
  * Compile-time failure — the parsed policy references something the table
69
69
  * doesn't declare, or uses a shape the v2.0 IR cannot represent. Carries
@@ -90,6 +90,9 @@ declare function parsePolicy(source: string): ParsedPolicy;
90
90
  * - a comparison must reference exactly ONE column side (column↔column and
91
91
  * claims↔literal comparisons have no PredIr shape in v2.0);
92
92
  * - `col = null` / `col != null` normalize to the isNull operator;
93
+ * - verified-borrower claims (`claim('ext_subject')`, `claim('ext_email')`)
94
+ * are text: they compare to a text column with `=` only, never under
95
+ * not(), so no negation, ordering or pattern can widen a borrower's rows;
93
96
  * - variant `when` guards are claims-only: capability()/role() under
94
97
  * and/or/not. (claim() comparisons in `when` are not representable in the
95
98
  * v2.0 IR — a targeted error says so.)
@@ -99,6 +102,11 @@ declare const POLICY_MAX_LINK_DEPTH = 3;
99
102
  interface PolicyColumnInfo {
100
103
  readonly name: string;
101
104
  readonly columnName: string;
105
+ /**
106
+ * The column's IR type when the resolver knows it (TableMetaIr always does).
107
+ * Required for a verified-borrower claim comparison: unknown is refused.
108
+ */
109
+ readonly type?: unknown;
102
110
  }
103
111
  interface PolicyLinkInfo {
104
112
  readonly name: string;
@@ -1,7 +1,7 @@
1
1
  import { ClaimKey, PredIr } from './ir.js';
2
2
  import { P as PolicyExpression } from './policy-1iZbGnZZ.js';
3
3
  import 'zod';
4
- import './constants-YoFGjIBM.js';
4
+ import './constants-C6EG6Wq7.js';
5
5
 
6
6
  declare const POLICY_COMPARE_OPS: readonly ["=", "!=", "<", "<=", ">", ">="];
7
7
  type PolicyCompareOp = (typeof POLICY_COMPARE_OPS)[number];
@@ -11,7 +11,7 @@ type PolicyOperand =
11
11
  kind: 'path';
12
12
  segments: readonly string[];
13
13
  }
14
- /** `claim('sub')` / `claim('org')` — the only claim() keys in CLAIMS_CONTRACT. */
14
+ /** `claim('sub')` / `claim('org')` / `claim('ext_subject')` / `claim('ext_email')` — the scalar keys in CLAIMS_CONTRACT. */
15
15
  | {
16
16
  kind: 'claim';
17
17
  key: ClaimKey;
@@ -63,7 +63,7 @@ declare class PolicyParseError extends Error {
63
63
  readonly name = "PolicyParseError";
64
64
  constructor(message: string, position: number);
65
65
  }
66
- type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only';
66
+ type PolicyCompileErrorCode = 'unknown-table' | 'unknown-column' | 'unknown-link' | 'not-to-one' | 'link-depth-exceeded' | 'unsupported-comparison' | 'invalid-null-comparison' | 'when-not-claims-only' | 'claim-type-mismatch';
67
67
  /**
68
68
  * Compile-time failure — the parsed policy references something the table
69
69
  * doesn't declare, or uses a shape the v2.0 IR cannot represent. Carries
@@ -90,6 +90,9 @@ declare function parsePolicy(source: string): ParsedPolicy;
90
90
  * - a comparison must reference exactly ONE column side (column↔column and
91
91
  * claims↔literal comparisons have no PredIr shape in v2.0);
92
92
  * - `col = null` / `col != null` normalize to the isNull operator;
93
+ * - verified-borrower claims (`claim('ext_subject')`, `claim('ext_email')`)
94
+ * are text: they compare to a text column with `=` only, never under
95
+ * not(), so no negation, ordering or pattern can widen a borrower's rows;
93
96
  * - variant `when` guards are claims-only: capability()/role() under
94
97
  * and/or/not. (claim() comparisons in `when` are not representable in the
95
98
  * v2.0 IR — a targeted error says so.)
@@ -99,6 +102,11 @@ declare const POLICY_MAX_LINK_DEPTH = 3;
99
102
  interface PolicyColumnInfo {
100
103
  readonly name: string;
101
104
  readonly columnName: string;
105
+ /**
106
+ * The column's IR type when the resolver knows it (TableMetaIr always does).
107
+ * Required for a verified-borrower claim comparison: unknown is refused.
108
+ */
109
+ readonly type?: unknown;
102
110
  }
103
111
  interface PolicyLinkInfo {
104
112
  readonly name: string;
@@ -32,6 +32,7 @@ var PolicyCompileError = class extends Error {
32
32
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
33
33
  var MAX_IDENTIFIER_LENGTH = 63;
34
34
  var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
35
+ var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
35
36
  var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
36
37
  var CELL_PART_SUFFIX = {
37
38
  at: "__at",
@@ -561,6 +562,8 @@ var canonicalLinkSchema = z.object({
561
562
  kind: z.enum(["toOne", "toMany", "toPlatformUser"]),
562
563
  /** Target table name (namespace-local). Absent for toPlatformUser. */
563
564
  target: identifier2.optional(),
565
+ /** Owning namespace of an imported target; omitted for local links. */
566
+ namespace: identifier2.max(41).optional(),
564
567
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
565
568
  on: columnIdentifier,
566
569
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
@@ -662,11 +665,12 @@ var canonicalForeignKeySchema = z.object({
662
665
  }).strict();
663
666
  var canonicalTableSchema = z.object({
664
667
  table: identifier2,
668
+ displayName: z.string().trim().min(1).max(120).optional(),
665
669
  description: z.string().max(2e3).optional(),
666
670
  /** Table-level rename hint (new in v2). */
667
671
  renamedFrom: identifier2.optional(),
668
- /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
669
- source: z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
672
+ /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
673
+ source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
670
674
  columns: z.array(canonicalColumnSchema).default([]),
671
675
  primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
672
676
  links: z.array(canonicalLinkSchema).default([]),
@@ -819,7 +823,9 @@ function validateCellMetadata(table, index, ctx) {
819
823
  }
820
824
  }
821
825
  }
822
- z.object({
826
+ var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string().min(1) }).strict(), z.object({ role: z.string().min(1) }).strict()]);
827
+ var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
828
+ var localDefinitionSchema = z.object({
823
829
  definition_format: z.literal(CANONICAL_FORMAT),
824
830
  namespace: identifier2.max(41),
825
831
  description: z.string().max(2e3).optional(),
@@ -827,6 +833,8 @@ z.object({
827
833
  tables: z.array(canonicalTableSchema).min(0).max(500),
828
834
  /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
829
835
  views: z.array(canonicalViewSchema).max(500).optional(),
836
+ /** Target-owned cross-namespace access contract, using existing namespace grants. */
837
+ exports: z.record(identifier2, relationExportSchema).optional(),
830
838
  /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
831
839
  capabilities: z.object({
832
840
  roles: z.unknown().optional(),
@@ -853,7 +861,16 @@ z.object({
853
861
  }
854
862
  }
855
863
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
864
+ const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
865
+ for (const name of Object.keys(definition.exports ?? {})) {
866
+ if (!relations.has(name)) ctx.addIssue({ code: "custom", path: ["exports", name], message: `export '${name}' is not a local object` });
867
+ }
856
868
  for (const [tableIndex, table] of definition.tables.entries()) {
869
+ for (const [linkIndex, link] of table.links.entries()) {
870
+ if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
871
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
872
+ }
873
+ }
857
874
  const names = /* @__PURE__ */ new Set([
858
875
  ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
859
876
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
@@ -1023,23 +1040,36 @@ z.object({
1023
1040
  };
1024
1041
  for (const view of views) visit(view.view, []);
1025
1042
  });
1043
+ localDefinitionSchema.safeExtend({
1044
+ /** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
1045
+ imports: z.array(localDefinitionSchema).max(32).optional()
1046
+ });
1026
1047
 
1027
1048
  // src/ir/index.ts
1028
- var IR_VERSION = 2;
1029
1049
  var LEGACY_IR_VERSION = 1;
1030
- var irVersionSchema = z.union([z.literal(LEGACY_IR_VERSION), z.literal(IR_VERSION)]);
1050
+ var GUARDED_IR_VERSION = 2;
1051
+ var COMPOSED_IR_VERSION = 3;
1052
+ var SQL_IR_VERSION = 4;
1053
+ var RUNTIME_SQL_IR_VERSION = 5;
1054
+ var irVersionSchema = z.union([z.literal(LEGACY_IR_VERSION), z.literal(GUARDED_IR_VERSION), z.literal(COMPOSED_IR_VERSION), z.literal(SQL_IR_VERSION), z.literal(RUNTIME_SQL_IR_VERSION)]);
1031
1055
  z.object({ irVersion: irVersionSchema });
1032
1056
  var CLAIMS_CONTRACT = {
1033
1057
  sub: "string",
1034
1058
  org: "string?",
1059
+ ext_subject: "string?",
1060
+ ext_email: "string?",
1035
1061
  roles: "string[]",
1036
1062
  capabilities: "string[]"
1037
1063
  };
1064
+ var EXTERNAL_POLICY_CLAIM_KEYS = ["ext_subject", "ext_email"];
1065
+ function isExternalPolicyClaimKey(key) {
1066
+ return EXTERNAL_POLICY_CLAIM_KEYS.includes(key);
1067
+ }
1038
1068
  var scalarLiteral = z.union([z.string(), z.number(), z.boolean(), z.null()]);
1039
1069
  var valueIrSchema = z.discriminatedUnion("kind", [
1040
1070
  z.object({ kind: z.literal("literal"), value: z.union([scalarLiteral, z.array(scalarLiteral)]) }),
1041
1071
  z.object({ kind: z.literal("variable"), name: z.string() }),
1042
- z.object({ kind: z.literal("claim"), key: z.enum(["sub", "org"]) })
1072
+ z.object({ kind: z.literal("claim"), key: z.enum(["sub", "org", ...EXTERNAL_POLICY_CLAIM_KEYS]) })
1043
1073
  ]);
1044
1074
  var CMP_OPS = [
1045
1075
  "eq",
@@ -1071,8 +1101,8 @@ var predIrSchema = z.lazy(
1071
1101
  value: z.number(),
1072
1102
  where: predIrSchema.optional()
1073
1103
  }),
1074
- z.object({ kind: z.literal("capability"), key: z.string() }),
1075
- z.object({ kind: z.literal("role"), key: z.string() }),
1104
+ z.object({ kind: z.literal("capability"), key: z.string(), namespace: z.string().optional() }),
1105
+ z.object({ kind: z.literal("role"), key: z.string(), namespace: z.string().optional() }),
1076
1106
  z.object({ kind: z.literal("dynamicFilter"), slot: z.string() }),
1077
1107
  z.object({ kind: z.literal("const"), value: z.boolean() })
1078
1108
  ])
@@ -1299,7 +1329,31 @@ var variableIrSchema = z.object({
1299
1329
  /** Absent = no default declared; see {@link variableDefaultIrSchema}. */
1300
1330
  defaultValue: variableDefaultIrSchema.optional()
1301
1331
  });
1332
+ var sqlColumnSchema = z.object({ name: z.string(), type: z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]), nullable: z.boolean() });
1302
1333
  var compiledOperationSchema = z.object({
1334
+ language: z.literal("sql").optional(),
1335
+ /** Server-compiled object catalog and permission ceiling, never caller SQL. */
1336
+ sqlQuery: z.object({
1337
+ catalog: z.object({
1338
+ identity: z.string(),
1339
+ objects: z.array(z.object({
1340
+ ref: z.string(),
1341
+ table: z.string(),
1342
+ columns: z.array(sqlColumnSchema.extend({ type: z.string() })).min(1),
1343
+ scan: z.string().min(1),
1344
+ read: z.array(z.string()).min(1)
1345
+ }).strict()).min(1).max(256)
1346
+ }).strict(),
1347
+ read: z.array(z.string()).min(1).max(256),
1348
+ maxRows: z.number().int().min(1).max(1e4)
1349
+ }).strict().optional(),
1350
+ sql: z.object({
1351
+ text: z.string().min(1).max(1048576),
1352
+ columns: z.array(sqlColumnSchema).min(1).max(256),
1353
+ parameters: z.array(z.string()).max(128),
1354
+ read: z.array(z.string()).min(1).max(256),
1355
+ maxRows: z.number().int().min(1).max(1e4)
1356
+ }).strict().optional(),
1303
1357
  irVersion: irVersionSchema,
1304
1358
  name: z.string(),
1305
1359
  opHash: z.string().regex(/^sha256:[0-9a-f]{64}$/),
@@ -1311,8 +1365,16 @@ var compiledOperationSchema = z.object({
1311
1365
  variables: z.array(variableIrSchema),
1312
1366
  /** Every table the op touches — data-token scoping + audit. */
1313
1367
  tableSet: z.array(z.string()).min(1),
1314
- roots: z.array(rootIrSchema).min(1)
1368
+ roots: z.array(rootIrSchema)
1315
1369
  }).superRefine((operation, ctx) => {
1370
+ if (operation.language === "sql") {
1371
+ const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
1372
+ if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !operation.requires?.capability) {
1373
+ ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
1374
+ }
1375
+ } else if (operation.sql || operation.sqlQuery || operation.roots.length === 0) {
1376
+ ctx.addIssue({ code: "custom", message: "GraphQL operations require roots and cannot carry SQL" });
1377
+ }
1316
1378
  for (const [index, root] of operation.roots.entries()) {
1317
1379
  if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
1318
1380
  if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
@@ -1359,11 +1421,16 @@ var resolvedMetaIrSchema = z.object({
1359
1421
  });
1360
1422
  var tableMetaIrSchema = z.object({
1361
1423
  table: z.string(),
1424
+ namespace: z.string().optional(),
1425
+ dependencies: z.array(z.string()).optional(),
1426
+ foreignReferences: z.array(z.object({ column: z.string(), target: z.string(), targetColumn: z.string() })).optional(),
1427
+ exportRead: z.union([z.literal(true), z.object({ capability: z.string() }), z.object({ role: z.string() })]).optional(),
1428
+ exportWrite: z.union([z.literal(true), z.object({ capability: z.string() }), z.object({ role: z.string() })]).optional(),
1362
1429
  schema: z.string(),
1363
1430
  typename: z.string(),
1364
1431
  /**
1365
1432
  * True when statements over this relation evaluate a claims-gated synced
1366
- * view: synced twins (`source: gold.*`) materialize as views whose baked
1433
+ * view: synced twins (`source: schema.table`) materialize as views whose baked
1367
1434
  * policy reads `current_setting('request.jwt.claims')`, and namespace views
1368
1435
  * inherit the gate transitively through their declared dependencies. The
1369
1436
  * executor must stamp that GUC (transaction-scoped) before any statement
@@ -1422,10 +1489,18 @@ z.object({
1422
1489
  irVersion: irVersionSchema,
1423
1490
  namespace: z.string(),
1424
1491
  schemaHash: z.string(),
1492
+ ownSchemaHash: z.string().optional(),
1493
+ dependencies: z.record(z.string(), z.string()).optional(),
1494
+ readPolicies: z.record(z.string(), predIrSchema).optional(),
1425
1495
  opsHash: z.string(),
1496
+ /** Apply-observed synced objects without a serving backing; not part of the authored schema hash. */
1497
+ unbackedTables: z.array(z.string()).optional(),
1426
1498
  tables: z.record(z.string(), tableMetaIrSchema),
1427
1499
  operations: z.record(z.string(), compiledOperationSchema)
1428
1500
  }).superRefine((artifact, ctx) => {
1501
+ if (Object.keys(artifact.dependencies ?? {}).length > 0 && artifact.irVersion < COMPOSED_IR_VERSION) {
1502
+ ctx.addIssue({ code: "custom", path: ["irVersion"], message: "cross-namespace dependencies require IR v3" });
1503
+ }
1429
1504
  for (const [name, operation] of Object.entries(artifact.operations)) {
1430
1505
  if (operation.irVersion > artifact.irVersion) {
1431
1506
  ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
@@ -1744,6 +1819,7 @@ var PolicyCompiler = class {
1744
1819
  case "or":
1745
1820
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
1746
1821
  case "not":
1822
+ assertNoNegatedExternalClaim(node.operand);
1747
1823
  return { kind: "not", operand: this.compile(node.operand, table, depth) };
1748
1824
  case "capability":
1749
1825
  return { kind: "capability", key: node.key };
@@ -1823,6 +1899,9 @@ var PolicyCompiler = class {
1823
1899
  columnCandidates(info)
1824
1900
  );
1825
1901
  }
1902
+ if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
1903
+ assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
1904
+ }
1826
1905
  let cmp = this.buildCmp(op, column, valueSide);
1827
1906
  for (let i = links.length - 1; i >= 0; i -= 1) {
1828
1907
  cmp = { kind: "exists", link: links[i].name, where: cmp };
@@ -1843,6 +1922,33 @@ var PolicyCompiler = class {
1843
1922
  return { kind: "cmp", op: CMP_BY_OP[op], column, value };
1844
1923
  }
1845
1924
  };
1925
+ function assertExternalClaimComparison({ op, key, column }) {
1926
+ if (op !== "=") {
1927
+ throw new PolicyCompileError(
1928
+ `claim('${key}') supports = comparisons only, got '${op}'`,
1929
+ "unsupported-comparison"
1930
+ );
1931
+ }
1932
+ if (column?.type !== "text") {
1933
+ const actual = column?.type === void 0 ? "of unknown type" : typeof column.type === "string" ? column.type : "an enum";
1934
+ throw new PolicyCompileError(
1935
+ `claim('${key}') is text and compares to a text column only \u2014 '${column?.name ?? "?"}' is ${actual}`,
1936
+ "claim-type-mismatch"
1937
+ );
1938
+ }
1939
+ }
1940
+ function assertNoNegatedExternalClaim(operand) {
1941
+ walkPolicy(operand, (node) => {
1942
+ if (node.kind !== "compare") return;
1943
+ const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
1944
+ if (claim?.kind === "claim") {
1945
+ throw new PolicyCompileError(
1946
+ `claim('${claim.key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
1947
+ "unsupported-comparison"
1948
+ );
1949
+ }
1950
+ });
1951
+ }
1846
1952
  function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
1847
1953
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
1848
1954
  const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });