Answer prose round 1: 1 🔴, 4 🟡 (0 misread); correct the older changelog entries, keep each fact in one home, rewrap the Keto header
CI / full-gate (push) Successful in 4m37s

This commit is contained in:
2026-11-08 22:15:54 +01:00
parent 63c90ea3d6
commit 2b19ed27d2
4 changed files with 10 additions and 16 deletions
+8 -10
View File
@@ -16,18 +16,17 @@ The host's contract version is now 0.5.0, so discovery refuses a plugin whose `a
`transactTuples({ delete, insert })` sends both lists to Keto in one `PATCH /admin/relation-tuples`, `transactTuples({ delete, insert })` sends both lists to Keto in one `PATCH /admin/relation-tuples`,
so all of them apply or none does. A `KetoClient` you build by hand, such as a test stub, must add the so all of them apply or none does. A `KetoClient` you build by hand, such as a test stub, must add the
method. The admin example saves a user's or group's permissions through it: a Keto failure now method. The admin example saves a user's or group's permissions through it, so a Keto failure leaves
changes nothing and answers with the error page, instead of a "stopped part-way" report. Its every grant as it was and answers with the error page.
catalogs lose `admin.grants.partial`.
### The admin example lists a user's or group's grants that no installed plugin declares, with a Remove ### The admin example lists a user's or group's grants that no installed plugin declares, with a Remove
A direct grant of a name no installed plugin declares, such as one left by an uninstalled plugin, A direct grant of a name no installed plugin declares, such as one left by an uninstalled plugin,
used to stay invisible and unchangeable, and reinstalling the plugin silently reactivated it. The used to stay invisible and unchangeable, and reinstalling the plugin silently reactivated it. The
user and group pages now list these apart from the checkboxes, each with a Remove button (hidden from user and group pages now list these apart from the checkboxes, each with a Remove button (hidden from
a `:read`-only holder). The example's catalogs gain `admin.grants.remove`, `admin.grants.undeclaredHint` a `:read`-only holder). The example's catalogs gain `admin.grants.remove`, `admin.grants.removed`,
and `admin.grants.undeclaredLegend`, and its routes gain `POST /auth-admin/users/:id/permissions/remove` `admin.grants.undeclaredHint` and `admin.grants.undeclaredLegend`, and its routes gain
and `POST /auth-admin/groups/:name/permissions/remove`. `POST /auth-admin/users/:id/permissions/remove` and `POST /auth-admin/groups/:name/permissions/remove`.
### An operator can hide or rename a plugin's profile-menu entry ### An operator can hide or rename a plugin's profile-menu entry
@@ -174,14 +173,13 @@ page's `baseline` field.
### The admin example confirms a permission save and reports a partial one ### The admin example confirms a permission save and reports a partial one
A save now lands on the page with "Permissions saved." If the permission service fails midway, the A save now lands on the page with "Permissions saved." On a user, a changed grant reaches the user's
page says the save stopped part-way instead of showing the generic error page. On a user, the live sessions at once when the revocation denylist is on.
changes that landed still reach the user's live sessions at once when the revocation denylist is on.
### A read-only holder sees View, not Edit, on the admin example's users list ### A read-only holder sees View, not Edit, on the admin example's users list
A holder of `users:read` without `users:write` gets a "View" row action with an eye icon. The admin A holder of `users:read` without `users:write` gets a "View" row action with an eye icon. The admin
example's catalogs gain `admin.grants.partial`, `admin.grants.saved`, `admin.grants.stale` and example's catalogs gain `admin.grants.saved`, `admin.grants.stale` and
`admin.users.view`, so a translated catalog mounted under `locales/plugins/auth-admin/` must add them. `admin.users.view`, so a translated catalog mounted under `locales/plugins/auth-admin/` must add them.
### Every page carries a smaller icon sprite ### Every page carries a smaller icon sprite
@@ -126,8 +126,6 @@ test("applyGrants sends the whole change as one Keto transaction, and a failure
await assert.rejects(applyGrants(keto(true), userSubject("u1"), diff), /boom/); await assert.rejects(applyGrants(keto(true), userSubject("u1"), diff), /boom/);
}); });
// grantDiff never touches a held name nobody declares, so the picker lists them apart and offers a
// Remove for each. A reader is told what is held but offered no button.
test("buildPermissionPicker lists direct grants no plugin declares, with a remove action only for a writer", () => { test("buildPermissionPicker lists direct grants no plugin declares, with a remove action only for a writer", () => {
const direct = ["zed:read", "users:read", "old:write"]; const direct = ["zed:read", "users:read", "old:write"];
const picker = buildPermissionPicker({ action: "/auth-admin/users/u1/permissions", declared, direct }); const picker = buildPermissionPicker({ action: "/auth-admin/users/u1/permissions", declared, direct });
@@ -9,8 +9,6 @@
Locals: csrfToken, permissions ({ action, baseline, choices, empty, error, field, hint, inheritedNote, legend, notice, pending, readOnly, submit, undeclared }). Locals: csrfToken, permissions ({ action, baseline, choices, empty, error, field, hint, inheritedNote, legend, notice, pending, readOnly, submit, undeclared }).
`undeclared` lists direct grants of names no plugin declares, outside the form above because grantDiff
never revokes one. Each has its own Remove form, and a reader gets the names without the button.
%> %>
<section class="form-card" aria-labelledby="permissions-h"> <section class="form-card" aria-labelledby="permissions-h">
<h2 class="card-title" id="permissions-h"><%= permissions.legend %></h2> <h2 class="card-title" id="permissions-h"><%= permissions.legend %></h2>
+2 -2
View File
@@ -1,7 +1,7 @@
// Keto client: typed `fetch` wrappers over Ory Keto's relation-tuple APIs — // Keto client: typed `fetch` wrappers over Ory Keto's relation-tuple APIs —
// `check` a permission, `listRelations`/`expand` to inspect them (read API), `writeTuple`/ // `check` a permission, `listRelations`/`expand` to inspect them (read API), `writeTuple`/
// `deleteTuple`/`transactTuples` to grant/revoke them (write API). Built-in `fetch` only, no SDK dep (AGENTS.md); // `deleteTuple`/`transactTuples` to grant/revoke them (write API). Built-in `fetch` only, no SDK dep
// `fetchImpl`-injectable like the kratos clients. Read/write split onto the two ports config.ts // (AGENTS.md); `fetchImpl`-injectable like the kratos clients. Read/write split onto the two ports config.ts
// targets (ketoReadUrl 4466 / ketoWriteUrl 4467). // targets (ketoReadUrl 4466 / ketoWriteUrl 4467).
// A subject set: a relation on another object (e.g. Group:eng#members), resolved // A subject set: a relation on another object (e.g. Group:eng#members), resolved