hypgraph/1 · draft 1

The record format

Everything needed to write another implementation, a verifier or a viewer of HypGraph records.

Status: draft 3 (HypGraph 0.3). Licence: CC BY 4.0. This page is enough to write another implementation, a verifier or a viewer.

1. Model

A record is a set of nodes (typed, versioned), edges (typed links between two nodes), withdrawals, retractions (of edges), leases (who is working on a node) and events. Every table is append-only: a correction is a new row, never an edited one.

  • A node has an id, a type, a display code (H1, CL3 …, unique per type within its investigation), the

investigation it belongs to (inv, empty for an investigation itself), created_at and created_by.

  • Its content lives in versions (node, v, at, by, reason, body). body is the canonical JSON (§3) of the

node's fields. The current version is the highest v.

  • An edge records the version of each end when it was made (src_v, dst_v). A review or decision is about that

version.

2. Vocabulary

typecodefields (* required)what it is
investigationINVtitle\, objective\, success_criteria, scope, state, reviewers, constants, strict_claims, keywords, projects, doi, note, legacy_caseThe study: an objective, its scope, who reviews it. Every other node belongs to one.
questionQtext*, whySomething you want to know.
hypothesisHstatement\, assumptions, predictions\, distinguishing, open_questionsA possible answer, with the predictions that would show it wrong.
claimCLstatement*, scope, limitations, kind, measurements, evidence_note, exposure, exposure_unit, legacyWhat you assert is known, with its scope and its numbers bound to results.
experimentXaim\, protocol\, controls, success_criteria*, method, budget, power_check, cost_minutes, seedA planned test: aim, protocol, controls, success criteria, and what each hypothesis expects.
runRUNkind*, job_id, name, state, code, inputs, outputs, args, config, seed, env, started, finished, exit_code, log_sha256, noteOne execution: code, inputs and outputs hashed, seed, state. kind=lab only from an instrument.
resultRSsummary*, kind, outcome, values, expected, limitations, power, evidence_note, legacyWhat a run (or an observation, or a citation) found: values, outcome, power.
artifactAsha256*, path, size, formatExact bytes, by sha256 — data, code, a model, a figure.
sourceScitation*, doi, url, passage, retrieved, saysOutside literature or data: citation, DOI, the passage that says it.
reviewRVverdict\, method\, notes*, officialA verdict on one version of a claim, result, run, experiment or hypothesis.
decisionDchoice\, alternatives, reasons\, revisit_if, meetingA choice made on the evidence; flagged when what it was based on changes.
taskTkanban_id*, titleA unit of work in your tracker, linked to what it works on.
relationfromtomeaning
part_ofany but investigationinvestigation, questionbelongs to an investigation or question
answersclaim, hypothesisquestionoffers an answer to a question
alternative_tohypothesishypothesiscompetes with another hypothesis
testsexperimentclaim, hypothesisan experiment tests a hypothesis or claim (data.expected: what it should show if true)
executesrunexperimenta run carries out an experiment
producedrunartifact, resulta run produced a result or artifact
usesrunartifacta run used an artifact (data.role: data / code / model / config)
derived_fromartifact, claim, resultartifact, result, run, sourcecomputed or taken from
supportsclaim, result, sourceclaim, hypothesisevidence for (why required)
challengesclaim, result, sourceclaim, hypothesisevidence against (why required)
requiresclaim, hypothesisclaimrests on another claim as a premise
replicatesrunruna run repeats another (data.differs: same_code / new_seed / new_impl / new_data / new_env)
reviewsreviewclaim, experiment, hypothesis, result, runa review of a node (written by review())
supersedesany but artifact, review, investigationthe same typereplaces an older node of the same type
correctsclaim, decision, resultthe same typefixes an error in a node of the same type
motivated_byexperiment, hypothesis, questionanyprompted by
citesclaim, experiment, hypothesis, resultsourcecites a source
based_ondecisionclaim, hypothesis, resulta decision rests on this evidence (pinned to its version)
works_ontaskany but task, artifact, reviewa task works on a node
blocked_bytasktaska task waits on another task

Field kinds and lengths: hypgraph schema --json (or /schema/hypgraph-1.json on the site). A claim's measurements are {name, value | k & n | lo & hi, result: <result id>, key: <field of that result's values>}.

Verdicts: positive checked, pass, reproduced, replicated, separate_implementation, generalizes; negative fail, not_reproduced, not_replicated, does_not_generalize; and objection, resolved, inconclusive, comment.

3. Canonical JSON and the event chain

canon(x) is JSON with keys sorted at every level, separators , and : with no spaces, and non-ASCII characters written as-is (UTF-8). In Python: json.dumps(x, sort_keys=True, ensure_ascii=False, separators=(",", ":")).

Every write appends one or more events. An event's hash is

hash = sha256_hex( canon({"at", "actor", "kind", "ref", "payload", "prev"}) )

where prev is the previous event's hash (64 zeros for the first). Kinds and payloads:

kindrefpayload
createthe new node{type, code, inv, v: 1, body_sha}; body_sha = sha256 of the version's stored body string
revisethe node{v, reason, body_sha}
reviewthe reviewed node{review: <review node id>, verdict, on_v, official, body_sha}
linkthe edge's source{edge, rel, dst, src_v, dst_v, data_sha}; data_sha = sha256 of the stored edge data string
retractthe edge's source{edge}
withdrawthe node{why}
lease_take / lease_releasethe node{task, expires}

Any event may carry task in its payload: the unit of work that wrote it.

Verifying a record means recomputing every hash in order and checking each prev. Then check that every stored version body and edge matches the body_sha / data_sha of the event that wrote it, and that no row exists without its event.

4. Bundles — hypgraph/1-bundle

A portable record. Every event is carried as the exact string that was hashed (body), and every version body and edge data as the exact stored string. A verifier therefore never has to reproduce canonical JSON: it hashes strings and compares.

{"format": "hypgraph/1-bundle", "generator": "hypgraph 0.1.0", "exported_at": "…",
 "complete": true, "redacted_events": 0, "scope": {"inv": "", "title": "whole graph"},
 "policy": {"reviewers": [], "instrument": "lab", "operator": "operator", "importer": "importer", "system": "system"},
 "head": "<hash of the last event>",
 "events": [{"seq": 1, "hash": "…", "body": "{\"actor\":…,\"at\":…,\"kind\":…,\"payload\":{…},\"prev\":…,\"ref\":…}"}],
 "nodes": [{"id": "…", "type": "…", "code": "…", "inv": "…", "created_at": "…", "created_by": "…"}],
 "versions": [{"node": "…", "v": 1, "at": "…", "by": "…", "reason": "", "body": "<stored canonical string>"}],
 "edges": [{"id": 1, "src": "…", "src_v": 1, "rel": "…", "dst": "…", "dst_v": 1, "data": "<stored string>", "…": "…"}],
 "withdrawals": [], "retractions": [], "leases": [],
 "derived": {"…": "standing at export time, for viewers; never trusted by a verifier"}}

A scoped bundle (one investigation out of a shared record) replaces events outside it with {"seq", "hash", "prev", "redacted": true}. The chain still links, but the content of those events can't be checked, so a change hidden among them would not show. Verifiers must report complete: false. To publish a study, keep it in a record of its own.

Anchoring. A bundle proves only its own consistency. To prove it wasn't rebuilt from scratch, publish its head hash somewhere independent at the time (a paper, a preregistration, a repository tag). A later bundle whose chain passes through that hash can't have rewritten anything before it.

5. Derived standing (normative)

Implementations must derive these, never store them:

  • lifecycle: withdrawn if withdrawn; superseded if a live node of the same type supersedes / corrects

it; an investigation's own state; else active.

  • review: computed over reviews by the board (the policy's reviewers ∪ the investigation's reviewers ∪ the

operator and importer), excluding comments. An objection stays open until its author records resolved. The operator, the importer and the investigation's reviewers may resolve anyone's. Then, on the current version: any negative verdict or open objection → contested; else any positive verdict → checked; else a positive verdict only on an older version → needs_revalidation; else unchecked.

  • fallen: withdrawn; or a contested claim / result / hypothesis; or a run whose state is failed / timeout /

cancelled / interrupted; or a result whose outcome is failed. Superseded is not fallen.

  • at_risk: the fallen nodes reachable upstream through requires, derived_from, based_on and the run that

produced it.

  • independent lines: live supporters, grouped when their roots overlap. Roots are producing runs, the data /

model artifacts those runs use, and sources. Supporters with no provenance at all form one group between them.

  • evidence: the best live supporter's profile, ranked `unverified < agent_reported < lab_recorded <

project_rerun < separate_implementation < new_data < external`.

    • A source is external.
    • A result produced by a kind=lab run that the instrument filed is lab_recorded.
    • A replication edge on the run raises the profile according to its differs.
    • A result of kind=cited is external only if it cites a source node.
  • basis_changed (decisions): a based_on edge whose target's version has moved past the pinned dst_v.

6. Write rules (normative for writers)

  • supports / challenges need a why of at least 10 characters. tests needs data.expected. replicates

needs data.differs. uses needs data.role.

  • Only the instrument files a kind=lab run, and only the instrument records what that run produced.
  • Claims must pass the checks in §7, unless written by the operator or importer, or in an investigation with

strict_claims: false.

  • Nobody officially reviews their own node.
  • Writes into an archived or published investigation are refused (reserved authors excepted).
  • Only the importer may set a write's time. Everyone else's comes from the writer's clock.

7. Claim checks

A claim is checked before it enters the record. Each failed rule is reported with a stable code, a message that does the arithmetic, a suggested fix, and the words that triggered it. Checks are skipped for the operator and importer, and in an investigation with strict_claims: false. Leads and conventions skip rules 7–9.

Arithmetic. For a count of k in n trials, the 95% interval is the Wilson score interval. At k = 0 (or k = n) it is the exact one-sided bound 1 − 0.05^(1/n), the "rule of three" (≈ 3/n). "Never, in n trials" means at most that rate, and never zero. Two counts are compared with Newcombe's hybrid score interval and a pooled two-proportion z-test. The smallest detectable difference uses 80% power at two-sided α = 0.05.

#CodeRefused when
1SCOPE_MISSINGa finding or exclusion has a scope shorter than 10 characters
2EVIDENCE_MISSINGa finding, exclusion or inconclusive claim has no supporting node
3MEASUREMENT_MALFORMED / MEASUREMENT_UNKNOWN_FIELD / MEASUREMENT_MISMATCHa measurement doesn't name a result and a key present in its values, or states a value / k / n different from the result's at the precision written (a fraction ≤ 1 may be written as a percentage)
4UNBOUND_NUMBERa number in the statement (digits, or the words two to twenty; digits in quotes are names) is not one of: a bound measurement's numbers; for a count, its rate, complement or n − k; for any two measurements, their difference, ratios, relative change or sum; the scope's numbers; the investigation's constants; or the conventions 95 / 99 / 90 / 80 / 0.05 / 0.01 when the statement speaks of intervals, bounds, confidence, power or significance. Statistical quantities (a rate's 95% bounds; for two counts the difference interval, p-value and detectable difference) count only when written to at least one decimal place.
5ABSOLUTE_WITHOUT_COUNTan absolute word (never, always, all, every, none, impossible, cannot, proves, certainly, guaranteed, eliminates, entirely, completely, universally, consistently, perfect, solves, zero, 100%, …) with no bound count where k is 0 or n
5aABSOLUTE_UNRELATED_COUNTthe counts with k ∈ {0, n} share no content word with the statement in their name or key
5bABSOLUTE_UNDERPOWEREDthe 95% bound on the opposite rate is above 5% and the statement doesn't say n ("never leaks" rather than "no leaks in 50 prompts" — a statement that gives its n is scoped to what was seen, and accepted). The message gives the honest wording and the trials needed for a 1-in-1,000 bound.
5cABSOLUTE_HIDES_DENOMINATORthe count is enough, but n appears in neither the statement nor the scope
6CONVENTION_HEDGEDa convention hedges (could, might, suggests, consistent with, …)
7COMPARATIVE_WITHOUT_COMPARISONa comparative (better, beats, outperforms, higher than, improves, exceeds, …) with fewer than two measured sides, no bound p-value or interval, and no supporting claim that binds two sides
8SIGNIFICANCE_WITHOUT_TEST / SIGNIFICANCE_NOT_SHOWN"significantly" / "statistically" with no bound p-value and fewer than two counts, or when the bound p-value, or the test the record runs on the first two counts, is not below 0.05
9CAUSAL_WITHOUT_INTERVENTIONa causal word (causes, leads to, results in, drives, due to, because of, responsible for, …) when no supporting result was produced by a run that executes an experiment
10EXCLUSION_WITHOUT_POWERkind: exclusion, or wording that says an effect is absent (no effect, no difference, makes no difference, does not affect, rules out, ineffective, …) in a finding, without a supporting result with power: demonstrated or one produced by a run of an experiment with a power_check. With two counts, the message gives the smallest detectable difference.

Exposure. A claim may set exposure (how many uses, users or units it will be applied to) and exposure_unit. Absolute refusals then state how many failures the bound leaves unruled out at that scale. An accepted claim's standing carries bounds (per count: k, n, rate, lo, hi and, with exposure, unruled_out), its sentence reads "'never' = at most 0.1% (0 of 3,000, 95%), up to 998 of 1,000,000 not ruled out", and a "significantly" claim carries test (the difference, its interval and p).

Blast radius. Withdrawing a node, or a verdict that makes it fall, returns blast_radius: every node resting on it by type, with claims and decisions named and, if a spend meter is connected, the cost of the work behind them.

8. The refusal log

A refused claim never enters the record, so its trace lives in a separate append-only table, refusal_log, with its own SHA-256 chain over canon({at, kind, author, inv, node, op, statement, codes, detail, ref, prev}). The record's own chain and verify() are unaffected.

  • kind: refused: the author, the investigation, the statement (≤ 1,500 characters), the sorted codes, and the

problems (code, message, fix) it was refused with. op is create or revise; node is set for a revision.

  • kind: fixed: a later claim or revision by the same author in the same investigation, accepted within 24 hours,

that says the same thing. "The same" means the same node for a revision; otherwise a Jaccard overlap ≥ 0.3 of the statements' content words. ref is the refusal it fixes.

A report aggregates per author (refused, fixed, open, codes) and per rule.

9. Checking a write-up against the record

A checker reads markdown sentence by sentence and checks it against the record. Code blocks, inline code, URLs, link targets and HTML comments are skipped. Quoted passages and negated words ("cannot be proven") are not treated as the writer's assertion. Codes are matched within the investigation being reported on.

levelcodewhen
errorREPORT_CITES_WITHDRAWNa sentence cites a withdrawn node and doesn't say so (withdrawn, retracted, failed, superseded, earlier, …)
errorREPORT_OVERSTATES_STANDINGsettled language (confirmed, established, shown, proven, holds, …) about a claim or hypothesis that is unchecked, stale, contested or at risk, or a result that is contested or at risk
errorREPORT_UNKNOWN_CODEan INV / RUN / CL / RS / RV code the investigation doesn't have
errorREPORT_ABSOLUTE_UNBACKEDnever / always / impossible / guaranteed / eliminates / proven / 100% asserted, not in an instruction, with no cited claim carrying a count with k ∈ {0, n}
errorREPORT_ABSENCE_UNBACKED"no effect" / "rules out" / "doesn't matter" without citing a claim filed as exclusion or inconclusive
errorREPORT_SIGNIFICANCE_UNBACKED"significantly" without a p-value or interval in the sentence and without citing a node that carries a test (a negated "not significant" is a warning)
warningREPORT_UNSOURCED_NUMBERa number not contained in or implied by the investigation's results, live claims' measurements, statements and scopes, the investigation's scope and constants, or experiment and run settings. Integers ≤ 12, years, dates, times and 7-digit+ ids are skipped. Statistical quantities need one decimal.
warningREPORT_OMITS_DISPUTEcites a contested or at-risk node without saying so
warningREPORT_CITES_SUPERSEDEDcites a superseded node without saying so
warningREPORT_CAUSAL_UNCITEDcausal language with no node cited
noteREPORT_BOUNDan absolute backed by a cited count: what the count supports

Calibrated on 24 real write-ups from PegasusDMZ against its live record.