@atlasauth/pca-analyzer 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Atlas Authorization
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,110 @@
1
- # Temporary Holding Version
1
+ # @atlasauth/pca-analyzer
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Static analyzability of authority for Proof-Carrying Authority (PCA). PCA can verify one action and prove a quantitative trust-budget bound, but on its own it cannot answer "what can this whole delegated authority ever do?". This package decides that over PCA's predicate/caveat policy (reachability, vacuity, totality, delegation-safety subsumption, disjointness, equivalence, and conformance to a declared intent) without an SMT dependency.
4
+
5
+ It is a sound, bounded decision procedure in the spirit of AWS Cedar's symbolic compiler (SymCC, arXiv:2403.04651), which decides policy equivalence, subsumption and always-allow/deny with counterexamples. Instead of discharging to Z3, it exploits a finite-distinguishing-value property of PCA's predicate theory and decides by enumeration against the real `evaluatePredicates` from `@atlasauth/pca`.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm i @atlasauth/pca-analyzer @atlasauth/pca
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { compilePolicy } from '@atlasauth/pca';
17
+ import { subsumes, intentConformance, reachable } from '@atlasauth/pca-analyzer';
18
+
19
+ const parent = compilePolicy({ permissions: { stripe: ['refund'] }, limits: { refund: '$500' } });
20
+ const child = compilePolicy({ permissions: { stripe: ['refund'] }, limits: { refund: '$100' } });
21
+
22
+ // Delegation safety: a child must not widen its parent.
23
+ subsumes(parent, child).subsumes; // true
24
+ subsumes(child, parent); // { subsumes: false, counterexample: { verb: 'stripe.refund', params: { amount: 300 }, ... } }
25
+
26
+ // Can any input complete this action into an ALLOW? Returns a verified witness.
27
+ reachable(parent, { verb: 'stripe.refund', resource: 'charge:1', params: { amount: 50 } }); // { reachable: true, witness }
28
+
29
+ // Intent conformance: does the authority stay inside what was meant?
30
+ const policy = compilePolicy({ permissions: { stripe: ['refund'] }, limits: { refund: '$20000' } });
31
+ const r = intentConformance(policy, { verbs: ['stripe.refund'], maxAmount: 10000 });
32
+ // r.conforms === false; r.violations[0].kinds includes 'over-amount'
33
+ ```
34
+
35
+ ## Queries
36
+
37
+ All take a `CompiledPolicy` (from `compilePolicy` / `agent().policy`) or a bare
38
+ `{ predicates, caveats? }`. Every negative/relational verdict carries a concrete counterexample or
39
+ witness.
40
+
41
+ | Function | Decides |
42
+ |---|---|
43
+ | `reachable(policy, action)` | Can any input complete `(verb, resource, params)` into an ALLOW? Returns a verified `witness`. |
44
+ | `alwaysDenies(policy, verb?)` | Is the policy vacuous (denies everything / everything for `verb`)? |
45
+ | `alwaysAllows(policy, verb)` | Is the policy total for `verb` (admits every action of it)? |
46
+ | `subsumes(a, b)` | Does `a` permit everything `b` does (`admitted(b) ⊆ admitted(a)`)? The delegation-safety check: a child must be subsumed by its parent. |
47
+ | `disjoint(a, b)` | Do the two admit no common action? |
48
+ | `equivalent(a, b)` | Do the two admit exactly the same actions? |
49
+ | `intentConformance(policy, intent)` | Does the policy admit **only** actions inside a declared intent envelope (verbs + resource scopes + amount ceiling)? |
50
+
51
+ `intentConformance` is a safety check distinct from the runtime trust-budget. It catches the
52
+ *"a $14k renewal under a $10k cap on an approved vendor still violates intent"* class: an action that
53
+ passes the policy's predicates and stays under budget, yet falls outside what the principal intended,
54
+ because the policy's own ceiling is higher than (or absent relative to) the intent cap.
55
+
56
+
57
+ ## How it decides (the engine)
58
+
59
+ PCA's `where` conditions are a conjunction of threshold tests (`lt/lte/gt/gte/eq` on numbers),
60
+ membership tests (`eq/ne/in/nin` over finite values) and string `prefix` tests; predicates match on a
61
+ verb allowlist and a resource matcher; the envelope caveats are append-only and conjunctive. The
62
+ admitted region of a policy is therefore a **union of cells** of the arrangement defined by the literals
63
+ that appear in it. The analyzer builds one representative value per cell — every literal and numeric
64
+ threshold, the midpoints between consecutive thresholds, boundary points just outside the range, prefix
65
+ probes, a *fresh* verb / resource / value matching none of the literals, and an *absent* probe for each
66
+ field — then evaluates both policies at every representative with the real evaluator. Because that set
67
+ is distinguishing, agreeing on all representatives implies agreeing on all actions, so the relational
68
+ queries are decided exactly, and any mismatch is a real counterexample. Caveat satisfiability is decided
69
+ separately (`analyzeCaveats`): the context (time window, blast radius, depth, rate) is taken as
70
+ favourably chosen, and the one action-tied caveat, `reversibility_max`, becomes a per-action cap.
71
+
72
+ ## Decided exactly vs. sound-over-approximated
73
+
74
+ **Decided exactly** (the fragment):
75
+
76
+ - verb allowlists (`string`, `string[]`, `'*'`);
77
+ - resource matchers that are exact, `'*'`, or a trailing-`*` **prefix**;
78
+ - numeric `where` over `lt/lte/gt/gte/eq` (the interval engine; merges bounds, detects empty
79
+ conjunctions such as `amount >= 100 AND amount <= 10`);
80
+ - finite-domain `eq/ne/in/nin` and string `prefix` (the set engine — the fresh/absent probes make
81
+ unbounded string domains decidable for these operators);
82
+ - `exists` (via the absent probe);
83
+ - the conjunctive envelope caveats: `expires`, `not_before` (empty-window detection), `rate`,
84
+ `max_blast_radius`, `reversibility_max`, `delegation_depth`, `budget_alloc`.
85
+
86
+ **Sound but over-approximated** — the result is reported with `approximate: true` and **fails safe**
87
+ (never claims *subsumed / always-deny / disjoint / equivalent / conforms* without proof; falls back to
88
+ *not-subsumed / reachable / not-disjoint / not-equivalent / non-conforming*):
89
+
90
+ - **`re:` regex resource matchers** — a regex relation (containment/overlap) is not decided; the
91
+ resource dimension cannot be soundly enumerated. (A regex is still evaluated *exactly* against a fully
92
+ concrete resource in `reachable`, where there is nothing to enumerate.)
93
+ - **cross-field `ref` conditions** — a comparison whose operand is another field (`a != env.b`); the two
94
+ fields co-vary and are not resolved statically.
95
+ - **whole-`action.params` conditions** (a condition on the container rather than a `.subfield`).
96
+ - **grid overflow** — a representative grid larger than `maxCombinations` (default 200,000); no
97
+ enumeration is performed.
98
+
99
+ The soundness contract: a proven verdict (`approximate` falsy) is correct for the fragment above; an
100
+ `approximate` verdict is never a false "safe".
101
+
102
+ ## Status
103
+
104
+ Experimental. Verdicts without `approximate: true` are exact for the fragment listed above; the cryptography in `@atlasauth/pca` is unaudited, though this package performs no cryptography itself. An external SMT backend for the regex and cross-field fragments is not included.
105
+
106
+ Source and issues: https://github.com/Atlas-Authorization/pca
107
+
108
+ ## License
109
+
110
+ MIT - see LICENSE
@@ -0,0 +1,22 @@
1
+ import type { Caveat } from '@atlasauth/pca';
2
+ /**
3
+ * Static satisfiability of a PCA envelope caveat set (the leaf's cumulative, append-only, conjunctive
4
+ * caveats). This answers "is there ANY runtime context under which these caveats can all hold?" — the
5
+ * global gate on whether the authority can ever admit anything.
6
+ *
7
+ * The caveats constrain the runtime CaveatContext (now, blast radius, delegation depth, rate window,
8
+ * reversibility class), not the action's predicate fields. The analyzer treats the context as chosen
9
+ * favourably (the most permissive the authority could ever encounter) — the right stance for "what can
10
+ * this authority EVER do". The one caveat tied to the action itself is `reversibility_max`, whose cap
11
+ * is returned so the per-action check can enforce `class(action) <= cap`.
12
+ *
13
+ * Mirrors `envelopeCaveatEvaluator` in @atlasauth/pca (unknown/ malformed => fail closed).
14
+ */
15
+ export interface CaveatAnalysis {
16
+ /** Some context satisfies every caveat. */
17
+ sat: boolean;
18
+ reason?: string;
19
+ /** Strictest `reversibility_max` cap as an index into REVERSIBILITY_ORDER, if any caveat sets one. */
20
+ revCap?: number;
21
+ }
22
+ export declare function analyzeCaveats(caveats: readonly Caveat[] | undefined): CaveatAnalysis;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeCaveats = analyzeCaveats;
4
+ const types_1 = require("./types");
5
+ function finite(v) {
6
+ return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
7
+ }
8
+ function analyzeCaveats(caveats) {
9
+ if (!Array.isArray(caveats))
10
+ return { sat: false, reason: 'caveats are not an array' };
11
+ let notBeforeMax = -Infinity; // the action can only happen at now >= this
12
+ let expiresMin = Infinity; // ...and now < this
13
+ let revCap;
14
+ for (const cv of caveats) {
15
+ if (cv === null || typeof cv !== 'object' || typeof cv.type !== 'string') {
16
+ return { sat: false, reason: 'malformed caveat (not an object with a string type)' };
17
+ }
18
+ switch (cv.type) {
19
+ case 'expires': {
20
+ const at = finite(cv.at);
21
+ if (at === undefined)
22
+ return { sat: false, reason: 'expires.at is not a finite number' };
23
+ expiresMin = Math.min(expiresMin, at);
24
+ break;
25
+ }
26
+ case 'not_before': {
27
+ const at = finite(cv.at);
28
+ if (at === undefined)
29
+ return { sat: false, reason: 'not_before.at is not a finite number' };
30
+ notBeforeMax = Math.max(notBeforeMax, at);
31
+ break;
32
+ }
33
+ case 'rate': {
34
+ const max = finite(cv.max);
35
+ const per = finite(cv.per_secs);
36
+ if (max === undefined || per === undefined || per <= 0) {
37
+ return { sat: false, reason: 'rate caveat requires finite max and per_secs > 0' };
38
+ }
39
+ // With zero prior actions the count is 0, so the gate `0 < max` needs max >= 1.
40
+ if (max < 1)
41
+ return { sat: false, reason: `rate.max ${max} < 1 forbids every action` };
42
+ break;
43
+ }
44
+ case 'max_blast_radius': {
45
+ const max = finite(cv.max);
46
+ if (max === undefined)
47
+ return { sat: false, reason: 'max_blast_radius.max is not a finite number' };
48
+ // The least blast radius is 0, so this is satisfiable iff max >= 0.
49
+ if (max < 0)
50
+ return { sat: false, reason: 'max_blast_radius.max < 0 forbids every action' };
51
+ break;
52
+ }
53
+ case 'reversibility_max': {
54
+ const idx = types_1.REVERSIBILITY_ORDER.indexOf(String(cv.class));
55
+ if (idx < 0)
56
+ return { sat: false, reason: `reversibility_max.class '${String(cv.class)}' is unknown` };
57
+ revCap = revCap === undefined ? idx : Math.min(revCap, idx);
58
+ break;
59
+ }
60
+ case 'delegation_depth': {
61
+ const max = finite(cv.max);
62
+ if (max === undefined)
63
+ return { sat: false, reason: 'delegation_depth.max is not a finite number' };
64
+ // The least depth is 0, so this is satisfiable iff max >= 0.
65
+ if (max < 0)
66
+ return { sat: false, reason: 'delegation_depth.max < 0 forbids every action' };
67
+ break;
68
+ }
69
+ case 'budget_alloc': {
70
+ const lim = finite(cv.limit);
71
+ if (lim === undefined || lim < 0) {
72
+ return { sat: false, reason: 'budget_alloc.limit must be a finite number >= 0' };
73
+ }
74
+ break;
75
+ }
76
+ default:
77
+ return { sat: false, reason: `unknown caveat type '${cv.type}' (fail closed)` };
78
+ }
79
+ }
80
+ if (notBeforeMax !== -Infinity && expiresMin !== Infinity && notBeforeMax >= expiresMin) {
81
+ return {
82
+ sat: false,
83
+ reason: `not_before (${notBeforeMax}) >= expires (${expiresMin}): the validity window is empty`,
84
+ };
85
+ }
86
+ return { sat: true, revCap };
87
+ }
88
+ //# sourceMappingURL=caveats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caveats.js","sourceRoot":"","sources":["../src/caveats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { Caveat, Condition, Predicate } from '@atlasauth/pca';
2
+ import type { AnalyzerAction, PolicyInput } from './types';
3
+ type Cell = unknown;
4
+ /** Default cap on the representative grid size (product of all dimensions). */
5
+ export declare const DEFAULT_MAX_COMBINATIONS = 200000;
6
+ export interface PolicyModel {
7
+ predicates: Predicate[];
8
+ caveats: Caveat[];
9
+ caveatSat: boolean;
10
+ caveatReason?: string;
11
+ /** Strictest `reversibility_max` cap (index into REVERSIBILITY_ORDER), if any. */
12
+ revCap?: number;
13
+ /** Non-empty when the policy uses features the analyzer cannot decide exactly. */
14
+ undecidable: string[];
15
+ }
16
+ export declare function normalizePolicy(p: PolicyInput): {
17
+ predicates: Predicate[];
18
+ caveats: Caveat[];
19
+ };
20
+ export declare function toModel(p: PolicyInput): PolicyModel;
21
+ export interface Probes {
22
+ verbs: string[];
23
+ resources: string[];
24
+ /** `undefined` means "no reversibility dimension" (no policy sets a `reversibility_max`). */
25
+ revClasses: (string | undefined)[];
26
+ fields: {
27
+ path: string;
28
+ values: Cell[];
29
+ }[];
30
+ }
31
+ export interface ProbeInputs {
32
+ predicateSets: Predicate[][];
33
+ extraVerbs?: string[];
34
+ extraResources?: string[];
35
+ extraFieldConditions?: {
36
+ path: string;
37
+ cond: Condition;
38
+ }[];
39
+ /** Vary the reversibility class over all three classes (needed when any policy caps reversibility). */
40
+ varyReversibility: boolean;
41
+ }
42
+ export declare function collectProbes(inputs: ProbeInputs): Probes;
43
+ export declare function productSize(pr: Probes): number;
44
+ export interface Rep {
45
+ verb: string;
46
+ resource: string;
47
+ revClass: string | undefined;
48
+ /** Field path -> value, excluding absent fields. */
49
+ assignments: ReadonlyMap<string, unknown>;
50
+ }
51
+ export declare function representatives(pr: Probes): Generator<Rep>;
52
+ /** Does the policy admit this representative action? (predicate match AND static caveat gate.) */
53
+ export declare function admits(model: PolicyModel, rep: Rep): boolean;
54
+ /** Concrete admission of a fully-specified action (used by reachable's fast path; exact). */
55
+ export declare function admitsAction(model: PolicyModel, action: AnalyzerAction): boolean;
56
+ export declare function repToAction(rep: Rep): AnalyzerAction;
57
+ export interface SearchOutcome {
58
+ /** A representative for which the predicate returned true, if the search stopped on one. */
59
+ hit?: Rep;
60
+ /** The grid exceeded the configured bound; no enumeration was performed. */
61
+ overflow: boolean;
62
+ }
63
+ /**
64
+ * Walk the representative grid, stopping at the first rep for which `predicate` is true. Returns
65
+ * `overflow: true` (without walking) when the grid is larger than `max`.
66
+ */
67
+ export declare function search(pr: Probes, predicate: (rep: Rep) => boolean, max: number): SearchOutcome;
68
+ export {};