hypgraph 0.7.16__tar.gz → 0.7.18__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.7.16 → hypgraph-0.7.18}/CHANGELOG.md +39 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/PKG-INFO +1 -1
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/PLAN.md +1 -1
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/RELEASE-READINESS.md +23 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/spec.md +13 -5
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/verification.md +5 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/__init__.py +1 -1
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/bundle.py +60 -12
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/cli.py +5 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/hypgraph.css +2 -0
- hypgraph-0.7.18/src/hypgraph/ui/hypgraph.i18n.js +543 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/hypgraph.js +458 -213
- hypgraph-0.7.18/tests/test_i18n.py +183 -0
- hypgraph-0.7.18/tests/test_verify_lifecycle.py +195 -0
- hypgraph-0.7.18/tests/test_verify_scoped_views.py +198 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_verify_views.py +2 -1
- {hypgraph-0.7.16 → hypgraph-0.7.18}/.gitignore +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/AGENTS.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/CITATION.cff +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/DISCLAIMER.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/LICENSE +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/NOTICE +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/README.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/decisions/ADR-001-token-bound-authors.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/decisions/ADR-002-governance-profiles.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/profile-workflows-plan.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/review-2026-10-24.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/site-plan.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/ui-plan.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/workflows/README.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/workflows/knowledge.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/workflows/optimization.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/workflows/scientific.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/workflows/software.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/docs/workflows/trading.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/examples/frameworks/README.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/pyproject.toml +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/__main__.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/blind.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/checks.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/coord.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/demos/__init__.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/demos/broad_street.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/derive.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/graph.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/language.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/mcp.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/plan.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/policy.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/profiles.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/references.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/refusals.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/report_check.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/schema.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/server.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/skill/SKILL.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/stats.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/store.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/__init__.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/atlas.css +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/atlas.js +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/hypgraph.svg +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/index.html +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/vendor/LICENSES.md +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/vendor/cose-base.js +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/vendor/cytoscape-fcose.js +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/vendor/cytoscape.min.js +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/ui/vendor/layout-base.js +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/src/hypgraph/workflow.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_accept_work_kind.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_anchoring.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_blind.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_blind_oracles.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_bound_support.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_bundle_telemetry.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_cancel_releases_lease.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_cited_and_failed_support.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_coord.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_d10.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_engine.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_engine_fixes.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_framework_examples.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_incremental.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_link_translate.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_log_and_check.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_measure_questions.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_opposed_answers.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_option_lead.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_order_detail.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_profiles.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_profiles_041.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_questions_not_assertions.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_references.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_refusal_friction.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_refusal_results.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_refusals.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_relation_words.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_report_answers.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_rerunnable.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_restates_scale.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_server_auth.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_server_hosts.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_shape_refusals.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_stats_not_backtests.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_stats_values.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_surfaces.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_telemetry_stream.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_trust_boundary.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_ui_app.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_ui_engine.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_uncorrected_effect.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_unnamed_inputs.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_verify_complete.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_verify_scope.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_verify_trust.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_versions_not_figures.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_vocabulary_friction.py +0 -0
- {hypgraph-0.7.16 → hypgraph-0.7.18}/tests/test_workflows.py +0 -0
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.18 — 2026-10-31: a scoped bundle can't call an active node superseded
|
|
4
|
+
|
|
5
|
+
- **A scoped bundle can't show an active node as superseded and still say its views are checked.** A re-audit of
|
|
6
|
+
0.7.17 found that a bundle naming a `scope.inv` (which the chain doesn't cover) could show any node, an active claim
|
|
7
|
+
or the investigation itself, as superseded, and verify against its published head with `views: "checked"` and
|
|
8
|
+
coverage complete: the verifier let a scoped bundle call any non-withdrawn node superseded, because links from
|
|
9
|
+
other investigations are left out. Now, in the Python and browser verifiers alike, that allowance applies only
|
|
10
|
+
when events really are redacted (without them every link is in the bundle, so the lifecycle fails) and only to a
|
|
11
|
+
type a `supersedes` or `corrects` link can point at (never an investigation or a review), and even then the views
|
|
12
|
+
are `"unconfirmed"`, not `"checked"`, with a warning naming each node; `hypgraph verify` prints
|
|
13
|
+
`views: NOT CHECKED`. The Explorer header shows a *views not checked* pill for `historical` and `unconfirmed`
|
|
14
|
+
views, and the verify page names both instead of calling them checked (`tests/test_verify_scoped_views.py`).
|
|
15
|
+
- **The spec says what a verifier does with lifecycles.** `docs/spec.md` and `docs/verification.md` no longer say a
|
|
16
|
+
verifier accepts the value either rule gives: the current rule is authoritative, a 0.3-only lifecycle is
|
|
17
|
+
`historical`, a scoped one superseded from outside is `unconfirmed`, anything else fails.
|
|
18
|
+
|
|
19
|
+
## 0.7.17 — 2026-10-31: a superseded node can't verify as active
|
|
20
|
+
|
|
21
|
+
- **A superseded node can't verify as active.** A pre-launch audit of 0.7.16 found that the verifier accepted a node's
|
|
22
|
+
lifecycle under either the current rule or 0.3's, which counted only an active replacement: in a chain RS3
|
|
23
|
+
supersedes RS2 supersedes RS1, 0.3 calls RS1 active, so a bundle edited to show RS1 active verified with
|
|
24
|
+
`views: "checked"`, its chain, rows and head untouched; setting the bundle's `generator` (which the chain doesn't
|
|
25
|
+
cover) to 0.3 also turned `--standing` from a problem into "not checked". Now, in the Python and browser verifiers
|
|
26
|
+
alike, a lifecycle is checked against the current rule only. One that matches only 0.3's rule still verifies (a 0.3
|
|
27
|
+
export shows it so) but the result says `views: "historical"`, not `"checked"`, and the warning names each such node
|
|
28
|
+
and what the current rule makes it; `hypgraph verify` prints `views: NOT CHECKED`. The generator is never consulted.
|
|
29
|
+
Covered for `supersedes` and `corrects`, chains of three, withdrawn replacements, scoped bundles and a forged or
|
|
30
|
+
deleted generator, with the browser verifier held to the same verdict, warning and coverage
|
|
31
|
+
(`tests/test_verify_lifecycle.py`).
|
|
32
|
+
- **The Explorer and the website speak Spanish, French, German and Simplified Chinese.** The UI's strings are in a
|
|
33
|
+
table (English, the source, in `hypgraph.js`; translations in `hypgraph.i18n.js`, loaded on demand or registered
|
|
34
|
+
with `HypGraph.addStrings`). The language comes from `?lang=`, the host page's `lang`, a saved choice, then the
|
|
35
|
+
browser's, else English; a switcher in the header changes it (`langSwitch: false` hides it in an embed). The main
|
|
36
|
+
site pages are built in each language under `site/<lang>/` by `dev/build_site.py` from `i18n/site/<lang>.json`,
|
|
37
|
+
with `hreflang` alternates, a language menu and a note that the English is authoritative. `i18n/glossary.json`
|
|
38
|
+
fixes how claim, standing, supersedes, withdrawn, head, bundle, scope … are rendered, and the tests hold every
|
|
39
|
+
table to it (`tests/test_i18n.py`). What the verifier, the engine and the CLI say stays English and byte-identical
|
|
40
|
+
in Python and JS: agents and audits parse it.
|
|
41
|
+
|
|
3
42
|
## 0.7.16 — 2026-10-31: a bundle's scope must be a real one
|
|
4
43
|
|
|
5
44
|
- **A bundle's scope must be a real one.** A re-audit of 0.7.15 found that `scope.inv`, which the chain doesn't
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hypgraph
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.18
|
|
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/
|
|
@@ -109,7 +109,7 @@ claim's derived standing.
|
|
|
109
109
|
Multi-writer sync between graphs (bundles cover sharing), authentication (the host's job; the HTTP server binds to
|
|
110
110
|
loopback and is read-only unless started with `--writable`), and a hosted service.
|
|
111
111
|
|
|
112
|
-
## Status at 0.7.
|
|
112
|
+
## Status at 0.7.18 (2026-10-31)
|
|
113
113
|
|
|
114
114
|
All seven phases are done: the library, bundles, the server and UI, the MCP server, the website, Pegasus running on
|
|
115
115
|
the package, and releases to PyPI from tags with trusted publishing. What came after the plan, by release, is in
|
|
@@ -47,6 +47,29 @@ was taken from a changelog without being checked against the code.*
|
|
|
47
47
|
> **Where that stands.** #72 is merged with green CI (17 of 17 checks); the suite is 412 passed, and hypswarm and
|
|
48
48
|
> hypmesh pass on it. Released as 0.7.16.
|
|
49
49
|
|
|
50
|
+
> **Pre-launch audit of 0.7.16 (2026-10-31).** Confirmed and fixed for 0.7.17: the verifier accepted a lifecycle under
|
|
51
|
+
> either the current rule or 0.3's, so the oldest node of a chain of two or more replacements could be shown active
|
|
52
|
+
> and the bundle verified with `views: "checked"`, chain, rows and head untouched; a `generator` set to 0.3 (not
|
|
53
|
+
> covered by the chain) also turned `--standing` into "not checked". A lifecycle is now checked against the current
|
|
54
|
+
> rule only; one that matches only 0.3's verifies with `views: "historical"` and a warning naming each node, in
|
|
55
|
+
> Python and the browser alike, whatever the generator says. Tests: `tests/test_verify_lifecycle.py` (five fail on
|
|
56
|
+
> the 0.7.16 code). The required checks are now all 17 CI jobs, and the repository has its homepage and topics.
|
|
57
|
+
>
|
|
58
|
+
> **Where that stands.** #75 is merged with green CI (17 of 17 checks); from the 0.7.17 wheel in a fresh venv the
|
|
59
|
+
> suite is 424 passed, and hypswarm (118 passed) and hypmesh (37 passed) pass on it. Released as 0.7.17.
|
|
60
|
+
|
|
61
|
+
> **Re-audit of 0.7.17 (2026-10-31).** Confirmed and fixed for 0.7.18: a bundle naming a `scope.inv` (not covered by
|
|
62
|
+
> the chain) could show any non-withdrawn node, an active claim or the investigation itself, as superseded and verify
|
|
63
|
+
> against its published head with `views: "checked"` and coverage complete; the published Broad Street bundle, which
|
|
64
|
+
> has no redacted events, was enough. The scoped allowance now needs redacted events and a type a `supersedes` /
|
|
65
|
+
> `corrects` link can reach (never an investigation or a review), and even then the views are `"unconfirmed"`, with
|
|
66
|
+
> each node named, in Python and the browser alike. The verify page and the Explorer header now say *views not
|
|
67
|
+
> checked* for `historical` and `unconfirmed` views, and the spec no longer says a verifier accepts either rule.
|
|
68
|
+
> Tests: `tests/test_verify_scoped_views.py`.
|
|
69
|
+
>
|
|
70
|
+
> **Where that stands.** #77 is merged with green CI (17 of 17 checks); from the 0.7.18 wheel in a fresh venv the
|
|
71
|
+
> suite is 431 passed, and hypswarm (118 passed) and hypmesh (37 passed) pass on it. Released as 0.7.18.
|
|
72
|
+
|
|
50
73
|
## Recommendation: CONDITIONAL GO
|
|
51
74
|
|
|
52
75
|
HypGraph is ready for a public developer preview once #59, #60 and #61 are merged with green CI and released as one
|
|
@@ -153,7 +153,9 @@ out a change hidden among them. `scope.title` is a label; a viewer names the sco
|
|
|
153
153
|
**What a verifier vouches for.** `ok` means the chain links and every row and event agree, both ways. `coverage:
|
|
154
154
|
complete` means every row field was vouched for by its event and nothing is redacted (`partial` otherwise). `views:
|
|
155
155
|
checked` means every fact the derived views show that the rows fix is present and agrees with the rows, and the
|
|
156
|
-
scope is a real one; `views: none` means the bundle carries no views.
|
|
156
|
+
scope is a real one; `views: none` means the bundle carries no views. `views: historical` and `views: unconfirmed`
|
|
157
|
+
mean the views verify but some lifecycles in them couldn't be checked (below), and the warning names each one: a
|
|
158
|
+
viewer must not present them as checked. None of them vouches for standing, the report,
|
|
157
159
|
stages or work orders (as exported), for who an author really is, or that the head was published.
|
|
158
160
|
|
|
159
161
|
**Anchoring.** A bundle proves only its own consistency. To prove it wasn't rebuilt from scratch, publish its `head`
|
|
@@ -171,10 +173,16 @@ investigation node), which investigations the list shows (each once), which node
|
|
|
171
173
|
`by` and `why`. Every one of these must be **present**, not only equal where present: a viewer fills a missing fact
|
|
172
174
|
with a default, and a withdrawn claim without its lifecycle would read as active. The lifecycle is derived from the
|
|
173
175
|
verified rows (withdrawn when a withdrawal exists; superseded when a live `supersedes` or `corrects` replacement is
|
|
174
|
-
active or, since 0.4, itself superseded; an investigation's own `state` otherwise)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
active or, since 0.4, itself superseded; an investigation's own `state` otherwise). A verifier checks the
|
|
177
|
+
lifecycle against the current rule. A lifecycle that only 0.3's rule gives (it counted an active replacement only)
|
|
178
|
+
verifies, since a 0.3 export shows it so, but the chain doesn't record which version exported a bundle, so the views
|
|
179
|
+
are `historical`, not `checked`. A scoped bundle with redacted events leaves out links from other investigations, and
|
|
180
|
+
one of them may replace a node: a node shown superseded with no replacement in the rows verifies only if its type can
|
|
181
|
+
be replaced at all (every type but `investigation` and `review`) and it isn't withdrawn, and the views are
|
|
182
|
+
`unconfirmed`, since that replacement is among the redacted events. A bundle with no redacted events holds every link,
|
|
183
|
+
scoped or not, so any other lifecycle the rows don't give fails. `scope` isn't covered by the chain, so naming one
|
|
184
|
+
never loosens a check on its own. A bundle exported without derived views verifies, and a verifier says it has none
|
|
185
|
+
(`views: none`). **Standing** (review state, support, at-risk, the sentence) can't
|
|
178
186
|
be checked from the rows alone: it is what the exporting engine derived, and so are the report, the workflow stages,
|
|
179
187
|
completion, the question board and work-order status. A verifier reports them as not checked, or re-derives them with
|
|
180
188
|
the same engine version (`hypgraph verify FILE --standing`, under the caller's policy with `--policy`) and compares.
|
|
@@ -90,6 +90,11 @@ separate implementation.
|
|
|
90
90
|
title and lifecycle, each link's ends and fields, the full investigation list — must be present in the derived
|
|
91
91
|
views, not only right where present: a view that drops a withdrawn claim's lifecycle fails, rather than being shown
|
|
92
92
|
as active. A bundle exported without derived views (`export(derived=False)`) verifies and says `views: none`.
|
|
93
|
+
- **A lifecycle that can't be checked isn't called checked.** One only hypgraph 0.3's rule gives verifies with
|
|
94
|
+
`views: historical`. In a scoped bundle with redacted events, a node shown superseded with no replacement in the
|
|
95
|
+
rows (a replacement in another investigation would be redacted) verifies with `views: unconfirmed`, and only if its
|
|
96
|
+
type can be superseded at all; investigations and reviews can't. Without redacted events every link is in the
|
|
97
|
+
bundle, so such a node fails. The warning names each node; the Explorer and the verify page say *views not checked*.
|
|
93
98
|
|
|
94
99
|
## Scoped bundles and references
|
|
95
100
|
|
|
@@ -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.
|
|
19
|
+
__version__ = "0.7.18"
|
|
20
20
|
|
|
21
21
|
from .bundle import anchored, verify_bundle # noqa: E402
|
|
22
22
|
from .graph import Author, Graph, GraphError, file_sha256 # noqa: E402
|
|
@@ -34,7 +34,7 @@ from pathlib import Path
|
|
|
34
34
|
from typing import TYPE_CHECKING, Any
|
|
35
35
|
|
|
36
36
|
from . import __version__
|
|
37
|
-
from .schema import SAME_TYPE
|
|
37
|
+
from .schema import RELS, SAME_TYPE
|
|
38
38
|
from .store import GENESIS, ROW_TABLES, check_rows, event_body, now
|
|
39
39
|
|
|
40
40
|
if TYPE_CHECKING:
|
|
@@ -241,13 +241,16 @@ def _verify_bundle(b: dict, policy: dict | None = None) -> dict:
|
|
|
241
241
|
problem = _scope_problem(b, tables, red)
|
|
242
242
|
if problem:
|
|
243
243
|
return {"ok": False, "problem": problem}
|
|
244
|
-
|
|
244
|
+
historical: list = []
|
|
245
|
+
unconfirmed: list = []
|
|
246
|
+
problem = _derived_problem(b, tables, historical, unconfirmed, red)
|
|
245
247
|
if problem:
|
|
246
248
|
return {"ok": False, "problem": f"the derived views contradict the verified record: {problem}"}
|
|
247
249
|
out = {"ok": True, "checked": checked, "head": prev, "complete": red == 0, "redacted": red,
|
|
248
250
|
"nodes": len(tables["nodes"]), "edges": len(tables["edges"]), "unchecked": rows["unchecked"],
|
|
249
251
|
"coverage": "complete" if not rows["unchecked"] and not red else "partial",
|
|
250
|
-
"views": "none" if b.get("derived") is None else "
|
|
252
|
+
"views": ("none" if b.get("derived") is None else "historical" if historical
|
|
253
|
+
else "unconfirmed" if unconfirmed else "checked"),
|
|
251
254
|
"policy": {"trusted": sorted(policy or {}), "importer": importer},
|
|
252
255
|
"standing": {"checked": False, "note": STANDING_NOTE}}
|
|
253
256
|
notes = []
|
|
@@ -259,11 +262,33 @@ def _verify_bundle(b: dict, policy: dict | None = None) -> dict:
|
|
|
259
262
|
if stated != importer:
|
|
260
263
|
notes.append(f"The bundle names {stated} as its importer; the chain doesn't cover that, so {importer} was "
|
|
261
264
|
"used.")
|
|
265
|
+
if historical:
|
|
266
|
+
notes.append(_historical_note(historical))
|
|
267
|
+
if unconfirmed:
|
|
268
|
+
notes.append(_unconfirmed_note(unconfirmed))
|
|
262
269
|
if notes:
|
|
263
270
|
out["warning"] = " ".join(notes)
|
|
264
271
|
return out
|
|
265
272
|
|
|
266
273
|
|
|
274
|
+
def _listed(names: list) -> str:
|
|
275
|
+
return "; ".join(names[:8]) + (f"; and {len(names) - 8} more" if len(names) > 8 else "")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _historical_note(historical: list) -> str:
|
|
279
|
+
shown = _listed([f"{name} shown {s}, the current rule makes it {w}" for name, s, w in historical])
|
|
280
|
+
return (f"The views are not checked: {len(historical)} lifecycle(s) follow hypgraph 0.3's rule, not the current "
|
|
281
|
+
f"one ({shown}). A bundle exported by 0.3 shows them so, but the chain doesn't record which version "
|
|
282
|
+
"exported a bundle, so this can't be told from an edited view. Go by the current rule.")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _unconfirmed_note(unconfirmed: list) -> str:
|
|
286
|
+
return (f"The views are not checked: {len(unconfirmed)} node(s) are shown superseded with no replacement in this "
|
|
287
|
+
f"bundle ({_listed(unconfirmed)}). A replacement in another investigation would be among the redacted "
|
|
288
|
+
"events, which keep only their hashes, so this can't be confirmed here: the rows make them active. Only "
|
|
289
|
+
"the full record, verified against the published head, settles it.")
|
|
290
|
+
|
|
291
|
+
|
|
267
292
|
def _j(v: Any) -> str:
|
|
268
293
|
"""A value in a problem, written as the browser's verifier writes it (JSON.stringify)."""
|
|
269
294
|
return json.dumps(v, ensure_ascii=False, separators=(",", ":"))
|
|
@@ -309,6 +334,10 @@ def _scope_problem(b: dict, tables: dict, redacted: int) -> str:
|
|
|
309
334
|
return ""
|
|
310
335
|
|
|
311
336
|
|
|
337
|
+
# the node types a supersedes or corrects link can point at: no other type can be superseded
|
|
338
|
+
REPLACEABLE = frozenset().union(*(RELS[r][1] for r in SAME_TYPE))
|
|
339
|
+
|
|
340
|
+
|
|
312
341
|
def _eid(v: Any) -> int | None:
|
|
313
342
|
"""A link id as the browser's verifier reads one (Number.isInteger): 7 and 7.0, not true or "7"."""
|
|
314
343
|
if isinstance(v, bool) or not isinstance(v, (int, float)) or not float(v).is_integer():
|
|
@@ -316,12 +345,24 @@ def _eid(v: Any) -> int | None:
|
|
|
316
345
|
return int(v)
|
|
317
346
|
|
|
318
347
|
|
|
319
|
-
def _derived_problem(b: dict, tables: dict
|
|
348
|
+
def _derived_problem(b: dict, tables: dict, historical: list | None = None, unconfirmed: list | None = None,
|
|
349
|
+
redacted: int = 0) -> str:
|
|
320
350
|
"""The derived views are presentation, but a viewer shows them beside a verified chain: every fact in them that
|
|
321
351
|
the rows fix — which investigations there are, which nodes and live links each has, each node's type, code,
|
|
322
352
|
author, time, version, title and lifecycle, each link's ends and fields — must be present and agree with the
|
|
323
353
|
verified rows. A view that leaves a fact out fails: a viewer would fill the gap with a default (a withdrawn claim
|
|
324
|
-
shown as active). The rest of standing can't be checked this way.
|
|
354
|
+
shown as active). The rest of standing can't be checked this way.
|
|
355
|
+
|
|
356
|
+
A lifecycle is checked against the current rule only. One that matches only 0.3's rule (the end of a chain of
|
|
357
|
+
replacements shown active) is no problem — a 0.3 export shows it so — but it isn't checked either: the chain
|
|
358
|
+
doesn't record which version exported a bundle, so it can't be told from an edited view. Each such node goes on
|
|
359
|
+
``historical`` as (name, shown, current), and the bundle's views are reported as historical, not checked.
|
|
360
|
+
|
|
361
|
+
A bundle with no redacted events holds every link, scoped or not, so a lifecycle the rows don't give fails. A
|
|
362
|
+
scoped bundle with redacted events leaves out links from other investigations, and one of them may replace a node:
|
|
363
|
+
a node of a type that can be replaced (``REPLACEABLE``) shown superseded with no replacement in the rows goes on
|
|
364
|
+
``unconfirmed`` by name, and the views are reported as unconfirmed, not checked. ``scope`` isn't covered by the
|
|
365
|
+
chain, so naming one never loosens anything on its own."""
|
|
325
366
|
d = b.get("derived")
|
|
326
367
|
if d is None:
|
|
327
368
|
return ""
|
|
@@ -373,13 +414,20 @@ def _derived_problem(b: dict, tables: dict) -> str:
|
|
|
373
414
|
st = x.get("state")
|
|
374
415
|
if not isinstance(st, dict) or "lifecycle" not in st:
|
|
375
416
|
return f"{where} shows {name} without its lifecycle"
|
|
376
|
-
w = lifecycle(n["id"], True)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
417
|
+
w, shown = lifecycle(n["id"], True), st["lifecycle"]
|
|
418
|
+
if shown == w:
|
|
419
|
+
return ""
|
|
420
|
+
if (shown == "superseded" and scope and redacted and n["id"] not in withdrawn
|
|
421
|
+
and n["type"] in REPLACEABLE):
|
|
422
|
+
# a replacement in another investigation is among the redacted events: possible, not confirmable
|
|
423
|
+
if unconfirmed is not None and name not in unconfirmed:
|
|
424
|
+
unconfirmed.append(name)
|
|
425
|
+
return ""
|
|
426
|
+
if shown == lifecycle(n["id"], False):
|
|
427
|
+
if historical is not None and (name, shown, w) not in historical:
|
|
428
|
+
historical.append((name, shown, w))
|
|
429
|
+
return ""
|
|
430
|
+
return f"{where} shows {name} as {_j(shown)}; the record makes it {_j(w)}"
|
|
383
431
|
by_inv = d.get("by_inv")
|
|
384
432
|
if not isinstance(by_inv, dict) or set(by_inv) != invs:
|
|
385
433
|
return "the views don't cover the record's investigations"
|
|
@@ -297,6 +297,11 @@ def cmd_verify(args) -> int:
|
|
|
297
297
|
text += "\n coverage: PARTIAL — not every row field is vouched for by its event (see below)"
|
|
298
298
|
if ok and res.get("views") == "none":
|
|
299
299
|
text += "\n no derived views: exported without them, so a viewer has nothing to show beyond the rows"
|
|
300
|
+
if ok and res.get("views") == "historical":
|
|
301
|
+
text += "\n views: NOT CHECKED — some lifecycles follow hypgraph 0.3's rule, not the current one (see the note)"
|
|
302
|
+
if ok and res.get("views") == "unconfirmed":
|
|
303
|
+
text += ("\n views: NOT CHECKED — some nodes are shown superseded by something outside this bundle, which "
|
|
304
|
+
"can't be confirmed here (see the note)")
|
|
300
305
|
if ok and res.get("unchecked"):
|
|
301
306
|
text += ("\n not covered (written before events hashed these fields): "
|
|
302
307
|
+ ", ".join(f"{f} ×{n}" for f, n in sorted(res["unchecked"].items())))
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
.hg .hg-search { display: flex; align-items: center; gap: 8px; width: 260px; padding: 6px 10px; border: 1px solid var(--line);
|
|
49
49
|
border-radius: 8px; background: var(--surface2); color: var(--ink3); cursor: pointer; }
|
|
50
50
|
.hg .hg-search kbd { margin-left: auto; font: 11px var(--mono); padding: 1px 6px; border: 1px solid var(--line2); border-radius: 4px; }
|
|
51
|
+
.hg .hg-lang { padding: 5px 6px; font-size: 12.5px; max-width: 112px; cursor: pointer; } /* the language menu */
|
|
51
52
|
.hg .btn { border: 1px solid var(--line); background: var(--surface); border-radius: 8px; padding: 6px 10px; cursor: pointer;
|
|
52
53
|
display: inline-flex; gap: 6px; align-items: center; white-space: nowrap; }
|
|
53
54
|
.hg .btn:hover { background: var(--sunken); } .hg .btn:disabled { opacity: .5; cursor: default; background: var(--surface); } .hg .btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
@@ -117,6 +118,7 @@
|
|
|
117
118
|
.hg .note { display: flex; gap: 10px; align-items: flex-start; padding: 9px 14px; border-radius: 9px; margin-bottom: 14px;
|
|
118
119
|
background: var(--accent-soft); color: var(--accent-ink); font-size: 12.5px; }
|
|
119
120
|
.hg .note.warn { background: var(--warn-soft); color: var(--warn); }
|
|
121
|
+
.hg .note[data-exported] { display: block; } /* one sentence with inline code: keep it one paragraph */
|
|
120
122
|
|
|
121
123
|
/* stages and gates */
|
|
122
124
|
.hg .bar { display: flex; gap: 3px; }
|