permdock 0.0.0 → 0.1.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/README.md +268 -1
- package/dist/a2a/index.d.ts +178 -0
- package/dist/a2a/index.js +271 -0
- package/dist/ai-sdk/index.d.ts +74 -0
- package/dist/ai-sdk/index.js +137 -0
- package/dist/approval-required-TzNB9oQA.js +6 -0
- package/dist/approvals/index.d.ts +3 -0
- package/dist/approvals/index.js +4 -0
- package/dist/approvals-DaxY-Bdg.js +218 -0
- package/dist/approvers-aH0PJkmf.js +20 -0
- package/dist/arazzo-CTeVuXr5.js +70 -0
- package/dist/arazzo-aoTkqYsJ.js +54 -0
- package/dist/authzen/index.d.ts +48 -0
- package/dist/authzen/index.js +336 -0
- package/dist/better-auth/index.d.ts +92 -0
- package/dist/better-auth/index.js +324 -0
- package/dist/budget-JqcRqyhK.d.ts +11 -0
- package/dist/budget-NbEtbJC_.js +11 -0
- package/dist/capability-B68OFnZx.js +171 -0
- package/dist/capability-BDG_nkxt.d.ts +99 -0
- package/dist/catalog/index.d.ts +12 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog-B0E6bggc.js +48 -0
- package/dist/catalog-H-vFGwWK.js +55 -0
- package/dist/catalog-doc-BSo_Q_-6.js +298 -0
- package/dist/catalog-idsHc0vo.js +2 -0
- package/dist/catalog-path-CnI0XTFK.js +12 -0
- package/dist/claude-agent/index.d.ts +81 -0
- package/dist/claude-agent/index.js +83 -0
- package/dist/clerk/index.d.ts +56 -0
- package/dist/clerk/index.js +280 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/index.d.ts +46 -0
- package/dist/cli/index.js +5 -0
- package/dist/cloud/index.d.ts +91 -0
- package/dist/cloud/index.js +390 -0
- package/dist/cloud-BNQTrmrr.js +50 -0
- package/dist/cloud-Cuxr6Czw.js +117 -0
- package/dist/collect-B7_TIfmg.js +49 -0
- package/dist/collect-O-tOtbnk.js +350 -0
- package/dist/collect-fRPAQIWb.js +2 -0
- package/dist/commands-BMh_Etm2.js +93 -0
- package/dist/compact-y6vfu2_f.js +19 -0
- package/dist/compare-pfyVnBjE.js +12 -0
- package/dist/compile-CQbvyIBr.d.ts +30 -0
- package/dist/compile-D22iwvuV.js +558 -0
- package/dist/config-BPYmjTOp.js +26 -0
- package/dist/connection-JvqwHODC.js +194 -0
- package/dist/context-CKEAabd7.js +73 -0
- package/dist/context-gtAwAILx.js +6 -0
- package/dist/convex/index.d.ts +40 -0
- package/dist/convex/index.js +55 -0
- package/dist/create-BKF3dgFk.d.ts +162 -0
- package/dist/create-CSkhDU9T.js +222 -0
- package/dist/credential-B_rkUNlY.js +326 -0
- package/dist/credentials-BgRX3WEg.js +268 -0
- package/dist/describe-Bt1YD5uS.js +99 -0
- package/dist/describe-DernyItf.d.ts +37 -0
- package/dist/diff-BUzf5Poi.js +48 -0
- package/dist/diff-x5Il8LVa.js +418 -0
- package/dist/digest-B3kc_PgQ.d.ts +19 -0
- package/dist/digest-BI-6aa6d.js +27 -0
- package/dist/doctor-BGle8Gqi.js +1314 -0
- package/dist/doctor-Bqo6FwUa.js +53 -0
- package/dist/drizzle/index.d.ts +72 -0
- package/dist/drizzle/index.js +160 -0
- package/dist/duration-xK2jGFVc.js +25 -0
- package/dist/elysia/index.d.ts +71 -0
- package/dist/elysia/index.js +103 -0
- package/dist/errors-DSMnAVNa.js +43 -0
- package/dist/errors-DbZ1YOnb.d.ts +138 -0
- package/dist/errors-Iy71s7Oj.js +177 -0
- package/dist/errors-m3u9TmDu.js +27 -0
- package/dist/evaluations-DsAU9ppl.js +479 -0
- package/dist/eve/index.d.ts +95 -0
- package/dist/eve/index.js +181 -0
- package/dist/express/index.d.ts +46 -0
- package/dist/express/index.js +108 -0
- package/dist/fastify/index.d.ts +56 -0
- package/dist/fastify/index.js +141 -0
- package/dist/files-B_wAGYmt.js +121 -0
- package/dist/fixtures-Cvaw0gDG.js +64 -0
- package/dist/freeze-y1_jQL4v.js +31 -0
- package/dist/from-snapshot-BMGnhmgv.js +552 -0
- package/dist/generate-CH7ThJ14.js +173 -0
- package/dist/grantee-Bv04_Vzf.js +521 -0
- package/dist/headers-B5RRv3Xm.js +6 -0
- package/dist/headers-C8SHQccD.d.ts +6 -0
- package/dist/helpers-DnSOrhB9.js +682 -0
- package/dist/hono/index.d.ts +70 -0
- package/dist/hono/index.js +131 -0
- package/dist/hosted-BwKQ1BTA.js +265 -0
- package/dist/http-DPuDaChb.d.ts +13 -0
- package/dist/http-GgVApX1k.js +111 -0
- package/dist/index-CltcQ4Bf.d.ts +138 -0
- package/dist/index-LF_hS-yn.d.ts +177 -0
- package/dist/index-fG6NSXlc.d.ts +9 -0
- package/dist/index-xIxJLwOQ.d.ts +415 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +21 -0
- package/dist/jwt/index.d.ts +87 -0
- package/dist/jwt/index.js +571 -0
- package/dist/kernel-YOGCWNLf.js +211 -0
- package/dist/kysely/index.d.ts +77 -0
- package/dist/kysely/index.js +129 -0
- package/dist/load-BZ24CpZP.js +69 -0
- package/dist/load-QOOm1z70.js +2 -0
- package/dist/load-jose-BL6LH4bL.js +11 -0
- package/dist/lru-8q4WZjBV.js +28 -0
- package/dist/manifest-DDQbC6K0.d.ts +118 -0
- package/dist/map-DowSO96j.js +158 -0
- package/dist/map-claims-B_6I1Rn-.js +360 -0
- package/dist/map-error-RJ3ZsUyA.js +16 -0
- package/dist/markers-B0hYLvMD.js +60 -0
- package/dist/may-use-CnFEjVEp.js +37 -0
- package/dist/mcp/index.d.ts +127 -0
- package/dist/mcp/index.js +563 -0
- package/dist/nest/index.d.ts +80 -0
- package/dist/nest/index.js +270 -0
- package/dist/next/client.d.ts +24 -0
- package/dist/next/client.js +31 -0
- package/dist/next/index.d.ts +110 -0
- package/dist/next/index.js +195 -0
- package/dist/next/plugin.d.ts +14 -0
- package/dist/next/plugin.js +2 -0
- package/dist/node/index.d.ts +43 -0
- package/dist/node/index.js +66 -0
- package/dist/openai/index.d.ts +71 -0
- package/dist/openai/index.js +105 -0
- package/dist/openapi/index.d.ts +93 -0
- package/dist/openapi/index.js +3 -0
- package/dist/openapi-CwePaF7C.js +164 -0
- package/dist/openapi-DWD0Uc_4.js +250 -0
- package/dist/openapi-DcqM8nU7.js +306 -0
- package/dist/openapi-import-BWRUNNeW.js +5959 -0
- package/dist/openapi-schema-D8j_T9tO.js +6902 -0
- package/dist/orpc/index.d.ts +64 -0
- package/dist/orpc/index.js +188 -0
- package/dist/otel/index.d.ts +13 -0
- package/dist/otel/index.js +202 -0
- package/dist/package-root-zGkxO3oN.js +30 -0
- package/dist/parse-DPfIvFMU.js +135 -0
- package/dist/pdp/index.d.ts +22 -0
- package/dist/pdp/index.js +795 -0
- package/dist/peer-BOr_kJE1.js +31 -0
- package/dist/permdock-yoZ7EjxX.js +2956 -0
- package/dist/permissions-B4apnahQ.d.ts +185 -0
- package/dist/permissions-B8vCg_bI.d.ts +52 -0
- package/dist/permissions-CofRqesP.js +142 -0
- package/dist/permissions-DC7GROPA.js +600 -0
- package/dist/pg-4GJWDUxG.js +953 -0
- package/dist/pins-CqE1jZuF.js +12 -0
- package/dist/plugin-4gr7IxFJ.js +65 -0
- package/dist/policy-ANp36PnI.js +971 -0
- package/dist/policy-CInmNGkM.d.ts +1959 -0
- package/dist/policy-Cq2fB8Q5.d.ts +20 -0
- package/dist/prisma/index.d.ts +135 -0
- package/dist/prisma/index.js +430 -0
- package/dist/problem-Cgp9-KZA.js +226 -0
- package/dist/prompt-CXYhLfsS.js +32 -0
- package/dist/protected-B4K3BKoX.js +138 -0
- package/dist/protected-BuPAycJT.d.ts +26 -0
- package/dist/protected-wY7nXhY9.d.ts +23 -0
- package/dist/provider-_wx46HpB.js +48 -0
- package/dist/provider-wTAUda2v.d.ts +6 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -0
- package/dist/react/provider-client.d.ts +2 -0
- package/dist/react/provider-client.js +3 -0
- package/dist/react-native/index.d.ts +42 -0
- package/dist/react-native/index.js +174 -0
- package/dist/refs-ChLSxbST.js +90 -0
- package/dist/relations-r4soIFZB.js +426 -0
- package/dist/revocations-Cy06JNx2.d.ts +21 -0
- package/dist/rls-BOFU3chG.js +233 -0
- package/dist/rls-CJVja9Ww.js +2119 -0
- package/dist/rls-graph-DxQ39PJj.js +371 -0
- package/dist/rls-helpers-BxRuif0l.js +1346 -0
- package/dist/rls-import-CXV6LBlS.js +942 -0
- package/dist/rls-migrate-DrFPbf8p.js +366 -0
- package/dist/row-check-BajTgQZ-.d.ts +43 -0
- package/dist/run-CJ6UvS3a.js +155 -0
- package/dist/runtime-DP9ttFh1.js +27 -0
- package/dist/saas-BRrZzJkM.js +1193 -0
- package/dist/schema-P8yoma__.js +296 -0
- package/dist/scim/index.d.ts +2 -0
- package/dist/scim/index.js +2 -0
- package/dist/scim-9NFa0XVZ.js +1445 -0
- package/dist/scopes-cKVp_WTw.js +212 -0
- package/dist/server/index.d.ts +123 -0
- package/dist/server/index.js +8 -0
- package/dist/sha256-BO0swRXL.js +150 -0
- package/dist/sink-Bd9PSyfq.js +141 -0
- package/dist/sink-Cfr9-7YC.d.ts +104 -0
- package/dist/skills-C_lpV4Jx.js +50 -0
- package/dist/skills-Dx3QLnMY.js +110 -0
- package/dist/snapshot-DE8dE_hz.js +1051 -0
- package/dist/solid/index.d.ts +63 -0
- package/dist/solid/index.js +197 -0
- package/dist/source-DoBBYJi2.js +6 -0
- package/dist/sources-DOpW5Ila.d.ts +347 -0
- package/dist/sources-DVHM3Mwq.js +284 -0
- package/dist/sql-Bll1d7y0.js +22 -0
- package/dist/src-CTNT-PBW.js +430 -0
- package/dist/ssf/index.d.ts +3 -0
- package/dist/ssf/index.js +622 -0
- package/dist/store-Bq90395i.d.ts +27 -0
- package/dist/store-C6hFW6ta.js +508 -0
- package/dist/stores-CkG2HrSg.js +140 -0
- package/dist/stores-CvOnu_2T.d.ts +41 -0
- package/dist/stream-BhMfZOwr.d.ts +12 -0
- package/dist/stream-Q_xlB6Ko.js +52 -0
- package/dist/style-CTm1AXDT.js +22 -0
- package/dist/subject-C6dLxqHU.js +24 -0
- package/dist/subject-settings-xt7V845g.js +82 -0
- package/dist/supabase/index.d.ts +67 -0
- package/dist/supabase/index.js +4 -0
- package/dist/supabase/middleware.d.ts +77 -0
- package/dist/supabase/middleware.js +68 -0
- package/dist/supabase-D8UlBA7Z.js +83 -0
- package/dist/supabase-DTxGtYdi.js +796 -0
- package/dist/supabase-hook-DW-xP4hU.js +2 -0
- package/dist/supabase-hook-D_OqGb_V.js +1528 -0
- package/dist/svelte/Protected.svelte +34 -0
- package/dist/svelte/index.d.ts +10 -0
- package/dist/svelte/index.js +53 -0
- package/dist/svelte/runtime.d.ts +6 -0
- package/dist/svelte/runtime.js +3 -0
- package/dist/svelte/source.d.ts +10 -0
- package/dist/svelte/source.js +8 -0
- package/dist/terminal/index.d.ts +171 -0
- package/dist/terminal/index.js +821 -0
- package/dist/testing/index.d.ts +656 -0
- package/dist/testing/index.js +3208 -0
- package/dist/testing/saas/index.d.ts +194 -0
- package/dist/testing/saas/index.js +3 -0
- package/dist/testing/saas/permissions.d.ts +2 -0
- package/dist/testing/saas/permissions.js +2 -0
- package/dist/text-diff-BdkkYSbi.js +15 -0
- package/dist/thenable-okc2xDsj.js +10 -0
- package/dist/timeout-D51niuQP.js +8 -0
- package/dist/trpc/index.d.ts +80 -0
- package/dist/trpc/index.js +205 -0
- package/dist/types-B8w5PFtS.d.ts +141 -0
- package/dist/types-B9dmiiab.d.ts +66 -0
- package/dist/types-BBK-exvJ.d.ts +177 -0
- package/dist/types-BvBk1wgn.d.ts +118 -0
- package/dist/types-CxquUByI.d.ts +9 -0
- package/dist/types-D6Z6-rKJ.d.ts +312 -0
- package/dist/types-DbWdEfcm.d.ts +87 -0
- package/dist/types-bva3i4Gf.d.ts +98 -0
- package/dist/types-i4BAOvdb.d.ts +97 -0
- package/dist/unplugin/index.d.ts +7 -0
- package/dist/unplugin/index.js +30 -0
- package/dist/usage-1ozMubLY.js +42 -0
- package/dist/usage-CUnK6Ou9.js +530 -0
- package/dist/usage-PyEor6GA.js +2 -0
- package/dist/validity-D0uRmJKJ.js +224 -0
- package/dist/verifier-B90agnhl.js +461 -0
- package/dist/version-CnSh33Fj.js +16 -0
- package/dist/vocabulary-Dk0fs-Jk.d.ts +56 -0
- package/dist/vue/index.d.ts +57 -0
- package/dist/vue/index.js +203 -0
- package/dist/webmcp/index.d.ts +75 -0
- package/dist/webmcp/index.js +313 -0
- package/package.json +423 -4
- package/rulesets/permdock.yaml +29 -0
- package/schemas/catalog-v1.json +507 -0
- package/schemas/openapi/oas-3.1.json +1298 -0
- package/schemas/openapi/oas-3.2.json +1516 -0
- package/schemas/openapi/overlay-1.1.json +79 -0
- package/schemas/supabase-claims-v1.json +139 -0
- package/schemas/supabase-manifest-v1.json +217 -0
- package/skills/permdock/SKILL.md +66 -0
- package/skills/permdock-agents/SKILL.md +77 -0
- package/skills/permdock-agents/references/runtimes.md +211 -0
- package/skills/permdock-approvals/SKILL.md +80 -0
- package/skills/permdock-approvals/references/stores-and-resume.md +83 -0
- package/skills/permdock-audit/SKILL.md +67 -0
- package/skills/permdock-credentials/SKILL.md +55 -0
- package/skills/permdock-credentials/references/tokens-keys-links.md +127 -0
- package/skills/permdock-data/SKILL.md +57 -0
- package/skills/permdock-data/references/queries-and-rls.md +54 -0
- package/skills/permdock-data/references/relationships.md +65 -0
- package/skills/permdock-tenancy/SKILL.md +77 -0
- package/skills/permdock-tenancy/references/ownership-and-custom-roles.md +83 -0
- package/skills/permdock-wire/SKILL.md +116 -0
- package/skills/permdock-wire/references/adapters.md +480 -0
- package/skills/permdock-wire/references/policy-options.md +43 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { i as findPermission } from "./permissions-DC7GROPA.js";
|
|
2
|
+
import { m as memoryRoleSource } from "./src-CTNT-PBW.js";
|
|
3
|
+
import { l as canonicalJson } from "./snapshot-DE8dE_hz.js";
|
|
4
|
+
import { t as createPermDock } from "./permdock-yoZ7EjxX.js";
|
|
5
|
+
import { t as buildCatalog } from "./catalog-doc-BSo_Q_-6.js";
|
|
6
|
+
import { a as loadModule, n as asPolicy, o as pickNamed } from "./load-BZ24CpZP.js";
|
|
7
|
+
import { t as parseCatalog } from "./parse-DPfIvFMU.js";
|
|
8
|
+
import { a as usageResult } from "./errors-DSMnAVNa.js";
|
|
9
|
+
import { n as fixtureSubject, r as loadFixtures, t as fixtureRow } from "./fixtures-Cvaw0gDG.js";
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
//#region src/cli/diff.ts
|
|
13
|
+
const EMPTY_SCAN = {
|
|
14
|
+
roots: [],
|
|
15
|
+
definitionFiles: {},
|
|
16
|
+
usages: {},
|
|
17
|
+
unknown: [],
|
|
18
|
+
dynamic: [],
|
|
19
|
+
roleNames: [],
|
|
20
|
+
planNames: [],
|
|
21
|
+
allowKeys: [],
|
|
22
|
+
snapshots: []
|
|
23
|
+
};
|
|
24
|
+
async function loadSide(cwd, source, now) {
|
|
25
|
+
const abs = resolve(cwd, source);
|
|
26
|
+
if (!existsSync(abs)) throw new Error(`PermDock CLI: not found: ${source}`);
|
|
27
|
+
if (abs.endsWith(".json")) return {
|
|
28
|
+
source,
|
|
29
|
+
catalog: parseCatalog(readFileSync(abs, "utf8")),
|
|
30
|
+
policy: void 0
|
|
31
|
+
};
|
|
32
|
+
const policy = asPolicy(pickNamed(await loadModule(abs), ["policy"]));
|
|
33
|
+
return {
|
|
34
|
+
source,
|
|
35
|
+
policy,
|
|
36
|
+
catalog: buildCatalog(policy.permissions, EMPTY_SCAN, now.toISOString(), policy)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function setDiff(before, after) {
|
|
40
|
+
const a = new Set(before);
|
|
41
|
+
const b = new Set(after);
|
|
42
|
+
return {
|
|
43
|
+
added: after.filter((item) => !a.has(item)).toSorted(),
|
|
44
|
+
removed: before.filter((item) => !b.has(item)).toSorted()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** What makes two grants "the same grant" across versions: everything but the body. */
|
|
48
|
+
function grantIdentity(grant) {
|
|
49
|
+
return canonicalJson({
|
|
50
|
+
permission: grant.permission,
|
|
51
|
+
effect: grant.effect,
|
|
52
|
+
role: grant.role,
|
|
53
|
+
to: grant.to,
|
|
54
|
+
scope: grant.scope
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function same(a, b) {
|
|
58
|
+
return canonicalJson(a ?? null) === canonicalJson(b ?? null);
|
|
59
|
+
}
|
|
60
|
+
/** The body differences between two grants with one identity, in a fixed order. */
|
|
61
|
+
function grantChanges(before, after) {
|
|
62
|
+
const out = [];
|
|
63
|
+
if (!same(before.where, after.where)) out.push(before.where === void 0 ? "where added" : after.where === void 0 ? "where removed" : "where changed");
|
|
64
|
+
if (!same(before.check, after.check)) out.push(before.check === void 0 ? "check added" : after.check === void 0 ? "check removed" : "check changed");
|
|
65
|
+
if (!same(before.approval, after.approval)) out.push(before.approval === void 0 ? "approval added" : after.approval === void 0 ? "approval removed" : "approval changed");
|
|
66
|
+
if (!same(before.fields, after.fields)) out.push(fieldsChange(before.fields, after.fields));
|
|
67
|
+
if (!same(before.validity, after.validity)) out.push(validityChange(before.validity, after.validity));
|
|
68
|
+
if (!same(before.limit, after.limit)) out.push(before.limit === void 0 ? "limit added" : after.limit === void 0 ? "limit removed" : "limit changed");
|
|
69
|
+
if (!same(before.purpose, after.purpose)) out.push("purpose changed");
|
|
70
|
+
if (before.name !== after.name) out.push("name changed");
|
|
71
|
+
if (before.portable !== after.portable) out.push(after.portable === false ? "portable: false" : "portable");
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
function fieldsChange(before, after) {
|
|
75
|
+
if (before === void 0) return "fields narrowed";
|
|
76
|
+
if (after === void 0) return "fields widened";
|
|
77
|
+
const kept = new Set(after);
|
|
78
|
+
return before.every((field) => kept.has(field)) ? "fields widened" : "fields narrowed";
|
|
79
|
+
}
|
|
80
|
+
const validFrom = (value) => value?.from ?? Number.NEGATIVE_INFINITY;
|
|
81
|
+
const validUntil = (value) => value?.until ?? Number.POSITIVE_INFINITY;
|
|
82
|
+
/** Narrowed when the window starts later or ends earlier than before; a new window on an open grant narrows it. */
|
|
83
|
+
function validityChange(before, after) {
|
|
84
|
+
return validFrom(after) > validFrom(before) || validUntil(after) < validUntil(before) ? "validity narrowed" : "validity widened";
|
|
85
|
+
}
|
|
86
|
+
const NARROWING = /* @__PURE__ */ new Set([
|
|
87
|
+
"where added",
|
|
88
|
+
"where changed",
|
|
89
|
+
"check added",
|
|
90
|
+
"check changed",
|
|
91
|
+
"approval added",
|
|
92
|
+
"approval changed",
|
|
93
|
+
"fields narrowed",
|
|
94
|
+
"validity narrowed",
|
|
95
|
+
"limit added",
|
|
96
|
+
"limit changed",
|
|
97
|
+
"purpose changed",
|
|
98
|
+
"portable: false"
|
|
99
|
+
]);
|
|
100
|
+
function describeGrant(grant) {
|
|
101
|
+
const role = grant.role === null ? "top-level" : `role ${grant.role}`;
|
|
102
|
+
const scope = grant.scope === "global" ? "" : typeof grant.scope === "string" ? ` in ${grant.scope}` : ` on ${grant.scope.resource}`;
|
|
103
|
+
return `${grant.effect} ${grant.permission} (${role}${scope})`;
|
|
104
|
+
}
|
|
105
|
+
function delegationIdentity(delegation) {
|
|
106
|
+
return canonicalJson({
|
|
107
|
+
from: delegation.from,
|
|
108
|
+
to: delegation.to
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function describeDelegation(delegation) {
|
|
112
|
+
const to = delegation.to.id === void 0 ? delegation.to.kind : `${delegation.to.kind} ${delegation.to.id}`;
|
|
113
|
+
return `delegation ${canonicalJson(delegation.from)} → ${to} (${delegation.permissions.join(", ")})`;
|
|
114
|
+
}
|
|
115
|
+
function delegationChanges(before, after) {
|
|
116
|
+
const changes = [];
|
|
117
|
+
const lost = before.permissions.filter((key) => !after.permissions.includes(key));
|
|
118
|
+
const gained = after.permissions.filter((key) => !before.permissions.includes(key));
|
|
119
|
+
if (lost.length > 0) changes.push(`permissions removed: ${lost.join(", ")}`);
|
|
120
|
+
if (gained.length > 0) changes.push(`permissions added: ${gained.join(", ")}`);
|
|
121
|
+
if (!same(before.validity, after.validity)) changes.push(validityChange(before.validity, after.validity));
|
|
122
|
+
return changes;
|
|
123
|
+
}
|
|
124
|
+
function diffDelegations(a, b, breaking) {
|
|
125
|
+
const byIdentityA = new Map(a.map((item) => [delegationIdentity(item), item]));
|
|
126
|
+
const byIdentityB = new Map(b.map((item) => [delegationIdentity(item), item]));
|
|
127
|
+
const added = [];
|
|
128
|
+
const removed = [];
|
|
129
|
+
const changed = [];
|
|
130
|
+
for (const [identity, before] of byIdentityA) {
|
|
131
|
+
const after = byIdentityB.get(identity);
|
|
132
|
+
if (after === void 0) {
|
|
133
|
+
removed.push(before);
|
|
134
|
+
breaking.push({
|
|
135
|
+
kind: "delegation-removed",
|
|
136
|
+
detail: `${describeDelegation(before)} removed`
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const changes = delegationChanges(before, after);
|
|
141
|
+
if (changes.length === 0) continue;
|
|
142
|
+
changed.push({
|
|
143
|
+
before,
|
|
144
|
+
after,
|
|
145
|
+
changes
|
|
146
|
+
});
|
|
147
|
+
const narrowing = changes.filter((item) => item.startsWith("permissions removed") || item === "validity narrowed");
|
|
148
|
+
if (narrowing.length > 0) breaking.push({
|
|
149
|
+
kind: "delegation-narrowed",
|
|
150
|
+
detail: `${describeDelegation(after)}: ${narrowing.join(", ")}`
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
for (const [identity, after] of byIdentityB) if (!byIdentityA.has(identity)) added.push(after);
|
|
154
|
+
return {
|
|
155
|
+
added,
|
|
156
|
+
removed,
|
|
157
|
+
changed
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function roleEntries(catalog) {
|
|
161
|
+
return new Map((catalog.roles ?? []).map((role) => [role.key, canonicalJson(role)]));
|
|
162
|
+
}
|
|
163
|
+
function sideRef(side) {
|
|
164
|
+
return side.catalog.fingerprint === void 0 ? { source: side.source } : {
|
|
165
|
+
source: side.source,
|
|
166
|
+
fingerprint: side.catalog.fingerprint
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Former key to current key, from each permission's `renamedFrom`. */
|
|
170
|
+
function aliasesOf(catalog) {
|
|
171
|
+
return new Map(catalog.permissions.flatMap((item) => (item.renamedFrom ?? []).map((old) => [old, item.key])));
|
|
172
|
+
}
|
|
173
|
+
/** `a` with every key `b` renamed written as its new key, so grants compare by the current name. */
|
|
174
|
+
function underNames(a, aliases) {
|
|
175
|
+
if (a.catalog.grants === void 0 || aliases.size === 0) return a;
|
|
176
|
+
return {
|
|
177
|
+
...a,
|
|
178
|
+
catalog: {
|
|
179
|
+
...a.catalog,
|
|
180
|
+
grants: a.catalog.grants.map((grant) => {
|
|
181
|
+
const to = aliases.get(grant.permission);
|
|
182
|
+
return to === void 0 ? grant : {
|
|
183
|
+
...grant,
|
|
184
|
+
permission: to
|
|
185
|
+
};
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function diffCatalogs(original, b) {
|
|
191
|
+
const breaking = [];
|
|
192
|
+
const aliasesA = aliasesOf(original.catalog);
|
|
193
|
+
const aliasesB = aliasesOf(b.catalog);
|
|
194
|
+
const keysB = new Set(b.catalog.permissions.map((item) => item.key));
|
|
195
|
+
const a = underNames(original, aliasesB);
|
|
196
|
+
const keyDiff = setDiff(a.catalog.permissions.map((item) => item.key), b.catalog.permissions.map((item) => item.key));
|
|
197
|
+
const renamed = [];
|
|
198
|
+
const removedKeys = [];
|
|
199
|
+
for (const key of keyDiff.removed) {
|
|
200
|
+
const to = aliasesB.get(key);
|
|
201
|
+
if (to === void 0) removedKeys.push(key);
|
|
202
|
+
else renamed.push({
|
|
203
|
+
from: key,
|
|
204
|
+
to
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const permissions = {
|
|
208
|
+
added: keyDiff.added,
|
|
209
|
+
removed: removedKeys,
|
|
210
|
+
renamed
|
|
211
|
+
};
|
|
212
|
+
for (const key of permissions.removed) breaking.push({
|
|
213
|
+
kind: "permission-removed",
|
|
214
|
+
permission: key,
|
|
215
|
+
detail: `${key} no longer exists`
|
|
216
|
+
});
|
|
217
|
+
for (const [old, to] of aliasesA) if (!aliasesB.has(old) && !keysB.has(old)) breaking.push({
|
|
218
|
+
kind: "alias-removed",
|
|
219
|
+
permission: to,
|
|
220
|
+
detail: `${old} no longer resolves to ${to}: stored custom roles, tokens and SQL that still name it now deny`
|
|
221
|
+
});
|
|
222
|
+
const scopes = setDiff((a.catalog.scopes ?? []).map((scope) => scope.name), (b.catalog.scopes ?? []).map((scope) => scope.name));
|
|
223
|
+
for (const name of scopes.removed) breaking.push({
|
|
224
|
+
kind: "scope-removed",
|
|
225
|
+
detail: `scope ${name} no longer exists`
|
|
226
|
+
});
|
|
227
|
+
const rolesA = roleEntries(a.catalog);
|
|
228
|
+
const rolesB = roleEntries(b.catalog);
|
|
229
|
+
const roleNames = setDiff([...rolesA.keys()], [...rolesB.keys()]);
|
|
230
|
+
const changedRoles = [...rolesA.keys()].filter((key) => rolesB.has(key) && rolesB.get(key) !== rolesA.get(key)).toSorted();
|
|
231
|
+
for (const name of roleNames.removed) breaking.push({
|
|
232
|
+
kind: "role-removed",
|
|
233
|
+
role: name,
|
|
234
|
+
detail: `role ${name} no longer exists`
|
|
235
|
+
});
|
|
236
|
+
const removedPermissions = new Set(permissions.removed);
|
|
237
|
+
const removedRoles = new Set(roleNames.removed);
|
|
238
|
+
let grants;
|
|
239
|
+
if (a.catalog.grants !== void 0 && b.catalog.grants !== void 0) {
|
|
240
|
+
const byIdentityA = new Map(a.catalog.grants.map((grant) => [grantIdentity(grant), grant]));
|
|
241
|
+
const byIdentityB = new Map(b.catalog.grants.map((grant) => [grantIdentity(grant), grant]));
|
|
242
|
+
const added = [];
|
|
243
|
+
const removed = [];
|
|
244
|
+
const changed = [];
|
|
245
|
+
for (const [identity, before] of byIdentityA) {
|
|
246
|
+
const after = byIdentityB.get(identity);
|
|
247
|
+
if (after === void 0) {
|
|
248
|
+
removed.push(before);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const changes = grantChanges(before, after);
|
|
252
|
+
if (changes.length > 0) changed.push({
|
|
253
|
+
before,
|
|
254
|
+
after,
|
|
255
|
+
changes
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
for (const [identity, after] of byIdentityB) if (!byIdentityA.has(identity)) added.push(after);
|
|
259
|
+
for (const grant of removed) {
|
|
260
|
+
if (grant.effect !== "allow") continue;
|
|
261
|
+
if (removedPermissions.has(grant.permission) || grant.role !== null && removedRoles.has(grant.role)) continue;
|
|
262
|
+
breaking.push({
|
|
263
|
+
kind: "allow-removed",
|
|
264
|
+
permission: grant.permission,
|
|
265
|
+
role: grant.role,
|
|
266
|
+
detail: `${describeGrant(grant)} removed`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
for (const grant of added) if (grant.effect === "deny") breaking.push({
|
|
270
|
+
kind: "deny-added",
|
|
271
|
+
permission: grant.permission,
|
|
272
|
+
role: grant.role,
|
|
273
|
+
detail: `${describeGrant(grant)} added`
|
|
274
|
+
});
|
|
275
|
+
for (const change of changed) {
|
|
276
|
+
if (change.after.effect === "deny") {
|
|
277
|
+
breaking.push({
|
|
278
|
+
kind: "deny-changed",
|
|
279
|
+
permission: change.after.permission,
|
|
280
|
+
role: change.after.role,
|
|
281
|
+
detail: `${describeGrant(change.after)}: ${change.changes.join(", ")}`
|
|
282
|
+
});
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const narrowing = change.changes.filter((item) => NARROWING.has(item));
|
|
286
|
+
if (narrowing.length > 0) breaking.push({
|
|
287
|
+
kind: "allow-narrowed",
|
|
288
|
+
permission: change.after.permission,
|
|
289
|
+
role: change.after.role,
|
|
290
|
+
detail: `${describeGrant(change.after)}: ${narrowing.join(", ")}`
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
grants = {
|
|
294
|
+
added,
|
|
295
|
+
removed,
|
|
296
|
+
changed
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const delegations = grants === void 0 ? void 0 : diffDelegations(a.catalog.delegations ?? [], b.catalog.delegations ?? [], breaking);
|
|
300
|
+
return {
|
|
301
|
+
a: sideRef(a),
|
|
302
|
+
b: sideRef(b),
|
|
303
|
+
permissions,
|
|
304
|
+
scopes,
|
|
305
|
+
roles: {
|
|
306
|
+
...roleNames,
|
|
307
|
+
changed: changedRoles
|
|
308
|
+
},
|
|
309
|
+
...grants === void 0 ? {} : { grants },
|
|
310
|
+
...delegations === void 0 ? {} : { delegations },
|
|
311
|
+
breaking
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
async function impactOf(input) {
|
|
315
|
+
const { fixtures, customRoles } = await loadFixtures(input.cwd, input.fixtures);
|
|
316
|
+
const now = Math.floor(input.now.getTime() / 1e3);
|
|
317
|
+
const outcome = async (policy, fixture) => {
|
|
318
|
+
const permission = findPermission(policy.permissions, fixture.action);
|
|
319
|
+
if (permission === void 0) return "unknown";
|
|
320
|
+
const [decision] = (await createPermDock(policy, fixtureSubject(fixture.subject), { customRoles: memoryRoleSource(customRoles) })).simulate([[permission, fixtureRow(fixture, permission.kind)]], { now });
|
|
321
|
+
return decision?.outcome ?? "denied";
|
|
322
|
+
};
|
|
323
|
+
const rows = [];
|
|
324
|
+
const breaking = [];
|
|
325
|
+
for (const fixture of fixtures) {
|
|
326
|
+
const [before, after] = await Promise.all([outcome(input.a, fixture), outcome(input.b, fixture)]);
|
|
327
|
+
if (before === after) continue;
|
|
328
|
+
rows.push({
|
|
329
|
+
action: fixture.action,
|
|
330
|
+
subject: fixture.subject.id,
|
|
331
|
+
...fixture.subject.tenant === void 0 ? {} : { tenant: fixture.subject.tenant },
|
|
332
|
+
before,
|
|
333
|
+
after
|
|
334
|
+
});
|
|
335
|
+
if (before === "granted" && after !== "granted") breaking.push({
|
|
336
|
+
kind: "access-lost",
|
|
337
|
+
permission: fixture.action,
|
|
338
|
+
detail: `${fixture.subject.id} loses ${fixture.action}: ${before} → ${after}`
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
rows,
|
|
343
|
+
breaking
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function formatText(diff) {
|
|
347
|
+
const lines = [];
|
|
348
|
+
const section = (title, added, removed, changed = []) => {
|
|
349
|
+
if (added.length === 0 && removed.length === 0 && changed.length === 0) return;
|
|
350
|
+
lines.push(title);
|
|
351
|
+
for (const item of added) lines.push(` + ${item}`);
|
|
352
|
+
for (const item of removed) lines.push(` - ${item}`);
|
|
353
|
+
for (const item of changed) lines.push(` ~ ${item}`);
|
|
354
|
+
};
|
|
355
|
+
section("permissions", diff.permissions.added, diff.permissions.removed, diff.permissions.renamed.map((rename) => `${rename.from} → ${rename.to} (renamed)`));
|
|
356
|
+
section("scopes", diff.scopes.added, diff.scopes.removed);
|
|
357
|
+
section("roles", diff.roles.added, diff.roles.removed, diff.roles.changed.map((name) => `${name}: declaration changed`));
|
|
358
|
+
if (diff.grants === void 0) lines.push("grants: not compared (a catalog was built without its policy; run `permdock collect` with `policy` configured)");
|
|
359
|
+
else section("grants", diff.grants.added.map(describeGrant), diff.grants.removed.map(describeGrant), diff.grants.changed.map((change) => `${describeGrant(change.after)}: ${change.changes.join(", ")}`));
|
|
360
|
+
if (diff.delegations !== void 0) section("delegations", diff.delegations.added.map(describeDelegation), diff.delegations.removed.map(describeDelegation), diff.delegations.changed.map((change) => `${describeDelegation(change.after)}: ${change.changes.join(", ")}`));
|
|
361
|
+
if (diff.impact !== void 0) {
|
|
362
|
+
lines.push(diff.impact.length === 0 ? "impact: no fixture changes outcome" : `impact (${String(diff.impact.length)} fixture(s) change outcome)`);
|
|
363
|
+
for (const row of diff.impact) {
|
|
364
|
+
const where = row.tenant === void 0 ? "" : ` in ${row.tenant}`;
|
|
365
|
+
lines.push(` ${row.subject}${where} ${row.action}: ${row.before} → ${row.after}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (lines.length === 0) lines.push("no changes");
|
|
369
|
+
if (diff.breaking.length > 0) {
|
|
370
|
+
lines.push(`breaking (${String(diff.breaking.length)})`);
|
|
371
|
+
for (const change of diff.breaking) lines.push(` ${change.kind}: ${change.detail}`);
|
|
372
|
+
}
|
|
373
|
+
return `${lines.join("\n")}\n`;
|
|
374
|
+
}
|
|
375
|
+
async function runDiff(input) {
|
|
376
|
+
const [sourceA, sourceB, ...extra] = input.sources;
|
|
377
|
+
if (sourceA === void 0 || sourceB === void 0 || extra.length > 0) return {
|
|
378
|
+
code: 2,
|
|
379
|
+
output: "PermDock CLI: diff takes two inputs, each a permissions.catalog.json or a module exporting policy"
|
|
380
|
+
};
|
|
381
|
+
let a;
|
|
382
|
+
let b;
|
|
383
|
+
try {
|
|
384
|
+
[a, b] = await Promise.all([loadSide(input.cwd, sourceA, input.now), loadSide(input.cwd, sourceB, input.now)]);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
return usageResult(error);
|
|
387
|
+
}
|
|
388
|
+
let diff = diffCatalogs(a, b);
|
|
389
|
+
if (input.impact) {
|
|
390
|
+
if (a.policy === void 0 || b.policy === void 0) return {
|
|
391
|
+
code: 2,
|
|
392
|
+
output: "PermDock CLI: diff --impact needs two policy modules; a catalog file cannot be evaluated"
|
|
393
|
+
};
|
|
394
|
+
const fixtures = input.fixtures ?? input.config.rls?.fixtures ?? "rls.fixtures.json";
|
|
395
|
+
try {
|
|
396
|
+
const impact = await impactOf({
|
|
397
|
+
cwd: input.cwd,
|
|
398
|
+
a: a.policy,
|
|
399
|
+
b: b.policy,
|
|
400
|
+
fixtures,
|
|
401
|
+
now: input.now
|
|
402
|
+
});
|
|
403
|
+
diff = {
|
|
404
|
+
...diff,
|
|
405
|
+
impact: impact.rows,
|
|
406
|
+
breaking: [...diff.breaking, ...impact.breaking]
|
|
407
|
+
};
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return usageResult(error);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
code: diff.breaking.length > 0 ? 1 : 0,
|
|
414
|
+
output: input.json ? `${JSON.stringify(diff, null, 2)}\n` : formatText(diff)
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
418
|
+
export { runDiff };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/core/digest.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* What a PermDock error's `digest` says. React keeps `digest` when an error
|
|
4
|
+
* crosses from a Server Component to the client, where production builds
|
|
5
|
+
* redact the message, so a client error boundary can still tell a denial or
|
|
6
|
+
* an approval request from any other failure.
|
|
7
|
+
*/
|
|
8
|
+
type PermDockDigest = {
|
|
9
|
+
readonly outcome: "denied";
|
|
10
|
+
readonly permission: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly outcome: "approval-required";
|
|
13
|
+
readonly permission: string;
|
|
14
|
+
readonly token: string;
|
|
15
|
+
};
|
|
16
|
+
/** Reads `error.digest`; anything that is not a PermDock digest is `null`. */
|
|
17
|
+
declare function parsePermDockDigest(digest: unknown): PermDockDigest | null;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { parsePermDockDigest as n, PermDockDigest as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/core/digest.ts
|
|
2
|
+
const DENIED = "PERMDOCK_DENIED";
|
|
3
|
+
const APPROVAL_REQUIRED = "PERMDOCK_APPROVAL_REQUIRED";
|
|
4
|
+
function deniedDigest(permission) {
|
|
5
|
+
return `${DENIED};${permission}`;
|
|
6
|
+
}
|
|
7
|
+
function approvalDigest(permission, token) {
|
|
8
|
+
return `${APPROVAL_REQUIRED};${permission};${token}`;
|
|
9
|
+
}
|
|
10
|
+
/** Reads `error.digest`; anything that is not a PermDock digest is `null`. */
|
|
11
|
+
function parsePermDockDigest(digest) {
|
|
12
|
+
if (typeof digest !== "string") return null;
|
|
13
|
+
const [kind, permission, token, ...rest] = digest.split(";");
|
|
14
|
+
if (rest.length > 0 || permission === void 0 || permission === "") return null;
|
|
15
|
+
if (kind === DENIED && token === void 0) return {
|
|
16
|
+
outcome: "denied",
|
|
17
|
+
permission
|
|
18
|
+
};
|
|
19
|
+
if (kind === APPROVAL_REQUIRED && token !== void 0 && token !== "") return {
|
|
20
|
+
outcome: "approval-required",
|
|
21
|
+
permission,
|
|
22
|
+
token
|
|
23
|
+
};
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { deniedDigest as n, parsePermDockDigest as r, approvalDigest as t };
|