preview.erikbethke.com · probed 2026-09-24 14:21 UTC · probe 1.0.3 · pack mechanicals@2026.09.0 · supersedes the 2026-09-21 readout
The deterministic floor: 84 checks that are either correct or not, that nobody has to have an opinion about. This is the current readout for the new build — what is shipped and working, and the one thing left. Every line carries its own evidence. Both sites were probed within seconds of each other.
The new build passes 78 of 84 by cutover, up from 45 today, with zero failures. Every check carries its own evidence.
www.futurumgroup.com must 301 to the apex, as the live site already does. It is on the README cutover checklist (PR #64), with the MCP registry listing.The 12 repo-side checks apply to the new build only, because the incumbent's source is not ours to scan. 11 pass; the twelfth, a GA/GTM env slot, is the same cutover hold. The incumbent's GA4 and GTM show green because they are live there today.
A site now has three kinds of visitor: people, search engines and AI assistants like ChatGPT and Claude. The current site was built for the first two; the new one is built for all three. Tap any card to see the proof.
$ curl -s https://preview.erikbethke.com/insights/opswat-at-gisec-2026-can-you-secure-what-you-cant-detect/ | grep -o '"articleBody":"[^"]\{0,110\}'
"articleBody":"OPSWAT debuted its AI Content Inspector at GISEC Global 2026 in Dubai, targeting a new class of file-borne ris
(1,020 words in full)$ curl -s https://futurumgroup.com/insights/opswat-at-gisec-2026-can-you-secure-what-you-cant-detect/ | grep -o '"articleBody":"[^"]\{0,110\}'
(no output: the page describes itself to machines as a web page, an image and breadcrumbs, never as article text)$ curl -s -o /dev/null -w '%{size_download}' https://<site>/
futurumgroup.com 414,488 bytes
preview.erikbethke.com 282,133 bytes$ curl -s https://futurumgroup.com/llms.txt | head -c 4 | xxd
00000000: efbb bf23 ...#
(an invisible byte-order mark sits before the "#", so readers expecting a title on line one don't find one)$ curl -s -o /dev/null -w '%{http_code} %{size_download}' https://<site>/llms-full.txt
futurumgroup.com 404 (missing)
preview.erikbethke.com 200 2,071,305 bytes$ curl -s https://preview.erikbethke.com/llms.txt | head -3
# Futurum
> AI decision intelligence: research, benchmarks and live market signal for the people who have to choose.$ curl -s https://preview.erikbethke.com/api/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "search_posts", "arguments": {"query": "quantum computing", "limit": 3}}}'
{ "count": 24, "posts": [
{ "title": "Is Quantum Computing Languishing? – Report Summary", "path": "/press-release/is-quantum-computing-languishing-report-summary/", "publishedAt": "2024-12-19" },
{ "title": "Are You “Quantum Ready,” Whatever That Means? – Report Summary", "path": "/press-release/are-you-quantum-ready-whatever-that-means-report-summary/", "publishedAt": "2025-06-02" },
{ "title": "Will Quantum Computing Ever Be Useful for AI? – Report Summary", "path": "/press-release/will-quantum-computing-ever-be-useful-for-ai-report-summary/", "publishedAt": "2025-02-25" }
] }$ curl -s -o /dev/null -w '%{http_code}' -X POST https://futurumgroup.com/api/mcp -d '{...}'
404 (no front desk: nothing answers)The preview carries an 881-record sample of the library until the full import lands, so counts here are smaller than they will be.
$ curl -s https://preview.erikbethke.com/api/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "verify_quote", "arguments": {"quote": "Quantum computing is not new. The scientists Paul Benioff, Yuri Manin, and Richard Feynman proposed the"}}}'
{ "found": true,
"title": "Is Quantum Computing Languishing? – Report Summary",
"url": "https://preview.erikbethke.com/press-release/is-quantum-computing-languishing-report-summary/" }$ curl -s https://preview.erikbethke.com/api/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "verify_quote", "arguments": {"quote": "this is a quote Futurum never published about quantum"}}}'
{ "found": false }$ curl -s https://preview.erikbethke.com/api/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_citation", "arguments": {"path": "/press-release/is-quantum-computing-languishing-report-summary/"}}}'
APA: The Futurum Group. (2024, December 19). Is Quantum Computing Languishing? – Report Summary. https://preview.erikbethke.com/press-release/is-quantum-computing-languishing-report-summary/
plain: The Futurum Group, "Is Quantum Computing Languishing? – Report Summary," December 19, 2024. https://preview.erikbethke.com/press-release/is-quantum-computing-languishing-report-summary/$ curl -s https://preview.erikbethke.com/openapi.json | jq -r '.info.title, (.paths | keys[])'
Futurum Public Read API
/api/content/companies
/api/content/companies/{slug}
/api/content/pages
/api/content/pages/{slug}
/api/content/people
/api/content/people/{slug}
/api/content/posts
/api/content/posts/{slug}
/api/content/practice-areas
/api/search$ for p in /openapi.json /.well-known/agent-card.json /agents.json /for-agents/; do curl -s -o /dev/null -w "$p %{http_code}\n" https://<site>$p; done
futurumgroup.com new site
/openapi.json 404 200
/.well-known/agent-card.json 404 200
/agents.json 404 200
/for-agents/ 404 200$ curl -s https://<site>/robots.txt | grep -i '^sitemap'
futurumgroup.com (nothing)
preview.erikbethke.com Sitemap: https://preview.erikbethke.com/sitemap.xml$ curl -s -o /dev/null -w '%{http_code} %{redirect_url}' https://preview.erikbethke.com/analyst/donald-jin/
308 https://preview.erikbethke.com/donald-jin/ (moved for good; Google carries the link value across)$ curl -s -o /dev/null -w '%{http_code}' https://preview.erikbethke.com/tag/creative-stack/
410 ("gone, deliberately": Google drops it cleanly instead of retrying a broken link)1,199 served directly, 132 redirected, 25,290 retired on purpose, the rest 404 for a tracked reason (import still landing, pending the content owner, or dropped on purpose). No unexplained 404s, down from 35,295 before this work — full breakdown in What changed.
$ curl -s https://preview.erikbethke.com/.well-known/security.txt
Contact: mailto:security@futurumgroup.com
Expires: 2027-08-23T00:00:00Z
Preferred-Languages: en
Canonical: https://preview.erikbethke.com/.well-known/security.txt$ curl -s -o /dev/null -w '%{http_code}' https://futurumgroup.com/.well-known/security.txt
404$ pnpm audit:mechanicals --repo . # the 12 repo checks, summarised
llms.txt generated route app/llms.txt/route.ts
llms-full.txt generated route app/.../llms-full.txt/route.ts
openapi.json generated route app/openapi.json/route.ts
feed.xml generated route app/feed.xml/route.ts
sitemap route handlers app/sitemap.xml/route.ts, app/sitemap/[shard]/...
robots app/robots.ts
MCP server app/api/mcp/route.ts
.well-known app/.well-known/agent-card.json/route.ts, ...$ pnpm pre-deploy # the gate every pull request must pass
type-check ok
lint ok
test 2,845 tests passed (PR #70's run)
build okA guard test fails any pull request that makes a page read the whole library, so the site stays fast as it grows. WordPress's equivalents live in plugins and theme settings, which nobody outside can inspect.
Nothing is lost at the switch. Analytics, Search Console and the ad pixels switch on the day the new site goes live, so marketing keeps every number it has today.
What this doesn't promise. These are capabilities, not guaranteed traffic. Whether Google ranks a page, or an AI chooses to quote it, still depends on the research itself and on time. What changes is that nothing technical stands in the way, and after the switch we can measure the result.
| Answer | URLs |
|---|---|
| 200, served directly | 1,199 |
| 301 to an equivalent page | 132 |
| 410 Gone, deliberately retired | 25,290 |
| 404, not imported yet (lands with the slim runtime; its first phase merged as PR #70) | 9,880 |
| 404, waiting on the content owner | 18 |
| 404, mockup or test pages we're not carrying | 14 |
/.well-known/mcp.json now serves and points at the live server; T2 went from 67% to 100%, and the server grew from 11 tools to 16.llms.txt was restructured (36 KB to 12 KB, index-first); the MCP endpoint now sits inside the first 1,024 bytes, where a truncating fetcher reads it..md twin, negotiated at CloudFront. Agent-readiness Tier 3, done./ai4me, security.txt and a web app manifestsecurity.txt lists security@futurumgroup.com and expires 2027-08-23.repo.sitemap and repo.well_known failed only because the probe didn't recognise App Router route handlers. Fixed; both now pass on their own.noindex until cutover; only futurum-web-prod flips indexable.The new build leads on every tier, and ships four capabilities the incumbent has none of.
Per-tier only — these are never averaged into one grade. T4 reads “n/m”, not 0%: the tier holds no conformance checks, so there is nothing there to score.
llms.txt — 12,291 bytes, H1 + blockquote, MCP in the first KB (incumbent: no H1, no MCP)llms-full.txt — 1,922,889 bytes (404)agents.json — valid, 6,379 bytes (404)openapi.json — OpenAPI 3.1.0, 10 paths (404)/for-agents — 19,562 chars server-rendered (404)/ai4me — 3,980 chars (404)feed.xml — 50 items (incumbent: RSS at /feed/, discovered via the homepage's advertised <link rel=alternate>; the probe now follows that link, so it passes on its own)/.well-known/mcp.json → /api/mcpinitialize → futurum-web-mcp 1.0.0, protocol 2025-06-18, open (no auth)articleBody in the deep-page JSON-LD:
7,353 chars / 1,020 words, plus SpeakableSpecificationrobots.txt 200, 8 groups, Sitemap: line present
(incumbent has none — its only T0 miss)security.txt and a web app manifest (incumbent: neither)Every surface below is a real route that renders at request time, so it cannot drift from the content the way a hand-maintained file does. Repo checks: conformance 2 of 2, adoption 3 of 3.
| Surface | Source | State |
|---|---|---|
| llms.txt | app/llms.txt/route.ts | ✓pass |
| llms-full.txt | app/llms-full.txt + app/practice-areas/[slug]/llms-full.txt | ✓pass |
| openapi.json | app/openapi.json/route.ts | ✓pass |
| feed.xml | app/feed.xml/route.ts | ✓pass |
| robots | app/robots.ts | ✓pass |
| MCP | app/api/mcp/route.ts | ✓pass |
| sitemap | app/sitemap.xml/route.ts + app/sitemap/[shard]/route.ts | ✓pass |
| .well-known | app/.well-known/*/route.ts (mcp.json, agent-card.json, security.txt) | ✓pass |
| markdown mirrors | app/api/md/[...segments]/route.ts + lib/seo/mirror/edge.ts | ✓live |
The preview carries no GA4, GTM, Ads, Reddit or Meta tag yet, by decision: the pixels
go in at cutover, not on a noindex preview where they would pollute production
data. The incumbent is measured today — GA4 G-ES8BSET5RC and GTM containers
GTM-KM6XDZR6 and GTM-W4999QMH. This is a cutover gate: measurement
must not switch off on the day the before/after matters most.
noindex offSITE_INDEXABLE is true only on futurum-web-prod.t5.ttfb passes: 364 ms median of five (342–453 ms; the live site 391 ms) on a preview stage, not productionA white-hat security review ran on 2026-09-26: automated static analysis plus five independent, read-only reviews across the codebase, followed by two further pre-cutover passes. Every fix shipped as its own reviewed pull request, verified on a preview stage before merging. Nothing was deployed to production mid-review. There were no Critical findings.
| Severity | Found | Fixed | Open |
|---|---|---|---|
| High | 2 | 2 | 0 |
| Medium | 12 | 11 | 1 |
| Low | 14 | 14 | 0 |
| Total | 28 | 27 | 1 |
| Severity | Finding | Fix | Where |
|---|---|---|---|
| High | A patched Next.js image-processing vulnerability (remote code execution) plus a related cache-poisoning issue were present, and the image pipeline accepted more source hosts than it needed to. | Fixed — upgraded Next.js and narrowed the accepted image hosts, with a test that forbids a wildcard host from being reintroduced. | PR #100 |
| High | The image-rendering function bundled an image-processing library with known vulnerabilities. | Fixed — pinned to a patched version, confirmed in the deployed build. | PR #106 |
| Medium | The backend functions behind the site and its image pipeline could be reached directly, bypassing the CDN, its cache and its edge protections. | Fixed — direct access now returns a clean rejection; all traffic must come through the CDN, which carries the protections below. | PR #106 |
| Medium | The public site's backend held broader database and storage permissions than the code uses. | Fixed — scoped to least privilege, matched to the commands the code sends. | PR #106 |
| Medium | Scanning a publish request for credentials and unsafe content could take several seconds on a large request, before it was even rate-limited. | Fixed — rewritten to run in linear time; the same requests now complete in well under a second. | PR #108 |
| Medium | A publishing credential could edit, delete or reassign another author's posts. | Fixed — ownership is enforced; only an editor-level credential can act on someone else's content. | PR #101 |
| Medium | Content stored outside the normal git-reviewed path was not re-checked against the site's safety rules at render time. | Fixed — re-checked against the same rules on every render; anything that fails renders as plain text and is logged. | PR #104 |
| Medium | Search and the AI-agent interface (MCP) shared no rate limit, and a single agent request could trigger many expensive lookups at once. | Fixed — results are briefly cached, batch size is capped, and both surfaces are rate-limited at the edge. | PR #105, #110 |
| Medium | A deleted or unpublished item could still appear in search results for a while. | Fixed — search now checks against what is published, and the cache expires in minutes rather than persisting until the next restart. | PR #105 |
| Medium | The site's script policy (CSP) allows inline scripts on most routes by default, which would widen the impact if HTML injection ever occurred. | Partly fixed — canvas documents (like this one) now render in a fully isolated sandbox, on their own web address, under their own tighter policy. The two most dynamic non-canvas routes moved to a stronger per-request policy. The rest of the site's routes are the one item still open — see below. | PR #111, #116, #131 |
| Medium | A number of previously published articles still linked to a hostname associated with a past incident on the site's former hosting provider. | Fixed — those links were rewritten to point at this site instead, with a test that blocks a recurrence. | PR #109 |
| Medium | A now-unused legacy publishing path, and the shared access key that could use it, were still live. | Fixed — retired entirely; the old key is now rejected outright. | PR #128 |
| Medium | The edge network had no broad protection against common attack patterns beyond the site's own request checks. | Fixed — managed protection rules and a site-wide request-rate limit were added, tuned in a monitor-only mode first to confirm no legitimate traffic was affected. | PR #129 |
| Medium | Stored content had no point-in-time recovery or version history, so a bad delete or a bug could be permanent. | Fixed — both enabled; a mistaken delete or cleanup is now recoverable. | PR #129 |
| Low | Error responses from the AI-agent interface could include internal technical detail. | Fixed — a generic message is returned; the detail is logged privately instead. | PR #105 |
| Low | A markdown-format submission could slip an unsafe link scheme past the safety check, and a few text fields were not scanned at all. | Fixed — the check was hardened and extended to cover those fields. | PR #108 |
| Low | The standalone-document viewer used on pages like this one did not check where a document came from before rendering it. | Fixed — it now only renders reviewed, git-committed documents; verified against every such document currently on the site. | PR #104 |
| Low | The deployed server bundle included some source, documentation and test files it did not need. | Fixed — excluded from the deployed package. | PR #107 |
| Low | Automated build steps referenced third-party tooling by a movable label rather than a fixed, verifiable version, and dependency updates were not automated. | Fixed — pinned to exact, verified versions; automatic dependency-update proposals turned on. | PR #99 |
| Low | A strict, hard-to-reverse browser security header was being sent on every environment, including ones still under test. | Fixed — scoped to when it is the right call. | PR #107 |
| Low | Some links carried marketing tracking parameters, or routed through an old link-redirect service. | Fixed — parameters stripped and links normalized across the affected content. | PR #109 |
| Low | Content-import tooling followed a redirect without checking where it led. | Fixed — every redirect hop is now checked against an approved list of hosts. | PR #109 |
| Low | Deleted content bodies were never cleaned up in storage. | Fixed — an operator-run cleanup tool was added, off by default, with a safety grace period before anything is removed. | PR #117 |
| Low | A testing-only dependency carried a known low-severity advisory. | Fixed — upgraded. | PR #118 |
| Low | The build pipeline did not scan for accidentally committed secrets or run a dependency audit. | Fixed — both added to the automated build. | PR #127 |
| Low | There was no structured logging or alerting for security-relevant events, such as a spike in blocked or unauthorized requests. | Fixed — dedicated logs and four automated alarms were added. Routing an alert to an actual person is still pending — see below. | PR #129 |
| Low | Publishing credentials had no expiry or rotation, and no simple way to see which ones existed. | Fixed — optional expiry and rotation added, plus a listing an operator can audit without ever seeing the credential itself. | PR #133 |
| Low | The site depended on a third-party font-hosting service. | Fixed — fonts are now self-hosted, and the dependency was dropped from the site's own security policy. (The standalone documents on this canvas lane still use it, deliberately, per this project's authoring rules.) | PR #135 |
Everything above measures whether machines can read this site. This is what lets Futurum (or an AI acting for Futurum) write to it: one checked, safe call that gets a new post live in seconds, with no rebuild and no deploy.
The exhibit is live: preview.erikbethke.com/insights/sample-insight-publishing-pipeline/, created exactly the way the curl example below creates one. A second exhibit went further and published a report about itself: preview.erikbethke.com/research-reports/agentic-research-reports/, a research report on agentic research reports, written and published by an agent through this exact pipeline.
| Step | Time |
|---|---|
Create answers 201 | 2.7 s |
| Article live at its own URL | 9.9 s |
/insights/ lists it | 10.6 s |
feed.xml carries it | 11.3 s |
Replaying the same request — same Idempotency-Key,
same body — came back byte-identical with Idempotent-Replayed: true: no
second revision, no duplicate post. No rebuild and no deploy anywhere in this; the whole
round trip is HTTP.
Four blocks, each complete on its own. Tap Copy and the exact text — with this site's own address filled in — is on your clipboard.
Paste this whole block into Claude, ChatGPT, Gemini or any coding agent, along with your token, and tell it what you want published.
You can publish directly to this site over HTTP. Read this whole block before you write any code, then follow it exactly — every rule below is enforced server-side, not a suggestion.
ENDPOINT
Create POST https://preview.erikbethke.com/api/posts
Update PUT https://preview.erikbethke.com/api/posts/<slug>
Delete DELETE https://preview.erikbethke.com/api/posts/<slug>?kind=<kind> (soft delete; append &force=true to hard-delete)
AUTH
Authorization: Bearer <YOUR_TOKEN>
A token looks like fxp_<id>.<secret>. You do not mint your own — it is issued
by the site owner (see "For operators" below) and handed to you, usually as an
environment variable.
IDEMPOTENCY
Send an Idempotency-Key header on every write. Derive it from the request's own content
— e.g. the slug plus a hash of the body — rather than a random value, so retrying
after a timeout or a 5xx is always safe: the same key and the same body replay the original
response with an Idempotent-Replayed: true header instead of creating a duplicate. The same
key with a DIFFERENT body is refused outright (422 idempotency_key_reused) — mint a new
key if the request is meant to change.
SUBMISSION BODY (POST and PUT), as JSON:
{
"kind": "insight", // insight | news | press-release | research-report — "page" is not a post; pages stay in the git lane
"slug": "your-post-slug", // lowercase-kebab-case; fixed at creation, PUT cannot move it
"title": "…", // <= 300 characters
"dek": "…", // <= 1000 characters; the one- or two-sentence summary
"body": { "format": "html", "source": "<p>…</p>" }, // format is "html" or "markdown" — "canvas" is refused, see below
"authors": ["a-person-slug", { "guest": "A Guest Name" }], // optional; a guest is a name only, never a profile page
"taxonomy": {
"companies": ["nvidia"], // slugs — an unknown company is rejected, not invented
"practiceAreas": ["ai"], // codes or slugs — unknown ones are rejected
"featuredPeople": [], // person slugs — must resolve
"insightType": "…", // optional, free text
"verticals": [],
"tags": ["free-text-tag"] // created on first use
},
"seo": { "title": "…", "description": "…" }, // optional overrides only — there is no canonical override; the URL is derived from kind + slug
"status": "draft" // draft | pending | publish
}
HTML RULES (format: "html")
Only these survive a server-side sanitizer; everything else is stripped (with its contents,
for script/style/iframe/object/form and the like) or unwrapped: p, br, hr, h2, h3, h4, ul,
ol, li, blockquote, figure, figcaption, img, a, strong, em, b, i, u, s, sub, sup, code, pre,
and the full table family (table, caption, colgroup, col, thead, tbody, tfoot, tr, th, td).
h1/h5/h6 are demoted to h2/h4. <a> keeps only href/title/class; <img> keeps only
src/alt/title/width/height — no onclick, no style, no target. Every href or src must be
http(s), mailto:, tel:, a root-relative path, or a #fragment. A TABLE NEEDS REAL
<table> MARKUP — there is no markdown-table support anywhere on this site.
MARKDOWN RULES (format: "markdown")
No raw HTML tags and no { or } anywhere in the source — either one is refused outright.
That also rules out a table in a markdown body (see above): use format: "html" instead.
NOT ACCEPTED HERE
format: "canvas" is refused (422 canvas_not_supported). A canvas document is a complete
standalone HTML page and needs a sandboxed renderer this lane doesn't have yet —
that stays a git-lane, reviewed-pull-request thing.
A future publishAt is refused (422 scheduling_not_available). Publish now, or leave status
as draft/pending and have a person publish it later.
IF IT FAILS
401 unauthenticated the token is missing, malformed, or wrong — check Authorization
403 forbidden the token's principal lacks the capability this needs (e.g. it can create but not publish)
409 duplicate_slug that slug already exists for this kind — pick another, or PUT to the existing one
422 scan_blocked the body reads like a credential, an internal hostname, or similar — find it and remove it
422 unknown_taxonomy a company, practice-area or featured-person slug doesn't exist — drop it or fix it
422 idempotency_key_reused this key was already used with a different body — mint a new one
429 (Retry-After: N) over the daily quota — wait N seconds, then retry with the SAME idempotency key
NEVER include a credential, an internal hostname, or an unreleased customer name in a
submission — the server scans every field before it stores anything and blocks the
request outright when it finds one.
The same four calls, run straight from a terminal.
export SITE="https://preview.erikbethke.com" # or your own site
export TOKEN="<YOUR_TOKEN>"
# Create a draft insight
curl -sS -X POST "$SITE/api/posts" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-first-post-v1" \
-d @- <<'JSON'
{
"kind": "insight",
"slug": "my-first-post",
"title": "My first post",
"dek": "A short summary of what this post covers.",
"body": { "format": "html", "source": "<p>Hello from an AI-assisted publish.</p>" },
"taxonomy": { "tags": ["hello-world"] },
"status": "draft"
}
JSON
# Replay: run the exact command above again, unchanged. The response comes back
# byte-identical, plus an Idempotent-Replayed: true header — nothing is created twice.
# Edit the title and publish it
curl -sS -X PUT "$SITE/api/posts/my-first-post" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: my-first-post-edit-1" \
-d '{"kind":"insight","slug":"my-first-post","title":"My first post, now live","dek":"A short summary of what this post covers.","body":{"format":"html","source":"<p>Hello from an AI-assisted publish.</p>"},"taxonomy":{"tags":["hello-world"]},"status":"publish"}'
# Soft-delete it (restorable); append &force=true to hard-delete instead
curl -sS -X DELETE "$SITE/api/posts/my-first-post?kind=insight" \
-H "Authorization: Bearer $TOKEN" -H "Idempotency-Key: my-first-post-delete-1"
Frontmatter in, a live post out. A .html file sends as HTML; anything else sends as markdown. The idempotency key is derived from the file's path and content, so re-running after a timeout is always safe.
export FUTURUM_SITE_URL="https://preview.erikbethke.com" # or your own site
export FUTURUM_PUBLISH_TOKEN="<YOUR_TOKEN>"
# Print the request and its Idempotency-Key without sending it
pnpm publish:post content/drafts/my-post.md --dry-run
# Publish it for real — frontmatter (kind, slug, title, dek, status, taxonomy…) becomes the Submission
pnpm publish:post content/drafts/my-post.md
# Edit the same file later, matched by its frontmatter kind + slug
pnpm publish:post content/drafts/my-post.md --update
Run by the site owner, never by the AI itself — this is the one place a credential is minted, and it needs infrastructure access the other three blocks don't. The token prints once, to stdout, and is never logged.
# Issue a credential for a new agent, capped at 50 writes a day
pnpm publishing:principal create my-agent --role agent --caps create,publish --quota 50
# Revoke it
pnpm publishing:principal disable my-agent
publishAt is refused, full stop)
· the WordPress-compatible adapter and its signed webhook back to Polaris ·
authenticated MCP write tools · canvas documents on this API lane (a canvas page
stays a git-lane, reviewed thing until a sandboxed renderer exists). Image upload
isn't wired up either — there is no featuredImage media endpoint
live yet — but a plain <img> with an absolute https://
src in an HTML body works today; the sanitizer keeps src,
alt, title, width and height.
Issue #121: the security and infrastructure work for moving this site to Futurum's own AWS account, safer to do on a freshly created environment than to migrate in place. Every item below is still open.
added 2026-09-28, from a broader mail-security scan