Publish OGP ADR — pgen adapter・実行・cache・privacy
状態・決定・根拠
提案・未実装(2026-10-05)。Publish Next ADR のドメイン・資格・可視性・revision を前提とする #7230 の決定記録です。現行は Publication Design の保存 HTML と明示した共有画像であり、ここで定める自動 OGP、job、registry、quota、object association はまだ存在しません。pgen の導入、paid rendering、infra provisioning、deploy はこの文書作成では行っていません。
本番の目標は publish Worker → 専用 browser executor → pgen headless Canvas → private R2 とする。 Worker は product policy と公開 commit を所有し、pgen の公開 browser API に PNG を生成させます。Node service は下記 readiness gate を満たせない場合に再評価する代案です。障害時に同じ job を Node へ自動転送しません。article HTML は引き続き @takazudo/publication-renderer、Free attribution / Pro CTA は server-owned document shell が描画し、pgen に記事本文や footer の HTML を作らせません。
scenario ごとの deterministic tests、provider smoke の gate、後続 issue 仕様は Publish 受け入れマトリクスと実装 issue 分割 にあります。
調査証拠(2026-10-05)
| 証拠 | 確認できたこと / 未確認の境界 |
|---|---|
| 固定 integration guide、上流 #6301 | #6311 は 8478d9637121f5f6f527bd8b7032ce25a925d27f に merge 済み、#6301 は 2026-10-04 に closed。Node の packaged consumer と local Chromium 検証の報告はある。Cloudflare example は typecheck/prototype のみで live Browser binding・Worker deploy・R2 write の証拠ではない。 |
npm registry の 4.0.0 manifest(npm view @takazudo/pgen version dist-tags と npm view @takazudo/ で read-only 確認) | latest / next はともに 4.0.0、exports は . のみ。必要な /、/、/、/ はない。現 npm release は利用条件を満たさない。上流 source の完了と package availability は別。 |
| GitHub latest release API | 回答は _attachments(2026-04-21)。render API の release 証拠には使えない。将来の提供版・integrity・export smoke を固定することが実装前提。 |
| Cloudflare Browser Run limits、pricing | Browser Rendering の文書は現在 Browser Run へ redirect。Paid の browser sessions は既定同時 200、起動 3/秒、60 秒はinactivity timeout。課金は browser 時間と月平均の daily peak 同時数。公開上限・料金とこの account の実際の quota は別。 |
Cloudflare の上記価格表は Paid の月 10 browser hours を超える部分が 0.09/hour、月平均同時数 10 を超える部分が 2/browser と記載(2026-10-05 取得)。この選択を「1 枚の価格が保証された」と解釈しません。起動・font load・描画・cleanup・失敗試行の browser 秒数を実測し、Worker/queue/D1/R2 の料金と固定費も加え、実装時に最新価格と account limits を再確認します。製品の Free 利用者にも同じ標準 OGP を提供し、Cloudflare Workers Free plan で本番運用できるという意味にはしません。
product registry と pgen の境界
PublishingThemeSelection は client が選べる semantic 値です。server registry が (templateId, templateRevision) を approved templateJson、正確な pgen field mapping、HTML reading theme、固定 slice ID、font manifests、policy、headless bundle digest に解決します。registry は review 済み immutable revision として配布し、同じ revision の bytes を上書きしません。未知・廃止・未検証の revision は新規選択を拒否。安全上 revoke した版は再描画も止め、以前配信した画像の association を閉じるかを incident policy で明示します。
| product 入力 | pgen 入力への変換 / 資格 |
|---|---|
| post の公開 title | system field Title、1–200 Unicode scalar。private note の title/body を再取得せず、今回明示した candidate または保存公開 snapshot だけを使う。 |
| profile の displayName | system field Author、1–80 scalar。server が取得。username、bio、link、avatar は v1 の描画入力にしない。 |
accent, pattern | catalog token → 固定 Palette / Pattern field 値。Free は選んだ Free template の default、Pro は allowlist の token。任意色・CSS・pattern 定義・field path は受け付けない。 |
font | token → review 済み template/font variant と face manifest。field を持つ版だけその実在する schema 名へ対応させる。pgen に汎用 Font API があるとは仮定しない。 |
subtitle | 0–120 scalar。Subtitle field を宣言した catalog 版でのみ Pro が設定可。field がない版は非 default 入力を拒否。Free default は空。 |
| seed、slice、寸法 | server が post:<opaque-post-id> と {id: "slice-1"}、1200×630 を指定。利用者による seed refresh、任意 slice/size は v1 対象外。 |
上流 example の Free palette 選択は example 固有です。zudo-text の Free は親 ADR の default のみを適用します。資格は JWT と server subscription に基づき admission と commit の両方で検証。downgrade 後は親 ADR の「既存 Pro theme の完全一致維持」の例外だけ許可し、新規 post へコピーする資格は与えません。raw pgen fields object、template JSON、URL/path、font bytes、plan、実行コードを owner API の入力にしません。
title と author は保存済み plain text をそのまま渡し、隠れた切り詰め・言語変換をしません。上限内でも文字が枠に収まることは curated template の全境界 fixture で確認します。日本語・結合文字・emoji 等は実際の承認 font の glyph coverage が必要です。Inter の上流 fixture が日本語を描けるとは扱いません。未対応 glyph は明示した field エラーとして公開前に返し、OS fallback font や文字置換で成功にしません。
template は modern slices-first、最初の slice は slice-1、全 slice/layer ID は固定、active variant は concrete template に export 済みであることを登録条件とします。移行で random ID を生成する legacy template は登録前に authoring lane で再保存します。MCP/CLI/Composer はそこでのみ使用し、本番では package の公開 API 以外へ依存しません。repeat、motion、component/instance、path、frame decorator、未対応 effect/filter は初期 catalog に入れず、pgen が許可した text/pattern/shape/group のみを使います。
v1 は avatar を描画しません。 pgen は remote/embedded/managed image を拒否し、assetDigests は空です。親 ADR の avatar identity は将来の trusted asset adapter 用の拡張余地であり、現在使える機能ではありません。HTML profile avatar の upload/配信は独立です。将来 avatar を OGP に使う版は digest/所有権/画像上限と adapter capability を追加して policy を改版し、その digest 変更で URL を変えます。
実在する公開 API の使用例
以下は未導入 package に対する将来 adapter の例です。entry、candidate、profile、postId は server が検証した値、executor は次節の product 実装。registry の例では Title, Author, Palette, Pattern が実在する template を選びます。request に tier 等を加えず、上流 types と browser API を使います。
import {
TemplateRenderError,
type TemplateRenderPolicy,
type TemplateRenderRequest,
type TemplateRenderResult,
} from "@takazudo/pgen/render";
import {
renderTemplateSliceBrowser,
type BrowserRenderAssets,
type BrowserRenderExecutor,
} from "@takazudo/pgen/render/browser";
const request: TemplateRenderRequest = {
templateJson: entry.templateJson,
templateId: entry.id,
templateRevision: entry.revision,
fields: {
Title: candidate.title,
Author: profile.displayName,
Palette: entry.resolvedPalette,
Pattern: entry.resolvedPattern,
},
seed: `post:${postId}`,
slice: { id: entry.ogpSliceId }, // registry asserts "slice-1"
expectedSize: { width: 1200, height: 630 },
};
const policy: TemplateRenderPolicy = entry.policy;
const assets: BrowserRenderAssets = entry.assets;
const trustedExecutor: BrowserRenderExecutor = executor;
async function renderApprovedOgp(): Promise<TemplateRenderResult> {
try {
return await renderTemplateSliceBrowser(
request, policy, assets, trustedExecutor,
{ deadlineMs: 15_000, signal: jobAbortController.signal },
);
} catch (error) {
if (error instanceof TemplateRenderError) {
// Map category + allowlisted path; never expose raw error.message.
throw toSafeOgpError(error.category, error.path);
}
throw toSafeOgpError("render-failure");
}
}
const result = await renderApprovedOgp();policy は id: "zudo-text-ogp", version: "1", requiredFields: ["Title", "Author"] と、承認した allowedPatterns、対応する patternDefinitions、必要 face の fonts: [{family, weight, style, sha256}]、次節の limits を持ちます。pattern 定義は trusted build が package から選び配布し、request から executable definition を注入しません。BrowserRenderAssets の実形は {bundle: Uint8Array, fonts: Array<{face: FontFaceDigest, bytes: Uint8Array}>}。template/font は npm に含まれないので product が licensed な完全 font と template を trusted key で provision します。bundle は trusted build 時に @takazudo/ の asset export を読み digest を固定します。Worker がこの IIFE を import/実行することはありません。
Node API を選ぶ代案も同じ request / policy を使います。path は request で渡しません。
import { renderTemplateSlice } from "@takazudo/pgen/render/node";
const result = await renderTemplateSlice(request, policy, {
trustedFontFile: (face) => approvedFontPaths.get(face.sha256),
});
// Isolated process supervisor owns admission, deadline and termination.result は {png: Uint8Array, width: 1200, height: 630, contentType: "image/png", provenance}。provenance の実 field は cacheKey(64 桁 SHA-256)、templateId, templateRevision, templateHash, normalizedFields, seed, slice, fontDigests, assetDigests, adapter: {id, version, rendererVersion, runtimeFingerprint} です。browser の adapter.id は headless-browser、rendererVersion は headless-bundle-sha256:<digest>。値を偽造せず実 result を保存します。policy ID/version は cacheKey の計算に入りますが result の直下にはないので、product が immutable registry receipt として別に記録します。
pgen の structured failure 例は TemplateRenderError {name: "TemplateRenderError", category: "missing-field", path: "fields.Title", message: "Required field Title is missing"}。toSafeOgpError は product 側の変換を表す説明上の関数で、pgen の export ではありません。親 ADR の ogp.provenance はこの pgen receipt と product の policy/profile identity receipt を包む内部 envelope として扱います。
owner 向け product error 例は 422 {contractVersion: 2, error: "ogp_invalid_input", field: "title", requestId: "…"}、一時失敗は 503 {contractVersion: 2, error: "ogp_unavailable", requestId: "…"} と Retry-After。これは pgen 自身の HTTP API ではありません。raw error message に font path や入力文字列が含まれ得るため、category を安全な定型文へ変換します。
実行基盤の選択と停止境界
| 観点 | Node service | Worker → browser executor(採用目標) |
|---|---|---|
| fidelity | 既存 native Canvas renderer。Canvas/Pango/Cairo/FreeType、platform と完全 font を固定する。 | 既存 headless Canvas bundle。canonical interactive viewer そのものではなく、native Node とも pixel parity を保証しない。対象 catalog の日本語/長題/全 token をこの経路で承認する。 |
| runtime / cold start | 別 service と native binary/container の管理、起動・font 登録が必要。Worker の Node compatibility で native canvas が動くと仮定しない。 | Worker は validation/orchestration、ブラウザ内で bundle を実行。起動・bundle 注入・FontFace load が遅延に加わる。既存 Worker/R2 topology に接続しやすい。 |
| operations / cost | service の固定/稼働費、CPU/memory、scale/queue、patching と process kill の運用を追加。provider は未選択で価格は未算出。 | browser 秒数と concurrency の従量、queue/admission と orphan session 監視が必要。warm reuse は費用/汚染リスクを増やすため v1 は 1 job/1 session、明示 close。 |
| fonts / assets | trustedFontFile が固定 path に解決。実 bytes・face・glyph を検証。process-wide first-file-wins registry があるため別経路の registerFont を禁止。 | trusted bytes を adapter が検証し executor が FontFace を読み込む。ネットワーク font、OS fallback、任意 URL は使用不可。 |
| determinism / cache | native adapter fingerprint の中だけで同じ入力を再利用。binary/OS/font 変更で境界を変える。 | bundle hash と実 Chromium version/platform の fingerprint の中だけで再利用。provider browser update 時は新境界、再承認する。 |
| deadline | 上流の同時 1 / admission 8 は hard cancellation ではない。1 job ごとに隔離 process/worker と supervisor の kill/reap が必要。 | executor は abort/期限切れで page/context/browser を閉じ、遅い起動完了も捕捉して close。Promise race だけで成功と扱わない。 |
BrowserRenderExecutor.render の実契約は {prepared, bundle, fonts, signal?, deadlineMs} → Promise<{dataUrl, runtimeFingerprint}>。pgen の上流 browser adapter は期限を executor に渡すだけなので、product executor が hard deadline を実装する必要があります。新しい空 page に trusted bundle/font だけを注入し、navigation、外向き request、worker/service worker、download を禁止。title は構造化引数として渡し、HTML/script source に連結しません。cookie/storage/credential は空、Browser binding は executor だけが持ちます。device scale は 1、承認 FontFace の load 完了を待ってから描画し、OS font の暗黙使用を許しません。public HTML の screenshot を撮る方式は採用しません。
起動時から 15 秒の monotonic deadline を設定し、期限後の結果は採用しません。finally と abort handler の双方で idempotent cleanup、最大 5 秒で close 確認、close 不明なら session ID だけを安全な運用 ledger に記録し再接続/終了する reaper に渡します。close 不明の session は admission slot を保持し、reaper が終了を確認するまで新 session で置き換えません。lease を失った executor は R2 attach/公開 commit ができません。controller crash に備え keep-alive/heartbeat を止め、provider inactivity cleanup も使いますが、その 60 秒を job の hard deadline と読み替えません。起動完了が abort 後に戻る race、close 失敗、controller crash の cleanup を実環境で証明できなければ本番 gate を通しません。
readiness gate と代案の選択基準
render subpath と headless asset を含む npm release を exact version/integrity で固定し、隔離 consumer の import/declaration/PNG smoke を通す。4.0.0 や source checkout を本番 release の代用にしない。
完全な日本語 font のライセンス・サイズ・face/glyph coverage、全 curated template の fidelity、1200×630 の decode、想定 browser fingerprint と bundle/font digests を承認する。
Browser binding/R2 staging を使う別途認可した検証で、cold/warm latency、同時 admission、429、timeout/abort、late launch、session cleanup、network isolation、D1 CAS/R2 orphan GC を測る。prototype/typecheck を deploy proof と呼ばない。
初期 SLO は cache miss 1 試行の p95 が 15 秒以内、cleanup は 5 秒以内。これは provider 保証ではなく採用判定の product 目標。想定 load と retry を含む月額上限は運用 owner が実装時に承認し、global browser 秒予算と kill switch を設定する。未設定なら render admission は閉じる。
browser で承認 template を忠実に描けない、deadline/cleanup を保証できない、または measured latency/cost が上記予算を満たさず Node の隔離検証が満たす場合は Node service ADR へ変更する。単なる一時障害は 503 と retry。変更時は adapter identity、新 URL、golden fixture、運用責任者を再承認する。
resource bounds・admission・失敗
以下は将来 product policy v1 の初期値です。pgen の default より狭い制限は registry validation と public contract の policy.limits で適用し、JSON/layout expansion の前後に検査します。上流最大値を増やして通す運用はしません。
| 対象 | 制限 |
|---|---|
| template / nested base JSON | maxTemplateBytes: 262144, maxBaseBytes: 524288、depth 16、nodes 10000。承認済み server data にも適用。 |
| field / text | 最大 8 scalar fields、field payload 8192 UTF-8 bytes、各 string 1024 UTF-16 units。Title/Author/Subtitle は前述 scalar 上限も検査。repeat 入力は拒否。maxStringLength は nested base JSON 自体を許す 524288 とし、これを文字欄の上限には流用しない。 |
| geometry / complexity | maxLayers: 128, maxPixels: 756000, maxDimension: 1200, maxCoordinate: 2400。選択 slice は幅 1200、高さ 630 完全一致、group 内と解決済み layout にも制限。 |
| font / bundle | 使用 face は最大 4、各完全 font は 10,000,000 bytes 以下、合計 24 MiB 以下。bundle は 20,000,000 bytes 以下。trusted inventory との一致と glyph preflight 必須。 |
| PNG / transport | PNG 5 MiB 以下、data URL は 7 MiB 以下で decode 前に拒否。pgen の structural check に加え executor で decode 幅高さを確認。expectedSize は assert であり resize/crop はしない。 |
| owner admission | account ごと publish mutation は 10/分、render miss は 30/時・100/日、同時 1。UTC clock による直近 60 秒 / 3600 秒 / 86400 秒の window を server で原子的に計数。Free/Pro 共通。cache hit / 同一 job replay は render miss quota を消費しない。 |
| global admission | 初期は同時 browser 2、起動 1/秒、待機最大 100 job、1 account の pending render job 最大 2。実 account quota と費用 budget の低い方を採用。満杯は 429 / dependency 障害は 503、Retry-After。 |
| time / retry | prep は 5 秒、browser は起動を含め 15 秒、cleanup 5 秒。1 owner HTTP は最大 25 秒待機。job は最大 3 試行、全体 120 秒、再試行間隔 2 秒・10 秒 + jitter、provider Retry-After を下限にする。120 秒を超える待機は同じ key による次の明示 retry が必要。 |
policy.limits に実在する名前は maxTemplateBytes, maxBaseBytes, maxDepth, maxNodes, maxStringLength, maxFieldBytes, maxLayers, maxPixels, maxDimension, maxCoordinate です。text scalar、field count、PNG/transport、font 総量、time、quota は product が別途検証し、架空の pgen option を渡しません。large Japanese font を含む bundle の Worker memory/CPU 適合性は readiness gate で測り、満たせない場合は executor の provision 方法または Node 代案を再設計します。
render quota は replay key ではなく account/job admission と各実試行の browser 秒 budget を計数します。異なる key で同じ hash を要求しても single-flight に合流し、同じ quota を回避する無制限 retry を許しません。cache hit の確認に必要な browser 起動も global 起動/秒数 budget には計上します。profile fan-out は最新 identity に coalesce し、1 account/1 post の旧 job を取消、owner publish を優先しつつ同じ global 上限を使います。fan-out は durable cursor で候補を順に走査し、pending 枠が空くまで投入を止めます。quota 待ちで落とさず次の window に再開、post ごとに最新可視性を再検査します。unpublish/archive/delete/password を付ける安全操作は render quota や subscription 障害で止めません(認証・通常の abuse 制御は適用)。
| pgen category / product 状態 | retry / owner 応答 |
|---|---|
missing-field, unknown-field, invalid-field-value | request 起因は 422 ogp_invalid_input、許可した product field だけ表示。registry/default 起因は operator defect として 503、無限 retry せず版を quarantine。 |
invalid-template, missing-slice, ambiguous-slice, unsupported-asset, fidelity-refusal | 承認済み registry の defect。自動 retry なし、503 ogp_template_unavailable、quarantine。unsupported image を fetch して補完しない。 |
unavailable-font | 対応外 glyph は field correction が必要な 422。provision 不足/digest/face 不一致は 503、quarantine。原因を trusted stage で分離できない場合も 503 とし、raw message の文字列一致で glyph 問題と断定しない。承認 font へ勝手に差し替えない。 |
resource-limit | 入力超過は parse 前 413 / 意味的上限 422。admission は 429、deadline は 503。category 一つで HTTP status を決めず trusted failure stage で分類。 |
render-failure, provider 429/5xx, R2 一時失敗 | 上限内だけ backoff retry、枯渇時 503 ogp_unavailable。不正 PNG・未知 fingerprint・digest 不一致は quarantine、retry なし。 |
| CAS/lease/profile/plan/source ownership 不一致 | 409 stale または現在の権限に対応する 403/404。古い結果を attach しない。新しい intent の確認が必要。 |
public GET や social bot request は render を起動しません。timeout した HTTP を受けた client は成功と推測せず、同じ key の retry または owner read で commit 状態を確認します。
公開 commit と render job
通常の publish/update は PNG を含む snapshot を原子的に公開する。 first publish が途中のとき post/timeline/canonical は存在しません。既存 post 更新中は前の snapshot/metadata/OGP の組を配信します。「本文だけ新しくして OGP は pending」は選びません。例外は後述の profile identity 更新と、OGP を公開しない protected 保存、および既存 legacy share image の維持です。
Worker が owner、plan、profile/post revision、source、assets、入力を検証。idempotency ledger に request fingerprint と固定 post ID(初回は予約 ID)、frozen candidate、desired render identity、期限を紐付ける。これは owner-only staging であり draft の PublishedPost row ではない。
article renderer の blocking diagnostic がなければ、current gate と scope を確認して cache lookup / render job を作る。job payload は approved OGP fields と registry keys のみ。Markdown/source/password/token は executor に渡さない。認証 token を queue に保存しない。
executor の trusted Worker 側が private R2 へ検証 PNG を stage。browser page に R2 binding/credential は渡さない。job の lease token / generation で completion を CAS。owner request がまだ有効なら commit へ進み、HTTP が既に終了していれば
readyのまま保存。遅い worker が単独で snapshot を公開しない。次の同じ key の認証済み retry が commit を行う。commit 直前に同じ profileRevision、post publishRevision/contentRevision、profile identity、state/gate epoch、source ownership、実効 plan を再検査。R2 object の existence/digest を確認し、D1 transaction で snapshot・OGP association・必要 asset association・revision・idempotency safe result を一緒に確定。title/description/theme だけ先行更新しない。
成功を返す前に disconnect しても ledger の同じ 201/200 を replay。R2 put 成功後に transaction が失敗した場合は association を作らず orphan cleanup へ。D1 commit 成功後に response が失われても公開済み object を compensation で削除しない。
job は (owner, idempotency operation, desired identity, generation) で識別し、durable admission/lease と deadline を持ちます。queue の重複配送は新たな browser を無条件に起動せず lease を検査。pending 同一 key は 409 publish_in_progress + Retry-After、異なる入力で key 再利用は 409 conflict。in-flight result は R2/ledger で合流し、同一 hash の不一致 bytes は上書きせず operator incident とします。
候補の本文等が必要な staging は owner-only、暗号化された service storage に置き、最大 24 時間で削除します。これは安全な応答だけを置く replay ledger と別の candidate storage です。初回 protection の平文 password は保持せず、request 内で verifier と fingerprint 用 HMAC に変換し、verifier は executor/job payload に渡しません。元の workspace E2EE key は取得しません。同じ key を再送する client が同じ fingerprint の入力を再提示できる間だけ期限切れ staging を再構築し、古い post/profile revision は改めて拒否します。完了 ledger の安全な応答は親 ADR の 30 日保持。永久 validation failure は同じ入力で自動再試行せず、変更した入力は新 key。transient failure の同じ key retry は quota を再検査します。
| 条件 | 公開結果 / 補償 |
|---|---|
| 初回 render/preflight 失敗 | PublishedPost と public association を作らない。予約 slug claim は pending 操作に限定、期限切れで解放。opaque post ID は同じ ledger key に固定し他操作へ再割当せず、同 key の retry は slug を再取得して検査。owner に 422/503。 |
| update / metadata / replace render 失敗 | 前の公開 snapshot、title/theme、revision、OGP をすべて保持。private note を取り直さない。 |
| 一時失敗の retry | 同じ key/fingerprint/post ID/seed、既存検証済み bytes があれば再利用。成功した commit は 1 回だけ。 |
| HTTP deadline / disconnect / crash | worker は stage だけ継続可能、lease/deadline 内のみ。commit 完了の有無は ledger で判定し、未確定は owner retry が commit。 |
| render 中の unpublish/archive/delete/protection/profile deactivate | 安全操作を先に commit、job invalidate。遅い完成は公開しない。delete 済み post を復活させない。 |
| render 中の profile/plan/post/source 変更 | commit 再検査で拒否。R2 は orphan または同じ owner の有効 cache としてのみ残す。 |
| R2 put 後の D1 failure / CAS loser | D1 に reference がない object だけ後述 GC。retry で二重 post を作らない。 |
| D1 commit 後に object 欠損/破損を検知 | 本文 snapshot は戻さず og:image を一時省略し generic card、画像 read は no-store 503。保存済み公開 inputs だけから修復 job、alarm。private note は読まない。 |
| protected / unpublished の明示 snapshot 保存 | OGP は生成・公開せず ogp: null として保存可。旧 association は無効化。後の明示 publish / password remove で露出させる前に必要 OGP を生成し、失敗なら従前の非公開/保護状態を保持。 |
| public → unlisted / password enable / unpublish | 新 render を待たず gate を変更。unlisted 化は同じ現行 OGP を利用可。保護化・公開停止は画像も閉じる。 |
OGP 補助 record は mode: "auto" | "existing-share" | "none" と desired identity、ogpGeneration を保持します。これは親 ADR の logical schema を具体化する案で、初回の次期 publish は auto が既定、既存 share image は existing-share、既存情報なしは none と解釈します。owner read には raw provenance の代わりに ready/ と安全な error code を返します。unpublish/保護化で image association を消しても auto の選択は残し、将来の明示再公開時に評価します。
reactivate は親 ADR の明示確認を要します。保存済み image がある public/unlisted post は現在の profile identity を評価し、古い identity なら下記 profile job に進めます。新 metadata のない既存 post は share asset/generic card を保ち、導入時の全件 backfill はしません。
live profile identity と画像の版
v1 profileIdentityDigest は schema version と 実際に描く displayName から server が求め、job desired identity に結びます。displayName の変更は published かつ active な public/unlisted で自動 OGP が既に有効な post だけを enqueue。title/theme/seed は最後に確定した公開値を読みます。profile の avatar/bio/link、username/canonical、plan だけの変更では v1 OGP を再生成しません。profile default theme の変更も既存 post に伝播しません。
profile PUT は live HTML slots を即更新し、古い OGP を一時保持できます。この派生 job だけは owner retry を待たず association を更新可。完了時に desired profile identity、post contentRevision、現在の publishRevision/state/gate、auto OGP mode、active profile を CAS し、ogpGeneration を進めます。post の contentRevision/publishRevision/updatedAt は動かさず、新しい note を公開しません。連続 rename は最新 displayName に coalesce。古い job の結果は捨て、失敗時は旧 OGP と owner 向け安全な stale status を保ちます。protected/unpublished/archived/inactive は読み出して fan-out しません。
hash・R2・内部 cache
renderHash = result.provenance.cacheKey とし、自前の簡略 title hash を発明しません。実計算は上流 contract.ts の prepareTemplateRender と createRenderProvenance / createRenderCacheKey に任せます。
| hash 入力 | product が固定・検証する内容 |
|---|---|
| contract / policy | 上流 contract version、product policy ID/version。mapping、token、default、semantic policy を変えたら版を更新。policy の変更を同じ version で隠さない。 |
| template | caller-owned ID/revision、canonical normalized template hash、解決済み config(layout 等も含む)。template/font variant を変えたら identity が変わる。 |
| fields | pgen の normalizedFields。system Title/Author と使用 semantic fields を全部含める。object key 順・数値/boolean 正規化は upstream の契約を使う。 |
| seed / slice | post:<id>、解決済み slice の ID・geometry。retry の日時/random を入れない。 |
| fonts / assets | 使用した approved face の実 bytes digest/face metadata、将来の approved asset digest。v1 asset list は空。 |
| adapter | adapter ID/version、renderer/build identity、runtime fingerprint。browser は bundle digest と実 browser version/platform、Node は native stack/platform identity。 |
title、使用 subtitle/author identity、theme 選択の意味ある変更で新 hash / URL。本文だけ・descriptionだけ・tagだけ・未使用 profile 値だけなら PNG 再利用可能です。canonical rename は HTML metadata だけを更新します。出力の byte SHA-256 は別の integrity field として保存し、renderHash と混同しません。identical identity の byte reuse は 同じ adapter boundary 内のみ。異なる adapter の pixel/determinism や interactive Composer との一致は約束しません。
cache lookup には実際の runtime fingerprint が必要です。browser を起動する前の「おそらく今の Chromium」という値から最終 hash を作りません。v1 はセッションを確保して実 fingerprint と provisioned digests を取得後、公開 helper で key を求めて bytes hit を確認します。未知 fingerprint は描画前に quarantine。既に保存した現行 association の配信は新 session 不要です。実測 runtime の事前 attestation による起動省略は別最適化とし、TTL だけで browser 版を推測しません。
R2 は private binding のみ。object key は og/owners/<internal-owner-id>/posts/<post-id>/<render-hash>.png、公開 URL は /。key の owner segment を public DTO に出さず、post ID/hash は server 発行・検証し、path traversal や caller-selected object key は拒否します。別 owner/post への association コピーと cache timing による cross-account 検索を認めません。post を seed に含めるため通常は post 間 dedup しません。
D1 association は owner/post/hash、byte digest、size、寸法、registry receipt、provenance、current/retired、作成時刻を持ちます。raw provenance の fields は機微情報になり得るので public DTO/log に返しません。公開 handler は 現在の post/profile/state/gate と current association を先に検査し、その後だけ内部 bytes cache/R2 を読む。旧 hash は object が残っていても 404。通常 public も Cache-Control: no-store と CDN-Cache-Control: no-store、画像は Content-Type: image/png, X-Content-Type-Options: nosniff, X-Robots-Tag: noindex。認可前の CDN cache、公開 bucket、永続 presigned URL を作りません。
| retention / GC | 契約 |
|---|---|
| published の current image | association の参照中は保持。read では render しない。 |
| superseded / CAS loser / failed staging | 新規参照なしなら 24 時間後に GC 対象、daily sweep で削除。旧 public URL は即 404、内部 cache は同 owner の retry 用だけ。 |
| unpublish/archive/profile deactivate | public gate は即閉じる。保存 snapshot の current image は 30 日まで private に保持、その後 association を外し GC。republish は現在 identity を再検証し必要なら再描画。 |
| protected 化 | current public image association を即無効化。bytes は非参照になり上記 24 時間 GC。認証後も / では配信しない。 |
| permanent delete / account delete | 同じ transaction で全 association を外し job を取消、delete queue へ。daily sweep で残存 object を再照合・削除。URL claim tombstone に画像/fields を残さない。 |
GC は object の年齢だけで削除せず、D1 の参照と in-flight lease を検査し、GC tombstone を CAS で取得してから R2 delete。同じ hash を再使用する commit と競合する場合は tombstone の間 attach を拒否し、削除完了後に再 stage させます。削除失敗は ledger で再試行、最大 48 時間を運用上の削除目標として alarm を出します。logical denial は storage deletion を待ちません。すでに受信者/social service が保存した bytes は回収不能であり、no-store は第三者 cache の消去保証ではありません。
metadata と privacy matrix
HTML shell が metadata を一元生成し、Markdown/frontmatter/raw template から head tag をそのまま受け取りません。値は context に応じて escape。public origin は検証済み service registry、request Host や profile link で canonical を組み立てません。legacy の明示 share asset は保存契約どおり維持し、自動 OGP が有効な次期 post は Title/description を親 ADR の post metadata に統一します。既存 sharing title/description override のある post は次の明示更新で統一後の値を提示して確定し、導入時に勝手に変更しません。
| 状態 | title / description / canonical / author | OG / social card | 発見性・cache・画像 |
|---|---|---|---|
| active public post、password なし | 確定 title/description、現在の canonical、live displayName と public profile URL。description が空なら省略し private body から補完しない。 | og:type=article, og:title, og:description(あれば), og:url、article:author は profile URL。og:image, og:image:width=1200, og:image:height=630, og:image:type=image/png, og:image:alt、同じ twitter title/description/image/alt と twitter:card=summary_large_image。任意の twitter handle を生成しない。 | timeline/tag/sitemap 掲載、index 可。HTML/JSON/redirect/image は no-store、画像自体は noindex。 |
| active unlisted post、password なし | public と同じ。URL を知る人への共有であり秘密保護ではない。 | 同じ OGP。social bot も直接 URL から閲覧可。 | noindex,nofollow。timeline/tag/sitemap/件数に出さず、private/no-store。画像も gate 後だけ返す。 |
| protected、未認証(public/unlisted 共通) | generic 保護された投稿、generic description または省略。real title/author/canonical を出さない。 | generic text card のみ。real og:url/image/author/structured article metadata は省略。 | challenge/JSON 401、noindex,nofollow、private/no-store、Vary: Cookie。hash を知っても / は 404。 |
| protected、unlock 後 | 本文、確定 title/description、現在の canonical/author を認証済み応答にだけ表示可。 | og:image と twitter image は出さず、card は引き続き generic。social bot 用に capability/token 付き URL を作らない。 | noindex,nofollow、private/no-store、Vary: Cookie。未認証 public JSON/一覧には載せない。capability 付き single JSON は親 ADR の認証済み契約に従う。 |
| unpublished / archived / inactive / deleted | generic 404、title/description/canonical/author は省略。owner preview は別 JWT route。 | 全 post 固有 metadata と image を省略。 | private/no-store、noindex。過去 hash/alias/asset route も同じ gate。 |
| active profile / tag 一覧 | public displayName/bio と current canonical、tag 名は可視 post があるものだけ。 | og:type=website、public title/description/url、service 固定 generic card。v1 は profile 専用 pgen 画像を作らない。 | public かつ非保護の post だけを列挙、no-store。tag 0 件は HTML 404。 |
画像 alt は title と実際に描かれた author を用いる安全な plain text(400 scalar 以内)で、profile job pending 中は古い画像の provenance と一致させます。HTML author / article:author は live のままです。新旧 identity が一時混在することは owner status で示し、古い画像を現在の名前で描いたと偽らないようにします。画像がない legacy post、profile、欠損復旧中は twitter:card=summary と安全な text metadata を返し、private 内容や別 post の画像で穴埋めしません。
署名 cursor、source、workspace ID、private Markdown、password verifier、gate cookie、upload receipt、internal owner ID、render job ID、template JSON、raw provenance は公開 head/JSON/画像 URL に含めません。protected から unprotected への変更は parent の明示操作と CAS が必要で、image route の拒否を cookie の存在だけで解除しません。
観測と実装 acceptance
記録するのは opaque request/job correlation ID、registry/policy/adapter version、allowlisted error class・stage、queue/render/cleanup duration、cache hit/miss、PNG size、quota/retry/CAS-reject/GC/session-close の集計 counter です。raw title/author/fields/template/font path、URL query、source、本文、tokens、IP/UA の永続記録を行いません。運用 job record は owner-only で完了/取消から 24 時間(pending candidate も作成から最大 24 時間)、error log は 7 日、匿名 aggregate は 30 日を初期保持値とします。画像 fetch や article 閲覧に行動追跡 script/pixel/cookie を追加しません。provider console/trace/screenshot の capture も通常は無効にし、障害時も private input を収集する既定値にしません。
後続実装 PR の完了には次を要求します。この ADR 自体は runtime acceptance の代用ではありません。
release export/isolated consumer、registry schema/entitlement、unknown token、Free default、downgrade grandfathering、exact slice missing/ambiguous/size、max complexity、missing Japanese glyph、bad PNG/digest の contract tests。
同じ adapter/input の hash/bytes、意味ある title/theme/author 変更の新 URL、非描画 metadata の再利用、browser/native の別 key、runtime update quarantine を確認。
初回失敗・更新失敗・同 key replay・別入力 conflict・HTTP disconnect・R2/D1 中間失敗・late completion・lease/GC race・profile fan-out coalescing・plan/source/gate CAS の durable integration tests。
privacy matrix を HTML/JSON/alias/OG image/timeline/sitemap/owner preview で検証。protected hash guessing、unpublish 後の旧 hash、認可前 cache bypass、source/error-message leak を含む。
別途認可された実 Browser 環境で 15 秒 deadline、5 秒 cleanup、cold start、font isolation、global quota と月額試算、reaper を確認。承認 catalog の代表画像を人が視覚確認。実行していないものは gate 未達と記録。
docs は
pnpm --filter doc check。manager が heavy guard 経由の build と生成ページ確認を行い、broken anchor を失敗扱いにする。native build/e2e は docs 変更の検証には不要。
2026-10 amendments
採択した実装契約(2026-10-06、#7328 / #7329)。以下は承認済み UI に合わせた追記です。この追記と以前の提案・現行説明が矛盾する場合は追記を優先します。追記自体は runtime の実装・release・deploy の証拠ではありません。上書きの全体表と型の正本は Publish Next ADR、描画境界は Publish OGP ADR を参照してください。
picture registry・seed・描画入力
D3 / D4 は以前の post:<id>、seed refresh 対象外、単一 slice-1、Free palette default のみ、raw color 全拒否という制限を、Publish Next ADR の上書き表の範囲で変更します。pattern registry の immutable revision は thumb-3x2(1200×800)と ogp(1200×630)を持ち、slice ID を利用者が指定する API は作りません。thumb slice の maxPixels は 960000、OGP は従前の 756000 とし、寸法・complexity policy は slice ごとに検証します。expectedSize は検査であり server resize/crop ではありません。
PictureSpec は {pictureId, templateId, templateRevision, palette, customColors?, seedCounter}、seed は pic:<pictureId>:<seedCounter>。pictureId は client UUID、seedCounter は integer 0–999、post-level counter はありません。retry は同じ seed、利用者の regenerate は明示した counter 更新です。Palette は catalog token、Custom colors は Pro の最大 6 個の #rrggbb と background。save 時の immutable copy を使い、titled ogp slice だけ Title, Author, Text treatment を描きます。raw fields / template JSON / executable pattern / font URL は引き続き入力不可です。
post の sharingCard は {mode: "card" | "picture" | "picture-title", treatment: "plain" | "outline" | "plate" | "band", followThumbnail, picture?, ogpAsset?}。pattern thumbnail は同じ PictureSpec を使い、unlinked な non-card mode は独立 picture を必須にします。写真 thumbnail で mode === "picture" の場合は client の 1200×630 derivative を ogpAsset: {sha256} として使い、pgen に写真を入力しません。写真の picture-title は延期です。avatar/image-input 拒否、glyph / font proof、network isolation、hard deadline / cleanup、private R2 / GC の制限は維持します。
OGP identity は mode / treatment / followThumbnail と解決済み PictureSpec、使用 title/author、derivative digest を含みます。pgen render の renderHash は upstream provenance cacheKey のままで、全ての描画に使う選択を registry mapping / policy / normalized fields / seed / slice に反映します。写真 derivative は検証済み digest に identity を結び、pgen render を行った provenance を捏造しません。legibility は client-side derived verdict とし保存・hash 入力にしません。
admission と atomic commit
render admission が open の publish/update では render job が snapshot の atomic commit の一部です。初回 render 失敗は何も公開せず、update 失敗は以前の snapshot と association を保持します。PNG の stage 成功だけでは公開せず、commit 直前の profile / plan / post / gate CAS と R2 existence / digest を確認します。late completion が勝手に公開することはありません。
admission が closed の場合は publish を成功させ、ogp.status: "none" と generic service card を返します。closed は package/provider readiness、budget / kill switch 等に基づく server 状態です。open の render failure を自動的に closed の成功へ読み替えません。閉じた状態で記事が公開できることと、automatic pictures が提供済みであることは別です。
automatic pictures の delivered 判定には release-consumer proof S20 が必要です。exact npm release / integrity、required exports、isolated consumer の import / declarations と実 PNG の decode / dimensions / digest を通すまで、source checkout、prototype、fake executor、generic fallback だけを成功根拠にしません。Browser provider、licensed Japanese font、fidelity、15 秒 deadline、5 秒 cleanup、quota / cost / production readiness の別 gate も維持します。
2026-10-06 の parent workflow 調査では、上流 #6341 は open epic #6345 に置き換わり、render API の npm release はその epic の後へ延期されています。公開 4.0.0 の exports は依然 . のみで、必要 render subpaths の release-consumer gate は未達です。提供版を推測して admission を開けません。
配信と資格
Free は 3 pattern templates + 2 palettes、card / picture。Pro は全 themes / catalog、accent / pattern / font / subtitle overrides、treatments、custom colors、unlinked card picture、attribution / own text / CTA。資格は GET / と mutation commit の server policy が正本で、違反は 403 capability_required + field。
D9 をこの epic で採用します。public HTML / JSON / / / / / thumbnail はすべて Cache-Control: no-store + CDN-Cache-Control: no-store、unlisted / protected は X-Robots-Tag: noindex。現在の gate / association の認可を先に行い、内部 render reuse のみ cache します。後付け no-store は shared cache 済みの応答を撤回できず、従前の最大 600 秒の漏れを許すためです。
この epic からの延期
次の項目を後続 epic へ移します。このリストは実装済み・互換経路の追加を意味しません。
/route set。api/ v2 username/slug alias tables と、それらを使う 308 aliases。
POST /posts/:id/rename。POST /。posts/ archives/ purge idempotency ledger v2 fingerprint rules。
写真 thumbnail 上の
picture-title(photo + title)。pgen が image inputs を受け付けるまで延期。named presets。v1 は profile 上の一組の既定値だけを持ち、操作名は Save as defaults。