hypgraph 0.7.3__tar.gz → 0.7.5__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 (97) hide show
  1. {hypgraph-0.7.3 → hypgraph-0.7.5}/CHANGELOG.md +25 -0
  2. {hypgraph-0.7.3 → hypgraph-0.7.5}/PKG-INFO +1 -1
  3. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/__init__.py +1 -1
  4. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/derive.py +25 -2
  5. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/graph.py +14 -3
  6. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/schema.py +19 -0
  7. hypgraph-0.7.5/tests/test_bound_support.py +55 -0
  8. hypgraph-0.7.5/tests/test_cited_and_failed_support.py +60 -0
  9. hypgraph-0.7.5/tests/test_relation_words.py +38 -0
  10. {hypgraph-0.7.3 → hypgraph-0.7.5}/.gitignore +0 -0
  11. {hypgraph-0.7.3 → hypgraph-0.7.5}/AGENTS.md +0 -0
  12. {hypgraph-0.7.3 → hypgraph-0.7.5}/CITATION.cff +0 -0
  13. {hypgraph-0.7.3 → hypgraph-0.7.5}/LICENSE +0 -0
  14. {hypgraph-0.7.3 → hypgraph-0.7.5}/NOTICE +0 -0
  15. {hypgraph-0.7.3 → hypgraph-0.7.5}/README.md +0 -0
  16. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/PLAN.md +0 -0
  17. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/decisions/ADR-001-token-bound-authors.md +0 -0
  18. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/decisions/ADR-002-governance-profiles.md +0 -0
  19. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/profile-workflows-plan.md +0 -0
  20. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/review-2026-10-24.md +0 -0
  21. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/site-plan.md +0 -0
  22. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/spec.md +0 -0
  23. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/ui-plan.md +0 -0
  24. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/workflows/README.md +0 -0
  25. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/workflows/knowledge.md +0 -0
  26. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/workflows/optimization.md +0 -0
  27. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/workflows/scientific.md +0 -0
  28. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/workflows/software.md +0 -0
  29. {hypgraph-0.7.3 → hypgraph-0.7.5}/docs/workflows/trading.md +0 -0
  30. {hypgraph-0.7.3 → hypgraph-0.7.5}/pyproject.toml +0 -0
  31. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/__main__.py +0 -0
  32. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/bundle.py +0 -0
  33. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/checks.py +0 -0
  34. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/cli.py +0 -0
  35. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/coord.py +0 -0
  36. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/demos/__init__.py +0 -0
  37. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/demos/broad_street.py +0 -0
  38. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/language.py +0 -0
  39. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/mcp.py +0 -0
  40. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/plan.py +0 -0
  41. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/policy.py +0 -0
  42. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/profiles.py +0 -0
  43. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/refusals.py +0 -0
  44. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/report_check.py +0 -0
  45. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/server.py +0 -0
  46. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/skill/SKILL.md +0 -0
  47. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/stats.py +0 -0
  48. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/store.py +0 -0
  49. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/__init__.py +0 -0
  50. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/atlas.css +0 -0
  51. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/atlas.js +0 -0
  52. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/hypgraph.css +0 -0
  53. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/hypgraph.js +0 -0
  54. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/hypgraph.svg +0 -0
  55. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/index.html +0 -0
  56. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/vendor/LICENSES.md +0 -0
  57. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/vendor/cose-base.js +0 -0
  58. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/vendor/cytoscape-fcose.js +0 -0
  59. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/vendor/cytoscape.min.js +0 -0
  60. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/ui/vendor/layout-base.js +0 -0
  61. {hypgraph-0.7.3 → hypgraph-0.7.5}/src/hypgraph/workflow.py +0 -0
  62. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_accept_work_kind.py +0 -0
  63. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_bundle_telemetry.py +0 -0
  64. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_cancel_releases_lease.py +0 -0
  65. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_coord.py +0 -0
  66. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_d10.py +0 -0
  67. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_engine.py +0 -0
  68. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_engine_fixes.py +0 -0
  69. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_incremental.py +0 -0
  70. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_link_translate.py +0 -0
  71. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_log_and_check.py +0 -0
  72. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_measure_questions.py +0 -0
  73. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_opposed_answers.py +0 -0
  74. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_option_lead.py +0 -0
  75. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_order_detail.py +0 -0
  76. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_profiles.py +0 -0
  77. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_profiles_041.py +0 -0
  78. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_questions_not_assertions.py +0 -0
  79. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_refusal_friction.py +0 -0
  80. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_refusal_results.py +0 -0
  81. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_refusals.py +0 -0
  82. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_report_answers.py +0 -0
  83. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_rerunnable.py +0 -0
  84. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_restates_scale.py +0 -0
  85. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_server_auth.py +0 -0
  86. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_shape_refusals.py +0 -0
  87. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_stats_not_backtests.py +0 -0
  88. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_stats_values.py +0 -0
  89. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_surfaces.py +0 -0
  90. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_telemetry_stream.py +0 -0
  91. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_ui_app.py +0 -0
  92. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_ui_engine.py +0 -0
  93. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_uncorrected_effect.py +0 -0
  94. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_unnamed_inputs.py +0 -0
  95. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_versions_not_figures.py +0 -0
  96. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_vocabulary_friction.py +0 -0
  97. {hypgraph-0.7.3 → hypgraph-0.7.5}/tests/test_workflows.py +0 -0
@@ -1,4 +1,29 @@
1
1
  # Changelog
2
+
3
+ ## 0.7.5 — 2026-10-28: a cited source is a line of support, and a finding of failure is not a fall
4
+
5
+ - **A cited source is a line of support, and a succeeded run's "failed" outcome is a finding, not a fall.**
6
+ A knowledge claim that cites its sources (`cites` → source, or `derived_from` → source) read "no live
7
+ support · unverified" even after 0.7.4 counted bound results; the source is what such a claim rests on,
8
+ and its line is `external`. And a result with `outcome: failed` counted as a broken premise whatever its
9
+ run did: testers write "failed" for the code under test failing its spec (30 results in the challenge
10
+ records, 13 carrying checked claims that then read UNSUPPORTED — every support has fallen). A result a
11
+ succeeded run produced did happen; it falls only when there is no run or the run did not succeed.
12
+
13
+ ## 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
14
+
15
+ - **A bound result is a line of support.** A claim's standing counted only `supports` edges into it, so a claim
16
+ that derives from a result and binds its numbers to it by `measurements` read "no live support", evidence
17
+ "unverified" — 73 of 113 active claims in the challenge records, each with its result right there. The results a
18
+ claim derives from and the results its measurements bind now count as lines of support (each once; shared runs
19
+ or data still merge into one line), so the evidence profile is the result's — lab-recorded when the instrument
20
+ filed it — and `unsupported` means what it says.
21
+ - **A relation word the grammar doesn't have is read as the relation it names.** "generated", "created", "wrote"
22
+ → `produced`; "ran", "executed" → `executes`; "test" → `tests`; "answer" → `answers`; "used", "reads" → `uses`;
23
+ "contradicts", "refutes" → `challenges`; and so on (`schema.REL_SYNONYMS`, one reading each), noted on the
24
+ link like a translation. A hypothesis "derived from" a claim or result is `motivated_by` it; an experiment
25
+ "derived from" a hypothesis `tests` it. The link refusals left in the runs after 0.7.3: an unknown relation
26
+ `generated`, `derived_from hypothesis → claim`.
2
27
 
3
28
  ## 0.7.3 — 2026-10-28: the refusal log sees everything — results, shape, links — and reads a link as the relation that joins
4
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hypgraph
3
- Version: 0.7.3
3
+ Version: 0.7.5
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.5"
20
20
 
21
21
  from .bundle import verify_bundle # noqa: E402
22
22
  from .graph import Author, Graph, GraphError, file_sha256 # noqa: E402
@@ -134,7 +134,11 @@ def fallen(s: Snap, nid: str) -> bool:
134
134
  if n["type"] == "run" and n["body"].get("state") in ("failed", "timeout", "cancelled", "interrupted"):
135
135
  return True
136
136
  if n["type"] == "result" and n["body"].get("outcome") == "failed":
137
- return True
137
+ # "failed" means the measurement didn't happen. A result a succeeded run produced did happen: its author
138
+ # wrote "failed" for what it measured (the code under test failing its spec — 30 results in the challenge
139
+ # records, 13 carrying checked claims that then read UNSUPPORTED). That is a finding, not a fall.
140
+ run = producing_run(s, nid)
141
+ return not (run and s.nodes[run]["body"].get("state") == "succeeded")
138
142
  return False
139
143
 
140
144
 
@@ -349,7 +353,26 @@ def dependents(s: Snap, nid: str, limit: int = 500) -> list[tuple[str, str, int]
349
353
 
350
354
 
351
355
  def supporters(s: Snap, nid: str) -> list[dict]:
352
- return [e for e in s.inn.get(nid, []) if e["rel"] == "supports"]
356
+ """What stands behind a node: every `supports` edge into it, and — for a claim — the results it is derived from,
357
+ the sources it derives from or cites, and the results its measurements bind (each once). A claim that binds its numbers to a result rests on that
358
+ result whether or not anyone also wrote `supports`; until 0.7.4 only the edge counted, and most honest claims
359
+ read "no live support" with their evidence "unverified"."""
360
+ out = [e for e in s.inn.get(nid, []) if e["rel"] == "supports"]
361
+ n = s.nodes.get(nid)
362
+ if n and n["type"] == "claim":
363
+ seen = {e["src"] for e in out}
364
+ for e in s.out.get(nid, []):
365
+ # a result it derives from, or a source it derives from or cites: what the claim rests on
366
+ if e["rel"] in ("derived_from", "cites") and e["dst"] in s.nodes and e["dst"] not in seen \
367
+ and s.nodes[e["dst"]]["type"] in (("result", "source") if e["rel"] == "derived_from" else ("source",)):
368
+ seen.add(e["dst"])
369
+ out.append({"src": e["dst"], "rel": e["rel"], "dst": nid, "data": {}, "by": n["created_by"]})
370
+ for m in n["body"].get("measurements") or []:
371
+ rid = m.get("result") if isinstance(m, dict) else None
372
+ if rid and rid in s.nodes and s.nodes[rid]["type"] == "result" and rid not in seen:
373
+ seen.add(rid)
374
+ out.append({"src": rid, "rel": "measurement", "dst": nid, "data": {}, "by": n["created_by"]})
375
+ return out
353
376
 
354
377
 
355
378
  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,60 @@
1
+ """The last claims reading 'no live support' after bound results counted: a knowledge claim resting on the sources it
2
+ cites, and claims on results whose author wrote outcome 'failed' for the code under test failing its spec, on runs
3
+ that succeeded (E4: 13 checked claims read UNSUPPORTED — every support has fallen)."""
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
+ inv = g.author("lead").investigation("Support", "Find out what a claim rests on.")["id"]
14
+ return g, inv
15
+
16
+
17
+ def _state(g, nid):
18
+ return D.state(g.snapshot(), nid)
19
+
20
+
21
+ def test_a_cited_source_is_a_line_of_support_rated_external(env):
22
+ g, inv = env
23
+ r = g.author("researcher")
24
+ s1 = r.add("source", inv, citation="config.py in the 2.4.0 release", url="file:sources/config.py",
25
+ passage='"retry_backoff": 30, # seconds; 60 before 2.3.0')["id"]
26
+ s2 = r.add("source", inv, citation="The 2.4 configuration guide", url="file:sources/config-guide.md",
27
+ passage="retry_backoff defaults to 30 seconds")["id"]
28
+ c = r.add("claim", inv, statement="The default retry_backoff in the current release is 30 seconds.",
29
+ scope="The 2.4.0 release's config module and its configuration guide.",
30
+ links=[{"rel": "cites", "to": s1}, {"rel": "cites", "to": s2}])
31
+ st = _state(g, c["id"])
32
+ assert st["independent_lines"] == 2 and st["evidence"] == "external" and not st["unsupported"]
33
+
34
+
35
+ def test_a_failed_outcome_on_a_succeeded_run_is_a_finding_not_a_fall(env):
36
+ g, inv = env
37
+ t = g.author("tester")
38
+ run = t.add("run", inv, kind="agent", name="spec cases", state="succeeded")["id"]
39
+ r = t.add("result", inv, kind="computed", outcome="failed",
40
+ summary="Of the 8 valid-input cases, parse_duration gets exactly 1 wrong: case 8.",
41
+ values={"valid_wrong": {"k": 1, "n": 8}}, links=[{"rel": "produced", "from": run}])["id"]
42
+ assert not D.fallen(g.snapshot(), r)
43
+ c = t.add("claim", inv, statement="parse_duration gets exactly 1 of the 8 valid-input cases wrong.",
44
+ scope="SPEC.md's 8 valid-input cases on the current diff.",
45
+ measurements=[{"name": "valid wrong", "result": r, "key": "valid_wrong", "k": 1, "n": 8}],
46
+ links=[{"rel": "derived_from", "to": r}])
47
+ st = _state(g, c["id"])
48
+ assert st["independent_lines"] == 1 and not st["unsupported"] and "UNSUPPORTED" not in st["sentence"]
49
+
50
+
51
+ def test_a_failed_outcome_still_falls_when_the_run_failed_or_there_is_none(env):
52
+ g, inv = env
53
+ t = g.author("tester")
54
+ bad = t.add("run", inv, kind="agent", name="spec cases", state="failed")["id"]
55
+ r1 = t.add("result", inv, kind="computed", outcome="failed", summary="The harness crashed before the cases ran.",
56
+ values={"n_cases": 12}, links=[{"rel": "produced", "from": bad}])["id"]
57
+ r2 = t.add("result", inv, kind="computed", outcome="failed", summary="pytest could not import the module.",
58
+ values={"n_collected": 0})["id"]
59
+ s = g.snapshot()
60
+ assert D.fallen(s, r1) and D.fallen(s, r2)
@@ -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