mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-17 03:32:32 -06:00
Placement-as-grant, replacing the inferred auto-place idea. api_token_target_zones is an ADDITIVE second table (does NOT touch the proven api_token_targets), structurally anchored: a composite FK to api_token_targets(token_id, playlist_id) makes a zone grant orphan- impossible and cascade away when the playlist grant is revoked - "narrow" is structural, not conventional. zone_id FK -> layout_zones cascades on zone/layout delete. Confinement (lib/agency-targets.resolveGrantedZone, called in the item-add): grants exist -> the item MUST land in a granted zone (a body zone_id picks among grants, never escapes them); none -> whole-playlist/full-screen as before. The item-add stamps the granted zone_id. Bite-tested (6, all proven incl. neutralize->red on the confinement): granted YES; non- granted/cross-playlist/ambiguous blocked; orphan-grant rejected by the FK; cascade on playlist-grant revoke, on playlist delete, on zone/layout delete; and foreign_keys=ON asserted (a cascade that no-ops because FKs are off is the trap). 153 suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
43 lines
2.2 KiB
JavaScript
43 lines
2.2 KiB
JavaScript
'use strict';
|
|
|
|
// #73: the single query behind GET /api/agency/playlists. Returns ONLY this token's
|
|
// designated playlists, in its bound workspace. The WHERE clause IS the confinement and is
|
|
// the thing to bite-test:
|
|
// t.token_id = ? -> this token's targets, never another token's
|
|
// (JOIN api_token_targets) -> only allowlisted playlists, never one outside the allowlist
|
|
// p.workspace_id = ? -> only the bound workspace, never cross-workspace
|
|
// db is passed in (not module-required) so the confinement is unit-testable in isolation.
|
|
function listDesignatedPlaylists(db, tokenId, workspaceId) {
|
|
return db.prepare(`
|
|
SELECT p.id, p.name, p.status
|
|
FROM api_token_targets t
|
|
JOIN playlists p ON p.id = t.playlist_id
|
|
WHERE t.token_id = ? AND p.workspace_id = ?
|
|
ORDER BY p.name
|
|
`).all(tokenId, workspaceId);
|
|
}
|
|
|
|
// #73: resolve which zone an agency item-add lands in, enforcing the zone grants. The grant
|
|
// is the boundary; a body-supplied zone can pick WITHIN it but never escape it.
|
|
// - No zone grants for (token, playlist) -> whole-playlist/full-screen (zone_id NULL); a
|
|
// body zone_id is ignored (placement isn't agency-driven when nothing's granted).
|
|
// - Zone grants exist -> the item MUST land in a GRANTED zone:
|
|
// requested zone that IS granted -> use it (agency picks among its grants);
|
|
// requested zone NOT granted -> { ok:false, reason:'forbidden' } (403);
|
|
// no request, exactly one grant -> auto-place into it;
|
|
// no request, multiple grants -> { ok:false, reason:'ambiguous' } (must pick).
|
|
function resolveGrantedZone(db, tokenId, playlistId, requestedZoneId) {
|
|
const grants = db.prepare('SELECT zone_id FROM api_token_target_zones WHERE token_id = ? AND playlist_id = ?')
|
|
.all(tokenId, playlistId).map(r => r.zone_id);
|
|
if (!grants.length) return { ok: true, zoneId: null };
|
|
if (requestedZoneId) {
|
|
return grants.includes(requestedZoneId)
|
|
? { ok: true, zoneId: requestedZoneId }
|
|
: { ok: false, reason: 'forbidden' };
|
|
}
|
|
if (grants.length === 1) return { ok: true, zoneId: grants[0] };
|
|
return { ok: false, reason: 'ambiguous' };
|
|
}
|
|
|
|
module.exports = { listDesignatedPlaylists, resolveGrantedZone };
|