Run GND behind your interface or in front of customers
Use this when one GND installation serves two kinds of reader. Your own team, behind your interface, wants the population values that explain a failure. A customer, reached through your service, must not see them. One setting, the disclosure mode, tells GND which reader it is serving. The full rules are in the disclosure reference.
Files on GitHub
disclosure/model.orm.yaml: a support desk's contacts, with the email role declared sensitivedisclosure/population.json: one customer with two email addresses- this page's Markdown source
The example
A support desk records each customer's email address. The model says a customer has at most one, and marks the address as sensitive:
roles:
- { object_type: customer_code, role_name: customer }
- { object_type: email_address, role_name: email, redact: true }
The population gives customer c-1 two addresses, so the check fails. Copy
the example:
python -c "import gnd, pathlib, shutil; shutil.copytree(pathlib.Path(gnd.__file__).parent / 'examples/disclosure', 'disclosure', dirs_exist_ok=True)"
The blocks below print two fields of the check report: the violation message and the offending facts. Save the filter once:
cat > show.py <<'PY'
import json, sys
verdict = json.load(sys.stdin)["verdicts"][0]
print(verdict["message"])
print(verdict["offending_facts"])
PY
With no mode set: as before
Without a mode, GND behaves as it always has: the declared role is redacted
unless you ask for it with --unredacted.
The check still fails. The report says which constraint failed and which role was withheld, but not the addresses.
Behind your interface: internal
Set the mode on the root command, before the subcommand, or in the environment of the process that runs GND:
gnd --disclosure internal check disclosure/model.orm.yaml disclosure/population.json --format json | python show.py
Each customer plays 'has_email' with at most one combination of the other roles — violated: customer 'c-1' has 2 facts.
[{'customer': 'c-1', 'email': 'ann@example.com'}, {'customer': 'c-1', 'email': 'ann.lee@example.com'}]
internal acts as if every raw option were passed. Your team sees the two
addresses and can decide which one to keep.
In front of customers: customer
Set the mode in the environment of the service that faces customers, so no request can loosen it. In a real deployment you export it once in that service's environment; here it is set for one command:
GND_DISCLOSURE=customer gnd check disclosure/model.orm.yaml disclosure/population.json --format json | python show.py
The verdict is the same; only the values are withheld.
Requests for values are refused, not downgraded
A customer's request might be turned into --unredacted by your service.
Under customer GND refuses it and prints nothing on stdout, so
nobody mistakes a redacted answer for the raw one:
The root option can tighten the mode but never loosen it:
GND_DISCLOSURE=customer gnd --disclosure internal check disclosure/model.orm.yaml disclosure/population.json
A mistyped value is refused too, so a misspelled lock never opens:
Why-not and witness answers
The mode also covers the commands that explain a derivation: witness,
witness-view, replay, why-not, and the Lean and Soufflé exports. This
uses the quoted_claims example from
Repair a claim that does not derive. In its
spelling-gap.pop.json the claimed quotation is missing because the work the
claim calls "Field Notes" is recorded as "FIELD NOTES".
python -c "import gnd, pathlib, shutil; shutil.copytree(pathlib.Path(gnd.__file__).parent / 'examples/quoted_claims', 'quoted_claims', dirs_exist_ok=True)"
cat > blocker.py <<'PY'
import json, sys
answer = json.load(sys.stdin)
blocker = answer["clauses"][0]["blocker"]
print(answer["view"], blocker["fact_type"], blocker["near_miss_count"])
print(blocker["near_misses"])
PY
Behind your interface, the answer shows the row spelled differently:
gnd --disclosure internal why-not quoted_claims/model.orm.json quoted_claims/spelling-gap.pop.json \
--fact-type certified --row quoted_claims/claim.json | python blocker.py
raw authored 1
[{'candidate': {'person': 'Ada Byrne', 'work': 'FIELD NOTES'}, 'roles': ['work'], 'sample': {'person': 'Ada Byrne', 'work': 'Field Notes'}}]
In front of customers, the same answer names the blocking step and keeps the count, but not the values:
GND_DISCLOSURE=customer gnd why-not quoted_claims/model.orm.json quoted_claims/spelling-gap.pop.json \
--fact-type certified --row quoted_claims/claim.json | python blocker.py
and --raw is refused:
GND_DISCLOSURE=customer gnd why-not quoted_claims/model.orm.json quoted_claims/spelling-gap.pop.json \
--fact-type certified --row quoted_claims/claim.json --raw
From Python
The Python API reads the same environment variable. A request for values under
customer raises DisclosureRefused, a ValueError:
import os
os.environ["GND_DISCLOSURE"] = "customer"
from gnd import api
try:
api.check("disclosure/model.orm.yaml", "disclosure/population.json", unredacted=True)
except ValueError as error:
print(type(error).__name__, error)
What the mode does not change
gnd exp runsession feedback,gnd discover(apart from its fact-type family, whose--show-valuesoption the mode governs and whose attribute digestscustomeromits),gnd export ilp,gnd fidelityandgnd doccheckbehave the same in every mode. Do not route their output to a customer.- Values in check messages for roles you have not declared with
redact: trueare not withheld. Declare every sensitive role. - Under
customer, the processes GND starts (the agents ofgnd exp run, the stations ofgnd solve) receiveGND_DISCLOSURE=customer. Underinternalor no mode they receive nothing extra.