From 42644ba0f5f1c2b42899ee699222800eff90614e Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 9 Nov 2026 00:11:47 +0100 Subject: [PATCH 1/4] Correct the stale and false comments and docs the comprehension seats flagged --- README.md | 8 ++++---- ci.sh | 2 +- compose.yml | 2 +- docs/reading-cost.md | 17 +++++++++++++++++ examples/plugins/auth-admin/README.md | 2 +- examples/plugins/auth-admin/admin-groups.ts | 2 +- .../views/partials/group-detail-body.ejs | 4 +++- examples/plugins/scheduling/plugin.ts | 2 +- public/css/styles.css | 4 ++++ src/auth/bootstrap.ts | 5 +++-- src/auth/jwt.ts | 5 +++-- src/auth/login.ts | 2 +- src/plugin-host/router.ts | 3 ++- src/plugin-host/system.ts | 4 ++-- src/ui/chrome.ts | 4 ++-- 15 files changed, 46 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 86c2694..6435fef 100644 --- a/README.md +++ b/README.md @@ -347,8 +347,8 @@ curl -X PUT http://keto:4467/admin/relation-tuples -H 'content-type: application **A permission's name is authored in plugin code; only its *grants* live in Keto.** A plugin declares the permissions it gates on (`permissions:` in the manifest) and the host collects them into one catalog — `ctx.declaredPermissions` — which is the fixed list the admin screens offer. -Nothing in a GUI invents a name, and a tuple naming something no installed plugin declares gates -nothing. +Nothing in a GUI invents a name; a tuple naming something no installed plugin declares is not +offered by the admin screens, though a plugin may still gate on it. A change takes effect on the user's **next login or JWT re-mint** (~10 min) — see [Instant revoke](#instant-revoke-the-optional-denylist) when you need it sooner. @@ -1363,8 +1363,8 @@ be `metadata_public`, not `metadata_admin` — the session Kratos hands the toke public metadata, and the user can already read these permissions in their own JWT. The projection is a per-login cache, authoritative nowhere; a stale one self-heals on the next login. -Login resolves both direct grants and group membership, so the JWT `permissions` match what the -admin **Effective access** view shows. Cost: **a handful of Keto reads + one identity refresh per +Login resolves both direct grants and group membership, so the JWT `permissions` match what Keto +says the user holds. Cost: **a handful of Keto reads + one identity refresh per login**, never per request. JWKS is cached, so signature verification hits the network only on key rotation. diff --git a/ci.sh b/ci.sh index 0a3bef7..7c62cd5 100755 --- a/ci.sh +++ b/ci.sh @@ -6,7 +6,7 @@ # bash ci.sh # # Exits non-zero on the first failure. Each E2E suite OWNS a clean stack — never point two suites at -# one backend (auth-refresh revokes the admin's sessions; full-flow writes users/groups/roles to Keto). +# one backend (auth-refresh revokes the admin's sessions; full-flow writes users, groups and grants). set -euo pipefail cd "$(dirname "$0")" diff --git a/compose.yml b/compose.yml index 987c103..93a5b3e 100644 --- a/compose.yml +++ b/compose.yml @@ -165,7 +165,7 @@ services: # Ory Hydra — OAuth2/OIDC provider (other apps log in *through* plainpages; README). # DSN is its own `hydra` DB (init.sql); config in ory/hydra/hydra.yml. web implements the - # login challenge at /oauth2/login (consent next). Dev permits the http issuer via --dev + # login challenge at /oauth2/login and the consent challenge at /oauth2/consent. Dev permits the http issuer via --dev # (compose.override.yml); prod sets an https issuer via env (URLS_SELF_ISSUER). hydra-migrate: image: oryd/hydra:v26.2.0 diff --git a/docs/reading-cost.md b/docs/reading-cost.md index 012edf9..cd54ac5 100644 --- a/docs/reading-cost.md +++ b/docs/reading-cost.md @@ -513,3 +513,20 @@ Read most (opens and searches): - `AGENTS.md` 1 - `README.md` 1 - `docs/decisions.md` 1 + +## Reading cost, chunk f0194a598d2a, finished 2026-11-08T23:10:24Z + +81419 new input tokens before the first commit. + +Read most (opens and searches): + +- `src/http/app-admin.test.ts` 11 +- `examples/plugins/auth-admin/admin-users.ts` 7 +- `todo.md` 6 +- `examples/plugins/auth-admin/admin-groups.test.ts` 4 +- `README.md` 3 +- `examples/plugins/auth-admin/admin-groups.ts` 3 +- `CHANGELOG.md` 2 +- `docs/reading-cost.md` 2 +- `examples/plugins/auth-admin/admin-grants.ts` 2 +- `examples/plugins/auth-admin/i18n/en-US.ts` 2 diff --git a/examples/plugins/auth-admin/README.md b/examples/plugins/auth-admin/README.md index 16980d5..a33b48e 100644 --- a/examples/plugins/auth-admin/README.md +++ b/examples/plugins/auth-admin/README.md @@ -46,7 +46,7 @@ A grant of a name no installed plugin declares is listed apart on the same page, ## Layout -- `plugin.ts` — the manifest: the Admin nav fragment, the six permissions the plugin declares, and +- `plugin.ts` — the manifest: the Admin nav fragment, the seven permissions the plugin declares, and the route table — one thin handler per method+path, gated via `permissionName(resource, actionForMethod(method))` so a GET needs `:read` and a POST `:write`. - `admin-grants.ts` — the permission picker and the grant diff, shared by the Users and Groups diff --git a/examples/plugins/auth-admin/admin-groups.ts b/examples/plugins/auth-admin/admin-groups.ts index 94eb0c1..9e255db 100644 --- a/examples/plugins/auth-admin/admin-groups.ts +++ b/examples/plugins/auth-admin/admin-groups.ts @@ -15,7 +15,7 @@ const GROUP_NS = "Group"; const MEMBERS = "members"; const DEFAULT_PAGE_SIZE = 25; const PAGE_SIZES = [25, 50, 100]; -// One Keto page of candidate users is fetched for the member pickers (mirrors admin-users). +// One Kratos `listIdentities` page of candidate users is fetched for the member pickers (mirrors admin-users). const LIST_FETCH_SIZE = 250; const GROUP_NAME = /^[a-z0-9][a-z0-9_-]*$/; // URL-safe; doubles as the path segment const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; // a Kratos identity id diff --git a/examples/plugins/auth-admin/views/partials/group-detail-body.ejs b/examples/plugins/auth-admin/views/partials/group-detail-body.ejs index d34d4c6..2fe888c 100644 --- a/examples/plugins/auth-admin/views/partials/group-detail-body.ejs +++ b/examples/plugins/auth-admin/views/partials/group-detail-body.ejs @@ -1,9 +1,11 @@ <%# Admin group membership body, captured into the shell content slot. Config: group { name } - members { action, rows: { kind:"group"|"identity", label, subject }[] } action = remove-member endpoint + members { action, rows: { kind:"group"|"user", label, subject }[] } action = remove-member endpoint add { action, options: {label,value}[] } action = add-member endpoint del { action } delete the whole group + canWrite? false (a `groups:read` holder) drops the add, remove and delete forms + permissions? PermissionPicker; when set, the group's permission picker is rendered csrfToken, error? %><% const group = locals.group; diff --git a/examples/plugins/scheduling/plugin.ts b/examples/plugins/scheduling/plugin.ts index 1533963..29f6a8a 100644 --- a/examples/plugins/scheduling/plugin.ts +++ b/examples/plugins/scheduling/plugin.ts @@ -34,7 +34,7 @@ export default definePlugin({ public: true, // the header gates nothing; each child names its own gate, and an empty header is dropped }], - // Roles this plugin introduces (docs + Keto seeding). Namespaced `:`. + // Permissions this plugin declares (docs + Keto seeding), named `:`. permissions: [ { description: "View shifts", name: READ }, { description: "Create and edit shifts", name: WRITE }, diff --git a/public/css/styles.css b/public/css/styles.css index eddc972..c123361 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -20,6 +20,10 @@ resolution is unreliable across engines. color-scheme is still set as a hint for native widgets (date picker, scrollbars, selects). + Footgun: a token value a floor engine can't parse (a function or unit it lacks) + is invalid at computed-value time, so every declaration reading it through + var() computes as `unset`; a fallback declared before it doesn't help. + src/test-tooling/stylesheet-floor.ts fails its test on one. ------------------------------------------------------------- */ :root { /* ---- raw LIGHT palette ---- */ diff --git a/src/auth/bootstrap.ts b/src/auth/bootstrap.ts index 5aeed8a..b9cca48 100644 --- a/src/auth/bootstrap.ts +++ b/src/auth/bootstrap.ts @@ -1,8 +1,9 @@ // One-command bootstrap (the MVP bar). One-shot compose service: runs after // kratos+keto are healthy (web waits on it), idempotent on every `docker compose up`: // 1. generate the JWKS signing key if absent (committed dev key makes this a safety net); -// 2. seed a demo admin (admin@plainpages.local / admin) in Kratos; -// 3. grant it its permissions in Keto so menu/permission checks resolve out of the box — every +// 2. create a database and login role for each discovered plugin that declares storage; +// 3. seed a demo admin (admin@plainpages.local / admin) in Kratos; +// 4. grant it its permissions in Keto so menu/permission checks resolve out of the box — every // discovered plugin's declared permission names (plus any ADMIN_PERMISSIONS), so a dropped-in // plugin is usable by the demo admin with no host config edit (the host stays plugin-agnostic). // Then prints a first-run banner; fails loud on any unexpected upstream error. diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts index ca211ed..0583f0d 100644 --- a/src/auth/jwt.ts +++ b/src/auth/jwt.ts @@ -4,8 +4,9 @@ import type { JsonWebKey } from "./jwks.ts"; // JWS signature verification with the Node stdlib — no `jose`/JWT dep: // `createPublicKey({format:"jwk"})` imports a JWK and verifies the RS*/ES* signatures -// the Kratos tokenizer produces (see AGENTS.md). Signature only — adds claim checks -// (exp/iss/aud, clock skew), JWKS-by-`kid` fetch/cache/rotation, and `token` bounds. +// the Kratos tokenizer produces (see AGENTS.md). Signature only: the caller +// (`jwt-middleware.ts`) adds claim checks (exp/iss/aud, clock skew) and picks the key by `kid` +// from the cached JWKS (`jwks.ts` fetches and rotates it). // JOSE `alg` → Node verify parameters. ES* signatures are raw r‖s (IEEE P1363), not DER. // Extend this map to widen support. Security invariant: never add `HS*`/`none` — this map diff --git a/src/auth/login.ts b/src/auth/login.ts index d24849b..623d15c 100644 --- a/src/auth/login.ts +++ b/src/auth/login.ts @@ -40,7 +40,7 @@ export interface CompletedLogin { // The coarse permissions a user holds — directly (`Permission:#granted@user:`) or transitively via a // group granted the permission. Enumerates the defined permissions (the distinct objects in the Permission // namespace) and asks Keto to check each grant, so a permission granted to a group reaches the JWT — -// matching the OPL model and the admin "Effective access" view. At login/refresh only, never per +// matching the OPL model. At login/refresh only, never per // request; permission count is small, so the per-permission checks are cheap and run in parallel. export async function readPermissions(keto: KetoClient, userId: string): Promise { const subject_id = `user:${userId}`; diff --git a/src/plugin-host/router.ts b/src/plugin-host/router.ts index 8ac5c70..5ad681e 100644 --- a/src/plugin-host/router.ts +++ b/src/plugin-host/router.ts @@ -1,7 +1,8 @@ // Router: pure core mapping method + pathname → a discovered plugin route. I/O-free; // app.ts is the shell (build context, gate, call handler, render RouteResult). A route mounts at // `/` + its path (fullPath, shared with conflict detection); `:name` segments → path params. -// Specificity: a literal segment beats a `:param` (/users/new wins /users/:id), order-independent. +// Specificity: the route with fewer `:param` segments wins (/users/new beats /users/:id). A tie, such +// as /a/:x/b against /a/b/:y, falls to discovery order. import { fullPath, type Plugin, type Route } from "./plugin.ts"; diff --git a/src/plugin-host/system.ts b/src/plugin-host/system.ts index 3ce0631..db486c0 100644 --- a/src/plugin-host/system.ts +++ b/src/plugin-host/system.ts @@ -1,5 +1,5 @@ -// System capabilities: privileged host services a first-party/system plugin (the built-in admin -// screens are the reference consumer) needs but an ordinary domain plugin does not — the Ory admin +// System capabilities: privileged host services a first-party/system plugin (the `auth-admin` +// example plugin is the reference consumer) needs but an ordinary domain plugin does not — the Ory admin // clients and the instant-revoke hook. Exposed on ctx.system and re-exported via @plainpages/plugin-api. // // Every field is optional: it is present only when the host wired that dependency (Ory configured, diff --git a/src/ui/chrome.ts b/src/ui/chrome.ts index 68ca09a..41c2ff0 100644 --- a/src/ui/chrome.ts +++ b/src/ui/chrome.ts @@ -66,7 +66,7 @@ export function buildPluginChrome(opts: ChromeOptions): PageChrome { const nav = composeNav(fragments, opts.menu.override, opts.user ?? null, t); if (opts.currentPath) { // Mark by the *best* (longest) href that is the path or a parent of it, so a sub-path like - // /admin/users/new marks the Users base leaf (/admin/users) and the dashboard marks Dashboard. + // /auth-admin/users/new marks the Users base leaf (/auth-admin/users) and the dashboard marks Dashboard. // Marked before the locale rides along, so an href still matches the plain request path. const target = bestHref(nav, opts.currentPath); if (target) markCurrent(nav, target); @@ -140,7 +140,7 @@ function carryLocaleInto(nodes: NavNode[], carryLocale: (href: string) => string } // The href of the leaf that owns `path`: an exact match, else the longest href that is a parent of -// it (href + "/" prefixes path), so /admin/users/123 resolves to the /admin/users leaf. "/" never +// it (href + "/" prefixes path), so /auth-admin/users/123 resolves to the /auth-admin/users leaf. "/" never // counts as a parent (it would own everything). Returns undefined when nothing matches. function bestHref(nodes: NavNode[], path: string): string | undefined { let best: string | undefined; -- 2.52.0 From 1773c2737fec8aab052fa78fdad819b3a24f0706 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 9 Nov 2026 00:14:53 +0100 Subject: [PATCH 2/4] =?UTF-8?q?Answer=20product-owner=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=201=20=F0=9F=9F=A1=20(0=20misread);=20list=20plug?= =?UTF-8?q?in-settings=20among=20the=20Admin=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- examples/plugins/auth-admin/README.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6435fef..6c72473 100644 --- a/README.md +++ b/README.md @@ -347,8 +347,8 @@ curl -X PUT http://keto:4467/admin/relation-tuples -H 'content-type: application **A permission's name is authored in plugin code; only its *grants* live in Keto.** A plugin declares the permissions it gates on (`permissions:` in the manifest) and the host collects them into one catalog — `ctx.declaredPermissions` — which is the fixed list the admin screens offer. -Nothing in a GUI invents a name; a tuple naming something no installed plugin declares is not -offered by the admin screens, though a plugin may still gate on it. +Nothing in a GUI invents a name. A grant of a name that no installed plugin declares still reaches +the user's JWT, so a plugin may gate on that name without declaring it. A change takes effect on the user's **next login or JWT re-mint** (~10 min) — see [Instant revoke](#instant-revoke-the-optional-denylist) when you need it sooner. diff --git a/examples/plugins/auth-admin/README.md b/examples/plugins/auth-admin/README.md index a33b48e..a5c0135 100644 --- a/examples/plugins/auth-admin/README.md +++ b/examples/plugins/auth-admin/README.md @@ -35,9 +35,9 @@ screen degrades to a themed 503 rather than crashing. Everything else is an ordi folder-discovered, gated per route by its screen's `:` permission, rendering the core building blocks in `views/`. -Each screen is its own resource — `users`, `groups`, `oauth2-clients` — split into `:read` and -`:write`, so a helpdesk account can be given `users:read` alone. Holding none of the six hides the -Admin section entirely. +Each screen is its own resource — `users`, `groups`, `oauth2-clients`, `plugin-settings` — split +into `:read` and `:write` (`plugin-settings` has only `:read`), so a helpdesk account can be given +`users:read` alone. Holding none of the seven hides the Admin section entirely. There is **no Permissions screen**. Permission names are declared in plugin code, not created in a GUI, so the host's catalog (`ctx.declaredPermissions`) is the fixed list — and holding one is a -- 2.52.0 From 8e3270d4796aa8d01c5fbc2bf7fb53be7c2430f4 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 9 Nov 2026 00:14:53 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Answer=20prose=20round=201:=200=20?= =?UTF-8?q?=F0=9F=94=B4,=205=20=F0=9F=9F=A1=20(0=20misread);=20cut=20the?= =?UTF-8?q?=20duplicated=20specificity=20line=20and=20reflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compose.yml | 5 +++-- examples/plugins/auth-admin/admin-groups.ts | 3 ++- examples/plugins/scheduling/plugin.ts | 2 +- public/css/styles.css | 9 +++++---- src/auth/jwt.ts | 5 ++--- src/auth/login.ts | 4 ++-- src/plugin-host/router.ts | 2 -- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/compose.yml b/compose.yml index 93a5b3e..9565337 100644 --- a/compose.yml +++ b/compose.yml @@ -165,8 +165,9 @@ services: # Ory Hydra — OAuth2/OIDC provider (other apps log in *through* plainpages; README). # DSN is its own `hydra` DB (init.sql); config in ory/hydra/hydra.yml. web implements the - # login challenge at /oauth2/login and the consent challenge at /oauth2/consent. Dev permits the http issuer via --dev - # (compose.override.yml); prod sets an https issuer via env (URLS_SELF_ISSUER). + # login challenge at /oauth2/login and the consent challenge at /oauth2/consent. Dev permits the + # http issuer via --dev (compose.override.yml); prod sets an https issuer via env + # (URLS_SELF_ISSUER). hydra-migrate: image: oryd/hydra:v26.2.0 depends_on: diff --git a/examples/plugins/auth-admin/admin-groups.ts b/examples/plugins/auth-admin/admin-groups.ts index 9e255db..9cec05c 100644 --- a/examples/plugins/auth-admin/admin-groups.ts +++ b/examples/plugins/auth-admin/admin-groups.ts @@ -15,7 +15,8 @@ const GROUP_NS = "Group"; const MEMBERS = "members"; const DEFAULT_PAGE_SIZE = 25; const PAGE_SIZES = [25, 50, 100]; -// One Kratos `listIdentities` page of candidate users is fetched for the member pickers (mirrors admin-users). +// One Kratos `listIdentities` page of candidate users is fetched for the member pickers (mirrors +// admin-users). const LIST_FETCH_SIZE = 250; const GROUP_NAME = /^[a-z0-9][a-z0-9_-]*$/; // URL-safe; doubles as the path segment const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; // a Kratos identity id diff --git a/examples/plugins/scheduling/plugin.ts b/examples/plugins/scheduling/plugin.ts index 29f6a8a..573618b 100644 --- a/examples/plugins/scheduling/plugin.ts +++ b/examples/plugins/scheduling/plugin.ts @@ -34,7 +34,7 @@ export default definePlugin({ public: true, // the header gates nothing; each child names its own gate, and an empty header is dropped }], - // Permissions this plugin declares (docs + Keto seeding), named `:`. + // Permissions this plugin declares: the admin picker offers them and bootstrap grants them to the demo admin. permissions: [ { description: "View shifts", name: READ }, { description: "Create and edit shifts", name: WRITE }, diff --git a/public/css/styles.css b/public/css/styles.css index c123361..eaccaca 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -20,10 +20,11 @@ resolution is unreliable across engines. color-scheme is still set as a hint for native widgets (date picker, scrollbars, selects). - Footgun: a token value a floor engine can't parse (a function or unit it lacks) - is invalid at computed-value time, so every declaration reading it through - var() computes as `unset`; a fallback declared before it doesn't help. - src/test-tooling/stylesheet-floor.ts fails its test on one. + Footgun: a token value an engine of the oldest browser floor can't parse (a + function or unit it lacks) is invalid at computed-value time, so every + declaration reading it through var() computes as `unset`; a fallback declared + before it doesn't help. The test in src/test-tooling/stylesheet-floor.ts + fails on such a value. ------------------------------------------------------------- */ :root { /* ---- raw LIGHT palette ---- */ diff --git a/src/auth/jwt.ts b/src/auth/jwt.ts index 0583f0d..cc227e0 100644 --- a/src/auth/jwt.ts +++ b/src/auth/jwt.ts @@ -4,9 +4,8 @@ import type { JsonWebKey } from "./jwks.ts"; // JWS signature verification with the Node stdlib — no `jose`/JWT dep: // `createPublicKey({format:"jwk"})` imports a JWK and verifies the RS*/ES* signatures -// the Kratos tokenizer produces (see AGENTS.md). Signature only: the caller -// (`jwt-middleware.ts`) adds claim checks (exp/iss/aud, clock skew) and picks the key by `kid` -// from the cached JWKS (`jwks.ts` fetches and rotates it). +// the Kratos tokenizer produces. Signature only: `jwt-middleware.ts` +// picks the key by `kid` and checks the claims. // JOSE `alg` → Node verify parameters. ES* signatures are raw r‖s (IEEE P1363), not DER. // Extend this map to widen support. Security invariant: never add `HS*`/`none` — this map diff --git a/src/auth/login.ts b/src/auth/login.ts index 623d15c..847be26 100644 --- a/src/auth/login.ts +++ b/src/auth/login.ts @@ -40,8 +40,8 @@ export interface CompletedLogin { // The coarse permissions a user holds — directly (`Permission:#granted@user:`) or transitively via a // group granted the permission. Enumerates the defined permissions (the distinct objects in the Permission // namespace) and asks Keto to check each grant, so a permission granted to a group reaches the JWT — -// matching the OPL model. At login/refresh only, never per -// request; permission count is small, so the per-permission checks are cheap and run in parallel. +// matching the OPL model. At login/refresh only, never per request; permission count is small, so the +// per-permission checks are cheap and run in parallel. export async function readPermissions(keto: KetoClient, userId: string): Promise { const subject_id = `user:${userId}`; const names = new Set(); diff --git a/src/plugin-host/router.ts b/src/plugin-host/router.ts index 5ad681e..d3446d9 100644 --- a/src/plugin-host/router.ts +++ b/src/plugin-host/router.ts @@ -1,8 +1,6 @@ // Router: pure core mapping method + pathname → a discovered plugin route. I/O-free; // app.ts is the shell (build context, gate, call handler, render RouteResult). A route mounts at // `/` + its path (fullPath, shared with conflict detection); `:name` segments → path params. -// Specificity: the route with fewer `:param` segments wins (/users/new beats /users/:id). A tie, such -// as /a/:x/b against /a/b/:y, falls to discovery order. import { fullPath, type Plugin, type Route } from "./plugin.ts"; -- 2.52.0 From 8b88cd4a40dad75882b5a53defef45f1e99b279b Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 9 Nov 2026 00:15:23 +0100 Subject: [PATCH 4/4] Remove the shipped item 63 from the plan --- todo.md | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/todo.md b/todo.md index cb69dce..2eacff0 100644 --- a/todo.md +++ b/todo.md @@ -12,7 +12,6 @@ | ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score | |---|---|---|---|---|---|---|---|---|---|---|---| -| 63 | 0.5.0 | | | **Correct the stale and false comments and docs the comprehension seats flagged, so each states what the code does today.** | 2 | 2 | 1 | 3 | 3 | code health by 3, 9 by 2 | 15.4 | | 68 | 0.5.0 | | | **Make `serveHome` and `serveDashboard` return their result to the request pipeline, as every other stage does, instead of sending it themselves.** | 2 | 1 | 3 | 4 | 3 | code health by 3 | 13.5 | | 66 | 0.5.0 | | | **Make `src/auth/bootstrap.ts` read its settings through `src/config.ts` and call Kratos and Keto through their client modules.** | 2 | 1 | 3 | 4 | 4 | code health by 3 | 9.0 | | 70 | 0.5.0 | | | **Give the duplicated paging loop, Link-header parser and `FieldConfig` type one home each, and rename `buildPluginChrome` to `buildPageChrome`.** | 2 | 1 | 1 | 2 | 3 | code health by 2 | 9.0 | @@ -30,37 +29,6 @@ ## Details -### 63. Correct the stale and false comments and docs the comprehension seats flagged, so each states what the code does today. - -Prose only; no behaviour changes. Make each listed place easier to read, since the seats named -most of them. - -- `src/auth/login.ts:43` and `README.md` ~L1367 name an admin "Effective access" view that doesn't - exist. -- `src/plugin-host/router.ts:4` header says specificity is order-independent; `matchRoute` sorts by - `:param` count only, so a tie (`/a/:x/b` against `/a/b/:y`) falls to discovery order. -- `ci.sh:9` ("users/groups/roles") and `examples/plugins/scheduling/plugin.ts:37` ("Roles", and - `:` where the rule is `:`) use words the project doesn't have. -- `src/plugin-host/system.ts:1-2` calls "the built-in admin screens" the reference consumer; they - are the `auth-admin` example plugin. -- `examples/plugins/auth-admin/admin-groups.ts:18` says a Keto page; the call is Kratos - `listIdentities`. -- `compose.yml` ~L166 says consent comes next; `src/auth/oauth-consent.ts` implements it. -- `src/auth/jwt.ts:6-7`: "Signature only — adds claim checks…" lacks its subject (the caller, - `jwt-middleware.ts`, adds them). -- `src/ui/chrome.ts` ~L69 and ~L143 use `/admin/users` as example paths; admin lives at - `/auth-admin/users`. -- `README.md` ~L350 says a tuple naming an undeclared permission "gates nothing"; declaring stays - optional, so a plugin may gate on it. `examples/plugins/auth-admin/README.md:49` says six - permissions; `plugin.ts` declares seven. -- `examples/plugins/auth-admin/views/partials/group-detail-body.ejs:2-7` documents member - `kind:"identity"` where the model says `"user"`, and omits the `canWrite` and `permissions` - locals. -- `src/auth/bootstrap.ts:1-8` header lists the JWKS, admin and Keto steps, not the plugin-storage - step. -- The custom-property `unset` footgun is stated only in `src/test-tooling/stylesheet-floor.ts:10-12`; - say it beside the tokens at `public/css/styles.css:9-23`, where an author writes one. - ### 68. Make `serveHome` and `serveDashboard` return their result to the request pipeline, as every other stage does, instead of sending it themselves. In `src/http/app.ts`, both (~L143-169) call `sendResult` with the owning plugin's renderer and -- 2.52.0