The Publisher: Graph API Behind a Human GO Gate
Two preconditions have no override. Everything else about publishing is a validation error you can fix and resubmit.
The last gate before anything goes live
Every other lane in the operation -- grading, scheduling, mining -- can be wrong without anything irreversible happening. A misgraded draft sits in REVISE. A missed upcycle window just means a post doesn't go back out this week. The publisher is different: it's the one lane where a mistake is instantly public, and where "we'll catch it in the next run" isn't a recovery plan, it's an admission that something already shipped that shouldn't have.
That asymmetry is why the publisher is built around fail-closed preconditions rather than best-effort validation. Two of its checks have no override path at all, and the rest of this lesson is about which two, and why the pipeline is deliberately built so a human can't talk their way past either one under pressure.
Four valves, two of them absolute
The publish pipeline runs every asset through four checks before the Graph API is ever called.
The GO gate. No asset publishes without a human approval token attached. This is the literal enforcement of the operation's central rule: agents may decide, but humans approve every outward-facing action. No GO token, no publish -- there's no code path around it.
The disclosure gate. If the asset is AI-generated, the platform's native AI-media disclosure toggle has to be on. This is the check most worth dwelling on, because it's easy to assume a valid GO token is sufficient authority to skip it. It isn't. A human approving that a post is good content is a separate question from whether that post is properly disclosed as AI-generated, and the pipeline treats them as two independently mandatory gates rather than one gate that implies the other. There is no override for a missing disclosure toggle -- not with a GO token, not with an exception request, not "just this once." AI-generated media is disclosed every time, using the platform's native toggle specifically, because a caption mention alone doesn't carry the same machine-readable weight.
Caption and surface validation. Captions are plain text -- no markdown. A markdown link renders as literal broken text on the platform, and beyond the cosmetic problem, it's a signal the content wasn't actually built for the surface it's targeting. Each surface also carries its own structural limits -- a feed carousel has a slide-count range it has to fall within -- and a draft that violates either gets rejected with a specific, fixable error rather than a generic failure.
The idempotency check. The last gate before the network call, and the one that protects against a failure mode that has nothing to do with content quality at all.
Why idempotency has to run before the call, not after
A publish call can fail in a way that's genuinely ambiguous: the request times out, or the connection drops, and the caller has no way to know whether the Graph API actually created the post before that happened. Treating a timeout as certain failure and simply retrying is how an asset ends up posted twice -- once from the original attempt that actually succeeded silently, and again from the retry that assumed it hadn't.
The fix is that every publish request carries an idempotency key, and the retry logic checks that key against a store BEFORE calling the Graph API again -- not after. If a postId is already recorded for that key, the retry returns the stored postId and the pipeline never touches the API a second time. If the key is unseen, the call proceeds normally. This is the difference between a retry that's actually safe and a retry that's just optimistic: the safety comes entirely from checking state before acting, not from any property of the network call itself.
Ordering the checks by how absolute they are
The four checks don't run in an arbitrary order. GO-token presence and disclosure compliance are checked first, specifically because they're the two checks with zero override path -- if either one fails, nothing else about the draft matters, so there's no reason to spend effort validating caption formatting or slide counts on a request that was never going to be authorized in the first place. Caption and surface validation come next, because those are genuinely fixable -- a caption gets rewritten, a carousel gets trimmed to fit the slide limit, and the draft can be resubmitted. The idempotency check runs last, immediately before the one operation in this whole pipeline that actually has an external, hard-to-reverse effect.
That ordering is a small design decision with an outsized effect: it means a request that was never going to be allowed to publish fails fast and cheap, and a request that's genuinely ready gets exactly one network call, protected by a lookup that runs before it, not a cleanup step that runs after.
Build it: implement publishAsset
You're implementing the publish function itself: the GO-token check, the disclosure precondition, plain-text caption validation, per-surface slide limits, and the idempotency lookup that has to run before the Graph API call, not after it.