hypgraph 0.4.0__tar.gz → 0.4.2__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.
- {hypgraph-0.4.0 → hypgraph-0.4.2}/CHANGELOG.md +18 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/PKG-INFO +1 -1
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/__init__.py +1 -1
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/checks.py +3 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/cli.py +1 -1
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/coord.py +11 -1
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/language.py +13 -1
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/profiles.py +20 -2
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/report_check.py +11 -0
- hypgraph-0.4.2/tests/test_profiles_041.py +69 -0
- hypgraph-0.4.2/tests/test_questions_not_assertions.py +43 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/.gitignore +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/AGENTS.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/CITATION.cff +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/LICENSE +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/NOTICE +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/README.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/docs/PLAN.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/docs/decisions/ADR-001-token-bound-authors.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/docs/decisions/ADR-002-governance-profiles.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/docs/review-2026-10-24.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/docs/spec.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/pyproject.toml +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/__main__.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/bundle.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/demos/__init__.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/demos/broad_street.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/derive.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/graph.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/mcp.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/plan.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/policy.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/refusals.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/schema.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/server.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/skill/SKILL.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/stats.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/store.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/__init__.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/atlas.css +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/atlas.js +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/hypgraph.svg +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/index.html +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/LICENSES.md +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/cose-base.js +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/cytoscape-fcose.js +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/cytoscape.min.js +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/layout-base.js +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/studies/harmony/tests/test_suite.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_cancel_releases_lease.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_coord.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_d10.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_engine.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_engine_fixes.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_incremental.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_log_and_check.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_order_detail.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_profiles.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_refusals.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_server_auth.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_stats_values.py +0 -0
- {hypgraph-0.4.0 → hypgraph-0.4.2}/tests/test_surfaces.py +0 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.2 — 2026-10-25
|
|
4
|
+
|
|
5
|
+
- **A question is not an assertion.** `language.interrogative` / `asserted` return nothing for a sentence that ends
|
|
6
|
+
with a question mark, and the report check skips the language and number rules on a sentence that asks or that
|
|
7
|
+
quotes one of the investigation's own questions (citations are still checked). Found in the first real HypSwarm
|
|
8
|
+
mixed-swarm run: the lead headed a section "Q2 — Is A significantly more accurate than B…?" and lost its report
|
|
9
|
+
to `REPORT_SIGNIFICANCE_UNBACKED` three times.
|
|
10
|
+
|
|
11
|
+
## 0.4.1 — 2026-10-25
|
|
12
|
+
|
|
13
|
+
Three rules the hypswarm examples exposed, each with a test:
|
|
14
|
+
- A claim's numbers that appear in a cited source's passage, citation or title are accounted for (a knowledge claim
|
|
15
|
+
"signed on 24 October 1648" citing the passage that says so is no longer `UNBOUND_NUMBER`).
|
|
16
|
+
- A rework's predicates count evidence written since the **first** order of its chain (`coord.chain_root`): a
|
|
17
|
+
`tests_pass` met by the instrument's re-run under the original order stays met under the rework.
|
|
18
|
+
- `PASS_WITHOUT_INSTRUMENT_RUN` accepts a claim resting on the author's result when a live lab run `replicates`
|
|
19
|
+
the run that produced it and the re-run's result is positive (`profiles._instrument_backed`).
|
|
20
|
+
|
|
3
21
|
## 0.4.0 — 2026-10-25: the coordination layer
|
|
4
22
|
|
|
5
23
|
Held until the harmony study had exercised every path (pilots 1–4, a 142-run confirmatory, two follow-up rounds). Governance profiles, token-bound authors, work orders with predicates, order text that carries the target, instrument reproduction, replayable refusals.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hypgraph
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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.4.
|
|
19
|
+
__version__ = "0.4.2"
|
|
20
20
|
|
|
21
21
|
from .bundle import verify_bundle # noqa: E402
|
|
22
22
|
from .graph import Author, Graph, GraphError, file_sha256 # noqa: E402
|
|
@@ -385,6 +385,9 @@ def claim_issues(s: Snap, body: dict, support: list[str], inv: str) -> list[Prob
|
|
|
385
385
|
plain, stat = derived_numbers(bound)
|
|
386
386
|
allowed = plain + [v for v, *_ in numbers(scope)] + \
|
|
387
387
|
[float(c) for c in (invb.get("constants") or []) if isinstance(c, (int, float))]
|
|
388
|
+
for src in support: # a cited source's own text accounts for the numbers in it
|
|
389
|
+
sb = s.nodes[src]["body"] if s.nodes[src]["type"] == "source" else {}
|
|
390
|
+
allowed += [v for v, *_ in numbers(" ".join(str(sb.get(k) or "") for k in ("passage", "citation", "title")))]
|
|
388
391
|
if _CONVENTIONS.search(clean): # "95% interval", "α 0.05", "80% power" are conventions
|
|
389
392
|
allowed += [95.0, 99.0, 90.0, 80.0, 0.05, 0.01]
|
|
390
393
|
loose = [p for p in numbers(clean) if not _matches(p, allowed) and not _matches_stat(p, stat)]
|
|
@@ -515,7 +515,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
515
515
|
s.add_argument("file", help="the markdown file ('-' for stdin)")
|
|
516
516
|
s.add_argument("--inv", default="", help="the investigation it reports on (default: inferred)")
|
|
517
517
|
s.add_argument("--strict", action="store_true", help="warnings (e.g. unsourced numbers) fail too")
|
|
518
|
-
s = cmd("bound", cmd_bound, "what k of n actually supports: the 95
|
|
518
|
+
s = cmd("bound", cmd_bound, "what k of n actually supports: the 95%% bounds behind any 'never'")
|
|
519
519
|
s.add_argument("k", type=int, help="how many times it happened")
|
|
520
520
|
s.add_argument("n", type=int, help="how many trials")
|
|
521
521
|
s.add_argument("--exposure", type=float, default=0, help="how many uses / users / units it will apply to")
|
|
@@ -235,10 +235,20 @@ def _claims_on(s: D.Snap, t: str) -> list[str]:
|
|
|
235
235
|
return list(dict.fromkeys(out))
|
|
236
236
|
|
|
237
237
|
|
|
238
|
+
def chain_root(s: D.Snap, oid: str) -> str:
|
|
239
|
+
"""The first order of a rework chain (follows ``supersedes``); an order that replaced nothing is its own root."""
|
|
240
|
+
seen = [oid]
|
|
241
|
+
while True:
|
|
242
|
+
older = [e["dst"] for e in s.out.get(seen[-1], []) if e["rel"] == "supersedes"]
|
|
243
|
+
if not older or older[0] in seen:
|
|
244
|
+
return seen[-1]
|
|
245
|
+
seen.append(older[0])
|
|
246
|
+
|
|
247
|
+
|
|
238
248
|
def _eval(g: "Graph", s: D.Snap, oid: str, pred: dict, text: str) -> dict:
|
|
239
249
|
p = pred.get("p", "")
|
|
240
250
|
t = target_of(s, oid)
|
|
241
|
-
at = s.nodes[oid]["created_at"]
|
|
251
|
+
at = s.nodes[chain_root(s, oid)]["created_at"] # a rework keeps what its earlier attempts wrote
|
|
242
252
|
res = {"p": p, "met": False, "evidence": [], "why": ""}
|
|
243
253
|
if p in profiles.PRED_DESC:
|
|
244
254
|
return res | profiles.evaluate(p, s, oid, t, at, s.nodes[oid]["body"].get("assignee", ""), s.nodes[oid]["inv"])
|
|
@@ -64,9 +64,21 @@ _ZERO_ON_A_SCALE = re.compile(r"\b(below|above|near|to|from|of|than|at|cross(?:e
|
|
|
64
64
|
re.I)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
# a sentence that asks: it ends with a question mark (markdown emphasis or a closing quote may follow it)
|
|
68
|
+
_INTERROGATIVE = re.compile(r"\?\s*[*_\"'’”)\]]*\s*$")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def interrogative(text: str) -> bool:
|
|
72
|
+
"""Does the sentence ask rather than assert? "Is A significantly better than B?" asserts nothing — found when a
|
|
73
|
+
lead headed a report section with the question it was answering and lost the report to the significance check."""
|
|
74
|
+
return bool(_INTERROGATIVE.search((text or "").strip()))
|
|
75
|
+
|
|
76
|
+
|
|
67
77
|
def asserted(pattern: re.Pattern, text: str) -> list[str]:
|
|
68
78
|
"""The distinct matches of a pattern that their sentence actually asserts: not negated in their clause, not an
|
|
69
|
-
epistemic hedge ("cannot conclude"),
|
|
79
|
+
epistemic hedge ("cannot conclude"), not a number on a scale ("below zero"), and nothing in a question."""
|
|
80
|
+
if interrogative(text):
|
|
81
|
+
return []
|
|
70
82
|
out = []
|
|
71
83
|
for m in pattern.finditer(text or ""):
|
|
72
84
|
if negated(text, m.start()):
|
|
@@ -200,6 +200,23 @@ def _lab_positive(s: D.Snap, rid: str) -> bool:
|
|
|
200
200
|
return bool(r) and s.nodes[r]["body"].get("kind") == "lab" and s.nodes[rid]["body"].get("outcome") == "positive"
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
def _instrument_backed(s: D.Snap, rid: str) -> bool:
|
|
204
|
+
"""A result is instrument-backed when it comes from a lab run, or when a lab run replicates the run that
|
|
205
|
+
produced it and that re-run's own result is positive (the instrument re-executed the author's code)."""
|
|
206
|
+
if _lab_positive(s, rid):
|
|
207
|
+
return True
|
|
208
|
+
r = _producing_run(s, rid)
|
|
209
|
+
if not r:
|
|
210
|
+
return False
|
|
211
|
+
for lab in (e["src"] for e in s.inn.get(r, []) if e["rel"] == "replicates"):
|
|
212
|
+
if s.nodes[lab]["body"].get("kind") != "lab" or not _live(s, lab):
|
|
213
|
+
continue
|
|
214
|
+
if any(e["rel"] == "produced" and s.nodes[e["dst"]]["body"].get("outcome") == "positive"
|
|
215
|
+
for e in s.out.get(lab, [])):
|
|
216
|
+
return True
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
|
|
203
220
|
def _bearing(s: D.Snap, t: str, ntype: str) -> list[str]:
|
|
204
221
|
"""Nodes of a type linked to the target by any relation, either way."""
|
|
205
222
|
out = [e["src"] for e in s.inn.get(t, []) if s.nodes[e["src"]]["type"] == ntype]
|
|
@@ -287,11 +304,12 @@ def claim_issues(s: D.Snap, inv: str, body: dict, support: list, pending: list)
|
|
|
287
304
|
"or gap — an incumbent without a bound is a solution, not an optimum",
|
|
288
305
|
fix="bind a result with bound or gap, or say 'best found'"))
|
|
289
306
|
if "pass_needs_instrument" in p.validators and L.asserted(_PASSES, stmt):
|
|
290
|
-
ok = any(
|
|
307
|
+
ok = any(_instrument_backed(s, r) for r in results)
|
|
291
308
|
if not ok:
|
|
292
309
|
out.append(Problem("PASS_WITHOUT_INSTRUMENT_RUN", "'passes' is asserted, but no supporting result comes "
|
|
293
310
|
"from an instrument-recorded run — tests must be run by the instrument, not reported",
|
|
294
|
-
fix="register_run with the test command as code; the instrument's re-run is the evidence"
|
|
311
|
+
fix="register_run with the test command as code; the instrument's re-run is the evidence — write the "
|
|
312
|
+
"claim once the re-run has landed (the order's reply names it)"))
|
|
295
313
|
if "claim_needs_source" in p.validators and kind in ("finding", "exclusion"):
|
|
296
314
|
cites = any(rel == "cites" and s.nodes.get(b, {}).get("type") == "source" for a, rel, b in pending) or \
|
|
297
315
|
any(_cites_source(s, x) for x in support if x in s.nodes and s.nodes[x]["type"] in ("claim", "result"))
|
|
@@ -71,6 +71,10 @@ _NEGATOR = re.compile(r"\b(not|cannot|can't|never|no|nor|without|hardly|unlikely
|
|
|
71
71
|
r"doesn't|didn't|won't)\b[\w\s-]{0,24}$", re.I)
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
def _norm(text: str) -> str:
|
|
75
|
+
return re.sub(r"[^a-z0-9]+", " ", (text or "").lower()).strip()
|
|
76
|
+
|
|
77
|
+
|
|
74
78
|
def _own_words(sentence: str) -> str:
|
|
75
79
|
"""The writer's own words: quoted passages (often the very wording being corrected) blanked out."""
|
|
76
80
|
return _QUOTE.sub(lambda m: " " * len(m.group(0)), sentence)
|
|
@@ -152,6 +156,10 @@ def check(g: "Graph", text: str, inv: str = "") -> dict:
|
|
|
152
156
|
if n["code"] and (n["inv"] in invs or i in invs):
|
|
153
157
|
by_code.setdefault(n["code"], []).append(i)
|
|
154
158
|
plain, stat = _allowed(s, invs)
|
|
159
|
+
# the investigations' own questions: a report may quote the question it answers without asserting it
|
|
160
|
+
qtexts = [_norm(n["body"].get("text", "")) for i, n in s.nodes.items()
|
|
161
|
+
if n["type"] == "question" and (n["inv"] in invs or i in invs)]
|
|
162
|
+
qtexts = [q for q in qtexts if len(q) >= 20]
|
|
155
163
|
findings: list[dict] = []
|
|
156
164
|
counted = {"sentences": 0, "numbers": 0, "codes": 0}
|
|
157
165
|
|
|
@@ -170,6 +178,7 @@ def check(g: "Graph", text: str, inv: str = "") -> dict:
|
|
|
170
178
|
continue
|
|
171
179
|
counted["sentences"] += 1
|
|
172
180
|
own = _own_words(sent)
|
|
181
|
+
asks = L.interrogative(sent) or any(q in _norm(sent) for q in qtexts)
|
|
173
182
|
qualified = bool(_QUALIFIED.search(sent))
|
|
174
183
|
settled = _asserted(_SETTLED, own)
|
|
175
184
|
cited = []
|
|
@@ -208,6 +217,8 @@ def check(g: "Graph", text: str, inv: str = "") -> dict:
|
|
|
208
217
|
add(ln, "warning", "REPORT_OMITS_DISPUTE", sent,
|
|
209
218
|
f"{code} is {'contested' if review == 'contested' else 'at risk'} in the record — say so where "
|
|
210
219
|
"you cite it.")
|
|
220
|
+
if asks: # a question, or the question's own words: citations checked, nothing asserted
|
|
221
|
+
continue
|
|
211
222
|
# numbers
|
|
212
223
|
for p in numbers(sent):
|
|
213
224
|
if _skip_number(p, sent) or re.search(rf"(?<![\w-])[A-Z]{{1,3}}{re.escape(p[3])}(?![\w-])", sent):
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""0.4.1: three rules the hypswarm examples exposed. A cited source accounts for the numbers in its passage; a
|
|
2
|
+
rework keeps the evidence its chain already produced; a claim of passing tests may rest on the author's result once
|
|
3
|
+
the instrument's re-run of that run is positive."""
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from hypgraph import Graph, GraphError, Policy
|
|
7
|
+
from hypgraph.coord import chain_root
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
H = "harness"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _g(profile):
|
|
15
|
+
g = Graph(policy=Policy(reviewers={"critic"}, extra_reserved={H}, instrument="lab"))
|
|
16
|
+
inv = g.create(author=H, type="investigation", fields={"title": "A profiled investigation.",
|
|
17
|
+
"objective": "See the 0.4.1 rules hold.", "profile": profile})["id"]
|
|
18
|
+
q = g.create(author=H, type="question", inv=inv, code="Q1", fields={"text": "When was it signed?"})["id"]
|
|
19
|
+
return g, inv, q
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_a_cited_source_accounts_for_its_numbers():
|
|
23
|
+
g, inv, q = _g("knowledge@1")
|
|
24
|
+
src = g.create(author="r", type="source", inv=inv, fields={"citation": "Britannica, 'Peace of Westphalia'.",
|
|
25
|
+
"url": "https://www.britannica.com/event/Peace-of-Westphalia",
|
|
26
|
+
"passage": "the treaties were signed on October 24, 1648"})["id"]
|
|
27
|
+
c = g.create(author="r", type="claim", inv=inv, fields={"statement": "The Peace of Westphalia was signed on 24 October 1648.",
|
|
28
|
+
"scope": "The treaties of Osnabrück and Münster.", "kind": "finding"},
|
|
29
|
+
links=[{"rel": "answers", "to": q}, {"rel": "cites", "to": src}])
|
|
30
|
+
assert c["code"].startswith("CL")
|
|
31
|
+
with pytest.raises(GraphError, match="UNBOUND_NUMBER"): # a number the source does not contain
|
|
32
|
+
g.create(author="r", type="claim", inv=inv, fields={"statement": "It was signed by 109 delegations.",
|
|
33
|
+
"scope": "The treaties of Osnabrück and Münster.", "kind": "finding"},
|
|
34
|
+
links=[{"rel": "answers", "to": q}, {"rel": "cites", "to": src}])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_a_rework_keeps_its_chains_evidence():
|
|
38
|
+
g, inv, q = _g("software@1")
|
|
39
|
+
o = g.issue_work(author=H, inv=inv, target=q, assignee="tester", objective="Run the tests.")
|
|
40
|
+
time.sleep(1.1)
|
|
41
|
+
r = g.create(author="tester", type="run", inv=inv, fields={"kind": "agent", "name": "pytest", "state": "succeeded"})
|
|
42
|
+
rs = g.create(author="tester", type="result", inv=inv, fields={"summary": "Ran pytest.", "values": {"passed": {"k": 3, "n": 3}}, "outcome": "positive"},
|
|
43
|
+
links=[{"rel": "produced", "from": r["id"]}])
|
|
44
|
+
lab = g.create(author="lab", type="run", inv=inv, fields={"kind": "lab", "name": "re-run of pytest", "state": "succeeded"},
|
|
45
|
+
links=[{"rel": "replicates", "to": r["id"], "data": {"differs": "same_code"}}])
|
|
46
|
+
g.create(author="lab", type="result", inv=inv, fields={"summary": "Re-executed: 1 of 1 values confirmed.", "values": {"passed": {"k": 3, "n": 3}}, "outcome": "positive"},
|
|
47
|
+
links=[{"rel": "produced", "from": lab["id"]}])
|
|
48
|
+
first = {p["p"]: p["met"] for p in g.check_work(o["order"])["predicates"]}
|
|
49
|
+
assert first == {"tests_pass": True, "claim_made": False}
|
|
50
|
+
rw = g.rework(author=H, order=o["order"], missing="no live claim on Q1 since the order was issued")
|
|
51
|
+
assert chain_root(g.snapshot(), rw["order"]) == o["order"]
|
|
52
|
+
second = {p["p"]: p["met"] for p in g.check_work(rw["order"])["predicates"]}
|
|
53
|
+
assert second["tests_pass"] is True # 0.4.0 lost it: evidence predated the rework
|
|
54
|
+
# (c) the claim of passing may now rest on the author's result: the instrument replicated its run positively
|
|
55
|
+
c = g.create(author="tester", type="claim", inv=inv, fields={"statement": "The importer passes all 3 of 3 tests in test_importer.py.",
|
|
56
|
+
"scope": "test_importer.py at this commit."},
|
|
57
|
+
links=[{"rel": "answers", "to": q}, {"rel": "derived_from", "to": rs["id"]}])
|
|
58
|
+
assert c["code"].startswith("CL")
|
|
59
|
+
assert all(p["met"] for p in g.check_work(rw["order"])["predicates"])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_passing_still_needs_the_instrument():
|
|
63
|
+
g, inv, q = _g("software@1")
|
|
64
|
+
r = g.create(author="tester", type="run", inv=inv, fields={"kind": "agent", "name": "pytest", "state": "succeeded"})
|
|
65
|
+
rs = g.create(author="tester", type="result", inv=inv, fields={"summary": "Ran pytest.", "values": {"passed": {"k": 3, "n": 3}}, "outcome": "positive"},
|
|
66
|
+
links=[{"rel": "produced", "from": r["id"]}])
|
|
67
|
+
with pytest.raises(GraphError, match="PASS_WITHOUT_INSTRUMENT_RUN"):
|
|
68
|
+
g.create(author="tester", type="claim", inv=inv, fields={"statement": "The importer passes all 3 of 3 tests.", "scope": "test_importer.py at this commit."},
|
|
69
|
+
links=[{"rel": "answers", "to": q}, {"rel": "derived_from", "to": rs["id"]}])
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""0.4.2: a question is not an assertion. From the first real HypSwarm mixed-swarm run: the lead headed a report
|
|
2
|
+
section with the question it was answering and the report was refused three times for 'significantly'."""
|
|
3
|
+
from hypgraph import Graph, Policy
|
|
4
|
+
from hypgraph import language as L
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_interrogatives_assert_nothing():
|
|
8
|
+
assert L.asserted(L.SIGNIFICANCE, "Is A significantly more accurate than B?") == []
|
|
9
|
+
assert L.asserted(L.SIGNIFICANCE, "**Q2 — Is A significantly more accurate than B at the two-sided 0.05 level?**") == []
|
|
10
|
+
assert L.asserted(L.ABSOLUTE, "Does it never fail?") == []
|
|
11
|
+
assert L.asserted(L.SIGNIFICANCE, "A is significantly more accurate than B.") == ["significantly"]
|
|
12
|
+
assert L.interrogative("Is it so?\"") and not L.interrogative("It is so.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _record():
|
|
16
|
+
g = Graph(policy=Policy(reviewers={"critic"}))
|
|
17
|
+
inv = g.create(author="h", type="investigation", fields={"title": "Does A beat B.", "objective": "Compare A and B on the same items."})["id"]
|
|
18
|
+
q = g.create(author="h", type="question", inv=inv, code="Q2",
|
|
19
|
+
fields={"text": "Is A significantly more accurate than B (two-sided, 0.05)?"})["id"]
|
|
20
|
+
r = g.create(author="stats", type="run", inv=inv, fields={"kind": "agent", "name": "mcnemar", "state": "succeeded"})
|
|
21
|
+
rs = g.create(author="stats", type="result", inv=inv, fields={"summary": "Compared A and B on the same 1,000 items.",
|
|
22
|
+
"values": {"a": {"k": 900, "n": 1000}, "b": {"k": 800, "n": 1000}}},
|
|
23
|
+
links=[{"rel": "produced", "from": r["id"]}])
|
|
24
|
+
c = g.create(author="stats", type="claim", inv=inv, fields={"statement": "A is right on 900 of 1000 items and B on 800 of 1000.",
|
|
25
|
+
"scope": "The 1,000 shared items."},
|
|
26
|
+
links=[{"rel": "answers", "to": q}, {"rel": "derived_from", "to": rs["id"]}])
|
|
27
|
+
g.review(author="critic", target=c["id"], verdict="checked", method="Recounted both columns from the file.", notes="The counts hold and the claim says what the result says.")
|
|
28
|
+
return g, inv, c["code"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_a_report_may_quote_the_question_it_answers():
|
|
32
|
+
g, inv, cl = _record()
|
|
33
|
+
text = ("## Q2 — Is A significantly more accurate than B at the two-sided 0.05 level?\n\n"
|
|
34
|
+
f"A is right on 900 of 1000 items and B on 800 of 1000 ({cl}).\n\n"
|
|
35
|
+
"The question 'Is A significantly more accurate than B (two-sided, 0.05)?' is what this section answers.\n")
|
|
36
|
+
r = g.check_text(text, inv)
|
|
37
|
+
assert not [f for f in r["findings"] if f["code"] == "REPORT_SIGNIFICANCE_UNBACKED"], r["findings"]
|
|
38
|
+
assert r["ok"]
|
|
39
|
+
# the assertion itself is still caught, and an unknown code in a question heading is still an unknown code
|
|
40
|
+
bad = g.check_text("A is significantly more accurate than B.", inv)
|
|
41
|
+
assert any(f["code"] == "REPORT_SIGNIFICANCE_UNBACKED" and f["level"] == "error" for f in bad["findings"])
|
|
42
|
+
unk = g.check_text("## Is CL99 significantly better?", inv)
|
|
43
|
+
assert any(f["code"] == "REPORT_UNKNOWN_CODE" for f in unk["findings"])
|
|
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
|
|
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
|
|
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
|