Skip to content

docs(rest,qa): record the deliberate absence of a REST action-invoke route (#7680) - #7913

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-7680-package-capability-doctrine
Aug 12, 2026
Merged

docs(rest,qa): record the deliberate absence of a REST action-invoke route (#7680)#7913
hotlong merged 1 commit into
mainfrom
claude/issue-7680-package-capability-doctrine

Conversation

@hotlong

@hotlong hotlong commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Fixes #7680

Executes the maintainer ruling of 2026-08-12 (comment 5261755580) as written: 记录口径,不建路由 — record the doctrine, do not build the route. No REST action-invoke route is added here; the ruling declines to build one, and adding it would reverse the decision.

Premise re-verified on current origin/main (e3a6f6e)

check result
data-actions family in rest-route-ledger.ts still only clone / import / import-jobs / export — no …/actions/<name> invoke row
the four probe spellings in packages/rest/src absent (/api/v1/actions/:n, /api/v1/action/:n, /api/v1/objects/:o/actions/:n, /api/v1/data/:o/actions/:n)
only actions-shaped REST row GET /api/v1/approvals/requests/:id/actions — an approvals audit trail, unrelated
enforcement point actionPermissionError @ packages/runtime/src/action-execution.ts:383 (ADR-0066 D4), reached from runtime/src/domains/actions.ts (dispatcher /actionsql.executeAction) and runtime/src/domains/mcp.ts:433 (MCP run_action)

Premise holds. Line numbers were re-derived by text search, not trusted from the issue.

One correction to the issue's framing, which the ledger comment now records. The issue reads as though nothing requires a package capability. Four shipped type:'script' actions do — showcase_zoo_perm_held / _missing / _and / _empty in examples/app-showcase/src/ui/actions/predicate-matrix.action.ts — and being script actions on the zoo object, the platform action path can invoke them. What they lack is a REST door, not a requiring resource. That is what makes the re-pointed clause measurable against real fixtures rather than the manage_metadata fallback the QA run had to use.

1 — packages/rest/src/rest-route-ledger.ts, data-actions block

36 added lines, all comment; zero code lines changed (git diff filtered for non-comment +/- lines in the TS file returns empty). It records that the missing invoke route is deliberate, names the platform paths that do enforce ADR-0066 D4 and where they are ledgered (packages/runtime/src/route-ledger.ts, not this file), and carries the ruling's forward-looking clause verbatim in substance:

IF YOU ARE THE AUTHOR ADDING AN ACTION-INVOKE ROUTE HERE: server-side requiredPermissions enforcement is a DAY-ONE requirement of that route, not a follow-up you file behind it. […] a REST invoke door that ships without calling the same gate resurrects precisely the client-side fail-open #3923 reported — the action greyed out in the UI and wide open on the wire, which is the worst of both, because the UI's refusal reads as proof the rule is being kept.

It also disambiguates a live trap for that author: actionPermissionError's own docstring says it is single-sourced "so the REST /actions/... route and the MCP run_action bridge enforce the SAME declaration" — that "REST" is the dispatcher's HTTP surface, not a route @objectstack/rest mounts. Read without the split, that docstring reads as a promise this package already keeps.

2 — access-security.capability-declaration-lifecycle, revision 1 → 2

Re-pointed, not deleted and not softened.

Before (acceptance clause 2):

clause: the three-way resolves by NAME: a resource requiring the capability is denied for a member whose granted permission sets do not carry it, and permitted for one that does — both sides
verify: 403 for the non-granted member, 2xx for the OpsPermissionSet holder, on a resource carrying requiredPermissions:['showcase.export_data']
evidence: the two responses (scratch resource if none ships — record which)

After:

clause: the three-way resolves by NAME on the surface that actually enforces it — the PLATFORM action path (ADR-0066 D4 actionPermissionError, reached via the dispatcher /actions domain, the MCP run_action bridge, or ql.executeAction): invoking an action that REQUIREs the capability is refused for a member whose granted sets do not carry it and executes for one that does, the refusal follows a grant WITHDRAWN from the permission set, and execution returns when it is re-granted — grant / deny / regrant, all three legs
verify: on the platform surface this build exposes (recorded per steps): showcase_zoo_perm_held executes for the OpsPermissionSet holder and is refused for a non-granted member with the actionPermissionError string naming the action and the missing capability; after withdrawing showcase.export_data from OpsPermissionSet the SAME holder is refused, and after restoring it executes again. Cross-checks on the same surface: showcase_zoo_perm_missing (showcase.restricted_ops, granted to nobody) is refused for every persona, and showcase_zoo_perm_empty (requiredPermissions:[]) executes for every persona. This clause is NOT scored against @objectstack/rest — it mounts no action-invoke route by design (#7680)
evidence: the invocation results for all three legs (grant → deny → regrant) plus the two cross-checks, each with the refusal text or the executed result, and a note of which platform surface was driven

The old clause was unrunnable, which is why the run fell back to a platform capability: it demanded a 403 from a surface with no door to knock on. The new one names four shipped fixtures and a surface that exists.

The re-pointed clause still has a failure mode — exactly what turns it red

Enumerated in negative so a runner cannot tick it on vibes:

  1. Fail-open — a non-granted member's invocation of showcase_zoo_perm_held succeeds. This is ADR-0066 D4 failing open, the server-side shape of Action.requiredPermissions 在 Console 端形同虚设:会话不下发 systemPermissions + 客户端 fail-open(17.0.0-rc.0) #3923. Also red: showcase_zoo_perm_missing executing for anyone (nobody is granted showcase.restricted_ops), or showcase_zoo_perm_and executing for a caller holding only showcase.export_data (the gate is an AND, not an OR).
  2. Stale grant — after showcase.export_data is withdrawn from OpsPermissionSet, the former holder still executes. The gate is reading a cached grant.
  3. Over-block — the OpsPermissionSet holder is refused, or showcase_zoo_perm_empty is refused for anyone. "Declares nothing" is not "denies everything".
  4. Mis-run — a REST action-invoke 404 is recorded as pass or fail. It is neither; that surface does not exist by design. The item says re-run on the platform surface.

Concretely: delete the if (missing.length === 0) return null; guard's inverse — i.e. make actionPermissionError return null unconditionally — and legs 1 and 2 go red. That is the property the item is pinning.

Supporting edits: variants gains a REGRANT leg; steps 3 splits into surface-identification + the three legs + the missing/empty cross-checks; traps gains dispatcher-vs-hono-route (RUNNER.md's own name for "route exists in unit tests, 404s on the real server" — the precise trap this card is about); knownGaps moves from "no shipped resource REQUIREs it" (false — four do) to "which platform surface the build exposes, recorded per run"; source gains the runtime enforcement files and this ledger block.

Verification

This is a comment + checklist-prose card, so there is no behaviour to pin and I have not invented a test that pretends otherwise. The TS diff is comment lines only. What was run:

gate result
node scripts/pm/dispatch-gates.mjs <changed paths> matched check:authz-resolver, check:meta-type-normalized
pnpm check:authz-resolver ✅ self-test + real-repo positive control
pnpm check:meta-type-normalized ✅ 19 files
pnpm check:nul-bytes ✅ 7236 files
pnpm check:platform-checklist ✅ 15 areas, 182 items — incl. the revision↔history invariant the rev-2 bump has to satisfy
pnpm --filter "@objectstack/rest..." build ✅ incl. DTS
packages/rest test suite ✅ 92 files, 1477 tests

lint.yml's full farm is left to CI, per dispatch.

Changeset: none — a comment/prose diff releases nothing. Needs the skip-changeset label rather than an empty changeset, which would strand the release.

Explicitly not done

  • ❌ No REST action-invoke route, in any spelling — the ruling declines to build one.
  • ❌ The platform-capability hijack refusal (WARN + skippedPlatform) and the boot-time Zod hard-reject are untouched; the ruling confirms both as expected.
  • ❌ No edits to rest-server.ts, runtime/src/domains/**, runtime/src/sandbox/**, packages/mcp/**, or content/docs/releases/**.

Generated by Claude Code

…route (#7680)

Per the maintainer ruling of 2026-08-12 on #7680: record the doctrine, do
not build the route.

`@objectstack/rest` mounts no action-invoke route, so there is no REST
surface on which an action's `requiredPermissions` could be enforced. A QA
probe (#7637) measured all four candidate spellings 404 and read the
`data-actions` family as clone / import / import-jobs / export. That is a
surface which does not exist, not a gate which fails open — but nothing in
the tree said so, and the checklist item scored against it as if it did.

Two records, no behaviour change (the TS diff is comment lines only):

- `rest-route-ledger.ts`, the `data-actions` block: state that the missing
  invoke route is deliberate, name the platform paths that DO enforce
  ADR-0066 D4 (`actionPermissionError` via the dispatcher `/actions`
  domain, MCP `run_action`, `ql.executeAction`) and note they are ledgered
  in `packages/runtime/src/route-ledger.ts` rather than here — including
  the trap that `actionPermissionError`'s own docstring says "the REST
  `/actions/...` route" and means the DISPATCHER's surface. Written for
  the next author of that route: if one ever ships for real demand,
  server-side `requiredPermissions` enforcement is a day-one requirement,
  not a follow-up, or #3923's client-side fail-open returns server-side.

- `access-security.capability-declaration-lifecycle` (rev 1 → 2): re-point
  the REQUIRE clause at the surface that enforces it. It asked for a 403
  from a REST resource carrying `requiredPermissions`; unrunnable as
  written, so the run fell back to a platform capability. Now grant / deny
  / regrant on the platform action path against the four shipped
  predicate-matrix specimens (held / missing / and / empty), with the
  fail-open, stale-grant, over-block and AND-vs-OR failure modes spelled
  out in `negative`, and a REST 404 explicitly scoring nothing either way.
  The clause is re-pointed, not softened: it still goes red if the gate
  breaks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECUjuBFnZHLAfkf2ZQh2rd
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 12, 2026 4:01am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/permissions/system-context.mdx (via packages/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)

3 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v17.mdx (via @objectstack/rest)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 12, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review August 12, 2026 04:27
@hotlong
hotlong added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit bebfc0c Aug 12, 2026
29 checks passed
@hotlong
hotlong deleted the claude/issue-7680-package-capability-doctrine branch August 12, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

capability-declaration-lifecycle: no shipped resource enforces a package capability server-side (no REST action-invoke route on this build)

2 participants