@aventara/access 0.0.0-stage → 1.0.0-rc.1

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.
Files changed (248) hide show
  1. package/CHANGELOG.md +877 -0
  2. package/LICENSE +91 -0
  3. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  4. package/README.md +2536 -2
  5. package/dist/access/access-options.d.ts +74 -0
  6. package/dist/access/access-options.js +5 -0
  7. package/dist/access/access.error.d.ts +35 -0
  8. package/dist/access/access.error.js +11 -0
  9. package/dist/access/access.factory.d.ts +275 -0
  10. package/dist/access/access.factory.js +440 -0
  11. package/dist/access/better-auth.hardening.d.ts +120 -0
  12. package/dist/access/better-auth.hardening.js +420 -0
  13. package/dist/access/better-auth.loader.d.ts +67 -0
  14. package/dist/access/better-auth.loader.js +65 -0
  15. package/dist/access/better-auth.range.d.ts +9 -0
  16. package/dist/access/better-auth.range.js +28 -0
  17. package/dist/access/better-auth.require.d.ts +90 -0
  18. package/dist/access/better-auth.require.js +6 -0
  19. package/dist/access/better-auth.require.node.d.ts +7 -0
  20. package/dist/access/better-auth.require.node.js +52 -0
  21. package/dist/access/claims.mapper.d.ts +79 -0
  22. package/dist/access/claims.mapper.js +152 -0
  23. package/dist/access/key-set.cache.d.ts +10 -0
  24. package/dist/access/key-set.cache.js +63 -0
  25. package/dist/access/policies-block.checker.d.ts +64 -0
  26. package/dist/access/policies-block.checker.js +138 -0
  27. package/dist/access/principal.subject.d.ts +22 -0
  28. package/dist/access/principal.subject.js +89 -0
  29. package/dist/access/profile.policy.d.ts +74 -0
  30. package/dist/access/profile.policy.js +110 -0
  31. package/dist/access/request.state.d.ts +89 -0
  32. package/dist/access/request.state.js +266 -0
  33. package/dist/access/session.hooks.d.ts +114 -0
  34. package/dist/access/session.hooks.js +258 -0
  35. package/dist/access/token.verifier.d.ts +35 -0
  36. package/dist/access/token.verifier.js +69 -0
  37. package/dist/access-schema.d.ts +48 -0
  38. package/dist/access-schema.js +5 -0
  39. package/dist/access-warning.emitter.d.ts +4 -0
  40. package/dist/access-warning.emitter.js +24 -0
  41. package/dist/access.builder.d.ts +340 -0
  42. package/dist/access.builder.js +75 -0
  43. package/dist/access.engine.d.ts +155 -0
  44. package/dist/access.engine.js +477 -0
  45. package/dist/audit/audit-event.d.ts +135 -0
  46. package/dist/audit/audit-event.js +8 -0
  47. package/dist/audit/audit.log.d.ts +122 -0
  48. package/dist/audit/audit.log.js +345 -0
  49. package/dist/audit/audit.schema.d.ts +143 -0
  50. package/dist/audit/audit.schema.js +50 -0
  51. package/dist/auth/index.d.ts +12 -0
  52. package/dist/auth/index.js +2 -0
  53. package/dist/aventara-where.translator.d.ts +19 -0
  54. package/dist/aventara-where.translator.js +87 -0
  55. package/dist/beacon/beacon.check.d.ts +59 -0
  56. package/dist/beacon/beacon.check.js +128 -0
  57. package/dist/beacon/beacon.schema.d.ts +41 -0
  58. package/dist/beacon/beacon.schema.js +48 -0
  59. package/dist/beacon/beacon.store.d.ts +29 -0
  60. package/dist/beacon/beacon.store.js +78 -0
  61. package/dist/better-auth/access.client-plugin.d.ts +75 -0
  62. package/dist/better-auth/access.client-plugin.js +133 -0
  63. package/dist/better-auth/access.plugin.d.ts +164 -0
  64. package/dist/better-auth/access.plugin.js +348 -0
  65. package/dist/better-auth/access.provider.d.ts +80 -0
  66. package/dist/better-auth/access.provider.js +100 -0
  67. package/dist/better-auth/index.d.ts +7 -0
  68. package/dist/better-auth/index.js +4 -0
  69. package/dist/better-auth/organization.bridge.d.ts +66 -0
  70. package/dist/better-auth/organization.bridge.js +144 -0
  71. package/dist/canonical-json.writer.d.ts +12 -0
  72. package/dist/canonical-json.writer.js +30 -0
  73. package/dist/cli/avaccess.bin.d.ts +2 -0
  74. package/dist/cli/avaccess.bin.js +26 -0
  75. package/dist/cli/avaccess.program.d.ts +15 -0
  76. package/dist/cli/avaccess.program.js +166 -0
  77. package/dist/cli/beacon-pins.file.d.ts +14 -0
  78. package/dist/cli/beacon-pins.file.js +62 -0
  79. package/dist/cli/beacon.loader.d.ts +20 -0
  80. package/dist/cli/beacon.loader.js +71 -0
  81. package/dist/cli/cli.context.d.ts +49 -0
  82. package/dist/cli/cli.context.js +66 -0
  83. package/dist/cli/diff.command.d.ts +2 -0
  84. package/dist/cli/diff.command.js +66 -0
  85. package/dist/cli/generate.command.d.ts +2 -0
  86. package/dist/cli/generate.command.js +82 -0
  87. package/dist/cli/keys.command.d.ts +2 -0
  88. package/dist/cli/keys.command.js +96 -0
  89. package/dist/cli/lock.file.d.ts +31 -0
  90. package/dist/cli/lock.file.js +181 -0
  91. package/dist/cli/module.loader.d.ts +13 -0
  92. package/dist/cli/module.loader.js +63 -0
  93. package/dist/cli/policies.diff.d.ts +16 -0
  94. package/dist/cli/policies.diff.js +101 -0
  95. package/dist/cli/pull.command.d.ts +2 -0
  96. package/dist/cli/pull.command.js +234 -0
  97. package/dist/cli/push.command.d.ts +2 -0
  98. package/dist/cli/push.command.js +59 -0
  99. package/dist/cli/registry.client.d.ts +63 -0
  100. package/dist/cli/registry.client.js +287 -0
  101. package/dist/cli/schema.command.d.ts +2 -0
  102. package/dist/cli/schema.command.js +60 -0
  103. package/dist/cli/serve.command.d.ts +6 -0
  104. package/dist/cli/serve.command.js +200 -0
  105. package/dist/cli/status.command.d.ts +2 -0
  106. package/dist/cli/status.command.js +31 -0
  107. package/dist/cli/translator.flag.d.ts +6 -0
  108. package/dist/cli/translator.flag.js +24 -0
  109. package/dist/cli/verify.command.d.ts +2 -0
  110. package/dist/cli/verify.command.js +40 -0
  111. package/dist/client/access-client.factory.d.ts +92 -0
  112. package/dist/client/access-client.factory.js +269 -0
  113. package/dist/client/access-verbs.schema.d.ts +66 -0
  114. package/dist/client/access-verbs.schema.js +159 -0
  115. package/dist/client/verb-rate.limiter.d.ts +116 -0
  116. package/dist/client/verb-rate.limiter.js +358 -0
  117. package/dist/foreign/foreign-facts.d.ts +30 -0
  118. package/dist/foreign/foreign-facts.js +75 -0
  119. package/dist/foreign/foreign.merger.d.ts +10 -0
  120. package/dist/foreign/foreign.merger.js +52 -0
  121. package/dist/foreign-references.check.d.ts +6 -0
  122. package/dist/foreign-references.check.js +131 -0
  123. package/dist/generator/client.emitter.d.ts +28 -0
  124. package/dist/generator/client.emitter.js +411 -0
  125. package/dist/generator/copied-modules.reader.d.ts +14 -0
  126. package/dist/generator/copied-modules.reader.js +80 -0
  127. package/dist/generator/namespace.facts.d.ts +28 -0
  128. package/dist/generator/namespace.facts.js +32 -0
  129. package/dist/generator/orm.translator.d.ts +48 -0
  130. package/dist/generator/orm.translator.js +29 -0
  131. package/dist/generator/prisma7/prisma-schema.reader.d.ts +9 -0
  132. package/dist/generator/prisma7/prisma-schema.reader.js +394 -0
  133. package/dist/generator/prisma7/prisma-where.translator.d.ts +73 -0
  134. package/dist/generator/prisma7/prisma-where.translator.js +138 -0
  135. package/dist/generator/prisma7/prisma7.translator.d.ts +2 -0
  136. package/dist/generator/prisma7/prisma7.translator.js +87 -0
  137. package/dist/generator/schema.emitter.d.ts +14 -0
  138. package/dist/generator/schema.emitter.js +23 -0
  139. package/dist/generator/tsconfig.reader.d.ts +44 -0
  140. package/dist/generator/tsconfig.reader.js +110 -0
  141. package/dist/generator/typescript-literal.writer.d.ts +5 -0
  142. package/dist/generator/typescript-literal.writer.js +42 -0
  143. package/dist/guardrails/guardrails-contract.d.ts +45 -0
  144. package/dist/guardrails/guardrails-contract.js +12 -0
  145. package/dist/guardrails/guardrails.builder.d.ts +48 -0
  146. package/dist/guardrails/guardrails.builder.js +32 -0
  147. package/dist/guardrails/guardrails.compiler.d.ts +10 -0
  148. package/dist/guardrails/guardrails.compiler.js +254 -0
  149. package/dist/guardrails/guardrails.composer.d.ts +7 -0
  150. package/dist/guardrails/guardrails.composer.js +95 -0
  151. package/dist/guardrails/guardrails.hash.d.ts +10 -0
  152. package/dist/guardrails/guardrails.hash.js +38 -0
  153. package/dist/index.d.ts +16 -0
  154. package/dist/index.js +6 -0
  155. package/dist/kiosk/kiosk-verbs.schema.d.ts +19 -0
  156. package/dist/kiosk/kiosk-verbs.schema.js +61 -0
  157. package/dist/kiosk/kiosk.plugin.d.ts +217 -0
  158. package/dist/kiosk/kiosk.plugin.js +877 -0
  159. package/dist/kiosk/kiosk.schema.d.ts +176 -0
  160. package/dist/kiosk/kiosk.schema.js +101 -0
  161. package/dist/kiosk/kiosk.store.d.ts +171 -0
  162. package/dist/kiosk/kiosk.store.js +478 -0
  163. package/dist/kiosk/lan-range.policy.d.ts +31 -0
  164. package/dist/kiosk/lan-range.policy.js +113 -0
  165. package/dist/kiosk/pin.policy.d.ts +9 -0
  166. package/dist/kiosk/pin.policy.js +143 -0
  167. package/dist/kiosk/session-use.marker.d.ts +4 -0
  168. package/dist/kiosk/session-use.marker.js +16 -0
  169. package/dist/lock/lock-contract.d.ts +38 -0
  170. package/dist/lock/lock-contract.js +67 -0
  171. package/dist/lock/lock.verifier.d.ts +27 -0
  172. package/dist/lock/lock.verifier.js +62 -0
  173. package/dist/mcp/agent-scope.gate.d.ts +13 -0
  174. package/dist/mcp/agent-scope.gate.js +75 -0
  175. package/dist/mcp/agent.grant.d.ts +40 -0
  176. package/dist/mcp/agent.grant.js +92 -0
  177. package/dist/mcp/mcp-options.d.ts +60 -0
  178. package/dist/mcp/mcp-options.js +72 -0
  179. package/dist/oauth/access-token.verifier.d.ts +66 -0
  180. package/dist/oauth/access-token.verifier.js +201 -0
  181. package/dist/oauth/oauth-provider.hardening.d.ts +62 -0
  182. package/dist/oauth/oauth-provider.hardening.js +261 -0
  183. package/dist/passwords/argon2id.hasher.d.ts +97 -0
  184. package/dist/passwords/argon2id.hasher.js +256 -0
  185. package/dist/passwords/node-rs-argon2.adapter.d.ts +25 -0
  186. package/dist/passwords/node-rs-argon2.adapter.js +42 -0
  187. package/dist/passwords/passwords.options.d.ts +79 -0
  188. package/dist/passwords/passwords.options.js +212 -0
  189. package/dist/policies-contract.d.ts +219 -0
  190. package/dist/policies-contract.js +8 -0
  191. package/dist/policies.compiler.d.ts +38 -0
  192. package/dist/policies.compiler.js +1433 -0
  193. package/dist/policies.decider.d.ts +103 -0
  194. package/dist/policies.decider.js +390 -0
  195. package/dist/policies.hash.d.ts +10 -0
  196. package/dist/policies.hash.js +54 -0
  197. package/dist/predicate.evaluator.d.ts +62 -0
  198. package/dist/predicate.evaluator.js +378 -0
  199. package/dist/random-secret.generator.d.ts +2 -0
  200. package/dist/random-secret.generator.js +15 -0
  201. package/dist/read-governed-unique.check.d.ts +3 -0
  202. package/dist/read-governed-unique.check.js +21 -0
  203. package/dist/reference-reads.check.d.ts +16 -0
  204. package/dist/reference-reads.check.js +158 -0
  205. package/dist/registry/access.registry.d.ts +173 -0
  206. package/dist/registry/access.registry.js +865 -0
  207. package/dist/registry/beacon.ledger.d.ts +30 -0
  208. package/dist/registry/beacon.ledger.js +93 -0
  209. package/dist/registry/ownership.check.d.ts +54 -0
  210. package/dist/registry/ownership.check.js +221 -0
  211. package/dist/registry/registry.endpoints.d.ts +50 -0
  212. package/dist/registry/registry.endpoints.js +193 -0
  213. package/dist/registry/registry.key.d.ts +7 -0
  214. package/dist/registry/registry.key.js +13 -0
  215. package/dist/registry/registry.options.d.ts +13 -0
  216. package/dist/registry/registry.options.js +28 -0
  217. package/dist/registry/registry.refusal.d.ts +6 -0
  218. package/dist/registry/registry.refusal.js +11 -0
  219. package/dist/registry/registry.schema.d.ts +238 -0
  220. package/dist/registry/registry.schema.js +127 -0
  221. package/dist/resources/auth-tables.catalog.d.ts +96 -0
  222. package/dist/resources/auth-tables.catalog.js +110 -0
  223. package/dist/resources/auth-tables.guard.d.ts +106 -0
  224. package/dist/resources/auth-tables.guard.js +462 -0
  225. package/dist/scope.resolver.d.ts +144 -0
  226. package/dist/scope.resolver.js +529 -0
  227. package/dist/sha256.digest.d.ts +2 -0
  228. package/dist/sha256.digest.js +81 -0
  229. package/dist/standalone/access.server.d.ts +41 -0
  230. package/dist/standalone/access.server.js +301 -0
  231. package/dist/standalone/index.d.ts +1 -0
  232. package/dist/standalone/index.js +1 -0
  233. package/package.json +102 -3
  234. package/skill/SKILL.md +170 -0
  235. package/skill/reference/callers.md +99 -0
  236. package/skill/reference/errors.md +89 -0
  237. package/skill/reference/recipes.md +281 -0
  238. package/skill/reference/rules.md +153 -0
  239. package/skill/reference/setup.md +334 -0
  240. package/src/aventara-where.translator.ts +116 -0
  241. package/src/beacon/beacon.check.ts +204 -0
  242. package/src/canonical-json.writer.ts +51 -0
  243. package/src/generator/prisma7/prisma-where.translator.ts +243 -0
  244. package/src/policies-contract.ts +215 -0
  245. package/src/policies.decider.ts +662 -0
  246. package/src/predicate.evaluator.ts +574 -0
  247. package/src/scope.resolver.ts +819 -0
  248. package/src/sha256.digest.ts +85 -0
@@ -0,0 +1,101 @@
1
+ // What changed between two revisions of a namespace's policies file, by name: each
2
+ // grant and deny by its `id`, each role by its name, each governed Resource, each
3
+ // schema entry and each claim. Both files are compared in their canonical form
4
+ // (`policies.hash.ts`), so a reordering is no change, and a changed entry lists the
5
+ // members that differ.
6
+ import { canonicalJson } from "../canonical-json.writer.js";
7
+ import { canonicalPolicies } from "../policies.hash.js";
8
+ /** A file's members in canonical form. */
9
+ const canonicalOf = (file) => JSON.parse(canonicalPolicies(file));
10
+ const text = (value) => value === undefined ? "" : canonicalJson(value);
11
+ /** The members of two entries that differ (none when either is not an object). */
12
+ const differingMembers = (before, after) => {
13
+ if (typeof before !== "object" ||
14
+ typeof after !== "object" ||
15
+ before === null ||
16
+ after === null ||
17
+ Array.isArray(before) ||
18
+ Array.isArray(after)) {
19
+ return [];
20
+ }
21
+ const a = before;
22
+ const b = after;
23
+ return [...new Set([...Object.keys(a), ...Object.keys(b)])]
24
+ .filter((key) => text(a[key]) !== text(b[key]))
25
+ .sort();
26
+ };
27
+ const compareEntries = (part, before, after) => {
28
+ const changes = [];
29
+ for (const name of [...new Set([...before.keys(), ...after.keys()])].sort()) {
30
+ const a = before.get(name);
31
+ const b = after.get(name);
32
+ if (a === undefined) {
33
+ changes.push({ part, name, change: "added" });
34
+ }
35
+ else if (b === undefined) {
36
+ changes.push({ part, name, change: "removed" });
37
+ }
38
+ else if (text(a) !== text(b)) {
39
+ const members = differingMembers(a, b);
40
+ changes.push({
41
+ part,
42
+ name,
43
+ change: "changed",
44
+ ...(members.length > 0 ? { members } : {}),
45
+ });
46
+ }
47
+ }
48
+ return changes;
49
+ };
50
+ const byKey = (record) => new Map(Object.entries((record ?? {})).filter(([, value]) => value !== undefined));
51
+ const byId = (rules) => new Map((rules ?? []).map((rule) => [
52
+ String(rule.id),
53
+ rule,
54
+ ]));
55
+ /**
56
+ * Each governed Resource as its fields' modes (`"both"` for a field `governModes`
57
+ * gives none), so a change of mode is named as the field it changes.
58
+ */
59
+ const governOf = (file) => {
60
+ const govern = (file.govern ?? {});
61
+ const modes = (file.governModes ?? {});
62
+ return new Map(Object.entries(govern).map(([resource, fields]) => [
63
+ resource,
64
+ Object.fromEntries(fields.map((field) => [
65
+ field,
66
+ (Object.hasOwn(modes, resource) &&
67
+ Object.hasOwn(modes[resource] ?? {}, field)
68
+ ? modes[resource]?.[field]
69
+ : undefined) ?? "both",
70
+ ])),
71
+ ]));
72
+ };
73
+ /** The named changes from `before` to `after`, in the order of the file's parts. */
74
+ export const diffPolicies = (before, after) => {
75
+ const a = canonicalOf(before);
76
+ const b = canonicalOf(after);
77
+ const namespace = text(a.namespace) === text(b.namespace)
78
+ ? []
79
+ : [
80
+ {
81
+ part: "namespace",
82
+ name: String(b.namespace ?? a.namespace),
83
+ change: a.namespace === undefined
84
+ ? "added"
85
+ : b.namespace === undefined
86
+ ? "removed"
87
+ : "changed",
88
+ },
89
+ ];
90
+ return [
91
+ ...namespace,
92
+ ...compareEntries("claim", byKey(a.claims), byKey(b.claims)),
93
+ ...compareEntries("schema", byKey(a.schema), byKey(b.schema)),
94
+ ...compareEntries("role", byKey(a.roles), byKey(b.roles)),
95
+ ...compareEntries("govern", governOf(a), governOf(b)),
96
+ ...compareEntries("grant", byId(a.grants), byId(b.grants)),
97
+ ...compareEntries("deny", byId(a.denies), byId(b.denies)),
98
+ ];
99
+ };
100
+ /** A change as one line: `grant "Note/read/player#1": changed (where)`. */
101
+ export const changeLine = (change) => `${change.part} ${JSON.stringify(change.name)}: ${change.change}${change.members === undefined ? "" : ` (${change.members.join(", ")})`}`;
@@ -0,0 +1,2 @@
1
+ import { type CliContext, type ExitCode } from "./cli.context.js";
2
+ export declare const pullCommand: (context: CliContext) => Promise<ExitCode>;
@@ -0,0 +1,234 @@
1
+ // `avaccess pull [--namespace <namespace>] [--out <dir>]`: the namespace's current
2
+ // policies file and the org's guardrails file, from the registry into the project,
3
+ // and both hashes pinned in `avAccess.lock.json`. The only step that brings policies
4
+ // over the network; commit what it writes. The namespace is `--namespace`, else the
5
+ // lock's, else the one the key belongs to (the registry names it); the files go to `--out` (default: where the lock has them, else `access/`)
6
+ // as `<namespace>.policies.json` and `guardrails.json`.
7
+ //
8
+ // A pull never lowers what the lock holds the project to: when the lock pins the
9
+ // org's guardrails and the registry serves none, it is refused and nothing is
10
+ // written. The file the registry serves is held to its hash and to the namespace
11
+ // asked for (`registry.client.ts`). A signed revision, which would also prove who
12
+ // wrote the file, is not built yet.
13
+ //
14
+ // Foreign facts: `--foreign <owner>:<Resource>[,<Resource>…]` (repeatable) brings the
15
+ // owner's schema entries of those Resources into `foreign.json` beside the policies
16
+ // file, marked as the owner's, so `avaccess schema` adds them to the rules' types;
17
+ // a later pull keeps the ones pulled before. For every namespace the pulled file or
18
+ // the foreign facts read, the pull reads that namespace's beacon through the
19
+ // service's own connection (`--beacon <module>`, required then) and holds it to the
20
+ // hash the namespace pushed: on another database it is refused
21
+ // (`ACCESS_FOREIGN_CROSS_DATABASE`) and nothing is written. The lock pins the foreign
22
+ // facts; each owner's beacon hash goes to the selected environment's
23
+ // `avAccess.beacons.<environment>.json` (`--env`, else `AVACCESS_ENV`), which the
24
+ // engine holds the owner's row to at startup (`checkBeacons`). The lock holds no
25
+ // beacon, so it stays the same against any environment's registry.
26
+ import { access } from "node:fs/promises";
27
+ import { resolve } from "node:path";
28
+ import { BEACON_PINS_FORMAT, } from "../beacon/beacon.check.js";
29
+ import { canonicalForeignFacts, FOREIGN_FILE, FOREIGN_FORMAT, foreignFactsHash, } from "../foreign/foreign-facts.js";
30
+ import { canonicalGuardrails } from "../guardrails/guardrails.hash.js";
31
+ import { LOCK_FILE, LOCK_FORMAT, } from "../lock/lock-contract.js";
32
+ import { isName, isNamespace } from "../policies.compiler.js";
33
+ import { canonicalPolicies } from "../policies.hash.js";
34
+ import { foreignNamespaces } from "../registry/ownership.check.js";
35
+ import { beaconStoreOf, holdOwnerBeacon } from "./beacon.loader.js";
36
+ import { beaconPinsFile, ENV_VARIABLE, environmentOf, } from "./beacon-pins.file.js";
37
+ import { CliFailure, EXIT_ERROR, EXIT_FAILED, EXIT_OK, positionals, stringFlag, } from "./cli.context.js";
38
+ import { jsonText, readForeignPinned, readLockIfAny, writeProjectFile, } from "./lock.file.js";
39
+ import { registryClient } from "./registry.client.js";
40
+ /** Where pulled files go when neither `--out` nor the lock says. */
41
+ const DEFAULT_OUT = "access";
42
+ /** A directory relative to the project, `/`-separated, inside it. */
43
+ const projectDirectory = (out) => {
44
+ const directory = out
45
+ .replace(/\\/g, "/")
46
+ .replace(/^\.\/+/, "")
47
+ .replace(/\/+$/, "");
48
+ if (directory === "" ||
49
+ directory.startsWith("/") ||
50
+ /^[A-Za-z]:/.test(directory) ||
51
+ directory.split("/").includes("..")) {
52
+ throw new CliFailure(EXIT_ERROR, `--out is a directory inside the project, such as "access"; it was ${JSON.stringify(out)}`);
53
+ }
54
+ return directory;
55
+ };
56
+ const directoryOf = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : ".";
57
+ /** The Resources `--foreign` names, by owner: `<owner>:<Resource>[,<Resource>…]`, each value. */
58
+ const foreignFlags = (context) => {
59
+ const given = context.flags.foreign;
60
+ const values = given === undefined
61
+ ? []
62
+ : Array.isArray(given)
63
+ ? given
64
+ : [given];
65
+ const asked = new Map();
66
+ for (const value of values) {
67
+ const text = typeof value === "string" ? value : "";
68
+ const colon = text.indexOf(":");
69
+ const owner = text.slice(0, colon);
70
+ const resources = text.slice(colon + 1).split(",");
71
+ if (colon < 0 ||
72
+ !isNamespace(owner) ||
73
+ resources.some((resource) => !isName(resource))) {
74
+ throw new CliFailure(EXIT_ERROR, `--foreign names a namespace and the Resources the rules read of it, \`<namespace>:<Resource>[,<Resource>…]\` (such as \`campaign:Campaign,Member\`); it was ${JSON.stringify(text)}`);
75
+ }
76
+ const set = asked.get(owner) ?? new Set();
77
+ for (const resource of resources) {
78
+ set.add(resource);
79
+ }
80
+ asked.set(owner, set);
81
+ }
82
+ return asked;
83
+ };
84
+ /**
85
+ * The Resources the foreign facts the lock pins hold, by owner: none without a pin,
86
+ * and none when the file was deleted (how a project stops reading them). A file
87
+ * changed by hand is refused (`ACCESS_LOCK_MISMATCH`).
88
+ */
89
+ const pulledForeign = async (context, lock, namespace) => {
90
+ const held = new Map();
91
+ if (lock?.foreign === undefined || lock.namespace !== namespace) {
92
+ return held;
93
+ }
94
+ try {
95
+ await access(resolve(context.cwd, lock.foreign.path));
96
+ }
97
+ catch {
98
+ return held;
99
+ }
100
+ const facts = await readForeignPinned(context.cwd, lock);
101
+ for (const [resource, entry] of Object.entries(facts?.schema ?? {})) {
102
+ const set = held.get(entry.foreign) ?? new Set();
103
+ set.add(resource);
104
+ held.set(entry.foreign, set);
105
+ }
106
+ return held;
107
+ };
108
+ export const pullCommand = async (context) => {
109
+ positionals(context, "avaccess pull [--namespace <namespace>] [--out <dir>] [--foreign <namespace>:<Resource>,…] [--beacon <module>] [--env <environment>]", 0, 0);
110
+ const lock = await readLockIfAny(context.cwd);
111
+ const registry = registryClient(context);
112
+ // With neither the flag nor a lock, the namespace is the key's own, as the
113
+ // registry names it: a first pull needs only the registry and the key.
114
+ const namespace = stringFlag(context, "namespace") ??
115
+ lock?.namespace ??
116
+ (await registry.keyNamespace());
117
+ if (!isNamespace(namespace)) {
118
+ throw new CliFailure(EXIT_ERROR, `${JSON.stringify(namespace)} is not a namespace name (a lowercase letter, then lowercase letters, digits, "_" or "-")`);
119
+ }
120
+ const flag = stringFlag(context, "out");
121
+ const out = flag !== undefined
122
+ ? projectDirectory(flag)
123
+ : lock !== undefined && lock.namespace === namespace
124
+ ? directoryOf(lock.policies.path)
125
+ : DEFAULT_OUT;
126
+ const prefix = out === "." ? "" : `${out}/`;
127
+ const head = await registry.head(namespace);
128
+ const guardrails = await registry.guardrails(namespace);
129
+ if (lock?.guardrails != null && guardrails === null) {
130
+ throw new CliFailure(EXIT_FAILED, `${LOCK_FILE} pins the org's guardrails (${lock.guardrails.path}, ${lock.guardrails.hash}), and the registry serves none: the pull would drop them, and the org's denies with them. Nothing was written. Check that ${registry.url} is the org's registry; if the org has removed its guardrails, delete ${LOCK_FILE} and pull again`);
131
+ }
132
+ // The foreign facts: those pulled before, and those `--foreign` asks for now.
133
+ const foreign = await pulledForeign(context, lock, namespace);
134
+ for (const [owner, resources] of foreignFlags(context)) {
135
+ if (owner === namespace) {
136
+ throw new CliFailure(EXIT_ERROR, `--foreign names namespace "${namespace}", this project's own: its Resources are read as its own`);
137
+ }
138
+ const set = foreign.get(owner) ?? new Set();
139
+ for (const resource of resources) {
140
+ set.add(resource);
141
+ }
142
+ foreign.set(owner, set);
143
+ }
144
+ const owners = [
145
+ ...new Set([...foreign.keys(), ...foreignNamespaces(head.file)]),
146
+ ].sort();
147
+ const entries = {};
148
+ const beacons = {};
149
+ const environment = environmentOf(context);
150
+ if (owners.length > 0) {
151
+ if (environment === undefined) {
152
+ throw new CliFailure(EXIT_ERROR, `namespace "${namespace}" reads ${owners.map((owner) => `"${owner}"`).join(", ")}'s Resources as foreign, whose beacons are pinned per environment: name it with \`--env <environment>\` or ${ENV_VARIABLE} (such as \`--env production\`). Nothing was written`);
153
+ }
154
+ const store = await beaconStoreOf(context);
155
+ if (store === undefined) {
156
+ throw new CliFailure(EXIT_ERROR, `namespace "${namespace}" reads ${owners.map((owner) => `"${owner}"`).join(", ")}'s Resources as foreign: pass the service's beacon store with \`--beacon <module>\`, so the pull can check that this service reaches ${owners.length === 1 ? "that namespace's" : "their"} database. Nothing was written`);
157
+ }
158
+ for (const owner of owners) {
159
+ const asked = [...(foreign.get(owner) ?? [])].sort();
160
+ const answer = await registry.foreign(namespace, owner, asked);
161
+ await holdOwnerBeacon(store, owner, answer.beacon);
162
+ beacons[owner] = answer.beacon;
163
+ for (const resource of asked) {
164
+ entries[resource] = {
165
+ ...answer.resources[resource],
166
+ foreign: owner,
167
+ };
168
+ }
169
+ }
170
+ }
171
+ const facts = Object.keys(entries).length === 0
172
+ ? undefined
173
+ : { format: FOREIGN_FORMAT, namespace, schema: entries };
174
+ let foreignHash;
175
+ if (facts !== undefined) {
176
+ try {
177
+ foreignHash = foreignFactsHash(facts);
178
+ }
179
+ catch (error) {
180
+ throw new CliFailure(EXIT_FAILED, `the registry's entries of ${Object.keys(entries).sort().join(", ")} make no foreign facts file (${error.message}). Nothing was written`);
181
+ }
182
+ }
183
+ const policiesPath = `${prefix}${namespace}.policies.json`;
184
+ const guardrailsPath = `${prefix}guardrails.json`;
185
+ const foreignPath = `${prefix}${FOREIGN_FILE}`;
186
+ await writeProjectFile(context.cwd, policiesPath, jsonText(JSON.parse(canonicalPolicies(head.file))));
187
+ if (guardrails !== null) {
188
+ await writeProjectFile(context.cwd, guardrailsPath, jsonText(JSON.parse(canonicalGuardrails(guardrails.file))));
189
+ }
190
+ if (facts !== undefined) {
191
+ await writeProjectFile(context.cwd, foreignPath, jsonText(JSON.parse(canonicalForeignFacts(facts))));
192
+ }
193
+ const next = {
194
+ format: LOCK_FORMAT,
195
+ namespace,
196
+ policies: { path: policiesPath, hash: head.hash },
197
+ guardrails: guardrails === null
198
+ ? null
199
+ : { path: guardrailsPath, hash: guardrails.hash },
200
+ ...(facts === undefined || foreignHash === undefined
201
+ ? {}
202
+ : { foreign: { path: foreignPath, hash: foreignHash } }),
203
+ };
204
+ await writeProjectFile(context.cwd, LOCK_FILE, jsonText(next));
205
+ if (environment !== undefined && owners.length > 0) {
206
+ const pins = {
207
+ format: BEACON_PINS_FORMAT,
208
+ namespace,
209
+ environment,
210
+ beacons,
211
+ };
212
+ await writeProjectFile(context.cwd, beaconPinsFile(environment), jsonText(pins));
213
+ }
214
+ context.out(`pulled namespace "${namespace}" at ${head.hash} into ${policiesPath}`);
215
+ context.out(guardrails === null
216
+ ? "the registry has no guardrails file for the org yet: none pinned"
217
+ : `pulled the org's guardrails at ${guardrails.hash} into ${guardrailsPath}`);
218
+ if (facts !== undefined) {
219
+ context.out(`pulled the foreign facts of ${Object.keys(entries)
220
+ .sort()
221
+ .map((resource) => `${resource} (namespace "${entries[resource]?.foreign}")`)
222
+ .join(", ")} into ${foreignPath}`);
223
+ }
224
+ if (owners.length > 0) {
225
+ context.out(`checked the beacon${owners.length === 1 ? "" : "s"} of ${owners.map((owner) => `"${owner}"`).join(", ")}: this service reaches ${owners.length === 1 ? "its" : "their"} database; pinned for environment "${environment}" in ${beaconPinsFile(environment)}`);
226
+ }
227
+ context.out(`wrote ${LOCK_FILE}${lock === undefined
228
+ ? ""
229
+ : lock.policies.hash === head.hash &&
230
+ (lock.guardrails?.hash ?? null) === (guardrails?.hash ?? null)
231
+ ? " (unchanged)"
232
+ : " (updated)"}: commit it with the pulled files`);
233
+ return EXIT_OK;
234
+ };
@@ -0,0 +1,2 @@
1
+ import { type CliContext, type ExitCode } from "./cli.context.js";
2
+ export declare const pushCommand: (context: CliContext) => Promise<ExitCode>;
@@ -0,0 +1,59 @@
1
+ // `avaccess push <rules>`: the namespace's policies file, to the registry, with the
2
+ // namespace's push key. `<rules>` is the compiled file (`.json`) or the module that
3
+ // holds the rules (`definePolicies(model, { namespace })…`, or its `.contract()`), as
4
+ // its default export or as `rules`. The namespace is the file's own. The registry
5
+ // checks ownership and stores the file as an immutable revision under its hash.
6
+ //
7
+ // With `--beacon <module>` (the service's beacon store, `beacon.loader.ts`) the push
8
+ // carries the namespace's own beacon, its row in `_aventara_beacon` written the first
9
+ // time, and the beacon of each namespace the file reads as foreign, as the service's
10
+ // connection reads it: the registry keeps the first as the namespace's, and stores a
11
+ // file that reads another namespace only when the second matches what that
12
+ // namespace pushed (`ACCESS_FOREIGN_CROSS_DATABASE`).
13
+ import { isRecord } from "../policies.compiler.js";
14
+ import { POLICIES_FORMAT } from "../policies-contract.js";
15
+ import { foreignNamespaces } from "../registry/ownership.check.js";
16
+ import { beaconStoreOf, ownBeacon, readOwnerBeacon } from "./beacon.loader.js";
17
+ import { CliFailure, codeOf, EXIT_ERROR, EXIT_FAILED, EXIT_OK, messageOf, positionals, } from "./cli.context.js";
18
+ import { loadProjectModule, pickExport } from "./module.loader.js";
19
+ import { registryClient } from "./registry.client.js";
20
+ const isRules = (value) => isRecord(value) &&
21
+ (typeof value.contract === "function" || value.format === POLICIES_FORMAT);
22
+ export const pushCommand = async (context) => {
23
+ const [path] = positionals(context, "avaccess push <rules> [--beacon <module>]", 1, 1);
24
+ const exports = await loadProjectModule(context.cwd, path);
25
+ const rules = pickExport(exports, path, "rules", isRules, "policies file or `definePolicies(…)` rules");
26
+ let file;
27
+ try {
28
+ file = "contract" in rules ? rules.contract() : rules;
29
+ }
30
+ catch (error) {
31
+ throw new CliFailure(EXIT_FAILED, messageOf(error), codeOf(error));
32
+ }
33
+ const namespace = file.namespace;
34
+ if (typeof namespace !== "string") {
35
+ throw new CliFailure(EXIT_ERROR, `the rules in ${path} name no namespace: a pushed file belongs to one, \`definePolicies(model, { namespace: "<namespace>" })\``);
36
+ }
37
+ const store = await beaconStoreOf(context);
38
+ const owners = foreignNamespaces(file);
39
+ const registry = registryClient(context);
40
+ let beacons;
41
+ if (store !== undefined) {
42
+ beacons = {
43
+ [namespace]: await ownBeacon(store, namespace, await registry.namespaceBeacon(namespace)),
44
+ };
45
+ for (const owner of owners) {
46
+ beacons[owner] = await readOwnerBeacon(store, owner);
47
+ }
48
+ }
49
+ const pushed = await registry.push(namespace, file, beacons);
50
+ if (beacons !== undefined) {
51
+ context.out(`sent namespace "${namespace}"'s beacon${owners.length === 0
52
+ ? ""
53
+ : `, and the beacon${owners.length === 1 ? "" : "s"} of ${owners.map((owner) => `"${owner}"`).join(", ")} as this service's connection reads ${owners.length === 1 ? "it" : "them"}`}`);
54
+ }
55
+ context.out(pushed.created
56
+ ? `pushed namespace "${namespace}": ${pushed.hash}, a new revision, now its head`
57
+ : `pushed namespace "${namespace}": ${pushed.hash}, which the registry had; it is its head again`);
58
+ return EXIT_OK;
59
+ };
@@ -0,0 +1,63 @@
1
+ import type { AccessGuardrails } from "../guardrails/guardrails-contract.js";
2
+ import type { AccessPolicies, AccessSchemaResourceFacts } from "../policies-contract.js";
3
+ import { type CliContext } from "./cli.context.js";
4
+ /** The environment variable that holds the registry's URL, when `--registry` does not. */
5
+ export declare const REGISTRY_URL_VARIABLE = "AVACCESS_REGISTRY_URL";
6
+ /** The environment variable that holds the namespace's key. */
7
+ export declare const KEY_VARIABLE = "AVACCESS_KEY";
8
+ /** A revision as the registry answers it, its file hashed again. */
9
+ type PulledRevision<File> = {
10
+ readonly hash: string;
11
+ readonly file: File;
12
+ };
13
+ /** What a namespace reads of another one as foreign, as the registry answers it. */
14
+ type ForeignAnswer = {
15
+ /** The hash of the beacon the owner pushed; `null` when it has pushed none. */
16
+ readonly beacon: string | null;
17
+ /** The owner's schema entry of each Resource asked for. */
18
+ readonly resources: {
19
+ readonly [resource: string]: AccessSchemaResourceFacts;
20
+ };
21
+ };
22
+ /** One revision in a namespace's list, newest first. */
23
+ type RevisionEntry = {
24
+ readonly hash: string;
25
+ readonly createdAt: string;
26
+ };
27
+ /** The registry the context names, with its key. */
28
+ export declare const registryClient: (context: CliContext) => {
29
+ url: string;
30
+ /** The namespace the key belongs to, as the registry names it. */
31
+ keyNamespace: () => Promise<string>;
32
+ /** The hash of the namespace's current beacon (`null`: it has pushed none). */
33
+ namespaceBeacon: (namespace: string) => Promise<string | null>;
34
+ /** The namespace's current revision's hash (`null`: nothing pushed yet). */
35
+ headHash: (namespace: string) => Promise<string | null>;
36
+ /** The namespace's current revision, with its file. */
37
+ head: (namespace: string) => Promise<PulledRevision<AccessPolicies>>;
38
+ /** One of the namespace's revisions, with its file. */
39
+ revision: (namespace: string, hash: string) => Promise<PulledRevision<AccessPolicies>>;
40
+ /** The namespace's revisions, newest first. */
41
+ revisions: (namespace: string) => Promise<readonly RevisionEntry[]>;
42
+ /** The org's current guardrails, with their file (`null`: none pushed yet). */
43
+ guardrails: (namespace: string) => Promise<PulledRevision<AccessGuardrails> | null>;
44
+ /**
45
+ * What `namespace` reads of `owner` as foreign: the hash of `owner`'s beacon,
46
+ * and its schema entry of each of `resources`. Each entry is the owner's own,
47
+ * for a Resource asked for; anything else is refused.
48
+ */
49
+ foreign: (namespace: string, owner: string, resources: readonly string[]) => Promise<ForeignAnswer>;
50
+ /**
51
+ * Pushes the namespace's file, with the beacons its service read when there
52
+ * are; answers its hash and whether the registry stored it now.
53
+ */
54
+ push: (namespace: string, file: unknown, beacons?: {
55
+ readonly [namespace: string]: string;
56
+ }) => Promise<{
57
+ readonly hash: string;
58
+ readonly created: boolean;
59
+ }>;
60
+ };
61
+ /** The registry client `registryClient` builds. */
62
+ export type RegistryClient = ReturnType<typeof registryClient>;
63
+ export {};