hypgraph 0.5.1__tar.gz → 0.5.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.5.1 → hypgraph-0.5.2}/CHANGELOG.md +31 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/PKG-INFO +23 -14
- {hypgraph-0.5.1 → hypgraph-0.5.2}/README.md +22 -13
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/profile-workflows-plan.md +1 -1
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/spec.md +6 -1
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/workflows/knowledge.md +5 -1
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/workflows/optimization.md +4 -1
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/workflows/software.md +6 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/workflows/trading.md +4 -2
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/__init__.py +1 -1
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/derive.py +32 -2
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/plan.py +13 -6
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/report_check.py +276 -2
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/workflow.py +52 -14
- hypgraph-0.5.2/tests/test_report_answers.py +184 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_workflows.py +98 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/.gitignore +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/AGENTS.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/CITATION.cff +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/LICENSE +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/NOTICE +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/PLAN.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/decisions/ADR-001-token-bound-authors.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/decisions/ADR-002-governance-profiles.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/review-2026-10-24.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/workflows/README.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/docs/workflows/scientific.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/pyproject.toml +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/__main__.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/bundle.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/checks.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/cli.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/coord.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/demos/__init__.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/demos/broad_street.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/graph.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/language.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/mcp.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/policy.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/profiles.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/refusals.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/schema.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/server.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/skill/SKILL.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/stats.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/store.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/__init__.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/atlas.css +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/atlas.js +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/hypgraph.svg +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/index.html +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/vendor/LICENSES.md +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/vendor/cose-base.js +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/vendor/cytoscape-fcose.js +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/vendor/cytoscape.min.js +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/src/hypgraph/ui/vendor/layout-base.js +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/studies/harmony/tests/test_suite.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_cancel_releases_lease.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_coord.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_d10.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_engine.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_engine_fixes.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_incremental.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_log_and_check.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_order_detail.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_profiles.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_profiles_041.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_questions_not_assertions.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_refusals.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_server_auth.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_stats_values.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_surfaces.py +0 -0
- {hypgraph-0.5.1 → hypgraph-0.5.2}/tests/test_vocabulary_friction.py +0 -0
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.2 — 2026-10-26: freshness for sources, stale evidence in the report
|
|
4
|
+
|
|
5
|
+
For P3 (the change suite and the instruments in HypSwarm), the record had to know when a *page* moved, not only a
|
|
6
|
+
file:
|
|
7
|
+
- **A source is fresh while its page still says it.** A host that re-fetches a source's page registers an
|
|
8
|
+
`excerpt` artifact (`derived_from` the source; `meta.contains_passage`, `meta.fetched`). The source is STALE when
|
|
9
|
+
every excerpt of it was superseded, or the live one says the page no longer contains its passage — an edit
|
|
10
|
+
elsewhere on the page keeps it fresh. A source never re-fetched is not stale: nothing says it moved.
|
|
11
|
+
- **A claim is stale when every result and every cited source behind it is.** The knowledge gates
|
|
12
|
+
(`source_quoted`, `sourced`) take only fresh sources, so a changed page reopens the question at *sourced*.
|
|
13
|
+
- **The report marks it.** Claims and results on stale evidence read `checked · STALE` in the facts tables, a new
|
|
14
|
+
open item lists them, the workflow section lists stale sources too, and the report check treats calling a stale
|
|
15
|
+
claim settled as `REPORT_OVERSTATES_STANDING`.
|
|
16
|
+
- **Gates count only what still speaks for the question.** `result_exists`, `backtested` and `solved` take only
|
|
17
|
+
fresh results (their message says which went stale and why); `result_reviewed` doesn't count a check of a stale
|
|
18
|
+
result; a decision is stale when everything it was based on is, and `positioned` / `decided` take only fresh ones
|
|
19
|
+
— a position taken on a backtest the vendor later corrected has to be decided again.
|
|
20
|
+
- **The report check reads the answers block.** A fenced ```answers block (the part of a report a harness scores and a
|
|
21
|
+
reader copies) was skipped like any code block, so a report could carry an answer only stale evidence gave and pass.
|
|
22
|
+
Now each answer has to be one a current claim on its question makes — live, not stale — at the precision written and
|
|
23
|
+
with its sign: `REPORT_ANSWER_STALE` when only old claims or stale results give it (or a current claim binds the
|
|
24
|
+
same field to another value), `REPORT_ANSWER_UNBACKED` when no claim does or the question has no claim,
|
|
25
|
+
`REPORT_ANSWERS_UNREADABLE` for invalid JSON. Results count through the claims that bind them, and a count k of n
|
|
26
|
+
backs its rate; a number a claim names only as a superseded figure doesn't back an answer; URLs, paths and hashes
|
|
27
|
+
are provenance. Keys are question codes in any case, or a flat object checked against the investigation. Found by
|
|
28
|
+
HypSwarm's change suite.
|
|
29
|
+
- **The report check skips the record's own facts.** A line inside a "Facts from the research graph" block is not
|
|
30
|
+
linted when it is exactly a line the record generates now (a hypothesis that says "significantly" is a conjecture
|
|
31
|
+
the record lists, not the author's assertion). Anything else written under that heading is checked as usual.
|
|
32
|
+
Found when a report was refused for the hypothesis line the record itself had written.
|
|
33
|
+
|
|
3
34
|
## 0.5.1 — 2026-10-26
|
|
4
35
|
|
|
5
36
|
What wiring HypSwarm's scheduler to the stages (P2) found the workflows had no honest ending or no right answer for:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hypgraph
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.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/
|
|
@@ -135,21 +135,30 @@ team, and they can verify it without trusting you.
|
|
|
135
135
|
## One record, five disciplines
|
|
136
136
|
|
|
137
137
|
The rules above are the scientific case written as code. A **profile** says what a domain means by evidence,
|
|
138
|
-
acceptance and done — and
|
|
139
|
-
|
|
138
|
+
acceptance and done — and, from 0.5, carries the discipline's own **workflow**: the stages a question passes
|
|
139
|
+
through, the gate out of each, what invalidates evidence, the typed shapes. The record enforces all of it, so none
|
|
140
|
+
of it depends on a prompt being followed. Pin one on the investigation (`"profile": "software@2"`); without one, the
|
|
141
|
+
record behaves exactly as before.
|
|
140
142
|
|
|
141
|
-
| profile |
|
|
143
|
+
| profile | the stages a question passes through | the record refuses | done when |
|
|
142
144
|
|---|---|---|---|
|
|
143
|
-
| `scientific@
|
|
144
|
-
| `software@
|
|
145
|
-
| `trading@
|
|
146
|
-
| `knowledge@
|
|
147
|
-
| `optimization@
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
| `scientific@2` | asked → conjectured → preregistered (**before** any run) → run → evidenced → claimed → reviewed | numbers no result accounts for, "never" without the count, "significant" without the test, a check on stale evidence | every question settled |
|
|
146
|
+
| `software@2` | specified → implemented (a diff by hash) → verified (**the instrument ran the tests on that head**) → claimed → reviewed | *"it passes"* without an instrument run, or on a superseded head; the diff's author as its reviewer | every requirement's claim rests on a passing run on the current head |
|
|
147
|
+
| `trading@2` | asked → thesised → preregistered → backtested (**out of sample, after costs, trials declared**) → claimed → risk-reviewed → positioned → monitored | a backtest without its costs; *"edge"* without both, or without the configurations tried; a position without size, stop, review date; any live order | questions settled, a position taken or declined, nothing past its review date |
|
|
148
|
+
| `knowledge@2` | asked → (searched) → sourced (**the passage**, retrieved, kind) → claimed → checked | a claim with no source; a source without its passage | every claim cites a source and is checked |
|
|
149
|
+
| `optimization@2` | posed (the instance by hash) → modelled → configured → solved → verified → claimed → checked | an objective without feasibility; *"optimal"* without a bound, or on a changed instance; a solve that uses no instance | a feasible bounded incumbent, verified on the current instance |
|
|
150
|
+
|
|
151
|
+
**The stage is derived, never set**: `g.stage(question)` reads it off the record (the first stage whose gate is not
|
|
152
|
+
met, every gate MET / NOT MET with what would meet it); `GET /api/graph/stages`; MCP tool `stage`. A workflow
|
|
153
|
+
widens what puts dependants AT RISK — `uses` (a corrected data file, a superseded diff or instance), `cites` (a
|
|
154
|
+
withdrawn source) — and evidence goes **stale** when the artifact it used is superseded or its `valid_until` has
|
|
155
|
+
passed. An honest "it can't be answered" (a checked `inconclusive` claim), a declined thesis (`no_position`) and a
|
|
156
|
+
verified failure each complete a question the gates alone never would.
|
|
157
|
+
|
|
158
|
+
Profiles are versioned and frozen: a record written under `trading@1` means the same thing in a year, and the `@1`
|
|
159
|
+
profiles stay exactly as they were. `completion(inv)` reports each criterion; `hypgraph serve` exposes
|
|
160
|
+
`/api/graph/completion` and `/api/graph/profiles`. The method each workflow is taken from is quoted, one page each,
|
|
161
|
+
in [docs/workflows/](docs/workflows/). Adding a discipline is one `Profile` with a `Workflow`.
|
|
153
162
|
|
|
154
163
|
## Connect your agent
|
|
155
164
|
|
|
@@ -103,21 +103,30 @@ team, and they can verify it without trusting you.
|
|
|
103
103
|
## One record, five disciplines
|
|
104
104
|
|
|
105
105
|
The rules above are the scientific case written as code. A **profile** says what a domain means by evidence,
|
|
106
|
-
acceptance and done — and
|
|
107
|
-
|
|
106
|
+
acceptance and done — and, from 0.5, carries the discipline's own **workflow**: the stages a question passes
|
|
107
|
+
through, the gate out of each, what invalidates evidence, the typed shapes. The record enforces all of it, so none
|
|
108
|
+
of it depends on a prompt being followed. Pin one on the investigation (`"profile": "software@2"`); without one, the
|
|
109
|
+
record behaves exactly as before.
|
|
108
110
|
|
|
109
|
-
| profile |
|
|
111
|
+
| profile | the stages a question passes through | the record refuses | done when |
|
|
110
112
|
|---|---|---|---|
|
|
111
|
-
| `scientific@
|
|
112
|
-
| `software@
|
|
113
|
-
| `trading@
|
|
114
|
-
| `knowledge@
|
|
115
|
-
| `optimization@
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
113
|
+
| `scientific@2` | asked → conjectured → preregistered (**before** any run) → run → evidenced → claimed → reviewed | numbers no result accounts for, "never" without the count, "significant" without the test, a check on stale evidence | every question settled |
|
|
114
|
+
| `software@2` | specified → implemented (a diff by hash) → verified (**the instrument ran the tests on that head**) → claimed → reviewed | *"it passes"* without an instrument run, or on a superseded head; the diff's author as its reviewer | every requirement's claim rests on a passing run on the current head |
|
|
115
|
+
| `trading@2` | asked → thesised → preregistered → backtested (**out of sample, after costs, trials declared**) → claimed → risk-reviewed → positioned → monitored | a backtest without its costs; *"edge"* without both, or without the configurations tried; a position without size, stop, review date; any live order | questions settled, a position taken or declined, nothing past its review date |
|
|
116
|
+
| `knowledge@2` | asked → (searched) → sourced (**the passage**, retrieved, kind) → claimed → checked | a claim with no source; a source without its passage | every claim cites a source and is checked |
|
|
117
|
+
| `optimization@2` | posed (the instance by hash) → modelled → configured → solved → verified → claimed → checked | an objective without feasibility; *"optimal"* without a bound, or on a changed instance; a solve that uses no instance | a feasible bounded incumbent, verified on the current instance |
|
|
118
|
+
|
|
119
|
+
**The stage is derived, never set**: `g.stage(question)` reads it off the record (the first stage whose gate is not
|
|
120
|
+
met, every gate MET / NOT MET with what would meet it); `GET /api/graph/stages`; MCP tool `stage`. A workflow
|
|
121
|
+
widens what puts dependants AT RISK — `uses` (a corrected data file, a superseded diff or instance), `cites` (a
|
|
122
|
+
withdrawn source) — and evidence goes **stale** when the artifact it used is superseded or its `valid_until` has
|
|
123
|
+
passed. An honest "it can't be answered" (a checked `inconclusive` claim), a declined thesis (`no_position`) and a
|
|
124
|
+
verified failure each complete a question the gates alone never would.
|
|
125
|
+
|
|
126
|
+
Profiles are versioned and frozen: a record written under `trading@1` means the same thing in a year, and the `@1`
|
|
127
|
+
profiles stay exactly as they were. `completion(inv)` reports each criterion; `hypgraph serve` exposes
|
|
128
|
+
`/api/graph/completion` and `/api/graph/profiles`. The method each workflow is taken from is quoted, one page each,
|
|
129
|
+
in [docs/workflows/](docs/workflows/). Adding a discipline is one `Profile` with a `Workflow`.
|
|
121
130
|
|
|
122
131
|
## Connect your agent
|
|
123
132
|
|
|
@@ -95,7 +95,7 @@ instrument, mechanical feasibility recomputation (P3). **P2 built in HypSwarm 0.
|
|
|
95
95
|
orders, review on the node the previous gate produced, the record's word on done; the examples' scripted models walk
|
|
96
96
|
the stages; the discovery stub run through its stages). P2 fed back into hypgraph 0.5.1: honest endings the stages
|
|
97
97
|
lacked (a declined thesis, an inconclusive answer, a verified failure), artifacts that learn their kind on
|
|
98
|
-
re-registration, optimization's `checked` stage. P3
|
|
98
|
+
re-registration, optimization's `checked` stage. **P3 built (2026-10-26):** the instruments in HypSwarm 0.1.0a5 (world watcher, re-verification, source re-fetch, refresh orders) and hypgraph 0.5.2 (source freshness, fresh-only gates, stale in the report); the change suite in HypSwarm `studies/discovery/PLAN-CHANGE.md`, self-test and stub run at $0, predictions locked; the model run waits for the spend approval.
|
|
99
99
|
|
|
100
100
|
## 4. What stays as it is, on purpose
|
|
101
101
|
|
|
@@ -209,7 +209,9 @@ A report aggregates per author (refused, fixed, open, codes) and per rule.
|
|
|
209
209
|
## 9. Checking a write-up against the record
|
|
210
210
|
|
|
211
211
|
A checker reads markdown sentence by sentence and checks it against the record. Code blocks, inline code, URLs, link
|
|
212
|
-
targets and HTML comments are skipped
|
|
212
|
+
targets and HTML comments are skipped, except a fenced block whose info string is `answers`: that is the report's
|
|
213
|
+
machine-read answer (a JSON object, keyed by question code or flat), and every number or true / false in it is checked
|
|
214
|
+
against the claims. Quoted passages and negated words ("cannot be proven") are not treated as
|
|
213
215
|
the writer's assertion. Codes are matched within the investigation being reported on.
|
|
214
216
|
|
|
215
217
|
| level | code | when |
|
|
@@ -220,6 +222,9 @@ the writer's assertion. Codes are matched within the investigation being reporte
|
|
|
220
222
|
| error | `REPORT_ABSOLUTE_UNBACKED` | never / always / impossible / guaranteed / eliminates / proven / 100% asserted, not in an instruction, with no cited claim carrying a count with k ∈ {0, n} |
|
|
221
223
|
| error | `REPORT_ABSENCE_UNBACKED` | "no effect" / "rules out" / "doesn't matter" without citing a claim filed as `exclusion` or `inconclusive` |
|
|
222
224
|
| error | `REPORT_SIGNIFICANCE_UNBACKED` | "significantly" without a p-value or interval in the sentence and without citing a node that carries a test (a negated "not significant" is a warning) |
|
|
225
|
+
| error | `REPORT_ANSWER_STALE` | a value in the `answers` block that only stale, superseded or withdrawn claims (or stale results) on that question give, or that a current claim binds to a different value under the same field while an old one binds it to this; or a question whose every claim is old |
|
|
226
|
+
| error | `REPORT_ANSWER_UNBACKED` | a value in the `answers` block that no current claim on the question states or binds, at the precision the answer gives it and with its sign (a count k of n, bound or written as "k of n", backs its rate); or a question answered with no claim at all. A number a claim names only as a figure that no longer holds ("the earlier count of 789 is superseded") doesn't back it. URLs, paths and hashes in the block are provenance and aren't checked. |
|
|
227
|
+
| error | `REPORT_ANSWERS_UNREADABLE` | the `answers` block isn't valid JSON |
|
|
223
228
|
| warning | `REPORT_UNSOURCED_NUMBER` | a number not contained in or implied by the investigation's results, live claims' measurements, statements and scopes, the investigation's scope and constants, or experiment and run settings. Integers ≤ 12, years, dates, times and 7-digit+ ids are skipped. Statistical quantities need one decimal. |
|
|
224
229
|
| warning | `REPORT_OMITS_DISPUTE` | cites a contested or at-risk node without saying so |
|
|
225
230
|
| warning | `REPORT_CITES_SUPERSEDED` | cites a superseded node without saying so |
|
|
@@ -73,7 +73,11 @@ review.
|
|
|
73
73
|
|
|
74
74
|
1. *Is `searched` required?* **Optional** (reported, never blocks); a task that wants a systematic answer puts
|
|
75
75
|
`search_recorded` in its `done_when`.
|
|
76
|
-
2. *The re-fetch check?* **P3
|
|
76
|
+
2. *The re-fetch check?* **Built in P3** (hypgraph 0.5.2 + HypSwarm 0.1.0a5): a host's `refetch(url)` files each
|
|
77
|
+
source's page as an `excerpt` artifact with `contains_passage`; a page that no longer says the passage makes the
|
|
78
|
+
source and the claims citing it stale, and the question returns to *sourced*. `NUMBER_NOT_IN_PASSAGE` is not a
|
|
79
|
+
separate refusal: a claim number that is in no bound result and not in the cited passage is already
|
|
80
|
+
`UNBOUND_NUMBER`.
|
|
77
81
|
3. *Can the fact-checker re-kind a source?* **Yes**: `corrects` now joins sources; the correcting source carries
|
|
78
82
|
the kind, and the old one is superseded.
|
|
79
83
|
|
|
@@ -72,7 +72,10 @@ recomputed feasibility.
|
|
|
72
72
|
|
|
73
73
|
1. *The model: hypothesis or experiment?* **Hypothesis** (`modelled`, gate `hypothesis_stated`): it can be wrong
|
|
74
74
|
independently of the solve and is revisable with its own version.
|
|
75
|
-
2. *Mechanical feasibility recomputation?* **P3
|
|
75
|
+
2. *Mechanical feasibility recomputation?* **Built in P3 as re-execution**: the instrument re-runs the solver's
|
|
76
|
+
registered code on the instance as it is now (a changed instance is a new artifact that supersedes the old), so
|
|
77
|
+
objective, feasibility and bound are recomputed mechanically; a domain-specific `check_solution` reading a
|
|
78
|
+
solution artifact stays a host hook for later. Built in P1: `result_reviewed` —
|
|
76
79
|
the verifier's check on the result, by someone who is not its author — and `incumbent_verified` needs it on the
|
|
77
80
|
current instance.
|
|
78
81
|
3. *Sensitivity?* **An optional `sensitivity` field on the decision.**
|
|
@@ -77,6 +77,12 @@ superseded heads, listed as superseded); open requirements.
|
|
|
77
77
|
|
|
78
78
|
Also decided: `REVIEW_BY_AUTHOR` covers the diff's author (built); `supersedes` joins artifacts (built).
|
|
79
79
|
|
|
80
|
+
**CI as the instrument (P3, HypSwarm 0.1.0a5).** `run(..., watch=True)` keeps the working tree of the task's
|
|
81
|
+
files as one `kind=diff` artifact that every commit supersedes; a run under an order uses the question's current
|
|
82
|
+
head; when a head moves, the instrument re-runs every stale result's command on the new tree — the same values bring
|
|
83
|
+
the claim back with no model call, a moved count leaves it stale and the question is redone. That is the CI rule
|
|
84
|
+
("tests on this commit"), kept by the record rather than by a pipeline.
|
|
85
|
+
|
|
80
86
|
**Verified failure (0.5.1).** When the instrument's run on the current head records a negative result ("1 of 2
|
|
81
87
|
checks pass"), `tests_pass_current` can never be met; a checked claim resting on that result settles the
|
|
82
88
|
requirement as a verified failure (`stage()` says so), and `requirements_verified_current` stays unmet. The record
|
|
@@ -70,8 +70,10 @@ passed without a newer result.
|
|
|
70
70
|
|
|
71
71
|
## Review questions and decisions (2026-10-26)
|
|
72
72
|
|
|
73
|
-
1. *Will agents report `trials` honestly?* **Required
|
|
74
|
-
need
|
|
73
|
+
1. *Will agents report `trials` honestly?* **Required, and checked like any value**: `backtested` and
|
|
74
|
+
`EDGE_WITHOUT_TRIALS` need it, and the instrument's re-run confirms or contradicts the printed `trials` as it does
|
|
75
|
+
every registered value. Counting a parameter grid statically from arbitrary code was not built (P3): it can't be
|
|
76
|
+
done reliably, and a wrong count would be worse than the confirmed one.
|
|
75
77
|
2. *`review_date` default?* **Required, no default** (`POSITION_WITHOUT_STOP` names what is missing).
|
|
76
78
|
3. *Who raises a regime notice?* **The host or the risk seat**: it is a withdrawal (or supersession) of the data
|
|
77
79
|
artifact, which only they write; `uses` then carries it to every position.
|
|
@@ -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.5.
|
|
19
|
+
__version__ = "0.5.2"
|
|
20
20
|
|
|
21
21
|
from .bundle import verify_bundle # noqa: E402
|
|
22
22
|
from .graph import Author, Graph, GraphError, file_sha256 # noqa: E402
|
|
@@ -163,11 +163,32 @@ def downstream(s: Snap, x: str) -> list[tuple[str, str]]:
|
|
|
163
163
|
return downs
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
def excerpts(s: Snap, sid: str) -> list[str]:
|
|
167
|
+
"""The pages a host fetched for a source: artifacts ``kind=excerpt`` derived_from it (oldest first)."""
|
|
168
|
+
return [e["src"] for e in s.inn.get(sid, []) if e["rel"] == "derived_from"
|
|
169
|
+
and s.nodes[e["src"]]["type"] == "artifact" and (s.nodes[e["src"]]["body"].get("kind") or "") == "excerpt"]
|
|
170
|
+
|
|
171
|
+
|
|
166
172
|
def stale(s: Snap, nid: str) -> list[str]:
|
|
167
173
|
"""Why a result (or a claim through its results) no longer speaks for what it measured; [] when fresh. A result
|
|
168
174
|
is stale when the run that produced it used an artifact that is now superseded or withdrawn, or when its
|
|
169
|
-
``valid_until`` has passed. A
|
|
175
|
+
``valid_until`` has passed. A source is stale when the host re-fetched its page and the page as it is now no
|
|
176
|
+
longer contains its passage (the live excerpt says ``contains_passage: false``), or every excerpt of it was
|
|
177
|
+
superseded. A claim is stale when it has results or cited sources and every one of them is stale."""
|
|
170
178
|
n = s.nodes[nid]
|
|
179
|
+
if n["type"] == "source":
|
|
180
|
+
ex = excerpts(s, nid)
|
|
181
|
+
if not ex:
|
|
182
|
+
return [] # never re-fetched: nothing says the page moved
|
|
183
|
+
live = [a for a in ex if lifecycle(s, a) == "active"]
|
|
184
|
+
if not live:
|
|
185
|
+
return [f"its page changed since it was read ({', '.join(s.nodes[a]['code'] or a for a in ex[-2:])} "
|
|
186
|
+
"superseded)"]
|
|
187
|
+
meta = [s.nodes[a]["body"].get("meta") or {} for a in live]
|
|
188
|
+
if all(m.get("contains_passage") is False for m in meta):
|
|
189
|
+
when = max((str(m.get("fetched") or "") for m in meta), default="")
|
|
190
|
+
return [f"the page no longer contains its passage (re-fetched{' ' + when if when else ''})"]
|
|
191
|
+
return []
|
|
171
192
|
if n["type"] == "result":
|
|
172
193
|
out = []
|
|
173
194
|
run = producing_run(s, nid)
|
|
@@ -183,11 +204,20 @@ def stale(s: Snap, nid: str) -> list[str]:
|
|
|
183
204
|
if vu and s.now and s.now[:len(vu)] > vu:
|
|
184
205
|
out.append(f"valid_until {vu} has passed")
|
|
185
206
|
return out
|
|
207
|
+
if n["type"] == "decision":
|
|
208
|
+
# a decision is stale when everything it was based on is (a position taken on a backtest the vendor corrected)
|
|
209
|
+
basis = [e["dst"] for e in s.out.get(nid, []) if e["rel"] == "based_on" and not fallen(s, e["dst"])]
|
|
210
|
+
reasons = [stale(s, b) for b in basis]
|
|
211
|
+
if basis and all(reasons):
|
|
212
|
+
return list(dict.fromkeys(x for r in reasons for x in r))
|
|
213
|
+
return []
|
|
186
214
|
if n["type"] == "claim":
|
|
187
215
|
rs = [e["src"] for e in s.inn.get(nid, []) if e["rel"] == "supports" and s.nodes[e["src"]]["type"] == "result"
|
|
188
216
|
and not fallen(s, e["src"])]
|
|
189
217
|
rs += [e["dst"] for e in s.out.get(nid, []) if e["rel"] == "derived_from" and s.nodes[e["dst"]]["type"] == "result"
|
|
190
218
|
and not fallen(s, e["dst"])]
|
|
219
|
+
rs += [e["dst"] for e in s.out.get(nid, []) if e["rel"] == "cites" and s.nodes[e["dst"]]["type"] == "source"
|
|
220
|
+
and lifecycle(s, e["dst"]) == "active" and not fallen(s, e["dst"])]
|
|
191
221
|
rs = list(dict.fromkeys(rs))
|
|
192
222
|
if not rs:
|
|
193
223
|
return []
|
|
@@ -480,7 +510,7 @@ def state(s: Snap, nid: str) -> dict:
|
|
|
480
510
|
moved = basis_changed(s, nid)
|
|
481
511
|
if moved:
|
|
482
512
|
st["basis_changed"] = moved
|
|
483
|
-
if t in ("result", "claim"):
|
|
513
|
+
if t in ("result", "claim", "source", "decision"):
|
|
484
514
|
old = stale(s, nid)
|
|
485
515
|
if old:
|
|
486
516
|
st["stale"] = old
|
|
@@ -260,9 +260,10 @@ def report(g: "Graph", inv: str) -> dict:
|
|
|
260
260
|
st = D.state(s, c)
|
|
261
261
|
claims.append({**_brief_min(s, c), "lifecycle": st["lifecycle"], "review": (st.get("review") or {}).get("state"),
|
|
262
262
|
"verdicts": verdicts(c), "v": s.nodes[c]["v"], "evidence": st.get("evidence"),
|
|
263
|
-
"lines": st.get("independent_lines")})
|
|
263
|
+
"lines": st.get("independent_lines"), "stale": list(st.get("stale") or [])})
|
|
264
264
|
results = [{**_brief_min(s, r), "outcome": D.state(s, r).get("outcome"), "by": s.nodes[r]["created_by"],
|
|
265
|
-
"in_a_claim": r in cited_by_claim, "lifecycle": D.state(s, r)["lifecycle"]
|
|
265
|
+
"in_a_claim": r in cited_by_claim, "lifecycle": D.state(s, r)["lifecycle"],
|
|
266
|
+
"stale": list(D.state(s, r).get("stale") or [])} for r in by_type["result"]]
|
|
266
267
|
hyps = [{**_brief_min(s, h), "lifecycle": D.state(s, h)["lifecycle"],
|
|
267
268
|
"support": D.state(s, h).get("independent_lines"), "challenges": D.state(s, h).get("challenges")}
|
|
268
269
|
for h in by_type["hypothesis"]]
|
|
@@ -277,6 +278,8 @@ def report(g: "Graph", inv: str) -> dict:
|
|
|
277
278
|
open_items = {
|
|
278
279
|
"claims_unreviewed": [c["code"] for c in live if c["review"] in (None, "unchecked", "needs_revalidation")],
|
|
279
280
|
"claims_contested": [c["code"] for c in live if c["review"] == "contested"],
|
|
281
|
+
# a check made on evidence that has since gone stale (a file superseded, a page changed) is not a check now
|
|
282
|
+
"claims_stale": [c["code"] for c in live if c["stale"]],
|
|
280
283
|
"results_in_no_claim": [r["code"] for r in results if not r["in_a_claim"] and r["lifecycle"] != "withdrawn"
|
|
281
284
|
and r["by"] != g.policy.instrument],
|
|
282
285
|
"experiments_not_preregistered": [x["code"] for x in exps if not x["preregistered"]],
|
|
@@ -295,7 +298,7 @@ def report(g: "Graph", inv: str) -> dict:
|
|
|
295
298
|
prof = profiles.profile_of(s, inv)
|
|
296
299
|
if prof and prof.workflow:
|
|
297
300
|
stale = [{"code": s.nodes[i]["code"], "type": s.nodes[i]["type"], "why": D.state(s, i)["stale"]}
|
|
298
|
-
for i in by_type["result"] + by_type["claim"] if D.state(s, i).get("stale")
|
|
301
|
+
for i in by_type["result"] + by_type["claim"] + by_type["source"] if D.state(s, i).get("stale")
|
|
299
302
|
and D.state(s, i)["lifecycle"] != "withdrawn"]
|
|
300
303
|
out["workflow"] = {"profile": prof.id, "report": prof.workflow.report, "method": prof.workflow.method,
|
|
301
304
|
"questions": workflow.stages(s, inv, s.now), "stale": stale}
|
|
@@ -310,11 +313,13 @@ def report_markdown(r: dict) -> str:
|
|
|
310
313
|
"Interpretation goes below this section._", "", "### Claims", "",
|
|
311
314
|
"| claim | standing | review | verdicts | statement |", "|---|---|---|---|---|"]
|
|
312
315
|
for c in r["claims"]:
|
|
313
|
-
|
|
316
|
+
rv = (c['review'] or '-') + (" · STALE" if c.get("stale") else "")
|
|
317
|
+
L.append(f"| {c['code']} v{c['v']} | {c['lifecycle']} | {rv} | "
|
|
314
318
|
f"{'; '.join(c['verdicts']) or '-'} | {cut(c['title'])} |")
|
|
315
319
|
L += ["", "### Results", "", "| result | outcome | by | carried by a claim | summary |", "|---|---|---|---|---|"]
|
|
316
320
|
for x in r["results"]:
|
|
317
|
-
|
|
321
|
+
oc = (x['outcome'] or '-') + (" · STALE" if x.get("stale") else "")
|
|
322
|
+
L.append(f"| {x['code']} | {oc} | {x['by']} | {'yes' if x['in_a_claim'] else 'NO'} | "
|
|
318
323
|
f"{cut(x['title'])} |")
|
|
319
324
|
L += ["", "### Hypotheses", ""]
|
|
320
325
|
L += [f"- {h['code']} ({h['lifecycle']}; support lines {h['support']}, challenges {h['challenges']}): "
|
|
@@ -329,12 +334,14 @@ def report_markdown(r: dict) -> str:
|
|
|
329
334
|
for q in w["questions"]:
|
|
330
335
|
nxt = "complete" if q["complete"] else cut(q["next"], 140)
|
|
331
336
|
L.append(f"| {q['code']} | {q['stage']} ({q['index']}/{q['of']}) | {nxt} |")
|
|
332
|
-
L += ["", "Stale evidence (
|
|
337
|
+
L += ["", "Stale evidence (a file it used was superseded or withdrawn, its page changed, or valid_until passed): "
|
|
333
338
|
+ ("; ".join(f"{x['code']} — {x['why'][0]}" for x in w["stale"]) if w["stale"] else "none")]
|
|
334
339
|
o = r["open"]
|
|
335
340
|
L += ["", "### Open items", "",
|
|
336
341
|
f"- claims not yet reviewed: {', '.join(o['claims_unreviewed']) or 'none'}",
|
|
337
342
|
f"- claims contested: {', '.join(o['claims_contested']) or 'none'}",
|
|
343
|
+
f"- claims on stale evidence (what they rest on has changed since; a check of them no longer counts): "
|
|
344
|
+
f"{', '.join(o.get('claims_stale') or []) or 'none'}",
|
|
338
345
|
f"- results no claim carries (their conclusions are unreviewed): {', '.join(o['results_in_no_claim']) or 'none'}",
|
|
339
346
|
f"- experiments not preregistered: {', '.join(o['experiments_not_preregistered']) or 'none'}"]
|
|
340
347
|
sp = r["spend"]
|