# Claim register: every claim of the documents in `scope` traced to its evidence. # # Checked by `python tools/claims_check.py` (CI job `lint`, and tests/unit/test_assurance.py). # How to keep it green, and what each field means: docs/assurance/README.md#the-claim-register. # # id stable identifier, cited by the assurance case (AC-...) and the regulation mapping # doc the document that makes the claim # quote the claim's words (one string or a list); must still appear in `doc` # area structure | statistics | privacy-measured | privacy-formal | governance | security | scope # status supported the evidence establishes the claim as written # partial the evidence establishes part of it; `note` says which part is open # limitation the claim states a limit of relsynth; the evidence shows the limit is enforced or measured # scope says what a source (a regulation, a paper) says, not what relsynth does # evidence list of {prop: label} {check: Cnnn} {test: file::name} {report: path} {code: file::symbol} {source: text} # {claim: ID} (a summary of another registered claim; as strong as that claim) # note optional: what the evidence does not show scope: - docs/privacy.md - docs/dp.md - docs/security.md - docs/governance/guide.md - README.md - docs/documents.md - docs/agents.md claims: # ------------------------------------------------------------------ docs/privacy.md (measured privacy, T06) - id: PRV-01 doc: docs/privacy.md area: privacy-measured quote: "relsynth privacy attacks a synthetic release the way a recipient could, person by person, and writes a privacy report with one page per release context (internal, partner, public)." status: supported evidence: - test: tests/unit/test_privacy.py::test_assess_report_and_contexts - test: tests/unit/test_privacy.py::test_cli_privacy_writes_report_and_gates - id: PRV-02 doc: docs/privacy.md area: privacy-measured quote: "A data protection officer can read the pages without the annex; the annex and the JSON file hold the technical results." status: partial note: Readability by a DPO is a design aim; the test shows the report's structure (one page per context, annex, JSON), not that it is readable. evidence: - test: tests/unit/test_privacy.py::test_assess_report_and_contexts - id: PRV-03 doc: docs/privacy.md area: privacy-measured quote: - "The report never shows a real key." - "People are named by keyed hashes (P-3f2a…, HMAC-SHA256 of the key) under a secret 256-bit salt that is not the split seed" status: supported evidence: - test: tests/unit/test_privacy.py::test_assess_report_and_contexts - test: tests/unit/test_privacy.py::test_cli_privacy_writes_report_and_gates - id: PRV-04 doc: docs/privacy.md area: privacy-measured quote: "| No record isolation (sec. 3.4.1) | Can someone single out a real person?" status: supported evidence: - prop: prop:puctrl - check: C601 - test: tests/unit/test_privacy.py::test_wilson_and_residual_match_anonymeter_formulas - report: docs/assurance/evidence/T06/repro_anonymeter.json - source: "EDPB Guidelines 02/2026 v1.0, sec. 3.4.1, paras. 54-57" - id: PRV-05 doc: docs/privacy.md area: privacy-measured quote: "| No linkage (sec. 3.4.2) | Can someone connect two records about one person?" status: supported evidence: - test: tests/unit/test_privacy.py::test_copy_release_is_caught_and_fresh_release_is_not - report: docs/assurance/evidence/T06/repro_anonymeter.json - source: "EDPB Guidelines 02/2026 v1.0, sec. 3.4.2, paras. 58-65" - id: PRV-06 doc: docs/privacy.md area: privacy-measured quote: "| No inference (sec. 3.4.3) | Can someone learn a person's attribute?" status: supported evidence: - prop: prop:puattr - check: C606 - test: tests/unit/test_privacy.py::test_attribute_inference_against_population - source: "EDPB Guidelines 02/2026 v1.0, sec. 3.4.3, paras. 66-67" - id: PRV-07 doc: docs/privacy.md area: privacy-measured quote: "| Membership | Can someone tell that a person's data was used?" status: supported evidence: - prop: prop:pushadow - prop: prop:putpr - check: C602 - check: C604 - test: tests/unit/test_privacy.py::test_shadow_attack_raw_release_vs_population_generator - test: tests/unit/test_privacy.py::test_domias_detects_overfitting - report: docs/assurance/evidence/T06/repro_mia.json - id: PRV-08 doc: docs/privacy.md area: privacy-measured quote: - "Every number has a confidence interval (95% by default)." - "Membership attacks are run on the most exposed people, chosen by the Meeus et al. (2023) outlier score, and on as many random people." status: supported evidence: - prop: prop:putpr - check: C604 - test: tests/unit/test_privacy.py::test_clopper_pearson_edges - test: tests/unit/test_privacy.py::test_auc_interval_never_zero_width - test: tests/unit/test_privacy.py::test_vulnerability_ranks_outliers_first - id: PRV-09 doc: docs/privacy.md area: privacy-measured quote: - "The privacy unit is the table that holds the people" - "Every table that reaches it through foreign keys belongs to it: a customer's invoices and invoice lines are attacked together with the customer." - "Chains stop at other person tables: a customer's sales representative does not own the customer." status: supported evidence: - prop: prop:pusplit - check: C600 - test: tests/unit/test_privacy.py::test_unit_paths_and_split_keep_people_whole - test: tests/unit/test_privacy.py::test_unit_chain_stops_at_other_person_tables - id: PRV-10 doc: docs/privacy.md area: privacy-measured quote: "Profiles hold no key values, because synthetic keys are new labels." status: supported evidence: - prop: prop:puprofile - check: C605 - test: tests/unit/test_privacy.py::test_profiles_ignore_keys_and_row_order - id: PRV-11 doc: docs/privacy.md area: privacy-measured quote: - "Control people are never seen: the attacks' success on them is what the synthetic data teaches about anyone, and only the excess on training people is a risk to them." - "Training and control have the same size so the record-isolation rates are exactly comparable (Prop. 601); Anonymeter's fitted size correction showed risks of 0.10–0.14 under the null in our checks." status: supported evidence: - prop: prop:puctrl - check: C601 - report: docs/assurance/evidence/T06/repro_anonymeter.json - id: PRV-12 doc: docs/privacy.md area: privacy-measured quote: - "It never assumes a less motivated attacker (para. 31)." - "Each check compares the upper confidence bound with the limit (safety margin, para. 88):" - "pass when it is below, fail when even the lower bound is above, review otherwise." status: supported evidence: - test: tests/unit/test_privacy.py::test_assess_report_and_contexts - test: tests/unit/test_privacy.py::test_review_sign_off_per_person_and_small_population - source: "EDPB Guidelines 02/2026 v1.0, para. 31 (motivation), para. 88 (safety margins)" - id: PRV-13 doc: docs/privacy.md area: governance quote: "Contexts outside the organisation are blocked unless the release carries a differential-privacy guarantee: the organisation decided that DP is required for every external release, with ε set by the organisation (there is no built-in ε)." status: supported evidence: - test: tests/unit/test_privacy.py::test_assess_report_and_contexts - test: tests/unit/test_privacy.py::test_assess_with_dp_is_not_blocked - test: tests/unit/governance/test_governance.py::test_partner_without_dp_is_refused - id: PRV-14 doc: docs/privacy.md area: governance quote: "A named person (the DPO) signs off a review with relsynth privacy ... --signed-off-by \"Name, DPO\"; the report records the name." status: supported evidence: - test: tests/unit/test_privacy.py::test_review_sign_off_per_person_and_small_population - id: PRV-15 doc: docs/privacy.md area: privacy-measured quote: "With fewer than small_population (1000) people in the unit, the report warns, and every context outside the organisation fails without DP (check small_population)." status: supported evidence: - test: tests/unit/test_privacy.py::test_review_sign_off_per_person_and_small_population - id: PRV-16 doc: docs/privacy.md area: privacy-formal quote: - "When the release carries a DP guarantee (release.dp: {epsilon, delta}), the page also shows the largest TPR any attack can reach at FPR 1% (Kairouz et al. 2015; Prop. 603)." - "With cumulative_epsilon (and cumulative_delta), the budget spent by all releases from the same domain so far, it also shows the bound for all of them together, since releases about the same people compose." status: supported evidence: - prop: prop:pudp - check: C603 - test: tests/unit/test_privacy.py::test_dp_bound - test: tests/unit/test_privacy.py::test_assess_with_dp_is_not_blocked - id: PRV-17 doc: docs/privacy.md area: privacy-measured quote: "One set of fits serves every target (Prop. 602)." status: supported evidence: - prop: prop:pushadow - check: C602 - test: tests/unit/test_privacy.py::test_shadow_cache_reuses_fits - id: PRV-18 doc: docs/privacy.md area: privacy-measured quote: - "The scorecard's distance-to-closest-record ratio and exact-match rate stay in every run (relsynth run) for continuity, but they are not a privacy check: calibration showed the distance guard misses even 20% copied records, which DOMIAS catches at 5%." status: supported evidence: - report: docs/assurance/evidence/T06/guards_adult.json - test: tests/unit/test_privacy.py::test_copy_release_is_caught_and_fresh_release_is_not - id: PRV-19 doc: docs/privacy.md area: scope quote: - "Results hold for the attacks run, not for every possible attack." - "Only differential privacy gives a guarantee against all attacks; this module measures, it does not protect." status: limitation evidence: - prop: prop:pudp - source: "NIST SP 800-226, sec. 1.1 (DP protects against all attacks, including with auxiliary data)" # ------------------------------------------------------------------ docs/privacy.md: the quick check of every run (G5) - id: PRV-40 doc: docs/privacy.md area: privacy-measured quote: - "The written release was fitted on every person, so it has nobody unseen to compare with." - "The check samples up to synthesis.evaluation.privacy_check_people people (3000) of the privacy unit, fits the same generator with the same settings on a third of them (the members), and asks whether two attacks tell those members from a third the proxy never saw, using the last third as the attacker's population data:" status: supported evidence: - code: src/relsynth/privacy/quick.py::quick_check - test: tests/unit/test_privacy_quick.py::test_quick_check_passes_a_fitted_release_and_reports_counts - id: PRV-41 doc: docs/privacy.md area: privacy-measured quote: "It catches copied people that a kernel density smooths away (on one table of 1000 people, a release that copied every member gave DOMIAS AUC 0.63 and this attack 1.00, means over 10 seeds; 20% copies fail through its TPR at FPR 1%)." status: supported evidence: - check: C1740 - report: docs/assurance/evidence/G5/quick_check.json - test: tests/unit/test_privacy_quick.py::test_quick_check_catches_a_copying_generator - id: PRV-42 doc: docs/privacy.md area: privacy-measured quote: "Under a release that ignores who is a member, a false fail is rare (Prop. 1740, check C1740), and the check never changes the synthetic files: it runs after every file is written, on its own seeded random stream (Prop. 1741, check C1741; the CI job identity compares the files byte for byte)." status: supported note: Prop. 1740 bounds false alarms under the null of no membership signal; it does not say that a pass means no risk. evidence: - prop: prop:qcnull - check: C1740 - prop: prop:qcident - check: C1741 - test: tests/unit/test_privacy_quick.py::test_run_adds_the_check_and_keeps_the_release_identical - report: .github/workflows/ci.yml - code: tools/identity_check.py::main - id: PRV-43 doc: docs/privacy.md area: privacy-measured quote: "| skipped | no privacy unit was found (name one in privacy.yaml for relsynth privacy), fewer than 50 people, or the unit's tables were fitted from in-database statistics and a sample (pushdown), which does not hold every row of a person |" status: supported evidence: - test: tests/unit/test_privacy_quick.py::test_quick_check_skips_without_unit_too_few_people_or_pushdown - id: PRV-45 doc: docs/privacy.md area: security quote: - "| error | the proxy could not be fitted; the bundle names the exception type (never its message, which could hold a credential) and the run is unaffected |" status: supported evidence: - test: tests/unit/test_privacy_quick.py::test_quick_check_reports_generator_errors_without_raising - test: tests/unit/test_privacy_quick.py::test_pipeline_error_keeps_only_the_exception_type - id: PRV-46 doc: docs/privacy.md area: scope quote: "Runs in differential privacy mode (--dp, --epsilon) have no quick check: their guarantee is the DP statement, and relsynth privacy audits it." status: limitation evidence: - code: src/relsynth/cli.py::cmd_run_dp - test: tests/unit/test_privacy_quick.py::test_dp_specs_never_reach_the_quick_check - id: PRV-47 doc: docs/privacy.md area: scope quote: "When one attack cannot run (DOMIAS needs profiles that vary), the verdict covers the other one only; the result names the missing attack under `incomplete` and the printed line says so." status: limitation evidence: - code: src/relsynth/privacy/quick.py::quick_check - test: tests/unit/test_privacy_quick.py::test_an_attack_that_cannot_run_is_named - id: PRV-44 doc: docs/privacy.md area: scope quote: "It never replaces relsynth privacy (every criterion, shadow models, every context), and a release outside the organisation still needs differential privacy." status: limitation evidence: - code: src/relsynth/privacy/quick.py::quick_check - prop: prop:pudp # ------------------------------------------------------------------ docs/dp.md (formal privacy, T07) - id: DP-01 doc: docs/dp.md area: privacy-formal quote: - "relsynth can produce a synthetic database with a formal differential privacy (DP) guarantee." - "The guarantee is user level: anyone who sees the synthetic data learns about the same about one person (one row of the privacy unit table and every row that belongs to it) whether or not that person was in the real database." status: supported evidence: - prop: prop:run - prop: prop:bound - prop: prop:sens - check: C901 - check: C902 - test: tests/unit/dp/test_dp.py::test_end_to_end_statement_and_charges - id: DP-02 doc: docs/dp.md area: governance quote: - "An external release (a context with external: true in the organisation policy) must be a DP run." - "relsynth enforces this before it reads any data" status: supported evidence: - check: C906 - test: tests/unit/governance/test_governance.py::test_partner_without_dp_is_refused - test: tests/unit/governance/test_governance.py::test_invariants_hold_under_a_permissive_policy - id: DP-03 doc: docs/dp.md area: privacy-formal quote: - "relsynth has no default ε." - "A DP run without epsilon or rho is refused, and so is a δ above 10⁻⁵." status: supported evidence: - check: C906 - test: tests/unit/dp/test_dp.py::test_config_needs_a_budget_unit_and_small_delta - id: DP-04 doc: docs/dp.md area: privacy-formal quote: - "Plan, before any read. relsynth finds the tables that hold data of the unit: those with a chain of foreign keys to it." - "Every other table must be listed in public_tables, or all_unowned must be set." - "The plan refuses three cases, before reading anything or charging the budget: a table with two chains to the unit, a self-reference on an owned table, and a foreign key between owned tables off the chain." status: supported evidence: - check: C906 - test: tests/unit/dp/test_dp.py::test_plan_traces_ownership_and_public_tables - id: DP-05 doc: docs/dp.md area: privacy-formal quote: "Each statistic is charged to an accountant that refuses anything beyond the run's budget." status: supported evidence: - prop: prop:dgauss - check: C900 - test: tests/science/test_dp_checks.py::test_c900_accountant_refuses_beyond_the_plan - id: DP-06 doc: docs/dp.md area: privacy-formal quote: "This is post-processing, so it keeps the guarantee." status: supported evidence: - prop: prop:post - check: C905 - id: DP-07 doc: docs/dp.md area: privacy-formal quote: - "The proofs are Propositions 900 to 906 in docs/proofs." - "C902, for example, removes one customer at a time and confirms that no released statistic moves by more than the sensitivity it was charged for." status: supported evidence: - prop: prop:dgauss - prop: prop:run - check: C902 - test: tests/science/test_dp_checks.py::test_c902_every_released_statistic_respects_its_sensitivity - id: DP-08 doc: docs/dp.md area: privacy-formal quote: - "Every DP run produces a statement (schema relsynth.dp-statement/1; write it with --dp-statement)." - "the guarantee in ε, δ and ρ, and how it was computed (zCDP, converted with Canonne, Kamath and Steinke 2020);" - "It holds no data and no exact counts." status: supported evidence: - prop: prop:run - test: tests/unit/dp/test_dp.py::test_end_to_end_statement_and_charges - test: tests/unit/dp/test_dp.py::test_epsilon_is_converted_at_the_delta_left_after_level_selection - id: DP-09 doc: docs/dp.md area: governance quote: - "In a governed run it is signed into the manifest and goes into the release package." - "A run refused by the per-release, period or lifetime cap reads nothing." status: supported evidence: - prop: prop:reserve - check: C802 - test: tests/unit/governance/test_governance.py::test_external_dp_release_with_quorum_and_token - test: tests/unit/governance/test_governance.py::test_budget_exceeded_is_refused - id: DP-10 doc: docs/dp.md area: privacy-formal quote: "--assess runs the attack suite against the DP pipeline itself, and compares each attack's true-positive rate with the bound that ε implies." status: supported evidence: - prop: prop:pudp - test: tests/unit/test_privacy.py::test_assess_with_dp_is_not_blocked - report: docs/assurance/evidence/T07/audit_contoso_eps1.jsonl - id: DP-11 doc: docs/dp.md area: scope quote: "Those values are outside the guarantee unless you write the spec from public facts only and set spec_is_public: true." status: limitation evidence: - test: tests/unit/test_governance_design.py::test_external_context_cannot_waive_dp_or_approval - code: src/relsynth/dp/__init__.py - id: DP-12 doc: docs/dp.md area: privacy-formal quote: - "What DP mode refuses: tune, verify, export-model and doctor --apply refuse a DP spec, because they would turn real data into models or spec values outside the accounting." - "The ordinary pipeline (relsynth.run, execute) also refuses a DP spec rather than run it without DP." status: supported evidence: - test: tests/unit/dp/test_dp.py::test_ordinary_pipeline_refuses_a_dp_spec - test: tests/unit/dp/test_dp.py::test_cli_refuses_tune_and_export_model_on_a_dp_spec note: The CLI test covers tune and export-model; verify and doctor --apply are refused by the same guard but have no test of their own. - id: DP-13 doc: docs/dp.md area: scope quote: "Not supported yet: in-database pushdown in DP mode (every owned table is loaded), more than one privacy unit, composite parent keys (generated as (i, 1)), and values of pii, text and binary columns (placeholders)." status: limitation evidence: - test: tests/unit/dp/test_dp.py::test_plan_traces_ownership_and_public_tables - id: DP-14 doc: docs/dp.md area: privacy-formal quote: - "Membership-inference TPR at FPR 0.01 is 0.014, below the bound of 0.027." - "Shadow-model AUCs are 0.45 to 0.52, and attribute inference gains nothing over the population baseline." status: supported evidence: - report: docs/assurance/evidence/T07/audit_contoso_eps1.jsonl - id: DP-15 doc: docs/dp.md area: statistics quote: "The curve is nearly flat, so the loss comes from what stage 1 measures, not from the noise." status: supported evidence: - report: docs/assurance/evidence/T07/utility_vs_epsilon.jsonl note: The cause (one-dimensional stage-1 statistics) is an inference from the flat curve; roadmap T07b tests it with two-way marginals. - id: DP-16 doc: docs/dp.md area: statistics quote: - "- mst (McKenna, Miklau and Sheldon 2021) scores every pair by how badly the one-way model gets it wrong, picks a spanning tree pair by pair with the exponential mechanism, and measures the chosen pairs." - "When a measurement changes little, the next rounds spend more. relsynth keeps the model a tree (a forest), so fitting and sampling are exact." status: supported evidence: - prop: prop:selrun - prop: prop:dpforest - check: C1303 - test: tests/science/test_dp_select_checks.py::test_c1302_the_run_spends_within_its_plan - test: tests/science/test_dp_select_checks.py::test_c1303_forest_model_estimate_and_sampler_are_exact - id: DP-17 doc: docs/dp.md area: privacy-formal quote: - "The guarantee, the unit and the contribution bounding are those of section 2." - "Propositions 1300 to 1306 in docs/proofs give the proofs, and checks C1300 to C1306 (pytest -m science tests/science/test_dp_select_checks.py) test them." status: supported evidence: - prop: prop:noisymax - prop: prop:pairsens - prop: prop:selrun - check: C1300 - check: C1301 - check: C1302 - test: tests/science/test_dp_select_checks.py::test_c1300_noisy_max_is_rho_zcdp_exactly - test: tests/science/test_dp_select_checks.py::test_c1301_scores_and_pairs_respect_their_sensitivity - test: tests/science/test_dp_select_checks.py::test_c1302_the_run_spends_within_its_plan - id: DP-18 doc: docs/dp.md area: statistics quote: - "On one table, two-way marginals cut the two-way error by about a quarter at every ε." - "The error falls with ε until about ε = 4, where the bias of the tree takes over (pairs it does not model, and detail inside the 8 cells). relsynth's error is lower than smartnoise-synth MST at every ε, and lower than smartnoise-synth AIM at ε = 0.5; at ε = 1 the two are within each other's intervals." status: supported evidence: - prop: prop:dpmonotone - check: C1305 - test: tests/science/test_dp_select_checks.py::test_c1305_two_way_error_falls_with_epsilon - report: docs/assurance/evidence/T07b/compare.jsonl note: One table (ACS excerpt), three repetitions per cell; smartnoise-synth AIM did not finish at ε 4 and 8, so there is no comparison there. - id: DP-19 doc: docs/dp.md area: scope quote: "On Chinook (59 customers) every category level is suppressed at these ε, and no method can recover them." status: limitation evidence: - report: docs/assurance/evidence/T07b/curves.jsonl - prop: prop:sens - id: DP-20 doc: docs/dp.md area: privacy-measured quote: "An attack audit (tools/dp_audit.py, Contoso, ε = 1, 16 + 16 shadow databases) found the membership-inference true-positive rate at 1 % false positives to be 0.011 with mst and 0.012 with aim, below the bound e^ε · 0.01 + δ = 0.027." status: supported evidence: - report: docs/assurance/evidence/T07b/audit.jsonl # ------------------------------------------------------------------ docs/security.md - id: SEC-01 doc: docs/security.md area: security quote: "| Database credentials | Leaked through spec files, logs, error messages, CI output, diagnostics bundles, process lists | Credentials come from the environment, and every output masks them (see below) |" status: supported evidence: - test: tests/unit/test_security.py::test_mask_url - test: tests/unit/test_security.py::test_spec_urls_masked_everywhere - test: tests/unit/test_security.py::test_bundle_holds_no_password - test: tests/e2e/test_cli.py::test_connection_errors_exit_3_and_mask_passwords - id: SEC-02 doc: docs/security.md area: privacy-measured quote: "| Source data | Synthetic rows that copy real ones; rare values that identify a person; bundles that carry rows |" status: supported evidence: - test: tests/unit/test_security.py::test_synthetic_rows_are_not_copies - test: tests/unit/test_security.py::test_privacy_checks_run_by_default - prop: prop:guard - test: tests/unit/test_privacy.py::test_assess_report_and_contexts - id: SEC-03 doc: docs/security.md area: security quote: - "| The target database | SQL injection through table or column names or values | SQLAlchemy Core with quoted identifiers and bound values everywhere |" - "Tables are reflected by name and every statement is built with SQLAlchemy Core." - "Identifiers are quoted by the dialect, and values are bound parameters." - "Rule expressions are never translated into SQL text; they are evaluated in Python on loaded rows." status: supported evidence: - test: tests/unit/test_connectors.py::test_hostile_table_and_column_names_are_quoted - test: tests/integration/test_servers.py::test_hostile_identifiers_on_server - id: SEC-04 doc: docs/security.md area: security quote: - "Masked everywhere. mask_url replaces:" - "It is applied to every error message the CLI prints (_scrub), the JSON output, saved specs, diagnostics bundles (again when they are written, whoever built them), exported models and ping results." status: supported evidence: - test: tests/unit/test_security.py::test_mask_url - test: tests/unit/test_security.py::test_scrub_masks_urls_inside_messages - test: tests/unit/test_security.py::test_spec_urls_masked_everywhere - test: tests/unit/test_security.py::test_bundle_holds_no_password - test: tests/unit/test_security.py::test_failed_connection_logs_no_password - test: tests/unit/test_spec.py::test_save_masks_passwords - id: SEC-05 doc: docs/security.md area: security quote: - "YAML is read with a safe loader that also refuses anchors and aliases." - "Python tags (!!python/object) cannot construct objects, and an alias bomb (a few lines that expand to billions of nodes) is refused before it expands." status: supported evidence: - test: tests/unit/test_spec.py::test_yaml_aliases_are_refused - test: tests/unit/test_spec.py::test_yaml_python_tags_are_refused - test: tests/unit/test_security.py::test_spec_cannot_construct_python_objects - id: SEC-06 doc: docs/security.md area: security quote: - "Rule expressions are parsed with Python's ast module and evaluated by relsynth's own interpreter, never by eval:" - "attribute access, subscripts, lambdas, comprehensions, f-strings, assignment expressions and keyword arguments are refused;" - "expressions longer than 10,000 characters or nested more than 100 levels are refused." status: supported evidence: - test: tests/unit/test_security.py::test_rule_expressions_are_sandboxed - test: tests/unit/test_security.py::test_expression_size_and_depth_limits - test: tests/unit/test_security.py::test_allowed_expressions_still_work - id: SEC-07 doc: docs/security.md area: security quote: "Patterns that can backtrack catastrophically are refused, such as nested repetition (a+)+ or repeated alternatives that can match the same text (a|aa)+, and so are patterns longer than 500 characters." status: supported evidence: - test: tests/unit/test_security.py::test_catastrophic_regexes_are_refused - test: tests/unit/test_security.py::test_ordinary_regexes_are_accepted - id: SEC-08 doc: docs/security.md area: security quote: "Unknown keys are errors (with a \"did you mean\" suggestion), so a misspelt privacy setting cannot silently turn a protection off." status: supported evidence: - test: tests/unit/test_settings.py::test_validation_errors - test: tests/unit/test_privacy.py::test_config_rejects_unknown_keys - test: tests/unit/test_spec.py::test_invalid_settings_are_refused - id: SEC-09 doc: docs/security.md area: security quote: - ".., ., empty names, /, \\, NUL and absolute paths are refused;" - "so is any name whose file resolves outside the folder, through a symbolic link for example." status: supported evidence: - test: tests/unit/test_security.py::test_inside_refuses_non_plain_names - test: tests/unit/test_security.py::test_symlinked_table_cannot_escape - test: tests/unit/test_connectors.py::test_file_table_names_cannot_escape_the_folder - id: SEC-10 doc: docs/security.md area: privacy-measured quote: - "Diagnostics bundles, verify reports and profile output hold counts and statistics, not rows." - "Rows of pushed facts never leave the source as a whole; only aggregates and a sample do." status: partial note: The bundle test checks that no password is in it; there is no test that a bundle holds no row. Pushdown reads are aggregate queries plus a seeded sample (Props. 100 and 103). evidence: - test: tests/unit/test_security.py::test_bundle_holds_no_password - prop: prop:pdsuff - prop: prop:hashsample - test: tests/unit/test_pushdown.py::test_full_sample_pushdown_equals_in_memory - id: SEC-11 doc: docs/security.md area: privacy-formal quote: - "Differential privacy is optional." - "Without it, protection is measured, not guaranteed: relsynth privacy attacks the release per person (privacy.md)." - "With --epsilon, a run has a user-level (ε, δ) guarantee (dp.md)." - "Under the governance policy, a release outside the organisation is refused without DP (governance/guide.md)." status: supported evidence: - prop: prop:run - test: tests/unit/test_privacy.py::test_assess_report_and_contexts - test: tests/unit/governance/test_governance.py::test_partner_without_dp_is_refused - id: SEC-12 doc: docs/security.md area: security quote: - "The core package depends on seven well-known libraries (numpy, pandas, scipy, SQLAlchemy, PyYAML, DuckDB and its SQLAlchemy dialect) and the database driver you choose; every other dependency comes with an extra you install." - "Versions have lower bounds only, so you can patch them yourself." status: supported evidence: - report: pyproject.toml - id: SEC-13 doc: docs/security.md area: security quote: - "CI runs on every change:" - "Every suppressed finding is annotated in the code with its test ID and a reason, for example # nosec B608 where a query is built with SQLAlchemy and not by string formatting." status: supported evidence: - report: .github/workflows/ci.yml - report: pyproject.toml - id: SEC-14 doc: docs/security.md area: security quote: - "The package and the CLI connect only to the databases, warehouses and storage you name, and send no telemetry." - "The service also contacts the OIDC identity provider and the Vault server you configure, and exports traces only when you turn on OpenTelemetry (relsynth[service-otel]) and give it an endpoint." status: supported evidence: - test: tests/unit/test_assurance.py::test_network_access_is_confined_to_named_modules note: The test checks which modules may open connections; third-party drivers (database and warehouse clients) are outside it. - id: SEC-15 doc: docs/security.md area: security quote: "subprocess runs fixed argument lists, never a shell, in four places: pdflatex (when present) for the exported model's PDF, the OPA binary when the policy engine is opa, the GPU listing commands of relsynth compute, and the service worker's job runner." status: supported evidence: - report: pyproject.toml - test: tests/unit/governance/test_policy_parity.py::test_engine_failure_denies note: Enforced by bandit B602-B607 in CI (no shell=True accepted); the four call sites are annotated with nosec B404 and a reason. - id: SEC-16 doc: docs/security.md area: security quote: "top-level keys starting with `_`, a non-null `synthesis.settings_file` other than a built-in `preset:NAME`, and a non-null `synthesis.compute.file` are refused when the spec is saved and again when a job runs." status: supported evidence: - test: tests/unit/service/test_service_api.py::test_spec_cannot_name_service_files - code: src/relsynth/service/policy.py::spec_refusals note: The test exercises the save path; the run-time check calls the same spec_refusals function from the worker. # ------------------------------------------------------------------ docs/governance/guide.md (T08) - id: GOV-01 doc: docs/governance/guide.md area: governance quote: - "relsynth can record every run in a registry: a folder with signed run manifests, the organisation's privacy budget, approvals and releases, kept in a hash chain so that any later change is detected." status: supported evidence: - prop: prop:govchain - check: C803 - test: tests/unit/governance/test_governance.py::test_tampered_registry_is_detected - test: tests/unit/governance/test_governance.py::test_tampered_report_or_manifest_object_is_detected - id: GOV-02 doc: docs/governance/guide.md area: governance quote: - "Every person or service that signs gets an Ed25519 key." - "Private keys are written with mode 0600." status: supported evidence: - test: tests/unit/governance/test_governance.py::test_key_file_roundtrip_and_mode - test: tests/unit/governance/test_governance.py::test_dsse_signature_binds_payload_type_and_bytes - id: GOV-03 doc: docs/governance/guide.md area: governance quote: - "Until then, gov policy check refuses the policy with a sentence that says what is missing." - "Only a trusted key with the privacy owner's role can sign it." - "Every manifest records the digest of the policy it was judged under." status: supported evidence: - test: tests/unit/governance/test_governance.py::test_policy_without_budget_is_refused_with_guidance - test: tests/unit/governance/test_governance.py::test_policy_must_be_signed_by_privacy_owner - test: tests/unit/test_governance_design.py::test_budget_has_no_default_and_bounded_delta - test: tests/unit/governance/test_governance.py::test_cli_end_to_end - id: GOV-04 doc: docs/governance/guide.md area: governance quote: "Four invariants hold whatever the policy says: an external release needs DP, an external release needs an approval by someone other than the operator, a fail or blocked privacy verdict is never released, and no domain may exceed its lifetime budget." status: supported evidence: - test: tests/unit/governance/test_governance.py::test_invariants_hold_under_a_permissive_policy - test: tests/unit/governance/test_governance.py::test_failed_verdict_quality_and_target_are_refused - test: tests/unit/test_governance_design.py::test_external_context_cannot_waive_dp_or_approval - test: tests/unit/governance/test_policy_parity.py::test_builtin_engine_matches_fixture - id: GOV-05 doc: docs/governance/guide.md area: governance quote: - "A context outside the organisation is refused unless the run is differentially private (--epsilon, --dp or a dp section; see dp.md)." - "Without either, the verdict is missing and the run cannot be released." - "context \"partner\" is outside the organisation: differential privacy is required, and this run has none" status: supported evidence: - test: tests/unit/governance/test_governance.py::test_partner_without_dp_is_refused - test: tests/unit/governance/test_governance.py::test_failed_verdict_quality_and_target_are_refused - id: GOV-06 doc: docs/governance/guide.md area: governance quote: - "The run writes its synthetic data as usual and then records a signed manifest: hashes of the outputs and the spec, versions, seeds, the verdicts, the quality score and what the policy still needs before a release." - "The manifest holds no data, no row counts and no credentials." status: supported evidence: - test: tests/unit/governance/test_governance.py::test_manifest_holds_no_data_or_credentials - test: tests/unit/test_governance_design.py::test_example_policy_and_manifest_validate - id: GOV-07 doc: docs/governance/guide.md area: governance quote: - "On allow it signs a release token and writes the package: the synthetic tables byte for byte as signed, the manifest, the approvals, the token, a checkpoint, and the DP statement for DP runs." - "The privacy report never goes into a package: it was computed on real data." status: supported evidence: - test: tests/unit/governance/test_governance.py::test_external_dp_release_with_quorum_and_token - test: tests/unit/governance/test_governance.py::test_privacy_report_cannot_leave_the_organisation - id: GOV-08 doc: docs/governance/guide.md area: governance quote: - "Revoking never refunds privacy budget, because the data has already left." - "Budget is reserved before a DP run reads data and refunded only if it never read any (Proposition 802)." status: supported evidence: - prop: prop:reserve - check: C802 - test: tests/unit/governance/test_governance.py::test_ledger_reserve_touch_settle_refund - test: tests/unit/governance/test_governance.py::test_external_dp_release_with_quorum_and_token - test: tests/unit/governance/test_governance.py::test_rejection_and_revocation - id: GOV-09 doc: docs/governance/guide.md area: governance quote: "A registry check catches edited, deleted, inserted, reordered and truncated log entries and any changed report or manifest (Proposition 803, check C803)." status: supported evidence: - prop: prop:govchain - check: C803 - test: tests/unit/governance/test_governance.py::test_tampered_registry_is_detected - test: tests/unit/governance/test_governance.py::test_resigned_manifest_by_untrusted_key_is_detected - id: GOV-10 doc: docs/governance/guide.md area: privacy-formal quote: - "Values below 1 give strong protection; deployments use 1 < ε ≤ 20, and the risk above 1 should be measured, for example with relsynth privacy." - "δ should be at most 1/n² for n people and never above 10⁻⁵ (the policy schema refuses more)." - "The lifetime cap never resets; a period cap only spreads it over time (Proposition 801)." status: supported evidence: - prop: prop:period - prop: prop:ledger - check: C800 - check: C801 - test: tests/unit/governance/test_governance.py::test_policy_refuses_external_without_dp_and_large_delta - test: tests/unit/governance/test_governance.py::test_ledger_lifetime_cap_under_concurrency - source: "NIST SP 800-226, sec. 2.2 (no rule for choosing epsilon)" note: The ε ranges are the deployment practice NIST SP 800-226 reports, not something relsynth proves. - id: GOV-11 doc: docs/governance/guide.md area: governance quote: "each DP run's state (reserved, touched, settled, refunded) and the number of releases made without DP." status: supported evidence: - test: tests/unit/governance/test_governance.py::test_nondp_runs_are_recorded - test: tests/unit/governance/test_governance.py::test_ledger_reserve_touch_settle_refund # ------------------------------------------------------------------ README.md (summary of the guides; registered after 2.0.0) # Where a README sentence sums up a guide's claim it cites that claim, so the evidence is kept in one place. - id: RM-01 doc: README.md area: structure quote: "Every primary and foreign key resolves, every rule holds, and the statistics match the source." status: partial note: Keys and rules are exact on every row; statistics match within calibrated limits, and on real databases not every check passes (AdventureWorks 1229/1286 in memory). evidence: - prop: prop:rules - prop: prop:unique - check: C14 - check: C43 - test: tests/unit/core/test_evaluate.py::test_cardinality_and_fk_integrity - report: docs/pushdown.md#real-databases-in-memory-against-pushdown - id: RM-02 doc: README.md area: scope quote: - "Each statement the engine relies on is a numbered proposition with a proof (docs/proofs/relsynth-proofs.pdf) and a test that checks it." - "relsynth-proofs.pdf: 178 propositions with proofs, each tied to a numbered check." status: partial note: The count is checked (test below), and checks name their propositions, but no tool verifies that every proposition has a check of its own. evidence: - test: tests/unit/test_assurance.py::test_proposition_numbers_follow_latex_counters - test: tests/unit/test_assurance.py::test_claim_register_traces_every_claim - code: tools/check_proofs.py - report: docs/proofs/relsynth-proofs.tex - id: RM-03 doc: README.md area: privacy-formal quote: - "Release 2.0 adds an optional differential privacy mode with a per-person guarantee" - "Differential privacy (optional). relsynth run --epsilon 1 gives a user-level (ε, δ) guarantee for each person of the privacy unit and all their rows." - "The organisation sets ε; there is no default." - "Stage 1 bounds each person's contribution and releases noisy integer statistics with OpenDP's discrete Gaussian mechanism; stage 2 fits the ordinary engine to them." - "The guarantee is user-level (ε, δ) for one person and all their rows; δ is at most 10⁻⁵ and ε has no default." - "Every run writes a DP statement in the NIST SP 800-226 style." status: supported evidence: - claim: DP-01 - claim: DP-03 - claim: DP-06 - claim: DP-07 - claim: DP-08 - id: RM-04 doc: README.md area: structure quote: - "Primary keys are unique, foreign keys resolve, hierarchies have no cycles, and composite and unique keys hold." - "In both modes, every key resolves, every unique key but one holds" status: supported evidence: - prop: prop:unique - prop: prop:gw - check: C11 - check: C14 - check: C46 - test: tests/unit/core/test_evaluate.py::test_cardinality_and_fk_integrity - test: tests/unit/test_runtime.py::test_keys_and_links_hold_across_partitions - report: docs/pushdown.md#real-databases-in-memory-against-pushdown - id: RM-05 doc: README.md area: structure quote: - "lookups, running balances and aggregates (an order total equal to its lines) hold on every row." - "and every declared or found rule holds on every row." - "Rules are imposed by exact conditioning: rejection, a redraw instead of a deletion, or calibrated link draws." status: supported evidence: - prop: prop:rules - prop: prop:k3 - prop: prop:computed - prop: prop:cumulative - prop: prop:unique - check: C15 - check: C20 - check: C23 - check: C39 - check: C51 - id: RM-06 doc: README.md area: privacy-measured quote: "Rows are drawn from a fitted model; only tables you declare mode: copy are copied." status: supported note: Rows are new draws; a few may agree with a real row by chance on low-cardinality columns, which the test bounds on high-cardinality attributes. evidence: - test: tests/unit/test_security.py::test_synthetic_rows_are_not_copies - check: C04 - check: C66 - id: RM-07 doc: README.md area: privacy-measured quote: - "Text cannot reproduce rare source content, and memorisation guards are on by default." - "relsynth privacy attacks a release per person (membership, attribute inference, singling out, linkability) and writes a report per release context." status: supported evidence: - prop: prop:lm - prop: prop:guard - check: C13 - test: tests/unit/test_security.py::test_privacy_checks_run_by_default - claim: PRV-01 - id: RM-08 doc: README.md area: governance quote: - "Releases outside the organisation are refused without it." - "A run with --context partner or public is refused before reading data unless it is differentially private." status: supported evidence: - claim: DP-02 - claim: GOV-05 - id: RM-09 doc: README.md area: governance quote: - "Signed, hash-chained manifests of every run, a privacy budget ledger per domain, an organisation policy and human approvals before release" - "A registry of signed (Ed25519, DSSE / in-toto) manifests in a hash-chained log; an organisation policy with ε caps; a privacy budget ledger per domain that reserves before any data is read; approvals with quorum and separation of duties; signed release packages." status: supported evidence: - claim: GOV-01 - claim: GOV-02 - claim: GOV-04 - claim: GOV-07 - claim: GOV-08 - claim: GOV-09 - id: RM-10 doc: README.md area: security quote: - "Facts larger than memory are fitted from statistics computed inside the database (GROUP BY, window functions) plus a uniform sample. Rows never leave the database as a whole." - "Rows never leave the source database for pushed-down facts." status: supported evidence: - prop: prop:pdsuff - prop: prop:hashsample - test: tests/unit/test_pushdown.py::test_sampled_pushdown_keeps_quality_and_memory_bound - test: tests/unit/test_pushdown.py::test_full_sample_pushdown_equals_in_memory - id: RM-11 doc: README.md area: statistics quote: - "A scorecard compares synthetic with real data, check by check, with calibrated limits. --fail-under 0.98 makes a CI job fail when quality drops." - "The scorecard checks marginals, dependence, joins, cardinalities, trees, time, text, rules and privacy, each against a limit calibrated to the sampling noise of a posterior predictive draw." - "It holds hundreds of pass/fail checks" - "Families of checks are Šidák-corrected, and each check reports its significance level, so the number of false failures to expect is known." status: supported evidence: - prop: prop:ppnull - prop: prop:sidak - check: C41 - check: C76 - check: C79 - check: C80 - test: tests/e2e/test_cli.py::test_quality_gate_exit_code - id: RM-12 doc: README.md area: structure quote: "The file lists every table and column with its role, every link and every rule found, and a review: list of questions only you can answer." status: supported evidence: - check: C40 - check: C44 - test: tests/unit/test_api.py::test_introspect_draft_is_valid_and_runnable - id: RM-13 doc: README.md area: structure quote: "From release 2.0, introspect writes an IR 2.0 document by default (--format v1 keeps the 1.0 spec); every command reads both." status: supported evidence: - prop: prop:irround - test: tests/unit/test_ir.py::test_introspect_format_follows_the_release - test: tests/unit/test_ir.py::test_cli_runs_an_ir_file - test: tests/e2e/test_cli.py::test_doctor_and_tune_write_ir_when_given_ir - test: tests/unit/test_ir.py::test_ir_document_gives_identical_synthetic_data - id: RM-14 doc: README.md area: security quote: - "Passwords are never accepted on the command line; they come from password_env or url_env." - "Passwords are masked in every message, log, saved spec, diagnostics bundle and exported model." - "(saved specs never contain passwords)" status: supported evidence: - claim: SEC-01 - claim: SEC-04 - test: tests/e2e/test_cli.py::test_locations_from_environment - test: tests/unit/test_spec.py::test_save_masks_passwords - id: RM-15 doc: README.md area: scope quote: "Every command takes --json for machine-readable output and returns an exit code for scripts." status: partial note: The tests cover the exit codes 1, 2 and 3 and --json on run, check and the governed run; no test walks every command. evidence: - test: tests/e2e/test_cli.py::test_quality_gate_exit_code - test: tests/e2e/test_cli.py::test_spec_errors_exit_2 - test: tests/e2e/test_cli.py::test_connection_errors_exit_3_and_mask_passwords - id: RM-16 doc: README.md area: structure quote: "and it is validated strictly: every option is documented in docs/spec-reference.md, and misspelt keys are refused with a suggestion." status: supported evidence: - check: C67 - check: C68 - test: tests/unit/test_spec.py::test_invalid_settings_are_refused - id: RM-17 doc: README.md area: statistics quote: - "Every entity row (a customer, a product) gets a latent segment, fitted by Gibbs sampling over the whole schema." - "Each table's columns follow a Bayesian Gaussian copula with exact marginals, per segment or pooled." - "A dataset draws every parameter once from its posterior, then the rows, entities first and facts after." status: supported evidence: - prop: prop:ident - prop: prop:eta - prop: prop:pit - prop: prop:copula - prop: prop:postpred - check: C01 - check: C02 - check: C05 - check: C09 - check: C57 - id: RM-18 doc: README.md area: statistics quote: "Tune engine settings toward your objectives over several seeds; constraints report for what cannot be met" status: supported evidence: - test: tests/unit/test_autotune.py::test_loop_finds_the_planted_fix_and_explains_the_rest - test: tests/unit/test_autotune.py::test_loop_never_accepts_a_hard_violation - id: RM-19 doc: README.md area: scope quote: - "The full reference with every option is in docs/cli.md." - "The full reference, with every parameter, is in docs/api.md." - "| docs/cli.md | Every command and option |" - "| docs/api.md | Every public class and function |" - "| docs/spec-reference.md | Every specification option, with recipes (template) |" status: partial note: The references are generated from the parser, the public names and the spec schema (docs/make_*_reference.py), but unlike settings-reference.md and ir-reference.md no test fails when they are stale. evidence: - code: docs/make_cli_reference.py - code: docs/make_api_reference.py - code: docs/make_spec_reference.py - check: C68 - test: tests/unit/test_api.py::test_public_names - id: RM-20 doc: README.md area: scope quote: "| docs/ir-reference.md | Every IR field (generated from the model; JSON Schema in src/relsynth/ir/schema/) |" status: supported evidence: - test: tests/unit/test_ir.py::test_schema_and_reference_are_generated - id: RM-21 doc: README.md area: security quote: "The package's own CI is in .github/workflows/ci.yml: lint, security and licence scans, types, unit tests on Python 3.10–3.13 and three operating systems" status: supported evidence: - report: .github/workflows/ci.yml - claim: SEC-13 - id: RM-22 doc: README.md area: statistics quote: - "exact GROUP BY counts over the key tuples the model uses;" - "Entities (dimensions) are always loaded, because each of their rows carries its own latent segment." - "Proposition 100 proves it, and a run with a sample of every row gives exactly the in-memory result on all four databases." - "The column models are an exact posterior given the sample (Proposition 104)." status: supported evidence: - prop: prop:pdsuff - prop: prop:pdexact - prop: prop:pdsample - prop: prop:pdeligible - test: tests/unit/test_pushdown.py::test_full_sample_pushdown_equals_in_memory - test: tests/unit/test_pushdown.py::test_ineligible_reasons - test: tests/integration/test_servers.py::test_full_sample_pushdown_matches_sqlite_in_memory - report: docs/pushdown.md#real-databases-in-memory-against-pushdown - id: RM-23 doc: README.md area: privacy-formal quote: "On Contoso at ε = 1 the measured membership-inference rate stayed below the DP bound." status: supported evidence: - claim: DP-14 - report: docs/assurance/evidence/T07/audit_contoso_eps1.jsonl - id: RM-24 doc: README.md area: statistics quote: - "With default settings the output is unchanged byte for byte." - "relsynth autotune separates systematic failures from noise over several seeds" status: supported evidence: - test: tests/unit/test_settings.py::test_promoted_constants_keep_the_1_0_values - test: tests/unit/test_runtime.py::test_default_runtime_keeps_the_1_0_sampler - test: tests/unit/test_generators.py::test_copula_is_the_default_and_naming_it_changes_nothing - test: tests/unit/test_autotune.py::test_systematic_versus_sporadic - code: tools/identity_check.py - id: RM-25 doc: README.md area: security quote: - "Every SQL statement is built with SQLAlchemy Core, with quoted identifiers and bound values." - "Table and column names cannot inject SQL; tests use hostile names on every database." status: supported evidence: - claim: SEC-03 - test: tests/unit/test_connectors.py::test_hostile_table_and_column_names_are_quoted - test: tests/integration/test_servers.py::test_hostile_identifiers_on_server - id: RM-26 doc: README.md area: security quote: - "A spec is YAML read with a safe loader that also refuses aliases, so an alias bomb fails." - "Regular expressions with catastrophic backtracking are refused." - "Table names cannot leave the target folder (.., separators, absolute paths and symlinks are refused)." status: supported evidence: - claim: SEC-05 - claim: SEC-06 - claim: SEC-07 - claim: SEC-09 - id: RM-27 doc: README.md area: security quote: "Bundles and profiles hold aggregates only, and profile --top must be asked for explicitly." status: partial note: As SEC-10, no test shows that a diagnostics bundle holds no rows. evidence: - claim: SEC-10 - id: RM-28 doc: README.md area: statistics quote: "Fits use exact vectorised kernels, with numba kernels as an option (relsynth[speed])." status: supported evidence: - prop: prop:kernels - check: C69 - id: RM-29 doc: README.md area: scope quote: - "The statistical check suite now has C00–C82 plus the 2.0 checks C600–C606 (privacy attacks), C800–C803 (governance), C900–C906 (differential privacy) and C1200–C1206 (time series), with the generator propositions 700–706 tested in tests/unit/test_generators.py: 113 science tests" - "A benchmark harness (bench/) reruns the real databases" - "and flags drift against a five-seed baseline." status: supported note: The test counts are those of the 2.0.0 tree (pytest --collect-only -m science gives 113). evidence: - check: C82 - check: C606 - test: tests/unit/test_generators.py::test_reference_generators_keep_keys_links_and_rules - check: C803 - check: C906 - check: C1206 - report: .github/workflows/ci.yml - report: bench/baseline.json - test: tests/unit/test_bench_regress.py::test_r4_drift_of_a_tracked_value - id: RM-30 doc: README.md area: statistics quote: "Draws for the same seed change on databases with link tables, because degree laws are now the default (synthesis.facts.link_degree: segment restores the 1.0 draws)" status: limitation evidence: - prop: prop:linkdeg - test: tests/unit/core/test_link_degree.py::test_link_degree_spec_values - id: RM-31 doc: README.md area: statistics quote: - "Every number below comes from the final code." - "It then checks that each numbered proposition holds" - "All 79 checks (C00–C78, more than 500 assertions) pass." - "Four real databases, each from an unedited introspect draft, fitted in memory and with every eligible fact pushed down" status: supported note: These are the 1.0.0 measurements; on 2.0 the benchmark harness reruns the same databases. evidence: - check: C00 - check: C78 - report: docs/pushdown.md#real-databases-in-memory-against-pushdown - report: bench/baseline.json - id: RM-32 doc: README.md area: structure quote: "On every server, the same seed gives the same synthetic database as SQLite, value for value." status: supported evidence: - test: tests/integration/test_servers.py::test_full_sample_pushdown_matches_sqlite_in_memory - test: tests/integration/test_servers.py::test_introspect_fit_and_pushdown_run - test: tests/integration/test_servers.py::test_statistics_match_the_in_memory_reference - id: RM-33 doc: README.md area: scope quote: - "the layer 1 algorithms, with complexity and proofs." - "It covers the Bayesian Gaussian copula, latent segments from in-database sufficient statistics, the merge step and differential privacy on summaries." - "gives a readable summary of the model and of the six v1.0 propositions, with the proofs in full." status: supported evidence: - check: C00 - report: docs/proofs/algorithms.pdf - report: docs/theory.md - id: RM-34 doc: README.md area: scope quote: - "| Rules and keys | Rules by rejection; three-valued rules agree with the database; computed columns are exact; unique keys by exact conditioning; order headers (lossless split); running balances |" - "| Evaluation | Calibrated limits under the posterior predictive null; Šidák over families; distances with an atom at zero |" - "| In-database fitting (v1.0) | 100 sufficiency of the in-database statistics; 101 agreement with in-memory fitting; 102 Acklam's quantile bound; 103 hash sampling is Bernoulli; 104 the sample posterior is exact; 105 eligibility |" - "| IR, generators and quality (v2.0) | 106 loss-free 1.0 conversion; 107–108 privacy-unit tracing; 109–113 calibrated quality report; 700–706 the skeleton is kept whatever the generator |" - "| Privacy and governance (v2.0) | Per-person attack metrics; 800–803 the budget ledger bounds the domain's privacy loss, reserve before reading, tamper evidence; 900–906 discrete Gaussian, stable contribution bounding, exact partition selection, two-stage DP |" - "| Time and scale (v2.0) | 1200–1206 time change, gamma renewal bridge, sequence posterior, calibrated limits, incremental runs; degree laws of link tables; 950–954 worker independence, partitions keep the law, keys across partitions, drift tests |" status: supported note: The table names propositions; each label below is one of them, and the numbers are checked against the LaTeX counters. evidence: - prop: prop:rules - prop: prop:k3 - prop: prop:computed - prop: prop:unique - prop: prop:header - prop: prop:ppnull - prop: prop:sidak - prop: prop:dcr - prop: prop:pdsuff - prop: prop:pdeligible - prop: prop:irround - prop: prop:ratenorm - prop: prop:genskeleton - prop: prop:cartbb - prop: prop:puctrl - prop: prop:ledger - prop: prop:govchain - prop: prop:dgauss - prop: prop:run - prop: prop:tschange - prop: prop:tsincr - prop: prop:linkdeg - prop: prop:rtdet - prop: prop:rtdrift - id: RM-35 doc: README.md area: scope quote: "Rules that hold on every source row but are not declared or found are reproduced statistically, not exactly." status: limitation evidence: - prop: prop:rules - check: C58 - id: RM-36 doc: README.md area: privacy-formal quote: "Differential privacy mode releases one-way statistics, degrees and one correlation matrix per table, so its utility is nearly flat in ε" status: limitation evidence: - claim: DP-13 - claim: DP-15 - report: docs/assurance/evidence/T07/utility_vs_epsilon.jsonl # ------------------------------------------------------------------ README.md: synthetic text (T14, docs/text.md) - id: RM-37 doc: README.md area: privacy-formal quote: "| ngram + dp | CPU, no weights, air-gapped | user-level differential privacy (DP n-gram release) |" status: supported evidence: - prop: prop:txngram - check: C1501 - check: C1500 - test: tests/unit/test_textgen.py::test_dp_ngram_charges_stay_in_budget - test: tests/unit/test_textgen.py::test_dp_ngram_drops_one_persons_words - id: RM-38 doc: README.md area: privacy-measured quote: "relsynth ships no model weights; relsynth text models lists models with their licences. relsynth text pii-eval measures the recall of the PII recognizers, and relsynth text audit inserts canaries, measures their exposure against the DP bound and searches the output for copies of training texts." status: partial note: The audit, PII recall and licence register are tested; that the wheel holds no weights follows from the package data (text_models.yaml lists models, it does not contain them) and is not checked by a test of the built wheel. Licences are recorded from model cards and compared with the hub by tools/check_text_models.py in the text-lm CI job. evidence: - prop: prop:txexposure - prop: prop:txminhash - check: C1500 - check: C1505 - check: C1506 - test: tests/unit/test_textgen.py::test_labelled_offsets_and_regex_recall_floor - test: tests/unit/test_textgen.py::test_audit_of_dp_configuration_is_consistent - test: tests/unit/test_textgen.py::test_near_duplicates_finds_copies_and_edits - code: tools/check_text_models.py::main - report: docs/text.md - id: RM-39 doc: README.md area: scope quote: "Text columns are still empty in DP mode (--dp); use text.dp on the column (its guarantee covers that column only)." status: limitation evidence: - code: src/relsynth/dp/relational.py::PLACEHOLDER - report: docs/adr/0010-synthetic-text.md # ------------------------------------------------------------------ docs/documents.md and README (document databases, T13) - id: DOC-01 doc: docs/documents.md area: scope quote: Release 2.1; design in ADR 0011, propositions 1400–1405 in the proofs. status: supported evidence: - report: docs/adr/0011-document-databases.md - report: docs/proofs/sec_v40_documents.tex - id: DOC-02 doc: docs/documents.md area: structure quote: synth and check exit with code 1 (the quality gate, as for relsynth run --fail-under) when a document does not validate, a reference does not resolve or a shape metric is outside its limit. status: partial note: The test runs the check command with a passing and a failing collection; the synth command's gate shares the report code but is not run by a test. evidence: - code: src/relsynth/documents/cli.py::cmd_synth - code: src/relsynth/documents/cli.py::cmd_check - test: tests/unit/test_documents.py::test_docs_check_gates_on_invalid_documents - id: DOC-03 doc: docs/documents.md area: security quote: 'A MongoDB URL with a password is refused on the command line. collections: [...] limits a source to some collections.' status: supported evidence: - test: tests/unit/test_documents.py::test_no_password_on_the_command_line_and_hostile_keyspaces - code: src/relsynth/documents/connectors.py::parse_location - id: DOC-04 doc: docs/documents.md area: structure quote: 'discover profiles each collection by path: how many objects hold the field, the count of each type (null, bool, int, long, double, decimal, string, objectId, date, binary, object, array), array lengths, the distinct values, the set of fields of each object (its signature) and the depth.' status: supported evidence: - test: tests/unit/test_documents.py::test_inference_finds_shapes - code: src/relsynth/documents/infer.py::profile_collection - id: DOC-05 doc: docs/documents.md area: structure quote: - '- length fields: an integer field equal to an array''s length on every document (n_items == len(items));' - '- set arrays: arrays of scalars whose elements never repeat;' status: supported evidence: - test: tests/unit/test_documents.py::test_inference_finds_shapes - check: C1400 - id: DOC-06 doc: docs/documents.md area: structure quote: - Partial or near-unique matches are listed as candidates with their coverage and are not enforced. - Declare a reference the profile cannot see with --references refs.yaml ([{collection, path, to, to_path}]). status: supported evidence: - test: tests/unit/test_documents.py::test_reference_candidates_and_sequences - code: src/relsynth/documents/infer.py::find_references - id: DOC-07 doc: docs/documents.md area: structure quote: Every source document validates against the inferred schema (Prop. 1400). status: supported evidence: - prop: prop:docschema - check: C1400 - test: tests/unit/test_documents.py::test_every_source_document_validates - id: DOC-08 doc: docs/documents.md area: structure quote: The tables go to a SQLite work file with every key declared, and introspect drafts the spec. nest(shred(D)) gives D back exactly (Prop. 1401). status: supported evidence: - prop: prop:docshred - check: C1401 - test: tests/unit/test_documents.py::test_shred_nest_round_trip - id: DOC-09 doc: docs/documents.md area: statistics quote: 'Synthesise with the relational engine: a Bayesian copula per table, latent segments across tables, exact keys and links, and a Dirichlet-process law for the number of elements per parent.' status: supported evidence: - code: src/relsynth/documents/relational.py::spec_for - test: tests/unit/test_documents.py::test_document_synthesizer_end_to_end - id: DOC-10 doc: docs/documents.md area: structure quote: - Every synthetic document is made to validate against the schema (Prop. 1402). - A value that breaks it (an array outside the source's length range, a repeated element in a set, a field outside its variant, a missing required field) is replaced by a value of another synthetic document at the same path, never by a source value. - Every repair is counted by kind and path in the report (repairs); many repairs of one kind say the model misses a dependence. status: supported note: '"many repairs of one kind say the model misses a dependence" is guidance for reading the report, not a tested property.' evidence: - prop: prop:docconform - check: C1402 - test: tests/unit/test_documents.py::test_conform_repairs_every_violation - code: src/relsynth/documents/conform.py::conform - id: DOC-11 doc: docs/documents.md area: statistics quote: '| signature | TVD of the field sets of the documents (which top-level fields a document holds together) |' status: supported evidence: - code: src/relsynth/documents/metrics.py::shape_distances - test: tests/unit/test_documents.py::test_shape_metrics_and_limits - id: DOC-12 doc: docs/documents.md area: statistics quote: Each limit is the one-sided 99% prediction bound of the same distance between random halves of the real documents (--splits, default 10), rescaled to the compared sizes and to the variance of a posterior predictive draw (Props. 109, 1204 and 1403), the method of the quality report 2.0. status: supported evidence: - prop: prop:doclimits - check: C1403 - test: tests/unit/test_documents.py::test_shape_metrics_and_limits - id: DOC-13 doc: docs/documents.md area: structure quote: Validity (share of documents that validate) and references (share of synthetic reference values that resolve) must be 1. status: supported evidence: - prop: prop:docrefs - check: C1405 - code: src/relsynth/documents/pipeline.py::DocumentSynthesizer::report - id: DOC-14 doc: docs/documents.md area: structure quote: 'For each key attribute it learns a template (PK = CUSTOMER#{customer_id}, SK = ORDER#{ordered_at}#{order_id}): every #-separated part is a constant or one attribute of the item.' status: supported evidence: - prop: prop:dockeys - check: C1404 - test: tests/unit/test_documents.py::test_single_table_split_merge - id: DOC-15 doc: docs/documents.md area: security quote: relsynth.documents.connectors.profile_with_pushdown(con, collection, sample_size) computes the exact counts in the database with one $group pipeline (documents per $type per path and the $size histogram of every array) and reads only a $sample of documents for the rest of the profile. status: supported evidence: - code: src/relsynth/documents/connectors.py::profile_with_pushdown - test: tests/unit/test_documents.py::test_mongodb_pushdown_pipeline - test: tests/unit/test_documents.py::test_mongodb_live_pushdown - id: DOC-16 doc: docs/documents.md area: scope quote: 'benchmarks/documents/run_documents.py runs the whole path on the MongoDB sample datasets (downloaded, never committed: their licence is to be checked before redistribution) and on StackExchange dumps (CC BY-SA 4.0; questions with their answers and comments embedded, linked to users).' status: supported evidence: - code: benchmarks/documents/run_documents.py::stackexchange_set - report: docs/datasets.md - id: DOC-17 doc: docs/documents.md area: scope quote: '- A whole collection is shredded in memory; the pushdown profile bounds what is read for inference only.' status: limitation evidence: - report: docs/adr/0011-document-databases.md - code: src/relsynth/documents/pipeline.py::DocumentSynthesizer::build_spec - id: RM-40 doc: README.md area: structure quote: Collections are shredded into tables (one per array, map and optional sub-document, with tag and length columns), synthesised by the relational engine and nested again; nest(shred(D)) = D exactly (Prop. 1401). status: supported evidence: - claim: DOC-08 - claim: DOC-09 - id: RM-41 doc: README.md area: structure quote: Every synthetic document validates against the inferred schema and references resolve; what had to be repaired is counted per kind and path. status: supported evidence: - claim: DOC-10 - claim: DOC-13 - id: RM-42 doc: README.md area: statistics quote: '- Shape metrics (presence, type mix, array lengths, field sets, depth, size) with limits calibrated on real-versus-real splits.' status: supported evidence: - claim: DOC-11 - claim: DOC-12 - id: RM-43 doc: README.md area: security quote: '- In MongoDB, exact counts come from one $group pipeline and the rest from a $sample.' status: supported evidence: - claim: DOC-15 - id: RM-44 doc: README.md area: scope quote: '| Documents (v2.1) | 1400–1405 the inferred schema admits its source; shredding is invertible; conformance; calibrated shape limits; key templates; references and lengths |' status: supported evidence: - prop: prop:docschema - prop: prop:docshred - prop: prop:docconform - prop: prop:doclimits - prop: prop:dockeys - prop: prop:docrefs - id: AGT-01 doc: docs/agents.md area: governance quote: - 'People make every decision: which answers to accept, what to release, which epsilon to spend, which policy exception to grant.' - In the service an agent account can read and propose and nothing else; in the governance registry an agent key cannot sign an approval. status: supported evidence: - prop: prop:agauthority - check: C1604 - test: tests/unit/agents/test_agents.py::test_apply_decisions_needs_a_person_and_rebuilds_changes - test: tests/unit/service/test_service_agents.py::test_agent_account_reads_and_proposes_and_nothing_else - test: tests/unit/service/test_service_agents.py::test_agent_cannot_approve_and_explains_only_what_the_caller_may_read - test: tests/unit/governance/test_governance.py::test_agent_keys_cannot_approve - id: AGT-02 doc: docs/agents.md area: structure quote: - '| review | an introspected draft and its source | one answer per review: item: a verdict, the evidence measured on every row, an explanation, and the change it implies | the spec owner accepts or rejects each answer |' - 'The review agent checks each inference on every row of the table and answers:' - '| confirm | the inference holds on every row |' status: supported evidence: - prop: prop:agevidence - check: C1602 - test: tests/unit/agents/test_agents.py::test_review_toy_against_catalog - test: tests/unit/agents/test_agents.py::test_evidence_gate_refuses_confirmation_of_a_false_inference - id: AGT-03 doc: docs/agents.md area: governance quote: '| draft policy | a spec | an organisation policy with the privacy unit and source filled in and every budget empty | the privacy owner sets the budgets and signs it |' status: supported evidence: - test: tests/unit/agents/test_agents.py::test_drafts_leave_epsilon_to_the_privacy_owner - test: tests/unit/agents/test_injection.py::test_policy_and_contract_drafts_ignore_injected_descriptions - id: AGT-04 doc: docs/agents.md area: structure quote: Answers that need a person's attention (every reject, answer, unsure and privacy item, and any answer below 0.8 confidence or with a flag) are marked. status: supported evidence: - test: tests/unit/agents/test_injection.py::test_compromised_model_cannot_break_out - test: tests/unit/agents/test_agents.py::test_review_toy_against_catalog - id: AGT-05 doc: docs/agents.md area: security quote: - A server that is not on the local machine is refused unless allow_remote is set, must use https, and then gets the aggregates scope at most. status: supported evidence: - prop: prop:agscope - test: tests/unit/agents/test_agents.py::test_backend_configuration - test: tests/unit/agents/test_agents.py::test_remote_model_never_gets_values - id: AGT-06 doc: docs/agents.md area: privacy-measured quote: An agent never sees source rows. status: supported note: Proved for the code paths of the agents (Prop. 1600) and tried on random databases (C1600); an agent's model server is outside relsynth and sees only what the prompt carries. evidence: - prop: prop:agscope - check: C1600 - test: tests/unit/agents/test_agents.py::test_review_reads_no_rows_into_output - test: tests/unit/agents/test_agents.py::test_values_scope_shows_only_frequent_values - id: AGT-07 doc: docs/agents.md area: security quote: - 'The safeguards assume the worst case: a model that follows every instruction an attacker planted in a column name, a data value, a spec description or a report field' - The guard builds the change from the item, never from model text, and rebuilds it again when a person accepts it. status: supported evidence: - prop: prop:agguard - check: C1601 - test: tests/unit/agents/test_injection.py::test_compromised_model_cannot_break_out - test: tests/unit/agents/test_injection.py::test_compromised_model_cannot_apply_anything - id: AGT-08 doc: docs/agents.md area: structure quote: A confirmation that the evidence on every row contradicts (duplicates in a key, values without a parent, rule violations) is turned into a rejection. status: supported evidence: - prop: prop:agevidence - check: C1602 - test: tests/unit/agents/test_agents.py::test_evidence_gate_refuses_confirmation_of_a_false_inference - id: AGT-09 doc: docs/agents.md area: security quote: Explanations are plain text without links, images, HTML or addresses, and every number in a model's text must occur in the evidence; otherwise the guard's own wording is used. status: supported evidence: - prop: prop:aggrounded - check: C1603 - check: C1601 - test: tests/unit/agents/test_agents.py::test_explain_reports_are_grounded - test: tests/unit/agents/test_injection.py::test_compromised_model_cannot_word_an_explanation - id: AGT-10 doc: docs/agents.md area: security quote: The prompt-injection suite (tests/unit/agents/test_injection.py) places 20 payloads in names, values, review lines, descriptions and report fields and runs them against a backend that obeys every instruction; the checks C1600–C1604 try random model outputs and random databases. status: supported evidence: - test: tests/unit/agents/test_injection.py::test_detector_flags_every_payload - test: tests/unit/agents/test_injection.py::test_compromised_model_cannot_break_out - check: C1600 - check: C1601 - check: C1602 - check: C1603 - check: C1604 - id: AGT-11 doc: docs/agents.md area: security quote: - It holds that role only, whatever else it is given, and may read projects, specs, runs, reports and proposals and ask for reviews and explanations. - Approvals, runs, specs, connections, service accounts and the audit log are refused to it, and each refusal is audited. - OIDC tokens cannot claim the agent role. status: supported evidence: - test: tests/unit/service/test_service_agents.py::test_agent_account_reads_and_proposes_and_nothing_else - test: tests/unit/service/test_service_agents.py::test_oidc_tokens_cannot_claim_the_agent_role - id: AGT-12 doc: docs/agents.md area: governance quote: - 'Every agent action is recorded with the agent''s own identity, the person it acted for, the backend and model, the scope, and digests of its inputs and output:' - 'in a governance registry (--registry --key on the command line, with a key trusted with the role agent): a signed in-toto statement of type agent-action/1, covered by the registry''s hash chain;' status: supported evidence: - test: tests/unit/agents/test_agents.py::test_signed_action_records_and_manifest_entry - test: tests/unit/service/test_service_agents.py::test_review_proposals_and_a_persons_decision - id: AGT-13 doc: docs/agents.md area: statistics quote: - 'python -m bench.agents drafts each database with its catalog hidden (benchmark mode), lets the review agent answer, and compares every answer about a key or a link with ground truth the agent does not see: the declared catalog of Chinook, AdventureWorks DW and Contoso, and the hand-reviewed spec of bi/COREP.' - Accuracy on answered items is 122/124 = 0.984 (Wilson 95% interval 0.943–0.996), with 98.4% of items answered. - 'Both wrong answers are links that hold on every row but that the ground truth leaves out:' - A reviewer who accepts only the answers not flagged for attention would accept the second one, which adds no change to the spec because the draft already holds the link. - Of 92 lookups, derive, check and aggregate rules, constants, dates and key orders across the four databases, 90 held on every row. status: supported note: Measured with the rules backend on 2026-11-01; the model backend is measured in the CI job agents, not claimed. Sampling in introspection can change the draft's items between runs. evidence: - report: docs/assurance/evidence/T16/agents-summary-rules.json - report: docs/assurance/evidence/T16/agents-adventureworks-rules.json - report: docs/assurance/evidence/T16/agents-bi_corep_lr-rules.json - report: docs/assurance/evidence/T16/agents-chinook-rules.json - report: docs/assurance/evidence/T16/agents-contoso_sqlite-rules.json - id: AGT-14 doc: docs/agents.md area: scope quote: - Rule items have no independent ground truth, because the agent measures them on every row. - 'Links that introspection did not ask about cannot be answered: on the toy test database customer.support_rep is missing from the draft, and the evaluation reports such links separately.' - The agents do not judge whether a column holds personal data; that stays a privacy decision. status: limitation evidence: - test: tests/unit/agents/test_agents.py::test_review_toy_against_catalog - check: C1601 - id: AGT-15 doc: README.md area: structure quote: - 'review answers the review: items of an introspected draft (keys, links, lookups, rules, ASK questions) with evidence measured on every row, and proposes the change each answer implies.' - relsynth agent apply --by applies the answers that person accepts. - explain words a quality, privacy or scorecard report in plain language; doctor turns doctor findings into proposals (privacy settings become advice for the privacy officer); draft contract and draft policy write an ODCS contract and an organisation policy with every budget left empty. status: supported evidence: - claim: AGT-02 - claim: AGT-03 - test: tests/unit/agents/test_agents.py::test_doctor_proposals_hold_back_privacy_fixes - test: tests/unit/agents/test_agents.py::test_cli_review_apply_explain_draft - id: AGT-16 doc: README.md area: security quote: - 'Agents never see rows: they get names and whole-column aggregates, and single values only in the values scope when they occur at least 10 times.' - In the service an agent account can read and propose only, and in the governance registry an agent key cannot approve. - A prompt-injection suite runs 20 payloads against a model that obeys every instruction. status: supported evidence: - claim: AGT-06 - claim: AGT-01 - claim: AGT-10 - id: AGT-17 doc: README.md area: statistics quote: With the catalog hidden, the rules backend's answers about keys and links matched the ground truth on 122 of 124 answered items on Chinook, AdventureWorks DW, Contoso and bi/COREP status: supported evidence: - claim: AGT-13 # ------------------------------------------------------------------ README.md, release 2.1 summary - id: RM-45 doc: README.md area: scope quote: "Release 2.1 adds document databases (MongoDB, Cosmos DB, DynamoDB, Couchbase, JSON), row-conditioned synthetic text with differentially private methods, a PII layer and a leakage audit, agents that draft and explain while people decide, two-way marginals (MST, AIM) in DP mode, a claim register and assurance case for privacy, legal and audit teams, and wider security scanning with fixes for every finding." status: partial note: Summary of the 2.1 guides. "Every finding" means the CodeQL, Semgrep and Dependabot findings open when 2.1.0 was cut, apart from the cyclic-import notes left as a follow-up (CHANGELOG 2.1.0, Security). evidence: - claim: DOC-08 - claim: RM-37 - claim: RM-38 - claim: AGT-02 - check: C1300 - check: C1302 - test: tests/unit/test_assurance.py::test_claim_register_traces_every_claim - report: docs/development/ci.md#security-scanning - id: RM-46 doc: README.md area: privacy-formal quote: "A text column is generated from its row (a billing ticket talks about billing) by a word-level model, a local language model or Private Evolution, optionally with user-level differential privacy; personal data is replaced by typed placeholders and the output is audited for leaks (relsynth text)." status: supported evidence: - claim: RM-37 - claim: RM-38 - prop: prop:txpii - prop: prop:txconsist - check: C1506 - check: C1507 - test: tests/unit/test_textgen.py::test_redact_and_fill_round_trip - test: tests/unit/test_textgen.py::test_consistency_detects_shuffled_text - id: RM-47 doc: README.md area: security quote: "Every pull request also runs CodeQL (security-extended and security-and-quality suites), Semgrep with project rules, gitleaks, zizmor for the workflows and GitHub dependency review; Dependabot watches dependencies and the digest-pinned base images (docs/development/ci.md)." status: supported evidence: - report: docs/development/ci.md#security-scanning - report: .github/workflows/codeql.yml - report: .github/workflows/security.yml - report: .github/dependabot.yml - test: tests/unit/test_scanner_fixes.py::test_urlopen_refuses_other_schemes - id: RM-60 doc: README.md area: privacy-measured quote: "By default relsynth run ends with a quick membership check on a proxy release and prints one privacy line (except in DP mode, or when it is skipped: see docs/privacy.md); it never changes the synthetic files." status: supported evidence: - claim: PRV-42 - test: tests/unit/test_privacy_quick.py::test_run_adds_the_check_and_keeps_the_release_identical - id: RM-61 doc: README.md area: privacy-measured quote: "| Quick privacy check (v2.2) | 1740–1741 false alarms of the quick check are bounded under no membership signal; the check leaves the release unchanged |" status: supported evidence: - prop: prop:qcnull - prop: prop:qcident - check: C1740 - check: C1741 not_claims: - {doc: docs/privacy.md, quote: "Every key is optional.", reason: "describes the configuration file format"} - {doc: docs/privacy.md, quote: "Viswanath, The composition theorem for differential privacy, ICML 2015.", reason: "bibliography entry"} - {doc: docs/security.md, quote: "Pass secrets as masked CI variables (see cicd.md), and use read-only database accounts for sources.", reason: "advice to the operator"} - {doc: README.md, quote: "| A privacy officer or data protection officer |", reason: "reading guide: points to the documents"} - {doc: README.md, quote: "| Reviewing the method |", reason: "reading guide: points to the documents"} - {doc: README.md, quote: "- Privacy, differential privacy and governance", reason: "table of contents"} - {doc: README.md, quote: "- Theory: propositions and proofs", reason: "table of contents"} - {doc: README.md, quote: "Never put a password on the command line.", reason: "advice to the operator (the enforced part is RM-14)"} - {doc: README.md, quote: "A job that refreshes a test database every night and fails when quality drops:", reason: "introduces an example pipeline"} - {doc: README.md, quote: "Differential privacy (relsynth run --epsilon E, relsynth[dp], docs/dp.md).", reason: "bullet heading; the bullet's claims are RM-03 and RM-23"} - {doc: README.md, quote: "| docs/dp.md | Differential privacy mode: the guarantee, the two stages, MST/AIM two-way marginals, utility against ε |", reason: "documentation map: names a document's contents"} - {doc: README.md, quote: "| docs/governance/guide.md | Registry, signed manifests, policy, budget ledger, approvals (design) |", reason: "documentation map: names a document's contents"} - {doc: README.md, quote: "| docs/theory.md | The model in brief and the v1.0 propositions with proofs |", reason: "documentation map: names a document's contents"} - {doc: README.md, quote: "| docs/proofs/ | The full proofs (PDF and LaTeX source) |", reason: "documentation map: names a document's contents"} - {doc: docs/agents.md, quote: 'This guide covers what each agent does, the safeguards, the command line, the service, and how accurate the agents are on the four real databases', reason: introduces the guide} - {doc: docs/agents.md, quote: 'Review answers. relsynth introspect lists every inference it made from a sample under review:', reason: describes introspect's output format (the review list)} - {doc: README.md, quote: '- Agents: drafting and explaining, never deciding', reason: table of contents}