Bring the server to Content Telemetry v1.0 conformance - #2
Merged
Merged
Conversation
The consumer now implements the v1.0 line: documents declaring "1.0" (or nothing) read as v1, while "0.1" stays accepted for the transition because live member edge workers still declare it. v0.1 documents and stored pre-v1 rows are read under the spec 12.1 migration rules - absent data.citation_type reads as unclassified, absent content_grounded data.scope as turn/session by turn_id presence, and data.bot_category as purpose - so the "1.0" stamp on materialised session documents is truthful after normalisation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
content_reproduced existed only on the pre-release v1-draft line and is not part of v1 (spec 12.1). It leaves CORE_EVENT_TYPES and CONTENT_EVENT_TYPES, its reproduction_type enum and normalisation rule go, and the v1 structural rules no longer recognise it. Stored draft rows self-quarantine under extensions.events at materialisation, which the new standard.rs test verifies; the ingest error for the withdrawn content_displayed type now points at content_presented alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documents on the 1.0 line (and undeclared documents, read as current) are now held to the full v1 shape: content_grounded requires a closed data.scope and consistent provenance/cached (spec 6.4), content_cited requires data.citation_type (spec 6.5), content_retrieved requires source_role (spec 5.2.2), and fields scoped to one event type are rejected elsewhere - presentation_id and event-level ctx_token only on content_engaged, citation_id only on content_presented, turn only on turn events (spec 5.7.5). The envelope ctx_token-only-with-engagements rule moves out of resolve_binding so it applies even when a session_id is present. Documents declaring 0.1 are normalised per spec 12.1 before the checks and tolerated where no migration rule exists; normalisation now runs before checking on both ingest paths. The withdrawn content_fingerprint.preserved_in_output member is stripped wherever withdrawn fields are stripped (spec 6.4, 12.1). Materialisation applies the same rules when deciding which stored rows stay in the document body. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migration 0003 adds the two v1 event columns the original schema predates. ctx_token (spec 5.2, 7.4.1) records, on an agent-reported content_engaged event, the click token minted for that engagement's presentation, so destination reports can be joined to it at resolution; materialisation emits it on content_engaged only. terms_ref (spec 5.2.4) is the governing-terms reference: it is bound, stored and served byte-for-byte, never normalised or rewritten, and materialises on any event that carries it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tokens are now minted as ct_ plus 32 CSPRNG bytes base64url-encoded
without padding - 256 bits against the spec 7.4.1 floor of 96 - and a
ctx_token_well_formed() predicate enforces the
^ct_[A-Za-z0-9_-]{16,240}$ grammar at mint (caller-supplied overrides
included), at event binding, and on the recorded event-level ctx_token
of v1 documents. Legacy UUID-shaped tokens no longer bind events; no
live integration mints or holds any.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The read layer follows the spec 12.1 rename of the retrieval profile's bot_category field to purpose: every filter and grouping now reads COALESCE(event_data->>'purpose', event_data->>'bot_category'), the query params take purpose with bot_category accepted as a deprecated alias, and the agent breakdown returns the value under both names during the transition. The funnel and reconciliation surfaces drop the reproduced series (content_reproduced is not part of v1). Stored v0.1 content_displayed rows keep counting inside the presented stage as an explicit legacy branch, and the presented count is returned under both 'presented' and 'displayed' response keys - the website dashboard still reads 'displayed'; a coordinated rename retires it later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The README example and the smoke script declare schema_version 1.0 and carry the v1-required members (data.scope on grounding, data.citation_type on citations); the endpoint table and auth notes say click context rather than click manifest; the conformance section describes the enforced v1 rules per schema line. The RSL profile's spec_ref strings point at the data-profile sections that now carry the requirements (6.4 and 6.5). TODO markers reference spec 7.4.4 (the four-component click context) and 7.3 (content_id-prefix owner resolution), which are design work for a separate change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the v1.0 conformance worklist from the spec review, against the normative spec at SPUR-Coalition/telemetry (main = v1.0).
Compatibility posture
Live member Cloudflare workers still emit v0.1 edge batches (
schema_version: "0.1",data.bot_category,source_role: edge); no agents are integrated yet. So:"0.1"and"1.0"are accepted. Documents declaring"1.0"(or nothing) get full v1 strictness; documents declaring"0.1"are normalised per spec §12.1 where a rule exists (absentdata.citation_type→unclassified; absentcontent_groundeddata.scope→turn/sessionbyturn_idpresence;data.bot_categoryread aspurpose) and tolerated otherwise.schema_version: "1.0"— truthful because stored rows are read under the same §12.1 migration rules, and rows that still fail the v1 shape are quarantined underextensions.events.Changes
content_reproducedexcised as a core type (it existed only on the pre-release v1-draft line, §12.1): removed from the core sets, enum rules, structural rules, error text, docs and tests. Stored draft rows self-quarantine intoextensions.events— covered by a new test."1.0"documents:content_groundedrequires closeddata.scope(§6.4) and consistentprovenance/cachedpairing (agent_fetched⇒cached:false,agent_cached⇒cached:true);content_citedrequiresdata.citation_type(§6.5);content_retrievedrequiressource_role(§5.2.2).presentation_idand event-levelctx_tokenonly oncontent_engaged;citation_idonly oncontent_presented;turnonly on turn events. The envelopectx_token-only-with-engagements check is hoisted out ofresolve_bindingso it applies even when asession_idis present.content_fingerprint.preserved_in_outputis stripped wherever withdrawn fields are stripped (it lives one level down; the old sweep only caught top-level keys).ctx_tokenandterms_refcolumns toevents.terms_ref(§5.2.4) passes through byte-for-byte, never rewritten; the event-levelctx_tokenmaterialises oncontent_engagedonly.ct_+ 32 CSPRNG bytes base64url (256 bits vs the 96-bit floor); actx_token_well_formed()predicate (^ct_[A-Za-z0-9_-]{16,240}$) is applied at mint (including caller-supplied overrides), at event binding, and on recorded event-level tokens.bot_category→purposein the read layer (§12.1): every filter and grouping readsCOALESCE(event_data->>'purpose', event_data->>'bot_category'); query params takepurposewithbot_categoryas a deprecated serde alias; the agent breakdown returns the value under both names during the transition.reproducedseries is dropped; stored v0.1content_displayedrows keep counting inside thepresentedstage as an explicit legacy branch, and the presented count is returned under bothpresentedanddisplayedkeys (the website dashboard still readsdisplayed; a coordinated rename comes later)."1.0"line withscope/citation_type; "click manifest" → "click context"; RSL profilespec_refs point at §6.4/§6.5.Deliberately left out (separate PRs)
TODO(spec 7.4.4)markers atroutes.rs::lookup_ctxandclick_tokens.rs::lookup_by_token.content_id-prefix owner resolution (§7.3) —TODO(spec 7.3)inclick_tokens.rs.Testing
cargo test --workspacegreen (64 unit + 12 DB integration tests, against Postgres 17) — includes new tests for version acceptance, §12.1 migration defaults, the new structural and placement rules, token minting/grammar, and extension quarantine.cargo clippy --workspace --all-targetsandcargo fmt --checkclean.scripts/smoke.shpasses end-to-end against a live build.content_reproducedrow validates against the normativetelemetry-session.json(jsonschema 4.26, Draft 2020-12).🤖 Generated with Claude Code