Core's export manifest can tell a data subject "this source returns only some of your rows, and why". A fork tier's source cannot.
SubjectDataSource in lib/privacy/export-sources.ts has an optional scopeNote, and exportUserData() carries it into ExportedSourceSummary, so it lands in the bundle's meta beside the row count. That is the manifest's defence against a silently short answer: a source that narrows is required to say so, in a field a reader knows to look for.
AppSubjectDataSource in lib/privacy/subject-source-registry.ts has model, section, disposition and description, and nothing else. AppSourceSummary mirrors that. So a tier source that deliberately returns a subset has nowhere structured to disclose it.
The case that hit it
Resparkable has shared group workspaces. Its groupContributions section returns, for each group the subject belongs to, only the rows the subject created. Every other member's rows are withheld, because they are other people's personal data. That is exactly the situation scopeNote exists for.
What Resparkable does meanwhile
It writes the narrowing into description:
description: `What you wrote in each group workspace, one entry per group. ${GROUP_CONTRIBUTIONS_SCOPE_NOTE}`,
That works, in that the words reach meta.app. But it mixes "what this is" with "what was left out", and a reader (human or tool) scanning for narrowed sources has no field to find. It is the silent-omission failure the core manifest was built to prevent, one tier down.
Ask
- Add
scopeNote?: string to AppSubjectDataSource, with the same doc comment core's field has.
- Carry it into
AppSourceSummary with the same conditional spread core already uses for its own sources (...(source.scopeNote ? { scopeNote: source.scopeNote } : {})).
- Optionally, reject an empty-after-trim
scopeNote in registerAppSubjectSources() the way description is checked, so a blank one cannot pass as a disclosure.
Small, additive, and non-breaking: no existing registration changes shape.
When it lands, Resparkable moves the constant into scopeNote and shortens the description back to what the section is.
Filed from Resparkable (ask #47).
Core's export manifest can tell a data subject "this source returns only some of your rows, and why". A fork tier's source cannot.
SubjectDataSourceinlib/privacy/export-sources.tshas an optionalscopeNote, andexportUserData()carries it intoExportedSourceSummary, so it lands in the bundle'smetabeside the row count. That is the manifest's defence against a silently short answer: a source that narrows is required to say so, in a field a reader knows to look for.AppSubjectDataSourceinlib/privacy/subject-source-registry.tshasmodel,section,dispositionanddescription, and nothing else.AppSourceSummarymirrors that. So a tier source that deliberately returns a subset has nowhere structured to disclose it.The case that hit it
Resparkable has shared group workspaces. Its
groupContributionssection returns, for each group the subject belongs to, only the rows the subject created. Every other member's rows are withheld, because they are other people's personal data. That is exactly the situationscopeNoteexists for.What Resparkable does meanwhile
It writes the narrowing into
description:That works, in that the words reach
meta.app. But it mixes "what this is" with "what was left out", and a reader (human or tool) scanning for narrowed sources has no field to find. It is the silent-omission failure the core manifest was built to prevent, one tier down.Ask
scopeNote?: stringtoAppSubjectDataSource, with the same doc comment core's field has.AppSourceSummarywith the same conditional spread core already uses for its own sources (...(source.scopeNote ? { scopeNote: source.scopeNote } : {})).scopeNoteinregisterAppSubjectSources()the waydescriptionis checked, so a blank one cannot pass as a disclosure.Small, additive, and non-breaking: no existing registration changes shape.
When it lands, Resparkable moves the constant into
scopeNoteand shortens the description back to what the section is.Filed from Resparkable (ask #47).