Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Entries are newest-last within a release, matching the order they were written.
- **the lockfile's copy of the version drifted, twice, unnoticed.** `ci.yml` checks that `pyproject.toml` and `grapharc.__version__` agree, and its comment says why: if they drift, `pip show` and `import` disagree about what is installed. There is a third copy — `uv.lock` carries an entry for this project — and both 0.1.6 and 0.1.7 shipped with it reading `0.1.5`, because nothing re-locked after the bump and no job looked. Milder than the other two, since it misreports the project to a reader of the lockfile and to `uv sync --locked` rather than to an installed import, but the same class of bug as the one that step already guards. The check now parses `uv.lock` as well, names `uv lock` as the remedy, and refuses anything other than exactly one entry for the project so a rename cannot make it silently vacuous.
- **an imported retraction came back as current on the LadybugDB backend.** `superseded_by` was reconstructed from the `SUPERSEDED_BY` edge, and `add()` cannot create an edge towards a claim the store does not have yet — in the natural import order it does not have it, because `all_claims()` returns oldest-first, so a superseded claim arrives before the claim that superseded it. The edge was skipped while `superseded_at` was written, so one row said both things at once: `superseded_at` said retracted and `superseded_by IS NULL` said current, and `Claim.is_current` believed the second. `current("svc")` — which is supposed to answer "what is true now" — returned *both sides of a correction*, and the two other backends disagreed with this one. Every import, replay, backup restore and copy between backends went through that path; `supersede()` never did, which is why every test that exercised it passed. `superseded_by` is a column now and the edge is derived from it: reads project the column, `current()` filters on the column rather than on an `OPTIONAL MATCH`, and `_write` reconciles the edge in both directions — forward when this claim names a superseder that is present, backward when a stored claim names *this* one and could not have an edge until now — so the edge is complete once both ends have arrived, whatever order they arrived in, and it stays walkable for the Cypher path this backend exists for. Two alternatives were costed and rejected: failing closed breaks replaying `all_claims()` in the order it hands you, and a stub target node reads back as a `ValidationError` and has no `seq`. The column needs a schema change, which `CREATE NODE TABLE IF NOT EXISTS` will not perform on an existing database, so opening one adds the column and backfills it from the edges an older database does have — `supersede()` always wrote those. What `add()` dropped is not recoverable; what the edges recorded is.
- **a session whose runner died stayed wedged forever.** Not a defect so much as a limitation the package documented and then could not do anything about, which comes to the same thing for whoever hits it. A runner's claim on a session is a compare-and-set, not a lease: it stops a *second* runner from claiming a session and cannot notice one that died holding it. So a crash mid-turn left the row `running` — with the dead `runner_pid` still on it, meaning the store knew exactly who died — and every later `Session.run()` raised `SessionBusy` for the life of the file. The only remedy was a hand-written `UPDATE`, which skips the lifecycle validation and writes no transition row, corrupting the audit trail the store exists to keep. `SessionManager.reclaim(session_id)` releases one deliberately now: it refuses unless the recorded pid is gone from this host, moves the session to `failed` — not `interrupted`, because a turn that died left no point to be picked up from — and records the release as a transition naming the dead pid, so the reclaim is in the history rather than hidden from it. **An open approval hold survives**, so reclaiming a wedged session is not a way past a human. Four things are refused, each deliberately: a session that is not running, a pid that is still alive (or belongs to another user, which is read as alive), a pid that is this process, and a `running` row carrying no pid at all. Pid reuse is not solved and is not pretended to be — a recycled pid reads as alive, which is a refusal, and that is the direction to fail in. Nothing calls this automatically for the same reason: an automatic sweep would be a way for two live runners to fight over one session, which is what the claim exists to prevent.
- **nothing shipped showed the planner decomposing a goal.** `plan_incident` was the only registry in the box and it exists to demonstrate *refusal*: four chain-shaped kinds and one denied edge. So the other half of the planner's contract — a model turning one goal into a **parallel** topology — had no runnable example, and the honest reading of that is that the headline capability was the undemonstrated one. `grapharc.examples.plan_research` ships it, contributed by @only-ouc in #115: four evidence collectors fan out from `START`, join at `correlate`, fork into `test_hypothesis` and `estimate_impact`, and fan back in to `write_report` — eight nodes, thirteen edges, admitted, and runnable with no model or API key via `--scripted`. Each kind may write only its own state field, and a registered `page_oncall` kind stays policy-denied, so the registry demonstrates decomposition *and* refusal rather than trading one lesson for the other. Closes #51.

Tooling, in the same release and not defects in the shipped package: the suite
now runs weekly against dependencies re-resolved from scratch rather than only
Expand All @@ -31,7 +32,11 @@ a month of being red for a reason that was not its code. The styling gate — th
stripping the escapes from terminal output reproduces piped output exactly — now
covers `run`, `trace` and `metrics` as well as the four commands it started
with; those three are the ones most likely to be piped, and a leak confined to
any of them left the old gate entirely green.
any of them left the old gate entirely green. And the weekly dependency-drift
job now installs the built wheel into a clean environment and imports every
subpackage from it, not just running the suite from the source tree — the
failure #101 was only ever visible in an installed wheel, and `uv.lock` hides it
from every other job (#103).

## 0.1.7

Expand Down
Loading