{"id":"ops/activity-adoption-proof","relativePath":"ops/activity-adoption-proof.md","title":"Activity Feed Adoption Proof Runbook","markdown":"# Activity Feed Adoption Proof Runbook\n\nThis runbook supports the Era C exit-gate target: at least three declared external Linked Art systems consume `/api/activity` within the adoption window.\n\nThe command below records a partner-owned declared consumer ID by sending `x-linked-art-consumer-id` to `/api/activity`, then reads `/api/activity/readiness` to confirm the deployment observed that declared external consumer.\n\n## Command\n\n```bash\nBASE_URL=https://<deploy-host> \\\nMETAMUSEUM_ACTIVITY_CONSUMER_ID=<partner-owned-consumer-id> \\\npnpm activity:adoption:probe\n```\n\nEquivalent explicit form:\n\n```bash\npnpm activity:adoption:probe -- --base-url https://<deploy-host> --consumer-id <partner-owned-consumer-id>\n```\n\nTo prove the full three-consumer checkpoint in one run after all partner IDs are known:\n\n```bash\npnpm activity:partner-pack -- \\\n  --base-url https://<deploy-host> \\\n  --consumer-id wikibase-cloud-prod \\\n  --consumer-id partner-harvest-bot \\\n  --consumer-id analytics-reader-prod\n```\n\nThe partner pack writes sendable per-consumer read and durable-subscription\nrequests to `artifacts/activity-syndication/activity-partner-onboarding-pack-latest.md`\nplus a timestamped\n`artifacts/activity-syndication/runs/activity-partner-onboarding-pack-<timestamp>.json`\naudit copy, and stays blocked until the IDs are real partner-owned values.\n\nIf the first ask is a broad Linked Art community or Slack post instead of a\nnamed partner request, capture that trail separately:\n\n```powershell\npnpm activity:community-outreach -- --channel \"<community/channel>\" --message-ref \"<permalink-or-thread-ref>\" --posted-at \"<ISO timestamp>\" --owner \"<owner>\"\n```\n\nThat artifact records outreach only. It stays `strictEvidence: false` and does\nnot move any consumer row to ready until real partner-owned IDs, feed reads, and\ncallback verification arrive.\n\n```bash\nBASE_URL=https://<deploy-host> \\\nMETAMUSEUM_ACTIVITY_CONSUMER_IDS=wikibase-cloud-prod,partner-harvest-bot,analytics-reader-prod \\\npnpm activity:adoption:matrix\n```\n\nEquivalent explicit form:\n\n```bash\npnpm activity:adoption:matrix -- \\\n  --base-url https://<deploy-host> \\\n  --consumer-id wikibase-cloud-prod \\\n  --consumer-id partner-harvest-bot \\\n  --consumer-id analytics-reader-prod\n```\n\nArtifacts:\n\n- `artifacts/activity-adoption/activity-adoption-proof-latest.json`\n- `artifacts/activity-adoption/runs/activity-adoption-proof-<timestamp>.json`\n- `artifacts/activity-adoption/activity-adoption-matrix-latest.json`\n- `artifacts/activity-adoption/runs/activity-adoption-matrix-<timestamp>.json`\n- `artifacts/activity-syndication/activity-partner-onboarding-pack-latest.md`\n- `artifacts/activity-syndication/activity-partner-onboarding-pack-latest.json`\n- `artifacts/activity-syndication/runs/activity-partner-onboarding-pack-<timestamp>.json`\n\n## Durable Syndication Evidence\n\nAfter real external consumers are observed, prove that they also have durable\nActivityStreams callback workflows registered:\n\n```bash\npnpm activity:syndication:evidence\npnpm activity:syndication:check\n```\n\nThe command reads:\n\n- `storage/activity-consumers.json`\n- `storage/activity-subscriptions.json`\n- `docs/linked-art/activity-feed-read-confirmations.json`\n- `config/era-c-exit-gate-policy.json`\n\nIf `storage/activity-subscriptions.json` does not exist yet, the evidence\ncommand initializes it with an empty `subscriptions` array and prints\n`subscription ledger: 0 row(s)`. That removes missing-ledger ambiguity for\noperators, but the empty ledger is not proof and the report remains blocked\nuntil real partner callbacks are verified.\n\nArtifacts:\n\n- `artifacts/activity-syndication/activity-syndication-evidence-latest.json`\n- `artifacts/activity-syndication/runs/activity-syndication-evidence-<timestamp>.json`\n\nThe report has `status: \"ready\"` only when the required real declared external\nconsumers in the configured window have feed-read telemetry or tracked\nreal partner confirmations covering `Create`, `Update`, and `Delete`; each same\nconsumer also needs an active external HTTPS `application/activity+json`\nsubscription covering `Create`, `Update`, and `Delete`, plus recent callback verification evidence with a 2xx status code.\nUse `consumerOnboarding.rows` as the per-partner acceptance ledger: each row\nmust have `feedReadObserved: true`, `durableSubscriptionReady: true`,\n`callbackVerified: true`, no blockers, and a real verification evidence ref.\nThe callback host must be public, non-placeholder, non-private, and match the\ncallback URL host. Placeholder IDs, derived fingerprints, local or private\ncallbacks, non-HTTPS callbacks, mismatched callback hosts, incomplete event\nsets, stale subscription rows, and subscriptions without callback verification\nremain blocked evidence.\n\nEach accepted row in `storage/activity-subscriptions.json` must carry real\nverification metadata similar to:\n\n```json\n{\n  \"verification\": {\n    \"status\": \"verified\",\n    \"verifiedAt\": \"2026-06-28T18:00:00.000Z\",\n    \"statusCode\": 202,\n    \"evidenceRef\": \"artifacts/activity-syndication/partner-one-callback-verification.json\"\n  }\n}\n```\n\nUse partner-owned evidence references, not demo, fixture, placeholder, sample,\nsmoke, test, or localhost proof.\n\nRecord callback verification with the operator command instead of hand-editing\nthe ledger:\n\n```powershell\npnpm activity:subscription:verify -- --consumer-id <partner-owned-consumer-id> --callback-url https://<public-consumer-host>/<activity-callback-path> --status-code 204 --verified-at <ISO timestamp> --evidence-ref artifacts/activity-syndication/<real-callback-proof>.json\n```\n\nThe command updates `storage/activity-subscriptions.json`, rejects placeholder\nevidence refs, rejects `--status=verified` with non-2xx status codes, and prints\n`pnpm activity:syndication:evidence` as the next verification step when the\nrecorded callback proof can count toward durable syndication.\nThe next `pnpm activity:syndication:evidence` run prints the number of complete\nonboarded consumers and any pending consumer IDs.\n\n## Producing Real Update/Delete Rows\n\nStrict activity-type coverage can only pass after the public feed has real\n`Update` and `Delete` activity rows for partners to read. Do not create demo or\nplaceholder events for this gate.\n\nFor `Update`, make a reviewed metadata correction with real source evidence.\nThe current production path stores the reviewed delta under\n`_source.reviewedUpdates[]`, and `/api/activity?type=Update&source=reviewed-update`\nexposes that row. Operator-initiated record rewrites may also write a real\n`record.update` audit row when the changed full record is posted to\n`/api/records`.\n\nFor `Delete`, record a tombstone only after a canonical upstream record returns\nHTTP `404` or `410` and a real evidence reference exists:\n\n```powershell\npnpm activity:tombstone:scan\n```\n\nThe scan reads canonical Met upstream IDs from the local record ledgers, writes\n`artifacts/activity-syndication/activity-tombstone-scan-latest.json` plus a\ntimestamped run artifact, and does not create a `Delete` row when every upstream\nrecord still returns a non-tombstone status. When the scan artifact shows a real\n`404` or `410`, rerun with `--write-tombstones` to write the deletion ledger\nusing that scan run as the evidence reference:\n\n```powershell\npnpm activity:tombstone:scan -- --write-tombstones\n```\n\nManual single-row recording is still available when the upstream proof was\ncaptured separately:\n\n```powershell\npnpm activity:tombstone:evidence -- --object-id <canonical-record-uri> --provider <provider-key> --upstream-status 410 --observed-at <ISO timestamp> --evidence-ref artifacts/activity-syndication/<real-upstream-404-or-410-proof>.json\n```\n\nBoth tombstone commands write `pipeline/state/deletion-activities.json`, reject\nnon-`404`/`410` statuses, and reject demo, fixture, placeholder, sample, smoke,\ntest, localhost, or otherwise fake evidence refs. After deploying the changed\nledger, ask partners to read `/api/activity?limit=100&type=Delete` with their\ndeclared consumer IDs and then rerun `pnpm activity:syndication:evidence`.\n\n## MetaHistoryBook Callback Follow-Up\n\nMetaHistoryBook replied on 2026-07-05 with consumer ID\n`metahistorybook-harvester-prod`, public callback URL\n`https://www.metahistorybook.com/api/metamuseum/callback`, and event types\n`Create`, `Update`, and `Delete`. They reported a broad cursor-feed read at\n`2026-07-05T16:37Z` with `Create` present and explicit zero-item `Update` and\n`Delete` reads. MetaMuseum later rechecked callback liveness, sent one signed\nproduction `Create` Activity payload, received `204`, and recorded the callback\nproof with `pnpm activity:subscription:verify` at\n`2026-07-05T17:13:13.312Z`. Evidence:\n`artifacts/activity-syndication/metahistorybook-harvester-prod-callback-2026-07-05T17-13-13-312Z.json`.\nNever print or paste the HMAC secret or signature value into evidence artifacts,\nchat, or email.\n\nPartner-response answers:\n\n- R1 secret exchange: complete; the local `.env` key is\n  `METAMUSEUM_CALLBACK_SECRET`. `signingSecretRef:\n  metahistorybook-harvester-prod-v1` remains the stored label, not the secret\n  itself.\n- R2 delivery shape: send a single Activity payload for verification and normal\n  event callbacks; partners may accept an `OrderedCollectionPage`, but the strict\n  evidence row should point to the concrete single delivery that returned 2xx.\n- R3 timestamp format: `x-metamuseum-signature-timestamp` is an ISO 8601 UTC\n  string, signed as `<timestamp>.<raw JSON payload>`.\n- R4 retry/verification semantics: production retry/backoff delivery is still\n  follow-up work; the current strict gate records recent real 2xx verification\n  evidence with `pnpm activity:subscription:verify`, and MetaHistoryBook now has\n  an accepted `204` callback row.\n- R5 `Update`/`Delete`: ping MetaHistoryBook after the deployed feed contains\n  real reviewed update rows and real upstream `404`/`410` tombstone rows.\n  MetaHistoryBook rechecked after callback verification and confirmed\n  `type=Update -> 0` and `type=Delete -> 0`; MetaMuseum independently rechecked\n  at `2026-07-05T17:21:34Z` and observed `totalItems: 0` for both URLs. Their\n  reader is ready to re-read both typed feeds after real rows deploy.\n- R6 reconciliation: keep Wikidata QIDs in `object.equivalent[]`; the cursor\n  feed now uses the same dereferenceable `object.id`, `object.sourceId`, valid\n  UTC `endTime`, and default-row dedupe as the Change Discovery layer while\n  retaining `linkedArt` embeds.\n\n## Consumer ID Rules\n\n- Use a stable ID owned by the external consumer, for example `wikibase-cloud-prod` or `partner-harvest-bot`.\n- Do not use placeholder IDs such as `test-*`, `demo-*`, `example-*`, `local-*`, or localhost-oriented names for production evidence.\n- The command rejects placeholder/local-looking IDs by default. `--allow-placeholder` exists only for local rehearsals.\n\n## Pass Criteria\n\nThe proof report has `status: \"pass\"` when:\n\n- `/api/activity` returns HTTP 2xx.\n- `/api/activity/readiness` returns HTTP 2xx.\n- Readiness telemetry contains the provided consumer ID as:\n  - `class: \"declared\"`\n  - matching `declaredId`\n  - `isExternal: true`\n  - non-empty `lastSeenAt`\n  - `observedActivityTypes` containing the activity types returned to that consumer\n\nThe overall adoption exit gate still requires three unique declared external consumers within the configured window. A single passing proof only confirms one consumer was observed.\n\nThe matrix report has `status: \"pass\"` only when every requested consumer probes `/api/activity` successfully, every requested consumer appears in readiness as declared/external, `/api/activity/readiness` reports the `>=3` checkpoint as ready, and the matrix readiness block shows no missing `Create`/`Update`/`Delete` activity types. The matrix command performs one broad feed read plus one `type=Create`, `type=Update`, and `type=Delete` read for each requested consumer, so a three-consumer run records 12 activity probes before the readiness check. Run `pnpm activity:syndication:evidence` afterward to confirm the same declared consumers have durable callback evidence too.\n\n## Partner Hand-Off\n\nSend partners this minimum request:\n\n```bash\ncurl -H \"Accept: application/json\" \\\n  -H \"x-linked-art-consumer-id: <partner-owned-consumer-id>\" \\\n  \"https://<deploy-host>/api/activity?limit=100\"\n\ncurl -H \"Accept: application/json\" \\\n  -H \"x-linked-art-consumer-id: <partner-owned-consumer-id>\" \\\n  \"https://<deploy-host>/api/activity?limit=100&type=Create\"\n\ncurl -H \"Accept: application/json\" \\\n  -H \"x-linked-art-consumer-id: <partner-owned-consumer-id>\" \\\n  \"https://<deploy-host>/api/activity?limit=100&type=Update\"\n\ncurl -H \"Accept: application/json\" \\\n  -H \"x-linked-art-consumer-id: <partner-owned-consumer-id>\" \\\n  \"https://<deploy-host>/api/activity?limit=100&type=Delete\"\n```\n\nAfter the partner runs it, operators can verify:\n\n```bash\ncurl \"https://<deploy-host>/api/activity/readiness\"\npnpm activity:adoption:matrix -- --base-url https://<deploy-host> --consumer-ids <id-1>,<id-2>,<id-3>\npnpm activity:syndication:evidence\npnpm era-c:exit-gate:evidence\n```\n\n## Evidence Hygiene\n\n- Derived fingerprints are diagnostic only and do not satisfy the gate.\n- Operators must not run production proof with internal placeholder IDs.\n- Operators must not use `example.*`, `.local`, localhost, or private-network\n  callback hosts for production syndication evidence.\n- Keep partner IDs stable across checks so the 30-day activity window reflects real adoption.\n- Do not claim durable syndication until the same partner IDs have matching\n  external HTTPS subscriptions with recent 2xx callback verification evidence\n  in `activity-syndication-evidence-latest.json`.\n","sections":[{"level":2,"heading":"Command","anchor":"command"},{"level":2,"heading":"Durable Syndication Evidence","anchor":"durable-syndication-evidence"},{"level":2,"heading":"Producing Real Update/Delete Rows","anchor":"producing-real-update-delete-rows"},{"level":2,"heading":"MetaHistoryBook Callback Follow-Up","anchor":"metahistorybook-callback-follow-up"},{"level":2,"heading":"Consumer ID Rules","anchor":"consumer-id-rules"},{"level":2,"heading":"Pass Criteria","anchor":"pass-criteria"},{"level":2,"heading":"Partner Hand-Off","anchor":"partner-hand-off"},{"level":2,"heading":"Evidence Hygiene","anchor":"evidence-hygiene"}],"html":"<h1 id=\"activity-feed-adoption-proof-runbook\">Activity Feed Adoption Proof Runbook</h1>\n<p>This runbook supports the Era C exit-gate target: at least three declared external Linked Art systems consume `/api/activity` within the adoption window.</p>\n<p>The command below records a partner-owned declared consumer ID by sending `x-linked-art-consumer-id` to `/api/activity`, then reads `/api/activity/readiness` to confirm the deployment observed that declared external consumer.</p>\n<h2 id=\"command\">Command</h2>\n<pre><code>\nBASE_URL=https://&lt;deploy-host&gt; \\\nMETAMUSEUM_ACTIVITY_CONSUMER_ID=&lt;partner-owned-consumer-id&gt; \\\npnpm activity:adoption:probe\n</code></pre>\n<p>Equivalent explicit form:</p>\n<pre><code>\npnpm activity:adoption:probe -- --base-url https://&lt;deploy-host&gt; --consumer-id &lt;partner-owned-consumer-id&gt;\n</code></pre>\n<p>To prove the full three-consumer checkpoint in one run after all partner IDs are known:</p>\n<pre><code>\npnpm activity:partner-pack -- \\\n  --base-url https://&lt;deploy-host&gt; \\\n  --consumer-id wikibase-cloud-prod \\\n  --consumer-id partner-harvest-bot \\\n  --consumer-id analytics-reader-prod\n</code></pre>\n<p>The partner pack writes sendable per-consumer read and durable-subscription</p>\n<p>requests to `artifacts/activity-syndication/activity-partner-onboarding-pack-latest.md`</p>\n<p>plus a timestamped</p>\n<p>`artifacts/activity-syndication/runs/activity-partner-onboarding-pack-&lt;timestamp&gt;.json`</p>\n<p>audit copy, and stays blocked until the IDs are real partner-owned values.</p>\n<p>If the first ask is a broad Linked Art community or Slack post instead of a</p>\n<p>named partner request, capture that trail separately:</p>\n<pre><code>\npnpm activity:community-outreach -- --channel &quot;&lt;community/channel&gt;&quot; --message-ref &quot;&lt;permalink-or-thread-ref&gt;&quot; --posted-at &quot;&lt;ISO timestamp&gt;&quot; --owner &quot;&lt;owner&gt;&quot;\n</code></pre>\n<p>That artifact records outreach only. It stays `strictEvidence: false` and does</p>\n<p>not move any consumer row to ready until real partner-owned IDs, feed reads, and</p>\n<p>callback verification arrive.</p>\n<pre><code>\nBASE_URL=https://&lt;deploy-host&gt; \\\nMETAMUSEUM_ACTIVITY_CONSUMER_IDS=wikibase-cloud-prod,partner-harvest-bot,analytics-reader-prod \\\npnpm activity:adoption:matrix\n</code></pre>\n<p>Equivalent explicit form:</p>\n<pre><code>\npnpm activity:adoption:matrix -- \\\n  --base-url https://&lt;deploy-host&gt; \\\n  --consumer-id wikibase-cloud-prod \\\n  --consumer-id partner-harvest-bot \\\n  --consumer-id analytics-reader-prod\n</code></pre>\n<p>Artifacts:</p>\n<ul><li>`artifacts/activity-adoption/activity-adoption-proof-latest.json`</li><li>`artifacts/activity-adoption/runs/activity-adoption-proof-&lt;timestamp&gt;.json`</li><li>`artifacts/activity-adoption/activity-adoption-matrix-latest.json`</li><li>`artifacts/activity-adoption/runs/activity-adoption-matrix-&lt;timestamp&gt;.json`</li><li>`artifacts/activity-syndication/activity-partner-onboarding-pack-latest.md`</li><li>`artifacts/activity-syndication/activity-partner-onboarding-pack-latest.json`</li><li>`artifacts/activity-syndication/runs/activity-partner-onboarding-pack-&lt;timestamp&gt;.json`</li></ul>\n<h2 id=\"durable-syndication-evidence\">Durable Syndication Evidence</h2>\n<p>After real external consumers are observed, prove that they also have durable</p>\n<p>ActivityStreams callback workflows registered:</p>\n<pre><code>\npnpm activity:syndication:evidence\npnpm activity:syndication:check\n</code></pre>\n<p>The command reads:</p>\n<ul><li>`storage/activity-consumers.json`</li><li>`storage/activity-subscriptions.json`</li><li>`docs/linked-art/activity-feed-read-confirmations.json`</li><li>`config/era-c-exit-gate-policy.json`</li></ul>\n<p>If `storage/activity-subscriptions.json` does not exist yet, the evidence</p>\n<p>command initializes it with an empty `subscriptions` array and prints</p>\n<p>`subscription ledger: 0 row(s)`. That removes missing-ledger ambiguity for</p>\n<p>operators, but the empty ledger is not proof and the report remains blocked</p>\n<p>until real partner callbacks are verified.</p>\n<p>Artifacts:</p>\n<ul><li>`artifacts/activity-syndication/activity-syndication-evidence-latest.json`</li><li>`artifacts/activity-syndication/runs/activity-syndication-evidence-&lt;timestamp&gt;.json`</li></ul>\n<p>The report has `status: &quot;ready&quot;` only when the required real declared external</p>\n<p>consumers in the configured window have feed-read telemetry or tracked</p>\n<p>real partner confirmations covering `Create`, `Update`, and `Delete`; each same</p>\n<p>consumer also needs an active external HTTPS `application/activity+json`</p>\n<p>subscription covering `Create`, `Update`, and `Delete`, plus recent callback verification evidence with a 2xx status code.</p>\n<p>Use `consumerOnboarding.rows` as the per-partner acceptance ledger: each row</p>\n<p>must have `feedReadObserved: true`, `durableSubscriptionReady: true`,</p>\n<p>`callbackVerified: true`, no blockers, and a real verification evidence ref.</p>\n<p>The callback host must be public, non-placeholder, non-private, and match the</p>\n<p>callback URL host. Placeholder IDs, derived fingerprints, local or private</p>\n<p>callbacks, non-HTTPS callbacks, mismatched callback hosts, incomplete event</p>\n<p>sets, stale subscription rows, and subscriptions without callback verification</p>\n<p>remain blocked evidence.</p>\n<p>Each accepted row in `storage/activity-subscriptions.json` must carry real</p>\n<p>verification metadata similar to:</p>\n<pre><code>\n{\n  &quot;verification&quot;: {\n    &quot;status&quot;: &quot;verified&quot;,\n    &quot;verifiedAt&quot;: &quot;2026-06-28T18:00:00.000Z&quot;,\n    &quot;statusCode&quot;: 202,\n    &quot;evidenceRef&quot;: &quot;artifacts/activity-syndication/partner-one-callback-verification.json&quot;\n  }\n}\n</code></pre>\n<p>Use partner-owned evidence references, not demo, fixture, placeholder, sample,</p>\n<p>smoke, test, or localhost proof.</p>\n<p>Record callback verification with the operator command instead of hand-editing</p>\n<p>the ledger:</p>\n<pre><code>\npnpm activity:subscription:verify -- --consumer-id &lt;partner-owned-consumer-id&gt; --callback-url https://&lt;public-consumer-host&gt;/&lt;activity-callback-path&gt; --status-code 204 --verified-at &lt;ISO timestamp&gt; --evidence-ref artifacts/activity-syndication/&lt;real-callback-proof&gt;.json\n</code></pre>\n<p>The command updates `storage/activity-subscriptions.json`, rejects placeholder</p>\n<p>evidence refs, rejects `--status=verified` with non-2xx status codes, and prints</p>\n<p>`pnpm activity:syndication:evidence` as the next verification step when the</p>\n<p>recorded callback proof can count toward durable syndication.</p>\n<p>The next `pnpm activity:syndication:evidence` run prints the number of complete</p>\n<p>onboarded consumers and any pending consumer IDs.</p>\n<h2 id=\"producing-real-update-delete-rows\">Producing Real Update/Delete Rows</h2>\n<p>Strict activity-type coverage can only pass after the public feed has real</p>\n<p>`Update` and `Delete` activity rows for partners to read. Do not create demo or</p>\n<p>placeholder events for this gate.</p>\n<p>For `Update`, make a reviewed metadata correction with real source evidence.</p>\n<p>The current production path stores the reviewed delta under</p>\n<p>`_source.reviewedUpdates[]`, and `/api/activity?type=Update&amp;source=reviewed-update`</p>\n<p>exposes that row. Operator-initiated record rewrites may also write a real</p>\n<p>`record.update` audit row when the changed full record is posted to</p>\n<p>`/api/records`.</p>\n<p>For `Delete`, record a tombstone only after a canonical upstream record returns</p>\n<p>HTTP `404` or `410` and a real evidence reference exists:</p>\n<pre><code>\npnpm activity:tombstone:scan\n</code></pre>\n<p>The scan reads canonical Met upstream IDs from the local record ledgers, writes</p>\n<p>`artifacts/activity-syndication/activity-tombstone-scan-latest.json` plus a</p>\n<p>timestamped run artifact, and does not create a `Delete` row when every upstream</p>\n<p>record still returns a non-tombstone status. When the scan artifact shows a real</p>\n<p>`404` or `410`, rerun with `--write-tombstones` to write the deletion ledger</p>\n<p>using that scan run as the evidence reference:</p>\n<pre><code>\npnpm activity:tombstone:scan -- --write-tombstones\n</code></pre>\n<p>Manual single-row recording is still available when the upstream proof was</p>\n<p>captured separately:</p>\n<pre><code>\npnpm activity:tombstone:evidence -- --object-id &lt;canonical-record-uri&gt; --provider &lt;provider-key&gt; --upstream-status 410 --observed-at &lt;ISO timestamp&gt; --evidence-ref artifacts/activity-syndication/&lt;real-upstream-404-or-410-proof&gt;.json\n</code></pre>\n<p>Both tombstone commands write `pipeline/state/deletion-activities.json`, reject</p>\n<p>non-`404`/`410` statuses, and reject demo, fixture, placeholder, sample, smoke,</p>\n<p>test, localhost, or otherwise fake evidence refs. After deploying the changed</p>\n<p>ledger, ask partners to read `/api/activity?limit=100&amp;type=Delete` with their</p>\n<p>declared consumer IDs and then rerun `pnpm activity:syndication:evidence`.</p>\n<h2 id=\"metahistorybook-callback-follow-up\">MetaHistoryBook Callback Follow-Up</h2>\n<p>MetaHistoryBook replied on 2026-07-05 with consumer ID</p>\n<p>`metahistorybook-harvester-prod`, public callback URL</p>\n<p>`https://www.metahistorybook.com/api/metamuseum/callback`, and event types</p>\n<p>`Create`, `Update`, and `Delete`. They reported a broad cursor-feed read at</p>\n<p>`2026-07-05T16:37Z` with `Create` present and explicit zero-item `Update` and</p>\n<p>`Delete` reads. MetaMuseum later rechecked callback liveness, sent one signed</p>\n<p>production `Create` Activity payload, received `204`, and recorded the callback</p>\n<p>proof with `pnpm activity:subscription:verify` at</p>\n<p>`2026-07-05T17:13:13.312Z`. Evidence:</p>\n<p>`artifacts/activity-syndication/metahistorybook-harvester-prod-callback-2026-07-05T17-13-13-312Z.json`.</p>\n<p>Never print or paste the HMAC secret or signature value into evidence artifacts,</p>\n<p>chat, or email.</p>\n<p>Partner-response answers:</p>\n<p>  `METAMUSEUM_CALLBACK_SECRET`. `signingSecretRef:</p>\n<p>  metahistorybook-harvester-prod-v1` remains the stored label, not the secret</p>\n<p>  itself.</p>\n<p>  event callbacks; partners may accept an `OrderedCollectionPage`, but the strict</p>\n<p>  evidence row should point to the concrete single delivery that returned 2xx.</p>\n<p>  string, signed as `&lt;timestamp&gt;.&lt;raw JSON payload&gt;`.</p>\n<p>  follow-up work; the current strict gate records recent real 2xx verification</p>\n<p>  evidence with `pnpm activity:subscription:verify`, and MetaHistoryBook now has</p>\n<p>  an accepted `204` callback row.</p>\n<p>  real reviewed update rows and real upstream `404`/`410` tombstone rows.</p>\n<p>  MetaHistoryBook rechecked after callback verification and confirmed</p>\n<p>  `type=Update -&gt; 0` and `type=Delete -&gt; 0`; MetaMuseum independently rechecked</p>\n<p>  at `2026-07-05T17:21:34Z` and observed `totalItems: 0` for both URLs. Their</p>\n<p>  reader is ready to re-read both typed feeds after real rows deploy.</p>\n<p>  feed now uses the same dereferenceable `object.id`, `object.sourceId`, valid</p>\n<p>  UTC `endTime`, and default-row dedupe as the Change Discovery layer while</p>\n<p>  retaining `linkedArt` embeds.</p>\n<ul><li>R1 secret exchange: complete; the local `.env` key is</li><li>R2 delivery shape: send a single Activity payload for verification and normal</li><li>R3 timestamp format: `x-metamuseum-signature-timestamp` is an ISO 8601 UTC</li><li>R4 retry/verification semantics: production retry/backoff delivery is still</li><li>R5 `Update`/`Delete`: ping MetaHistoryBook after the deployed feed contains</li><li>R6 reconciliation: keep Wikidata QIDs in `object.equivalent[]`; the cursor</li></ul>\n<h2 id=\"consumer-id-rules\">Consumer ID Rules</h2>\n<ul><li>Use a stable ID owned by the external consumer, for example `wikibase-cloud-prod` or `partner-harvest-bot`.</li><li>Do not use placeholder IDs such as `test-<em>`, `demo-</em>`, `example-<em>`, `local-</em>`, or localhost-oriented names for production evidence.</li><li>The command rejects placeholder/local-looking IDs by default. `--allow-placeholder` exists only for local rehearsals.</li></ul>\n<h2 id=\"pass-criteria\">Pass Criteria</h2>\n<p>The proof report has `status: &quot;pass&quot;` when:</p>\n<ul><li>`/api/activity` returns HTTP 2xx.</li><li>`/api/activity/readiness` returns HTTP 2xx.</li><li>Readiness telemetry contains the provided consumer ID as:</li><li>`class: &quot;declared&quot;`</li><li>matching `declaredId`</li><li>`isExternal: true`</li><li>non-empty `lastSeenAt`</li><li>`observedActivityTypes` containing the activity types returned to that consumer</li></ul>\n<p>The overall adoption exit gate still requires three unique declared external consumers within the configured window. A single passing proof only confirms one consumer was observed.</p>\n<p>The matrix report has `status: &quot;pass&quot;` only when every requested consumer probes `/api/activity` successfully, every requested consumer appears in readiness as declared/external, `/api/activity/readiness` reports the `&gt;=3` checkpoint as ready, and the matrix readiness block shows no missing `Create`/`Update`/`Delete` activity types. The matrix command performs one broad feed read plus one `type=Create`, `type=Update`, and `type=Delete` read for each requested consumer, so a three-consumer run records 12 activity probes before the readiness check. Run `pnpm activity:syndication:evidence` afterward to confirm the same declared consumers have durable callback evidence too.</p>\n<h2 id=\"partner-hand-off\">Partner Hand-Off</h2>\n<p>Send partners this minimum request:</p>\n<pre><code>\ncurl -H &quot;Accept: application/json&quot; \\\n  -H &quot;x-linked-art-consumer-id: &lt;partner-owned-consumer-id&gt;&quot; \\\n  &quot;https://&lt;deploy-host&gt;/api/activity?limit=100&quot;\n\ncurl -H &quot;Accept: application/json&quot; \\\n  -H &quot;x-linked-art-consumer-id: &lt;partner-owned-consumer-id&gt;&quot; \\\n  &quot;https://&lt;deploy-host&gt;/api/activity?limit=100&amp;type=Create&quot;\n\ncurl -H &quot;Accept: application/json&quot; \\\n  -H &quot;x-linked-art-consumer-id: &lt;partner-owned-consumer-id&gt;&quot; \\\n  &quot;https://&lt;deploy-host&gt;/api/activity?limit=100&amp;type=Update&quot;\n\ncurl -H &quot;Accept: application/json&quot; \\\n  -H &quot;x-linked-art-consumer-id: &lt;partner-owned-consumer-id&gt;&quot; \\\n  &quot;https://&lt;deploy-host&gt;/api/activity?limit=100&amp;type=Delete&quot;\n</code></pre>\n<p>After the partner runs it, operators can verify:</p>\n<pre><code>\ncurl &quot;https://&lt;deploy-host&gt;/api/activity/readiness&quot;\npnpm activity:adoption:matrix -- --base-url https://&lt;deploy-host&gt; --consumer-ids &lt;id-1&gt;,&lt;id-2&gt;,&lt;id-3&gt;\npnpm activity:syndication:evidence\npnpm era-c:exit-gate:evidence\n</code></pre>\n<h2 id=\"evidence-hygiene\">Evidence Hygiene</h2>\n<p>  callback hosts for production syndication evidence.</p>\n<p>  external HTTPS subscriptions with recent 2xx callback verification evidence</p>\n<p>  in `activity-syndication-evidence-latest.json`.</p>\n<ul><li>Derived fingerprints are diagnostic only and do not satisfy the gate.</li><li>Operators must not run production proof with internal placeholder IDs.</li><li>Operators must not use `example.*`, `.local`, localhost, or private-network</li><li>Keep partner IDs stable across checks so the 30-day activity window reflects real adoption.</li><li>Do not claim durable syndication until the same partner IDs have matching</li></ul>","updatedAt":"2018-10-20T01:46:40.000Z","checksum":"b3c1d8e3399b11af1dc4c56e0e50a85b6de5054497d923d4cc0d84509234fb7b","checksumPrefix":"b3c1d8e3399b","anchorCount":8,"lineCount":296,"rawUrl":"/api/docs/content?path=ops%2Factivity-adoption-proof.md","htmlUrl":"/docs?doc=ops%2Factivity-adoption-proof.md","apiUrl":"/api/docs/content?path=ops%2Factivity-adoption-proof.md"}