hypgraph 0.4.1__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.1 → hypgraph-0.4.2}/CHANGELOG.md +8 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/PKG-INFO +1 -1
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/__init__.py +1 -1
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/language.py +13 -1
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/report_check.py +11 -0
- hypgraph-0.4.2/tests/test_questions_not_assertions.py +43 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/.gitignore +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/AGENTS.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/CITATION.cff +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/LICENSE +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/NOTICE +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/README.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/docs/PLAN.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/docs/decisions/ADR-001-token-bound-authors.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/docs/decisions/ADR-002-governance-profiles.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/docs/review-2026-10-24.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/docs/spec.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/pyproject.toml +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/__main__.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/bundle.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/checks.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/cli.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/coord.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/demos/__init__.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/demos/broad_street.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/derive.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/graph.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/mcp.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/plan.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/policy.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/profiles.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/refusals.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/schema.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/server.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/skill/SKILL.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/stats.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/store.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/__init__.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/atlas.css +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/atlas.js +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/hypgraph.svg +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/index.html +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/LICENSES.md +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/cose-base.js +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/cytoscape-fcose.js +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/cytoscape.min.js +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/src/hypgraph/ui/vendor/layout-base.js +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/studies/harmony/tests/test_suite.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_cancel_releases_lease.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_coord.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_d10.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_engine.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_engine_fixes.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_incremental.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_log_and_check.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_order_detail.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_profiles.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_profiles_041.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_refusals.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_server_auth.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_stats_values.py +0 -0
- {hypgraph-0.4.1 → hypgraph-0.4.2}/tests/test_surfaces.py +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 0.4.1 — 2026-10-25
|
|
4
12
|
|
|
5
13
|
Three rules the hypswarm examples exposed, each with a test:
|
|
@@ -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
|
|
@@ -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()):
|
|
@@ -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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|