The announcement set¶
Three words, set in spec 0061:
- an article is a blog post, identified by its bundle directory;
- a reel is a reel, a workspace under
reels/<slug>belonging to an article; - a post is a social-media post of an article: one piece of messaging, text with an optional image, sent to one or more platforms.
Reels and posts are an article's pieces. An article can have any number of
each, so it can be campaigned at different times with different messaging. All
of an article's pieces, with their per-platform copy and posting state, live in
one file, social.json, in the article's bundle directory, beside
reel-caption.md.
{
"schema_version": 2,
"pieces": [
{
"id": "my-post",
"kind": "reel",
"platforms": {
"youtube": { "text": "…", "title": "…", "status": "posted", "due": "2026-09-29T12:00:00+01:00",
"posted_at": "2026-09-29T11:00:02Z", "post_url": "https://…", "post_id": "…" }
}
},
{
"id": "launch",
"kind": "post",
"label": "Launch week",
"image": { "path": "cover-my-post.jpg", "url": "https://phpboyscout.uk/my-post/cover-my-post.jpg", "alt": "…" },
"platforms": {
"bluesky": { "text": "…", "hashtags": ["…"], "link": "https://phpboyscout.uk/my-post/", "title": "…",
"description": "one sentence for the link card", "status": "approved", "due": "2026-09-22T09:00:00+01:00" },
"instagram": { "text": "…", "status": "draft" }
}
}
]
}
Fields¶
| Field | Meaning |
|---|---|
id |
a reel's workspace slug, or the name you gave post new (a slug, unique within the article) |
kind |
reel or post; recorded by how the piece was made, never typed |
label |
optional prose for a post (the campaign's name) |
image |
a post's attachment: path (bundle-relative, for adapters that upload bytes), url (public, for Instagram, which accepts only a URL; supplied by you, never derived), alt (composed by social gen when absent) |
platforms.<p> |
the piece's record on one platform: the copy (text, hashtags, link, title, description) and the state |
status |
draft, approved, posted (R-SOC-6, per outlet) |
due |
the hour an approved outlet becomes sendable, RFC3339 on the hour |
posted_at, post_url, post_id |
the idempotency record |
An outlet is one piece on one platform. It is the unit approved, scheduled and sent.
Rules¶
- A reel with no article has no set.
social,approveandpublishrefuse a workspace with nobundle:;keryx reel linkrecords one. A reel's piece appears in the set the first time copy is composed for it; a post is created withpost new <id>. - Due is on the hour.
approve --at 2026-09-22T09:30:00Zis refused. A bare date takes the platform's default hour; no--attakes the article's date. - One outlet per platform per hour, across the whole article. Approving a
second piece on a platform at an hour another already holds is refused, and
publish duesends at most one outlet per platform per article per run, the earliest due; the rest reportdeferredand go on the next run. - What a platform needs is its declared contract, applied to the piece's image. A post with no image is sent text-only where the platform allows it and refused at pre-flight where it does not; nobody types a kind.
- Targets are what the set contains. keryx never fans out to every enabled
platform and reads no front matter: an outlet exists because
social genorsocial setcreated it. - Earlier shapes are refused. A
social.jsonin a reel workspace, and a bundle file at schema version 1, are refused by name; neither was ever released.
Default hours¶
announce:
timezone: Europe/London # one project-wide zone; default UTC
platforms:
bluesky:
announce:
hour: 9 # default 9
instagram:
announce:
hour: 12
Keeping the file off the site¶
A static-site generator publishes page resources. Hugo publishes a .json in a
leaf bundle at /<slug>/social.json unless told otherwise; the owning project
excludes it (an ignoreFiles entry in the site config). The file name is fixed
so that line never has to chase it.
Targeting¶
Every command that reads or writes the set takes:
| Flags | Scope |
|---|---|
--article <dir> |
the whole set: every piece of the article |
--article <dir> --reel <slug> |
one reel piece |
--article <dir> --post <id> |
one post piece |
-w, --workspace <slug> |
shorthand for --article <its bundle> --reel <slug> |
publish due with none of them sweeps every article the project's
content.sources name, which is what a scheduled pipeline runs, hourly.