spec: consent-governance family — opt-in, explained, revocable, self-sovereign - #290
Open
mdheller wants to merge 1 commit into
Open
spec: consent-governance family — opt-in, explained, revocable, self-sovereign#290mdheller wants to merge 1 commit into
mdheller wants to merge 1 commit into
Conversation
…sovereign
Two contracts that record how consent GATES observation and action, and a gate
that refuses to certify a weakened default.
ConsentSurfaceRegistry governs what may be OBSERVED (telemetry surfaces:
category, sensitivity, pii, projectionMode, residency, purpose, consent state).
CapabilityConsentPolicy governs what may be DONE on the subject's behalf
(camera, microphone, screen capture, computer control, remote compute, file
writes, network egress, acting as the user).
Both are classification-and-consent contracts with NO CAPTURE MECHANICS — no
collector, no transport, no payload shape, no invocation path. A surface can be
fully registered and permanently unobserved, which is how a sensitive surface is
meant to appear here: a default-deny row that makes the non-collection
auditable rather than merely absent.
The inversion is structural, not aspirational: deploymentScope is
const "self-sovereign" and collectorPrincipal must equal subjectPrincipal, so a
document describing one party collecting from another is not expressible.
The asymmetry is deliberate — passive telemetry ships standing-persistent,
active capabilities ship per-use (microphone alone at standing-session, since a
per-utterance prompt makes continuous listening unusable and the honest
mitigation is a session bound). The matrix is pinned in the gate, not only in
the example: an example is a file anyone can edit, a gate is a thing you have to
argue with.
validate-consent-plane-examples enforces five invariants:
(a) self-sovereign — collectorPrincipal == subjectPrincipal
(b) default-deny — the schema must DECLARE consent.state default 'denied';
a granted/revoked surface must carry the evidence of
that transition, so a state cannot be edited into
existence; every per-use capability ships disabled
(c) canonical — a canonical capability may not be ABSENT (absence is not
a safe default, it is ungoverned); defaultStandard is
pinned; effectiveMode may diverge only under an
attributed userOverride (granted surfaces too; a
denied/revoked surface is off by consequence of consent)
(d) one-shot — a per-use capability may never carry a persistable or
standing grant, not even behind userOverride. The
subject may tighten; nothing may loosen. This closes the
hole through which one "allow" becomes a permanent
permission
(e) explanation — a non-trivial plain sentence on every governed thing;
consent to something unexplained is not consent
16 negative controls, all verified biting, built from synthetic in-memory
documents that never read or mutate examples/ — the validate_schema_references
self-exclusion discipline, so the proof cannot pollute the thing being proven.
If a control fails to trip, the gate exits non-zero and certifies nothing.
Example set grants BENIGN surfaces only (model:tokens_used, policy:gate_verdict);
sensitive and personal surfaces appear as off, classification-only rows.
Registry regenerated (367 canonical contracts). Referential-integrity gate,
duplicate-$id guardrail, ajv compile, CI example type-mapping, and the
reconciliation suites all pass.
See schemas/consent-plane.README.md.
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.
Two contracts that record how consent gates observation and action, and a gate that refuses to certify a weakened default.
ConsentSurfaceRegistryurn:srcos:consent-registry:pii,projectionMode, residency, purpose, consent stateCapabilityConsentPolicyurn:srcos:consent-policy:No capture mechanics
Both are classification-and-consent contracts: no collector, no transport, no payload shape, no invocation path. A surface can be fully registered and permanently unobserved — which is exactly how a sensitive surface is meant to appear here: as a default-deny row that makes the non-collection auditable rather than merely absent.
The inversion is structural, not aspirational.
deploymentScopeisconst: "self-sovereign"andcollectorPrincipalmust equalsubjectPrincipal, so a document describing one party collecting from another is not expressible in this family.The deliberate asymmetry
Passive telemetry ships
standing-persistent; active capabilities shipper-use. That gap is the design. A passive counter you opted into once should not re-interrupt you forever, but a camera that asks once and remembers the answer has stopped being consent.microphoneis the single exception (standing-session) — a per-utterance prompt makes continuous listening unusable, so the honest mitigation is a session bound rather than a prompt nobody reads.cameraper-usescreen_captureper-usemicrophonestanding-sessioncontrol_my_computerper-useskycomputerper-usefile_writeper-usenetwork_egressper-usesend_on_behalfper-useThe matrix is pinned in
tools/validate_consent_plane_examples.py, not only in the example. An example is a file anyone can edit; a gate is a thing you have to argue with.Five invariants —
make validate-consent-plane-examplescollectorPrincipal == subjectPrincipal.consent.statedefaultdenied; agrantedsurface must carrygrantedAt+grantRefand arevokedonerevokedAt+grantRef(a state cannot be edited into existence — it must point at something that happened); every per-use capability shipsdefaultState: disabled.defaultStandardis pinned to the matrix;effectiveModemay diverge only under an attributeduserOverride. Applied to granted surfaces too — a denied/revoked surface isoffby consequence of consent, not by override, and is exempt.userOverride. The subject may always tighten; nothing may loosen. This closes the hole through which one "allow" becomes a permanent permission.16 negative controls, all verified biting, built from synthetic in-memory documents that never read or mutate
examples/— thevalidate_schema_references.pyself-exclusion discipline, so the proof cannot pollute the thing being proven. If a control fails to trip, the gate exits non-zero and certifies nothing.Examples
examples/consent_surface_registry.jsongrants benign surfaces only (model:tokens_used,policy:gate_verdict— the latter demonstrating an attributeduserOverridedivergence).device:hardware_id(sensitive) is present purely as a default-deny classification row withOPAQUE_HANDLE_ONLYprojection and nothing reading it;app:session_start(personal) isrevoked, showing that a withdrawal stays legible rather than decaying back into an indistinguishable "denied".examples/capability_consent_policy.jsonships the full canonical set at defaults, alldisabled, withmicrophonetightened tooffunder an attributed override — the only direction an override may move.Verification
make validate-consent-plane-examplestools/validate_schema_references.py(referential integrity)$refs resolvescripts/check_duplicate_schema_ids.py$idajv compile(CI step 1, both new schemas w/ full-rset)typemappingpytest tools/ -q(tools-tests.yml)pytest tools/test_reconcile_contracts.py -qregistry/contract-registry.jsonregeneratedWiring
validate-consent-plane-examplesadded to.PHONYand to the aggregatevalidatetarget;schemas/consent-plane.README.mdadded; CHANGELOG entry under[Unreleased] → Added.Reviewer notes — two judgment calls
CapabilityConsentPolicycarriesid/type/specVersionbeyond the fields specified. Required by convention: CI's example validator mapsexamples/*.json→schemas/<type>.jsonby top-leveltype, so without it the example is silently skipped rather than validated.granted. Surfaces carry the identicaldefaultStandard/effectiveMode/userOverridetriple; leaving it unenforced there would be a declared-not-enforced hole of exactly the kind this repo exists to close. The exemption is the honest one: a denied surface isoffbecause consent was refused, not because anyone overrode a default.Not merging — flagged for review.