feat: add core authorization foundation

This commit is contained in:
ekklesia
2026-07-23 10:29:01 -05:00
parent 3fa1c4cdec
commit 72e082ff2f
23 changed files with 3029 additions and 3 deletions
+21 -1
View File
@@ -16,11 +16,23 @@ export const capabilityIdSchema = z
.max(160)
.regex(namespacedIdentifierPattern, "Must be a lowercase dot-namespaced capability identifier.");
/**
* Opaque at the type level via Zod's nominal `.brand()`, exactly like `EkklesiaUserId`: a plain
* `string` is not assignable to `PermissionId` without passing through
* `parsePermissionId`/`safeParsePermissionId`. The brand adds no runtime tag or wrapper — a value
* still serializes as a plain string. This is the one permission-identifier type shared by module
* manifest declarations (`moduleManifestSchema`'s `permissions` array) and Core's authorization
* evaluator, so a permission a module declares and a permission Core evaluates are provably the
* same identifier space. Namespacing (the required `.`-segments) is purely a naming convention for
* readability and collision-avoidance — it never implies hierarchy or inheritance during
* evaluation, and wildcard segments are not part of this format at all.
*/
export const permissionIdSchema = z
.string()
.min(3)
.max(160)
.regex(namespacedIdentifierPattern, "Must be a lowercase dot-namespaced permission identifier.");
.regex(namespacedIdentifierPattern, "Must be a lowercase dot-namespaced permission identifier.")
.brand<"PermissionId">();
export const logicalBucketIdSchema = z
.string()
@@ -58,3 +70,11 @@ export type CapabilityId = z.infer<typeof capabilityIdSchema>;
export type PermissionId = z.infer<typeof permissionIdSchema>;
export type LogicalBucketId = z.infer<typeof logicalBucketIdSchema>;
export type PackageRelativePath = z.infer<typeof packageRelativePathSchema>;
export function parsePermissionId(input: unknown): PermissionId {
return permissionIdSchema.parse(input);
}
export function safeParsePermissionId(input: unknown): z.ZodSafeParseResult<PermissionId> {
return permissionIdSchema.safeParse(input);
}