← 1.0.0 backlogs ISS-12 Done

The TypeScript client cannot present a grant, so it cannot reach a scoped operation

Type
Bug — a shipped feature one client cannot use
Found while
SAPE-21
Triage
Fix in 1.0.0
Status
Done
Version
1.0.0
Severity
High — an operation that declares a scope is uncallable from two of the three clients

Closed — and this page was wrong for longer than the bug lived

All three clients present a scope grant. Measured, each with a test of its own:

ClientWhereShape
Gosapedb.go:166Client.Present(scopes, grant) — attaches to the connection
PHPsrc/Client.php:89Client::present(array $scopes, string $grant)
TypeScriptsrc/client/index.ts:1186invoke takes a grant and sets body.grant = { scopes, sig }

The TypeScript client gained it in a commit whose subject line is "client: invoke can present a scope grant (ISS-12)". The PHP client gained it separately, and has a whole test file for it, GrantPresentationTest.php. The work was done. Nobody moved this page.

What the stale page cost, recorded because it is the useful part

A ticket is a record of what somebody found on a day. It is not a measurement of what is true now, and this one was read as though it were — twice in one sitting. First, a public documentation page was written saying Go was the only client that could present a grant. Then, while correcting that, a second wrong sentence was written saying TypeScript was the only one that could not. Both came from this page rather than from the code.

The learn site's own version of the claim carried its evidence inline — "measured: zero occurrences of 'grant' in either client's source" — which was true when it was written and false when it was read. A measurement with a date is honest. A measurement without one is a claim that ages silently, and the more precise it sounds the longer it survives.

Three pages on two sites carried it in seven places. All corrected.

What is still true from the original

Minting a grant is Server.Grant, inside the server package. No client mints one, and none should — that is the point of signing them with the server's own secret. Presenting is the client half, and that half is now complete everywhere.

ISS-11 is untouched by this: a grant still has no expiry and cannot be revoked short of rotating the server secret. The scheduling argument below turned out not to matter, because the clients were written before ISS-11 was settled rather than after — so if ISS-11 does change the signed message, three signers now have to move instead of one. That is worth knowing before ISS-11 is picked up.

Description (as filed)

SAPE-21 made a declared scope reachable: a caller sends {"grant": {"scopes": [...], "sig": "..."}} on the invoke payload and the server verifies it. The Go client gained Client.Present(scopes, grant), which attaches it to every subsequent Invoke.

The TypeScript client has no such thing. So an operation that declares a scope — the whole point of the feature — is an operation a TypeScript caller cannot run. It gets the fail-closed refusal from store.allowed, which is the same answer it got before grants existed.

Evidence (as filed — and now expired)