All three clients present a scope grant. Measured, each with a test of its own:
| Client | Where | Shape |
|---|---|---|
| Go | sapedb.go:166 | Client.Present(scopes, grant) — attaches to the connection |
| PHP | src/Client.php:89 | Client::present(array $scopes, string $grant) |
| TypeScript | src/client/index.ts:1186 | invoke 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.
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.
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.
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.
grep -rniI grant packages/ecosy-sapedb/src packages/ecosy-sapedb/tests →
0 matches. Not a partial implementation, not a TODO: the word does not appear.command, args and optionally
writeId. There is no grant key and nowhere to put one.src/client/index.ts:1186.internal/wire's
TestPresentingAGrantReachesAnOperationThatDeclaresAScope and four cases in
internal/server/scope_test.go. This part is still true.