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`,
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
changes nothing and answers with the error page, instead of a "stopped part-way" report. Its
catalogs lose `admin.grants.partial`.
method. The admin example saves a user's or group's permissions through it, so a Keto failure leaves
every grant as it was and answers with the error page.
### 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,
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
a `:read`-only holder). The example's catalogs gain `admin.grants.remove`, `admin.grants.undeclaredHint`
and `admin.grants.undeclaredLegend`, and its routes gain `POST /auth-admin/users/:id/permissions/remove`
and `POST /auth-admin/groups/:name/permissions/remove`.
a `:read`-only holder). The example's catalogs gain `admin.grants.remove`, `admin.grants.removed`,
`admin.grants.undeclaredHint` and `admin.grants.undeclaredLegend`, and its routes gain
`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
@@ -174,14 +173,13 @@ page's `baseline` field.
### 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
page says the save stopped part-way instead of showing the generic error page. On a user, the
changes that landed still reach the user's live sessions at once when the revocation denylist is on.
A save now lands on the page with "Permissions saved." On a user, a changed grant reaches 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 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.
### 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/);
});
// 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", () => {
const direct = ["zed:read", "users:read", "old:write"];
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 }).
`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">
<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 —
// `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);
// `fetchImpl`-injectable like the kratos clients. Read/write split onto the two ports config.ts
// `deleteTuple`/`transactTuples` to grant/revoke them (write API). Built-in `fetch` only, no SDK dep
// (AGENTS.md); `fetchImpl`-injectable like the kratos clients. Read/write split onto the two ports config.ts
// targets (ketoReadUrl 4466 / ketoWriteUrl 4467).
// A subject set: a relation on another object (e.g. Group:eng#members), resolved