Content studio API + preview page; widen the reachability guard; correct
two inventory errors INVENTORY CORRECTION, and it was mine. Phase 2's two "orphans" are NOT orphans -- my board grepped only web/src/app and missed component-level mounting. The transitive check says both are already mounted: BookComparisonPanel -> GradeResultCard -> app/scan/page.tsx NewsWire -> ExploreHub -> app/explore/page.tsx So book comparison is DONE (wired to /api/books, rendering on the grade card) and THE WIRE is DONE-BY-DESIGN, mounted in ExploreHub. Its header names an "Offseason Hub" as its home, and that hub genuinely does not exist -- but that is board item #8, not a mounting bug, and inventing a surface to satisfy a comment would be the wrong fix. The lesson is the same one this session keeps teaching: I checked one directory and reported a conclusion the check could not support. ALSO CAUGHT: I overwrote src/routes/content.js, which was the Session-29 content-templates route, by picking a filename without looking. Restored from git with no work lost; the new surface lives at /api/content-studio and both now coexist. PHASE 0/1 — /api/content-studio serves finished posts (copy, branded card, card_svg, the fact_contract each was REQUIRED to have, and the facts that actually backed it) plus a POST for editorial status in Redis. Private via internal key; the Next proxy holds the key server-side so the browser never does. /studio renders it as a thin client -- copy and card side by side with the fact contract visible, because reviewing copy by reading it is exactly how a wrong number ships. Never-blank: a night with nothing generated says so. API-FIRST is the point: the endpoint an autonomous poster will call is the one the page already renders, so the agent handoff is a pointer change, not a rebuild. Contract documented at docs/CONTENT-STUDIO-API.md. EXPRESS 5 BROKE 23 SUITES at first: `router.get('/:date?')` throws at mount time in Express 5, taking down everything that imports app.js. Two explicit routes instead. PHASE 3 — the reachability guard is widened from grade-fields-only to a general built-but-unread check. Book comparison, THE WIRE and the content studio are now registered surfaces; a page counts as its own entry point (Next mounts it by convention) while everything else must trace to one. 22 checks green; a registered-but-unimported surface still goes red. FULLY ISOLATED: read-only on model/slate/ledger, serving fingerprint verified unchanged, accrual clock unchanged at 0 eligible dates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# `/api/content-studio` — the agent-ready contract
|
||||
|
||||
The endpoint Kev's `/studio` page reads today and an autonomous poster reads
|
||||
later. **The page is a thin client**: no posting logic, no fact handling. Wiring
|
||||
a bot means pointing it here — nothing on this side changes.
|
||||
|
||||
Distinct from `/api/content` (Session 29), which serves structured content
|
||||
*objects* by data level. This serves finished **posts**.
|
||||
|
||||
## Auth
|
||||
|
||||
`x-internal-key: $VYNDR_INTERNAL_KEY` — private, never public. The browser never
|
||||
holds the key; the Next proxy at `web/src/app/api/content-studio/[...path]`
|
||||
attaches it server-side.
|
||||
|
||||
## `GET /api/content-studio/:date?`
|
||||
|
||||
`:date` optional, defaults to today ET.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"date": "2026-08-07",
|
||||
"count": 3,
|
||||
"posts": [{
|
||||
"id": "honesty_flex",
|
||||
"label": "The Honesty Flex",
|
||||
"sport": "mlb",
|
||||
"status": "pending", // pending | approved | skipped | regenerate_requested
|
||||
"ok": true,
|
||||
"skipped": false,
|
||||
"reason": null, // why it was skipped, when it was
|
||||
"honest_absence": false, // a real "nothing tonight" post, not a failure
|
||||
"copy": "WE GRADED 2140 PROPS TONIGHT...",
|
||||
"card": { "title": "...", "lines": [...] },
|
||||
"card_svg": "<svg ...>", // ready to render or rasterise
|
||||
"fact_contract": ["graded", "ceiling_letter", "..."], // REQUIRED fields
|
||||
"facts": { "graded": 2140, "...": "..." } // what backed it
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**`fact_contract` + `facts` are the point.** An agent (or a reviewer) can check
|
||||
what a claim rests on instead of trusting the sentence. A post whose contract
|
||||
could not be met never appears with invented values — it arrives `skipped` with
|
||||
a `reason`, or as an `honest_absence`.
|
||||
|
||||
## `POST /api/content-studio/:date/:id/status`
|
||||
|
||||
```jsonc
|
||||
{ "status": "approved" } // approved | skipped | regenerate_requested
|
||||
```
|
||||
|
||||
Editorial state only, stored in Redis for 14 days. **Approving a post changes
|
||||
nothing about the model** — status never touches a serving, model or ledger
|
||||
table.
|
||||
|
||||
## For the agent build
|
||||
|
||||
1. `GET` the date → filter `ok && !skipped`.
|
||||
2. Post `copy`; rasterise or attach `card_svg`.
|
||||
3. `POST` status `approved` on success.
|
||||
4. **Never** synthesise a claim not present in `facts`. The engine refuses to
|
||||
render an unbacked token; an agent must not reintroduce one downstream.
|
||||
|
||||
Adding a template changes the payload not at all — a new `id` simply appears.
|
||||
Reference in New Issue
Block a user