Skip to content

Add vcf-rdfizer-policy: ODRL policy attachment with declared selectors and partitions - #26

Merged
ecrum19 merged 4 commits into
mainfrom
feature/policy-demonstrator
Sep 25, 2026
Merged

ecrum19 merged 4 commits into
mainfrom
feature/policy-demonstrator

Conversation

@ecrum19

@ecrum19 ecrum19 commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Why

The paper's capabilities table lists Provenance/policy annotations as not implemented. docs/privacy-policy-design.md specifies a full governed-release system, but none of it exists. This PR adds a first slice of that design, general enough to be useful beyond this project's example.

It is governed release, not anonymization. The tool says so in its manifest, its docs and its README.

What

vcf-rdfizer-policy (explain, attach, evaluate, check) is a host-side command in the pattern of vcf-rdfizer-link, with no Docker. The engine has three generic steps, each configured in Turtle, not code:

Step What configures it
select A rule targets a resource IRI, or a selection computed by a selector type: a SPARQL SELECT ?resource with named parameters, declared in a profile or in the policy file itself
partition A profile's ownership rule, a property path plus IRI subtrees, says what a withheld resource takes with it
decide ODRL: a binding permission must own a resource and no binding prohibition may; deny wins; default-deny. Purposes come from any RDFS/SKOS vocabulary (bundled default: a DUO subset)

All VCF knowledge is in one Turtle file, the bundled VCF Core profile (vcf_rdfizer_data/policy/vcf-core-profile.ttl):

  • region and variant selectors;
  • the ownership rule: a record owns its call, alleles and per-sample calls, and a file owns everything under it;
  • the per-record report.

The engine itself never mentions VCF.

Refusal rule: anything the engine can't evaluate is refused with an error, never skipped. That includes an undeclared selector type, a missing parameter, a purpose outside the vocabulary, a failing precondition such as an assembly mismatch, or an unsupported ODRL construct.

check:

  • Structural checks, for any graph: the policy digest matches; nothing a binding prohibition owns is present; nothing lacks a permission; no reference dangles.
  • VCF oracle, --vcf: re-derives the expected records from the VCF text, never touching the converter, and requires an exact match.

Example

examples/policy/ holds:

  • a synthetic cohort of five single-sample VCFs (real GRCh38 loci, synthetic genotypes);
  • the cohort converted in both profiles, with provenance, so no Docker is needed;
  • policy.ttl and run_demo.sh;
  • custom-selector.ttl, a QUAL-threshold selector declared in the policy file, with no code.
requester P001.vcf  P002.vcf  P003.vcf  P004.vcf  P005.vcf  triples withheld
alz       22        16        18        withheld  20        4649
clinical  33        23        withheld  withheld  withheld  6539
gru       21        16        withheld  withheld  withheld  7869

Every view passes check, including the oracle, in both profiles. The generalisation reproduced the original grid and triple counts exactly.

Tests

As agreed, the plug-in's tests live apart from its code: ecrum19/vcf-rdfizer-testing#4 (plugin-tests/policy/, 32 tests, run by hand). They cover:

  • the decision grid cell by cell;
  • the refusals;
  • the engine's generality: a selector declared in a policy, a non-VCF graph partitioned by a property path, a SKOS vocabulary;
  • six planted faults the check must catch.

This repo keeps only the conda recipe's packaging checks for the plug-in.

On vcf-bench-1, inside the v3.1.0 image:

  • The plug-in suite passes 32 of 32.
  • The demo passes every check on both profiles.
  • This repo's CI suite runs 902 tests. The 2 failures (test_validate_compression_unit) also fail on the base commit in the same container, as root or not, so they're specific to that environment and unrelated to this PR.

Also

  • docs/policy-demonstrator.md is rewritten around select → partition → decide, including how to extend each without code. It adds a roadmap (v0.2–v0.5) mapped onto the full design; the sample, field and header selectors that design lists are now declarations rather than engine work.
  • pyproject.toml and the conda recipe gain the command and the bundled data, and the recipe checks that the profile and vocabulary are packaged.

Not in v0.1.0

  • multi-sample masking inside condensed vectors;
  • conversion-time enforcement;
  • pseudonymization; views keep their original IRIs;
  • generalise and threshold effects;
  • enforcing duties, which are only recorded;
  • anything beyond fixture scale: evaluation is in memory, with a 5M-triple guard.

🤖 Generated with Claude Code

ecrum19 and others added 3 commits September 25, 2026 13:05
A first, small slice of privacy-policy-design.md: ODRL policies attached
to files, regions and variants of synthetic single-sample VCFs, a
release view per request, and a check against a VCF-side oracle.
Governed release, not anonymization. Later versions are mapped onto the
full design's build order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vcf-rdfizer-policy attaches ODRL policies to VCF-RDFizer graphs,
evaluates them per request, and checks each release view against an
oracle read from the source VCFs:

- profile.py reads a policy and refuses anything outside the v0.1.0
  subset: file, region and variant targets; purpose constraints over
  DUO; drop only; deny wins.
- decide.py holds the decision rules, the one place they live; both
  evaluate and the oracle call it.
- release.py writes the view, per-record decisions, counts and a
  manifest with the policy digest and 'governed release; not
  anonymization'.
- check.py compares the view with the VCF-side oracle, and checks for
  prohibited content, dangling references and withheld-file mentions.

examples/policy has a deterministic synthetic cohort of five
single-sample VCFs, their converted graphs in both profiles, the
policy, and run_demo.sh. The check caught a real leak on its first run:
withheld files kept their header subtrees until split() learned that
every IRI under a file belongs to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y.json

Each file's per-record counts were merged into its entry under the key
'released', overwriting the boolean decision with a count. The demo
grid only read correctly because a count of 0 is falsy. The counts are
now records_released and records_withheld, and a test pins the schema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ecrum19 added a commit to ecrum19/vcf-rdfizer-testing that referenced this pull request Sep 25, 2026
Section 3.8 and Figure 7 present vcf-rdfizer-policy v0.1.0
(ecrum19/VCF-RDFizer#26): ODRL policies attached to files, a region and
a variant of a synthetic single-sample cohort, and three per-request
views each verified against a VCF-side oracle. Panel (a) is a TikZ
schematic of where policies attach; panel (b) is the decision grid,
drawn from the demo's own output as run on vcf-bench-1.

The capabilities row now reads 'Not in v3.1.0; v0.1.0 demonstrator
after the release / Exercised on a synthetic fixture', with its limits.
The Discussion and Code availability point to it, and the DUO paper is
cited.

make_figures.py writes PDFs without a creation timestamp, so an
unchanged figure regenerates to identical bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Sep 25, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The engine is now three generic steps, each configured in Turtle:

- select: a target is a resource IRI or a selection computed by a
  declared selector type, a SPARQL SELECT with named parameters;
- partition: a profile's ownership rule (a property path, plus IRI
  subtrees) says what a withheld resource takes with it;
- decide: ODRL, deny wins, default-deny.

VCF knowledge moves out of the code into the bundled VCF Core profile
(vcf_rdfizer_data/policy/vcf-core-profile.ttl): the region and variant
selectors, the ownership rule, and the per-record report. Purposes come
from any RDFS or SKOS vocabulary (--purposes); the DUO subset is the
default, with a DUO: prefix. Selector types can also be declared in the
policy file; examples/policy/custom-selector.ttl adds a QUAL threshold
with no code.

check keeps generic structural checks (digest, prohibited content,
default-deny, dangling) and moves the VCF-text oracle into
vcf_oracle.py, which now models every fixed column except INFO.

The demo reproduces v0.1.0's grid and triple counts exactly, in both
profiles. The tests move to vcf-rdfizer-testing (plugin-tests/policy),
kept apart from this code and run by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ecrum19 ecrum19 changed the title Add vcf-rdfizer-policy: a v0.1.0 ODRL policy demonstrator Add vcf-rdfizer-policy: ODRL policy attachment with declared selectors and partitions Sep 25, 2026
@ecrum19
ecrum19 merged commit 18db236 into main Sep 25, 2026
24 checks passed
@ecrum19 ecrum19 mentioned this pull request Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants