Convert /-/actions.json from top-level array to object

/-/actions.json now returns {"ok": true, "actions": [...]} instead of a
bare JSON array, so the response can grow additional keys without a
breaking change. The debug_actions.html template reads data.actions,
and the endpoint is now documented in docs/introspection.rst.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrHZSypDfMnym1tM5XJAFZ
This commit is contained in:
Claude 2026-07-04 13:46:26 +00:00
commit 23ccdaeffc
No known key found for this signature in database
7 changed files with 56 additions and 18 deletions

View file

@ -2588,7 +2588,7 @@ class Datasette:
JsonDataView.as_view(
self,
"actions.json",
self._actions,
lambda: {"actions": self._actions()},
template="debug_actions.html",
permission="permissions-debug",
),

View file

@ -9,7 +9,7 @@
{% include "_permissions_debug_tabs.html" %}
<p style="margin-bottom: 2em;">
This Datasette instance has registered {{ data|length }} action{{ data|length != 1 and "s" or "" }}.
This Datasette instance has registered {{ data.actions|length }} action{{ data.actions|length != 1 and "s" or "" }}.
Actions are used by the permission system to control access to different features.
</p>
@ -26,7 +26,7 @@
</tr>
</thead>
<tbody>
{% for action in data %}
{% for action in data.actions %}
<tr>
<td><strong>{{ action.name }}</strong></td>
<td>{% if action.abbr %}<code>{{ action.abbr }}</code>{% endif %}</td>

View file

@ -155,6 +155,30 @@ Shows currently attached databases. `Databases example <https://latest.datasette
]
}
.. _JsonDataView_actions:
/-/actions
----------
Shows all actions registered with the permission system, including those added by plugins. Requires the ``permissions-debug`` permission.
.. code-block:: json
{
"ok": true,
"actions": [
{
"name": "view-instance",
"abbr": "vi",
"description": "View Datasette instance",
"takes_parent": false,
"takes_child": false,
"resource_class": null,
"also_requires": null
}
]
}
.. _JumpView:
/-/jump

View file

@ -48,9 +48,9 @@ directory: every claim below is based on the route table in `datasette/app.py`
Every JSON endpoint that returns an object includes `"ok": true` on
success. `JsonDataView` injects it automatically for dict responses
(views/special.py); the homepage, jump, schema, permission-debug and
autocomplete views add it explicitly. The remaining top-level-array
endpoints (`/-/plugins`, `/-/databases`, `/-/actions`) are being converted
to objects.
autocomplete views add it explicitly. The former top-level-array endpoints
(`/-/plugins`, `/-/databases`, `/-/actions`) now return objects wrapping
their arrays (`{"ok": true, "plugins": [...]}` etc.).
### Error shape (canonical)
@ -276,9 +276,9 @@ Response: `{"ok": true, "actor": {...}}` or `{"ok": true, "actor": null}` (app.p
app.py:2580-2589. Permission **`permissions-debug`**. No parameters.
Response: a JSON array, sorted by name, of `{"name", "abbr", "description",
"takes_parent", "takes_child", "resource_class", "also_requires"}`
(app.py:2290-2304).
Response: `{"ok": true, "actions": [...]}` — each action is
`{"name", "abbr", "description", "takes_parent", "takes_child",
"resource_class", "also_requires"}`, sorted by name (app.py:2290-2304).
### GET /-/auth-token

View file

@ -102,15 +102,18 @@ key — see §2.)
---
## 2. Success envelope: `ok` is not universal, arrays are not extensible (P1/P2) — ✅ PARTIALLY IMPLEMENTED
## 2. Success envelope: `ok` is not universal, arrays are not extensible (P1/P2) — ✅ IMPLEMENTED (§2a-2c open)
> **Status:** recommendation 2 and 3 are implemented — every JSON-object
> **Status:** recommendations 1-3 are implemented. Every JSON-object
> success response now includes `"ok": true` (`JsonDataView` injects it for
> dict responses; homepage, jump, schema, permission-debug and autocomplete
> views set it explicitly; covered by `tests/test_success_envelope.py`).
> Recommendation 1 (wrapping the `/-/plugins`, `/-/databases`, `/-/actions`
> top-level arrays in objects) is being landed as separate per-endpoint
> commits. §2a/2b/2c remain open.
> views set it explicitly), and the three top-level-array endpoints now
> return objects: `/-/plugins``{"ok": true, "plugins": [...]}`,
> `/-/databases``{"ok": true, "databases": [...]}`, `/-/actions`
> `{"ok": true, "actions": [...]}`. Covered by
> `tests/test_success_envelope.py`. The sub-findings §2a (collection
> representations), §2b (`_extra`/`_shape` coverage) and §2c (count
> truncation) remain open.
Endpoints disagree about the success envelope:
@ -372,8 +375,8 @@ Two details make tiering urgent rather than optional:
2. `Forbidden` → JSON 403 for JSON requests (§1a).
3. No `ok: false` with HTTP 200 (§1b: `_shape=object`, write canned-query
SQL errors).
4. Wrap `/-/plugins`, `/-/databases`, `/-/actions` top-level arrays in
objects (§2).
4. ~~Wrap `/-/plugins`, `/-/databases`, `/-/actions` top-level arrays in
objects (§2).~~ ✅ Done.
5. Filter `/-/databases.json` by `view-database` or gate it behind
`permissions-debug` (§6).
6. 401 (not silent-anonymous) for invalid/expired bearer tokens (§1c).

View file

@ -579,7 +579,7 @@ async def test_actions_json(ds_client):
try:
ds_client.ds.root_enabled = True
response = await ds_client.get("/-/actions.json", actor={"id": "root"})
data = response.json()
data = response.json()["actions"]
finally:
ds_client.ds.root_enabled = original_root_enabled
assert isinstance(data, list)

View file

@ -101,3 +101,14 @@ async def test_databases_json_is_object(ds_client):
assert data["ok"] is True
assert isinstance(data["databases"], list)
assert "fixtures" in {db["name"] for db in data["databases"]}
@pytest.mark.asyncio
async def test_actions_json_is_object(ds_envelope):
response = await ds_envelope.client.get("/-/actions.json", actor={"id": "root"})
assert response.status_code == 200
data = response.json()
assert set(data.keys()) == {"ok", "actions"}
assert data["ok"] is True
assert isinstance(data["actions"], list)
assert "view-instance" in {action["name"] for action in data["actions"]}