diff --git a/CHANGELOG.md b/CHANGELOG.md
index 77d008f0..9f569270 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,19 @@ Historical entries retain their original delivery coordinates.
## Unreleased
+## 0.18.65
+
+Make GhostGet's articles and comparisons calmer, clearer, and more consistent.
+
+- Rewrite the blog and essays around useful explanations, with accurate sources
+ and independent AI review records.
+- Add a consistent SlopCamera illustration series with responsive images,
+ including the missing image for the web-access guide.
+- Keep linked headings below navigation and settled analytics preferences in
+ the footer, clear of reading content.
+- Remove private values embedded in analytics property names, including nested
+ records, before sending website events.
+
## 0.18.64
Supported global CLI installs update to the latest stable release before an
diff --git a/README.md b/README.md
index f25bf4aa..289a2bb6 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Install [Bun 1.3.14](https://bun.sh/docs/installation) if needed, then install
GhostGet and read a public page:
```sh
-bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
+bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.65/hraness-ghostget-0.18.65.tgz
ghostget read https://example.com
```
@@ -49,9 +49,9 @@ which always names the latest published release. Upgrading from Wrench? Read the
The optional Agent Skill teaches your agent when and how to use GhostGet:
```sh
-npx skills add hraness/ghostget#v0.18.64
+npx skills add hraness/ghostget#v0.18.65
# With Bun instead:
-bunx skills add hraness/ghostget#v0.18.64
+bunx skills add hraness/ghostget#v0.18.65
```
Start a new agent session, then ask: “Use GhostGet to read https://example.com
@@ -183,7 +183,7 @@ firewall.
## Built-in provider catalog
-This v0.18.64 source tree supports executable actions for 21 services: Beeper,
+This v0.18.65 source tree supports executable actions for 21 services: Beeper,
Bluesky, ClasificadosOnline, Facebook, Facebook Groups, Facebook Marketplace,
GitHub, Gmail, Hacker News, Instagram, iMessage, LinkedIn, Reddit, Substack,
Threads, TikTok, Twitch, WebMCP Registry, WhatsApp, X, and YouTube. LinkedIn
@@ -267,7 +267,7 @@ For that same released coordinate, install GhostGet in an agent or application
that owns its own model, planning, tool loop, approvals, and interface:
```sh
-bun add https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
+bun add https://github.com/hraness/ghostget/releases/download/v0.18.65/hraness-ghostget-0.18.65.tgz
```
```ts
diff --git a/STYLE.md b/STYLE.md
index 58e5f9bd..ad4b21ca 100644
--- a/STYLE.md
+++ b/STYLE.md
@@ -1,10 +1,10 @@
# Public writing style
-This guide covers everything written for readers outside a repository: product pages, documentation, READMEs, interface text, metadata, and text a model writes for publication. Apply the voice rules in [`WRITING.md`](WRITING.md) first. The [documentation guidelines](https://github.com/hraness/.github/blob/main/DOCUMENTATION_GUIDELINES.md) choose a document's purpose and shape, and the [README guidelines](https://github.com/hraness/.github/blob/main/README_GUIDELINES.md) cover the repository front door.
+This guide covers everything written for readers outside a repository: product pages, documentation, READMEs, interface text, metadata, and text a model writes for publication. Apply the voice rules in [`WRITING.md`](WRITING.md) first. The [documentation guidelines](DOCUMENTATION_GUIDELINES.md) choose a document's purpose and shape, and the [README guidelines](README_GUIDELINES.md) cover the repository front door.
Public prose must be precise, useful, and free of hype. Use a direct, natural voice that reads well aloud.
-This is a synced copy of the [Hraness public writing style](https://github.com/hraness/.github/blob/main/STYLE.md), kept here so agents can read it offline. Repository-specific rules appear under “Repository additions” below.
+This is the canonical copy. Repositories keep a synced copy so agents can read it offline; rules that apply to one repository go under “Repository additions” at the end of that copy.
## Leave the reader with a clearer model
@@ -120,11 +120,20 @@ Most Hraness copy is drafted by agents working inside repository guides full of
- Use shared foreground and surface pairs for interactive controls. Control text and glyphs must reach at least 4.5:1 contrast in both themes, including selected, hover, and focus states. Never assume white is readable on a brand accent.
- Review the page as a new visitor at desktop and phone widths. Confirm that the headline, example, and next action make sense before reading the documentation, and that essential limits appear beside the claims they qualify.
+## Write evergreen explanations
+
+- Give an educational article one useful question, a clear answer, and a concrete example the reader can reason through or apply. Explain the cause, choice, or tradeoff; a feature list with an introduction is not an explanation.
+- Write about the enduring idea and the public behavior a reader can use. Keep implementation diaries, internal file paths, test totals, task records, deployment history, and plans out of the article body. Link to public reference material when exact syntax or architecture helps the reader go further.
+- Keep release chronology in release notes and current setup requirements beside the relevant command. Do not date an evergreen explanation with “as of”, “recently”, a build number, or the author's review date. Preserve real publication and review dates in their metadata; include a historical date in prose only when the event's timing is part of the explanation.
+- State claims at their supported scope. Remove repeated permissions language, generic disclaimers, and defensive lists of things the article does not establish. Keep a material limitation beside the decision it changes, and link to detailed policy or reference where needed.
+- Check product names, destination links, public behavior, and image credits together. Replace a stale explanation rather than adding a caveat about its age. Never relabel historical evidence or a tool-generated asset as if it came from a different source.
+- Improve the existing collection before adding articles. Add a post for a distinct reader question that existing pages do not answer, with its own example and sources. Do not create thin variations to fill a series or repeat the product's pitch.
+
## State each limit once
Readers trust a page that states its limits plainly. They skim a page that repeats them.
-- State the product's status once, near the top, with one of these labels: *In development*, *Preview*, *Beta*, *Latest release: vX.Y.Z*, *Paused*, or *Retired*. Follow it with one sentence on how to install or use it today, such as “Install from source; there is no signed release yet.”
+- State release status where it changes how someone can use the product. Keep the current version with installation instructions and explain an unavailable capability beside the affected action. Do not add a release label or status paragraph to every educational page.
- Put each other limit beside the feature it limits, once. Link to the status or limits page instead of restating the caveat in each section. Never drop a true limit to make the copy read better.
- Write a claim at its true scope instead of following it with what it does not prove. “Tests cover local networks only” replaces “These are tested local cases, not hosted private networking or evidence about independent devices.”
- State a privacy or scope rule once, positively (“Only documents you choose to publish become public”), and keep the full list of exclusions on the privacy or security page.
@@ -164,7 +173,7 @@ Readers trust a page that states its limits plainly. They skim a page that repea
- Put literal input and interface values in `code`.
- Use an ellipsis glyph (`…`) only when an action opens another input step.
- Do not use em dashes in authored text: prose, titles, meta descriptions, social text, alt text, captions, image credits, list separators, and the templates that generate them. Rewrite the sentence instead of substituting a spaced hyphen. Quoted third-party titles keep their own punctuation. Use parentheses only for a short, necessary explanation.
-- Use each product's prose name exactly as its messaging record spells it (`names.name`), including case (xcb, Textbutler, AI Charts, Soundfish, Sys1). The all-capitals `names.catalog` form belongs only in designs that set every name in capitals. Do not use the repository slug or the domain as the name in prose, and do not use a product name as a common noun.
+- Use each product's prose name exactly as its messaging record spells it (`names.name`), including case. The all-capitals `names.catalog` form belongs only in designs that set every name in capitals. Do not use the repository slug or the domain as the name in prose, and do not use a product name as a common noun.
- Give each destination one label across the header, footer, breadcrumbs, and Markdown twins.
- Make interpolated counts agree with their nouns (“1 check”, “2 checks”), and test zero, one, and several.
- Spell out zero through nine in prose. Use numerals for 10 or more, measurements, dates, and money.
@@ -177,6 +186,22 @@ Readers trust a page that states its limits plainly. They skim a page that repea
- Preserve meaningful evidence captions, legal notices, and actual feature limits. State each once beside the claim it qualifies.
- Credit tools and models by their current names.
+## Keep article illustrations coherent
+
+- Give a product's article collection one authored visual direction: consistent drawing technique, line weight, texture, framing, and degree of abstraction. Start with a reviewed image from the collection and the product's brand palette.
+- Use a quiet neutral ground, one dominant brand color, and at most one supporting accent. Keep the accent subordinate and use tonal variations for depth. Avoid unrelated bright colors, glossy stock-art treatments, and a different style for each topic.
+- Generate editorial artwork through [SlopCamera](https://slopcamera.com). Retain its authored prompt, reference assets, and generation record with the source. Inspect the image at article and card sizes in both page themes before accepting it.
+- Make each illustration explain the article's central idea through one clear visual relationship. Avoid generic technology collages, decorative interface fragments, and text baked into images. A diagram may use labels when those labels carry the explanation.
+- Give every illustrated article a complete, intentional card and lead-image treatment. Preserve the actual generation history when replacing an old asset; credit the tool that made the new image and link its current public site.
+
+## Write social text
+
+- Posts on X, Bluesky, Threads, and LinkedIn follow every rule here, including no em dashes and no exclamation marks.
+- Use no emoji, hashtags, or thread numbering unless a repository addition allows them for one channel.
+- Keep image alt text to 125 characters or fewer.
+- Link the canonical URL without tracking parameters.
+- Launch posts follow the launch beats and social posts addendum in [`GENERATION_STYLE.md`](GENERATION_STYLE.md), and channel limits are in [`MESSAGING.md`](MESSAGING.md).
+
## Write focused documentation
- Decide whether a page is a tutorial, how-to guide, explanation, or reference.
@@ -196,7 +221,7 @@ Readers trust a page that states its limits plainly. They skim a page that repea
- Use one literal heading for the object, task, data view, or state.
- Add supporting text only for a distinct instruction, constraint, status, or scope.
- Name the action, object, current state, limit, or recovery step.
-- Do not narrate the interface or repeat visible information.
+- Do not narrate the interface or repeat visible information. Omit labels announcing how many cards are on screen or describing an obvious preview; retain counts only when they help navigation, selection, or comparison.
- Keep normal readiness silent. Show status text for pending work, important results, or problems that the reader can fix.
- Add search only when the collection is too large or varied for direct selection.
- Move secondary actions and settings out of persistent primary controls.
@@ -211,7 +236,7 @@ Readers trust a page that states its limits plainly. They skim a page that repea
- Name the consequence in a confirmation. Repeat the exact verb and object for a destructive action.
- Use nouns for labels. Use placeholders for a format or example, not a repeated label.
- State the completed result in past tense in a toast notification.
-- Follow [`CLI_MENU_STYLE.md`](https://github.com/hraness/.github/blob/main/CLI_MENU_STYLE.md) for command-line output, menu bar menus, and macOS permission notices.
+- Follow [`CLI_MENU_STYLE.md`](https://github.com/hraness/.github/blob/main/CLI_MENU_STYLE.md) for command-line output, the shared status and control commands, and macOS permission notices.
## Vary a generated series
@@ -236,7 +261,7 @@ A prompt, skill, or template that makes a model write published text is public c
- Read a sample of real outputs after every prompt change.
- Tell the model who reads the output and that the reader has not seen the inputs or the instructions. Name every field that is published, including rationales and labels.
- Set length limits as maximums. A minimum longer than the evidence forces padding.
-- Include the shared generation block from [`GENERATION_STYLE.md`](https://github.com/hraness/.github/blob/main/GENERATION_STYLE.md) and record its version with the prompt version.
+- Include the shared generation block from [`GENERATION_STYLE.md`](GENERATION_STYLE.md) and record its version with the prompt version.
- Check the prompt, skill, and examples for the patterns they forbid; a prompt that uses em dashes and staged contrasts produces them.
## Keep tests and guides from freezing copy
@@ -248,7 +273,7 @@ A prompt, skill, or template that makes a model write published text is public c
## Say who wrote and who checked
-- Show AI-drafting disclosure on hraness.com through its shared disclosure component, on every page with AI-drafted text. Essays and blog posts on any Hraness site also show the provenance note from [`GENERATION_STYLE.md`](https://github.com/hraness/.github/blob/main/GENERATION_STYLE.md), naming the recorded reviewer. Other pages on other Hraness sites and products do not carry AI-drafting disclosures, labels, or badges.
+- Show AI-drafting disclosure on hraness.com through its shared disclosure component, on every page with AI-drafted text. Essays and blog posts on any Hraness site also show the provenance note from [`GENERATION_STYLE.md`](GENERATION_STYLE.md), naming the recorded reviewer. Other pages on other Hraness sites and products do not carry AI-drafting disclosures, labels, or badges.
- Everywhere, keep a record of who drafted and who reviewed generated or agent-drafted text: the author, an independent human, or an AI agent, by name.
- Never credit AI-drafted text to a person as its sole author, never describe AI review as human review, and never claim a review that has no record. A page without a review record makes no review claim.
- Text an agent posts from a person's account does not claim that person wrote AI-drafted work.
@@ -258,5 +283,5 @@ A prompt, skill, or template that makes a model write published text is public c
### Generated pages and status notes
- The WebMCP provider pages are generated from third-party registry data. Every interpolated count agrees with its noun (test zero, one, and several), third-party text is clipped at a word boundary and ends with “…”, and a page promises use (“Use X with your agent”) only when the registry lists a tool the agent can call.
-- A review note states which Ghostget release a page describes. It does not say the page “was checked” unless a review record exists.
+- Keep release versions and check dates in reference material and review metadata. Evergreen editorial pages omit publication, update, and review-date chrome. Cite dates in the prose only when the date affects the explanation.
- `website/AGENTS.md` lists the internal words that most often reach ghostget.com and what to write instead.
diff --git a/bun.lock b/bun.lock
index e6683614..2d56de90 100644
--- a/bun.lock
+++ b/bun.lock
@@ -21,7 +21,7 @@
"typescript": "6.0.3",
},
"devDependencies": {
- "@hraness/design-kit": "github:hraness/design-kit#v0.35.0",
+ "@hraness/design-kit": "github:hraness/design-kit#v0.35.2",
"@hraness/direct": "https://github.com/hraness/direct/releases/download/v0.7.21/hraness-direct-0.7.21.tgz",
"@hraness/posthog": "https://github.com/hraness/posthog/releases/download/v0.3.9/hraness-posthog-0.3.9.tgz",
"@hraness/site-footer": "https://github.com/hraness/site-footer/releases/download/v0.20.5/hraness-site-footer-0.20.5.tgz",
@@ -131,7 +131,7 @@
"@hraness/cli-update": ["@hraness/cli-update@https://github.com/hraness/cli-update/releases/download/v0.1.0/hraness-cli-update-0.1.0.tgz", {}, "sha512-g0Bg0FYiyJqEJC9JGtAZsZ+r599c8iSNDd7ADi6weFCKBGuRXa2tOnn93+pTfLe1tcayXiLuAtDPp8meFHgKbg=="],
- "@hraness/design-kit": ["@hraness/design-kit@github:hraness/design-kit#1f4aa08", { "dependencies": { "@hugeicons/core-free-icons": "4.2.3", "@stylexjs/stylex": "0.19.0", "motion": "12.42.0", "next-themes": "0.4.6", "react-aria-components": "1.19.0", "recharts": "3.8.0", "sugar-high": "^1.2.1", "web-haptics": "0.0.6" }, "peerDependencies": { "@hraness/ui": ">=0.5.16 <0.6.0", "react": ">=18 <20", "react-dom": ">=18 <20" }, "optionalPeers": ["@hraness/ui"] }, "hraness-design-kit-1f4aa08", "sha512-3Xa04ZJGeSFcwWJ+ncG5opo2lp8op4dLqdzjVokoVe4Itd3YUHNtJbOk9wb5t9TF8EX1T7jWfWlJCkSTmejkPA=="],
+ "@hraness/design-kit": ["@hraness/design-kit@github:hraness/design-kit#229720e", { "dependencies": { "@hugeicons/core-free-icons": "4.2.3", "@stylexjs/stylex": "0.19.0", "motion": "12.42.0", "next-themes": "0.4.6", "react-aria-components": "1.19.0", "recharts": "3.8.0", "sugar-high": "^1.2.1", "web-haptics": "0.0.6" }, "peerDependencies": { "@hraness/ui": ">=0.5.16 <0.6.0", "react": ">=18 <20", "react-dom": ">=18 <20" }, "optionalPeers": ["@hraness/ui"] }, "hraness-design-kit-229720e", "sha512-GTknQ8JDWEVucTlpy+mEpipgc4vVOanbwp0k9R6/w93KsKSXL/xdpuCoZBJh3XUbSFs5HdrIXI2s5IldmbPovQ=="],
"@hraness/desktop-foundation": ["@hraness/desktop-foundation@https://github.com/hraness/desktop-foundation/releases/download/v0.9.0/hraness-desktop-foundation-0.9.0.tgz", { "bin": { "companion": "./dist/src/cli.js" } }, "sha512-+TlrHoM1wZ2rE4p3u1CIAz1+aGKPZhmiNOKjEKOYPZmTx8BFSyFLPeL06tupfX2WxcRzRZ3QwmOsyjlgSu/p1Q=="],
diff --git a/dist/apple-photos-client.js b/dist/apple-photos-client.js
index 285d306b..af69dfd3 100644
--- a/dist/apple-photos-client.js
+++ b/dist/apple-photos-client.js
@@ -1,7 +1,7 @@
// @bun
import {
GHOSTGET_VERSION
-} from "./index-rayqxqrk.js";
+} from "./index-4wh7rchw.js";
import {
canonicalJson,
sha256
diff --git a/dist/beeper-client.js b/dist/beeper-client.js
index 9ffb8b60..93519b30 100644
--- a/dist/beeper-client.js
+++ b/dist/beeper-client.js
@@ -4,7 +4,7 @@ import {
} from "./index-26yq8q16.js";
import {
GHOSTGET_VERSION
-} from "./index-rayqxqrk.js";
+} from "./index-4wh7rchw.js";
import {
canonicalJson,
canonicalJsonSha256Matches,
diff --git a/dist/index-rayqxqrk.js b/dist/index-4wh7rchw.js
similarity index 62%
rename from dist/index-rayqxqrk.js
rename to dist/index-4wh7rchw.js
index c96cb4a8..34940d51 100644
--- a/dist/index-rayqxqrk.js
+++ b/dist/index-4wh7rchw.js
@@ -1,5 +1,5 @@
// @bun
// src/version.ts
-var GHOSTGET_VERSION = "0.18.64";
+var GHOSTGET_VERSION = "0.18.65";
export { GHOSTGET_VERSION };
diff --git a/docs/contracts.md b/docs/contracts.md
index 48bd1fa0..d8608e1d 100644
--- a/docs/contracts.md
+++ b/docs/contracts.md
@@ -128,7 +128,7 @@ matches some IDs lists the installed ones and exits 0.
{
"ok": true,
"contract": "ghostget.contract-catalog.v1",
- "ghostget": { "version": "0.18.64" },
+ "ghostget": { "version": "0.18.65" },
"generatedAt": "2026-09-21T20:00:00.000Z",
"vocabulary": {
"risks": ["R1", "R2", "R3", "R4"],
@@ -186,7 +186,7 @@ does not parse is an error (exit 3) and no catalog is read.
{
"ok": false,
"contract": "ghostget.contract-check.v1",
- "ghostget": { "version": "0.18.64" },
+ "ghostget": { "version": "0.18.65" },
"plan": { "collectionKey": "hraness-social-profile-statistics", "reads": 15 },
"reads": [
{ "index": 0, "accountKey": "x-hraness", "adapter": "x-web", "operation": "profiles.read",
diff --git a/docs/publishing.md b/docs/publishing.md
index 933824f0..02d37c91 100644
--- a/docs/publishing.md
+++ b/docs/publishing.md
@@ -243,12 +243,12 @@ delivery proceeds through a new source-qualified version.
## Install the canonical release
-These commands require the matching published immutable v0.18.64 release.
+These commands require the matching published immutable v0.18.65 release.
For the CLI:
```sh
-bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
+bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.65/hraness-ghostget-0.18.65.tgz
ghostget --version
ghostget doctor --json
```
diff --git a/kb/launch/social-kit.md b/kb/launch/social-kit.md
index cf3c5a98..2579d125 100644
--- a/kb/launch/social-kit.md
+++ b/kb/launch/social-kit.md
@@ -57,7 +57,7 @@ The plan is for GhostGet to stay small. Your agent does the thinking, and GhostG
Post 9 of 9, 192 characters
```text
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.65.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -115,7 +115,7 @@ The plan is for GhostGet to stay small. Your agent does the thinking, and GhostG
Post 9 of 9, 192 characters
```text
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.65.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -173,7 +173,7 @@ The plan is for GhostGet to stay small. Your agent does the thinking, and GhostG
Post 9 of 9, 192 characters
```text
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.65.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -197,7 +197,7 @@ GhostGet is for Claude Code, Codex, Cursor, and other agents that run commands o
The plan is for GhostGet to stay small. Your agent does the thinking, and GhostGet runs only actions someone has reviewed. Each new service arrives as reviewed actions with their own previews.
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.65.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -224,8 +224,8 @@ Topics: Developer Tools, Artificial Intelligence, Open Source
- Sometimes a post goes through but the answer gets lost on the way back. GhostGet writes down every send before it leaves and never sends it again on its own until it knows what happened.
- Anything beyond a read starts as a preview that shows the service, the account, and exactly what will be sent. Your agent can prepare it. Nothing is sent until someone confirms that exact preview.
- GhostGet is for Claude Code, Codex, Cursor, and other agents that run commands on your Mac or Linux machine and need to read the web and use the accounts you already have.
-- GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
-- Latest release: v0.18.64. https://ghostget.com/blog/introducing-ghostget/
+- GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.65.
+- Latest release: v0.18.65. https://ghostget.com/blog/introducing-ghostget/
## Beats
@@ -251,4 +251,4 @@ Topics: Developer Tools, Artificial Intelligence, Open Source
- claimsTotal: 247. verification/claims.json, every claim
- claimsEvidenced: 228. verification/claims.json, status evidenced
- claimsConfigReadback: 15. verification/claims.json, layer configuration-readback
-- status: Latest release: v0.18.64. package.json version
+- status: Latest release: v0.18.65. package.json version
diff --git a/package.json b/package.json
index 27e3e5bc..8390b3d2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@hraness/ghostget",
- "version": "0.18.64",
+ "version": "0.18.65",
"description": "GhostGet gives your AI agent named web actions: read a page, archive one media item, or use a connected account, without credentials or a browser to steer.",
"license": "MIT",
"type": "module",
@@ -710,7 +710,7 @@
"@hraness/cli-update": "https://github.com/hraness/cli-update/releases/download/v0.1.0/hraness-cli-update-0.1.0.tgz"
},
"devDependencies": {
- "@hraness/design-kit": "github:hraness/design-kit#v0.35.0",
+ "@hraness/design-kit": "github:hraness/design-kit#v0.35.2",
"@hraness/direct": "https://github.com/hraness/direct/releases/download/v0.7.21/hraness-direct-0.7.21.tgz",
"@hraness/posthog": "https://github.com/hraness/posthog/releases/download/v0.3.9/hraness-posthog-0.3.9.tgz",
"@hraness/site-footer": "https://github.com/hraness/site-footer/releases/download/v0.20.5/hraness-site-footer-0.20.5.tgz",
diff --git a/scripts/npm-release-workflow.test.ts b/scripts/npm-release-workflow.test.ts
index 01e65a9f..2f285233 100644
--- a/scripts/npm-release-workflow.test.ts
+++ b/scripts/npm-release-workflow.test.ts
@@ -1220,7 +1220,7 @@ describe("npm publication contract", () => {
(MAX_UNPACKED_BYTES + MAX_PACKED_ENTRIES * 1_023 + 1_024) / 512,
) * 512,
);
- expect(MAX_PACKAGE_TAR_BYTES).toBe(24_695_296);
+ expect(MAX_PACKAGE_TAR_BYTES).toBe(24_695_808);
expect(MAX_PACKAGE_TAR_BYTES % 512).toBe(0);
expect(artifact).toContain("maxOutputLength: MAX_PACKAGE_TAR_BYTES");
expect(artifact).not.toContain("const maximumTarBytes");
@@ -1430,8 +1430,8 @@ describe("npm publication contract", () => {
expect(budget).toContain("785b8fa60c329d7ac46bc8fcf4d959b5fa9d96455bba9e7cdea63f6a3827c4f6");
expect(Object.isFrozen(repairPackageMeasurement)).toBeTrue();
expect(repairPackageMeasurement).toMatchObject({
- archiveSha256: "99bdbc86d7e2016b7a003496d4d6a2bec847d6c8c3b01aed57c1b55a3b25cbb3",
- packedBytes: 12_154_912, unpackedBytes: 24_054_352, entryCount: 625,
+ archiveSha256: "09e69ff231a08b63c399662cdcb0fe9a08c5f5faa563e5e855e2c6b832e8003f",
+ packedBytes: 12_155_065, unpackedBytes: 24_054_930, entryCount: 625,
packedPlatformProjection: 12_387, packedPortabilityAllowance: 4_096,
payloadPlatformProjection: 353, payloadAllowance: 65,
});
@@ -1447,8 +1447,8 @@ describe("npm publication contract", () => {
expect(budget).toContain("24,024,705 + 353 + 65 = 24,025,123 unpacked");
expect(budget).toContain("12,152,562 + 12,387 + 4,096 = 12,169,045 packed");
expect(budget).toContain("24,093,786 + 353 + 65 = 24,094,204 unpacked");
- expect(MAX_PACKED_BYTES).toBe(12_171_395);
- expect(MAX_PACKED_BYTES).toBe(12_154_912 + 12_387 + 4_096);
+ expect(MAX_PACKED_BYTES).toBe(12_171_548);
+ expect(MAX_PACKED_BYTES).toBe(12_155_065 + 12_387 + 4_096);
expect(budget).toContain("aa127b3193c9bb3b0cb5deece5927be60ccb7111a50169320d322ffdeaa13f39");
expect(budget).toContain("0c331bab3ab3df69a108e18f5f29845b0db90c281cbd6455c0d90fa0b24081e2");
expect(budget).toContain("873cad8139fda303e2d19c6afd61cf549cf9b4d1d76b2a1d6d632a6afe6bd0d1");
@@ -1569,7 +1569,7 @@ describe("npm publication contract", () => {
expect(budget).toContain("47684b3e2eb5cf3ed07fbb520aade8c7251d993f75262fbf1af627d9081a1a5f");
expect(budget).toContain("23,688,277 + 353 + 65 = 23,688,695");
expect(budget).toContain("23,759,283 + 353 + 65 = 23,759,701");
- expect(MAX_UNPACKED_BYTES).toBe(24_054_770);
+ expect(MAX_UNPACKED_BYTES).toBe(24_055_348);
expect(budget).toContain("23,037,873 + 65 = 23,037,938");
expect(budget).toContain("f9f3ab38a682690ceaa2699a7309997512030f0fa500a9dc29dcd108123dc41f");
expect(budget).toContain("23,038,557 + 65 = 23,038,622");
@@ -1602,7 +1602,7 @@ describe("npm publication contract", () => {
expect(budget).toContain("01875f12ab73a49d6c7d6bf520dc3d318db816addee2fa7981889f35c958cf7c");
expect(budget).toContain("b12909f08f7c19460ced56e30619f4860a1183f4b0106170c07837dae577a937");
expect(budget).toContain("0b212ac291218528dcf979370110a36f10850e046ca90a536057d9a44e807d1d");
- expect(MAX_UNPACKED_BYTES).toBe(24_054_352 + 353 + 65);
+ expect(MAX_UNPACKED_BYTES).toBe(24_054_930 + 353 + 65);
expect(budget).toContain("22,794,052 + 65 = 22,794,117");
expect(budget).toContain("c482efe748f880e3717727d6d39fd92a68953e6eea766642b329ba47ae772d80");
expect(budget).toContain("22,759,423 + 65 = 22,759,488");
@@ -1638,8 +1638,8 @@ describe("npm publication contract", () => {
expect(packageArtifactBudget).toEqual({
entryCount: { min: 625, max: 625 },
fileCount: { min: 625, max: 625 },
- packedBytes: { min: 1_600_000, max: 12_171_395 },
- unpackedBytes: { min: 9_000_000, max: 24_054_770 },
+ packedBytes: { min: 1_600_000, max: 12_171_548 },
+ unpackedBytes: { min: 9_000_000, max: 24_055_348 },
});
});
diff --git a/scripts/package-budget.ts b/scripts/package-budget.ts
index 904535c2..27bb3ca5 100644
--- a/scripts/package-budget.ts
+++ b/scripts/package-budget.ts
@@ -2382,21 +2382,29 @@
// SHA-256 ed461e46d1d9d4514cffe577415bd641d3cbfbd1c3cde0619d0fe55acf6170bb.
// The forward 0.18.64 updater preserves that studio source and failed-tag
// history. Two identical packs under the same toolchain measured 625 entries,
-// 12,154,912 packed and 24,054,352 payload bytes; SHA-256 below. The measured
-// addition is one src/update.ts file, 2,069 packed and 7,325 payload bytes.
+// 12,154,912 packed and 24,054,352 payload bytes; SHA-256
+// 99bdbc86d7e2016b7a003496d4d6a2bec847d6c8c3b01aed57c1b55a3b25cbb3.
+// The measured addition is one src/update.ts file, 2,069 packed and 7,325 payload bytes.
// The 425-byte payload increase over the previous updater candidate is solely
// CHANGELOG.md, retaining studio changes and the annotated-tag failure.
// Preserve the same projections and allowances:
// 12,154,912 + 12,387 + 4,096 = 12,171,395 packed;
// 24,054,352 + 353 + 65 = 24,054,770 payload.
+// The 0.18.65 editorial release preserves that runtime and file inventory.
+// Two byte-identical packs with the same qualified toolchain measured 625
+// entries, 12,155,065 packed and 24,054,930 payload bytes. The 578-byte payload
+// increase is the new changelog entry; article and image files are website-only.
+// Retain every projection and allowance:
+// 12,155,065 + 12,387 + 4,096 = 12,171,548 packed;
+// 24,054,930 + 353 + 65 = 24,055,348 payload.
export const repairPackageMeasurement = Object.freeze({
- scope: "Ghostget 0.18.64 CLI updater over preserved tagged studio main773c537",
+ scope: "GhostGet 0.18.65 editorial release over the verified 0.18.64 runtime",
command: "npm pack --ignore-scripts",
npmVersion: "11.19.0",
platform: "darwin-arm64",
- archiveSha256: "99bdbc86d7e2016b7a003496d4d6a2bec847d6c8c3b01aed57c1b55a3b25cbb3",
- packedBytes: 12_154_912,
- unpackedBytes: 24_054_352,
+ archiveSha256: "09e69ff231a08b63c399662cdcb0fe9a08c5f5faa563e5e855e2c6b832e8003f",
+ packedBytes: 12_155_065,
+ unpackedBytes: 24_054_930,
entryCount: 625,
packedPlatformProjection: 12_387,
packedPortabilityAllowance: 4_096,
diff --git a/skills/ghostget/references/install.md b/skills/ghostget/references/install.md
index 3ebea533..4fbcfaef 100644
--- a/skills/ghostget/references/install.md
+++ b/skills/ghostget/references/install.md
@@ -17,14 +17,14 @@ If Bun is missing, stop and direct the user to the official
[Bun installation guide](https://bun.sh/docs/installation). Do not switch
package managers or pipe an unreviewed installer into a shell.
-This reference is authored for the exact v0.18.64 release coordinate. Use it
+This reference is authored for the exact v0.18.65 release coordinate. Use it
only from the matching release-bound Agent Skill after its canonical archive and
immutable GitHub Release exist. If the coordinate is not public, stop instead
of substituting `main`, another tag, or a different package version. Install
that exact release and verify a public-page read:
```sh
-bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
+bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.65/hraness-ghostget-0.18.65.tgz
ghostget read https://example.com
```
@@ -47,21 +47,21 @@ for the requested workflow; an unconnected provider or missing media tool does
not prevent a public-page read, and overall provider readiness can be false on
a fresh installation.
-The package is `@hraness/ghostget`; `@hraness/ghostget@0.18.64` is an optional npm
+The package is `@hraness/ghostget`; `@hraness/ghostget@0.18.65` is an optional npm
mirror only after verified registry publication. Canonical installation does not
wait for registry publication.
When upgrading from Wrench, use `ghostget` for new commands and
`GHOSTGET_STATE_HOME` for an explicit state root. Existing state is selected in
place; do not rename, copy, or delete a state directory as part of the upgrade.
-The [migration guide](https://github.com/hraness/ghostget/blob/v0.18.64/docs/ghostget-migration.md)
+The [migration guide](https://github.com/hraness/ghostget/blob/v0.18.65/docs/ghostget-migration.md)
explains the retained state aliases and durable protocol names.
Do not clone the repository merely to run the CLI. Importing the SDK is a
separate project dependency and does not install a global command:
```sh
-bun add https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
+bun add https://github.com/hraness/ghostget/releases/download/v0.18.65/hraness-ghostget-0.18.65.tgz
```
`ghostget adapter sync-bundled` upgrades exact bundled baselines, including an
diff --git a/src/beeper-client-types.ts b/src/beeper-client-types.ts
index 21a822e1..6b0b9d96 100644
--- a/src/beeper-client-types.ts
+++ b/src/beeper-client-types.ts
@@ -96,7 +96,7 @@ export type BeeperContactInteractionExportReceipt = Readonly<{
implementation: Readonly<{
producer: Readonly<{
package: "@hraness/ghostget";
- version: "0.18.64";
+ version: "0.18.65";
}>;
officialCli: Readonly<{
implementation: "github.com/beeper/cli";
diff --git a/src/cli.ts b/src/cli.ts
index 9086a455..2fa23163 100755
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -114,7 +114,7 @@ export function isImmediateGhostgetVersionRequest(
&& (second === undefined || second === "--json");
}
-/** `ghostget 0.18.64`, or `{"name":"ghostget","version":"0.18.64"}` with `--json`. */
+/** `ghostget 0.18.65`, or `{"name":"ghostget","version":"0.18.65"}` with `--json`. */
export function ghostgetVersionText(rawArguments: readonly string[]): string {
return rawArguments.includes("--json")
? `${JSON.stringify({ name: "ghostget", version: GHOSTGET_VERSION })}\n`
diff --git a/src/contracts-check.test.ts b/src/contracts-check.test.ts
index 9d1c5f9c..266e3557 100644
--- a/src/contracts-check.test.ts
+++ b/src/contracts-check.test.ts
@@ -35,7 +35,7 @@ describe("checkCollectionPlan", () => {
const check = checkCollectionPlan(examplePlan(), exampleCatalog());
expect(check.ok).toBeTrue();
expect(check.contract).toBe("ghostget.contract-check.v1");
- expect(check.ghostget).toEqual({ version: "0.18.64" });
+ expect(check.ghostget).toEqual({ version: "0.18.65" });
expect(check.plan).toEqual({ collectionKey: "example-social-statistics", reads: 3 });
expect(check.reads[0]).toEqual({
index: 0,
diff --git a/src/contracts.test-support.ts b/src/contracts.test-support.ts
index 52f35f32..392677ce 100644
--- a/src/contracts.test-support.ts
+++ b/src/contracts.test-support.ts
@@ -95,7 +95,7 @@ export function exampleCatalog(): ContractCatalogV1 {
return {
ok: true,
contract: CONTRACT_CATALOG_V1,
- ghostget: { version: "0.18.64" },
+ ghostget: { version: "0.18.65" },
generatedAt: "2026-09-21T20:00:00.000Z",
vocabulary: catalogVocabularyValue,
adapters: [
diff --git a/src/media/manifest.test.ts b/src/media/manifest.test.ts
index 96ab50ec..007018e8 100644
--- a/src/media/manifest.test.ts
+++ b/src/media/manifest.test.ts
@@ -465,7 +465,7 @@ function trackedYtDlpManifest(
describe("Ghostget media manifest", () => {
test("uses one Ghostget-owned schema and transcriber identity", () => {
expect(GHOSTGET_MEDIA_SCHEMA_VERSION).toBe(1);
- expect(GHOSTGET_MEDIA_VERSION).toBe("0.18.64");
+ expect(GHOSTGET_MEDIA_VERSION).toBe("0.18.65");
expect(localTranscriptVariantSegments(localIdentity)).toEqual([
"transcript",
"local",
diff --git a/src/version.ts b/src/version.ts
index e37f2737..05c1d69e 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1,2 +1,2 @@
/** Canonical immutable Ghostget package release identity. */
-export const GHOSTGET_VERSION = "0.18.64" as const;
+export const GHOSTGET_VERSION = "0.18.65" as const;
diff --git a/website/analytics.test.ts b/website/analytics.test.ts
index c03bbf63..71201d42 100644
--- a/website/analytics.test.ts
+++ b/website/analytics.test.ts
@@ -216,6 +216,35 @@ describe("ghostget.com analytics contract", () => {
expect(ATTRIBUTION_PARAMETERS.has("ref")).toBe(false);
});
+ test("drops private data in property names at the root and inside objects and arrays", () => {
+ for (const key of [
+ "0@-.AA",
+ "reader@example.com",
+ "reader%40example.com",
+ "phx_private_key",
+ "Bearer private_access_token",
+ "https://example.com/path?code=private_value",
+ "/path?token=private_value",
+ ]) {
+ const capture = sanitizeCapture({
+ event: "$pageview",
+ properties: {
+ [key]: "present",
+ $current_url: home.href,
+ $lib: "posthog-js",
+ token,
+ utm_source: "news",
+ safe_metric: 1,
+ diagnostic: { [key]: "present", safe_metric: 2, items: [{ [key]: "present", safe_metric: 3 }] },
+ },
+ }, home);
+ expect(capture).not.toBeNull();
+ expect(Object.hasOwn(capture!.properties, key)).toBe(false);
+ expect(capture!.properties.diagnostic).toEqual({ safe_metric: 2, items: [{ safe_metric: 3 }] });
+ expect(capture!.properties).toMatchObject({ $lib: "posthog-js", token, utm_source: "news", safe_metric: 1 });
+ }
+ });
+
test("drops foreign, preview, and local hosts", () => {
for (const href of ["https://preview.example.com/", "https://ghostget-git-x.vercel.app/", "http://localhost:3000/"]) {
expect(sanitizeCapture({ event: "$pageview", properties: { $current_url: href, token } }, evidenceFor("/", href))).toBeNull();
diff --git a/website/blog.test.ts b/website/blog.test.ts
index c3f9e0d8..474922aa 100644
--- a/website/blog.test.ts
+++ b/website/blog.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, test } from "bun:test";
+import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -14,6 +15,7 @@ import {
BLOG_POSTS,
BUILT_ON_RELATIONS,
blogPostPath,
+ blogPostJsonLd,
blogSitemapPaths,
fillBlogShell,
ghostgetRelations,
@@ -27,6 +29,8 @@ import {
type BlogSite,
} from "./blog";
+import { editorialImages, editorialImage, editorialImageUrl, editorialImageSrcSet } from "./editorial-images";
+
const websiteRoot = dirname(fileURLToPath(import.meta.url));
const site: BlogSite = {
description: "GhostGet test site.",
@@ -47,7 +51,7 @@ describe("GhostGet blog admission", () => {
expect(() => assertArticleAdmissions(admissions)).not.toThrow();
for (const post of BLOG_POSTS) {
expect(post.admission.href).toBe(blogPostPath(post.slug));
- expect(post.admission.drafting).toBe("ai-from-source");
+ expect(post.admission.drafting).toBe("ai");
expect(post.admission.review?.reviewerType).toBe("ai");
expect(post.admission.humanReview).toBeNull();
if (post.admission.lifecycle === "indexable") {
@@ -60,24 +64,25 @@ describe("GhostGet blog admission", () => {
test("an AI review is disclosed as AI and never called human", () => {
for (const post of BLOG_POSTS) {
const sentence = articleProvenanceSentence(articleProvenanceFromAdmission(post.admission));
- expect(sentence).toBe(`Drafted with AI from the source code and reviewed by ${post.admission.review?.reviewer}.`);
- expect(sentence).toMatch(/\bClaude\b/u);
+ expect(sentence).toBe(`Drafted with AI and reviewed by ${post.admission.review?.reviewer}.`);
+ expect(sentence).toMatch(/\bAI\b/u);
expect(sentence).not.toMatch(/human/iu);
}
});
- test("every post body renders with the Hraness byline, provenance note, sources, and related products", async () => {
+ test("every post body renders with the Hraness byline, provenance note, sources, and the article image", async () => {
for (const post of BLOG_POSTS) {
const fragment = await readFile(join(websiteRoot, "source/blog", post.bodyFile), "utf8");
expect(fragment).not.toContain("
By Hraness");
+ expect(main).toContain('By Hraness ');
expect(main).toContain('class="plain-publication__provenance"');
expect(main).toContain('class="plain-publication__sources"');
- expect(main).toContain('class="plain-publication__related"');
- expect(main.match(/\{\{[A-Z0-9_]+\}\}/gu) ?? []).toEqual(
- fragment.includes("{{GHOSTGET_RELEASE}}") ? expect.arrayContaining(["{{GHOSTGET_RELEASE}}"]) : [],
- );
+ expect(main).toContain('class="editorial-figure"');
+ expect(main).not.toMatch(/(?:Published|Updated|Checked) {
}])).toThrow(/Review the new GhostGet relation/u);
});
});
+
+
+describe("Editorial image delivery", () => {
+ test("every promoted image matches its completed SlopCamera receipt and responsive files", async () => {
+ expect(editorialImages).toHaveLength(10);
+ for (const image of editorialImages) {
+ const bytes = await readFile(join(websiteRoot, "public", image.src));
+ expect(createHash("sha256").update(bytes).digest("hex")).toBe(image.imageSha256);
+ const receipt = JSON.parse(await readFile(join(websiteRoot, image.provenance.receipt), "utf8"));
+ const job = JSON.parse(await readFile(join(websiteRoot, image.provenance.job), "utf8"));
+ const prompt = await readFile(join(websiteRoot, image.provenance.prompt), "utf8");
+ expect(receipt.outputs[0].sha256).toBe(image.imageSha256);
+ expect(receipt.localValidation.status).toBe("decode-passed");
+ expect(job.state).toBe("completed");
+ expect(job.noSlopcameraRetry).toBe(true);
+ expect(job.clientMaxRetries).toBe(0);
+ expect(createHash("sha256").update(prompt.trim()).digest("hex")).toBe(image.provenance.promptSha256);
+ expect(job.request.promptSha256).toBe(image.provenance.promptSha256);
+ for (const derivative of image.derivatives) {
+ const derived = await readFile(join(websiteRoot, "public", derivative.src));
+ expect(createHash("sha256").update(derived).digest("hex")).toBe(derivative.sha256);
+ expect(derived.byteLength).toBeLessThan(bytes.byteLength);
+ }
+ }
+ });
+
+ test("blog cards, figures, social metadata, and schema use each article's own image", async () => {
+ const shell = await readFile(join(websiteRoot, "source/blog.html"), "utf8");
+ const index = renderBlogIndexMain();
+ expect(index).not.toContain("SlopCamera');
+ expect(main).not.toContain("editorial-provenance/");
+ expect(page).toContain(`property="og:image" content="${editorialImageUrl(image!)}"`);
+ expect(page).toContain(`name="twitter:image" content="${editorialImageUrl(image!)}"`);
+ const graph = blogPostJsonLd(post, site, [], "#organization")["@graph"] as Array>;
+ const article = graph.find(entry => entry["@type"] === "BlogPosting");
+ expect(article?.image).toMatchObject({ contentUrl: editorialImageUrl(image!), creditText: image!.credit, width: 1536, height: 864 });
+ expect(article?.datePublished).toBe(`${post.published}T00:00:00.000Z`);
+ expect(article?.dateModified).toBe(`${post.updated}T00:00:00.000Z`);
+ }
+ });
+});
diff --git a/website/blog.ts b/website/blog.ts
index 3c9e00fe..84388daf 100644
--- a/website/blog.ts
+++ b/website/blog.ts
@@ -15,7 +15,6 @@ import {
escapeArticleHtml,
renderArticleHtml,
renderArticleIndexHtml,
- renderArticleRelatedHtml,
renderArticleSourcesHtml,
type ArticleAdmission,
type ArticleAuthor,
@@ -35,29 +34,25 @@ import {
type SitemapPath,
} from "@hraness/web-discovery";
+import { editorialImage, editorialImageUrl, editorialImageSrcSet, EDITORIAL_ARTICLE_IMAGE_SIZES } from "./editorial-images";
+import { product as canonicalProduct } from "./portfolio-copy";
+
export const BLOG_PATH = "/blog/" as const;
export const BLOG_FEED_PATH = "/blog/feed.xml" as const;
export const BLOG_TITLE = "GhostGet blog" as const;
export const BLOG_DESCRIPTION =
- "Posts on how GhostGet's named web actions work for AI agents, how its releases and tests are checked, and which products use it." as const;
+ "Practical guides to web access, account actions, software verification, and repeatable tests." as const;
/** The Portfolio product id GhostGet still carries in the registry. */
export const GHOSTGET_PORTFOLIO_ID = "wrench" as const;
-const EVIDENCE_COMMIT = "76a79fc" as const;
-const GHOSTGET_BLOB = `https://github.com/hraness/ghostget/blob/${EVIDENCE_COMMIT}` as const;
-
-export const BLOG_AUTHOR: ArticleAuthor = { kind: "organization", name: "Hraness" };
+export const BLOG_AUTHOR: ArticleAuthor = { kind: "organization", name: "Hraness", href: "https://hraness.com" };
const BLOG_PARTY: ArticleParty = {
kind: "Organization",
name: "Hraness",
url: "https://hraness.com",
sameAs: ["https://github.com/hraness"],
};
-const AI_REVIEWER = "Claude Opus 5.5 (claude-opus-5-5) editorial review" as const;
-const INDEPENDENT_AI_REVIEWER = "Claude Opus 5.5 (claude-opus-5-5) independent review" as const;
-const REVIEWED_ON: ArticleIsoDate = "2026-09-24";
-const REASSESS_ON: ArticleIsoDate = "2026-11-05";
-const OWNER = "Hraness, maintainers of hraness/ghostget" as const;
+const REVIEWED_ON: ArticleIsoDate = "2026-09-30";
export type BlogPost = Readonly<{
slug: string;
@@ -79,302 +74,452 @@ export function blogPostPath(slug: string): `/blog/${string}/` {
return `/blog/${slug}/`;
}
-function ghostgetSource(title: string, path: string): ArticleSourceItem {
- return { title, href: `${GHOSTGET_BLOB}/${path}`, publisher: "GitHub", checkedOn: REVIEWED_ON };
-}
-
-function webSource(title: string, href: string, publisher?: string): ArticleSourceItem {
- return publisher === undefined
- ? { title, href, checkedOn: REVIEWED_ON }
- : { title, href, publisher, checkedOn: REVIEWED_ON };
-}
-
-function sourceRecords(sources: readonly ArticleSourceItem[]): ArticleAdmission["sources"] {
- return sources.map((source) => ({ title: source.title, url: source.href, checkedOn: source.checkedOn }));
-}
-
-function review(): NonNullable {
- return { reviewer: AI_REVIEWER, reviewerType: "ai", reviewedOn: REVIEWED_ON };
-}
-
-const introducingSources = [
- ghostgetSource("GhostGet README", "README.md"),
- ghostgetSource("GhostGet assurance case, rendered from the claims register", "docs/assurance.md"),
- ghostgetSource("GhostGet claims register", "verification/claims.json"),
- ghostgetSource("Beeper message bundle export", "src/beeper-message-bundle-v1.ts"),
- ghostgetSource("GhostGet website maintainer rules", "website/AGENTS.md"),
- webSource("GhostGet supported services and actions", "https://ghostget.com/docs/reference/provider-capabilities/", "GhostGet"),
- webSource("GhostGet getting-started guide", "https://ghostget.com/docs/tutorials/getting-started/", "GhostGet"),
-] as const satisfies readonly ArticleSourceItem[];
-
-const builtOnSources = [
- ghostgetSource("GhostGet's Beeper bundle writer, built on TextButler's bundle format", "src/beeper-message-bundle-v1.ts"),
- ghostgetSource("GhostGet README: Beeper and WhatsApp export commands, recorded limits, and truncation", "README.md"),
- ghostgetSource("GhostGet website rule: describe other products only as the registry or their README records", "website/AGENTS.md"),
- webSource("TextButler bundle format v1 (Beeper)", "https://github.com/hraness/textbutler/blob/0f83a82/docs/local-message-bundle-v1.md", "GitHub"),
-] as const satisfies readonly ArticleSourceItem[];
-
-const claimsRegisterSources = [
- ghostgetSource("GhostGet claims register", "verification/claims.json"),
- ghostgetSource("GhostGet assurance case, rendered from the register", "docs/assurance.md"),
- ghostgetSource("Allow-once approval model", "verification/quint/approvals.qnt"),
- ghostgetSource("Confirmed-write fence model (at-most-once and no-retry invariants)", "verification/quint/fence.qnt"),
- ghostgetSource("Formal verification plan, defect D13 (allow-once enforced by the client)", "kb/plans/formal-verification-assurance.md"),
- ghostgetSource("GhostGet website maintainer rules", "website/AGENTS.md"),
-] as const satisfies readonly ArticleSourceItem[];
-
-const releaseSources = [
- ghostgetSource("GhostGet release workflow", ".github/workflows/release.yml"),
- ghostgetSource("GhostGet publishing notes", "docs/publishing.md"),
- ghostgetSource("ghostget.com production release verifier", "website/production-release-verifier.ts"),
- ghostgetSource("Claims register: release rerun and npm recovery entries", "verification/claims.json"),
- ghostgetSource("Quint model of a release run and its attempts", "verification/quint/release.qnt"),
- webSource("GhostGet Latest GitHub Release", "https://github.com/hraness/ghostget/releases/latest", "GitHub"),
- webSource("GitHub CLI: gh attestation verify", "https://cli.github.com/manual/gh_attestation_verify", "GitHub"),
-] as const satisfies readonly ArticleSourceItem[];
-
-const propertyTestSources = [
- ghostgetSource("GhostGet property test helpers (replay coordinates, seed corpus, soak multiplier)", "src/test-support.ts"),
- ghostgetSource("GhostGet claims register (property, stateful-model, and differential layers)", "verification/claims.json"),
- ghostgetSource("GhostGet property seed corpus", "verification/seeds/corpus.json"),
- ghostgetSource("Named regression and coordinate pin for the __proto__ counterexample", "src/contracts-invoke-read.test.ts"),
- webSource("Commit adding the __proto__ regression (#341)", "https://github.com/hraness/ghostget/commit/aa8cce0", "GitHub"),
- ghostgetSource("Crash-schedule command-sequence tests", "src/state-crash-harness.test.ts"),
- ghostgetSource("Differential tests against the Rust oracles, with planted defects", "scripts/verification-oracles.test.ts"),
- ghostgetSource("Independent oracles README (RFC 8785 canonicalizer and url-crate policy)", "verification/oracles/README.md"),
- ghostgetSource("Contributor guide: replaying one property", "CONTRIBUTING.md"),
- ghostgetSource("Nightly verification workflow (soak multiplier default)", ".github/workflows/verification-nightly.yml"),
- webSource("RFC 8785: JSON Canonicalization Scheme", "https://www.rfc-editor.org/rfc/rfc8785", "RFC Editor"),
- webSource("WHATWG URL Standard", "https://url.spec.whatwg.org/", "WHATWG"),
- webSource("Rust url crate 2.5.8", "https://docs.rs/url/2.5.8/url/", "docs.rs"),
- webSource("fast-check model-based testing", "https://fast-check.dev/docs/advanced/model-based-testing/", "fast-check"),
-] as const satisfies readonly ArticleSourceItem[];
-
export const BLOG_POSTS: readonly BlogPost[] = [
{
- slug: "introducing-ghostget",
- title: "Introducing GhostGet: named web actions for AI agents",
- dek: "GhostGet lets your AI agent work in your own accounts through named, reviewed actions, without handing it your passwords, tokens, or a signed-in browser.",
- eyebrow: "Introducing",
- published: "2026-09-24",
- updated: "2026-09-30",
- keywords: ["ghostget", "agents", "accounts", "previews", "verification", "claims register"],
- bodyFile: "introducing-ghostget.html",
- sources: introducingSources,
- admission: {
- href: "/blog/introducing-ghostget/",
- lifecycle: "indexable",
- readerJob: "Decide whether to let an AI agent act in my own accounts through GhostGet, and see how to start.",
- nonObviousAnswer: "GhostGet hands the agent named, reviewed actions instead of credentials or a browser, previews every write above a read, and never resends a write whose result is unknown; its own claims register says which of those rules are checked and which have no automated check.",
- originalContribution: "States the Gmail read-only and WhatsApp no-send limits, the unencrypted Gmail refresh credential, and the claims register's evidenced, planned, and unverified counts next to the product pitch, taken from the source tree at 76a79fc.",
- hostFit: "The product's own introduction on the product's own site, linking to the getting-started guide and the provider reference already on ghostget.com.",
- nearestUrls: [
- { url: "https://ghostget.com/", distinction: "The homepage sells and installs; this post explains the account-safety model and its stated limits in prose." },
- { url: "https://ghostget.com/about/", distinction: "About states what GhostGet is and is not; this post walks one account workflow and the preview and no-resend rules." },
- { url: "https://ghostget.com/docs/explanation/security-model/", distinction: "The security guide is reference depth; this post is the first read that links to it." },
+ "slug": "introducing-ghostget",
+ "title": "Introducing GhostGet",
+ "dek": "Read web pages, work with connected accounts, and review consequential actions before they run.",
+ "eyebrow": "Getting started",
+ "published": "2026-09-24",
+ "updated": "2026-09-30",
+ "keywords": [
+ "getting started",
+ "agents",
+ "web"
+ ],
+ "bodyFile": "introducing-ghostget.html",
+ "sources": [
+ {
+ "title": "GhostGet getting-started guide",
+ "href": "https://ghostget.com/docs/tutorials/getting-started/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet supported services",
+ "href": "https://ghostget.com/docs/reference/provider-capabilities/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet security model",
+ "href": "https://ghostget.com/docs/explanation/security-model/",
+ "checkedOn": "2026-09-30"
+ }
+ ],
+ "admission": {
+ "href": "/blog/introducing-ghostget/",
+ "lifecycle": "indexable",
+ "readerJob": "Understand how named actions fit into an agent setup.",
+ "nonObviousAnswer": "A named action lets an agent request a specific result while the tool handles account selection and the underlying connection.",
+ "originalContribution": "A concrete worked example and practical next step, edited for a reader who has not seen the product source or development history.",
+ "hostFit": "Explains a recurring decision for readers using agents to work with web pages and accounts.",
+ "nearestUrls": [
+ {
+ "url": "https://ghostget.com/docs/",
+ "distinction": "Documentation gives product procedures and reference; this article explains the decision or mechanism."
+ }
],
- sources: sourceRecords(introducingSources),
- observations: [
- "The fact check corrected four overclaims before review: Gmail actions only read, the README runs adapter sync-bundled before capabilities, GhostGet never asks the agent to copy a token rather than making copying impossible, and the duplicate-risk re-send is limited to one web-session post named by its source run.",
- "On 2026-09-24 the claims register at 76a79fc held 244 claims: 180 evidenced, 45 planned, and 19 not verified, 15 of them resting on configuration readback.",
- "On 2026-09-26 a Claude Opus 5.5 (claude-opus-5-5) AI editorial review rewrote headings and prose for clarity and re-read the diff against the source; it changed no facts, numbers, commands, links, or dates of record.",
- "On 2026-09-27 a fact check against verification/claims.json at origin/main found the register lists 246 claims, 227 evidenced, 0 planned, and 19 not verified; statements that claims were still planned were re-dated to 2026-09-24 and the current status added.",
- "On 2026-09-28 the opening line and a section on the internal browser were added; the section restates the agent-browser comparison page and the agent-browser 0.32.3 pin in package.json, and names no new capability.",
- "On 2026-09-29 the opening was rebuilt as standalone beats, each with a code-built illustration labelled as one, and the earlier sections moved under 'The longer version' unchanged. Every number in the beats resolves from website/launch/facts.ts, which launch.test.ts pins to the provider directory, verification/claims.json, the homepage Measured table, and package.json; the X, Bluesky, Threads, and LinkedIn posts in kb/launch/social-kit.md are cut from the same beats. An independent Claude Opus 5.5 (claude-opus-5-5) review on 2026-09-29 re-read the new opening against facts.ts and the source, removed a line about how the post is laid out, restored the wget line to the first beat, and kept the scores at 11 of 12: the beats add no claim the longer version does not already source, and maintenance stays at 1 because the longer version still carries dated counts.",
- "On 2026-09-30 an independent AI review by a Claude Opus 5.5 (claude-opus-5-5) agent that neither drafted nor edited the post re-scored the rebuilt opening against origin/main 9ebaf16 and the live page at 10 of 12 with no zero: reader utility 2, original evidence 2, factual confidence 1, host fit 2, voice integrity 2, maintenance value 1. It confirmed every beat number against facts.ts and its source, and lowered factual confidence for three wordings the maintainers then corrected: the no-double-posts beat now says a lost reply never becomes an automatic double post, matching the documented duplicate-risk re-send; the preview beat names the service, the account, and what will be sent instead of a recipient; and the first beat says the agent is never handed the password. The current claims register counts in the longer version now render from website/launch/facts.ts, pinned to verification/claims.json by launch.test.ts, and the 2026-09-24 snapshot stays dated. The social kit, regenerated with design-kit v0.31.0, carries no limits beat; the limits stay in this post.",
+ "sources": [
+ {
+ "title": "GhostGet getting-started guide",
+ "url": "https://ghostget.com/docs/tutorials/getting-started/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet supported services",
+ "url": "https://ghostget.com/docs/reference/provider-capabilities/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet security model",
+ "url": "https://ghostget.com/docs/explanation/security-model/",
+ "checkedOn": "2026-09-30"
+ }
],
- scores: { readerUtility: 2, originalEvidence: 2, factualConfidence: 1, hostFit: 2, voiceIntegrity: 2, maintenanceValue: 1 },
- owner: OWNER,
- drafting: "ai-from-source",
- review: { reviewer: INDEPENDENT_AI_REVIEWER, reviewerType: "ai", reviewedOn: "2026-09-30" },
- humanReview: null,
- reassessOn: REASSESS_ON,
- harmIfWrong: "A reader could connect an account believing a write or storage guarantee exists that does not, such as encrypted Gmail credentials or WhatsApp sending.",
- refreshTriggers: [
- "GhostGet release tag bump",
- "Change to verification/claims.json totals or to the status of the indeterminate-never-retried or mutation-exact-preview-confirmation claims",
- "Change to the service count or to the Gmail or WhatsApp actions",
- "Change to the risk classes, preview-and-confirm flow, or duplicate-risk re-send rule in the README",
- "Rename of GhostGet or TextButler, or a change to the registered TextButler relation detail",
+ "observations": [
+ "The email-thread example explains when a named action saves navigation while pointing unfamiliar visual tasks to browser control; it establishes fit before listing features.",
+ "Reading and consequential writes are separated through concrete account, preview, and unknown-outcome behavior, with exact public commands supplying a complete first step."
],
- },
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "owner": "Hraness, maintainers of hraness/ghostget",
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "reassessOn": "2026-11-11",
+ "harmIfWrong": "A reader could make the wrong tool, retry, verification, or data-import decision.",
+ "refreshTriggers": [
+ "A documented interface or relevant service behavior changes",
+ "A product rename or registered relationship changes",
+ "A cited primary source changes its explanation"
+ ]
+ }
},
{
- slug: "built-on-ghostget",
- title: "Products built on GhostGet",
- dek: "PeopleBlade reads contacts and messages through GhostGet, TextButler imports the Beeper history that GhostGet exports, and Sponge captures signed-in pages through it.",
- eyebrow: "Integration",
- published: "2026-09-24",
- updated: "2026-09-30",
- keywords: ["ghostget", "integrations", "peopleblade", "textbutler", "local-first"],
- bodyFile: "built-on-ghostget.html",
- sources: builtOnSources,
- admission: {
- href: "/blog/built-on-ghostget/",
- lifecycle: "indexable",
- readerJob: "Find out which Hraness products use GhostGet and what each one uses it for.",
- nonObviousAnswer: "PeopleBlade reads contacts and message search results through a pinned GhostGet release and checks each result before saving it; TextButler imports Beeper exports that GhostGet writes in TextButler's own bundle format and marks as complete, truncated, or unknown.",
- originalContribution: "A provider hub whose entries render from the registered relation sentences, so the list changes when the registry changes rather than when someone edits copy.",
- hostFit: "The provider hub belongs on the provider's own site and links out to each consumer's post along a registered relation.",
- nearestUrls: [
- { url: "https://peopleblade.com/blog/how-peopleblade-uses-ghostget", distinction: "The consumer's own post explains the integration in depth; the hub only indexes it." },
- { url: "https://textbutler.app/blog/how-textbutler-uses-ghostget", distinction: "The consumer's own post explains the import; the hub only indexes it." },
+ "slug": "built-on-ghostget",
+ "title": "How other tools use GhostGet",
+ "dek": "Account reads, message exports, and signed-in page captures supply information for contact search, writing, and research.",
+ "eyebrow": "Connections",
+ "published": "2026-09-24",
+ "updated": "2026-09-30",
+ "keywords": [
+ "connections",
+ "agents",
+ "web"
+ ],
+ "bodyFile": "built-on-ghostget.html",
+ "sources": [
+ {
+ "title": "GhostGet message exports",
+ "href": "https://github.com/hraness/ghostget/blob/20c1f999/README.md",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "TextButler account connections",
+ "href": "https://github.com/hraness/textbutler",
+ "checkedOn": "2026-09-30"
+ }
+ ],
+ "admission": {
+ "href": "/blog/built-on-ghostget/",
+ "lifecycle": "indexable",
+ "readerJob": "Understand which tools use GhostGet and what information they receive.",
+ "nonObviousAnswer": "A connection and the data it returns are separate from the receiving tool’s task; source context and completeness need to travel with an export.",
+ "originalContribution": "A concrete worked example and practical next step, edited for a reader who has not seen the product source or development history.",
+ "hostFit": "Explains a recurring decision for readers using agents to work with web pages and accounts.",
+ "nearestUrls": [
+ {
+ "url": "https://ghostget.com/docs/",
+ "distinction": "Documentation gives product procedures and reference; this article explains the decision or mechanism."
+ }
],
- sources: sourceRecords(builtOnSources),
- observations: [
- "On 2026-09-24 relatedFor('wrench') in design-kit v0.17.0 returned exactly two relations with detail sentences: PeopleBlade provider transport and the TextButler private bundle export.",
- "A Sponge relation exists in code but was not registered on the portfolio registry's main branch on 2026-09-24, so the hub leaves it out until it is.",
- "On 2026-09-26 a Claude Opus 5.5 (claude-opus-5-5) AI editorial review rewrote headings and prose for clarity and re-read the diff against the source; it changed no facts, numbers, commands, links, or dates of record.",
- "On 2026-09-30, by owner decision, the hub lists Sponge along the registered relation runtime:sponge:wrench:captures-signed-in-pages-through (design-kit v0.30.3), quoting its registry detail sentence and linking to Sponge's canonical page from the registry because no public post about the integration exists yet. The GhostGet Skills relation registered in design-kit v0.31.0 is classified and left off the hub.",
+ "sources": [
+ {
+ "title": "GhostGet message exports",
+ "url": "https://github.com/hraness/ghostget/blob/20c1f999/README.md",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "TextButler account connections",
+ "url": "https://github.com/hraness/textbutler",
+ "checkedOn": "2026-09-30"
+ }
],
- scores: { readerUtility: 1, originalEvidence: 1, factualConfidence: 2, hostFit: 2, voiceIntegrity: 1, maintenanceValue: 2 },
- owner: OWNER,
- drafting: "ai-from-source",
- review: review(),
- humanReview: null,
- reassessOn: REASSESS_ON,
- harmIfWrong: "A reader could believe a product reads or imports data through GhostGet that it does not, or miss a product that does.",
- refreshTriggers: [
- "Any portfolio registry change to a relation with GhostGet as source or target, including a reword of the TextButler detail",
- "A Sponge relation registered with GhostGet and a GhostGet release that emits the capture bundle it imports",
- "PeopleBlade changes its GhostGet pin or the providers it reads through GhostGet",
- "GhostGet changes the Beeper export limits, truncation marking, or the TextButler bundle format version",
- "GhostGet's homepage related cards move to the registry render",
+ "observations": [
+ "The hub now uses current runtime relations, avoiding the stale export relationship that incorrectly assigned the legacy importer to the current TextButler product.",
+ "The incomplete-conversation example explains why source context and truncation metadata matter downstream, giving the hub a useful lesson beyond a product list."
],
- },
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "owner": "Hraness, maintainers of hraness/ghostget",
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "reassessOn": "2026-11-11",
+ "harmIfWrong": "A reader could make the wrong tool, retry, verification, or data-import decision.",
+ "refreshTriggers": [
+ "A documented interface or relevant service behavior changes",
+ "A product rename or registered relationship changes",
+ "A cited primary source changes its explanation"
+ ]
+ }
},
{
- slug: "ghostget-claims-register",
- title: "What GhostGet has and has not verified",
- dek: "On 24 September 2026, 180 of the 244 claims in GhostGet's public register had a check that runs on every change, and 19 had no automated check.",
- eyebrow: "Technique",
- published: "2026-09-24",
- updated: "2026-09-27",
- keywords: ["ghostget", "claims register", "verification", "quint", "lean", "property testing", "agents"],
- bodyFile: "ghostget-claims-register.html",
- sources: claimsRegisterSources,
- admission: {
- href: "/blog/ghostget-claims-register/",
- lifecycle: "indexable",
- readerJob: "Decide how far to trust GhostGet before letting an agent write in your own accounts: which of its safety promises have a running check, what kind, and where each check stops.",
- nonObviousAnswer: "The three promises that matter most for agent writes (allow-once approval, at-most-once writes, no resend after an unknown outcome) are checked by small Quint models replayed against the real code, each with a stated size, while preview-before-write, messaging reconciliation, local-program writes, and idempotency keys were planned on 2026-09-24 and marked evidenced by 2026-09-25, and 19 claims, mostly hosting settings, have no automated check at all.",
- originalContribution: "Reads the register entry by entry and names the stated model sizes and the planned gaps, instead of summarising the product as verified.",
- hostFit: "The register and its checks live in the GhostGet repository; the post is the reader-facing guide to it on the product's site.",
- nearestUrls: [
- { url: "https://github.com/hraness/ghostget/blob/main/docs/assurance.md", distinction: "The assurance case is the generated register listing; the post selects the entries a reader deciding on account writes needs." },
- { url: "https://hraness.com/reference/correctness/claims-ledgers", distinction: "The general technique page; this is the GhostGet instance with real counts." },
+ "slug": "ghostget-claims-register",
+ "title": "Why a failed send can still have succeeded",
+ "dek": "A timeout can hide a completed action. Check the result and understand idempotency before repeating a consequential request.",
+ "eyebrow": "Reliability",
+ "published": "2026-09-24",
+ "updated": "2026-09-30",
+ "keywords": [
+ "reliability",
+ "agents",
+ "web"
+ ],
+ "bodyFile": "ghostget-claims-register.html",
+ "sources": [
+ {
+ "title": "Amazon EC2 API idempotency",
+ "href": "https://docs.aws.amazon.com/ec2/latest/devguide/ec2-api-idempotency.html",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet send and recovery behavior",
+ "href": "https://ghostget.com/docs/explanation/security-model/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet claims register",
+ "href": "https://ghostget.com/claims/",
+ "checkedOn": "2026-09-30"
+ }
+ ],
+ "admission": {
+ "href": "/blog/ghostget-claims-register/",
+ "lifecycle": "indexable",
+ "readerJob": "Decide what to do after a send returns a timeout.",
+ "nonObviousAnswer": "A failed response and a failed action are different states; replaying the request can duplicate an effect that already happened.",
+ "originalContribution": "A concrete worked example and practical next step, edited for a reader who has not seen the product source or development history.",
+ "hostFit": "Explains a recurring decision for readers using agents to work with web pages and accounts.",
+ "nearestUrls": [
+ {
+ "url": "https://ghostget.com/docs/",
+ "distinction": "Documentation gives product procedures and reference; this article explains the decision or mechanism."
+ }
],
- sources: sourceRecords(claimsRegisterSources),
- observations: [
- "Every count in the post was re-derived from verification/claims.json at 76a79fc on 2026-09-24 and carries that date in the body.",
- "The allow-once approval rule is enforced by the client (defect D13 in the formal verification plan), which the post states rather than implying a server-side guarantee.",
- "On 2026-09-26 a Claude Opus 5.5 (claude-opus-5-5) AI editorial review rewrote headings and prose for clarity and re-read the diff against the source; it changed no facts, numbers, commands, links, or dates of record.",
- "On 2026-09-27 a fact check against verification/claims.json at origin/main found the register lists 246 claims, 227 evidenced, 0 planned, and 19 not verified; statements that claims were still planned were re-dated to 2026-09-24 and the current status added.",
+ "sources": [
+ {
+ "title": "Amazon EC2 API idempotency",
+ "url": "https://docs.aws.amazon.com/ec2/latest/devguide/ec2-api-idempotency.html",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet send and recovery behavior",
+ "url": "https://ghostget.com/docs/explanation/security-model/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet claims register",
+ "url": "https://ghostget.com/claims/",
+ "checkedOn": "2026-09-30"
+ }
],
- scores: { readerUtility: 2, originalEvidence: 2, factualConfidence: 2, hostFit: 1, voiceIntegrity: 2, maintenanceValue: 1 },
- owner: OWNER,
- drafting: "ai-from-source",
- review: review(),
- humanReview: null,
- reassessOn: REASSESS_ON,
- harmIfWrong: "A reader could trust an agent write path more than its checks justify, or overlook an unverified claim that matters for their accounts.",
- refreshTriggers: [
- "verification/claims.json totals or per-layer counts change",
- "Status or notVerified text of approval-allow-once, confirmed-write-at-most-once, indeterminate-never-retried, mutation-exact-preview-confirmation, messaging-uncertain-not-resubmitted, or local-cli-post-spawn-indeterminate changes",
- "Bounds in verification/quint/approvals.qnt or fence.qnt change",
- "Status definitions or verify:claims checks in docs/assurance.md change",
- "Release tag bump (re-derive counts from the tag)",
+ "observations": [
+ "The timeout example separates loss of the reply from failure of the action and explains why an empty search result may not settle the outcome.",
+ "The idempotency discussion identifies both reusing a key and service-side behavior as necessary, avoiding the mistaken implication that a locally generated identifier prevents remote duplicates."
],
- },
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "owner": "Hraness, maintainers of hraness/ghostget",
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "reassessOn": "2026-11-11",
+ "harmIfWrong": "A reader could make the wrong tool, retry, verification, or data-import decision.",
+ "refreshTriggers": [
+ "A documented interface or relevant service behavior changes",
+ "A product rename or registered relationship changes",
+ "A cited primary source changes its explanation"
+ ]
+ }
},
{
- slug: "releases-that-prove-their-origin",
- title: "How to check a GhostGet download",
- dek: "One gh command checks that a downloaded GhostGet tarball was signed by the release workflow at its version tag.",
- eyebrow: "Technique",
- published: "2026-09-24",
- updated: "2026-09-26",
- keywords: ["ghostget", "releases", "provenance", "supply chain", "github releases", "npm", "quint"],
- bodyFile: "releases-that-prove-their-origin.html",
- sources: releaseSources,
- admission: {
- href: "/blog/releases-that-prove-their-origin/",
- lifecycle: "indexable",
- readerJob: "Confirm that a downloaded GhostGet tarball was built and signed by the public release workflow at its version tag, then install that exact file.",
- nonObviousAnswer: "The release ships its own signature bundle (provenance.jsonl), so gh attestation verify can pin the signer workflow, tag, and GitHub-hosted runner offline; a failed-jobs rerun republishes the already-signed bytes instead of rebuilding; releases before v0.17.0 are signed under the Wrench name or not at all.",
- originalContribution: "A command sequence run against the real Latest Release on 2026-09-24, including the observation that gh attestation verify prints nothing when stdout is not a terminal.",
- hostFit: "Install verification for the product's own release, next to its getting-started guide.",
- nearestUrls: [
- { url: "https://ghostget.com/docs/tutorials/getting-started/", distinction: "The guide installs; this post checks the file before installing it." },
- { url: "https://hraness.com/reference/correctness/release-provenance", distinction: "The general technique page; this is the GhostGet command sequence." },
+ "slug": "releases-that-prove-their-origin",
+ "title": "How to verify where a download came from",
+ "dek": "Check the file fingerprint and signed build record, then install the exact package you verified.",
+ "eyebrow": "Software provenance",
+ "published": "2026-09-24",
+ "updated": "2026-09-30",
+ "keywords": [
+ "software provenance",
+ "agents",
+ "web"
+ ],
+ "bodyFile": "releases-that-prove-their-origin.html",
+ "sources": [
+ {
+ "title": "GitHub CLI attestation verification",
+ "href": "https://cli.github.com/manual/gh_attestation_verify",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet releases",
+ "href": "https://github.com/hraness/ghostget/releases",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet release format",
+ "href": "https://github.com/hraness/ghostget/blob/20c1f999/docs/publishing.md",
+ "checkedOn": "2026-09-30"
+ }
+ ],
+ "admission": {
+ "href": "/blog/releases-that-prove-their-origin/",
+ "lifecycle": "indexable",
+ "readerJob": "Verify that a package was built by the expected project and workflow.",
+ "nonObviousAnswer": "A checksum establishes matching bytes; a signed build record connects those bytes to the repository, workflow, and selected version.",
+ "originalContribution": "A concrete worked example and practical next step, edited for a reader who has not seen the product source or development history.",
+ "hostFit": "Explains a recurring decision for readers using agents to work with web pages and accounts.",
+ "nearestUrls": [
+ {
+ "url": "https://ghostget.com/docs/",
+ "distinction": "Documentation gives product procedures and reference; this article explains the decision or mechanism."
+ }
],
- sources: sourceRecords(releaseSources),
- observations: [
- "2026-09-24: all five assets of the Latest GitHub Release were downloaded; shasum -a 256 -c SHA256SUMS passed and gh attestation verify with --bundle provenance.jsonl and the release.yml signer workflow exited 0.",
- "2026-09-24: gh attestation verify printed nothing when stdout was not a terminal; the exit status and --format json were the reliable signals.",
- "2026-09-24: the npm latest dist-tag integrity decoded to the archive SHA-512 recorded in that Release's release-manifest.json.",
- "On 2026-09-26 a Claude Opus 5.5 (claude-opus-5-5) AI editorial review rewrote headings and prose for clarity and re-read the diff against the source; it changed no facts, numbers, commands, links, or dates of record, and replaced an unlinked reference to \"deploy proofs\" with a pointer to the website section of the same post.",
+ "sources": [
+ {
+ "title": "GitHub CLI attestation verification",
+ "url": "https://cli.github.com/manual/gh_attestation_verify",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet releases",
+ "url": "https://github.com/hraness/ghostget/releases",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "GhostGet release format",
+ "url": "https://github.com/hraness/ghostget/blob/20c1f999/docs/publishing.md",
+ "checkedOn": "2026-09-30"
+ }
],
- scores: { readerUtility: 2, originalEvidence: 2, factualConfidence: 2, hostFit: 2, voiceIntegrity: 2, maintenanceValue: 1 },
- owner: OWNER,
- drafting: "ai-from-source",
- review: review(),
- humanReview: null,
- reassessOn: REASSESS_ON,
- harmIfWrong: "A reader could accept a tampered download as genuine, or reject a genuine one, if a command or flag in the post is wrong.",
- refreshTriggers: [
- "Release tag bump (status line only; the body has no typed version)",
- "Status, statement, or notVerified text of release-failed-jobs-rerun-recovers-publish or npm-failure-never-blocks-canonical changes",
- "Canonical five-file asset set, signer workflow path, tarball name, npm publication path, or npm package coordinate changes",
- "gh attestation verify, gh release verify, or npm audit signatures flags or output change",
- "website-production.yml or production-release-verifier.ts changes what the site checks before going live",
+ "observations": [
+ "The article distinguishes byte integrity from builder identity before showing verification commands, so a matching checksum is not presented as proof of origin.",
+ "Installing the exact verified local archive completes the trust chain; downloading or installing a different file would break the connection the earlier checks establish."
],
- },
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "owner": "Hraness, maintainers of hraness/ghostget",
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "reassessOn": "2026-11-11",
+ "harmIfWrong": "A reader could make the wrong tool, retry, verification, or data-import decision.",
+ "refreshTriggers": [
+ "A documented interface or relevant service behavior changes",
+ "A product rename or registered relationship changes",
+ "A cited primary source changes its explanation"
+ ]
+ }
},
{
- slug: "replayable-property-tests",
- title: "How GhostGet replays a failing generated test",
- dek: "When one of GhostGet's generated tests fails, the report gives a seed and a shrink path, and passing both back reruns that exact case on any machine.",
- eyebrow: "Technique",
- published: "2026-09-24",
- updated: "2026-09-27",
- keywords: ["ghostget", "property testing", "fast-check", "model-based testing", "differential testing", "rfc 8785", "verification"],
- bodyFile: "replayable-property-tests.html",
- sources: propertyTestSources,
- admission: {
- href: "/blog/replayable-property-tests/",
- lifecycle: "indexable",
- readerJob: "Understand how GhostGet makes a failing generated test rerun exactly, and what its property, crash, and comparison tests do and do not prove.",
- nonObviousAnswer: "Every property goes through shared helpers that validate a seed-and-shrink-path coordinate, a checked-in corpus replays a named failure (the {\"__proto__\":0} counterexample) before the random run, a pin test fails if a fast-check upgrade moves that coordinate, and planted defects show the Rust comparison would catch a wrong key order.",
- originalContribution: "Traces one real counterexample from the failing report to its permanent corpus entry and pin test, with the commit that added it.",
- hostFit: "Describes the test harness in the GhostGet repository for readers evaluating or contributing to GhostGet.",
- nearestUrls: [
- { url: "https://hraness.com/reference/correctness/property-tests", distinction: "The general technique page; this post is the GhostGet harness and its real counterexample." },
- { url: "https://hraness.com/reference/correctness/two-implementations-one-spec", distinction: "The general differential-testing page; this post covers only GhostGet's Rust oracles." },
+ "slug": "replayable-property-tests",
+ "title": "Turn an unexpected input into a repeatable test",
+ "dek": "Generate inputs from a rule, reduce a failure, and keep both the replay recipe and the smallest example.",
+ "eyebrow": "Testing",
+ "published": "2026-09-24",
+ "updated": "2026-09-30",
+ "keywords": [
+ "testing",
+ "agents",
+ "web"
+ ],
+ "bodyFile": "replayable-property-tests.html",
+ "sources": [
+ {
+ "title": "fast-check runners",
+ "href": "https://fast-check.dev/docs/core-blocks/runners/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "fast-check model-based testing",
+ "href": "https://fast-check.dev/docs/advanced/model-based-testing/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "JSON key regression example",
+ "href": "https://github.com/hraness/ghostget/blob/20c1f999/src/contracts-invoke-read.test.ts",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "JSON Canonicalization Scheme",
+ "href": "https://www.rfc-editor.org/rfc/rfc8785",
+ "checkedOn": "2026-09-30"
+ }
+ ],
+ "admission": {
+ "href": "/blog/replayable-property-tests/",
+ "lifecycle": "indexable",
+ "readerJob": "Understand how generated tests find and preserve unexpected cases.",
+ "nonObviousAnswer": "A seed and shrink path reproduce a discovery for the same generator, while an explicit regression preserves its meaning across library changes.",
+ "originalContribution": "A concrete worked example and practical next step, edited for a reader who has not seen the product source or development history.",
+ "hostFit": "Explains a recurring decision for readers using agents to work with web pages and accounts.",
+ "nearestUrls": [
+ {
+ "url": "https://ghostget.com/docs/",
+ "distinction": "Documentation gives product procedures and reference; this article explains the decision or mechanism."
+ }
],
- sources: sourceRecords(propertyTestSources),
- observations: [
- "The {\"__proto__\":0} counterexample was added as a named regression in #341 (aa8cce0) with a coordinate pin test in src/contracts-invoke-read.test.ts.",
- "On 2026-09-24 the seed corpus at 76a79fc held one entry, and property defaults were 200 runs with a 10 second interrupt and a x20 nightly soak multiplier.",
- "On 2026-09-26 a Claude Opus 5.5 (claude-opus-5-5) AI editorial review rewrote headings and prose for clarity and re-read the diff against the source; it changed no facts, numbers, commands, links, or dates of record.",
- "On 2026-09-27 a fact check against verification/claims.json at origin/main found the register lists 246 claims, 227 evidenced, 0 planned, and 19 not verified; statements that claims were still planned were re-dated to 2026-09-24 and the current status added.",
+ "sources": [
+ {
+ "title": "fast-check runners",
+ "url": "https://fast-check.dev/docs/core-blocks/runners/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "fast-check model-based testing",
+ "url": "https://fast-check.dev/docs/advanced/model-based-testing/",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "JSON key regression example",
+ "url": "https://github.com/hraness/ghostget/blob/20c1f999/src/contracts-invoke-read.test.ts",
+ "checkedOn": "2026-09-30"
+ },
+ {
+ "title": "JSON Canonicalization Scheme",
+ "url": "https://www.rfc-editor.org/rfc/rfc8785",
+ "checkedOn": "2026-09-30"
+ }
],
- scores: { readerUtility: 2, originalEvidence: 2, factualConfidence: 2, hostFit: 2, voiceIntegrity: 2, maintenanceValue: 1 },
- owner: OWNER,
- drafting: "ai-from-source",
- review: review(),
- humanReview: null,
- reassessOn: REASSESS_ON,
- harmIfWrong: "A contributor could replay a failure with the wrong coordinate format or trust a test layer for a property it does not cover.",
- refreshTriggers: [
- "GhostGet release tag bump",
- "Change to verification/claims.json property, stateful-model, or differential claim counts or statuses",
- "New entry in verification/seeds/corpus.json or a change to the replay coordinate format or env vars in src/test-support.ts",
- "Change to property defaults (runs, interrupt time) or the nightly soak multiplier",
- "Change to the crash harness fault modes or the Rust oracles in verification/oracles",
+ "observations": [
+ "The real __proto__ regression supplies a concrete reason to generate awkward structured values, and the public fast-check sample makes the replay mechanism portable.",
+ "The article pairs a seed/path recipe with a permanent minimal example, explaining why a dependency upgrade can change replay while the explicit regression remains useful."
],
- },
- },
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 2,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "owner": "Hraness, maintainers of hraness/ghostget",
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "reassessOn": "2026-11-11",
+ "harmIfWrong": "A reader could make the wrong tool, retry, verification, or data-import decision.",
+ "refreshTriggers": [
+ "A documented interface or relevant service behavior changes",
+ "A product rename or registered relationship changes",
+ "A cited primary source changes its explanation"
+ ]
+ }
+ }
];
assertArticleAdmissions(BLOG_POSTS.map((post) => post.admission));
@@ -422,13 +567,12 @@ export function blogArticleDiscovery(post: BlogPost, site: BlogSite): ArticleDis
blogPath: BLOG_PATH,
canonicalPath: blogPostPath(post.slug),
description: post.dek,
- image: {
- alt: site.socialImageAlt,
- contentType: "image/png",
- height: 630,
- path: "/og.png",
- width: 1200,
- },
+ image: (() => {
+ const image = editorialImage(blogPostPath(post.slug));
+ return image === undefined
+ ? { alt: site.socialImageAlt, contentType: "image/png" as const, height: 630, path: "/og.png" as const, width: 1200 }
+ : { alt: image.alt, contentType: "image/webp" as const, height: image.height, path: image.src, width: image.width };
+ })(),
isAccessibleForFree: true,
keywords: post.keywords,
...(post.updated === undefined ? {} : { modifiedTime: isoTimestamp(post.updated) }),
@@ -507,6 +651,7 @@ export function blogPostJsonLd(
): JsonObject {
const url = `${site.origin}${blogPostPath(post.slug)}`;
const article = withoutContext(articleJsonLd(searchSite(site), blogArticleDiscovery(post, site)));
+ const image = editorialImage(blogPostPath(post.slug));
return {
"@context": "https://schema.org",
"@graph": [
@@ -524,6 +669,7 @@ export function blogPostJsonLd(
},
{
...article,
+ ...(image === undefined ? {} : { image: { ...(article.image as JsonObject), creditText: image.credit } }),
about: { "@id": `${site.origin}/#software` },
author: [{ "@id": organizationId }],
publisher: { "@id": organizationId },
@@ -574,9 +720,10 @@ export const BUILT_ON_RELATIONS: Readonly${escapeArticleHtml(item.name)}`,
- `${escapeArticleHtml(item.relationship)}
`,
+ `${escapeArticleHtml(name)} `,
+ `${escapeArticleHtml(item.relationship)}
`,
`Read more: ${escapeArticleHtml(post.label)}
`,
].join("\n");
}).filter((entry) => entry !== "").join("\n");
@@ -639,48 +787,58 @@ export function renderBlogPostMain(post: BlogPost, bodyFragment: string): string
const bodyHtml = bodyFragment.includes("{{BLOG_RELATION_ENTRIES}}")
? bodyFragment.replace("{{BLOG_RELATION_ENTRIES}}", () => renderBuiltOnEntries())
: bodyFragment;
- const related = ghostgetRelations().map((item) => ({
- href: item.href,
- mark: item.mark,
- name: item.name,
- role: item.role,
- }));
+ const image = editorialImage(blogPostPath(post.slug));
+ const figure = image === undefined ? "" : `Generated with SlopCamera . `;
const article = renderArticleHtml({
- afterHtml: renderArticleSourcesHtml({ sources: post.sources }) + renderArticleRelatedHtml({ items: related }),
+ afterHtml: renderArticleSourcesHtml({ sources: post.sources, showDates: false }),
author: BLOG_AUTHOR,
- bodyHtml,
+ bodyHtml: figure + bodyHtml,
dek: post.dek,
- eyebrow: post.eyebrow,
heading: post.title,
provenance: articleProvenanceFromAdmission(post.admission),
published: post.published,
+ showDates: false,
toc: tocFromBody(bodyHtml),
...(post.updated === undefined ? {} : { updated: post.updated }),
});
return [
- `GhostGet Blog ${escapeArticleHtml(post.title)} `,
+ `Blog `,
article,
].join("\n");
}
export function renderBlogIndexMain(posts: readonly BlogPost[] = BLOG_POSTS): string {
const listed = indexablePosts(posts);
+ const indexHtml = renderArticleIndexHtml({
+ heading: BLOG_TITLE,
+ headingId: "blog-title",
+ headingLevel: 1,
+ items: listed.map((post) => ({
+ dek: post.dek,
+ href: blogPostPath(post.slug),
+ published: post.published,
+ title: post.title,
+ ...(post.updated === undefined ? {} : { updated: post.updated }),
+ })),
+ showDates: false,
+ summary: BLOG_DESCRIPTION,
+ });
+ let entryIndex = 0;
+ const illustratedIndex = new HTMLRewriter().on(".plain-publication__entry", {
+ element(entry) {
+ const post = listed[entryIndex++];
+ if (post === undefined) throw new Error("Article index rendered an unexpected entry");
+ const href = blogPostPath(post.slug);
+ const image = editorialImage(href);
+ if (image !== undefined) {
+ entry.prepend(` `, { html: true });
+ }
+ },
+ }).transform(indexHtml);
+ if (entryIndex !== listed.length) throw new Error("Article index omitted an expected entry");
return [
`GhostGet Blog `,
- renderArticleIndexHtml({
- heading: BLOG_TITLE,
- headingId: "blog-title",
- headingLevel: 1,
- items: listed.map((post) => ({
- dek: post.dek,
- eyebrow: post.eyebrow,
- href: blogPostPath(post.slug),
- published: post.published,
- title: post.title,
- ...(post.updated === undefined ? {} : { updated: post.updated }),
- })),
- summary: BLOG_DESCRIPTION,
- }),
+ illustratedIndex,
`Atom feed
`,
].join("\n");
}
@@ -724,6 +882,13 @@ export function fillBlogShell(shell: string, site: BlogSite, head: BlogPageHead,
// A function replacement keeps `$` sequences in shell examples literal.
page = page.replaceAll(placeholder, () => value);
}
+ const image = editorialImage(head.canonicalPath);
+ if (image !== undefined) {
+ page = page.replaceAll('content="https://ghostget.com/og.png"', `content="${editorialImageUrl(image)}"`)
+ .replaceAll('{{GHOSTGET_SOCIAL_IMAGE_ALT}}', escapeArticleHtml(image.alt))
+ .replace('property="og:image:width" content="1200"', `property="og:image:width" content="${image.width}"`)
+ .replace('property="og:image:height" content="630"', `property="og:image:height" content="${image.height}"`);
+ }
if (/\{\{BLOG_[A-Z_]+\}\}/u.test(page)) throw new Error("The blog shell has an unknown placeholder.");
return page;
}
diff --git a/website/build.ts b/website/build.ts
index a76c5651..ccbb736a 100644
--- a/website/build.ts
+++ b/website/build.ts
@@ -1,3 +1,5 @@
+import { renderArticleProvenanceHtml } from "@hraness/design-kit";
+import { essayReviews } from "./essay-reviews";
import { marketing, renderMarketingCopy } from "./portfolio-copy";
import { releaseArchiveUrl } from "./github-release-artifact.mjs";
import { snapshotMarketingPreset } from "./marketing-preset";
@@ -134,7 +136,7 @@ export const HRANESS_LOGO_URL = "https://hraness.com/icon.png" as const;
export const HRANESS_LINKEDIN_URL = "https://www.linkedin.com/company/hraness" as const;
export const NPM_PACKAGE_URL = "https://www.npmjs.com/package/@hraness/ghostget" as const;
export const SKILL_REPOSITORY = "hraness/ghostget" as const;
-export const CONTENT_REVIEWED_RELEASE = "v0.18.64" as const;
+export const CONTENT_REVIEWED_RELEASE = "v0.18.65" as const;
export const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com" as const;
export const DEMO_PUBLIC_FILES = [
"wrench-first-capture.gif",
@@ -273,10 +275,10 @@ export const PUBLIC_PAGES = [
{
canonicalPath: "/compare/",
description:
- "Six ways agents reach the web, from browser-driving libraries to reader services and integration platforms, compared with GhostGet's named actions.",
+ "Choose a page reader, browser tool, or account integration around the work the agent needs to do.",
outputFile: "compare/index.html",
sourceFile: "compare-index.html",
- title: "How agents reach the web: browser-use, Playwright MCP, Firecrawl, Composio, and GhostGet",
+ title: "How agents reach the web",
},
{
canonicalPath: "/compare/browser-use/",
@@ -329,50 +331,50 @@ export const PUBLIC_PAGES = [
{
canonicalPath: "/compare/personal-agents-browser-use/",
description:
- "Persistent personal agents work through a general browser. GhostGet gives an agent a fixed list of named web actions, and anything unlisted stays unavailable.",
+ "Choose browser control for unfamiliar visual work and named actions for repeated tasks with known inputs, outputs, and account permissions.",
outputFile: "compare/personal-agents-browser-use/index.html",
sourceFile: "compare-personal-agents-browser-use.html",
- title: "Browser-using personal agents and GhostGet's named web actions",
+ title: "Browser control or named actions: choosing an agent tool",
},
{
canonicalPath: "/agentic-web-spoofing/",
description:
- "Known Agents measures inbound bot impersonation. That check does not establish a safe outbound GhostGet operation or name its operator.",
+ "A bot name in an HTTP request is a claim. Learn how identity checks work and why authentication and permission answer separate questions.",
outputFile: "agentic-web-spoofing/index.html",
sourceFile: "agentic-web-spoofing.html",
- title: "Agentic-web index spoofing, and why attested operations still matter",
+ title: "How to verify a web agent’s identity",
},
{
canonicalPath: "/vms-cannot-contain-agents/",
description:
- "The Wednesday 26 August 2026 rough.day tech edition ranked Trail of Bits' argument that VMs cannot reliably contain cyber-capable AI agents. A guest machine does not establish a safe GhostGet operation.",
+ "Virtual machines isolate a computing environment. Network access, account permissions, and shared files determine what an agent can reach outside it.",
outputFile: "vms-cannot-contain-agents/index.html",
sourceFile: "vms-cannot-contain-agents.html",
- title: "VMs cannot contain agents, and why attested web operations still matter",
+ title: "What a virtual machine isolates, and what an agent can still do",
},
{
canonicalPath: "/paypal-grapheneos-attestation/",
description:
- "The Thursday 27 August 2026 rough.day tech edition ranked PayPal crashing on GrapheneOS with a RootDetectionSecurityException. Device-policy attestation is not a named GhostGet operation.",
+ "Device checks establish whether a phone matches an app\u2019s requirements. Understand the policy before choosing a supported way to use the service.",
outputFile: "paypal-grapheneos-attestation/index.html",
sourceFile: "paypal-grapheneos-attestation.html",
- title: "PayPal attested a hardened phone as rooted, and why named web operations still matter",
+ title: "What a device integrity check tells an app",
},
{
canonicalPath: "/rumour-is-the-exploit/",
description:
- "This Monday 31 August 2026 sourced take starts from Anil Madhavapeddy’s essay that a rumour of a bug is enough for agentic search. A search direction is not a named GhostGet operation.",
+ "A reported flaw gives a starting point. Establish the affected behavior, reduce the reproduction, and check the fix against the original failure.",
outputFile: "rumour-is-the-exploit/index.html",
sourceFile: "rumour-is-the-exploit.html",
- title: "A rumour is enough for agentic search, and why named web operations still matter",
+ title: "Turn a bug report into a reproducible test",
},
{
canonicalPath: "/omarchy-root-escalation/",
description:
- "The Sunday 30 August 2026 rough.day tech edition ranked Omarchy’s default desktop allowing any user process to escalate to root. A host privilege grant is not a named GhostGet operation.",
+ "Administrator access changes what a program can affect. Separate installation needs from everyday work and keep recurring permissions specific.",
outputFile: "omarchy-root-escalation/index.html",
sourceFile: "omarchy-root-escalation.html",
- title: "Omarchy lets any user process escalate to root, and why named web operations still matter",
+ title: "Review the administrator access a tool needs",
},
] as const;
@@ -391,7 +393,7 @@ export const INDEXABLE_PUBLIC_PAGES: readonly PublicPage[] = PUBLIC_PAGES.filter
/** Editorial images whose pages are indexable, for homepage cards and the image sitemap. */
export const INDEXABLE_EDITORIAL_IMAGES = editorialImages.filter(
- (image) => !isNoindexDocumentPath(image.canonicalPath),
+ (image) => !image.canonicalPath.startsWith("/blog/") && !isNoindexDocumentPath(image.canonicalPath),
);
const ROBOTS_META = / /gu;
@@ -645,12 +647,12 @@ function imageObject(image: EditorialImage): Readonly> {
};
}
-function renderEditorialFigure(image: EditorialImage): string {
+export function renderEditorialFigure(image: EditorialImage): string {
return `
- ${escapeHtml(image.caption)} ${escapeHtml(image.credit)}
+ ${image.caption ? `${escapeHtml(image.caption)} ` : ""}Generated with SlopCamera .
`;
}
@@ -902,7 +904,7 @@ const GHOSTGET_PLATFORM_BADGES = renderPlatformBadges(["macos", "linux", { id: "
/**
* The install command for each platform, in the portfolio order macOS, Linux,
- * Windows. The command is the same Bun install everywhere Ghostget runs.
+ * Windows. The command is the same Bun install everywhere GhostGet runs.
*/
function renderGhostgetPlatformInstall(installCommand: string): string {
return renderPlatformInstall({
@@ -958,6 +960,9 @@ function renderTemplate(
);
}
if (page) {
+ if (Object.hasOwn(essayReviews, page.sourceFile)) {
+ rendered = replaceRequired(rendered, "{{ESSAY_PROVENANCE}}", renderArticleProvenanceHtml(essayReviews[page.sourceFile as keyof typeof essayReviews]));
+ }
const structuredData = JSON.stringify(structuredDataOverride ?? jsonLd(identity, page)).replaceAll("<", "\\u003c");
rendered = replaceRequired(rendered, "{{JSON_LD}}", structuredData);
rendered = replaceRequired(
@@ -970,7 +975,7 @@ function renderTemplate(
if (/\{\{EDITORIAL_(?:FIGURE|IMAGE_)/u.test(rendered)) {
throw new Error(`Only registered editorial pages may use editorial image placeholders: ${page.canonicalPath}`);
}
- } else {
+ } else if (!page.canonicalPath.startsWith("/blog/")) {
const editorialValues = new Map([
["{{EDITORIAL_FIGURE}}", renderEditorialFigure(image)],
["{{EDITORIAL_IMAGE_ALT}}", escapeHtml(image.alt)],
@@ -1219,8 +1224,11 @@ export function renderSitemapXml(
? ""
: `
${escapeXml(typeof entry.lastModified === "string" ? entry.lastModified : entry.lastModified.toISOString())} `;
+ const image = editorialImage(entry.path);
+ const imageMarkup = image === undefined ? "" : `
+ ${escapeXml(editorialImageUrl(image))} ${escapeXml(image.title)} `;
return `
- ${SITE_ORIGIN}${entry.path} ${lastModified}
+ ${SITE_ORIGIN}${entry.path} ${lastModified}${imageMarkup}
`;
});
const urls = [...pages.filter((page) => !isNoindexDocumentPath(page.canonicalPath)).map((page) => {
diff --git a/website/editorial-images.ts b/website/editorial-images.ts
index d0c95806..eca5990d 100644
--- a/website/editorial-images.ts
+++ b/website/editorial-images.ts
@@ -1,179 +1,381 @@
export type EditorialImage = Readonly<{
alt: string;
- canonicalPath:
- | "/compare/personal-agents-browser-use/"
- | "/agentic-web-spoofing/"
- | "/vms-cannot-contain-agents/"
- | "/webmcp/";
+ canonicalPath: `/blog/${string}/` | "/compare/" | "/compare/personal-agents-browser-use/" | "/agentic-web-spoofing/" | "/vms-cannot-contain-agents/" | "/webmcp/";
caption: string;
cardDescription: string;
cardTitle: string;
credit: string;
- derivatives: readonly [
- Readonly<{
- height: 216;
- sha256: string;
- src: `/images/editorial/${string}-384.webp`;
- width: 384;
- }>,
- Readonly<{
- height: 432;
- sha256: string;
- src: `/images/editorial/${string}-768.webp`;
- width: 768;
- }>,
- ];
+ creditUrl: string;
+ derivatives: readonly Readonly<{ height: number; sha256: string; src: `/images/editorial/${string}.webp`; width: number }>[];
height: 864;
imageSha256: string;
- provenance: Readonly<{
- generator?: string;
- job: string;
- prompt: string;
- promptSha256: string;
- receipt: string;
- }>;
+ provenance: Readonly<{ generator: string; cliSha256: string; job: string; prompt: string; promptSha256: string; receipt: string }>;
src: `/images/editorial/${string}.webp`;
title: string;
width: 1536;
}>;
-const credit = "Editorial illustration generated with Atet.";
-
export const editorialImages = [
{
- alt: "Many branching browser windows contrasted with one bounded operation path and receipt",
- canonicalPath: "/compare/personal-agents-browser-use/",
- caption: "General browser paths and one named operation solve different jobs.",
- cardDescription: "Why GhostGet exposes named operations instead of falling back to general browser control.",
- cardTitle: "Browser-using agents and named operations",
- credit,
- derivatives: [
- {
- height: 216,
- sha256: "e0c64330bd289346d1525399f6f98b79c865d99bc4416bf87b9aec39499afef7",
- src: "/images/editorial/personal-agents-browser-use-384.webp",
- width: 384,
+ "alt": "Ivory ribbons, stepping stones, and an arch lead toward a small amber doorway.",
+ "canonicalPath": "/compare/",
+ "caption": "",
+ "cardDescription": "Choose a reader, browser tool, or account integration based on the work the agent needs to do.",
+ "cardTitle": "How agents reach the web",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "9f738d05ea9af35a6b713792fd45b271eacc9637cb5fff19855c67a1c50d2ec3",
+ "src": "/images/editorial/how-agents-reach-the-web-paper-384.webp",
+ "width": 384
},
{
- height: 432,
- sha256: "a5b99a499e0263461274a83ef24eb0dfd3e666be381ccc7b1e98400516f41563",
- src: "/images/editorial/personal-agents-browser-use-768.webp",
- width: 768,
+ "height": 432,
+ "sha256": "9854f14527e3e3ae2dc634e03c85f86f69f70b3becf3bfec6665f1122feac03a",
+ "src": "/images/editorial/how-agents-reach-the-web-paper-768.webp",
+ "width": 768
+ }
+ ],
+ "height": 864,
+ "imageSha256": "f959955b59f1cd4865cd65e75cde962abb4ed95d4a4624245232b006a52dddb4",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/how-agents-reach-the-web/job.json",
+ "prompt": "editorial-provenance/brand-series/how-agents-reach-the-web/prompt.txt",
+ "promptSha256": "19c3998e7dd6f6b551ed00f14615de95c72814ff41c49387efd35d57006771e7",
+ "receipt": "editorial-provenance/brand-series/how-agents-reach-the-web/receipt.json"
+ },
+ "src": "/images/editorial/how-agents-reach-the-web-paper.webp",
+ "title": "How agents reach the web",
+ "width": 1536
+ },
+ {
+ "alt": "An ivory paper fan sits beside a single amber tab fitted into a matching recess.",
+ "canonicalPath": "/compare/personal-agents-browser-use/",
+ "caption": "",
+ "cardDescription": "Choose tools by how much of the task is known before the agent starts.",
+ "cardTitle": "Browser control or named actions",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "ff10e7152da213b475a96c2e98e6127073265bb4fb525981c043cac57a6f7756",
+ "src": "/images/editorial/personal-agents-browser-use-paper-384.webp",
+ "width": 384
},
+ {
+ "height": 432,
+ "sha256": "93c9b8b7e801ce9a82fd78906dc9bb904cd7f10e1e6054a3a29ae928727b6d8f",
+ "src": "/images/editorial/personal-agents-browser-use-paper-768.webp",
+ "width": 768
+ }
],
- height: 864,
- imageSha256: "482c7291a484712f8baae25d6cca945caf770c567ec5144a4d55fe3a60f8483d",
- provenance: {
- job: "editorial-provenance/personal-agents-browser-use/job.json",
- prompt: "editorial-provenance/personal-agents-browser-use/prompt.txt",
- promptSha256: "ca8732247170e0df20119e1f528699943f807e6f140c2850887c6df2878022c7",
- receipt: "editorial-provenance/personal-agents-browser-use/receipt.json",
+ "height": 864,
+ "imageSha256": "d333963c073c171b0c7d7f9479bbb031de5c77aa05d567e3be81a911fe415e22",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/personal-agents-browser-use/job.json",
+ "prompt": "editorial-provenance/brand-series/personal-agents-browser-use/prompt.txt",
+ "promptSha256": "80478b24d6b23e433342591710fd0e50e83e2c17b1a0a55ba99bfcb7d3bc1ce0",
+ "receipt": "editorial-provenance/brand-series/personal-agents-browser-use/receipt.json"
},
- src: "/images/editorial/personal-agents-browser-use.webp",
- title: "Browser-using personal agents, and which web operations GhostGet attests",
- width: 1536,
+ "src": "/images/editorial/personal-agents-browser-use-paper.webp",
+ "title": "Browser control or named actions",
+ "width": 1536
},
{
- alt: "Similar identity masks casting different shadows beside one bounded outbound operation token",
- canonicalPath: "/agentic-web-spoofing/",
- caption: "An inbound identity claim is not the same proof as a named outbound operation.",
- cardDescription: "Why a failed inbound identity check does not establish a safe outbound operation.",
- cardTitle: "Agentic-web spoofing and outbound operations",
- credit,
- derivatives: [
- {
- height: 216,
- sha256: "a8ccf9a07d8c42ff57cf860cbbb7110acd8c2cb53d2c34232476513c66b1efe8",
- src: "/images/editorial/agentic-web-spoofing-384.webp",
- width: 384,
+ "alt": "Two folded ivory forms have circular openings; amber light passes through one.",
+ "canonicalPath": "/agentic-web-spoofing/",
+ "caption": "",
+ "cardDescription": "Separate a caller’s claimed name, verified identity, and permission to act.",
+ "cardTitle": "How to verify a web agent’s identity",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "c572f7edbb78c2599b1ad1a988c21aac88019aabded36d0af5124752116d3d54",
+ "src": "/images/editorial/agentic-web-spoofing-paper-384.webp",
+ "width": 384
},
{
- height: 432,
- sha256: "cbad44e839f07b0e7bc4416669805d8f966b7cdcb71f2eecbe47e95b2a59b394",
- src: "/images/editorial/agentic-web-spoofing-768.webp",
- width: 768,
+ "height": 432,
+ "sha256": "4ead503e9a2073656779339397774c56af23f1ff355d0463ad495ef0776e3bf6",
+ "src": "/images/editorial/agentic-web-spoofing-paper-768.webp",
+ "width": 768
+ }
+ ],
+ "height": 864,
+ "imageSha256": "98e8d514df2e7c365a5324e1da085c1f593e4fd7e7cb1bd2dd7789b89e6bb3e8",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/agentic-web-spoofing/job.json",
+ "prompt": "editorial-provenance/brand-series/agentic-web-spoofing/prompt.txt",
+ "promptSha256": "cb29f2af5565c1a1ade9d3e07cde2fc12f5384d70ae3bd7122e1dc7c8d19dd6d",
+ "receipt": "editorial-provenance/brand-series/agentic-web-spoofing/receipt.json"
+ },
+ "src": "/images/editorial/agentic-web-spoofing-paper.webp",
+ "title": "How to verify a web agent’s identity",
+ "width": 1536
+ },
+ {
+ "alt": "An amber thread crosses an open doorway in a small ivory paper room.",
+ "canonicalPath": "/vms-cannot-contain-agents/",
+ "caption": "",
+ "cardDescription": "Understand the files, accounts, and connections an agent can reach beyond its environment.",
+ "cardTitle": "What a virtual machine isolates",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "6d0cab958723b57e273543e3be974b7a2593bdc089d2ccf719c1580c971c9b05",
+ "src": "/images/editorial/vms-cannot-contain-agents-paper-384.webp",
+ "width": 384
+ },
+ {
+ "height": 432,
+ "sha256": "7f810af7c4df6aaf684e268534566d0cdc9093d16dd8bd03895c45a6373a6957",
+ "src": "/images/editorial/vms-cannot-contain-agents-paper-768.webp",
+ "width": 768
+ }
+ ],
+ "height": 864,
+ "imageSha256": "16df7ad5e2354cf6426b9b30fd72e7d50e45f5afafdaa83c8a22177609c5d256",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/vms-cannot-contain-agents/job.json",
+ "prompt": "editorial-provenance/brand-series/vms-cannot-contain-agents/prompt.txt",
+ "promptSha256": "df5245fca299a765d19e7833b64ad6503179f0b16deb439df3292b20ad357cdf",
+ "receipt": "editorial-provenance/brand-series/vms-cannot-contain-agents/receipt.json"
+ },
+ "src": "/images/editorial/vms-cannot-contain-agents-paper.webp",
+ "title": "What a virtual machine isolates",
+ "width": 1536
+ },
+ {
+ "alt": "An amber branch connects three ivory socket tiles beside a fourth unconnected tile.",
+ "canonicalPath": "/webmcp/",
+ "caption": "",
+ "cardDescription": "Discover a site’s tools, inspect their inputs, and make a supported read-only call.",
+ "cardTitle": "The tools websites publish for agents",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "a38db105d9785e2bcb1a46a901ff3012c3e7d5125e68366399474861e20e001c",
+ "src": "/images/editorial/webmcp-paper-384.webp",
+ "width": 384
},
+ {
+ "height": 432,
+ "sha256": "1bb53af82e747db6bd960ab0cd2271b8ad405281036a99f343a2165fc1c33090",
+ "src": "/images/editorial/webmcp-paper-768.webp",
+ "width": 768
+ }
],
- height: 864,
- imageSha256: "c0a9d91d6ea69998473912683caa8f74e967d775ae40a7250a638335d515016b",
- provenance: {
- job: "editorial-provenance/agentic-web-spoofing/job.json",
- prompt: "editorial-provenance/agentic-web-spoofing/prompt.txt",
- promptSha256: "0dd95428231fee8662d8441d2333fb07871acf8b80d6bebaa09e42d5acff6621",
- receipt: "editorial-provenance/agentic-web-spoofing/receipt.json",
+ "height": 864,
+ "imageSha256": "10f1bd03fad2be9bc5807e640812a82df83bffd7733cef4b899ab507a2f62a63",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/webmcp/job.json",
+ "prompt": "editorial-provenance/brand-series/webmcp/prompt.txt",
+ "promptSha256": "7bcc5e8515ad47425d6fa71dc5dbd672d44af7506b78da6a7857be587d0d661b",
+ "receipt": "editorial-provenance/brand-series/webmcp/receipt.json"
},
- src: "/images/editorial/agentic-web-spoofing.webp",
- title: "Agentic-web index spoofing, and why attested operations still matter",
- width: 1536,
+ "src": "/images/editorial/webmcp-paper.webp",
+ "title": "The tools websites publish for agents",
+ "width": 1536
},
{
- alt: "Tangled paths crossing nested translucent boxes beside one narrow named operation channel",
- canonicalPath: "/vms-cannot-contain-agents/",
- caption: "A broad machine boundary and a narrow named operation answer different questions.",
- cardDescription: "Why a virtual machine around an agent does not define a safe web operation.",
- cardTitle: "Virtual machines and attested operations",
- credit,
- derivatives: [
- {
- height: 216,
- sha256: "4769a9aa9e477586b9ac5e3d0a36e48470494c86439e8d7d7db3d3b42d28a143",
- src: "/images/editorial/vms-cannot-contain-agents-384.webp",
- width: 384,
+ "alt": "A folded ivory paper hand lowers an amber token into a shallow ivory tray.",
+ "canonicalPath": "/blog/introducing-ghostget/",
+ "caption": "",
+ "cardDescription": "Read pages, connect accounts, and review consequential actions before they run.",
+ "cardTitle": "Named web actions for your agent",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "ba58b66c69b704f18a2a33715cbdd7f43ddc3a9e5179fa67caa84ab7d64a098c",
+ "src": "/images/editorial/introducing-ghostget-paper-384.webp",
+ "width": 384
},
{
- height: 432,
- sha256: "fa8989edc05ce9a1e82816fc929af39c4ffd3164c0211ad2ccb5084c58e5237a",
- src: "/images/editorial/vms-cannot-contain-agents-768.webp",
- width: 768,
+ "height": 432,
+ "sha256": "79b44247a0faaa1f1b53cbc7fcba074e80b39d37f2d51fbae62dee2147605e44",
+ "src": "/images/editorial/introducing-ghostget-paper-768.webp",
+ "width": 768
+ }
+ ],
+ "height": 864,
+ "imageSha256": "75ba88760cb79563ba838c9fd12eb574f6290b26603b917f877f41b38c3e1c08",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/introducing-ghostget/job.json",
+ "prompt": "editorial-provenance/brand-series/introducing-ghostget/prompt.txt",
+ "promptSha256": "25077a690d47122cda32c56a8a3a7bea1b24808d5ec65c6301122e1d27dc5891",
+ "receipt": "editorial-provenance/brand-series/introducing-ghostget/receipt.json"
+ },
+ "src": "/images/editorial/introducing-ghostget-paper.webp",
+ "title": "Named web actions for your agent",
+ "width": 1536
+ },
+ {
+ "alt": "An amber thread joins an ivory paper bowl, envelope, and shallow tray.",
+ "canonicalPath": "/blog/built-on-ghostget/",
+ "caption": "",
+ "cardDescription": "Account reads, message exports, and page captures supply information for other tools.",
+ "cardTitle": "How other tools use GhostGet",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "45ecde6518af153e77721b3c49dbe2395c6a9438e647627acf3822ce4cc15a15",
+ "src": "/images/editorial/built-on-ghostget-paper-384.webp",
+ "width": 384
},
+ {
+ "height": 432,
+ "sha256": "d629f23c24f274b8361e3dfcd54b6b546837fd92c0b858cd85513e7ee694399a",
+ "src": "/images/editorial/built-on-ghostget-paper-768.webp",
+ "width": 768
+ }
],
- height: 864,
- imageSha256: "32cb5f31e6388c7e177af0b5a2c07fe4fb9bebf60892d1ec9935d6c86917adda",
- provenance: {
- job: "editorial-provenance/vms-cannot-contain-agents/job.json",
- prompt: "editorial-provenance/vms-cannot-contain-agents/prompt.txt",
- promptSha256: "8c1ad33c417c4e47a6086e396ac97f0ae0234417f9da64f89f2d6b5a318bd7ab",
- receipt: "editorial-provenance/vms-cannot-contain-agents/receipt.json",
+ "height": 864,
+ "imageSha256": "c872147ae99fa5301e3e9fed30ed03a822f561cdfcecfb40da6128e9aede954f",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/built-on-ghostget/job.json",
+ "prompt": "editorial-provenance/brand-series/built-on-ghostget/prompt.txt",
+ "promptSha256": "ce1548caa8b552ba70bc1be97eb220a843533531c4ba954d950eb96d5654f601",
+ "receipt": "editorial-provenance/brand-series/built-on-ghostget/receipt.json"
},
- src: "/images/editorial/vms-cannot-contain-agents.webp",
- title: "VMs cannot contain agents, and why attested web operations still matter",
- width: 1536,
+ "src": "/images/editorial/built-on-ghostget-paper.webp",
+ "title": "How other tools use GhostGet",
+ "width": 1536
},
{
- alt: "A dark field of faint site tiles each bearing a socket mark, crossed by one ivory channel whose gold thread lights a subset of sockets",
- canonicalPath: "/webmcp/",
- caption: "Many sites publish their own tools; GhostGet calls only the ones declared read-only.",
- cardDescription: "How WebMCP sites publish tools, what the public registry adds, and the three commands GhostGet runs against it.",
- cardTitle: "WebMCP sites and the tools your agent can call",
- credit: "Editorial illustration generated with SlopCamera.",
- derivatives: [
- {
- height: 216,
- sha256: "339c27da0aa8c319b4d7254ec5ddd4e656495c88a260ef54d9376f00b9e5a9ec",
- src: "/images/editorial/webmcp-384.webp",
- width: 384,
+ "alt": "A magnifying lens reveals an amber-lit seam in three overlapping ivory paper records.",
+ "canonicalPath": "/blog/ghostget-claims-register/",
+ "caption": "",
+ "cardDescription": "Check the result and understand idempotency before repeating a consequential request.",
+ "cardTitle": "Why a failed send can still have succeeded",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "806b7bfc81187996f59e3e0c9abb46a4b5ce213c9b32c48c7185b28af3ded4e7",
+ "src": "/images/editorial/ghostget-claims-register-paper-384.webp",
+ "width": 384
},
{
- height: 432,
- sha256: "b3ad3d785e0a5dd7a090cbd3aee16955535e545b7786cd5d5b8feabcb9b09b4f",
- src: "/images/editorial/webmcp-768.webp",
- width: 768,
+ "height": 432,
+ "sha256": "d8570d8594ff61861e9557f0845e1f03f1a87eab883cacb64429c1f4f6be8a3e",
+ "src": "/images/editorial/ghostget-claims-register-paper-768.webp",
+ "width": 768
+ }
+ ],
+ "height": 864,
+ "imageSha256": "f370dd07f25c16db9fd9d11e6a5dc4b7cd8a737fbdb7a81c046ddadd90fda03f",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/ghostget-claims-register/job.json",
+ "prompt": "editorial-provenance/brand-series/ghostget-claims-register/prompt.txt",
+ "promptSha256": "f4b9ab6c60e0f3ba946fe7a261c1016691fe83cf389e0fec812526e92e290c35",
+ "receipt": "editorial-provenance/brand-series/ghostget-claims-register/receipt.json"
+ },
+ "src": "/images/editorial/ghostget-claims-register-paper.webp",
+ "title": "Why a failed send can still have succeeded",
+ "width": 1536
+ },
+ {
+ "alt": "An ivory paper parcel and its matching impression are joined by an amber thread.",
+ "canonicalPath": "/blog/releases-that-prove-their-origin/",
+ "caption": "",
+ "cardDescription": "Match the file fingerprint and signed build record before installing a package.",
+ "cardTitle": "How to verify where a download came from",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "e582f9b6ebf4fdfe37a8772ea6c5696ab7debf914ceb46c601d0120e539806bb",
+ "src": "/images/editorial/releases-that-prove-their-origin-paper-384.webp",
+ "width": 384
},
+ {
+ "height": 432,
+ "sha256": "1a275b71d83bea57f3a08c2c6a6376a53cb0208b705d631a9be77c7312fd9b31",
+ "src": "/images/editorial/releases-that-prove-their-origin-paper-768.webp",
+ "width": 768
+ }
],
- height: 864,
- imageSha256: "639cc4bce3a64fd7e705855516447aa23f43debb713a3a67d632dc65e2c9e6d9",
- provenance: {
- generator:
- "@hraness/slopcamera source commit 6d08486ef5eac38720a436c50b8ff018c90fd334",
- job: "editorial-provenance/webmcp/job.json",
- prompt: "editorial-provenance/webmcp/prompt.txt",
- promptSha256: "8ddeacb2ccc9459cd5ac28089fb1da952867f7c7b2acb86bdaa182826926887a",
- receipt: "editorial-provenance/webmcp/receipt.json",
+ "height": 864,
+ "imageSha256": "547fe376f7be3de1c1ac8199fd3375d95cee91d99e939113b050e779af1ebb38",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/releases-that-prove-their-origin/job.json",
+ "prompt": "editorial-provenance/brand-series/releases-that-prove-their-origin/prompt.txt",
+ "promptSha256": "40e72190e248e97892fb962ded3762fba0357d7669209597b1f4d6584ead6520",
+ "receipt": "editorial-provenance/brand-series/releases-that-prove-their-origin/receipt.json"
},
- src: "/images/editorial/webmcp.webp",
- title: "WebMCP for agents: call website-published tools through GhostGet",
- width: 1536,
+ "src": "/images/editorial/releases-that-prove-their-origin-paper.webp",
+ "title": "How to verify where a download came from",
+ "width": 1536
},
+ {
+ "alt": "One amber fold interrupts an ivory paper strip and repeats in a separate small piece.",
+ "canonicalPath": "/blog/replayable-property-tests/",
+ "caption": "",
+ "cardDescription": "Generate inputs from a rule and preserve the smallest failure as a regression.",
+ "cardTitle": "Turn an unexpected input into a repeatable test",
+ "credit": "Generated with SlopCamera.",
+ "creditUrl": "https://slopcamera.com",
+ "derivatives": [
+ {
+ "height": 216,
+ "sha256": "7bb0d884662911f347142ff84d29303f654ba4f208032658c3e1deefcef3d2d0",
+ "src": "/images/editorial/replayable-property-tests-paper-384.webp",
+ "width": 384
+ },
+ {
+ "height": 432,
+ "sha256": "dcd19c09d807813a2f3c9154e3936e558d6028be865ffaaf19d27f03e64cea6b",
+ "src": "/images/editorial/replayable-property-tests-paper-768.webp",
+ "width": 768
+ }
+ ],
+ "height": 864,
+ "imageSha256": "3b3f8e55d30713ca1099ee860a5b544e190188648f29c4879f1d64e5c45d23f7",
+ "provenance": {
+ "generator": "@hraness/slopcamera source commit 7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "job": "editorial-provenance/brand-series/replayable-property-tests/job.json",
+ "prompt": "editorial-provenance/brand-series/replayable-property-tests/prompt.txt",
+ "promptSha256": "f11cd25f23011c943fdd23cb5e1360aade7f056f70c40a33345c0a91e1f9bc24",
+ "receipt": "editorial-provenance/brand-series/replayable-property-tests/receipt.json"
+ },
+ "src": "/images/editorial/replayable-property-tests-paper.webp",
+ "title": "Turn an unexpected input into a repeatable test",
+ "width": 1536
+ }
] as const satisfies readonly EditorialImage[];
export type EditorialPath = (typeof editorialImages)[number]["canonicalPath"];
diff --git a/website/editorial-provenance/brand-series/agentic-web-spoofing/job.json b/website/editorial-provenance/brand-series/agentic-web-spoofing/job.json
new file mode 100644
index 00000000..badb7f0d
--- /dev/null
+++ b/website/editorial-provenance/brand-series/agentic-web-spoofing/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004335430Z-image-08891af1-647","outputs":[{"bytes":283964,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004335430Z-image-08891af1-647/image-01.webp","sha256":"98e8d514df2e7c365a5324e1da085c1f593e4fd7e7cb1bd2dd7789b89e6bb3e8"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004335430Z-image-08891af1-647/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:43:58.317Z","createdAt":"2026-10-01T00:43:35.428Z","dispatchedAt":"2026-10-01T00:43:35.436Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_4c686192cdcd473ea9b2ce8f736f3fe4","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1223,"promptSha256":"cb29f2af5565c1a1ade9d3e07cde2fc12f5384d70ae3bd7122e1dc7c8d19dd6d","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"18bc67f7bed9e9e0f034e19ae514494664a3441cb896f7f58ab5a7c4b4f5b557","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:f056692c55c0396a3abe4f270f358f0261d5c21d7b883e2ad418eb971b92b802","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:43:58.317Z"}
diff --git a/website/editorial-provenance/brand-series/agentic-web-spoofing/prompt.txt b/website/editorial-provenance/brand-series/agentic-web-spoofing/prompt.txt
new file mode 100644
index 00000000..60059d50
--- /dev/null
+++ b/website/editorial-provenance/brand-series/agentic-web-spoofing/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+Two nearly identical folded ivory masks stand apart on a matte charcoal ground. A small amber beam passes through one clean circular opening and falls on the ground; the other casts a solid shadow. Sophisticated sculptural editorial metaphor about identity and verification, no faces or human eyes.
diff --git a/website/editorial-provenance/brand-series/agentic-web-spoofing/receipt.json b/website/editorial-provenance/brand-series/agentic-web-spoofing/receipt.json
new file mode 100644
index 00000000..8bd29d37
--- /dev/null
+++ b/website/editorial-provenance/brand-series/agentic-web-spoofing/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:43:35.430Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004335430Z-image-08891af1-647/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":283964,"file":"image-01.webp","mediaType":"image/webp","sha256":"98e8d514df2e7c365a5324e1da085c1f593e4fd7e7cb1bd2dd7789b89e6bb3e8"}],"request":{"promptCharacters":1223,"promptSha256":"cb29f2af5565c1a1ade9d3e07cde2fc12f5384d70ae3bd7122e1dc7c8d19dd6d","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:f056692c55c0396a3abe4f270f358f0261d5c21d7b883e2ad418eb971b92b802","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/built-on-ghostget/job.json b/website/editorial-provenance/brand-series/built-on-ghostget/job.json
new file mode 100644
index 00000000..4ba93d31
--- /dev/null
+++ b/website/editorial-provenance/brand-series/built-on-ghostget/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004612426Z-image-00baf251-b70","outputs":[{"bytes":159402,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004612426Z-image-00baf251-b70/image-01.webp","sha256":"c872147ae99fa5301e3e9fed30ed03a822f561cdfcecfb40da6128e9aede954f"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004612426Z-image-00baf251-b70/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:46:37.209Z","createdAt":"2026-10-01T00:46:12.424Z","dispatchedAt":"2026-10-01T00:46:12.433Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_bf253b34ae9546e3bf37ff07569a37eb","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1200,"promptSha256":"ce1548caa8b552ba70bc1be97eb220a843533531c4ba954d950eb96d5654f601","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"40ece1fc85ef28599402e1c49870767adc3bc36d45492e5f1c436beb92f8ac62","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:44dd458032e7f63da470e62e82821924cb4823d13ce8dc17642d050bf861e356","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:46:37.209Z"}
diff --git a/website/editorial-provenance/brand-series/built-on-ghostget/prompt.txt b/website/editorial-provenance/brand-series/built-on-ghostget/prompt.txt
new file mode 100644
index 00000000..3d06af7c
--- /dev/null
+++ b/website/editorial-provenance/brand-series/built-on-ghostget/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+Three different ivory folded-paper vessels share one continuous amber thread: a small open bowl, a narrow envelope-like fold, and a shallow rectangular tray. Each object has its own silhouette, with generous charcoal space. Visual subject is information moving between tools.
diff --git a/website/editorial-provenance/brand-series/built-on-ghostget/receipt.json b/website/editorial-provenance/brand-series/built-on-ghostget/receipt.json
new file mode 100644
index 00000000..c8219880
--- /dev/null
+++ b/website/editorial-provenance/brand-series/built-on-ghostget/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:46:12.426Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004612426Z-image-00baf251-b70/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":159402,"file":"image-01.webp","mediaType":"image/webp","sha256":"c872147ae99fa5301e3e9fed30ed03a822f561cdfcecfb40da6128e9aede954f"}],"request":{"promptCharacters":1200,"promptSha256":"ce1548caa8b552ba70bc1be97eb220a843533531c4ba954d950eb96d5654f601","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:44dd458032e7f63da470e62e82821924cb4823d13ce8dc17642d050bf861e356","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/ghostget-claims-register/job.json b/website/editorial-provenance/brand-series/ghostget-claims-register/job.json
new file mode 100644
index 00000000..5735e959
--- /dev/null
+++ b/website/editorial-provenance/brand-series/ghostget-claims-register/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004637410Z-image-3981ae7f-0e8","outputs":[{"bytes":260158,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004637410Z-image-3981ae7f-0e8/image-01.webp","sha256":"f370dd07f25c16db9fd9d11e6a5dc4b7cd8a737fbdb7a81c046ddadd90fda03f"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004637410Z-image-3981ae7f-0e8/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:46:59.237Z","createdAt":"2026-10-01T00:46:37.406Z","dispatchedAt":"2026-10-01T00:46:37.907Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_7a24ced9936f4c6db17b28fe54c77275","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1181,"promptSha256":"f4b9ab6c60e0f3ba946fe7a261c1016691fe83cf389e0fec812526e92e290c35","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"4cdcb15a76d8477ded9c459d7ba861b1594ea727b67bfd4e02fa6d8e9d2c6d25","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:6200a9fa423a8c307c364c69ae08c94701eb244a99927843b509d25bb3e5a2e3","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:46:59.237Z"}
diff --git a/website/editorial-provenance/brand-series/ghostget-claims-register/prompt.txt b/website/editorial-provenance/brand-series/ghostget-claims-register/prompt.txt
new file mode 100644
index 00000000..512440cc
--- /dev/null
+++ b/website/editorial-provenance/brand-series/ghostget-claims-register/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+Three ivory paper swatches lie in a staggered arrangement on charcoal. A narrow amber light reveals the grain and seam of the central swatch through a simple circular magnifying lens. No writing or tick marks. Visual subject is looking closely at evidence.
diff --git a/website/editorial-provenance/brand-series/ghostget-claims-register/receipt.json b/website/editorial-provenance/brand-series/ghostget-claims-register/receipt.json
new file mode 100644
index 00000000..a6745ec1
--- /dev/null
+++ b/website/editorial-provenance/brand-series/ghostget-claims-register/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:46:37.410Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004637410Z-image-3981ae7f-0e8/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":260158,"file":"image-01.webp","mediaType":"image/webp","sha256":"f370dd07f25c16db9fd9d11e6a5dc4b7cd8a737fbdb7a81c046ddadd90fda03f"}],"request":{"promptCharacters":1181,"promptSha256":"f4b9ab6c60e0f3ba946fe7a261c1016691fe83cf389e0fec812526e92e290c35","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:6200a9fa423a8c307c364c69ae08c94701eb244a99927843b509d25bb3e5a2e3","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/how-agents-reach-the-web/job.json b/website/editorial-provenance/brand-series/how-agents-reach-the-web/job.json
new file mode 100644
index 00000000..693a9c91
--- /dev/null
+++ b/website/editorial-provenance/brand-series/how-agents-reach-the-web/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004251915Z-image-6f0ff62f-1df","outputs":[{"bytes":247520,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004251915Z-image-6f0ff62f-1df/image-01.webp","sha256":"f959955b59f1cd4865cd65e75cde962abb4ed95d4a4624245232b006a52dddb4"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004251915Z-image-6f0ff62f-1df/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:43:14.705Z","createdAt":"2026-10-01T00:42:51.912Z","dispatchedAt":"2026-10-01T00:42:51.923Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_c81b89d7abc94939a160ce5ac48f419c","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1278,"promptSha256":"19c3998e7dd6f6b551ed00f14615de95c72814ff41c49387efd35d57006771e7","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"c33dd799fdc3d9979b63884fc2d1b014a6fe67474b0d724dca0bdd95d001e9b6","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:ca28131576cafc703d4461bb032d93edd83052cad9b745d3268938bf541b058a","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:43:14.705Z"}
diff --git a/website/editorial-provenance/brand-series/how-agents-reach-the-web/prompt.txt b/website/editorial-provenance/brand-series/how-agents-reach-the-web/prompt.txt
new file mode 100644
index 00000000..07272630
--- /dev/null
+++ b/website/editorial-provenance/brand-series/how-agents-reach-the-web/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+Several distinct ivory paths cross an open charcoal field and converge toward a small amber-lit threshold. One path is a folded ribbon, one a stepping-stone sequence, one an open arch, one a fine parallel track. Balanced editorial composition, pathways visibly different and equal in emphasis; no arrows or flowchart. Visual subject is choosing a route.
diff --git a/website/editorial-provenance/brand-series/how-agents-reach-the-web/receipt.json b/website/editorial-provenance/brand-series/how-agents-reach-the-web/receipt.json
new file mode 100644
index 00000000..0983b915
--- /dev/null
+++ b/website/editorial-provenance/brand-series/how-agents-reach-the-web/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:42:51.915Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004251915Z-image-6f0ff62f-1df/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":247520,"file":"image-01.webp","mediaType":"image/webp","sha256":"f959955b59f1cd4865cd65e75cde962abb4ed95d4a4624245232b006a52dddb4"}],"request":{"promptCharacters":1278,"promptSha256":"19c3998e7dd6f6b551ed00f14615de95c72814ff41c49387efd35d57006771e7","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:ca28131576cafc703d4461bb032d93edd83052cad9b745d3268938bf541b058a","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/introducing-ghostget/job.json b/website/editorial-provenance/brand-series/introducing-ghostget/job.json
new file mode 100644
index 00000000..d9951c3c
--- /dev/null
+++ b/website/editorial-provenance/brand-series/introducing-ghostget/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004548154Z-image-60cbae37-fcc","outputs":[{"bytes":167244,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004548154Z-image-60cbae37-fcc/image-01.webp","sha256":"75ba88760cb79563ba838c9fd12eb574f6290b26603b917f877f41b38c3e1c08"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004548154Z-image-60cbae37-fcc/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:46:12.394Z","createdAt":"2026-10-01T00:45:48.152Z","dispatchedAt":"2026-10-01T00:45:48.161Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_f17943a8a91d4c59ad280e298c7623af","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1186,"promptSha256":"25077a690d47122cda32c56a8a3a7bea1b24808d5ec65c6301122e1d27dc5891","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"a75650c6bef674bbaf1084ada45dcbb32743af4a3725659adc04775be1b2d478","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:9e3b0272e70639df34a17f31d81c366be2e5957a57c3c851f424cdb73c5db20e","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:46:12.394Z"}
diff --git a/website/editorial-provenance/brand-series/introducing-ghostget/prompt.txt b/website/editorial-provenance/brand-series/introducing-ghostget/prompt.txt
new file mode 100644
index 00000000..c804aeeb
--- /dev/null
+++ b/website/editorial-provenance/brand-series/introducing-ghostget/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+A delicate ivory folded paper hand places a single small amber token into a shallow ivory tray. Cropped as an abstract still life without a body, central action clear at thumbnail scale. Visual subject is delegating one defined action, no human facial features.
diff --git a/website/editorial-provenance/brand-series/introducing-ghostget/receipt.json b/website/editorial-provenance/brand-series/introducing-ghostget/receipt.json
new file mode 100644
index 00000000..532ef49d
--- /dev/null
+++ b/website/editorial-provenance/brand-series/introducing-ghostget/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:45:48.154Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004548154Z-image-60cbae37-fcc/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":167244,"file":"image-01.webp","mediaType":"image/webp","sha256":"75ba88760cb79563ba838c9fd12eb574f6290b26603b917f877f41b38c3e1c08"}],"request":{"promptCharacters":1186,"promptSha256":"25077a690d47122cda32c56a8a3a7bea1b24808d5ec65c6301122e1d27dc5891","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:9e3b0272e70639df34a17f31d81c366be2e5957a57c3c851f424cdb73c5db20e","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/personal-agents-browser-use/job.json b/website/editorial-provenance/brand-series/personal-agents-browser-use/job.json
new file mode 100644
index 00000000..555916db
--- /dev/null
+++ b/website/editorial-provenance/brand-series/personal-agents-browser-use/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004314770Z-image-a4622f4d-12e","outputs":[{"bytes":294648,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004314770Z-image-a4622f4d-12e/image-01.webp","sha256":"d333963c073c171b0c7d7f9479bbb031de5c77aa05d567e3be81a911fe415e22"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004314770Z-image-a4622f4d-12e/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:43:35.293Z","createdAt":"2026-10-01T00:43:14.768Z","dispatchedAt":"2026-10-01T00:43:14.777Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_40e5b8e0fc7645d2974416a07e790c63","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1221,"promptSha256":"80478b24d6b23e433342591710fd0e50e83e2c17b1a0a55ba99bfcb7d3bc1ce0","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"c6f44b70b0a4bf3965040258448acd758de246e4ac3e6ed4e87fa8e720d26b21","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:4bf26f2b56571725c2623aed3fa6efa39939b347d509e9ff54214fa36db483a2","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:43:35.293Z"}
diff --git a/website/editorial-provenance/brand-series/personal-agents-browser-use/prompt.txt b/website/editorial-provenance/brand-series/personal-agents-browser-use/prompt.txt
new file mode 100644
index 00000000..e919b642
--- /dev/null
+++ b/website/editorial-provenance/brand-series/personal-agents-browser-use/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+An open fan of three blank ivory paper planes occupies the left half; a single precisely fitted amber tab rests in an ivory slot on the right. Spacious asymmetrical still life. The fan and tab are tactile abstract objects, not a chart. Visual subject is exploratory movement and a defined action.
diff --git a/website/editorial-provenance/brand-series/personal-agents-browser-use/receipt.json b/website/editorial-provenance/brand-series/personal-agents-browser-use/receipt.json
new file mode 100644
index 00000000..3d9fe5a1
--- /dev/null
+++ b/website/editorial-provenance/brand-series/personal-agents-browser-use/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:43:14.770Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004314770Z-image-a4622f4d-12e/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":294648,"file":"image-01.webp","mediaType":"image/webp","sha256":"d333963c073c171b0c7d7f9479bbb031de5c77aa05d567e3be81a911fe415e22"}],"request":{"promptCharacters":1221,"promptSha256":"80478b24d6b23e433342591710fd0e50e83e2c17b1a0a55ba99bfcb7d3bc1ce0","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:4bf26f2b56571725c2623aed3fa6efa39939b347d509e9ff54214fa36db483a2","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/releases-that-prove-their-origin/job.json b/website/editorial-provenance/brand-series/releases-that-prove-their-origin/job.json
new file mode 100644
index 00000000..5a91f538
--- /dev/null
+++ b/website/editorial-provenance/brand-series/releases-that-prove-their-origin/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004659364Z-image-9571a6a9-88c","outputs":[{"bytes":296548,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004659364Z-image-9571a6a9-88c/image-01.webp","sha256":"547fe376f7be3de1c1ac8199fd3375d95cee91d99e939113b050e779af1ebb38"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004659364Z-image-9571a6a9-88c/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:47:21.607Z","createdAt":"2026-10-01T00:46:59.361Z","dispatchedAt":"2026-10-01T00:46:59.371Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_b6feb44363894cedb74ddcb342922189","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1193,"promptSha256":"40e72190e248e97892fb962ded3762fba0357d7669209597b1f4d6584ead6520","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"34868662c0c0515e8b266881eb8502f3f45d8968d0be0967cad94093098eafc6","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:a34df1763824eda04b59c06a2b111f1a8bff318b970c1da3f7b5be37956aa33e","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:47:21.607Z"}
diff --git a/website/editorial-provenance/brand-series/releases-that-prove-their-origin/prompt.txt b/website/editorial-provenance/brand-series/releases-that-prove-their-origin/prompt.txt
new file mode 100644
index 00000000..4a07e716
--- /dev/null
+++ b/website/editorial-provenance/brand-series/releases-that-prove-their-origin/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+One folded ivory paper parcel and its separate matching embossed impression sit across a charcoal field, connected by a short amber filament. Their distinctive irregular edge matches visibly. Tactile, quiet, precise. Visual subject is matching an object to its source.
diff --git a/website/editorial-provenance/brand-series/releases-that-prove-their-origin/receipt.json b/website/editorial-provenance/brand-series/releases-that-prove-their-origin/receipt.json
new file mode 100644
index 00000000..d6dbe56e
--- /dev/null
+++ b/website/editorial-provenance/brand-series/releases-that-prove-their-origin/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:46:59.364Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004659364Z-image-9571a6a9-88c/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":296548,"file":"image-01.webp","mediaType":"image/webp","sha256":"547fe376f7be3de1c1ac8199fd3375d95cee91d99e939113b050e779af1ebb38"}],"request":{"promptCharacters":1193,"promptSha256":"40e72190e248e97892fb962ded3762fba0357d7669209597b1f4d6584ead6520","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:a34df1763824eda04b59c06a2b111f1a8bff318b970c1da3f7b5be37956aa33e","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/replayable-property-tests/job.json b/website/editorial-provenance/brand-series/replayable-property-tests/job.json
new file mode 100644
index 00000000..ba5dcb69
--- /dev/null
+++ b/website/editorial-provenance/brand-series/replayable-property-tests/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004721773Z-image-74eea3a1-486","outputs":[{"bytes":237686,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004721773Z-image-74eea3a1-486/image-01.webp","sha256":"3b3f8e55d30713ca1099ee860a5b544e190188648f29c4879f1d64e5c45d23f7"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004721773Z-image-74eea3a1-486/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:47:45.541Z","createdAt":"2026-10-01T00:47:21.771Z","dispatchedAt":"2026-10-01T00:47:21.781Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_125aade4ba284743a536c217f9e78675","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1199,"promptSha256":"f11cd25f23011c943fdd23cb5e1360aade7f056f70c40a33345c0a91e1f9bc24","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"d29182b4fb7620f5d607ff7584a1e09d0bba278207a4c46917054bd6f5428468","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:7294c7976dc6c8410186d27fce42dc0bb24f80b96730999d806310139d65633f","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:47:45.541Z"}
diff --git a/website/editorial-provenance/brand-series/replayable-property-tests/prompt.txt b/website/editorial-provenance/brand-series/replayable-property-tests/prompt.txt
new file mode 100644
index 00000000..6a59da6d
--- /dev/null
+++ b/website/editorial-provenance/brand-series/replayable-property-tests/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+A flowing ivory paper strip repeats a sequence of simple folds. One unusual amber fold interrupts the sequence near the center, then appears again enlarged as a separate small paper object. Clear focal structure. Visual subject is finding and reproducing an unexpected case.
diff --git a/website/editorial-provenance/brand-series/replayable-property-tests/receipt.json b/website/editorial-provenance/brand-series/replayable-property-tests/receipt.json
new file mode 100644
index 00000000..c934a835
--- /dev/null
+++ b/website/editorial-provenance/brand-series/replayable-property-tests/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:47:21.773Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004721773Z-image-74eea3a1-486/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":237686,"file":"image-01.webp","mediaType":"image/webp","sha256":"3b3f8e55d30713ca1099ee860a5b544e190188648f29c4879f1d64e5c45d23f7"}],"request":{"promptCharacters":1199,"promptSha256":"f11cd25f23011c943fdd23cb5e1360aade7f056f70c40a33345c0a91e1f9bc24","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:7294c7976dc6c8410186d27fce42dc0bb24f80b96730999d806310139d65633f","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/review.json b/website/editorial-provenance/brand-series/review.json
new file mode 100644
index 00000000..29591bdb
--- /dev/null
+++ b/website/editorial-provenance/brand-series/review.json
@@ -0,0 +1,21 @@
+{
+ "generator": "SlopCamera",
+ "sourceCommit": "7a2de2fe1a67ae74baf52b574c07ea98c5e6cfce",
+ "cliSha256": "732e6afe087912e9c015b6bc3889733a5ecb74e4f5aeb9bd67940c1bd5962b5b",
+ "model": "openai/gpt-image-2",
+ "generated": 10,
+ "paidAttempts": 10,
+ "referencesUploaded": 0,
+ "palette": [
+ "#0e1113",
+ "#f0ede4",
+ "#e6ad63"
+ ],
+ "medium": "matte paper relief with fine grain and engraved contours",
+ "reference": "Sleepyland public/editorial/research/best-sleep-sounds.webp (visually inspected locally, not uploaded)",
+ "review": {
+ "fullSize": "Inspected every 1536\u00d7864 original for accidental lettering, distorted objects, palette drift, and framing.",
+ "thumbnails": "Inspected ten-image contact sheet at384px width per image.",
+ "independent": "Root Codex agent approved the full contact sheet: cohesive palette, distinct compositions, clear missing comparison image."
+ }
+}
diff --git a/website/editorial-provenance/brand-series/vms-cannot-contain-agents/job.json b/website/editorial-provenance/brand-series/vms-cannot-contain-agents/job.json
new file mode 100644
index 00000000..f165c916
--- /dev/null
+++ b/website/editorial-provenance/brand-series/vms-cannot-contain-agents/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004358410Z-image-06479c66-ad2","outputs":[{"bytes":149464,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004358410Z-image-06479c66-ad2/image-01.webp","sha256":"16df7ad5e2354cf6426b9b30fd72e7d50e45f5afafdaa83c8a22177609c5d256"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004358410Z-image-06479c66-ad2/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:44:19.953Z","createdAt":"2026-10-01T00:43:58.407Z","dispatchedAt":"2026-10-01T00:43:58.416Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_5b3bf57a643d461e8ef35d42e9495a77","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1221,"promptSha256":"df5245fca299a765d19e7833b64ad6503179f0b16deb439df3292b20ad357cdf","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"ce658dc484d5eecaa789fc1dfbeab1bdf8a51d90a3126fd0eaa9886d79d296c1","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:7bbef96377b50dd5486f28b2123586b5097c226d14fb300e03d099221470f67e","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:44:19.953Z"}
diff --git a/website/editorial-provenance/brand-series/vms-cannot-contain-agents/prompt.txt b/website/editorial-provenance/brand-series/vms-cannot-contain-agents/prompt.txt
new file mode 100644
index 00000000..f669c18e
--- /dev/null
+++ b/website/editorial-provenance/brand-series/vms-cannot-contain-agents/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+A small ivory architectural room with three walls and no roof rests on a charcoal plane. A narrow amber thread enters through its open doorway and continues beyond the room. Calm elevated angle, large negative space. Visual subject is an enclosure and the connections it deliberately leaves open.
diff --git a/website/editorial-provenance/brand-series/vms-cannot-contain-agents/receipt.json b/website/editorial-provenance/brand-series/vms-cannot-contain-agents/receipt.json
new file mode 100644
index 00000000..83ded108
--- /dev/null
+++ b/website/editorial-provenance/brand-series/vms-cannot-contain-agents/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:43:58.410Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004358410Z-image-06479c66-ad2/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":149464,"file":"image-01.webp","mediaType":"image/webp","sha256":"16df7ad5e2354cf6426b9b30fd72e7d50e45f5afafdaa83c8a22177609c5d256"}],"request":{"promptCharacters":1221,"promptSha256":"df5245fca299a765d19e7833b64ad6503179f0b16deb439df3292b20ad357cdf","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:7bbef96377b50dd5486f28b2123586b5097c226d14fb300e03d099221470f67e","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/brand-series/webmcp/job.json b/website/editorial-provenance/brand-series/webmcp/job.json
new file mode 100644
index 00000000..83f7a251
--- /dev/null
+++ b/website/editorial-provenance/brand-series/webmcp/job.json
@@ -0,0 +1 @@
+{"artifacts":{"directory":"artifacts/slopcamera/generated/gateway/20261001T004420329Z-image-9b6c8e22-fc9","outputs":[{"bytes":273754,"mediaType":"image/webp","path":"artifacts/slopcamera/generated/gateway/20261001T004420329Z-image-9b6c8e22-fc9/image-01.webp","sha256":"10f1bd03fad2be9bc5807e640812a82df83bffd7733cef4b899ab507a2f62a63"}],"receiptPath":"artifacts/slopcamera/generated/gateway/20261001T004420329Z-image-9b6c8e22-fc9/receipt.json","sampleFulfillment":{"produced":1,"requested":1,"status":"complete"}},"chargeMayHaveOccurred":true,"clientMaxRetries":0,"completedAt":"2026-10-01T00:44:43.481Z","createdAt":"2026-10-01T00:44:20.303Z","dispatchedAt":"2026-10-01T00:44:20.345Z","gatewayProviderFailover":"may-attempt-multiple-providers","interruptionSemantics":"A nonterminal dispatched job is ambiguous and must not be retried by Slopcamera; AI Gateway may have attempted multiple providers internally.","jobId":"gateway_d7cdf2cff17c47eb8febc5a022255695","kind":"slopcamera.gateway-media-job","model":"openai/gpt-image-2","noSlopcameraRetry":true,"operation":"image.generate","request":{"count":1,"inputs":[],"promptCharacters":1186,"promptSha256":"7bcc5e8515ad47425d6fa71dc5dbd672d44af7506b78da6a7857be587d0d661b","providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"requestSha256":"31b7defe5301defb3752f499234d351d13982235ae80be7a1eb9a1f734123dd9","routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:56b239dbeac72eecf977a59a3e3f8f26e1ecf101fba412eeeaa114a18533786c","providerCount":0,"providers":[],"providersTruncated":false},"schemaVersion":1,"state":"completed","updatedAt":"2026-10-01T00:44:43.481Z"}
diff --git a/website/editorial-provenance/brand-series/webmcp/prompt.txt b/website/editorial-provenance/brand-series/webmcp/prompt.txt
new file mode 100644
index 00000000..f2f0495d
--- /dev/null
+++ b/website/editorial-provenance/brand-series/webmcp/prompt.txt
@@ -0,0 +1,3 @@
+Create a text-free editorial banner for Ghostget, 1536 by 864 pixels, 16:9. One coherent limited-palette art direction inspired by restrained nocturnal editorial artwork: charcoal black (#0e1113), warm ivory (#f0ede4), and a single muted amber accent (#e6ad63). Use only these three color families and their tonal mixtures. Fine tactile paper grain, soft directional light, subtle engraved contour lines, matte sculptural paper relief. Calm, spacious, sophisticated, beautifully composed. Amber occupies less than 8% of the frame. Strong silhouette remains readable at 320 pixels. Keep meaningful objects within the middle 75% with generous crop margin. No words, numbers, lettering, logos, watermarks, literal screens, UI, security shields, padlocks, stock robots, neon glow, extra colors, glossy plastic, gradients across multiple hues, or fake scientific diagrams. This is an editorial metaphor, not a factual schematic.
+
+A few ivory ceramic tiles each contain a different simple carved socket. A slender amber branch joins three of the sockets across a charcoal field. Sparse offset composition with one focal connection. Visual subject is interfaces made to fit, no diagram labels.
diff --git a/website/editorial-provenance/brand-series/webmcp/receipt.json b/website/editorial-provenance/brand-series/webmcp/receipt.json
new file mode 100644
index 00000000..69ecdf24
--- /dev/null
+++ b/website/editorial-provenance/brand-series/webmcp/receipt.json
@@ -0,0 +1 @@
+{"catalog":{"snapshotId":"sha256:d0faa0deb3652fe891de07e6e842e5ecac9c4c429669a4dc44b951471052f173","status":"fresh"},"createdAt":"2026-10-01T00:44:20.329Z","inputs":[],"kind":"slopcamera.gateway-media-receipt","localValidation":{"decodeValidatedOutputs":1,"signatureOnlyOutputs":0,"status":"decode-passed"},"model":"openai/gpt-image-2","nextCommands":["slopcamera project add artifacts/slopcamera/generated/gateway/20261001T004420329Z-image-9b6c8e22-fc9/image-01.webp --role b-roll"],"operation":"image.generate","outputs":[{"bytes":273754,"file":"image-01.webp","mediaType":"image/webp","sha256":"10f1bd03fad2be9bc5807e640812a82df83bffd7733cef4b899ab507a2f62a63"}],"request":{"promptCharacters":1186,"promptSha256":"7bcc5e8515ad47425d6fa71dc5dbd672d44af7506b78da6a7857be587d0d661b","providerNamespaces":["openai"],"providerOptionsSha256":"ad58417c4d9c1cabbac5a5f29ba273700e9dc423b7d35d9a2b1c853fb3488cf0","size":"1536x864"},"routing":{"attemptCount":0,"attempts":[],"attemptsTruncated":false,"clientMaxRetries":0,"gatewayProviderFailover":"may-attempt-multiple-providers","generationId":"sha256:56b239dbeac72eecf977a59a3e3f8f26e1ecf101fba412eeeaa114a18533786c","providerCount":0,"providers":[],"providersTruncated":false},"sampleFulfillment":{"produced":1,"requested":1,"status":"complete"},"schemaVersion":1,"warnings":[]}
diff --git a/website/editorial-provenance/reviews/blog-editorial-review.json b/website/editorial-provenance/reviews/blog-editorial-review.json
new file mode 100644
index 00000000..93330bdd
--- /dev/null
+++ b/website/editorial-provenance/reviews/blog-editorial-review.json
@@ -0,0 +1,114 @@
+{
+ "reviews": [
+ {
+ "slug": "introducing-ghostget",
+ "bodySha256": "f76638820957ac1f602f24732c8016df0d9238617d9cc83a5955e5268098cd3b",
+ "approved": true,
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30",
+ "humanReview": null,
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "total": 11,
+ "observations": [
+ "The email-thread example explains when a named action saves navigation while pointing unfamiliar visual tasks to browser control; it establishes fit before listing features.",
+ "Reading and consequential writes are separated through concrete account, preview, and unknown-outcome behavior, with exact public commands supplying a complete first step."
+ ]
+ },
+ {
+ "slug": "built-on-ghostget",
+ "bodySha256": "1fabb17d38561245759e2b08676447248c1cb1a8691ded53f6fbf801b5002961",
+ "approved": true,
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30",
+ "humanReview": null,
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "total": 11,
+ "observations": [
+ "The hub now uses current runtime relations, avoiding the stale export relationship that incorrectly assigned the legacy importer to the current TextButler product.",
+ "The incomplete-conversation example explains why source context and truncation metadata matter downstream, giving the hub a useful lesson beyond a product list."
+ ]
+ },
+ {
+ "slug": "ghostget-claims-register",
+ "bodySha256": "f9b2a15d30957535a71df20cddeb27d979634a27528b72dd2d1620109b486497",
+ "approved": true,
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30",
+ "humanReview": null,
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "total": 11,
+ "observations": [
+ "The timeout example separates loss of the reply from failure of the action and explains why an empty search result may not settle the outcome.",
+ "The idempotency discussion identifies both reusing a key and service-side behavior as necessary, avoiding the mistaken implication that a locally generated identifier prevents remote duplicates."
+ ]
+ },
+ {
+ "slug": "releases-that-prove-their-origin",
+ "bodySha256": "856a51df8aa4f3354fe5f72fc66bb44397f0fb5f343178c28b457af6ace32c46",
+ "approved": true,
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30",
+ "humanReview": null,
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 1,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "total": 11,
+ "observations": [
+ "The article distinguishes byte integrity from builder identity before showing verification commands, so a matching checksum is not presented as proof of origin.",
+ "Installing the exact verified local archive completes the trust chain; downloading or installing a different file would break the connection the earlier checks establish."
+ ]
+ },
+ {
+ "slug": "replayable-property-tests",
+ "bodySha256": "7cb44c1f8533bfe3293b204bf4e7a03fb876eb93e1cf85739e10a33745ce94c8",
+ "approved": true,
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30",
+ "humanReview": null,
+ "scores": {
+ "readerUtility": 2,
+ "originalEvidence": 2,
+ "factualConfidence": 2,
+ "hostFit": 2,
+ "voiceIntegrity": 2,
+ "maintenanceValue": 2
+ },
+ "total": 12,
+ "observations": [
+ "The real __proto__ regression supplies a concrete reason to generate awkward structured values, and the public fast-check sample makes the replay mechanism portable.",
+ "The article pairs a seed/path recipe with a permanent minimal example, explaining why a dependency upgrade can change replay while the explicit regression remains useful."
+ ]
+ }
+ ]
+}
diff --git a/website/editorial-provenance/reviews/primary-source-checks.json b/website/editorial-provenance/reviews/primary-source-checks.json
new file mode 100644
index 00000000..a04e66ce
--- /dev/null
+++ b/website/editorial-provenance/reviews/primary-source-checks.json
@@ -0,0 +1,97 @@
+[
+ {
+ "url": "https://docs.aws.amazon.com/ec2/latest/devguide/ec2-api-idempotency.html",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ "kAclAssociation TerminateInstances The following API actions optionally support idempotency using a client token . The corresponding AWS CLI commands also support idempotency using a client token. A client token is a unique, case-sensitive string of up to 64 ASCII characters. To make an idempotent API request using one of these actions, specify a client token ",
+ "veloper Guide Idempotency in Amazon EC2 RunInstances idempotency Examples Retry recommendations for idempotent requests Ensuring idempotency in Amazon EC2 API requests When you make a mutating API request, the request typically returns a result before the operation's asynchronous workflows have completed. Operations might also time out or encounter other ser"
+ ]
+ },
+ {
+ "url": "https://fast-check.dev/docs/core-blocks/runners/",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ "appened. There are four major reasons for check to end: Reasons failed interrupted counterexample / counterexamplePath / error failure of the predicate true true / false not null too many pre-conditions failures true false null execution took too long given interruptAfterTimeLimit true true null successful run false true / false null Rewrite assert with check functi"
+ ]
+ },
+ {
+ "url": "https://fast-check.dev/docs/advanced/model-based-testing/",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ " Model based testing | fast-check {\"@context\":\"https://schema.org\",\"@type\":\"BreadcrumbList\",\"itemListElement\":[{\"@type\":\"ListItem\",\"item\":\"https://fast-check.dev/docs/advanced/\",\"name\":\"Advanced\",\"position\":1},{\"@type\":\"ListItem\",\"item\":\"https://fast-check",
+ "ed without it as well. In the context of fast-check, model-based testing involves defining a set of commands that can be seen as potential actions to be executed on your system. Each command consists of two elements: a check to verify if the action can be executed in the current context, and the action itself, which also performs assertions. Typically, we "
+ ]
+ },
+ {
+ "url": "https://cli.github.com/manual/gh_attestation_verify",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ "by the verification process. Ideally, the path of the signer workflow is also validated using the --signer-workflow or --cert-identity flags. Please note: if your attestation was generated via a reusable workflow then that reusable workflow is the signer whose identity needs to be validated. In this situation, you must use either the --signer-workflow or the --si",
+ " Enforce that the digest associated with the source repository matches the provided value --source-ref Enforce that the git ref associated with the source repository matches the provided value -t , --template Format JSON output using a Go template; see \"gh help formatting\" Examples # Verify an artifact linked with a repository $ gh ",
+ "--custom-trusted-root Path to a trusted_root.jsonl file; likely for offline verification --deny-self-hosted-runners Fail verification for attestations generated on self-hosted runners -d , --digest-alg (default \"sha256\") The algorithm used to compute a digest of the artifact: {sha256|sha512} --format Output format: {json} --hostname Con"
+ ]
+ },
+ {
+ "url": "https://www.rfc-editor.org/rfc/rfc8785",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ " RFC 8785: JSON Canonicalization Scheme (JCS) | RFC Editor function _updateScrollbarWidth(){if(!document.body||!document.body.style)return;document.body.style.setProperty('--rfc-editor-org-scrollbar-width',(window.innerWidth-document.documentElement.clientWidth)+'px')}window",
+ " RFC 8785: JSON Canonicalization Scheme (JCS) | RFC Editor function _updateScrollbarWidth(){if(!document.body||!document.body.style)return;document.body.style.setProperty('--rfc-editor-org-scrollbar-width',(window.innerWidth-document.documentElement.clientWidth)+'p"
+ ]
+ },
+ {
+ "url": "https://developers.google.com/search/docs/crawling-indexing/verifying-googlebot",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ "ming to be from Google. Google's crawlers and fetchers fall into three categories: Type Description Reverse DNS mask IP ranges Common crawlers The common crawlers used for Google's products (such as Googlebot). They always respect robots.txt rules for automatic crawls. crawl-***-***-***-***.googlebot.com or geo-crawl-***-***-***-***.geo.googlebot.com comm",
+ "tem\": \"https://developers.google.com/crawling/docs/crawlers-fetchers/verify-google-requests\" }] } Skip to main content Crawling infrastructure / English Deutsch Espa\u00f1ol Espa\u00f1ol \u2013 Am\u00e9rica Latina Fran\u00e7ais Indonesia Italiano Polski Portugu\u00eas \u2013 Brasil Ti\u00ea\u0301ng Vi\u00ea\u0323t T\u00fcrk\u00e7e \u0420\u0443\u0441\u0441\u043a\u0438\u0439 \u0627\u0644\u0639\u0631\u0628\u064a\u0651\u0629 \u0939\u093f\u0902\u0926\u0940 \u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22 \u4e2d\u6587 \u2013 \u7b80\u4f53 \u4e2d\u6587 \u2013 \u7e41\u9ad4 \u65e5\u672c\u8a9e \ud55c\uad6d\uc5b4 Sign in Home Docs Crawling in"
+ ]
+ },
+ {
+ "url": "https://www.rfc-editor.org/rfc/rfc9421",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ " RFC 9421: HTTP Message Signatures | RFC Editor function _updateScrollbarWidth(){if(!document.body||!document.body.style)return;document.body.style.setProperty('--rfc-editor-org-scrollbar-width',(window.innerWidth-document.documentElement.clientWidth)+'px')}window.addEventListener('",
+ " RFC 9421: HTTP Message Signatures | RFC Editor function _updateScrollbarWidth(){if(!document.body||!document.body.style)return;document.body.style.setProperty('--rfc-editor-org-scrollbar-width',(window.innerWidth-document.documentElement.clientWidth)+'px')}window."
+ ]
+ },
+ {
+ "url": "https://raw.githubusercontent.com/hraness/textbutler/main/README.md",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": []
+ },
+ {
+ "url": "https://knownagents.com/insights",
+ "checkedOn": "2026-09-30",
+ "status": 200,
+ "excerpts": [
+ "re most frequently impersonated, and how spoofing activity changes over time. A visit is considered spoofed when it claims a recognized agent identity but fails that agent's supported authentication method, such as verified IP or HTTP message signature (Web Bot Auth). Active Threat: AI Bot Spoofing Campaign We are observing a widespread campaign impersona",
+ "t is considered spoofed when it claims a recognized agent identity but fails that agent's supported authentication method, such as verified IP or HTTP message signature (Web Bot Auth). Active Threat: AI Bot Spoofing Campaign We are observing a widespread campaign impersonating AI bots to scan websites for vulnerabilities. The attacker appears to be targeting cre"
+ ]
+ },
+ {
+ "url": "https://developer.android.com/google/play/integrity/overview",
+ "status": 200,
+ "checkedOn": "2026-09-30",
+ "title": "Overview of the Play Integrity API | Android Developers",
+ "matchedTerms": [
+ "integrity",
+ "command",
+ "user",
+ "device"
+ ]
+ },
+ {
+ "url": "https://www.sudo.ws/docs/man/sudoers.man/",
+ "status": "unavailable",
+ "reason": "HTTP Error 403: Forbidden"
+ }
+]
diff --git a/website/essay-reviews.ts b/website/essay-reviews.ts
new file mode 100644
index 00000000..250f3675
--- /dev/null
+++ b/website/essay-reviews.ts
@@ -0,0 +1,65 @@
+import type { ArticleProvenanceRecord, ArticleReview } from "@hraness/design-kit";
+
+/** Independent written-content reviews; indexing policy remains in edge/robots.ts. */
+export const essayReviews = {
+ "agentic-web-spoofing.html": {
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "observation": "Explains why a claimed crawler identity is separate from verification; preserves distinctions among self-identification, network-origin checks, and signed requests."
+ },
+ "vms-cannot-contain-agents.html": {
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "observation": "Explains VM containment through the resources and credentials crossing the boundary, without promising that isolation alone constrains an agent’s authorized actions."
+ },
+ "compare-personal-agents-browser-use.html": {
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "observation": "Makes the decision depend on named action availability versus visual navigation, rather than presenting either interface as universally better."
+ },
+ "paypal-grapheneos-attestation.html": {
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "observation": "Separates device-policy signals from a complete security assessment and gives the reader a concrete compatibility troubleshooting path."
+ },
+ "rumour-is-the-exploit.html": {
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "observation": "Explains why reproduction must fail on the broken behavior before passing on the fix; approved exact actionable final-paragraph replacement supplied to author."
+ },
+ "omarchy-root-escalation.html": {
+ "drafting": "ai",
+ "review": {
+ "reviewer": "Codex editorial review (AI)",
+ "reviewerType": "ai",
+ "reviewedOn": "2026-09-30"
+ },
+ "humanReview": null,
+ "observation": "Distinguishes permission for a named operation from a persistent broad grant and includes revocation/background-service review as part of the decision."
+ }
+} as const satisfies Readonly>;
diff --git a/website/launch/launch.test.ts b/website/launch/launch.test.ts
index 9c4307d2..812ef119 100644
--- a/website/launch/launch.test.ts
+++ b/website/launch/launch.test.ts
@@ -42,11 +42,11 @@ describe("launch facts", () => {
expect(register.claims.filter((claim) => claim.status === "not-verified" && claim.layer === "configuration-readback").length).toBe(LAUNCH_CLAIMS_CONFIG_READBACK);
});
- test("the longer version takes its register counts from the facts module", async () => {
+ test("article fact slots resolve from source without requiring a register inventory in the prose", async () => {
const fragment = await readFile(join(repositoryRoot, "website/source/blog/introducing-ghostget.html"), "utf8");
const rendered = renderLaunchFactSlots(fragment);
expect(rendered).not.toContain("{{LAUNCH_FACT:");
- expect(rendered).toContain(`lists ${LAUNCH_CLAIMS_TOTAL} claims: ${LAUNCH_CLAIMS_EVIDENCED} evidenced and ${LAUNCH_CLAIMS_NOT_VERIFIED} not verified`);
+ expect(renderLaunchFactSlots("{{LAUNCH_FACT:claimsTotal}}")).toBe(String(LAUNCH_CLAIMS_TOTAL));
expect(() => renderLaunchFactSlots("{{LAUNCH_FACT:unknownFact}}")).toThrow(/Unknown launch fact/u);
});
diff --git a/website/public/images/editorial/agentic-web-spoofing-paper-384.webp b/website/public/images/editorial/agentic-web-spoofing-paper-384.webp
new file mode 100644
index 00000000..c0ffe552
Binary files /dev/null and b/website/public/images/editorial/agentic-web-spoofing-paper-384.webp differ
diff --git a/website/public/images/editorial/agentic-web-spoofing-paper-768.webp b/website/public/images/editorial/agentic-web-spoofing-paper-768.webp
new file mode 100644
index 00000000..92ab4f8f
Binary files /dev/null and b/website/public/images/editorial/agentic-web-spoofing-paper-768.webp differ
diff --git a/website/public/images/editorial/agentic-web-spoofing-paper.webp b/website/public/images/editorial/agentic-web-spoofing-paper.webp
new file mode 100644
index 00000000..33f74967
Binary files /dev/null and b/website/public/images/editorial/agentic-web-spoofing-paper.webp differ
diff --git a/website/public/images/editorial/built-on-ghostget-paper-384.webp b/website/public/images/editorial/built-on-ghostget-paper-384.webp
new file mode 100644
index 00000000..d771b02d
Binary files /dev/null and b/website/public/images/editorial/built-on-ghostget-paper-384.webp differ
diff --git a/website/public/images/editorial/built-on-ghostget-paper-768.webp b/website/public/images/editorial/built-on-ghostget-paper-768.webp
new file mode 100644
index 00000000..aabf6c01
Binary files /dev/null and b/website/public/images/editorial/built-on-ghostget-paper-768.webp differ
diff --git a/website/public/images/editorial/built-on-ghostget-paper.webp b/website/public/images/editorial/built-on-ghostget-paper.webp
new file mode 100644
index 00000000..fd7be8f2
Binary files /dev/null and b/website/public/images/editorial/built-on-ghostget-paper.webp differ
diff --git a/website/public/images/editorial/ghostget-claims-register-paper-384.webp b/website/public/images/editorial/ghostget-claims-register-paper-384.webp
new file mode 100644
index 00000000..960b193d
Binary files /dev/null and b/website/public/images/editorial/ghostget-claims-register-paper-384.webp differ
diff --git a/website/public/images/editorial/ghostget-claims-register-paper-768.webp b/website/public/images/editorial/ghostget-claims-register-paper-768.webp
new file mode 100644
index 00000000..a897a0ca
Binary files /dev/null and b/website/public/images/editorial/ghostget-claims-register-paper-768.webp differ
diff --git a/website/public/images/editorial/ghostget-claims-register-paper.webp b/website/public/images/editorial/ghostget-claims-register-paper.webp
new file mode 100644
index 00000000..c6560658
Binary files /dev/null and b/website/public/images/editorial/ghostget-claims-register-paper.webp differ
diff --git a/website/public/images/editorial/how-agents-reach-the-web-paper-384.webp b/website/public/images/editorial/how-agents-reach-the-web-paper-384.webp
new file mode 100644
index 00000000..4cf188b4
Binary files /dev/null and b/website/public/images/editorial/how-agents-reach-the-web-paper-384.webp differ
diff --git a/website/public/images/editorial/how-agents-reach-the-web-paper-768.webp b/website/public/images/editorial/how-agents-reach-the-web-paper-768.webp
new file mode 100644
index 00000000..77d84af5
Binary files /dev/null and b/website/public/images/editorial/how-agents-reach-the-web-paper-768.webp differ
diff --git a/website/public/images/editorial/how-agents-reach-the-web-paper.webp b/website/public/images/editorial/how-agents-reach-the-web-paper.webp
new file mode 100644
index 00000000..63f21c0e
Binary files /dev/null and b/website/public/images/editorial/how-agents-reach-the-web-paper.webp differ
diff --git a/website/public/images/editorial/introducing-ghostget-paper-384.webp b/website/public/images/editorial/introducing-ghostget-paper-384.webp
new file mode 100644
index 00000000..dff81f80
Binary files /dev/null and b/website/public/images/editorial/introducing-ghostget-paper-384.webp differ
diff --git a/website/public/images/editorial/introducing-ghostget-paper-768.webp b/website/public/images/editorial/introducing-ghostget-paper-768.webp
new file mode 100644
index 00000000..a40605ba
Binary files /dev/null and b/website/public/images/editorial/introducing-ghostget-paper-768.webp differ
diff --git a/website/public/images/editorial/introducing-ghostget-paper.webp b/website/public/images/editorial/introducing-ghostget-paper.webp
new file mode 100644
index 00000000..b5935b21
Binary files /dev/null and b/website/public/images/editorial/introducing-ghostget-paper.webp differ
diff --git a/website/public/images/editorial/personal-agents-browser-use-paper-384.webp b/website/public/images/editorial/personal-agents-browser-use-paper-384.webp
new file mode 100644
index 00000000..8b3324ec
Binary files /dev/null and b/website/public/images/editorial/personal-agents-browser-use-paper-384.webp differ
diff --git a/website/public/images/editorial/personal-agents-browser-use-paper-768.webp b/website/public/images/editorial/personal-agents-browser-use-paper-768.webp
new file mode 100644
index 00000000..9d58eb87
Binary files /dev/null and b/website/public/images/editorial/personal-agents-browser-use-paper-768.webp differ
diff --git a/website/public/images/editorial/personal-agents-browser-use-paper.webp b/website/public/images/editorial/personal-agents-browser-use-paper.webp
new file mode 100644
index 00000000..57960bd3
Binary files /dev/null and b/website/public/images/editorial/personal-agents-browser-use-paper.webp differ
diff --git a/website/public/images/editorial/releases-that-prove-their-origin-paper-384.webp b/website/public/images/editorial/releases-that-prove-their-origin-paper-384.webp
new file mode 100644
index 00000000..b0fc1bfc
Binary files /dev/null and b/website/public/images/editorial/releases-that-prove-their-origin-paper-384.webp differ
diff --git a/website/public/images/editorial/releases-that-prove-their-origin-paper-768.webp b/website/public/images/editorial/releases-that-prove-their-origin-paper-768.webp
new file mode 100644
index 00000000..1672a4fc
Binary files /dev/null and b/website/public/images/editorial/releases-that-prove-their-origin-paper-768.webp differ
diff --git a/website/public/images/editorial/releases-that-prove-their-origin-paper.webp b/website/public/images/editorial/releases-that-prove-their-origin-paper.webp
new file mode 100644
index 00000000..16fac8a3
Binary files /dev/null and b/website/public/images/editorial/releases-that-prove-their-origin-paper.webp differ
diff --git a/website/public/images/editorial/replayable-property-tests-paper-384.webp b/website/public/images/editorial/replayable-property-tests-paper-384.webp
new file mode 100644
index 00000000..b67e3d42
Binary files /dev/null and b/website/public/images/editorial/replayable-property-tests-paper-384.webp differ
diff --git a/website/public/images/editorial/replayable-property-tests-paper-768.webp b/website/public/images/editorial/replayable-property-tests-paper-768.webp
new file mode 100644
index 00000000..e9cb732d
Binary files /dev/null and b/website/public/images/editorial/replayable-property-tests-paper-768.webp differ
diff --git a/website/public/images/editorial/replayable-property-tests-paper.webp b/website/public/images/editorial/replayable-property-tests-paper.webp
new file mode 100644
index 00000000..bb3eea7f
Binary files /dev/null and b/website/public/images/editorial/replayable-property-tests-paper.webp differ
diff --git a/website/public/images/editorial/vms-cannot-contain-agents-paper-384.webp b/website/public/images/editorial/vms-cannot-contain-agents-paper-384.webp
new file mode 100644
index 00000000..b6691fe6
Binary files /dev/null and b/website/public/images/editorial/vms-cannot-contain-agents-paper-384.webp differ
diff --git a/website/public/images/editorial/vms-cannot-contain-agents-paper-768.webp b/website/public/images/editorial/vms-cannot-contain-agents-paper-768.webp
new file mode 100644
index 00000000..6f012a49
Binary files /dev/null and b/website/public/images/editorial/vms-cannot-contain-agents-paper-768.webp differ
diff --git a/website/public/images/editorial/vms-cannot-contain-agents-paper.webp b/website/public/images/editorial/vms-cannot-contain-agents-paper.webp
new file mode 100644
index 00000000..4b9c4822
Binary files /dev/null and b/website/public/images/editorial/vms-cannot-contain-agents-paper.webp differ
diff --git a/website/public/images/editorial/webmcp-paper-384.webp b/website/public/images/editorial/webmcp-paper-384.webp
new file mode 100644
index 00000000..58fa97f4
Binary files /dev/null and b/website/public/images/editorial/webmcp-paper-384.webp differ
diff --git a/website/public/images/editorial/webmcp-paper-768.webp b/website/public/images/editorial/webmcp-paper-768.webp
new file mode 100644
index 00000000..fb3ac01e
Binary files /dev/null and b/website/public/images/editorial/webmcp-paper-768.webp differ
diff --git a/website/public/images/editorial/webmcp-paper.webp b/website/public/images/editorial/webmcp-paper.webp
new file mode 100644
index 00000000..746d90b7
Binary files /dev/null and b/website/public/images/editorial/webmcp-paper.webp differ
diff --git a/website/site.test.ts b/website/site.test.ts
index 9c8f2059..ae57cdf9 100644
--- a/website/site.test.ts
+++ b/website/site.test.ts
@@ -54,7 +54,8 @@ import {
socialImageSiteDetails,
} from "@hraness/web-discovery/social-image/card";
import { SOCIAL_IMAGE_BRAND_MARK, SOCIAL_IMAGE_DESCRIPTION, SOCIAL_IMAGE_HOME_PAGE, socialSite } from "./social-image";
-import { BLOG_POSTS, blogPostPath, blogSitemapPaths } from "./blog";
+import { BLOG_POSTS, blogPostPath, blogSitemapPaths, indexablePosts } from "./blog";
+import { essayReviews } from "./essay-reviews";
import {
parseWebmcpRegistrySnapshot,
webmcpProviderPages,
@@ -208,13 +209,13 @@ describe("ghostget.com static site", () => {
expect(packageFiles).not.toContain("vercel.json");
expect(manifest).toMatchObject({
devDependencies: {
- "@hraness/design-kit": "github:hraness/design-kit#v0.35.0",
+ "@hraness/design-kit": "github:hraness/design-kit#v0.35.2",
"@hraness/site-footer": "https://github.com/hraness/site-footer/releases/download/v0.20.5/hraness-site-footer-0.20.5.tgz",
"@hraness/ui": "github:hraness/ui#v0.5.18",
},
});
- expect(lockfile).toContain('"@hraness/design-kit": "github:hraness/design-kit#v0.35.0"');
+ expect(lockfile).toContain('"@hraness/design-kit": "github:hraness/design-kit#v0.35.2"');
expect(lockfile).toContain('"@hraness/ui": "github:hraness/ui#v0.5.18"');
expect(lockfile).toContain('"@hraness/site-footer": "https://github.com/hraness/site-footer/releases/download/v0.20.5/hraness-site-footer-0.20.5.tgz"');
@@ -608,7 +609,8 @@ describe("ghostget.com static site", () => {
expect(html).not.toContain("data-analytics-event");
// The navigation and maker GitHub links plus the content-footer link.
expect(html.match(new RegExp(`href="${REPOSITORY_URL}"`, "gu"))).toHaveLength(3);
- expect(html).toContain("Privacy: cookieless PostHog analytics");
+ expect(html).not.toContain("Privacy: cookieless PostHog analytics");
+ expect(html).toContain('href="/privacy/"');
expect(html).toContain('href="/compare/personal-agents-browser-use/"');
for (const path of NOINDEX_ESSAY_PATHS) expect(html).not.toContain(`href="${path}"`);
expect(html).toContain('href="/docs/how-to/connect-beeper/"');
@@ -622,7 +624,7 @@ describe("ghostget.com static site", () => {
expect(argumentsSection).toContain('Arguments and comparisons ');
expect(argumentsSection).toContain('');
expect(argumentsSection?.match(/
/gu)).toHaveLength(
- INDEXABLE_EDITORIAL_IMAGES.length + 1,
+ INDEXABLE_EDITORIAL_IMAGES.length,
);
expect(guidesSection?.match(//gu)).toHaveLength(6);
expect(argumentsSection).toContain('href="/compare/"');
@@ -632,7 +634,7 @@ describe("ghostget.com static site", () => {
}
for (const image of editorialImages) {
expect(argumentsSection?.includes(`href="${image.canonicalPath}"`)).toBe(
- !isNoindexDocumentPath(image.canonicalPath),
+ !image.canonicalPath.startsWith("/blog/") && !isNoindexDocumentPath(image.canonicalPath),
);
expect(guidesSection).not.toContain(`href="${image.canonicalPath}"`);
}
@@ -726,7 +728,8 @@ describe("ghostget.com static site", () => {
expect(notFound).toContain(
' ',
);
- expect(notFound).toContain("Privacy: cookieless PostHog analytics");
+ expect(notFound).not.toContain("Privacy: cookieless PostHog analytics");
+ expect(notFound).toContain('href="/privacy/"');
// The shared design-kit status page: the homepage's primary action, three
// next links, one quiet agent line, and known pages for "Did you mean".
expect(notFound).toContain(' {
expect(llms).toContain(`${SITE_ORIGIN}/docs/tutorials/getting-started/`);
expect(llms).toContain(`${SITE_ORIGIN}/compare/personal-agents-browser-use/`);
for (const path of NOINDEX_ESSAY_PATHS) expect(llms).not.toContain(`${SITE_ORIGIN}${path}`);
- expect(llms).toContain("six ways agents reach the web");
+ expect(llms).toContain("[How agents reach the web](https://ghostget.com/compare/)");
expect(llms).toContain(`${SITE_ORIGIN}/docs/how-to/connect-beeper/`);
expect(llms).toContain(
`${String(beeperOperationCount)} supported actions. ${String(BEEPER_PRESENTATION_TRANSPORT_COUNTS.cliBackedOperationCount)} run through the pinned \`@beeper/cli\` 0.6.2 executable and ${String(BEEPER_PRESENTATION_TRANSPORT_COUNTS.desktopLoopbackOperationCount)} use fixed Desktop loopback reads.`,
@@ -785,7 +788,7 @@ describe("ghostget.com static site", () => {
expect(INDEXABLE_PUBLIC_PAGES).toHaveLength(PUBLIC_PAGES.length - NOINDEX_ESSAY_PATHS.length);
expect(sitemap.match(/
/gu)).toHaveLength(INDEXABLE_PUBLIC_PAGES.length + blogPaths.length);
expect(sitemap).toContain('xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"');
- expect(sitemap.match(//gu)).toHaveLength(INDEXABLE_EDITORIAL_IMAGES.length);
+ expect(sitemap.match(//gu)).toHaveLength(INDEXABLE_EDITORIAL_IMAGES.length + indexablePosts().length);
for (const page of INDEXABLE_PUBLIC_PAGES) {
expect(sitemap).toContain(`${SITE_ORIGIN}${page.canonicalPath} `);
}
@@ -838,9 +841,10 @@ describe("ghostget.com static site", () => {
const path = blogPostPath(post.slug);
const postHtml = await readFile(join(websiteRoot, "dist", path.slice(1), "index.html"), "utf8");
expect(postHtml).toContain(` `);
- expect(postHtml).toContain('By Hraness ');
- expect(postHtml).toContain(`Drafted with AI from the source code and reviewed by ${post.admission.review?.reviewer}.
`);
- expect(postHtml).toContain('