channels/dm-app-review · the four unauthored items

What these four look like, before anyone builds them

Grouped by what they are, not by their letter. A and C have a surface, so they are drawn as the screen. B and D are procedures, so they are drawn as the runbook you would actually follow. The plan behind all four is plans/rethink/dm-app-review-kickoff.md.

A

Deleting a flow that has run

Blocked on your ruling

Today it answers 500. The request was refused by a foreign key, but the operator is told the server broke. Two honest answers exist and they look different. Pick one.

Option 1 · Cascaderecommended

A session and a run event are meaningless without the flow that names them. The operator gets a path out, and the list never fills with rows nobody can remove.

Needs a migration. Two foreign keys get dropped and re-added, so it must be asked for before it is authored: one session authors migrations at a time.

Option 2 · Refuse, keep the historyno migration

Cheaper, and the audit trail survives. But archive does not exist yet, so this option is really two features, and until the second lands the operator still cannot clear the row.

What happens todaythe defect
Something went wrongRequest failed with status code 500

FlowVersion cascades. FlowSession and FlowRunEvent do not.

Ships either waynot optional
!This flow has run and cannot be deletedWhichever option you pick, the 500 becomes a stated refusal.

A 500 says we broke. A 409 says you asked for something we will not do. Only one of those is true.

Recommendation: cascade. Say the word and it goes red-then-green behind a test that seeds a session and a run event.

C

Buttons on the comment → DM opener

One part doable now

The opener is a bare string. For a year the code said that was Meta's rule. It is not a rule, it is our code — the claim was withdrawn on 2026-08-01 and the docstring never caught up. Whether the right-hand version is even possible is unproven in both directions.

What ships todayproven
is the marina 2 bed still available?
Hi! Thanks for reaching out. How can we help you today?

One string, sent as a private reply addressed by comment_id. Works. Boring. Real.

What nobody has testedunproven, both ways
is the marina 2 bed still available?
Hi! Thanks for reaching out. What would help most?
Book a viewingSend the floor planCall me

Settling it means sending one to a member of the public, which is exactly what App Review gates. Build after approval, not before.

Doable now, needs nobody: correct the two comments that still carry the withdrawn claim (comment-dm-opener.ts and comment-pipeline.ts). A withdrawn inference sitting in a docstring is how it gets re-inherited as fact by the next session.
B

A flow firing on a Facebook DM

Ready — needs a tester phone

Instagram is proven. Facebook has been carried as the word “untested” since 2026-08-04 with no procedure attached. Not blocked by App Review: a tester-role account can DM the Page today, which is precisely what Standard Access permits.

The run~15 minutes, one phone
  1. Flip facebook:dms to live. It reads off on prod. This is your flip, not a session's, and it goes back to off afterwards.
  2. Publish a flow with a dm trigger on facebook, set live. Use a nonsense keyword. A real word means a real customer can trip it.
  3. DM the Page that exact string from a tester-role Facebook account.
  4. Three checks, all three or it did not work: a messaging row in webhook_events with signature_valid and processed true; a row in flow_sessions; the entry node ok in flow_run_events and the message on the phone.
  5. Gate back to off, delete the test flow. Deleting it hits item A, because it has now run. That is the cheapest reproduction of A there is, so take the trace while you are there.
D

If the evidence clock lapses

Contingency · 2026-08-29

Rolling 30-day window from 2026-07-30. Submit after it and every permission must be re-exercised first. Two of the four are cheap. Two are not, and that asymmetry is the whole argument for not letting it lapse.

What re-exercising actually costsread the counts on Meta's Permissions page, not our DB
PermissionHowCost
instagram_basicAny Posts screen refresh hits GET /{ig-user}/mediaOne click
instagram_manage_commentsComment on our own post from a tester account, let the pipeline reply, hide it, unhide itFive minutes
instagram_manage_messagesA role-holding account DMs the business account, staff repliesPhone in hand, two people
pages_messagingThe same round trip on Messenger to the PagePhone in hand, two people
The last two cannot be scripted. Standard Access only permits a send to someone holding a role on the app, so there is no way to exercise them from our side alone. That is the same phone-in-hand session the screencasts need — which is the real reason to record and submit inside the window rather than pay for that session twice.