Activity Feed Adoption Proof Runbook
This runbook supports the Era C exit-gate target: at least three declared external Linked Art systems consume `/api/activity` within the adoption window.
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.
Command
BASE_URL=https://<deploy-host> \
METAMUSEUM_ACTIVITY_CONSUMER_ID=<partner-owned-consumer-id> \
pnpm activity:adoption:probe
Equivalent explicit form:
pnpm activity:adoption:probe -- --base-url https://<deploy-host> --consumer-id <partner-owned-consumer-id>
To prove the full three-consumer checkpoint in one run after all partner IDs are known:
pnpm activity:partner-pack -- \
--base-url https://<deploy-host> \
--consumer-id wikibase-cloud-prod \
--consumer-id partner-harvest-bot \
--consumer-id analytics-reader-prod
The partner pack writes sendable per-consumer read and durable-subscription
requests to `artifacts/activity-syndication/activity-partner-onboarding-pack-latest.md`
plus a timestamped
`artifacts/activity-syndication/runs/activity-partner-onboarding-pack-<timestamp>.json`
audit copy, and stays blocked until the IDs are real partner-owned values.
If the first ask is a broad Linked Art community or Slack post instead of a
named partner request, capture that trail separately:
pnpm activity:community-outreach -- --channel "<community/channel>" --message-ref "<permalink-or-thread-ref>" --posted-at "<ISO timestamp>" --owner "<owner>"
That artifact records outreach only. It stays `strictEvidence: false` and does
not move any consumer row to ready until real partner-owned IDs, feed reads, and
callback verification arrive.
BASE_URL=https://<deploy-host> \
METAMUSEUM_ACTIVITY_CONSUMER_IDS=wikibase-cloud-prod,partner-harvest-bot,analytics-reader-prod \
pnpm activity:adoption:matrix
Equivalent explicit form:
pnpm activity:adoption:matrix -- \
--base-url https://<deploy-host> \
--consumer-id wikibase-cloud-prod \
--consumer-id partner-harvest-bot \
--consumer-id analytics-reader-prod
Artifacts:
- `artifacts/activity-adoption/activity-adoption-proof-latest.json`
- `artifacts/activity-adoption/runs/activity-adoption-proof-<timestamp>.json`
- `artifacts/activity-adoption/activity-adoption-matrix-latest.json`
- `artifacts/activity-adoption/runs/activity-adoption-matrix-<timestamp>.json`
- `artifacts/activity-syndication/activity-partner-onboarding-pack-latest.md`
- `artifacts/activity-syndication/activity-partner-onboarding-pack-latest.json`
- `artifacts/activity-syndication/runs/activity-partner-onboarding-pack-<timestamp>.json`
Durable Syndication Evidence
After real external consumers are observed, prove that they also have durable
ActivityStreams callback workflows registered:
pnpm activity:syndication:evidence
pnpm activity:syndication:check
The command reads:
- `storage/activity-consumers.json`
- `storage/activity-subscriptions.json`
- `docs/linked-art/activity-feed-read-confirmations.json`
- `config/era-c-exit-gate-policy.json`
If `storage/activity-subscriptions.json` does not exist yet, the evidence
command initializes it with an empty `subscriptions` array and prints
`subscription ledger: 0 row(s)`. That removes missing-ledger ambiguity for
operators, but the empty ledger is not proof and the report remains blocked
until real partner callbacks are verified.
Artifacts:
- `artifacts/activity-syndication/activity-syndication-evidence-latest.json`
- `artifacts/activity-syndication/runs/activity-syndication-evidence-<timestamp>.json`
The report has `status: "ready"` only when the required real declared external
consumers in the configured window have feed-read telemetry or tracked
real partner confirmations covering `Create`, `Update`, and `Delete`; each same
consumer also needs an active external HTTPS `application/activity+json`
subscription covering `Create`, `Update`, and `Delete`, plus recent callback verification evidence with a 2xx status code.
Use `consumerOnboarding.rows` as the per-partner acceptance ledger: each row
must have `feedReadObserved: true`, `durableSubscriptionReady: true`,
`callbackVerified: true`, no blockers, and a real verification evidence ref.
The callback host must be public, non-placeholder, non-private, and match the
callback URL host. Placeholder IDs, derived fingerprints, local or private
callbacks, non-HTTPS callbacks, mismatched callback hosts, incomplete event
sets, stale subscription rows, and subscriptions without callback verification
remain blocked evidence.
Each accepted row in `storage/activity-subscriptions.json` must carry real
verification metadata similar to:
{
"verification": {
"status": "verified",
"verifiedAt": "2026-06-28T18:00:00.000Z",
"statusCode": 202,
"evidenceRef": "artifacts/activity-syndication/partner-one-callback-verification.json"
}
}
Use partner-owned evidence references, not demo, fixture, placeholder, sample,
smoke, test, or localhost proof.
Record callback verification with the operator command instead of hand-editing
the ledger:
pnpm 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
The command updates `storage/activity-subscriptions.json`, rejects placeholder
evidence refs, rejects `--status=verified` with non-2xx status codes, and prints
`pnpm activity:syndication:evidence` as the next verification step when the
recorded callback proof can count toward durable syndication.
The next `pnpm activity:syndication:evidence` run prints the number of complete
onboarded consumers and any pending consumer IDs.
Producing Real Update/Delete Rows
Strict activity-type coverage can only pass after the public feed has real
`Update` and `Delete` activity rows for partners to read. Do not create demo or
placeholder events for this gate.
For `Update`, make a reviewed metadata correction with real source evidence.
The current production path stores the reviewed delta under
`_source.reviewedUpdates[]`, and `/api/activity?type=Update&source=reviewed-update`
exposes that row. Operator-initiated record rewrites may also write a real
`record.update` audit row when the changed full record is posted to
`/api/records`.
For `Delete`, record a tombstone only after a canonical upstream record returns
HTTP `404` or `410` and a real evidence reference exists:
pnpm activity:tombstone:scan
The scan reads canonical Met upstream IDs from the local record ledgers, writes
`artifacts/activity-syndication/activity-tombstone-scan-latest.json` plus a
timestamped run artifact, and does not create a `Delete` row when every upstream
record still returns a non-tombstone status. When the scan artifact shows a real
`404` or `410`, rerun with `--write-tombstones` to write the deletion ledger
using that scan run as the evidence reference:
pnpm activity:tombstone:scan -- --write-tombstones
Manual single-row recording is still available when the upstream proof was
captured separately:
pnpm 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
Both tombstone commands write `pipeline/state/deletion-activities.json`, reject
non-`404`/`410` statuses, and reject demo, fixture, placeholder, sample, smoke,
test, localhost, or otherwise fake evidence refs. After deploying the changed
ledger, ask partners to read `/api/activity?limit=100&type=Delete` with their
declared consumer IDs and then rerun `pnpm activity:syndication:evidence`.
MetaHistoryBook Callback Follow-Up
MetaHistoryBook replied on 2026-07-05 with consumer ID
`metahistorybook-harvester-prod`, public callback URL
`https://www.metahistorybook.com/api/metamuseum/callback`, and event types
`Create`, `Update`, and `Delete`. They reported a broad cursor-feed read at
`2026-07-05T16:37Z` with `Create` present and explicit zero-item `Update` and
`Delete` reads. MetaMuseum later rechecked callback liveness, sent one signed
production `Create` Activity payload, received `204`, and recorded the callback
proof with `pnpm activity:subscription:verify` at
`2026-07-05T17:13:13.312Z`. Evidence:
`artifacts/activity-syndication/metahistorybook-harvester-prod-callback-2026-07-05T17-13-13-312Z.json`.
Never print or paste the HMAC secret or signature value into evidence artifacts,
chat, or email.
Partner-response answers:
`METAMUSEUM_CALLBACK_SECRET`. `signingSecretRef:
metahistorybook-harvester-prod-v1` remains the stored label, not the secret
itself.
event callbacks; partners may accept an `OrderedCollectionPage`, but the strict
evidence row should point to the concrete single delivery that returned 2xx.
string, signed as `<timestamp>.<raw JSON payload>`.
follow-up work; the current strict gate records recent real 2xx verification
evidence with `pnpm activity:subscription:verify`, and MetaHistoryBook now has
an accepted `204` callback row.
real reviewed update rows and real upstream `404`/`410` tombstone rows.
MetaHistoryBook rechecked after callback verification and confirmed
`type=Update -> 0` and `type=Delete -> 0`; MetaMuseum independently rechecked
at `2026-07-05T17:21:34Z` and observed `totalItems: 0` for both URLs. Their
reader is ready to re-read both typed feeds after real rows deploy.
feed now uses the same dereferenceable `object.id`, `object.sourceId`, valid
UTC `endTime`, and default-row dedupe as the Change Discovery layer while
retaining `linkedArt` embeds.
- R1 secret exchange: complete; the local `.env` key is
- R2 delivery shape: send a single Activity payload for verification and normal
- R3 timestamp format: `x-metamuseum-signature-timestamp` is an ISO 8601 UTC
- R4 retry/verification semantics: production retry/backoff delivery is still
- R5 `Update`/`Delete`: ping MetaHistoryBook after the deployed feed contains
- R6 reconciliation: keep Wikidata QIDs in `object.equivalent[]`; the cursor
Consumer ID Rules
- Use a stable ID owned by the external consumer, for example `wikibase-cloud-prod` or `partner-harvest-bot`.
- Do not use placeholder IDs such as `test-`, `demo-`, `example-`, `local-`, or localhost-oriented names for production evidence.
- The command rejects placeholder/local-looking IDs by default. `--allow-placeholder` exists only for local rehearsals.
Pass Criteria
The proof report has `status: "pass"` when:
- `/api/activity` returns HTTP 2xx.
- `/api/activity/readiness` returns HTTP 2xx.
- Readiness telemetry contains the provided consumer ID as:
- `class: "declared"`
- matching `declaredId`
- `isExternal: true`
- non-empty `lastSeenAt`
- `observedActivityTypes` containing the activity types returned to that consumer
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.
The 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.
Partner Hand-Off
Send partners this minimum request:
curl -H "Accept: application/json" \
-H "x-linked-art-consumer-id: <partner-owned-consumer-id>" \
"https://<deploy-host>/api/activity?limit=100"
curl -H "Accept: application/json" \
-H "x-linked-art-consumer-id: <partner-owned-consumer-id>" \
"https://<deploy-host>/api/activity?limit=100&type=Create"
curl -H "Accept: application/json" \
-H "x-linked-art-consumer-id: <partner-owned-consumer-id>" \
"https://<deploy-host>/api/activity?limit=100&type=Update"
curl -H "Accept: application/json" \
-H "x-linked-art-consumer-id: <partner-owned-consumer-id>" \
"https://<deploy-host>/api/activity?limit=100&type=Delete"
After the partner runs it, operators can verify:
curl "https://<deploy-host>/api/activity/readiness"
pnpm activity:adoption:matrix -- --base-url https://<deploy-host> --consumer-ids <id-1>,<id-2>,<id-3>
pnpm activity:syndication:evidence
pnpm era-c:exit-gate:evidence
Evidence Hygiene
callback hosts for production syndication evidence.
external HTTPS subscriptions with recent 2xx callback verification evidence
in `activity-syndication-evidence-latest.json`.
- Derived fingerprints are diagnostic only and do not satisfy the gate.
- Operators must not run production proof with internal placeholder IDs.
- Operators must not use `example.*`, `.local`, localhost, or private-network
- Keep partner IDs stable across checks so the 30-day activity window reflects real adoption.
- Do not claim durable syndication until the same partner IDs have matching