You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The server currently enforces three pieces of policy in route handlers — added by #9 as stopgaps. The design review moved each of them into core's invariant layer, where they're written once and inherited by every adapter and server. Tracking issue for removing the duplicates as core ships, so the server converges on "thin wire shell over ScopedStack."
Route guard today
Location
Core home
_attachment@1 immutable fields (fileId, size, mimeType) on PATCH
src/routes/records.ts:228-239
Record validation per haverstack/core#65 (mimeType conflict rule) — coordinate what remains for fileId/size with the owner; if core doesn't take those, keep the guard and say so in a comment
_grant@1 PATCH/DELETE owner-only
records.ts:240-246, 260-266
haverstack/core#57 (grantee out of entityId, listGrants/revoke scope) + core #59's owner/creator-only pattern for privilege-bearing verbs
?hard=true owner-only on DELETE
records.ts:257
haverstack/core#59's one-line carve-out at ScopedStack.delete (with the open question: non-owner {hard:true} → error vs silent soften, leaning error → 403 round-trip)
Why removal matters (not just hygiene)
The interim guards have real defects that inherit-from-core eliminates:
They consult the unscoped stack before the permission check. Both PATCH and DELETE call stack.get(id) (no requester scoping, records.ts:226, 259) and can answer 422/403 based on the record's type before ScopedStack ever evaluates visibility. A requester who can't read a record can still learn it exists and is a grant/attachment — a small oracle the spec's deliberate 403-vs-404 discipline doesn't intend to hand out.
Route guards protect exactly two routes; core guards protect every path (restore, future endpoints, other servers).
Also in scope once core lands: _config protection (delete → 409, entityId change → 409) arrives entirely via haverstack/core#67 — server work is only the #32 fixtures; and any wire surface for listGrants/revoke decided under core #57's follow-up gets its endpoints here.
The server currently enforces three pieces of policy in route handlers — added by #9 as stopgaps. The design review moved each of them into core's invariant layer, where they're written once and inherited by every adapter and server. Tracking issue for removing the duplicates as core ships, so the server converges on "thin wire shell over
ScopedStack."_attachment@1immutable fields (fileId,size,mimeType) on PATCHsrc/routes/records.ts:228-239fileId/sizewith the owner; if core doesn't take those, keep the guard and say so in a comment_grant@1PATCH/DELETE owner-onlyrecords.ts:240-246, 260-266entityId,listGrants/revokescope) + core #59's owner/creator-only pattern for privilege-bearing verbs?hard=trueowner-only on DELETErecords.ts:257ScopedStack.delete(with the open question: non-owner{hard:true}→ error vs silent soften, leaning error → 403 round-trip)Why removal matters (not just hygiene)
The interim guards have real defects that inherit-from-core eliminates:
stack.get(id)(no requester scoping,records.ts:226, 259) and can answer 422/403 based on the record's type beforeScopedStackever evaluates visibility. A requester who can't read a record can still learn it exists and is a grant/attachment — a small oracle the spec's deliberate 403-vs-404 discipline doesn't intend to hand out.Also in scope once core lands:
_configprotection (delete → 409,entityIdchange → 409) arrives entirely via haverstack/core#67 — server work is only the #32 fixtures; and any wire surface forlistGrants/revokedecided under core #57's follow-up gets its endpoints here.Work items
permission, 409, 422 per Core sync: wire error contract — typedcodevocabulary, structured bodies, 400/422 discipline (core #53) #33)stack.get()in PATCH/DELETE handlers_configfixtures per core #67listGrants/revokeendpoints if/when spec'd (core #57 follow-up)Refs haverstack/core#59, haverstack/core#57, haverstack/core#65, haverstack/core#67, haverstack/core#51, #32, #33.