@sequenceholdings/orm 2.2.0 → 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.
@@ -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;
@@ -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;
@@ -1056,14 +1056,20 @@ z.object({ irVersion: irVersionSchema });
1056
1056
  var CLAIMS_CONTRACT = {
1057
1057
  sub: "string",
1058
1058
  org: "string?",
1059
+ ext_subject: "string?",
1060
+ ext_email: "string?",
1059
1061
  roles: "string[]",
1060
1062
  capabilities: "string[]"
1061
1063
  };
1064
+ var EXTERNAL_POLICY_CLAIM_KEYS = ["ext_subject", "ext_email"];
1065
+ function isExternalPolicyClaimKey(key) {
1066
+ return EXTERNAL_POLICY_CLAIM_KEYS.includes(key);
1067
+ }
1062
1068
  var scalarLiteral = z.union([z.string(), z.number(), z.boolean(), z.null()]);
1063
1069
  var valueIrSchema = z.discriminatedUnion("kind", [
1064
1070
  z.object({ kind: z.literal("literal"), value: z.union([scalarLiteral, z.array(scalarLiteral)]) }),
1065
1071
  z.object({ kind: z.literal("variable"), name: z.string() }),
1066
- 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]) })
1067
1073
  ]);
1068
1074
  var CMP_OPS = [
1069
1075
  "eq",
@@ -1813,6 +1819,7 @@ var PolicyCompiler = class {
1813
1819
  case "or":
1814
1820
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
1815
1821
  case "not":
1822
+ assertNoNegatedExternalClaim(node.operand);
1816
1823
  return { kind: "not", operand: this.compile(node.operand, table, depth) };
1817
1824
  case "capability":
1818
1825
  return { kind: "capability", key: node.key };
@@ -1892,6 +1899,9 @@ var PolicyCompiler = class {
1892
1899
  columnCandidates(info)
1893
1900
  );
1894
1901
  }
1902
+ if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
1903
+ assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
1904
+ }
1895
1905
  let cmp = this.buildCmp(op, column, valueSide);
1896
1906
  for (let i = links.length - 1; i >= 0; i -= 1) {
1897
1907
  cmp = { kind: "exists", link: links[i].name, where: cmp };
@@ -1912,6 +1922,33 @@ var PolicyCompiler = class {
1912
1922
  return { kind: "cmp", op: CMP_BY_OP[op], column, value };
1913
1923
  }
1914
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
+ }
1915
1952
  function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
1916
1953
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
1917
1954
  const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });