@sequenceholdings/orm 2.2.0 → 2.4.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.
@@ -1058,14 +1058,20 @@ zod.z.object({ irVersion: irVersionSchema });
1058
1058
  var CLAIMS_CONTRACT = {
1059
1059
  sub: "string",
1060
1060
  org: "string?",
1061
+ ext_subject: "string?",
1062
+ ext_email: "string?",
1061
1063
  roles: "string[]",
1062
1064
  capabilities: "string[]"
1063
1065
  };
1066
+ var EXTERNAL_POLICY_CLAIM_KEYS = ["ext_subject", "ext_email"];
1067
+ function isExternalPolicyClaimKey(key) {
1068
+ return EXTERNAL_POLICY_CLAIM_KEYS.includes(key);
1069
+ }
1064
1070
  var scalarLiteral = zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]);
1065
1071
  var valueIrSchema = zod.z.discriminatedUnion("kind", [
1066
1072
  zod.z.object({ kind: zod.z.literal("literal"), value: zod.z.union([scalarLiteral, zod.z.array(scalarLiteral)]) }),
1067
1073
  zod.z.object({ kind: zod.z.literal("variable"), name: zod.z.string() }),
1068
- zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org"]) })
1074
+ zod.z.object({ kind: zod.z.literal("claim"), key: zod.z.enum(["sub", "org", ...EXTERNAL_POLICY_CLAIM_KEYS]) })
1069
1075
  ]);
1070
1076
  var CMP_OPS = [
1071
1077
  "eq",
@@ -1636,6 +1642,7 @@ var Parser2 = class {
1636
1642
  return node;
1637
1643
  }
1638
1644
  if (this.peekKeyword("exists")) return this.parseExists();
1645
+ if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
1639
1646
  return this.parseComparison();
1640
1647
  }
1641
1648
  parseBooleanCall(fn) {
@@ -1655,6 +1662,13 @@ var Parser2 = class {
1655
1662
  this.expect("rparen", "')' closing exists(...)");
1656
1663
  return { kind: "exists", path, where };
1657
1664
  }
1665
+ parseVisible() {
1666
+ this.advance();
1667
+ this.expect("lparen", "'(' after visible");
1668
+ const path = this.parsePath("a link path in visible(...)");
1669
+ this.expect("rparen", "')' closing visible(...)");
1670
+ return { kind: "visible", path };
1671
+ }
1658
1672
  parseComparison() {
1659
1673
  const left = this.parseOperand();
1660
1674
  const operator = this.expect("operator", "a comparison operator");
@@ -1770,6 +1784,24 @@ function parsePolicy(source) {
1770
1784
 
1771
1785
  // src/predicate-v2/compile.ts
1772
1786
  var POLICY_MAX_LINK_DEPTH = 3;
1787
+ function scopeToNamespace(pred, namespace) {
1788
+ switch (pred.kind) {
1789
+ case "capability":
1790
+ case "role":
1791
+ return pred.namespace ? pred : { ...pred, namespace };
1792
+ case "and":
1793
+ case "or":
1794
+ return { ...pred, operands: pred.operands.map((operand) => scopeToNamespace(operand, namespace)) };
1795
+ case "not":
1796
+ return { ...pred, operand: scopeToNamespace(pred.operand, namespace) };
1797
+ case "exists":
1798
+ return { ...pred, where: scopeToNamespace(pred.where, namespace) };
1799
+ case "aggPredicate":
1800
+ return pred.where ? { ...pred, where: scopeToNamespace(pred.where, namespace) } : pred;
1801
+ default:
1802
+ return pred;
1803
+ }
1804
+ }
1773
1805
  var CMP_BY_OP = {
1774
1806
  "=": "eq",
1775
1807
  "!=": "neq",
@@ -1803,7 +1835,7 @@ function resolveLink(info, ref) {
1803
1835
  function columnCandidates(info) {
1804
1836
  return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
1805
1837
  }
1806
- var PolicyCompiler = class {
1838
+ var PolicyCompiler = class _PolicyCompiler {
1807
1839
  constructor(ctx) {
1808
1840
  this.ctx = ctx;
1809
1841
  }
@@ -1815,13 +1847,16 @@ var PolicyCompiler = class {
1815
1847
  case "or":
1816
1848
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
1817
1849
  case "not":
1818
- return { kind: "not", operand: this.compile(node.operand, table, depth) };
1850
+ assertNoNegatedExternalClaim(node.operand);
1851
+ return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
1819
1852
  case "capability":
1820
1853
  return { kind: "capability", key: node.key };
1821
1854
  case "role":
1822
1855
  return { kind: "role", key: node.key };
1823
1856
  case "exists":
1824
1857
  return this.compileExists(node.path, node.where, table, depth);
1858
+ case "visible":
1859
+ return this.compileVisible(node.path, table, depth);
1825
1860
  case "compare":
1826
1861
  return this.compileCompare(node, table, depth);
1827
1862
  }
@@ -1835,6 +1870,36 @@ var PolicyCompiler = class {
1835
1870
  }
1836
1871
  return pred;
1837
1872
  }
1873
+ /** `visible(path)` → `exists(path, <final target's read policy>)`. */
1874
+ compileVisible(path, table, depth) {
1875
+ const { links, finalTable } = this.resolveLinkPath(path, table, depth, { toOneOnly: false });
1876
+ const readPolicy = this.ctx.resolver.readPolicy;
1877
+ if (!readPolicy) {
1878
+ throw new PolicyCompileError(
1879
+ `visible(${path.join(".")}) needs the target's read policy, which this compile context does not provide`,
1880
+ "visible-unresolvable"
1881
+ );
1882
+ }
1883
+ if (this.ctx.visiting.includes(finalTable)) {
1884
+ throw new PolicyCompileError(
1885
+ `visible(${path.join(".")}) on '${table}' reaches '${finalTable}' whose read policy is already being expanded (${[...this.ctx.visiting, finalTable].join(" \u2192 ")})`,
1886
+ "visible-cycle"
1887
+ );
1888
+ }
1889
+ const expression = readPolicy(finalTable);
1890
+ let pred = expression === void 0 ? { kind: "const", value: true } : compilePolicyAt({
1891
+ expression,
1892
+ table: finalTable,
1893
+ ctx: { ...this.ctx, visiting: [...this.ctx.visiting, finalTable] },
1894
+ depth: depth + links.length
1895
+ });
1896
+ const owner = this.ctx.resolver.policyNamespace?.(finalTable);
1897
+ if (owner) pred = scopeToNamespace(pred, owner);
1898
+ for (let i = links.length - 1; i >= 0; i -= 1) {
1899
+ pred = { kind: "exists", link: links[i].name, where: pred };
1900
+ }
1901
+ return pred;
1902
+ }
1838
1903
  resolveLinkPath(path, table, depth, { toOneOnly }) {
1839
1904
  const links = [];
1840
1905
  let current = table;
@@ -1894,6 +1959,10 @@ var PolicyCompiler = class {
1894
1959
  columnCandidates(info)
1895
1960
  );
1896
1961
  }
1962
+ if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
1963
+ if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
1964
+ assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
1965
+ }
1897
1966
  let cmp = this.buildCmp(op, column, valueSide);
1898
1967
  for (let i = links.length - 1; i >= 0; i -= 1) {
1899
1968
  cmp = { kind: "exists", link: links[i].name, where: cmp };
@@ -1914,10 +1983,45 @@ var PolicyCompiler = class {
1914
1983
  return { kind: "cmp", op: CMP_BY_OP[op], column, value };
1915
1984
  }
1916
1985
  };
1986
+ function assertExternalClaimComparison({ op, key, column }) {
1987
+ if (op !== "=") {
1988
+ throw new PolicyCompileError(
1989
+ `claim('${key}') supports = comparisons only, got '${op}'`,
1990
+ "unsupported-comparison"
1991
+ );
1992
+ }
1993
+ if (column?.type !== "text") {
1994
+ const actual = column?.type === void 0 ? "of unknown type" : typeof column.type === "string" ? column.type : "an enum";
1995
+ throw new PolicyCompileError(
1996
+ `claim('${key}') is text and compares to a text column only \u2014 '${column?.name ?? "?"}' is ${actual}`,
1997
+ "claim-type-mismatch"
1998
+ );
1999
+ }
2000
+ }
2001
+ function negatedExternalClaim(key) {
2002
+ return new PolicyCompileError(
2003
+ `claim('${key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2004
+ "unsupported-comparison"
2005
+ );
2006
+ }
2007
+ function assertNoNegatedExternalClaim(operand) {
2008
+ walkPolicy(operand, (node) => {
2009
+ if (node.kind !== "compare") return;
2010
+ const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
2011
+ if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
2012
+ });
2013
+ }
1917
2014
  function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
2015
+ return compilePredicateAt({
2016
+ expression,
2017
+ table,
2018
+ depth: 0,
2019
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: [table] }
2020
+ });
2021
+ }
2022
+ function compilePredicateAt({ expression, table, ctx, depth }) {
1918
2023
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
1919
- const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
1920
- return compiler.compile(parsed.ast, table, 0);
2024
+ return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
1921
2025
  }
1922
2026
  function assertClaimsOnlyWhen(parsed) {
1923
2027
  walkPolicy(parsed.ast, (node) => {
@@ -1934,25 +2038,34 @@ function assertClaimsOnlyWhen(parsed) {
1934
2038
  "when-not-claims-only"
1935
2039
  );
1936
2040
  }
1937
- if (node.kind === "exists") {
2041
+ if (node.kind === "exists" || node.kind === "visible") {
1938
2042
  throw new PolicyCompileError(
1939
- "variant 'when' guards are claims-only \u2014 exists() is not allowed",
2043
+ `variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
1940
2044
  "when-not-claims-only"
1941
2045
  );
1942
2046
  }
1943
2047
  });
1944
2048
  }
1945
- function compilePolicy({ expression, table, resolver, maxLinkDepth }) {
1946
- if (typeof expression === "string") {
1947
- return compilePredicateV2({ expression, table, resolver, maxLinkDepth });
1948
- }
1949
- const defaultPred = compilePredicateV2({ expression: expression.default, table, resolver, maxLinkDepth });
2049
+ function initialVisiting({ table, access }) {
2050
+ return (access ?? "read") === "read" ? [table] : [];
2051
+ }
2052
+ function compilePolicy({ expression, table, resolver, maxLinkDepth, access }) {
2053
+ return compilePolicyAt({
2054
+ expression,
2055
+ table,
2056
+ depth: 0,
2057
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: initialVisiting({ table, access }) }
2058
+ });
2059
+ }
2060
+ function compilePolicyAt({ expression, table, ctx, depth }) {
2061
+ if (typeof expression === "string") return compilePredicateAt({ expression, table, ctx, depth });
2062
+ const defaultPred = compilePredicateAt({ expression: expression.default, table, ctx, depth });
1950
2063
  if (expression.variants.length === 0) return defaultPred;
1951
2064
  const branches = expression.variants.map((variant) => {
1952
2065
  const parsedWhen = parsePolicy(variant.when);
1953
2066
  assertClaimsOnlyWhen(parsedWhen);
1954
- const when = compilePredicateV2({ expression: parsedWhen, table, resolver, maxLinkDepth });
1955
- const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateV2({ expression: variant.policy, table, resolver, maxLinkDepth });
2067
+ const when = compilePredicateAt({ expression: parsedWhen, table, ctx, depth });
2068
+ const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateAt({ expression: variant.policy, table, ctx, depth });
1956
2069
  return { kind: "and", operands: [when, grant] };
1957
2070
  });
1958
2071
  return { kind: "or", operands: [defaultPred, ...branches] };