Charge posts to the resolved account, never an artist (app#2248)
Implements the api step of app#2248. Contract: marketing#183. Column: database#117.
Decision 2026-10-19 (Sweets): every request has a caller, a resolved account_id (the caller's account, or an org/product admin's override), and an artist within that account. Credits are checked and deducted on the resolved account_id; an artist is never charged.
POST /api/posts
- New optional
artist_account_id(uuid): the artist whose connection posts. The connection is looked up onartist_account_id ?? accountId, at create time and again inpublishPostStep. validatePostAccounts(new): 400account_id is an artist; pass it as artist_account_idwhen the resolved account is an artist; 403 unlesscheckAccountArtistAccess(accountId, artist_account_id). Runs before the connection lookup, credit check or insert.isArtistAccount(new): held inaccount_artist_ids(selectArtistOwnerLink, new) orartist_organization_ids. A lookup error throws → 500, never "not an artist".- Credits unchanged in code:
ensurePostCreditsand the workflow'srecordCreditDeductionalready use the resolvedaccountId; with the artist split out, that is always the account. startPoststoresartist_account_id(null when omitted).
GET /api/posts: artist_account_id filter; every Post returns artist_account_id.
Tests (new files, RED first): selectArtistOwnerLink, isArtistAccount, validatePostAccounts, createPostHandler.artist, validateCreatePostBody.artist, startPost.artist, publishPostStep.artist, getPostsHandler.artist. Existing tests: validatePostAccounts mocked in the two createPostHandler suites; artist_account_id: null added to startPost's exact insert. Posts, artists, account_artist_ids, scheduled_posts and contracts suites: 263/263.
Types: tsc reports artist_account_id missing from scheduled_posts in types/database.types.ts until database#117 is applied and types are regenerated (pnpm update-types); no other errors in the touched files.
Docs fixture: social.json hash = marketing#183.
Merge order: marketing#183 → database#117 → this (after types are regenerated) → docs publication copy.
🤖 Generated with Claude Code
Summary by cubic
Implements the API step for charging posts to the resolved account, never an artist (app#2248). A post may name an artist via artist_account_id: the artist's connection publishes, while credits are charged to the resolved account. Artist accounts passed as account_id are rejected with 400, and artists the account cannot access with 403.
Changes
- Validation runs before any connection lookup or charge; lookup failures throw, returning 500 instead of being misread as "not an artist".
selectAccountArtistIdsnow takes account and/or artist filters and returns null on error, replacingselectArtistOwnerLinkas the source forisArtistAccount.- The publishing workflow resolves the connection on
artist_account_id ?? account_id, at create time and again inpublishPostStep. GET /api/postsfilters byartist_account_id, and every post returns it.scheduled_postsstoresartist_account_id(null when omitted); database types are regenerated.
Migration
- Requires database#117 to be applied.
Summary by CodeRabbit
- New Features
- Posts can be published through an artist’s connected account when an artist account is selected; otherwise, publishing uses the account’s own connection.
- Create, retrieve, and list scheduled posts for a specific artist account using the optional artist account identifier.
- Bug Fixes
- Post requests now validate account access before checking connectivity, with clear errors for invalid or inaccessible artist accounts.
- Scheduled posts now consistently retain and return their associated artist account.