Contract: public and private data
Much DIMS data is video of identifiable people. Visibility is therefore declared by the researcher, never inferred, and enforced by machinery rather than by memory.
Declare it
dims-case.json, at the root of every case repo:
{
"case": "karnatak",
"visibility": "private",
"dimsCore": "1.3.1",
"publishable": [],
"restricted": [
"assets/videos",
"assets/timeseries",
"assets/transcripts",
"assets/elan",
"assets/motion_tracking"
]
}
visibility is "public" or "private", chosen once when the case is created.
publishable is a default-deny allowlist: for a private case, nothing under
assets/ may be committed unless a path here says so. restricted is what the
allowlist opens exceptions to; dims-case new writes it, and every guard reads
it from here so the rule lives in one place. If it is absent the guards fall
back to the core's own list rather than passing everything — an earlier version
of this example omitted the key, and a study written by hand from it had guards
that declared themselves private and blocked nothing.
What private turns on
Three independent guards, ordered by how early they catch the mistake:
- Pre-commit hook — refuses to stage restricted paths. This is the one that matters: it stops data before it enters history, where removing it needs a rewrite and the data has already been pushed.
- Pre-push hook — the same check across the push range.
- CI — fails if any tracked file matches a restricted path, and fails if
"visibility": "private"while the GitHub repo is public. That last check catches the case nobody plans for: someone flipping visibility months later. Enable the hooks once per clone:
git config core.hooksPath .githooks
A .gitignore is not one of these guards. It is a convention, and git add -f
walks straight through it.
Working with private data comfortably
This half matters as much as the guards: a guard that makes the work painful gets disabled.
- Data lives outside the repo, at a path named in
data.local.json(untracked).serve.pyanddims-analysisboth resolve assets through it, so everything runs against real data with an empty trackedassets/. assets/MANIFEST.jsonis tracked and holds names, sizes and checksums — never content, so a rebuild can be confirmed complete without anyone seeing the data. Write it withdims-analysis manifest(orbuild_assets.py --write-manifest) once the assets are right; check it withdims-analysis manifest --check, which compares names and sizes, or--check --deep, which verifies the checksums and on a study with video takes minutes. Both hooks and the CI guard exempt this file by name, which is exactly why it may never carry content.- Private cases are not wired to GitHub Pages. They are served locally.
Going public
An explicit, gated transition. Never a default, never a side effect.
- Run the anonymisation step over every video.
- Verify frame by frame. Detection-based masking misses; the tooling's own documentation says it is a starting point, not a solution.
- Move the cleared directories onto
publishable. - Flip
visibilityto"public". - Only now does the Pages workflow become available.
Working in a private case
In a private case: never push, and never move asset content into anything external — an issue, a prompt, a hosted page, a paste. Describing the data is fine. Reproducing it is not.
Acceptance
git add -f assets/videos/x.mp4in a private case is refused.- Bypassing with
--no-verifyproduces a red CI run. - Making a
"private"case's repo public produces a red CI run. - A private case runs locally, renders every tab, and leaves
git statusclean.
Generated from
docs/contracts/data-visibility.md
at v1.0.1. Edit it there, not here.