hypgraph 0.7.3__tar.gz → 0.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {hypgraph-0.7.3 → hypgraph-0.7.4}/CHANGELOG.md +15 -0
  2. {hypgraph-0.7.3 → hypgraph-0.7.4}/PKG-INFO +1 -1
  3. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/__init__.py +1 -1
  4. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/derive.py +19 -1
  5. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/graph.py +14 -3
  6. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/schema.py +19 -0
  7. hypgraph-0.7.4/tests/test_bound_support.py +55 -0
  8. hypgraph-0.7.4/tests/test_relation_words.py +38 -0
  9. {hypgraph-0.7.3 → hypgraph-0.7.4}/.gitignore +0 -0
  10. {hypgraph-0.7.3 → hypgraph-0.7.4}/AGENTS.md +0 -0
  11. {hypgraph-0.7.3 → hypgraph-0.7.4}/CITATION.cff +0 -0
  12. {hypgraph-0.7.3 → hypgraph-0.7.4}/LICENSE +0 -0
  13. {hypgraph-0.7.3 → hypgraph-0.7.4}/NOTICE +0 -0
  14. {hypgraph-0.7.3 → hypgraph-0.7.4}/README.md +0 -0
  15. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/PLAN.md +0 -0
  16. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/decisions/ADR-001-token-bound-authors.md +0 -0
  17. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/decisions/ADR-002-governance-profiles.md +0 -0
  18. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/profile-workflows-plan.md +0 -0
  19. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/review-2026-10-24.md +0 -0
  20. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/site-plan.md +0 -0
  21. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/spec.md +0 -0
  22. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/ui-plan.md +0 -0
  23. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/workflows/README.md +0 -0
  24. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/workflows/knowledge.md +0 -0
  25. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/workflows/optimization.md +0 -0
  26. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/workflows/scientific.md +0 -0
  27. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/workflows/software.md +0 -0
  28. {hypgraph-0.7.3 → hypgraph-0.7.4}/docs/workflows/trading.md +0 -0
  29. {hypgraph-0.7.3 → hypgraph-0.7.4}/pyproject.toml +0 -0
  30. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/__main__.py +0 -0
  31. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/bundle.py +0 -0
  32. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/checks.py +0 -0
  33. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/cli.py +0 -0
  34. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/coord.py +0 -0
  35. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/demos/__init__.py +0 -0
  36. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/demos/broad_street.py +0 -0
  37. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/language.py +0 -0
  38. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/mcp.py +0 -0
  39. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/plan.py +0 -0
  40. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/policy.py +0 -0
  41. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/profiles.py +0 -0
  42. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/refusals.py +0 -0
  43. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/report_check.py +0 -0
  44. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/server.py +0 -0
  45. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/skill/SKILL.md +0 -0
  46. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/stats.py +0 -0
  47. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/store.py +0 -0
  48. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/__init__.py +0 -0
  49. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/atlas.css +0 -0
  50. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/atlas.js +0 -0
  51. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/hypgraph.css +0 -0
  52. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/hypgraph.js +0 -0
  53. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/hypgraph.svg +0 -0
  54. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/index.html +0 -0
  55. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/vendor/LICENSES.md +0 -0
  56. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/vendor/cose-base.js +0 -0
  57. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/vendor/cytoscape-fcose.js +0 -0
  58. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/vendor/cytoscape.min.js +0 -0
  59. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/ui/vendor/layout-base.js +0 -0
  60. {hypgraph-0.7.3 → hypgraph-0.7.4}/src/hypgraph/workflow.py +0 -0
  61. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_accept_work_kind.py +0 -0
  62. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_bundle_telemetry.py +0 -0
  63. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_cancel_releases_lease.py +0 -0
  64. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_coord.py +0 -0
  65. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_d10.py +0 -0
  66. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_engine.py +0 -0
  67. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_engine_fixes.py +0 -0
  68. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_incremental.py +0 -0
  69. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_link_translate.py +0 -0
  70. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_log_and_check.py +0 -0
  71. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_measure_questions.py +0 -0
  72. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_opposed_answers.py +0 -0
  73. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_option_lead.py +0 -0
  74. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_order_detail.py +0 -0
  75. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_profiles.py +0 -0
  76. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_profiles_041.py +0 -0
  77. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_questions_not_assertions.py +0 -0
  78. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_refusal_friction.py +0 -0
  79. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_refusal_results.py +0 -0
  80. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_refusals.py +0 -0
  81. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_report_answers.py +0 -0
  82. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_rerunnable.py +0 -0
  83. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_restates_scale.py +0 -0
  84. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_server_auth.py +0 -0
  85. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_shape_refusals.py +0 -0
  86. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_stats_not_backtests.py +0 -0
  87. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_stats_values.py +0 -0
  88. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_surfaces.py +0 -0
  89. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_telemetry_stream.py +0 -0
  90. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_ui_app.py +0 -0
  91. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_ui_engine.py +0 -0
  92. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_uncorrected_effect.py +0 -0
  93. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_unnamed_inputs.py +0 -0
  94. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_versions_not_figures.py +0 -0
  95. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_vocabulary_friction.py +0 -0
  96. {hypgraph-0.7.3 → hypgraph-0.7.4}/tests/test_workflows.py +0 -0
@@ -1,4 +1,19 @@
1
1
  # Changelog
2
+
3
+ ## 0.7.4 — 2026-10-28: a bound result is a line of support, and a relation word is read as the relation it names
4
+
5
+ - **A bound result is a line of support.** A claim's standing counted only `supports` edges into it, so a claim
6
+ that derives from a result and binds its numbers to it by `measurements` read "no live support", evidence
7
+ "unverified" — 73 of 113 active claims in the challenge records, each with its result right there. The results a
8
+ claim derives from and the results its measurements bind now count as lines of support (each once; shared runs
9
+ or data still merge into one line), so the evidence profile is the result's — lab-recorded when the instrument
10
+ filed it — and `unsupported` means what it says.
11
+ - **A relation word the grammar doesn't have is read as the relation it names.** "generated", "created", "wrote"
12
+ → `produced`; "ran", "executed" → `executes`; "test" → `tests`; "answer" → `answers`; "used", "reads" → `uses`;
13
+ "contradicts", "refutes" → `challenges`; and so on (`schema.REL_SYNONYMS`, one reading each), noted on the
14
+ link like a translation. A hypothesis "derived from" a claim or result is `motivated_by` it; an experiment
15
+ "derived from" a hypothesis `tests` it. The link refusals left in the runs after 0.7.3: an unknown relation
16
+ `generated`, `derived_from hypothesis → claim`.
2
17
 
3
18
  ## 0.7.3 — 2026-10-28: the refusal log sees everything — results, shape, links — and reads a link as the relation that joins
4
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hypgraph
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: The evidence layer for AI research agents: a hash-chained research record that checks every claim as it is written and derives how well each is known.
5
5
  Project-URL: Homepage, https://hypgraph.com
6
6
  Project-URL: Documentation, https://hypgraph.com/engineers/
@@ -16,7 +16,7 @@ Standing is derived on every read, never stored; every write is an event in a SH
16
16
  their numbers; reviews pin versions; independence is counted, not assumed. See https://hypgraph.com.
17
17
  """
18
18
 
19
- __version__ = "0.7.3"
19
+ __version__ = "0.7.4"
20
20
 
21
21
  from .bundle import verify_bundle # noqa: E402
22
22
  from .graph import Author, Graph, GraphError, file_sha256 # noqa: E402
@@ -349,7 +349,25 @@ def dependents(s: Snap, nid: str, limit: int = 500) -> list[tuple[str, str, int]
349
349
 
350
350
 
351
351
  def supporters(s: Snap, nid: str) -> list[dict]:
352
- return [e for e in s.inn.get(nid, []) if e["rel"] == "supports"]
352
+ """What stands behind a node: every `supports` edge into it, and — for a claim — the results it is derived from
353
+ and the results its measurements bind (each once). A claim that binds its numbers to a result rests on that
354
+ result whether or not anyone also wrote `supports`; until 0.7.4 only the edge counted, and most honest claims
355
+ read "no live support" with their evidence "unverified"."""
356
+ out = [e for e in s.inn.get(nid, []) if e["rel"] == "supports"]
357
+ n = s.nodes.get(nid)
358
+ if n and n["type"] == "claim":
359
+ seen = {e["src"] for e in out}
360
+ for e in s.out.get(nid, []):
361
+ if e["rel"] == "derived_from" and e["dst"] in s.nodes and s.nodes[e["dst"]]["type"] == "result" \
362
+ and e["dst"] not in seen:
363
+ seen.add(e["dst"])
364
+ out.append({"src": e["dst"], "rel": "derived_from", "dst": nid, "data": {}, "by": n["created_by"]})
365
+ for m in n["body"].get("measurements") or []:
366
+ rid = m.get("result") if isinstance(m, dict) else None
367
+ if rid and rid in s.nodes and s.nodes[rid]["type"] == "result" and rid not in seen:
368
+ seen.add(rid)
369
+ out.append({"src": rid, "rel": "measurement", "dst": nid, "data": {}, "by": n["created_by"]})
370
+ return out
353
371
 
354
372
 
355
373
  def at_risk(s: Snap, nid: str) -> list[str]:
@@ -31,7 +31,7 @@ from . import derive as D
31
31
  from . import refusals, store
32
32
  from .checks import claim_issues, value_issues
33
33
  from .policy import Policy
34
- from .schema import REPLICATION, RELS, SAME_TYPE, SYNONYMS, TRANSLATE, TYPES, VERDICTS
34
+ from .schema import REL_SYNONYMS, REPLICATION, RELS, SAME_TYPE, SYNONYMS, TRANSLATE, TYPES, VERDICTS
35
35
  from .store import canon, now
36
36
 
37
37
 
@@ -462,14 +462,25 @@ class Graph:
462
462
  @staticmethod
463
463
  def _translate(s: D.Snap, src: str, rel: str, dst: str) -> tuple[str, str]:
464
464
  """A relation that can't join these two types, read as the one that says the same thing between them
465
- (schema.TRANSLATE): the relation to apply and a note, or the given relation and ''."""
465
+ (schema.TRANSLATE), or a word the grammar doesn't have read as the relation it names (schema.REL_SYNONYMS):
466
+ the relation to apply and a note, or the given relation and ''."""
467
+ given = rel
468
+ if rel not in RELS:
469
+ word = str(rel or "").strip().lower().replace(" ", "_").replace("-", "_")
470
+ rel = REL_SYNONYMS.get(word, rel)
471
+ if rel in RELS and src in s.nodes and dst in s.nodes:
472
+ st, dt = s.nodes[src]["type"], s.nodes[dst]["type"]
473
+ srcs, dsts = RELS[rel]
474
+ if (st in srcs and dt in dsts) or (dt in srcs and st in dsts):
475
+ return rel, (f"{s.nodes[src].get('code') or src} -{given}-> {s.nodes[dst].get('code') or dst} "
476
+ f"read as {rel}")
466
477
  if rel in RELS and src in s.nodes and dst in s.nodes:
467
478
  st, dt = s.nodes[src]["type"], s.nodes[dst]["type"]
468
479
  srcs, dsts = RELS[rel]
469
480
  if not (st in srcs and dt in dsts) and not (dt in srcs and st in dsts):
470
481
  new = TRANSLATE.get((rel, st, dt))
471
482
  if new:
472
- return new, (f"{s.nodes[src].get('code') or src} -{rel}-> {s.nodes[dst].get('code') or dst} "
483
+ return new, (f"{s.nodes[src].get('code') or src} -{given}-> {s.nodes[dst].get('code') or dst} "
473
484
  f"read as {new} (a {st} {new.replace('_', ' ')} a {dt}; {rel} joins "
474
485
  f"{', '.join(sorted(srcs))} to {', '.join(sorted(dsts))})")
475
486
  return rel, ""
@@ -125,6 +125,25 @@ TRANSLATE: dict[tuple[str, str, str], str] = {
125
125
  ("part_of", "claim", "artifact"): "derived_from",
126
126
  ("supports", "decision", "hypothesis"): "based_on", ("supports", "decision", "claim"): "based_on",
127
127
  ("supports", "decision", "result"): "based_on",
128
+ ("derived_from", "hypothesis", "claim"): "motivated_by", ("derived_from", "hypothesis", "result"): "motivated_by",
129
+ ("derived_from", "experiment", "hypothesis"): "tests", ("derived_from", "question", "claim"): "motivated_by",
130
+ }
131
+
132
+ # A relation written as a word the grammar doesn't have — the run's own verb ("generated", "ran", "test") — read as
133
+ # the relation it names. Only words with one reading.
134
+ REL_SYNONYMS: dict[str, str] = {
135
+ "generated": "produced", "generates": "produced", "created": "produced", "creates": "produced",
136
+ "wrote": "produced", "writes": "produced", "produces": "produced", "output": "produced",
137
+ "ran": "executes", "runs": "executes", "executed": "executes", "execute": "executes", "performs": "executes",
138
+ "test": "tests", "tested": "tests", "testing": "tests", "answer": "answers", "answered": "answers",
139
+ "support": "supports", "supported": "supports", "challenge": "challenges", "challenged": "challenges",
140
+ "contradicts": "challenges", "refutes": "challenges", "derived": "derived_from", "derives_from": "derived_from",
141
+ "computed_from": "derived_from", "based": "based_on", "use": "uses", "used": "uses", "using": "uses",
142
+ "reads": "uses", "read": "uses", "cite": "cites", "cited": "cites", "citing": "cites", "replicate": "replicates",
143
+ "replicated": "replicates", "supersede": "supersedes", "superseded": "supersedes", "replaces": "supersedes",
144
+ "correct": "corrects", "corrected": "corrects", "require": "requires", "required": "requires",
145
+ "depends_on": "requires", "part": "part_of", "belongs_to": "part_of", "motivated": "motivated_by",
146
+ "rests": "rests_on", "works": "works_on", "blocked": "blocked_by", "settle": "settles", "settled": "settles",
128
147
  }
129
148
 
130
149
  SYNONYMS: dict[tuple[str, str], dict[str, str]] = {
@@ -0,0 +1,55 @@
1
+ """A result a claim derives from, or binds a measurement to, is a line of support; two bound results from one run are
2
+ one line; a bound result that fell leaves the claim unsupported. 73 of 113 active claims in the challenge records
3
+ read "no live support" beside the result they were bound to."""
4
+ import pytest
5
+
6
+ from hypgraph import Graph, Policy
7
+ from hypgraph import derive as D
8
+
9
+
10
+ @pytest.fixture
11
+ def env():
12
+ g = Graph(policy=Policy(reviewers={"critic"}, instrument="lab"))
13
+ lead = g.author("lead")
14
+ inv = lead.investigation("Support", "Find out what a bound claim rests on.")["id"]
15
+ run = g.create(author="lab", type="run", inv=inv, fields={"kind": "lab", "name": "count", "state": "succeeded"})["id"]
16
+ r1 = g.create(author="lab", type="result", inv=inv, fields={"summary": "607 of 821 rows pass the check.",
17
+ "values": {"rows": {"k": 607, "n": 821}}}, links=[{"rel": "produced", "from": run}])["id"]
18
+ r2 = g.create(author="lab", type="result", inv=inv, fields={"summary": "214 of 821 rows carry a comma.",
19
+ "values": {"commas": {"k": 214, "n": 821}}}, links=[{"rel": "produced", "from": run}])["id"]
20
+ return g, inv, run, r1, r2
21
+
22
+
23
+ def _state(g, nid):
24
+ return D.state(g.snapshot(), nid)
25
+
26
+
27
+ def test_a_claim_bound_by_measurements_and_derived_from_is_supported_by_its_result(env):
28
+ g, inv, run, r1, r2 = env
29
+ c = g.author("analyst").add("claim", inv, statement="607 of 821 rows pass the check.", scope="corpus.csv's 821 rows.",
30
+ measurements=[{"name": "rows", "result": r1, "key": "rows", "k": 607, "n": 821}],
31
+ links=[{"rel": "derived_from", "to": r1}])
32
+ st = _state(g, c["id"])
33
+ assert st["independent_lines"] == 1 and st["evidence"] != "unverified" and not st["unsupported"]
34
+ assert "1 independent line of support" in st["sentence"]
35
+
36
+
37
+ def test_two_bound_results_from_one_run_are_one_line_and_a_supports_edge_adds_none(env):
38
+ g, inv, run, r1, r2 = env
39
+ c = g.author("analyst").add("claim", inv, statement="607 of 821 rows pass and 214 of 821 carry a comma.",
40
+ scope="corpus.csv's 821 rows.",
41
+ measurements=[{"name": "rows", "result": r1, "key": "rows", "k": 607, "n": 821},
42
+ {"name": "commas", "result": r2, "key": "commas", "k": 214, "n": 821}])
43
+ assert _state(g, c["id"])["independent_lines"] == 1
44
+ g.author("lab").link(r1, "supports", c["id"], why="the count is the claim's measurement")
45
+ assert _state(g, c["id"])["independent_lines"] == 1 # the same result, not a second line
46
+
47
+
48
+ def test_a_claim_whose_bound_result_fell_is_unsupported(env):
49
+ g, inv, run, r1, r2 = env
50
+ c = g.author("analyst").add("claim", inv, statement="607 of 821 rows pass the check.", scope="corpus.csv's 821 rows.",
51
+ measurements=[{"name": "rows", "result": r1, "key": "rows", "k": 607, "n": 821}],
52
+ links=[{"rel": "derived_from", "to": r1}])
53
+ g.author("lab").withdraw(r1, "the count read the wrong column")
54
+ st = _state(g, c["id"])
55
+ assert st["unsupported"] and st["independent_lines"] == 0
@@ -0,0 +1,38 @@
1
+ """A relation word the grammar doesn't have is read as the relation it names; a hypothesis derived from a claim is
2
+ motivated by it. The link refusals left after 0.7.3's translation table."""
3
+ import pytest
4
+
5
+ from hypgraph import Graph, GraphError, Policy
6
+
7
+
8
+ @pytest.fixture
9
+ def env():
10
+ g = Graph(policy=Policy(reviewers={"critic"}))
11
+ lead = g.author("lead")
12
+ inv = lead.investigation("Words", "Find out which relation words the record reads.")["id"]
13
+ return g, inv
14
+
15
+
16
+ def test_a_run_that_generated_an_artifact_produced_it(env):
17
+ g, inv = env
18
+ a = g.author("quant")
19
+ art = a.add("artifact", inv, sha256="b" * 64, path="grid.csv", kind="data")["id"]
20
+ run = a.add("run", inv, kind="agent", name="grid", state="succeeded", links=[{"rel": "generated", "to": art}])
21
+ assert ("produced", art) in {(x["rel"], x["dst"]) for x in g.snapshot().out[run["id"]]}
22
+ assert any("read as produced" in n for n in run["notes"])
23
+ out = a.link(run["id"], "used", art, why="the run read the grid", role="data")
24
+ assert out["rel"] == "uses" and out["notes"]
25
+
26
+
27
+ def test_a_hypothesis_derived_from_a_claim_is_motivated_by_it_and_nonsense_is_still_refused(env):
28
+ g, inv = env
29
+ a = g.author("stats")
30
+ c = a.add("claim", inv, statement="steps_k is lower on the supplement arm in the pilot.", scope="pilot.csv, 40 rows.",
31
+ kind="lead")["id"]
32
+ h = a.add("hypothesis", inv, statement="The supplement lowers steps_k.",
33
+ predictions=["steps_k is lower on the supplement arm in the trial."],
34
+ links=[{"rel": "derived_from", "to": c}])
35
+ assert ("motivated_by", c) in {(x["rel"], x["dst"]) for x in g.snapshot().out[h["id"]]}
36
+ with pytest.raises(GraphError) as e:
37
+ a.link(h["id"], "resembles", c, why="no such relation, and no word for it")
38
+ assert {p["code"] for p in e.value.problems} == {"LINK_UNKNOWN_RELATION"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes