fix(#595): grants refuse space paths — membership is the grant #600

Merged
Cordy merged 1 commit from fix-595 into main 2026-09-18 03:03:55 +00:00
Owner

Implements the #595 decision (Option A): resolveOwned refuses any grant whose resolved backend path lands under /spaces/, with "space content is shared by space membership: ask the space owner to add them". One refusal branch at the same chokepoint as the re-share refusal.

This closes a real escalation, not a hypothetical: the red test minted a write grant on a space path as an ordinary member and got a 201 back — the recorded run shows the grant object created. Post-fix it is a 403 and the store stays empty. With this in place, resolveShared's grant-only writability derivation can no longer be reached with a space path, so the read-only-member-mints-write hole is closed at its single entrance.

Space sharing stays what it already is — membership, managed by the owner, roles read/write — and relaxing this later (per the options on the issue) is a deliberate decision away, not a retraction.

Test: TestGrantCreateRefusesSpacePaths beside the re-share refusal test, using a spaceGrantDriver whose Resolve behaves like the overlay does for spaces (virtual path = actual path). Red witnessed (201 + stored grant), green after, full go vet + go test ./... green.

Docs ride along (the #588 pattern): sharing.md gains "Space content is not grantable", spaces.md gains the mirror sentence under Read-only membership — both stating membership is the grant.

Process note for the record: the first run's post-condition failed because it counted a phrase my own splice-comment repeats — the fourth instance of the assert-on-the-line-not-the-word hazard, caught by the abort-and-write-nothing design. Now counts the full error literal.

Implements the #595 decision (Option A): `resolveOwned` refuses any grant whose resolved backend path lands under `/spaces/`, with *"space content is shared by space membership: ask the space owner to add them"*. One refusal branch at the same chokepoint as the re-share refusal. This closes a real escalation, not a hypothetical: the red test minted a **write** grant on a space path as an ordinary member and got a 201 back — the recorded run shows the grant object created. Post-fix it is a 403 and the store stays empty. With this in place, `resolveShared`'s grant-only writability derivation can no longer be reached with a space path, so the read-only-member-mints-write hole is closed at its single entrance. Space sharing stays what it already is — membership, managed by the owner, roles read/write — and relaxing this later (per the options on the issue) is a deliberate decision away, not a retraction. **Test:** `TestGrantCreateRefusesSpacePaths` beside the re-share refusal test, using a `spaceGrantDriver` whose `Resolve` behaves like the overlay does for spaces (virtual path = actual path). Red witnessed (201 + stored grant), green after, full `go vet` + `go test ./...` green. **Docs ride along** (the #588 pattern): sharing.md gains "Space content is not grantable", spaces.md gains the mirror sentence under Read-only membership — both stating membership *is* the grant. Process note for the record: the first run's post-condition failed because it counted a phrase my own splice-comment repeats — the fourth instance of the assert-on-the-line-not-the-word hazard, caught by the abort-and-write-nothing design. Now counts the full error literal.
fix(#595): grants refuse space paths -- membership is the grant
All checks were successful
ci / test-and-build (pull_request) Successful in 59s
deb57dfc35
Cordy merged commit e7d7b8f77f into main 2026-09-18 03:03:55 +00:00
Cordy deleted branch fix-595 2026-09-18 03:03:57 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#600
No description provided.