toolgraph 0.1.0__tar.gz → 0.2.0__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.
Files changed (59) hide show
  1. toolgraph-0.2.0/CHANGELOG.md +386 -0
  2. {toolgraph-0.1.0 → toolgraph-0.2.0}/PKG-INFO +227 -62
  3. {toolgraph-0.1.0 → toolgraph-0.2.0}/README.md +224 -60
  4. toolgraph-0.2.0/contracts/control-preflight.schema.json +128 -0
  5. toolgraph-0.2.0/contracts/observation-window.schema.json +113 -0
  6. {toolgraph-0.1.0 → toolgraph-0.2.0}/pyproject.toml +41 -15
  7. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/__init__.py +1 -1
  8. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/artifact_safety.py +8 -10
  9. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/artifacts.py +33 -16
  10. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/cli.py +204 -92
  11. toolgraph-0.2.0/toolgraph/config.py +227 -0
  12. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/control_plan.py +245 -76
  13. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/crawler/client.py +2 -5
  14. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/crawler/crawl.py +4 -6
  15. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/crawler/transports.py +13 -4
  16. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/graph/driver.py +104 -22
  17. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/graph/loader.py +2 -3
  18. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/graph/queries.py +186 -265
  19. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/graph/schema.py +129 -11
  20. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/graph/selector.py +33 -58
  21. toolgraph-0.2.0/toolgraph/health.py +229 -0
  22. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/manifest/ingest.py +452 -51
  23. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/models.py +11 -0
  24. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/policy_bundle.py +18 -24
  25. toolgraph-0.2.0/toolgraph/policy_propose.py +953 -0
  26. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/policy_review.py +25 -23
  27. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/preflight.py +8 -9
  28. toolgraph-0.2.0/toolgraph/quickstart/README.md +55 -0
  29. toolgraph-0.2.0/toolgraph/quickstart/governance.yaml +63 -0
  30. toolgraph-0.2.0/toolgraph/redaction.py +128 -0
  31. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/review_candidates.py +56 -93
  32. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/server/app.py +4 -13
  33. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/uris.py +1 -0
  34. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/yaml_loader.py +1 -3
  35. toolgraph-0.1.0/CHANGELOG.md +0 -153
  36. toolgraph-0.1.0/contracts/control-preflight.schema.json +0 -75
  37. toolgraph-0.1.0/toolgraph/config.py +0 -106
  38. toolgraph-0.1.0/toolgraph/quickstart/governance.yaml +0 -28
  39. toolgraph-0.1.0/toolgraph/redaction.py +0 -82
  40. {toolgraph-0.1.0 → toolgraph-0.2.0}/.gitignore +0 -0
  41. {toolgraph-0.1.0 → toolgraph-0.2.0}/LICENSE +0 -0
  42. {toolgraph-0.1.0 → toolgraph-0.2.0}/SECURITY.md +0 -0
  43. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/control-plan.schema.json +0 -0
  44. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/policy-bundle.schema.json +0 -0
  45. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/policy-review-plan.schema.json +0 -0
  46. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/preflight.schema.json +0 -0
  47. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/review-annotations.schema.json +0 -0
  48. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/tracegraph/README.md +0 -0
  49. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/tracegraph/review-candidates.schema.json +0 -0
  50. {toolgraph-0.1.0 → toolgraph-0.2.0}/contracts/tracegraph/v1.json +0 -0
  51. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/crawler/__init__.py +0 -0
  52. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/errors.py +0 -0
  53. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/graph/__init__.py +0 -0
  54. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/graph/store.py +0 -0
  55. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/manifest/__init__.py +0 -0
  56. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/manifest/parser.py +0 -0
  57. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/quickstart/policy_gateway_server.py +0 -0
  58. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/quickstart/servers.yaml +0 -0
  59. {toolgraph-0.1.0 → toolgraph-0.2.0}/toolgraph/server/__init__.py +0 -0
@@ -0,0 +1,386 @@
1
+ # Changelog
2
+
3
+ All notable changes to Toolgraph are documented here.
4
+
5
+ ## 0.2.0 - 2026-10-15
6
+
7
+ Third alpha. Read **Upgrading from 0.1.0** below before installing: it lists
8
+ seven changes an existing setup, checkout or embedding program can notice.
9
+ The one most installations meet first is that commands now refuse a backend
10
+ variable that disagrees with the config file.
11
+
12
+ ### Upgrading from 0.1.0
13
+
14
+ - Commands stop with `ERROR: conflicting backend configuration (...)` when
15
+ `TOOLGRAPH_BACKEND` or `TOOLGRAPH_DB_PATH` disagrees with the config file;
16
+ 0.1.0 silently let the variable win. Pass `toolgraph --config <file>
17
+ <command>`, or unset the variable; when it comes from `.env`, remove it
18
+ there, since the next command loads `.env` again. See the first bullet
19
+ under Changed.
20
+ - `control-preflight` can now return `advisory_warn` for a plan that 0.1.0
21
+ answered with `advisory_allow`. See the `control-preflight` bullet under
22
+ Changed before relying on `advisory_allow` as a gate.
23
+ - A program that imports Toolgraph no longer gets `./.env` loaded for it.
24
+ - `toolgraph check` no longer creates a missing Ladybug database; it exits 1.
25
+ A script that used `check` to initialize should run `toolgraph init` or
26
+ `init-schema` first. See the `check` bullet under Changed.
27
+ - Identities, control-plan strings and review notes that 0.1.0 accepted can
28
+ now be refused as credential-shaped (for example `?access_token=` or `?credential=`). See
29
+ the credential bullet under Changed.
30
+ - `GraphState.token()` and `queries.with_generation()` are gone; use the
31
+ `graph_state` token results carry and `queries.with_graph_state()`. See
32
+ Removed.
33
+ - The repository's `examples/policy_propose_from_observed.py`, which 0.1.0
34
+ introduced, is gone; use `toolgraph policy propose`, which reads the live
35
+ graph. See its entry under Removed for the provenance flags.
36
+
37
+ ### Added
38
+
39
+ - **`toolgraph doctor [--json]`** checks the configuration file (present,
40
+ valid JSON, mode 0600, and `backend_source`: which of `--config`, the
41
+ environment, the config file or the built-in default decided the backend),
42
+ backend connectivity, the physical schema and the graph state. It opens the
43
+ database read-only — on Neo4j in READ access mode — and redacts what it
44
+ reports. It exits 1 when any check fails; a warning alone exits 0.
45
+ - **`toolgraph ingest-manifest --dry-run [--json]`** reports the full delta a
46
+ manifest would apply without changing the database: agents, policies,
47
+ grants and `granted_by`, data access, `GOVERNED_BY`, expected exceptions,
48
+ provenance, and the resources and tools that would be pruned for having no
49
+ edges left. It exits 1 when the manifest would be rejected; with `--json` it
50
+ then prints a rejection report with the warnings, not a delta (the delta
51
+ lists stay empty because no comparison was made). A missing database file is refused and none is
52
+ created; an existing but uninitialized one is refused with `init-schema`
53
+ guidance. On Neo4j the preview
54
+ session opens in READ access mode, so the server rejects an auto-commit write
55
+ issued through it (verified on Neo4j 5.26 over `bolt://`); that is an access
56
+ mode, not access control, and does not replace a read-only role. `--json`
57
+ requires `--dry-run`. Plain `ingest-manifest` behaves as before.
58
+ - **`toolgraph policy propose GOVERNANCE OBSERVED`** proposes grant changes to
59
+ `governance.yaml` from an observation-window JSONL export
60
+ (`contracts/observation-window.schema.json`), resolving grants against the
61
+ live graph. It never writes to the graph and never edits the manifest. The
62
+ export's envelope must match the manifest and the live graph;
63
+ `--revalidate-current` accepts a moved generation and re-checks additions
64
+ against the current strict catalog, and `--unverified-provenance` accepts a
65
+ missing envelope or identity and labels the result unverified. Other
66
+ options: `--min-would-block` (default 3), `--window-label`, `--json`, and
67
+ `-o/--output`, which cannot be combined with `--json` and writes the file
68
+ atomically with mode 0600 (a failure before the replacement leaves an
69
+ existing file as it was). It refuses, with a redacted `ERROR:` diagnostic
70
+ and exit 1 rather than a traceback: an export over 50,000,000 bytes,
71
+ malformed YAML, and any proposal row it would emit whose agent, tool or note
72
+ is credential-shaped — that error names the position (`additions[0].tool`),
73
+ never the value. See
74
+ `docs/policy-propose.md`.
75
+ - **`control-preflight` reports potential exposures.** Each record in the new
76
+ `potential_exposures` field joins an eligible reader tool on one plan node to
77
+ an eligible writer tool on a distinct node downstream of it, with the
78
+ resources involved (redacted) and a shortest witness path. It marks a
79
+ potential path, not proof that data moved. The search is bounded: at most 100 records,
80
+ 64,000 bytes of records and 65,536 traversal steps; when it stops early the
81
+ result also carries `truncated: true` and `total_candidate_pairs_found`,
82
+ which is then a lower bound. The fields are additive to the v1 schema. See
83
+ `docs/adr/0013-bounded-control-plan-preflight.md`.
84
+ - `toolgraph audit-report` JSON now carries the graph state it was read at
85
+ (`graph_generation`, `graph_instance_id`, `graph_state`,
86
+ `graph_state_verified`), as the MCP `audit_report` tool already did. The
87
+ Markdown format is unchanged.
88
+ - `toolgraph example init` now writes a `README.md` into the generated
89
+ directory. `docs/` is excluded from the sdist, so a user who installed from
90
+ PyPI previously landed in a directory of three unexplained files with only
91
+ the CLI's one-line hint to go on. The file lists the commands in order,
92
+ states the expected outcome, and links the full guide.
93
+ - `examples/README.md` maps each `servers-*.yaml` / `governance-*.yaml` input
94
+ to the script or workflow that consumes it, and to what it needs to run.
95
+ - A `Documentation` URL in the project metadata, so the beginner guide is
96
+ reachable from the PyPI sidebar.
97
+
98
+ ### Changed
99
+
100
+ - **An environment variable can no longer silently override `toolgraph
101
+ init`'s config.** Without `--config`, `TOOLGRAPH_BACKEND` and
102
+ `TOOLGRAPH_DB_PATH` used to win over `.toolgraph/config.json` (or the file
103
+ `TOOLGRAPH_CONFIG` names). Copying `.env.example` (which sets
104
+ `TOOLGRAPH_BACKEND=neo4j`) into a directory where `toolgraph init` had
105
+ written a Ladybug config sent every command to Neo4j with no warning. When
106
+ the two disagree on the backend or on the resolved database path, commands
107
+ now stop with `ERROR: conflicting backend configuration (...)`. To migrate,
108
+ pass `--config <file>` to choose the file, or unset the variable (remove it
109
+ from `.env` if it is set there). Agreeing
110
+ values, or only one of the two being set, behave as before. `--help`,
111
+ `toolgraph init` and `toolgraph example init` are not blocked, so `init` can
112
+ still be used to rewrite the file. `toolgraph doctor` reports which input
113
+ decides the backend as `backend_source`.
114
+ - **Only the CLI reads `./.env`.** Importing `toolgraph` as a library used to
115
+ load `.env` from the current directory into `os.environ`. The CLI, including
116
+ `toolgraph serve`, still does; a program that embeds Toolgraph and relied on
117
+ that should call `toolgraph.config.load_dotenv_file()` or set its own
118
+ environment.
119
+ - **`toolgraph check` no longer creates a database.** It opens the configured
120
+ Ladybug file read-only, so a mistyped `db_path` exits 1 with
121
+ `BACKEND_UNAVAILABLE ... does not exist` instead of creating an empty
122
+ database and printing `OK`. Run `toolgraph init` or `init-schema` first.
123
+ - **`control-preflight` can now warn where 0.1.0 allowed.** The decision is
124
+ `advisory_warn`, not `advisory_allow`, when the plan has a potential
125
+ exposure (see Added) or the exposure search was truncated. The exit code is
126
+ still 0 for both. A consumer that gates on `advisory_allow` will see more
127
+ warnings and should review them rather than treat them as failures of the
128
+ tool. A result without the exposure fields, from 0.1.0, means "not
129
+ checked", not "none found"; `total_candidate_pairs_found` on a truncated
130
+ result is a lower bound. ADR-0013 states the decision rules.
131
+ - **Credential checks are shared and refuse more.** Agent and tool identities
132
+ (preflight, `policy compile`, `policy review-plan`, `policy propose`) and
133
+ strings in control plans are now also refused when they carry
134
+ `?access_token=` or `?credential=`, a password URI whose username is an
135
+ email address, or an `@` in a query after a port
136
+ (`https://user@host:443?x=a@b`). `review-candidates annotate` notes that
137
+ assign `secret=` or `access_token=` are now refused too. Error text
138
+ now also hides `credential=...` and a bare `Bearer <token>`. Nothing that
139
+ was refused or hidden before is accepted or shown now.
140
+ - Control-plan validation errors give the location and the reason, never the
141
+ rejected value: `invalid control plan at nodes.0.principals.1: ...`, and a
142
+ bad candidate tool is reported by its index.
143
+ - A file that is not a Ladybug database is now a non-retryable
144
+ `BACKEND_CORRUPT` configuration error, printed as one `ERROR:` line. In
145
+ 0.1.0 it surfaced as a Python traceback from Ladybug.
146
+ - `config.configure()` closes the cached backend connection, so a library
147
+ caller switching configs no longer keeps talking to the previous database.
148
+ - Deny-path rows (`check-access` evidence, `unsafe-tools`, `audit-report`,
149
+ selector deny paths) now have a fixed order when rows used to tie: one
150
+ tool under several DENY policies, one tool name on several servers, or a
151
+ READS and a WRITES edge to the same resource. Ties are broken by tool key,
152
+ policy and mode; before, the backend chose, and Ladybug and Neo4j could
153
+ disagree. Rows that did not tie keep their order.
154
+
155
+ ### Removed
156
+
157
+ - **`examples/policy_propose_from_observed.py`, the offline proposal script.**
158
+ There is no longer a graph-free way to produce a proposal. Its replacement,
159
+ `toolgraph policy propose` (see Added), applies the same row and proposal
160
+ rules but resolves grants against the live graph, and checks the envelope
161
+ against it too: an envelope the script accepted is refused when it lacks a
162
+ graph instance id, or when its generation, digest or instance differs from
163
+ the live graph. Re-export from the current graph, or pass
164
+ `--revalidate-current` (a moved generation) or `--unverified-provenance`
165
+ (missing identity) to proceed. The script was repository-only and never
166
+ shipped in the distribution. Its tests now drive `toolgraph.policy_propose`.
167
+ - `GraphState.token()`, which nothing called and which returned a different
168
+ set of keys than the `graph_state` token that results actually carry.
169
+ - `toolgraph.graph.queries.with_generation()`, which had no production
170
+ caller; use `with_graph_state()`. A strict read that sees the graph state
171
+ change on every retry now raises `queries.GraphStateChurnError`, a
172
+ `RuntimeError` subclass with the same message, so CLI output and exit codes
173
+ are unchanged.
174
+
175
+ ### Fixed
176
+
177
+ - Backend configuration errors print one `ERROR:` line and exit 1 instead of
178
+ a Python traceback: an unknown `TOOLGRAPH_BACKEND`, a missing `ladybug`
179
+ extra, and a schema-version mismatch. `toolgraph doctor` is the exception
180
+ by design: it reports the same failure as a FAIL check (exit 1) in its text
181
+ or JSON report.
182
+ - An outage or other backend error while reading the schema version is no
183
+ longer taken for a fresh database, so `init`, `init-schema`, `crawl` and
184
+ `ingest-manifest` stop before running any DDL.
185
+ - `toolgraph check` no longer prints credentials embedded in `NEO4J_URI`, and
186
+ `ingest-manifest` warnings and notices are redacted.
187
+ - `toolgraph init` with an unusable `--state-dir` prints `ERROR: cannot
188
+ initialize ...` and exits 1 instead of a traceback raised from its cleanup;
189
+ a partial database file it cannot remove is named in a warning.
190
+ - `policy review-plan` refuses a graph with no instance id instead of writing
191
+ a plan its own schema rejects.
192
+ - `policy compile` (and `build_policy_bundle()`) refuses a graph state whose
193
+ generation is not a non-negative integer — a boolean, float, string, null
194
+ or negative value — instead of writing it into the bundle. Toolgraph's own
195
+ graph always yields one; the check matters for a hand-edited graph or a
196
+ library caller's own graph reader.
197
+ - Writing a private artifact — `init`'s config file, `preflight --out`,
198
+ `control-preflight --out`, `policy compile --output`, `policy review-plan
199
+ --output` — no longer reports failure when the new file was already in
200
+ place and only re-applying its 0600 mode afterwards failed. The file is
201
+ created 0600 before it is written, so that second step was redundant.
202
+ - `crawl` no longer prints `vNone` for a server that reports no version.
203
+ - The packaged quickstart governance manifest now carries `provenance` on
204
+ every authored edge. The beginner guide claimed every authored edge had an
205
+ evidence pointer and then told the reader to run `unbacked-edges`, which
206
+ listed all three of them. The audit now comes back empty, and both the
207
+ manifest and the guide say what that does and does not prove: the demo tools
208
+ return strings and touch no file, so the READS/WRITES effects are synthetic
209
+ and their evidence cites the fixture that defines them. An empty audit means
210
+ every edge has a pointer, not that a claim is true. As a result `read_note`'s
211
+ risk in the quickstart drops from 0.4 to 0.0 and the compiled bundle's
212
+ digest changes.
213
+ - Beginner guide corrections (both languages): the shared-backend section
214
+ needs a repository clone, since `docker-compose.yml` and `.env.example` ship
215
+ with neither the package nor the generated quickstart; `policy compile`
216
+ prints metadata describing the bundle, not the bundle; and the gateway step
217
+ is labelled optional, matching the README's statement that memtomem-stm is
218
+ not a required dependency.
219
+
220
+ ### Dependencies
221
+
222
+ - The `pyjwt` security floor rises from `>=2.13.0` to `>=2.15.0`. 2.13.0
223
+ carries 14 advisories published after it was set: PYSEC-2026-4140 through
224
+ PYSEC-2026-4152 and PYSEC-2026-4183. PYSEC-2026-4141 and PYSEC-2026-4183
225
+ are fixed in 2.15.0, PYSEC-2026-4146 lists 2.13.0 as its last affected
226
+ version, and the rest are fixed in 2.14.0. The floor is what keeps an
227
+ install from PyPI from resolving an older version; the lock already resolves
228
+ a newer one. Toolgraph does not import `pyjwt` itself; it is pinned for the
229
+ MCP SDK's dependency tree.
230
+
231
+ ### Testing
232
+
233
+ - The `sse` crawl transport and auth-header forwarding on both HTTP
234
+ transports are now exercised end to end, at the lockfile version and at the
235
+ declared MCP SDK floor. Neither had been run before; no runtime behaviour
236
+ changed.
237
+
238
+ ## 0.1.0 - 2026-09-10
239
+
240
+ Second alpha. Upgrading from `0.0.1` takes three things: the MCP SDK 2.x, one
241
+ new field for anything caching MCP responses, and — if you generated a
242
+ quickstart with `0.0.1` — a regenerated server script. Read **Upgrading from
243
+ 0.0.1** below before installing.
244
+
245
+ ### Upgrading from 0.0.1
246
+
247
+ `toolgraph example init` copies a quickstart out of the package, so a
248
+ quickstart generated by `0.0.1` holds its own copy of the demo MCP server —
249
+ one that imports `mcp.server.fastmcp`. Upgrading that environment installs MCP
250
+ 2.x and the copied server stops importing, so the next `toolgraph crawl`
251
+ fails. Updating the package does not touch a directory it already wrote, and
252
+ `example init` refuses an existing destination on purpose.
253
+
254
+ To migrate one, generate a fresh quickstart beside it and move your own work
255
+ across:
256
+
257
+ ```bash
258
+ toolgraph example init quickstart-0.1.0
259
+ # Keep your authored governance and your database; take the new server script.
260
+ cp quickstart/governance.yaml quickstart-0.1.0/governance.yaml
261
+ cp -R quickstart/.toolgraph quickstart-0.1.0/.toolgraph
262
+ ```
263
+
264
+ `servers.yaml` records the interpreter that generated it, so regenerating from
265
+ the upgraded environment already points at the right one; check it only if you
266
+ generated from somewhere else. Re-run `toolgraph crawl`, then
267
+ `toolgraph ingest-manifest`, to confirm.
268
+
269
+ Verified end to end against the published `0.0.1`: a quickstart it generated
270
+ crawls cleanly on `0.0.1`, fails on the next crawl once the environment holds
271
+ `0.1.0`, and returns the same eligibility verdicts after these steps.
272
+
273
+ ### Added
274
+
275
+ - `examples/policy_propose_from_observed.py` reads a window of observed gateway
276
+ usage and prints a reviewable `governance.yaml` grant proposal. It never
277
+ writes governance. Repository-only — it is not part of the distribution — and
278
+ deliberately narrow: it requires an envelope declaring the governance digest
279
+ it was recorded against and refuses a mismatch, refuses an export with no
280
+ envelope unless `--unverified-provenance` is given, proposes a removal only
281
+ from an explicit zero-call row, and accepts only fully qualified timestamps
282
+ with an explicit UTC offset. Digest equality shows what the export declared,
283
+ not where its rows came from; issue #89 tracks what a real binding needs.
284
+
285
+ ### Changed
286
+
287
+ - **Breaking:** requires the MCP SDK 2.x (`mcp>=2.1.1,<3`). Environments pinned
288
+ to `mcp` 1.x can no longer install this package, because 2.x removed
289
+ `mcp.server.fastmcp` and renamed every model field from camelCase to
290
+ snake_case. The MCP *wire* format is unchanged, so a client that only speaks
291
+ the protocol needs no change; that is not the same as no change for anything
292
+ written against the Python SDK, caching responses, or reading exception text.
293
+ - An unexpected exception raised by a toolgraph tool body no longer puts its
294
+ message on the wire. Only errors raised deliberately as `ContractError` (for
295
+ example an unknown selector profile) are forwarded; the rest keep the SDK's
296
+ generic text, so an internal failure cannot disclose filesystem paths, query
297
+ bodies or credential-shaped values. Argument-validation failures still echo
298
+ the caller's own input, as the MCP SDK intends.
299
+ - **Breaking:** an MCP response whose read could not be bracketed by a stable
300
+ graph state now carries `graph_generation: null` and
301
+ `graph_state_verified: false` instead of the generation read afterwards.
302
+ Labelling such a result with a generation it was never read at let a verdict
303
+ from before a permission change be cached under the generation that changed
304
+ it, while the tool documentation tells callers to cache per generation.
305
+ Successful reads gain `graph_state_verified: true`, so the absence of a
306
+ guarantee is never inferred from a missing field. Null is not self-enforcing
307
+ — `None` is a usable cache key — so the obligation to skip the cache and to
308
+ attribute nothing to a graph state is the consumer's; ADR-0004 and the README
309
+ state it, and a consumer that turns eligibility into an authorization
310
+ decision should reject or retry such a response.
311
+
312
+ ## 0.0.1 - 2026-09-09
313
+
314
+ Initial public alpha release.
315
+
316
+ - Crawl MCP servers into a graph and combine factual capability data with
317
+ operator-authored governance.
318
+ - Explain access, reachability, drift, blast radius, and tool selection.
319
+ - Compile deterministic policy bundles for an independent runtime gateway.
320
+ - Support local Ladybug and shared Neo4j backends.
321
+ - Reject unknown manifest fields and duplicate YAML keys before graph mutation.
322
+ - Persist only credential-free endpoint labels and redact failure diagnostics.
323
+ - Ship an install-only, offline quickstart through `toolgraph example init`.
324
+ - Give MCP consumers a typed `backend_unavailable` error envelope for transient
325
+ graph outages while keeping contract and internal errors fail-loud. Outages
326
+ are typed at the backend seam, and `retryable` follows the tool's advertised
327
+ read-only annotation instead of being assumed, failing closed to `false`
328
+ whenever that annotation cannot be read.
329
+ - Add a body-free `toolgraph.control-plan` v1 contract and advisory
330
+ `control-preflight` command that lints bounded DAG topology and evaluates all
331
+ principal/tool surfaces under one collision-safe graph-state bracket.
332
+
333
+ ### Stabilization (2026-09-08)
334
+
335
+ - Resolve ingest references in bounded batches shared with the selector,
336
+ preserving atomic application, diagnostic order and ambiguous identities.
337
+ - Require explicit commit SHAs for ecosystem smokes and clean matching HEADs
338
+ for gateway smokes; record dependencies and artifact evidence.
339
+ - Include Ladybug in `make install` and refresh the pip audit dependency lock.
340
+ - Reconcile historical integration and selection roadmaps with shipped code.
341
+
342
+ ### Implementation review fixes (2026-09-07)
343
+
344
+ - Handle empty crawl and manifest collections on both backends while retaining
345
+ stale-edge reconciliation, revocation, and transactional generation updates.
346
+ - Preserve opaque artifact identities, including ordinary URI queries/fragments.
347
+ Reject credential-shaped identities and redact only evidence fields, including
348
+ policy review paths. Existing v1 schemas and decision vocabularies are unchanged.
349
+ - Use collision-safe `v2:` exception keys and deterministic last-entry-wins
350
+ metadata for duplicate logical exceptions. No physical schema migration is
351
+ required: existing keys remain readable. Re-ingest the original governance
352
+ manifest to rebuild keys and restore entries lost to old key collisions, then
353
+ recompile policy bundles. Old artifacts cannot reconstruct lost manifest rows.
354
+ - Require control-plan fields exactly as published: missing `maximum_cycles`,
355
+ explicit null bounds, string/boolean coercions are rejected. JSON integer
356
+ numbers such as `2.0` remain accepted; additive same-major fields remain valid.
357
+ - Compile catalogs in chunks of at most 4,096 candidates under one strict state
358
+ bracket; reject incomplete or conflicting evaluations. Injected readers own
359
+ all state reads, and adapters without the optional `evaluate` capability work.
360
+ - Load runtime settings lazily so help/version and explicit configuration remain
361
+ usable when the default config is broken. Invalid selected configs fail clearly.
362
+ - Reject naive and sub-minute timestamps before preflight/review I/O; development
363
+ validation now installs and checks the RFC 3339 format validator.
364
+
365
+ ### Upgrade note for pre-release users (historical)
366
+
367
+ `driver.session()` and `driver.verify_connectivity()` now raise
368
+ `BackendUnavailableError` where they previously surfaced the raw `neo4j`
369
+ `ServiceUnavailable`, `SessionExpired`, `ConnectionAcquisitionTimeoutError`, or
370
+ `DatabaseUnavailable`. Code embedding toolgraph as a library and catching those
371
+ types directly must catch `BackendUnavailableError` (or its `BackendLockedError`
372
+ subclass); the original exception is preserved as `__cause__`. Configuration,
373
+ authentication, and Cypher/client errors are unaffected, as is `get_driver()`.
374
+ Successful CLI output and query result shapes are unchanged; an unhandled
375
+ outage now names `BackendUnavailableError` in the traceback.
376
+
377
+ `driver.is_backend_unavailable()` narrowed to match: it now recognizes only
378
+ `BackendUnavailableError` (anywhere in the cause chain), not the raw `neo4j`
379
+ types it previously classified. Code that pairs the still-raw `get_driver()`
380
+ escape hatch with `is_backend_unavailable()` to decide whether to retry will
381
+ silently stop matching — catch `BackendUnavailableError`, or route the call
382
+ through `driver.session()` so the seam types it.
383
+
384
+ Earlier snapshots could persist stdio arguments or complete HTTP URLs in graph
385
+ endpoint/evidence properties. Reset and rebuild pre-release graphs after
386
+ upgrading. If any argument or URL contained a credential, rotate it as well.