{"version":3,"file":"predicate.mjs","names":[],"sources":["../src/predicate.ts"],"sourcesContent":["/**\n * `when.predicate`: a trilean predicate tree over the evaluation context.\n *\n * The tree is validated with trilean's own schema and evaluated with trilean's synchronous evaluator, so it follows the same three-valued rules as every other condition: a field the context does not carry is indeterminate, an indeterminate condition restricts a deny or ask rule and never satisfies an allow rule, and a definite result on one branch settles an `or` or `and` whatever the other branch is.\n */\n\nimport { createSyncEvaluator } from \"trilean/evaluator-factory\";\nimport type { Evaluation } from \"trilean/evaluation\";\nimport type { Resolution, SyncResolvers } from \"trilean/resolvers\";\nimport { PredicateNodeSchema, type PredicateNode } from \"trilean/tree\";\n\nimport type { EvaluationContext } from \"./evaluate.ts\";\nimport { normaliseRemote } from \"./remote.ts\";\n\n/**\n * Node kinds a predicate may not contain. `exists` reads an unknown field as absent, and a quantifier or fold over the context's roles reads unknown roles as an empty list, so each would turn \"the host did not say\" into a definite answer that can grant. The remaining kinds need resolvers a context does not have.\n */\nconst UNSUPPORTED_KINDS: ReadonlySet<string> = new Set([\n  \"exists\",\n  \"some\",\n  \"every\",\n  \"fold\",\n  \"accumulator\",\n  \"lookup\",\n  \"delegate\",\n  \"treeReference\",\n  \"call\",\n]);\n\n/** Fields whose value is opaque JSON data rather than a child node, so a `kind` inside one is not a node. */\nconst DATA_FIELDS: ReadonlySet<string> = new Set([\n  \"key\",\n  \"payload\",\n  \"table\",\n  \"collection\",\n]);\n\n/** The kinds of unsupported nodes in a predicate tree, in the order they are met. */\nexport function unsupportedPredicateKinds(tree: unknown): string[] {\n  const found: string[] = [];\n  const walk = (value: unknown): void => {\n    if (Array.isArray(value)) {\n      for (const item of value) walk(item);\n      return;\n    }\n    if (typeof value !== \"object\" || value === null) return;\n    if (\n      \"kind\" in value &&\n      typeof value.kind === \"string\" &&\n      UNSUPPORTED_KINDS.has(value.kind)\n    ) {\n      found.push(value.kind);\n    }\n    for (const [name, child] of Object.entries(value)) {\n      if (!DATA_FIELDS.has(name)) walk(child);\n    }\n  };\n  walk(tree);\n  return found;\n}\n\nconst NOT_FOUND: Resolution = { found: false };\n\nfunction text(value: string | undefined): Resolution {\n  return value === undefined\n    ? NOT_FOUND\n    : { found: true, value: { kind: \"text\", value } };\n}\n\n/**\n * The resolvers a predicate reads the context through. A reference key names a context field: `cwd`, `branch`, `actor`, `remote` (normalised, as `when.remote` compares it), `env:NAME` (text) or `role:NAME` (a boolean, unknown when the host reported no roles). A field the context does not carry resolves to not-found.\n */\nfunction contextResolvers(ctx: EvaluationContext): SyncResolvers {\n  return {\n    resolveValue: (key) => {\n      if (typeof key !== \"string\") return NOT_FOUND;\n      if (key === \"cwd\") return text(ctx.cwd);\n      if (key === \"branch\") return text(ctx.branch);\n      if (key === \"actor\") return text(ctx.actor);\n      if (key === \"remote\") {\n        return text(\n          ctx.remote === undefined ? undefined : normaliseRemote(ctx.remote),\n        );\n      }\n      if (key.startsWith(\"env:\")) return text(ctx.env?.[key.slice(4)]);\n      if (key.startsWith(\"role:\")) {\n        if (ctx.roles === undefined) return NOT_FOUND;\n        return {\n          found: true,\n          value: { kind: \"boolean\", value: ctx.roles.includes(key.slice(5)) },\n        };\n      }\n      return NOT_FOUND;\n    },\n    resolveLookup: () => NOT_FOUND,\n    resolveCollection: () => {\n      throw new Error(\n        \"a when.predicate cannot iterate a collection; the schema rejects some, every and fold\",\n      );\n    },\n  };\n}\n\nconst evaluator = createSyncEvaluator({});\n\n/**\n * Check a rule's `predicate` and return it as a tree.\n *\n * @throws A `ZodError` when it is not a predicate tree, or an `Error` naming the unsupported node kinds: a policy built in code does not pass through the schema, and a predicate the evaluator cannot judge soundly must not be evaluated.\n */\nexport function parsePredicate(predicate: unknown): PredicateNode {\n  const unsupported = unsupportedPredicateKinds(predicate);\n  if (unsupported.length > 0) {\n    throw new Error(\n      `a when.predicate cannot contain ${[...new Set(unsupported)].join(\", \")} nodes`,\n    );\n  }\n  return PredicateNodeSchema.parse(predicate);\n}\n\n/** A predicate's result under a context. */\nexport function evaluatePredicateCondition(\n  predicate: PredicateNode,\n  ctx: EvaluationContext,\n): Evaluation<boolean> {\n  return evaluator.evaluatePredicate(\n    predicate,\n    undefined,\n    contextResolvers(ctx),\n  );\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,MAAM,oCAAyC,IAAI,IAAI;CACrD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAM,8BAAmC,IAAI,IAAI;CAC/C;CACA;CACA;CACA;AACF,CAAC;;AAGD,SAAgB,0BAA0B,MAAyB;CACjE,MAAM,QAAkB,CAAC;CACzB,MAAM,QAAQ,UAAyB;EACrC,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,KAAK,MAAM,QAAQ,OAAO,KAAK,IAAI;GACnC;EACF;EACA,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EACjD,IACE,UAAU,SACV,OAAO,MAAM,SAAS,YACtB,kBAAkB,IAAI,MAAM,IAAI,GAEhC,MAAM,KAAK,MAAM,IAAI;EAEvB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAC9C,IAAI,CAAC,YAAY,IAAI,IAAI,GAAG,KAAK,KAAK;CAE1C;CACA,KAAK,IAAI;CACT,OAAO;AACT;AAEA,MAAM,YAAwB,EAAE,OAAO,MAAM;AAE7C,SAAS,KAAK,OAAuC;CACnD,OAAO,UAAU,SACb,YACA;EAAE,OAAO;EAAM,OAAO;GAAE,MAAM;GAAQ;EAAM;CAAE;AACpD;;;;AAKA,SAAS,iBAAiB,KAAuC;CAC/D,OAAO;EACL,eAAe,QAAQ;GACrB,IAAI,OAAO,QAAQ,UAAU,OAAO;GACpC,IAAI,QAAQ,OAAO,OAAO,KAAK,IAAI,GAAG;GACtC,IAAI,QAAQ,UAAU,OAAO,KAAK,IAAI,MAAM;GAC5C,IAAI,QAAQ,SAAS,OAAO,KAAK,IAAI,KAAK;GAC1C,IAAI,QAAQ,UACV,OAAO,KACL,IAAI,WAAW,SAAY,SAAY,gBAAgB,IAAI,MAAM,CACnE;GAEF,IAAI,IAAI,WAAW,MAAM,GAAG,OAAO,KAAK,IAAI,MAAM,IAAI,MAAM,CAAC,EAAE;GAC/D,IAAI,IAAI,WAAW,OAAO,GAAG;IAC3B,IAAI,IAAI,UAAU,QAAW,OAAO;IACpC,OAAO;KACL,OAAO;KACP,OAAO;MAAE,MAAM;MAAW,OAAO,IAAI,MAAM,SAAS,IAAI,MAAM,CAAC,CAAC;KAAE;IACpE;GACF;GACA,OAAO;EACT;EACA,qBAAqB;EACrB,yBAAyB;GACvB,MAAM,IAAI,MACR,uFACF;EACF;CACF;AACF;AAEA,MAAM,YAAY,oBAAoB,CAAC,CAAC;;;;;;AAOxC,SAAgB,eAAe,WAAmC;CAChE,MAAM,cAAc,0BAA0B,SAAS;CACvD,IAAI,YAAY,SAAS,GACvB,MAAM,IAAI,MACR,mCAAmC,CAAC,GAAG,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,OAC1E;CAEF,OAAO,oBAAoB,MAAM,SAAS;AAC5C;;AAGA,SAAgB,2BACd,WACA,KACqB;CACrB,OAAO,UAAU,kBACf,WACA,QACA,iBAAiB,GAAG,CACtB;AACF"}