Bump the contract for the new gate, and close the ways it could read as open
CI / full-gate (push) Successful in 2m45s

This commit is contained in:
2026-09-02 08:36:16 +02:00
parent a17ed96b54
commit bf146c07e7
19 changed files with 57 additions and 44 deletions
+5 -9
View File
@@ -1,16 +1,13 @@
// The coarse gate a route or nav node declares. One home for the rule, so the router and the menu
// can never disagree about what a visitor may reach.
// One home for the gate rule, so the router and the menu can never disagree about what a visitor
// may reach. README → Public pages & menu items.
import type { User } from "../http/context.ts";
// Widest first: whoever passes an earlier gate passes it without holding anything.
const GATES = ["public", "session", "permission"] as const;
// A route or nav node names exactly one of these; discovery refuses two. Omitting all three is the
// same as `public`, which is why stating it outright makes an open gate a choice, not an oversight.
export interface Gate {
permission?: string | undefined; // the Keto Permission the caller must hold, `<resource>:<action>`
public?: boolean | undefined; // anyone, signed in or not
session?: boolean | undefined; // any signed-in user, no grant to hold; anonymous is sent to /login
permission?: string; // the Keto Permission the caller must hold, `<resource>:<action>`
public?: boolean; // anyone, signed in or not
session?: boolean; // any signed-in user, no grant to hold; anonymous is sent to /login
}
export function allows(gate: Gate, user: User | null): boolean {
@@ -19,7 +16,6 @@ export function allows(gate: Gate, user: User | null): boolean {
return gate.permission == null || (user?.permissions.includes(gate.permission) ?? false);
}
// Which gates a declaration sets — discovery refuses more than one, since they contradict.
export function gatesSet(gate: Gate | null | undefined): string[] {
if (gate == null) return [];
return GATES.filter((name) => (name === "permission" ? gate.permission != null : gate[name] === true));