toolgraph 0.3.0__tar.gz → 0.4.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.
- {toolgraph-0.3.0 → toolgraph-0.4.0}/CHANGELOG.md +347 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/PKG-INFO +23 -7
- {toolgraph-0.3.0 → toolgraph-0.4.0}/README.md +22 -6
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/__init__.py +1 -1
- toolgraph-0.4.0/toolgraph/artifact_safety.py +260 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/artifacts.py +6 -3
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/cli.py +136 -27
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/config.py +19 -7
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/control_plan.py +63 -6
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/crawler/crawl.py +3 -2
- toolgraph-0.4.0/toolgraph/error_text.py +259 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/errors.py +15 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/graph/loader.py +2 -1
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/graph/queries.py +10 -2
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/health.py +6 -5
- toolgraph-0.4.0/toolgraph/log_safety.py +183 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/manifest/ingest.py +11 -4
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/models.py +86 -5
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/policy_impact.py +2 -2
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/policy_propose.py +24 -8
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/redaction.py +205 -14
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/review_candidates.py +35 -22
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/server/app.py +32 -2
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/yaml_loader.py +37 -6
- toolgraph-0.3.0/toolgraph/artifact_safety.py +0 -171
- {toolgraph-0.3.0 → toolgraph-0.4.0}/.gitignore +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/LICENSE +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/SECURITY.md +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/control-plan.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/control-preflight.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/observation-window.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/policy-bundle.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/policy-impact.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/policy-review-plan.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/preflight.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/review-annotations.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/tracegraph/README.md +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/tracegraph/review-candidates.schema.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/contracts/tracegraph/v1.json +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/pyproject.toml +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/crawler/__init__.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/crawler/client.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/crawler/transports.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/graph/__init__.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/graph/driver.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/graph/schema.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/graph/selector.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/graph/store.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/manifest/__init__.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/manifest/parser.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/policy_bundle.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/policy_impact_models.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/policy_review.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/preflight.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/quickstart/README.md +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/quickstart/governance.yaml +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/quickstart/policy_gateway_server.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/quickstart/servers.yaml +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/server/__init__.py +0 -0
- {toolgraph-0.3.0 → toolgraph-0.4.0}/toolgraph/uris.py +0 -0
|
@@ -2,6 +2,353 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Toolgraph are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.4.0 - 2026-10-21
|
|
6
|
+
|
|
7
|
+
Fifth alpha. Read **Upgrading from 0.3.0** below before installing: it lists
|
|
8
|
+
eight changes an existing setup, script or embedding program can notice.
|
|
9
|
+
Most setups need no change, because the new refusals reject only identities
|
|
10
|
+
and notes that hold credential-shaped data. What most often notices is
|
|
11
|
+
tooling that reads Toolgraph's text: evidence paths, error lines and log
|
|
12
|
+
output are shown differently, and more often withheld whole.
|
|
13
|
+
|
|
14
|
+
### Upgrading from 0.3.0
|
|
15
|
+
|
|
16
|
+
- A governance manifest or `servers.yaml` holding a credential-shaped
|
|
17
|
+
identity is refused at input (#217): an agent, policy id, tool ref or
|
|
18
|
+
`granted_by`, a `governed_by` policy or tool-ref key, or a server's name.
|
|
19
|
+
The manifest is rejected whole, so `ingest-manifest`, `policy propose` and
|
|
20
|
+
`policy impact` exit 1, and so is a `servers.yaml`, before any server is
|
|
21
|
+
contacted. A crawled server that reports such a tool name, or such a name
|
|
22
|
+
when `servers.yaml` gives it none, fails its own crawl; the rest of the
|
|
23
|
+
fleet still loads. The error names the field, never the value. Rename the
|
|
24
|
+
identity. A graph keeps the values it stored earlier until they are
|
|
25
|
+
re-ingested or reset.
|
|
26
|
+
- Identities refuse any URI with an `@` after its `://`, raw or as `%40`
|
|
27
|
+
(#210, #215, #216), where 0.3.0 refused only a `user:password@` it could
|
|
28
|
+
bound: agent ids, tool keys, run ids, candidates and graph instance ids in
|
|
29
|
+
preflight, policy bundles, review plans, `policy impact` and `policy
|
|
30
|
+
propose`, the run ids and tool keys of a Tracegraph review report, and a
|
|
31
|
+
control plan's identities and keys. `policy propose` refuses to emit a
|
|
32
|
+
proposal whose note would list a live-graph tool key holding userinfo.
|
|
33
|
+
- Review notes, reviewer names and control plans refuse a URI with a quote
|
|
34
|
+
in its userinfo and an `@` after it (#210), such as
|
|
35
|
+
`neo4j://u:"p w"@host`, which 0.3.0 accepted. A quoted URL followed by an
|
|
36
|
+
address in the same text, `"https://h" mail a@b`, is refused too.
|
|
37
|
+
- Artifact evidence shows resource URIs differently (#212). A consumer that
|
|
38
|
+
matches evidence text should expect `https://<redacted-uri>` when an `@`
|
|
39
|
+
follows a URI's authority (`https://registry/@scope/pkg`), and
|
|
40
|
+
`<redacted-uri>` for a percent-encoded scheme. A resource that cannot be
|
|
41
|
+
parsed is now hidden instead of stopping the command, `export:///sink`
|
|
42
|
+
keeps its three slashes, and the paths of `check-access`, `unsafe-tools`,
|
|
43
|
+
`blast-radius`, `audit-report` and the matching MCP tools show resources
|
|
44
|
+
and policy ids redacted. The raw `resource` and `policy` row values are
|
|
45
|
+
unchanged. See the first bullets under Changed.
|
|
46
|
+
- Diagnostics read differently, and more of them are withheld whole (#206,
|
|
47
|
+
#221, #223, #229, #232, #236). Validation errors are one line with fixed
|
|
48
|
+
text and `<key>` for keys outside the schema; YAML errors read `invalid
|
|
49
|
+
YAML at line L, column C: <what failed>`; MCP tool-call errors for a bad
|
|
50
|
+
request no longer quote it; a CLI usage error is withheld whole when a
|
|
51
|
+
token on the command line, or the tokens joined by spaces, is
|
|
52
|
+
credential-shaped; and a printed path that is
|
|
53
|
+
credential-shaped reads `<path withheld: credential-shaped>`. Messages
|
|
54
|
+
holding an escaped credential, or a URL whose path ends in a credential
|
|
55
|
+
name with text after it, are withheld: `Error accessing
|
|
56
|
+
https://host/token: connection refused` now reads `details withheld (they
|
|
57
|
+
contain credential-shaped data)`. Tooling
|
|
58
|
+
that matches stderr or MCP error text should expect these forms; the
|
|
59
|
+
entries under Fixed give each one.
|
|
60
|
+
- Logging changes for programs that embed Toolgraph (#206). Importing
|
|
61
|
+
`toolgraph.server.app` into a process with no root log handler filters
|
|
62
|
+
the root handlers the MCP SDK then creates, and running the Typer app
|
|
63
|
+
in-process filters `logging.lastResort` for the rest of the process once a
|
|
64
|
+
command reaches the root callback (root `--help`, `--version` and an
|
|
65
|
+
unknown command do not). Every record through a
|
|
66
|
+
filtered handler, the program's own included, is rebuilt: its message is
|
|
67
|
+
scrubbed (`password=<redacted>`), each exception in its chain is shown as
|
|
68
|
+
`<Type>: <message>` without a traceback, and `extra` attributes and `stack_info` are dropped,
|
|
69
|
+
so a formatter that names an `extra` field fails with `Formatting field
|
|
70
|
+
not found in record`. Configure logging before importing
|
|
71
|
+
`toolgraph.server.app`, or replace the handlers it filtered. Handlers a
|
|
72
|
+
program configured itself are not touched.
|
|
73
|
+
- Library callers: `ingest_governance`, `dry_run_ingest` and
|
|
74
|
+
`load_crawl_result` re-validate the model they are given, and a model
|
|
75
|
+
built with `model_construct()` or changed after validation that no longer
|
|
76
|
+
validates raises `ValueError: <Model> does not validate; build it with
|
|
77
|
+
<Model>.model_validate to see why` before the graph is opened.
|
|
78
|
+
`build_policy_impact` already re-validated, and now raises that error
|
|
79
|
+
instead of pydantic's. A YAML failure that PyYAML does not mark with a line
|
|
80
|
+
and column raises `toolgraph.yaml_loader.YAMLContentError`, a `ValueError`
|
|
81
|
+
(#223); marked failures still raise PyYAML's `MarkedYAMLError`.
|
|
82
|
+
- A durability warning escalated with `-W error` ends a command with one
|
|
83
|
+
`ERROR:` line and exit 1, without a traceback or a JSON receipt (#129).
|
|
84
|
+
`init` keeps the database it created, where 0.3.0 deleted it and kept the
|
|
85
|
+
`config.json` naming it. The annotation warning's text now ends `do not
|
|
86
|
+
retry: a retry appends a second event`.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- Artifact evidence shows each resource URI as a whole value (#212). A
|
|
91
|
+
resource is redacted before it is written into an evidence path, and
|
|
92
|
+
resource fields (`resource`, `resource_uri`, `read_resource`,
|
|
93
|
+
`write_resource`, `policy`, a resource `ref` and the `resources_*` lists of
|
|
94
|
+
`ingest-manifest --dry-run --json`) are redacted as one value even when
|
|
95
|
+
they hold whitespace. In free text, such as an exception reason, a URI ends
|
|
96
|
+
at whitespace or at a `)`, as in 0.3.0. Consumers that match evidence can
|
|
97
|
+
notice:
|
|
98
|
+
- An `@` anywhere after a URI's authority, raw or as `%40`, hides
|
|
99
|
+
everything after the scheme, since where an unquoted password ends cannot
|
|
100
|
+
be told: `https://registry/@scope/pkg` is shown as
|
|
101
|
+
`https://<redacted-uri>`. In free text the rest of the text goes too, as
|
|
102
|
+
it already did after a quoted password, because it may hold the rest of
|
|
103
|
+
the password; this includes a URI-shaped tool name inside evidence text.
|
|
104
|
+
- A value whose scheme is percent-encoded (`https%3A%2F%2Fh/r`) is shown as
|
|
105
|
+
`<redacted-uri>`, in free text with the rest of the text.
|
|
106
|
+
- A resource value that cannot be parsed (`https://u:p@[::1/r`) is hidden
|
|
107
|
+
as `<scheme>://<redacted-uri>` instead of stopping the command, and so is
|
|
108
|
+
any evidence string that is nothing but such a URI, whitespace around it
|
|
109
|
+
aside. A URI that cannot be parsed among other text, such as
|
|
110
|
+
`see https://u:p@[::1/r` in an exception reason, still stops it, as in
|
|
111
|
+
0.3.0.
|
|
112
|
+
- An evidence path no longer loses the rest of its line when its resource
|
|
113
|
+
holds a `)` or a quoted password: a resource `neo4j://u:p)ss@host/r` is
|
|
114
|
+
shown as `(t) -READS-> (neo4j://host/r) -GOVERNED_BY-> (p:DENY)`, and a
|
|
115
|
+
quoted one as `(t) -READS-> (neo4j://<redacted-uri>) -GOVERNED_BY->
|
|
116
|
+
(p:DENY)`. In free text, a `)` followed by an `@` hides everything after
|
|
117
|
+
the scheme.
|
|
118
|
+
- `export:///sink` keeps its three slashes; it was shown as `export:/sink`.
|
|
119
|
+
- Paths in `check-access`, `unsafe-tools`, `blast-radius` and `audit-report`
|
|
120
|
+
output, and in the matching MCP tools, show resources and policy ids the
|
|
121
|
+
same way. The rows still carry the raw `resource` and `policy` values
|
|
122
|
+
beside the path.
|
|
123
|
+
- `ingest-manifest` warnings that name a resource show it redacted.
|
|
124
|
+
- Identities are refused when they hold a URI with an `@` anywhere after its
|
|
125
|
+
`://`, raw or percent-encoded (#210): agent ids, tool keys, run ids,
|
|
126
|
+
candidates and graph instance ids in preflight, policy bundle, review plan,
|
|
127
|
+
policy impact and `policy propose` input and output, and the run ids and
|
|
128
|
+
tool keys of a Tracegraph review report. Before, only a `user:password@`
|
|
129
|
+
that the refusal rule could bound was refused, so `neo4j://u:p/w@host/r`
|
|
130
|
+
passed. An identity has no reason to hold userinfo, and an unquoted
|
|
131
|
+
password may hold `/`, `)` or whitespace. Review notes, reviewer names,
|
|
132
|
+
the notes of `policy propose` rows and control plans keep the rule for
|
|
133
|
+
prose. The live-graph tool keys an ambiguous grant's note lists are
|
|
134
|
+
checked as identities before they are joined into it, so `policy propose`
|
|
135
|
+
refuses to emit a proposal whose note would show one holding userinfo
|
|
136
|
+
(#216).
|
|
137
|
+
|
|
138
|
+
- The governance manifest and the crawler refuse a credential-shaped identity
|
|
139
|
+
at input, with the same identity rule (#217): an agent, policy id, tool ref
|
|
140
|
+
or `granted_by`, a `governed_by` policy or tool-ref key, a server's name
|
|
141
|
+
(its `servers.yaml` name, else the name it reports) and the tool names it
|
|
142
|
+
reports. Before, such a
|
|
143
|
+
value was stored and printed by `check-access`, `unsafe-tools`,
|
|
144
|
+
`blast-radius`, `unbacked-edges`, `audit-report` and `eligible-tools`. The
|
|
145
|
+
error names the field, never the value. A manifest is rejected whole
|
|
146
|
+
(`ingest-manifest`, `policy propose` and `policy impact` exit 1), and so
|
|
147
|
+
is a `servers.yaml`, before any server is contacted. A server without a
|
|
148
|
+
`servers.yaml` name that reports such a name, or any server that reports
|
|
149
|
+
such a tool name, fails its own crawl, and the rest of the fleet still
|
|
150
|
+
loads; a server's own reported name is never stored when `servers.yaml`
|
|
151
|
+
names it. Resource URIs are not identities and are not checked. A graph
|
|
152
|
+
keeps the values it stored earlier until they are re-ingested or reset.
|
|
153
|
+
|
|
154
|
+
- A control plan's identities get the identity rule at load (#215): `run_id`,
|
|
155
|
+
producer name and version, principal ids, node ids and the principals a
|
|
156
|
+
node lists, edge endpoints and candidate tools, so a credential URI with a
|
|
157
|
+
`/` in its password or a percent-encoded scheme is refused there instead of
|
|
158
|
+
being published as the result's `run_id`. Keys are field names and get the
|
|
159
|
+
same rule, so a duplicate or forbidden-field error can no longer print an
|
|
160
|
+
encoded credential-shaped key. String values of additive fields keep the
|
|
161
|
+
prose rule. The result's `graph_state` goes through the same artifact
|
|
162
|
+
check as the rest of the result.
|
|
163
|
+
|
|
164
|
+
### Fixed
|
|
165
|
+
|
|
166
|
+
- Artifact evidence no longer shows URI passwords the evidence scan could not
|
|
167
|
+
bound (#212). Before, a password holding an unquoted `/`, `?` or `#`
|
|
168
|
+
(`neo4j://u:p/w@host/r`, also read as a port when it starts with digits),
|
|
169
|
+
a credential URI inside another URI's path, an encoded `@` (`%40`), and a
|
|
170
|
+
resource whose query holds `)` (`https://h/r?k=a)b` kept `)b`) could reach
|
|
171
|
+
preflight, policy bundle, control-plan and review artifacts, `policy
|
|
172
|
+
impact` and `policy check` output and `ingest-manifest --dry-run --json`,
|
|
173
|
+
including its warnings. Inside free text some shapes are still not
|
|
174
|
+
bounded, as in 0.3.0: a password holding whitespace without quotes, a
|
|
175
|
+
query or fragment holding `)` (`see https://h/r?k=a)b` still shows `)b`),
|
|
176
|
+
and text after a `)` that no longer reads as a URI.
|
|
177
|
+
- A URI with a quote in its userinfo and an `@` after it is refused like any
|
|
178
|
+
other credential URI, raw or percent-encoded (#210): in identities, review
|
|
179
|
+
notes, reviewer names and control plans. Before, a quoted password holding
|
|
180
|
+
whitespace (`neo4j://u:"p w"@host`), a quoted part after a `?` or `#`
|
|
181
|
+
(`neo4j://u:p?"w s"@host`, `neo4j://u?x:"p w"@host`) and a quoted user
|
|
182
|
+
part (`neo4j://"u v":"p w"@host`) were accepted. The refusal now matches
|
|
183
|
+
what error-message scrubbing already hides, so a quoted URL followed by an
|
|
184
|
+
address in the same text (`"https://h" mail a@b`, `"https://h:443" mail
|
|
185
|
+
a@b`) is refused too; `see "https://x/y" and @alice`, whose quote follows
|
|
186
|
+
a path, is not. Error messages hide the new shapes as well: before,
|
|
187
|
+
`neo4j://u:p?"w s"@h/a` kept `"w s"@h/a`. A message whose decoded or
|
|
188
|
+
scrubbed text still holds such a URI is withheld, as one holding
|
|
189
|
+
`u:%22pw%22@` already was; `see "https://x/y" and @alice` as an error
|
|
190
|
+
message is now withheld for that reason. Userinfo whose unquoted part
|
|
191
|
+
holds whitespace (`neo4j://u v:"p w"@host`) still passes in prose and in
|
|
192
|
+
error messages, where it reads as a URL followed by text; identities
|
|
193
|
+
refuse it.
|
|
194
|
+
- Pydantic validation errors no longer print mapping keys, unknown field
|
|
195
|
+
names or input values (#221). Before, an invalid `servers.yaml` or
|
|
196
|
+
governance manifest (`crawl`, `ingest-manifest`, `policy impact`,
|
|
197
|
+
`policy propose`), an invalid review-candidate sidecar, and a server whose
|
|
198
|
+
`tools/list` reply the MCP SDK rejects printed the error location with
|
|
199
|
+
every key on the way, and the SDK's errors also printed the input; only the
|
|
200
|
+
credential shapes the scrubber recognizes were hidden, so
|
|
201
|
+
`governed_by: {"postgres://u:prefix QXZQ@h/x": ...}` showed `QXZQ@h/x` and
|
|
202
|
+
an `env` key showed as written. These errors are now one line, such as
|
|
203
|
+
`1 validation error for ServersConfig: servers.0.env.<key>: expected a
|
|
204
|
+
string [type=string_type]`: field names, list indexes and union member
|
|
205
|
+
names that match the schema are kept, any other key is `<key>`, and the
|
|
206
|
+
message is fixed text chosen by error type. A field's own refusal, such as
|
|
207
|
+
`agent must not contain credential-shaped data`, is kept as written.
|
|
208
|
+
Where Toolgraph cannot tell which model failed (`policy propose`, a
|
|
209
|
+
rejected server reply) no location is printed.
|
|
210
|
+
- A config file that is not valid YAML is reported as `invalid YAML at line L,
|
|
211
|
+
column C: <what failed>`, such as `could not be parsed` or `duplicate key`
|
|
212
|
+
(#223). Before, the message quoted the source line, and names from the
|
|
213
|
+
document: a duplicate key, an alias, anchor or tag, a character. A value
|
|
214
|
+
PyYAML could not convert was reported with the value (`!!int x`,
|
|
215
|
+
`!!float x`, `!!bool x`), or stopped `policy impact` and `policy propose`
|
|
216
|
+
with a traceback (`!!bool x`, `!!timestamp x`, an empty `!!int`); now it is
|
|
217
|
+
`invalid YAML: a value could not be read`. Invalid UTF-8 and a control
|
|
218
|
+
character name only their position.
|
|
219
|
+
Library callers still get PyYAML's error when it marks a line and column
|
|
220
|
+
(a `MarkedYAMLError`); the other failures raise
|
|
221
|
+
`toolgraph.yaml_loader.YAMLContentError`, a `ValueError`.
|
|
222
|
+
- Log output hides credential-shaped values and no longer prints tracebacks
|
|
223
|
+
(#206). Before, a crawled server whose reply failed JSON-RPC validation
|
|
224
|
+
made `crawl` print the MCP SDK's traceback with the reply's values
|
|
225
|
+
(`input_value=...`), a tool that crashed under `serve` logged its
|
|
226
|
+
exception message and traceback, and the SDK logged an unknown tool name
|
|
227
|
+
as sent. Now the CLI's fallback output (Python's last-resort handler, used
|
|
228
|
+
when no handler is configured) and the handlers the server configures
|
|
229
|
+
when the process has none print each record's message through the
|
|
230
|
+
credential scrubber, and each exception in its chain as
|
|
231
|
+
`<Type>: <message>` without its traceback, scrubbed the same way, with a validation error's
|
|
232
|
+
message rebuilt as in #221. A value the message or an exception prints
|
|
233
|
+
with `repr` (`%r`, a `KeyError` key), which escapes a tab or a no-break
|
|
234
|
+
space out of the scrubber's sight, is checked as it was before formatting,
|
|
235
|
+
and a credential-shaped one withholds the line. Other text in a message,
|
|
236
|
+
such as a tool name that is not credential-shaped, is still shown. Handlers a program
|
|
237
|
+
configured itself, before or after importing Toolgraph, and uvicorn's own
|
|
238
|
+
loggers under `serve --http` (#226), are not filtered.
|
|
239
|
+
- MCP tool-call errors for a bad request no longer quote the request (#206).
|
|
240
|
+
Before, the MCP SDK answered an unknown tool with `Unknown tool: <name>` as
|
|
241
|
+
sent, and invalid arguments with pydantic's text, which carried every
|
|
242
|
+
input value (`input_value={'token': ...}`). The tool name now goes through
|
|
243
|
+
the credential scrubber (`Unknown tool: nope` is unchanged), and invalid
|
|
244
|
+
arguments read `Error executing tool <name>: 1 validation error for
|
|
245
|
+
<name>Arguments: agent: expected a string [type=string_type]`, with field
|
|
246
|
+
names and fixed messages as in #221. A tool that crashes keeps the SDK's
|
|
247
|
+
generic `Error executing tool <name>`. Errors for resources and prompts,
|
|
248
|
+
which Toolgraph does not register, and the HTTP transport's reply to a
|
|
249
|
+
malformed JSON-RPC body still come from the SDK as they are.
|
|
250
|
+
- CLI usage errors and printed paths no longer show credential-shaped input
|
|
251
|
+
(#206). Before, Click's and Typer's own usage errors quoted what was typed
|
|
252
|
+
(`No such command '<arg>'`, `'<value>' is not a valid float`), as did
|
|
253
|
+
Toolgraph's (`unknown profile '<value>'`), and lines such as
|
|
254
|
+
`Invalid servers config at <path>` and the `--config` error printed the
|
|
255
|
+
path as given. A usage error is now withheld whole when a token on the
|
|
256
|
+
command line is credential-shaped, or the tokens joined by spaces are
|
|
257
|
+
(`password =hunter2` typed as two arguments), checked as typed, before
|
|
258
|
+
Click or Typer quotes or escapes them (`\x09`, `\xa0`) in the message;
|
|
259
|
+
otherwise its
|
|
260
|
+
message goes through the credential scrubber. Lines in
|
|
261
|
+
which Toolgraph prints a path it was given or created (`init`, `check`,
|
|
262
|
+
`crawl`, `ingest-manifest`, `example init`, `policy propose --output`, and
|
|
263
|
+
the runtime-config errors) show a credential-shaped path as
|
|
264
|
+
`<path withheld: credential-shaped>` and keep the rest of the line, and
|
|
265
|
+
an error from the operating system prints its file names the same way
|
|
266
|
+
(`[Errno 2] No such file or directory: '<path>'`) instead of quoting them
|
|
267
|
+
with `repr`, which escaped a tab out of the scrubber's sight. A typo reads
|
|
268
|
+
as before (`No such command 'chek'. Did you mean 'check'?`); a value the
|
|
269
|
+
scrubber treats as an assignment, such as `token:v2`, is still masked
|
|
270
|
+
(`'token=<redacted>`). The `"output"` path in the JSON receipts of `policy compile`,
|
|
271
|
+
`policy review-plan`, `policy impact --output`, `preflight` and
|
|
272
|
+
`control-preflight` is left as given: it names the file the caller asked
|
|
273
|
+
for.
|
|
274
|
+
- Diagnostics that print a value with `repr` no longer show a credential
|
|
275
|
+
whose tab or no-break space it escaped (#229). Before, `repr`, `ascii`,
|
|
276
|
+
`%r`, a list or dict printed whole, a `KeyError` key and library errors
|
|
277
|
+
such as `invalid literal for int() with base 10: '<value>'` or
|
|
278
|
+
`could not convert string to float: b'<value>'` printed
|
|
279
|
+
`password<TAB>=secret` as `password\t=secret`, which the scrubber does not
|
|
280
|
+
read as an assignment: in `unknown profile` errors from the MCP tools, the
|
|
281
|
+
`AGENT_NOT_FOUND` line of the selector commands, `policy propose`
|
|
282
|
+
observation errors, and log records whose arguments were bytes or
|
|
283
|
+
containers. Now every message that goes through the credential scrubber
|
|
284
|
+
(CLI errors, MCP error text, log records, printed paths) is read as given
|
|
285
|
+
for str and bytes literals that hold a backslash escape, exactly as `repr`
|
|
286
|
+
or `ascii` prints them; such a literal that reads back as
|
|
287
|
+
credential-shaped, alone or in place in the message, withholds the whole
|
|
288
|
+
message, and so do more than 32 such literals. A literal without an
|
|
289
|
+
escape is scrubbed as before (`'password=secret'` reads
|
|
290
|
+
`'password=<redacted>`). Ordinary text is unchanged: a path is read only
|
|
291
|
+
when quoted as `repr` would quote it, so `C:\bearer\notes.json` is still
|
|
292
|
+
shown. A message that holds an escaped literal and also a credential the
|
|
293
|
+
scrubber would mask is withheld instead of masked. Not read: a literal
|
|
294
|
+
printed through `repr` twice, one right after an odd run of backslashes,
|
|
295
|
+
and one cut without its closing quote, as `int()` prints a value whose
|
|
296
|
+
repr is longer than 200 characters; the `int()` error itself is now
|
|
297
|
+
rendered without its value (#236, below). Artifact and evidence text,
|
|
298
|
+
which is stored rather than printed, is not affected.
|
|
299
|
+
- An `int()` error and a reply the MCP SDK could not parse no longer show
|
|
300
|
+
a credential from the value they quote (#236, #239). Both print a
|
|
301
|
+
shortened repr the scrubber cannot read back: `int()` cuts it at 200
|
|
302
|
+
characters with no closing quote, and the SDK copies pydantic's error, which
|
|
303
|
+
shortens `input_value` in the middle and can split an escape such as
|
|
304
|
+
`\x01` or drop the quote that decided repr's quote character. A
|
|
305
|
+
streamable-http server answering a crawl with a JSON string showed
|
|
306
|
+
`password<TAB>=secret` in the `[error]` line, and an `int()` error
|
|
307
|
+
chained under a withheld one showed it in a log record. Now a
|
|
308
|
+
`ValueError` whose only argument is `int()`'s text prints
|
|
309
|
+
`invalid literal for int() with base N (value not shown)`, and an MCP
|
|
310
|
+
error with the JSON-RPC parse error code (-32700), including one a server
|
|
311
|
+
sent itself, prints `the server's reply could not be parsed (JSON-RPC
|
|
312
|
+
parse error)`, wherever Toolgraph renders an exception: crawl failures,
|
|
313
|
+
log records (where an exception whose arguments are credential-shaped is
|
|
314
|
+
still withheld whole, as before) and every exception `doctor` reports,
|
|
315
|
+
which now renders exceptions like the other commands. Not covered: an
|
|
316
|
+
`int()` error raised as a `ValueError` subclass (a `str` subclass whose
|
|
317
|
+
`__repr__` raises one), and `re.Match` reprs (`%.50R`).
|
|
318
|
+
- A credential name at the end of a URL no longer leaves its value readable
|
|
319
|
+
in a scrubbed message (#232). The scrubber rewrites each URL to its origin
|
|
320
|
+
before it masks credentials, and a URL ends at whitespace or a quote, so
|
|
321
|
+
`fetch http://h/password =secret` printed `fetch http://h =secret`; so did
|
|
322
|
+
a name in a query (`?token =x`), with a tab or `: ` after it,
|
|
323
|
+
`.../authorization: Basic x`, `.../bearer x`, and a bracketed value
|
|
324
|
+
starting in the URL (`.../token=[a, x]`). Such a message is now withheld
|
|
325
|
+
whole. The text cannot tell a prose colon from an assignment, so
|
|
326
|
+
diagnostics whose URL path ends in a credential name with text after it
|
|
327
|
+
are withheld too: `Error accessing https://host/token: connection
|
|
328
|
+
refused`, `GET https://h/oauth/bearer returned 404`, `Client error for url
|
|
329
|
+
'https://h/token='`, `GET https://h/password={id} returned 404`, a host
|
|
330
|
+
that is a credential name (`Error accessing https://token: connection
|
|
331
|
+
refused`), and a name in the host position of a malformed scheme
|
|
332
|
+
(`1https://password : x`, which was masked). A value that starts inside
|
|
333
|
+
the URL and continues past a quote, `<`, `>` or a tab
|
|
334
|
+
(`http://h/x?token=abc"def` shows `"def`, and with an input placeholder
|
|
335
|
+
`http://h/token=abc<redacted>x` shows `http://h>x`), is not caught, nor
|
|
336
|
+
are forms only the scrubber reads as an assignment: `.../password :x`,
|
|
337
|
+
`.../password:[a, x]`, `.../authorization=Basic x` and
|
|
338
|
+
`.../proxy_authorization=Basic x` (#241).
|
|
339
|
+
- A command that writes a file or a review annotation no longer prints a
|
|
340
|
+
traceback when the durability warning is escalated to an error (#129).
|
|
341
|
+
The warning (`ArtifactDurabilityWarning`, or
|
|
342
|
+
`ReviewCandidateDurabilityWarning` for `review-candidates annotate`) is
|
|
343
|
+
raised after the write is published, when the directory could not be
|
|
344
|
+
fsynced. With `-W error` the command still fails, as asked, but with one
|
|
345
|
+
`ERROR:` line and exit 1 instead of a traceback; no JSON receipt is
|
|
346
|
+
printed. Before, `init` also deleted the database it had just created
|
|
347
|
+
while keeping the published `config.json` that names it; both are now
|
|
348
|
+
kept. The annotation warning now ends `do not retry: a retry appends a
|
|
349
|
+
second event`, since the event is already recorded. Library calls still
|
|
350
|
+
raise the escalated warning after publication.
|
|
351
|
+
|
|
5
352
|
## 0.3.0 - 2026-10-19
|
|
6
353
|
|
|
7
354
|
Fourth alpha. Read **Upgrading from 0.2.0** below before installing: it lists
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: toolgraph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Graph-native MCP/tool registry, policy compiler, and impact analyzer
|
|
5
5
|
Project-URL: Homepage, https://github.com/memtomem/toolgraph
|
|
6
6
|
Project-URL: Documentation, https://github.com/memtomem/toolgraph/blob/main/docs/beginner-guide.md
|
|
@@ -342,8 +342,13 @@ come from the orchestrator and Tracegraph runs against your own fleet.
|
|
|
342
342
|
`preflight` wraps the same deterministic selector rules and stamps the result
|
|
343
343
|
with a bracketed `graph_generation`. It remains advisory: rejected candidates
|
|
344
344
|
produce `decision: advisory_warn` and exit 0. Unknown agents produce
|
|
345
|
-
`unresolved_identity`, never a false allow. Resource URIs in evidence
|
|
346
|
-
scrubbed of userinfo
|
|
345
|
+
`unresolved_identity`, never a false allow. Resource URIs in evidence paths
|
|
346
|
+
and resource fields are scrubbed of userinfo, query strings and fragments
|
|
347
|
+
before the artifact is written; one with an `@` after its authority, or one
|
|
348
|
+
that cannot be parsed, is shown as `<scheme>://<redacted-uri>`. Inside free
|
|
349
|
+
text, such as an exception reason, a URI ends at whitespace or `)`, so a
|
|
350
|
+
query or fragment holding `)` (`https://h/r?k=a)b`) still shows what follows
|
|
351
|
+
the `)`. Pass
|
|
347
352
|
`--features` only when the consumer needs the full selector feature rows.
|
|
348
353
|
With `--out` the artifact is written privately and atomically, and the command
|
|
349
354
|
prints a result envelope carrying `artifact_digest` — the SHA-256 of the exact
|
|
@@ -378,11 +383,14 @@ Local review notes may cite paths but reject credential-shaped content: a
|
|
|
378
383
|
`<name>=`, or a `<name>:` followed by a space or a quote, where the name is a
|
|
379
384
|
token, password, secret, credential, API key or authorization, so `token:
|
|
380
385
|
rotated weekly` has to be rephrased while a versioned tool name such as
|
|
381
|
-
`srv::get_token:v2` is fine. Reviewer names
|
|
382
|
-
keys
|
|
386
|
+
`srv::get_token:v2` is fine. Reviewer names are checked the same way, and the
|
|
387
|
+
report's run ids and tool keys more strictly: as identities, they also refuse
|
|
388
|
+
any URI with an `@` after its `://`.
|
|
383
389
|
If a genuine parent-directory fsync error occurs after replacement, the event
|
|
384
390
|
remains successful and visible while a durability warning is written to stderr;
|
|
385
|
-
retrying would append a duplicate event.
|
|
391
|
+
retrying would append a duplicate event. With that warning escalated to an
|
|
392
|
+
error (`-W error`), `annotate` exits 1 with one `ERROR:` line, and the event is
|
|
393
|
+
still recorded: do not retry.
|
|
386
394
|
|
|
387
395
|
Candidate ids use the same exact-tuple UUIDv5 as SyncMill's merged
|
|
388
396
|
`board import-review-candidates` consumer (SyncMill PR #57), so the two human
|
|
@@ -472,12 +480,20 @@ to stderr) and exit 0 by default — gating is opt-in.
|
|
|
472
480
|
| `audit-report --fail-on-blocking` | 1 when `status` is `fail` (unsafe violations or drifted governed tools) |
|
|
473
481
|
| `preflight` | 0 for advisory allow, warn, and unresolved identity · 1 when the artifact cannot be built or written |
|
|
474
482
|
| `control-preflight` | 0 including advisory warnings · 1 on an invalid plan or an I/O failure |
|
|
475
|
-
| `policy compile` | 0 after atomic bundle replacement · 1 on unresolved identity/state or I/O failure |
|
|
483
|
+
| `policy compile` | 0 after atomic bundle replacement (see the note below on escalated durability warnings) · 1 on unresolved identity/state or I/O failure |
|
|
476
484
|
| `policy review-plan` | 0 · 1 on an invalid or stale report, unresolved graph state, or I/O failure |
|
|
477
485
|
| `policy propose` | 0 · 1 on invalid input or a refused export · 2 for `--json` with `--output`, or `--min-would-block` below 1 |
|
|
478
486
|
| `review-candidates list/annotate` | 0 on valid JSON output · 1 on invalid/stale artifacts or I/O failure · 2 on invalid CLI arguments |
|
|
479
487
|
| `example init` | 2 when the destination already exists |
|
|
480
488
|
|
|
489
|
+
A command that writes a file (`init`, `preflight --out`, `control-preflight
|
|
490
|
+
--out`, `policy impact --output`, `policy compile`, `policy review-plan`,
|
|
491
|
+
`policy propose --output`) or a review annotation warns on stderr and exits as
|
|
492
|
+
above when the file is published but its directory could not be fsynced. With
|
|
493
|
+
that warning escalated to an error (`-W error`), it exits 1 with one `ERROR:`
|
|
494
|
+
line and prints no receipt; the file or event is published, but its crash
|
|
495
|
+
durability is uncertain.
|
|
496
|
+
|
|
481
497
|
An error outside these classes exits 1 with a Python traceback instead of an
|
|
482
498
|
`ERROR:` line.
|
|
483
499
|
|
|
@@ -303,8 +303,13 @@ come from the orchestrator and Tracegraph runs against your own fleet.
|
|
|
303
303
|
`preflight` wraps the same deterministic selector rules and stamps the result
|
|
304
304
|
with a bracketed `graph_generation`. It remains advisory: rejected candidates
|
|
305
305
|
produce `decision: advisory_warn` and exit 0. Unknown agents produce
|
|
306
|
-
`unresolved_identity`, never a false allow. Resource URIs in evidence
|
|
307
|
-
scrubbed of userinfo
|
|
306
|
+
`unresolved_identity`, never a false allow. Resource URIs in evidence paths
|
|
307
|
+
and resource fields are scrubbed of userinfo, query strings and fragments
|
|
308
|
+
before the artifact is written; one with an `@` after its authority, or one
|
|
309
|
+
that cannot be parsed, is shown as `<scheme>://<redacted-uri>`. Inside free
|
|
310
|
+
text, such as an exception reason, a URI ends at whitespace or `)`, so a
|
|
311
|
+
query or fragment holding `)` (`https://h/r?k=a)b`) still shows what follows
|
|
312
|
+
the `)`. Pass
|
|
308
313
|
`--features` only when the consumer needs the full selector feature rows.
|
|
309
314
|
With `--out` the artifact is written privately and atomically, and the command
|
|
310
315
|
prints a result envelope carrying `artifact_digest` — the SHA-256 of the exact
|
|
@@ -339,11 +344,14 @@ Local review notes may cite paths but reject credential-shaped content: a
|
|
|
339
344
|
`<name>=`, or a `<name>:` followed by a space or a quote, where the name is a
|
|
340
345
|
token, password, secret, credential, API key or authorization, so `token:
|
|
341
346
|
rotated weekly` has to be rephrased while a versioned tool name such as
|
|
342
|
-
`srv::get_token:v2` is fine. Reviewer names
|
|
343
|
-
keys
|
|
347
|
+
`srv::get_token:v2` is fine. Reviewer names are checked the same way, and the
|
|
348
|
+
report's run ids and tool keys more strictly: as identities, they also refuse
|
|
349
|
+
any URI with an `@` after its `://`.
|
|
344
350
|
If a genuine parent-directory fsync error occurs after replacement, the event
|
|
345
351
|
remains successful and visible while a durability warning is written to stderr;
|
|
346
|
-
retrying would append a duplicate event.
|
|
352
|
+
retrying would append a duplicate event. With that warning escalated to an
|
|
353
|
+
error (`-W error`), `annotate` exits 1 with one `ERROR:` line, and the event is
|
|
354
|
+
still recorded: do not retry.
|
|
347
355
|
|
|
348
356
|
Candidate ids use the same exact-tuple UUIDv5 as SyncMill's merged
|
|
349
357
|
`board import-review-candidates` consumer (SyncMill PR #57), so the two human
|
|
@@ -433,12 +441,20 @@ to stderr) and exit 0 by default — gating is opt-in.
|
|
|
433
441
|
| `audit-report --fail-on-blocking` | 1 when `status` is `fail` (unsafe violations or drifted governed tools) |
|
|
434
442
|
| `preflight` | 0 for advisory allow, warn, and unresolved identity · 1 when the artifact cannot be built or written |
|
|
435
443
|
| `control-preflight` | 0 including advisory warnings · 1 on an invalid plan or an I/O failure |
|
|
436
|
-
| `policy compile` | 0 after atomic bundle replacement · 1 on unresolved identity/state or I/O failure |
|
|
444
|
+
| `policy compile` | 0 after atomic bundle replacement (see the note below on escalated durability warnings) · 1 on unresolved identity/state or I/O failure |
|
|
437
445
|
| `policy review-plan` | 0 · 1 on an invalid or stale report, unresolved graph state, or I/O failure |
|
|
438
446
|
| `policy propose` | 0 · 1 on invalid input or a refused export · 2 for `--json` with `--output`, or `--min-would-block` below 1 |
|
|
439
447
|
| `review-candidates list/annotate` | 0 on valid JSON output · 1 on invalid/stale artifacts or I/O failure · 2 on invalid CLI arguments |
|
|
440
448
|
| `example init` | 2 when the destination already exists |
|
|
441
449
|
|
|
450
|
+
A command that writes a file (`init`, `preflight --out`, `control-preflight
|
|
451
|
+
--out`, `policy impact --output`, `policy compile`, `policy review-plan`,
|
|
452
|
+
`policy propose --output`) or a review annotation warns on stderr and exits as
|
|
453
|
+
above when the file is published but its directory could not be fsynced. With
|
|
454
|
+
that warning escalated to an error (`-W error`), it exits 1 with one `ERROR:`
|
|
455
|
+
line and prints no receipt; the file or event is published, but its crash
|
|
456
|
+
durability is uncertain.
|
|
457
|
+
|
|
442
458
|
An error outside these classes exits 1 with a Python traceback instead of an
|
|
443
459
|
`ERROR:` line.
|
|
444
460
|
|