@aventara/access 0.0.0-stage → 0.1.0-rc.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.
Files changed (240) hide show
  1. package/CHANGELOG.md +831 -0
  2. package/LICENSE +91 -0
  3. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  4. package/README.md +2439 -2
  5. package/dist/access/access-options.d.ts +66 -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 +273 -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 +12 -0
  28. package/dist/access/principal.subject.js +60 -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 +196 -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 +2 -0
  40. package/dist/access-warning.emitter.js +20 -0
  41. package/dist/access.builder.d.ts +299 -0
  42. package/dist/access.builder.js +66 -0
  43. package/dist/access.engine.d.ts +142 -0
  44. package/dist/access.engine.js +470 -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 +78 -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 +83 -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 +12 -0
  122. package/dist/foreign-references.check.js +134 -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 +72 -0
  134. package/dist/generator/prisma7/prisma-where.translator.js +136 -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 +11 -0
  174. package/dist/mcp/agent-scope.gate.js +75 -0
  175. package/dist/mcp/agent.grant.d.ts +36 -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 +192 -0
  190. package/dist/policies-contract.js +8 -0
  191. package/dist/policies.compiler.d.ts +36 -0
  192. package/dist/policies.compiler.js +1358 -0
  193. package/dist/policies.decider.d.ts +103 -0
  194. package/dist/policies.decider.js +382 -0
  195. package/dist/policies.hash.d.ts +10 -0
  196. package/dist/policies.hash.js +53 -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/reference-reads.check.d.ts +12 -0
  202. package/dist/reference-reads.check.js +148 -0
  203. package/dist/registry/access.registry.d.ts +173 -0
  204. package/dist/registry/access.registry.js +865 -0
  205. package/dist/registry/beacon.ledger.d.ts +30 -0
  206. package/dist/registry/beacon.ledger.js +93 -0
  207. package/dist/registry/ownership.check.d.ts +54 -0
  208. package/dist/registry/ownership.check.js +215 -0
  209. package/dist/registry/registry.endpoints.d.ts +50 -0
  210. package/dist/registry/registry.endpoints.js +193 -0
  211. package/dist/registry/registry.key.d.ts +7 -0
  212. package/dist/registry/registry.key.js +13 -0
  213. package/dist/registry/registry.options.d.ts +13 -0
  214. package/dist/registry/registry.options.js +28 -0
  215. package/dist/registry/registry.refusal.d.ts +6 -0
  216. package/dist/registry/registry.refusal.js +11 -0
  217. package/dist/registry/registry.schema.d.ts +238 -0
  218. package/dist/registry/registry.schema.js +127 -0
  219. package/dist/resources/auth-tables.catalog.d.ts +91 -0
  220. package/dist/resources/auth-tables.catalog.js +102 -0
  221. package/dist/resources/auth-tables.guard.d.ts +95 -0
  222. package/dist/resources/auth-tables.guard.js +405 -0
  223. package/dist/scope.resolver.d.ts +138 -0
  224. package/dist/scope.resolver.js +512 -0
  225. package/dist/sha256.digest.d.ts +2 -0
  226. package/dist/sha256.digest.js +81 -0
  227. package/dist/standalone/access.server.d.ts +41 -0
  228. package/dist/standalone/access.server.js +301 -0
  229. package/dist/standalone/index.d.ts +1 -0
  230. package/dist/standalone/index.js +1 -0
  231. package/package.json +101 -3
  232. package/src/aventara-where.translator.ts +116 -0
  233. package/src/beacon/beacon.check.ts +204 -0
  234. package/src/canonical-json.writer.ts +51 -0
  235. package/src/generator/prisma7/prisma-where.translator.ts +241 -0
  236. package/src/policies-contract.ts +184 -0
  237. package/src/policies.decider.ts +650 -0
  238. package/src/predicate.evaluator.ts +574 -0
  239. package/src/scope.resolver.ts +791 -0
  240. package/src/sha256.digest.ts +85 -0
@@ -0,0 +1,87 @@
1
+ // The Prisma 7 translator (`--orm prisma7`). In a generated client it adds
2
+ // `prismaWhere` (a scope as a Prisma `where`), `readable` (a read's fields as a
3
+ // Prisma `select`), `prismaExists` (`can`'s `exists`, through the service's Prisma
4
+ // client) and `prismaBeacon` (the ownership beacon's store over a Prisma model of
5
+ // `_aventara_beacon`), typed by the namespace's Resources, over the copied
6
+ // `prisma-where.translator.ts`. For `avaccess schema` it reads `schema.prisma`.
7
+ // Neither imports a Prisma module: the generated client stays free of any import.
8
+ import { propertyKey } from "../namespace.facts.js";
9
+ import { readPrismaSchema } from "./prisma-schema.reader.js";
10
+ import { prismaDelegateOf } from "./prisma-where.translator.js";
11
+ const clientPart = (namespace) => {
12
+ const delegates = namespace.resources.map((resource) => `\treadonly ${propertyKey(prismaDelegateOf(resource.name))}${resource.foreign === undefined ? "" : "?"}: PrismaFindFirst;`);
13
+ const text = `/**
14
+ * The Prisma client \`prismaExists\` asks through: a model delegate per Resource the
15
+ * rules name (a foreign one only when this service's schema maps it).
16
+ */
17
+ export type PrismaClientOf = {
18
+ ${delegates.join("\n")}
19
+ };
20
+
21
+ /** A Prisma \`select\` of the scalar fields of \`R\`. */
22
+ export type PrismaSelectOf<R extends ResourceName> = {
23
+ [F in ResourceFields[R]]?: true;
24
+ };
25
+
26
+ /** A scope (\`scopeOf\`'s answer) on \`resource\` as a Prisma \`where\`: \`true\` every row, \`false\` none. */
27
+ const prismaWhere = (
28
+ resource: ResourceName,
29
+ scope: boolean | AccessWhere,
30
+ ): PrismaWhere => toPrismaScope(policies, resource, scope);
31
+
32
+ /**
33
+ * The fields a read shows on every row, as a Prisma \`select\`. A governed field that
34
+ * a grant opens on some rows only is left out (ask \`fieldsOf\` for it); no relation
35
+ * is selected, since a related row would not be scoped.
36
+ */
37
+ const readable = <R extends ResourceName>(
38
+ question: QuestionOf<R>,
39
+ ): PrismaSelectOf<R> =>
40
+ toPrismaSelect(
41
+ policies,
42
+ question.resource,
43
+ decider.fieldsOf({ ...question, access: "read" }),
44
+ ) as PrismaSelectOf<R>;
45
+
46
+ /**
47
+ * \`can\`'s \`exists\`, through your Prisma client:
48
+ * \`exists: (resource, where) => access.prismaExists(prisma, resource, where)\`.
49
+ */
50
+ const prismaExists = (
51
+ prisma: PrismaClientOf,
52
+ resource: ResourceName,
53
+ where: AccessWhere,
54
+ ): Promise<boolean> => existsThroughPrisma(prisma, policies, resource, where);
55
+
56
+ /**
57
+ * The ownership beacon's store over your Prisma client (the \`AventaraBeacon\` model
58
+ * over the beacon table, see \`PrismaBeaconClient\`): what \`avaccess push --beacon\`
59
+ * and \`avaccess pull --beacon\` load, and what
60
+ * \`access.checkBeacons(access.prismaBeacon(prisma), pins, environment)\` reads at
61
+ * startup, \`pins\` being this environment's avAccess.beacons.<environment>.json.
62
+ */
63
+ const prismaBeacon = (prisma: PrismaBeaconClient) => beaconThroughPrisma(prisma);
64
+ `;
65
+ return {
66
+ text,
67
+ members: ["prismaWhere", "readable", "prismaExists", "prismaBeacon"],
68
+ imports: {
69
+ values: [
70
+ "beaconThroughPrisma",
71
+ "existsThroughPrisma",
72
+ "toPrismaScope",
73
+ "toPrismaSelect",
74
+ ],
75
+ types: ["PrismaBeaconClient", "PrismaFindFirst", "PrismaWhere"],
76
+ },
77
+ exports: ["PrismaBeaconClient", "PrismaWhere"],
78
+ };
79
+ };
80
+ export const prisma7Translator = {
81
+ orm: "prisma7",
82
+ title: "Prisma 7",
83
+ modules: ["src/generator/prisma7/prisma-where.translator.ts"],
84
+ clientPart,
85
+ defaultSchema: "prisma/schema.prisma",
86
+ readSchema: readPrismaSchema,
87
+ };
@@ -0,0 +1,14 @@
1
+ import type { AccessSchema } from "../access-schema.js";
2
+ import type { OrmTranslator } from "./orm.translator.js";
3
+ /** What a schema module is made from. */
4
+ type SchemaModuleInput = {
5
+ readonly schema: AccessSchema;
6
+ readonly translator: OrmTranslator;
7
+ /** The ORM schema it was read from, as the command named it. */
8
+ readonly source: string;
9
+ /** The command that regenerates the module, as the banner prints it. */
10
+ readonly command: string;
11
+ };
12
+ /** The schema module's text. */
13
+ export declare const emitSchemaModule: (input: SchemaModuleInput) => string;
14
+ export {};
@@ -0,0 +1,23 @@
1
+ // `avaccess schema`'s output: the `AccessSchema` read from the service's ORM schema,
2
+ // as a TypeScript module a service that does not run on Aventara authors its rules
3
+ // against: `definePolicies(model, { namespace })`, as an Aventara app does with its
4
+ // discovered model. `as const` keeps every name literal, so each rule's Resource,
5
+ // field, relation and enum value is checked at its key. The text is a function of the
6
+ // ORM schema alone.
7
+ import { typescriptLiteral } from "./typescript-literal.writer.js";
8
+ /** The schema module's text. */
9
+ export const emitSchemaModule = (input) => `// biome-ignore-all format: generated output
10
+ // biome-ignore-all lint: generated output
11
+ /* eslint-disable */
12
+ // Generated by \`avaccess schema\` from ${input.source} (${input.translator.title}). Do not edit
13
+ // this file: regenerate it with
14
+ //
15
+ // ${input.command}
16
+ //
17
+ // when the schema changes. Author the namespace's rules against it:
18
+ //
19
+ // definePolicies(model, { namespace: "<your namespace>" })
20
+
21
+ /** The Resources of ${input.source}, their fields and the field tuples that identify a row. */
22
+ export const model = ${typescriptLiteral(input.schema)} as const;
23
+ `;
@@ -0,0 +1,44 @@
1
+ /** The extension a relative import in the generated client is written with. */
2
+ type ImportExtension = "" | ".js" | ".ts";
3
+ /** Why the project's tsconfig cannot be followed: the sentence the CLI prints. */
4
+ export declare class TsconfigRefusal extends Error {
5
+ }
6
+ /** A tsconfig as `get-tsconfig` resolves it (`TsConfigJsonResolved`): the parts this reads. */
7
+ type ResolvedTsconfig = {
8
+ compilerOptions?: {
9
+ [option: string]: unknown;
10
+ };
11
+ references?: {
12
+ path: string;
13
+ }[];
14
+ files?: string[];
15
+ include?: string[];
16
+ exclude?: string[];
17
+ };
18
+ /** A tsconfig file and its resolved content (`TsConfigResult`). */
19
+ type LocatedTsconfig = {
20
+ path: string;
21
+ config: ResolvedTsconfig;
22
+ };
23
+ /** What `createFilesMatcher` is handed: the file lists it matches with, the rest riding along. */
24
+ type MatchedTsconfig = {
25
+ path: string;
26
+ config: {
27
+ files?: string[];
28
+ include?: string[];
29
+ exclude?: string[];
30
+ };
31
+ };
32
+ /** The three calls of `get-tsconfig` this makes, as `avclient` makes them. */
33
+ export type TsconfigLibrary = {
34
+ getTsconfig: (searchPath?: string) => LocatedTsconfig | null;
35
+ parseTsconfig: (tsconfigPath: string) => ResolvedTsconfig;
36
+ createFilesMatcher: (tsconfig: MatchedTsconfig) => (filePath: string) => ResolvedTsconfig | undefined;
37
+ };
38
+ /**
39
+ * The import extension for the generated client at `file` (absolute), in the project
40
+ * at `cwd`, from the tsconfig `named` (`--tsconfig`, relative to `cwd`) or found.
41
+ * Throws a `TsconfigRefusal` when there is none to follow.
42
+ */
43
+ export declare const importExtensionOf: (cwd: string, file: string, named: string | undefined) => Promise<ImportExtension>;
44
+ export {};
@@ -0,0 +1,110 @@
1
+ // How the generated client's two files name each other: the extension of the one
2
+ // relative import `avAccess.ts` writes, read from the project's `tsconfig.json` by
3
+ // Prisma 7's `prisma-client` rules, as the framework's `avclient` reads them:
4
+ //
5
+ // 1. `allowImportingTsExtensions` or `rewriteRelativeImportExtensions` → `.ts`;
6
+ // 2. `module` is `commonjs`, or `moduleResolution` is `bundler` (either case)
7
+ // → no extension;
8
+ // 3. otherwise → `.js` (what Node's ES module resolution, `node16`/`nodenext`,
9
+ // needs).
10
+ //
11
+ // Which tsconfig, found as `avclient` finds it, with the library and version Prisma 7
12
+ // and `avclient` use (`get-tsconfig`, loaded only when `generate` runs): the one
13
+ // `--tsconfig` names, used as named; otherwise `getTsconfig` from the generated
14
+ // file's directory up, its `extends` chain merged as TypeScript merges it. A
15
+ // solution-style root (Vite's: `"files": []` and `references`) compiles nothing
16
+ // itself: when the config found does not include the generated file and has
17
+ // `references`, the first referenced project that does include it is used, as
18
+ // TypeScript's language service picks a file's project; with none, the root stays.
19
+ //
20
+ // No tsconfig is refused (ruled 2026-10-23, as `avclient` refuses it): the client is
21
+ // TypeScript, compiled by the project's own toolchain the way its tsconfig says, and
22
+ // a project without one is a JavaScript project, not supported yet.
23
+ import { readFile } from "node:fs/promises";
24
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
25
+ /** Why the project's tsconfig cannot be followed: the sentence the CLI prints. */
26
+ export class TsconfigRefusal extends Error {
27
+ }
28
+ /**
29
+ * Whether `tsconfig` compiles `file`. `parseTsconfig` (4.10.0) turns a
30
+ * `${configDir}` entry of `files`, `include` or `exclude` into an absolute path, which
31
+ * `createFilesMatcher` then joins under the config's directory again and never
32
+ * matches; such an entry is handed to it relative to that directory.
33
+ */
34
+ const compiles = (library, tsconfig, file) => {
35
+ const directory = dirname(tsconfig.path);
36
+ const local = (list) => list?.map((entry) => isAbsolute(entry) ? relative(directory, entry) || "." : entry);
37
+ const files = local(tsconfig.config.files);
38
+ const include = local(tsconfig.config.include);
39
+ const exclude = local(tsconfig.config.exclude);
40
+ const config = {
41
+ ...tsconfig.config,
42
+ ...(files === undefined ? {} : { files }),
43
+ ...(include === undefined ? {} : { include }),
44
+ ...(exclude === undefined ? {} : { exclude }),
45
+ };
46
+ return (library.createFilesMatcher({ path: tsconfig.path, config })(file) !==
47
+ undefined);
48
+ };
49
+ /** The compiler options a run follows, from the tsconfig `avclient`'s rules pick. */
50
+ const compilerOptionsOf = async (cwd, file, named) => {
51
+ const library = (await import("get-tsconfig"));
52
+ const { getTsconfig, parseTsconfig } = library;
53
+ if (named !== undefined) {
54
+ const path = resolve(cwd, named);
55
+ try {
56
+ // `get-tsconfig` reads leniently: text that is not a tsconfig parses as an
57
+ // empty one. Reading it first makes a missing or unreadable file a refusal.
58
+ await readFile(path, "utf8");
59
+ }
60
+ catch (error) {
61
+ const code = error?.code;
62
+ throw new TsconfigRefusal(`--tsconfig names ${named}, which could not be read (${typeof code === "string" ? code : String(error)}); it must name the tsconfig.json your project compiles the generated client with. Nothing was written.`);
63
+ }
64
+ return parseTsconfig(path).compilerOptions ?? {};
65
+ }
66
+ const directory = dirname(file);
67
+ const found = getTsconfig(directory);
68
+ if (found === null) {
69
+ throw new TsconfigRefusal(`no tsconfig.json was found in ${relative(cwd, directory) || "."} or any directory above it. The client avaccess generate writes is TypeScript, compiled by your project's own toolchain and written the way its tsconfig.json says; JavaScript projects are not supported yet. Add a tsconfig.json to the project (\`npx tsc --init\` writes one), or, if it has one this search does not find, name it with \`--tsconfig <file>\`. Nothing was written.`);
70
+ }
71
+ const references = found.config.references;
72
+ if (references === undefined || compiles(library, found, file)) {
73
+ return found.config.compilerOptions ?? {};
74
+ }
75
+ for (const reference of references) {
76
+ // A config file, or a directory holding `tsconfig.json`; one not there is skipped.
77
+ const target = resolve(dirname(found.path), reference.path);
78
+ for (const candidate of [target, resolve(target, "tsconfig.json")]) {
79
+ try {
80
+ await readFile(candidate, "utf8");
81
+ }
82
+ catch {
83
+ continue;
84
+ }
85
+ const referenced = { path: candidate, config: parseTsconfig(candidate) };
86
+ if (compiles(library, referenced, file)) {
87
+ return referenced.config.compilerOptions ?? {};
88
+ }
89
+ break;
90
+ }
91
+ }
92
+ return found.config.compilerOptions ?? {};
93
+ };
94
+ /**
95
+ * The import extension for the generated client at `file` (absolute), in the project
96
+ * at `cwd`, from the tsconfig `named` (`--tsconfig`, relative to `cwd`) or found.
97
+ * Throws a `TsconfigRefusal` when there is none to follow.
98
+ */
99
+ export const importExtensionOf = async (cwd, file, named) => {
100
+ const options = await compilerOptionsOf(cwd, file, named);
101
+ if (options.allowImportingTsExtensions === true ||
102
+ options.rewriteRelativeImportExtensions === true) {
103
+ return ".ts";
104
+ }
105
+ const lower = (value) => typeof value === "string" ? value.toLowerCase() : "";
106
+ return lower(options.module) === "commonjs" ||
107
+ lower(options.moduleResolution) === "bundler"
108
+ ? ""
109
+ : ".js";
110
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * `value` as a TypeScript literal starting at column `indent` tabs deep (a tab counts
3
+ * as two columns), every opened line indented one tab more.
4
+ */
5
+ export declare const typescriptLiteral: (value: unknown, indent?: number) => string;
@@ -0,0 +1,42 @@
1
+ // JSON data written as a TypeScript literal for a generated file: an object or a list
2
+ // stays on one line when that line fits, and opens one member a line when it does
3
+ // not; a key is bare when it is an identifier. Members are written in the order they
4
+ // are given, so the caller decides the order (the generated files give a sorted or
5
+ // canonical one), and the same value always gives the same text.
6
+ /** The column a one-line value may reach, its indentation included. */
7
+ const WIDTH = 100;
8
+ const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
9
+ const keyOf = (key) => IDENTIFIER.test(key) ? key : JSON.stringify(key);
10
+ const oneLine = (value) => {
11
+ if (Array.isArray(value)) {
12
+ return `[${value.map(oneLine).join(", ")}]`;
13
+ }
14
+ if (typeof value === "object" && value !== null) {
15
+ const members = Object.entries(value).filter(([, member]) => member !== undefined);
16
+ return members.length === 0
17
+ ? "{}"
18
+ : `{ ${members.map(([key, member]) => `${keyOf(key)}: ${oneLine(member)}`).join(", ")} }`;
19
+ }
20
+ return JSON.stringify(value);
21
+ };
22
+ /**
23
+ * `value` as a TypeScript literal starting at column `indent` tabs deep (a tab counts
24
+ * as two columns), every opened line indented one tab more.
25
+ */
26
+ export const typescriptLiteral = (value, indent = 0) => {
27
+ const flat = oneLine(value);
28
+ if (typeof value !== "object" ||
29
+ value === null ||
30
+ indent * 2 + flat.length <= WIDTH) {
31
+ return flat;
32
+ }
33
+ const inner = "\t".repeat(indent + 1);
34
+ const close = "\t".repeat(indent);
35
+ if (Array.isArray(value)) {
36
+ return value.length === 0
37
+ ? "[]"
38
+ : `[\n${value.map((item) => `${inner}${typescriptLiteral(item, indent + 1)},`).join("\n")}\n${close}]`;
39
+ }
40
+ const members = Object.entries(value).filter(([, member]) => member !== undefined);
41
+ return `{\n${members.map(([key, member]) => `${inner}${keyOf(key)}: ${typescriptLiteral(member, indent + 1)},`).join("\n")}\n${close}}`;
42
+ };
@@ -0,0 +1,45 @@
1
+ import type { AccessAction, AccessClaimType, AccessPrincipalCondition } from "../policies-contract.js";
2
+ /** The format marker of every guardrails file (`AccessGuardrails["format"]`). */
3
+ export declare const GUARDRAILS_FORMAT: AccessGuardrails["format"];
4
+ /**
5
+ * The Resources a guardrail narrows: `"*"`, every Resource of every namespace, or,
6
+ * per namespace, `"*"` (all of that namespace's Resources) or a list of them. A
7
+ * service applies only its own namespace's list and leaves the others' alone.
8
+ */
9
+ export type AccessGuardrailResources = "*" | {
10
+ readonly [namespace: string]: "*" | readonly [string, ...string[]];
11
+ };
12
+ /** One guardrail: a deny on whole Resources, when the principal condition holds (always, without one). */
13
+ export type AccessGuardrailRule = {
14
+ readonly id: string;
15
+ readonly actions: "*" | readonly AccessAction[];
16
+ readonly resources: AccessGuardrailResources;
17
+ readonly if?: AccessPrincipalCondition;
18
+ };
19
+ /** The `guardrails@1` file: the claims its guardrails compare, by type, and the guardrails. */
20
+ export type AccessGuardrails = {
21
+ readonly format: "aventara-access/guardrails@1";
22
+ readonly claims: {
23
+ readonly [claim: string]: AccessClaimType;
24
+ };
25
+ readonly denies: readonly AccessGuardrailRule[];
26
+ };
27
+ /**
28
+ * A guardrails file as a JSON import types it: `resolveJsonModule` widens `format` to
29
+ * `string` and each tuple to an array, so the file `avaccess pull` wrote, imported
30
+ * with `import guardrails from "./access/guardrails.json" with { type: "json" }`, is
31
+ * not an `AccessGuardrails`. `policies.guardrails` and the engine take it as it is:
32
+ * only its outline is typed, and the engine checks the rest when it is built. A
33
+ * policies file (it has `schema` and `grants`) is not one.
34
+ */
35
+ export type AccessGuardrailsJson = {
36
+ readonly format: string;
37
+ readonly claims: {
38
+ readonly [claim: string]: {
39
+ readonly scalar: string;
40
+ };
41
+ };
42
+ readonly denies: readonly object[];
43
+ readonly schema?: never;
44
+ readonly grants?: never;
45
+ };
@@ -0,0 +1,12 @@
1
+ // The org's guardrails file, format `aventara-access/guardrails@1`. Central Access
2
+ // writes it with `defineGuardrails`, the registry serves it to every namespace, and
3
+ // each service's engine applies it on top of the service's own policies file. It is
4
+ // deny-only: the data owner writes the grants, and a guardrail can only narrow what
5
+ // they allow. A guardrail reads the principal alone (its claims and the request's
6
+ // origin) and names whole Resources, by namespace; it never reads a row.
7
+ //
8
+ // Like `policies@1`, it is passive JSON, checked before use, and frozen: a change to
9
+ // its shape is `guardrails@2` beside it. Its canonical byte form and hash are
10
+ // `guardrails.hash.ts`'s.
11
+ /** The format marker of every guardrails file (`AccessGuardrails["format"]`). */
12
+ export const GUARDRAILS_FORMAT = "aventara-access/guardrails@1";
@@ -0,0 +1,48 @@
1
+ import type { AccessClaimsDeclaration, AccessClaimsOf } from "../access/claims.mapper.js";
2
+ import type { AccessAction, AccessOrigin } from "../policies-contract.js";
3
+ import type { AccessGuardrailResources, AccessGuardrails } from "./guardrails-contract.js";
4
+ /** The options of a guardrail: its name and the principal condition it applies under. */
5
+ export type AccessGuardrailOptions<C extends AccessClaimsDeclaration> = {
6
+ /** The guardrail's id (else `guardrail/<actions>#n`). The id is what `explain` and a diff name. */
7
+ readonly name?: string;
8
+ /** A condition on the principal alone: its claims, typed from `defineGuardrails({ claims })`, and the request's origin. */
9
+ readonly if?: {
10
+ readonly claims?: AccessClaimsOf<C>;
11
+ readonly origin?: AccessOrigin;
12
+ };
13
+ };
14
+ declare const guardrailBrand: unique symbol;
15
+ /** A guardrail, as data, before compilation. */
16
+ export type AccessGuardrailDeny = {
17
+ readonly [guardrailBrand]: true;
18
+ readonly actions: "*" | AccessAction | readonly [AccessAction, ...AccessAction[]];
19
+ readonly resources: AccessGuardrailResources;
20
+ readonly options: {
21
+ readonly [option: string]: unknown;
22
+ };
23
+ };
24
+ /** `deny(actions, resources, options?)`: a guardrail. Every guardrail only narrows. */
25
+ export type AccessGuardrailDenyFunction<C extends AccessClaimsDeclaration> = (actions: "*" | AccessAction | readonly [AccessAction, ...AccessAction[]], resources: AccessGuardrailResources, options?: AccessGuardrailOptions<C>) => AccessGuardrailDeny;
26
+ /** The builder `defineGuardrails` returns. It denies and nothing else; each call returns a new builder. */
27
+ export interface AccessGuardrailsBuilder<C extends AccessClaimsDeclaration> {
28
+ /** Guardrails. Each one denies; none can grant. Order never matters. */
29
+ denies(rules: (tools: {
30
+ readonly deny: AccessGuardrailDenyFunction<C>;
31
+ }) => readonly AccessGuardrailDeny[]): AccessGuardrailsBuilder<C>;
32
+ /** The compiled `guardrails@1` file. */
33
+ contract(): AccessGuardrails;
34
+ }
35
+ /** What the builder holds before it compiles. */
36
+ export type AccessGuardrailsDefinition = {
37
+ readonly claims?: AccessClaimsDeclaration;
38
+ readonly denies: readonly AccessGuardrailDeny[];
39
+ };
40
+ /**
41
+ * Starts the org's guardrails: deny-only rules central Access adds on top of every
42
+ * namespace's own policies. `claims` declares the claims they compare, by type, the
43
+ * same declaration as `createAccess`'s `policies.claims`.
44
+ */
45
+ export declare const defineGuardrails: <const C extends AccessClaimsDeclaration = Record<never, never>>(options?: {
46
+ readonly claims?: C;
47
+ }) => AccessGuardrailsBuilder<C>;
48
+ export {};
@@ -0,0 +1,32 @@
1
+ // `defineGuardrails`: the typed authoring API of the org's guardrails. It is
2
+ // deny-only by construction: the builder has `denies` and no `grants`, its callback
3
+ // hands out `deny` and nothing else, and a guardrail's options are a name and a
4
+ // condition on the principal (`if: { claims, origin }`), never a row condition. It
5
+ // compiles to the `guardrails@1` file (`guardrails.compiler.ts`).
6
+ import { compileGuardrails } from "./guardrails.compiler.js";
7
+ const deny = (actions, resources, options = {}) => Object.freeze({
8
+ actions,
9
+ resources,
10
+ options,
11
+ });
12
+ const builderOf = (definition) => ({
13
+ denies: (rules) => builderOf({
14
+ ...definition,
15
+ denies: [
16
+ ...definition.denies,
17
+ ...rules({
18
+ deny: deny,
19
+ }),
20
+ ],
21
+ }),
22
+ contract: () => compileGuardrails(definition),
23
+ });
24
+ /**
25
+ * Starts the org's guardrails: deny-only rules central Access adds on top of every
26
+ * namespace's own policies. `claims` declares the claims they compare, by type, the
27
+ * same declaration as `createAccess`'s `policies.claims`.
28
+ */
29
+ export const defineGuardrails = (options) => builderOf({
30
+ ...(options?.claims !== undefined ? { claims: options.claims } : {}),
31
+ denies: [],
32
+ });
@@ -0,0 +1,10 @@
1
+ import type { AccessGuardrailsDefinition } from "./guardrails.builder.js";
2
+ import { type AccessGuardrails } from "./guardrails-contract.js";
3
+ /** Compiles the builder's guardrails to the `guardrails@1` file and checks it. */
4
+ export declare const compileGuardrails: (definition: AccessGuardrailsDefinition) => AccessGuardrails;
5
+ /**
6
+ * Checks a guardrails file before a registry stores it or an engine applies it.
7
+ * Throws an `Error` naming the guardrail. The file is trusted for nothing it can be
8
+ * checked for.
9
+ */
10
+ export declare const checkGuardrails: (guardrails: AccessGuardrails) => void;