Changelog¶
English | Português (Brasil)
All notable changes to KG-SSOT are documented here. The format follows
Keep a Changelog, and releases are tagged kg-ssot-vX.Y.Z.
The contract version (v4.N) is the version of the .kg.yaml / .kg.json contract that a release ships; it moves
only when the contract changes. Each release section is also the body of its GitHub release.
Releases before 4.16.0 are described in ADOPTING.md.
Unreleased¶
[4.45.0] - 2026-11-07¶
No contract change (still v4.18), and no verdict changes. Nothing that a reader, the gate or the store prints changes.
- Every artifact of the release has an SBOM in CycloneDX 1.7 JSON: the wheel and the sdist on PyPI, the npm tarball, the
MCP Bundle and the OCI image. Each
<artifact>.cdx.jsonis attached to the GitHub release and attested with the CycloneDX predicate (gh attestation verify <artifact> --repo marciocar/kg-ssot --predicate-type https://cyclonedx.org/bom); the image SBOM is also attested in the registry. It lists the dependencies that the artifact declares, read from inside it, as external (isExternal, with the declared range inversionRange), with the license, the tested version and the transitive dependencies of the environment where the suite passed. The bundle SBOM lists every package of itsuv.lock, with the hash of each file, and the image SBOM lists the base image by digest, its Debian packages (with the source package in the purlupstreamqualifier, which is how a scanner finds the Debian advisory), the Python interpreter (with its CPE) and every installed Python distribution. A declared range carries no purl: a purl without a version would match every version in a scanner. On grype 0.121.2 the image SBOM gives the same 189 matches as syft's, and the wheel, sdist, npm and bundle SBOMs give none below the declared range. - The build lockfile of the JavaScript reader moves
fast-urito 3.1.9, within ajv's range. - The generator is
packaging/sbom/sbom.py, with the Python standard library only, and it ships in the release. It refuses an artifact that embeds third-party code (node_modules,site-packages, a wheel inside a package): none does today, and the SBOM's claim is checked on every release. SECURITY.mdis new: only the latest release gets security fixes, reports go through GitHub Private Vulnerability Reporting on the public repository (no e-mail), with acknowledgement within 7 days, a first assessment within 14 and a fix within 90, the scope, and a 90-day coordinated disclosure.- The release hygiene fails a release without
SECURITY.md(with its five sections and the private channel), without the generator, or with a release workflow that does not generate, attach and attest the SBOM of each artifact. - The manual has a new section, "Verify a release: provenance and SBOM", in chapter 6.
- The recommended shape of
x_event, not normative (manual, chapter 3). An external event is anevidencenode whosex_eventuses the CloudEvents 1.0 core names (id,source,type,specversion: "1.0", and optionallysubject, a quoted RFC 3339time,datacontenttypeanddataref). The collector records the body as a file thatprovenance.sourceandprovenance.sha256point to;x_received_atis apart fromtimeandverified_at;source+idis the idempotency key; without a checked signature the node staysopen. With a complete example that passeskg_validateandkg_trace check. The contract does not change (still v4.18): with two adopters, or 50 real nodes in this shape, it becomes a canonical profile in the SHOULD. - The recommended shape of
x_change, not normative (manual, chapter 3), the sibling ofx_event. A planned change to a system, a policy or a process is adecisionwhosex_changehasstate(planned,communicated,applied,verified,reverted),target, a quoted RFC 3339window,rollback_plan(a pointer), and optionallykind(the ITIL 4 types),refandcommunicated_by(theevidencewith thex_eventof the notice). Each transition is anevidencewithx_change_transition(to,at) thatSUPPORTSthe decision;verifiedneeds ameasurement, and a reversionSUPERSEDESthe application it undoes. Who approved goes insealed_by, by reference, never by name.kg_query --where x_change.state=appliedlists the changes by state. With a complete example that passeskg_validateandkg_trace check. The contract does not change (still v4.18): with two adopters, or 50 real nodes in this shape, it becomes a canonical profile in the SHOULD. - Why: a buyer subject to the EU Cyber Resilience Act owes due diligence on the free components it integrates, and will ask for an SBOM and a vulnerability policy. The maintainer of a free project outside commercial activity has no legal duty here; the release ships both anyway.
- Personal data, LGPD and GDPR (ADOPTING.md): a new section on erasure
by design, with no contract change. The adopter is the controller of the personal data in the graph and in its git
history, and the tools send the graph to no one on their own; a person enters by reference, with the table that maps
the reference to the person kept out of git, and the reference stays pseudonymous personal data while that table
exists, so
labelandnarrativemust not identify the person either; anode_sha256published over a node that can identify a person is itself personal data and stays out of a public mirror (a salted hash is not in the contract yet); no public mirror holds personal data; rewriting history breaks hash verification and does not reach forks or archives;owneris a role; and an account handle or a machine path with a user name is personal data too.
[4.44.0] - 2026-11-05¶
No contract change (still v4.18), and no verdict changes. kg_query prints the same, and kg_trace check without --strict-sources keeps its exit codes and, in a corpus without the form repo#id@commit, the same kg-ssot.trace/1.
kg_refs.py(kg-refs) is new, with a new output,kg-ssot.refs/1(spec/refs.schema.json). It reads a declared list of local clones (--repo NAME=FOLDER, or--repos FILE), only from git objects, without network.cited-by REPO:IDsays who cites a node, in any node field and in which form, and--at NAME=COMMITreads an earlier state.pinscompares each citation to another repository of the list, at its pinned commit, against the tip:same,changed(status,text,edges,file,stamp_only,other, with the current status of the node),gone,ambiguousorunchecked, which also covers a graph that writes the id and does not read, and objects missing, as in a partial clone.--from REPOand--status gone,changedonly cut what it lists; the scan,summaryand the rc stay those of the whole list, withgoneandstatusfirst.- It is a query, not an alarm: a change never alters the exit code, and no option makes it. Every output has
scannedandnot_scanned, because an empty answer does not prove absence: rc 0 complete, rc 3 partial (a folder missing or unreadable, no graph read where one was needed, because the file rules took them all out, an--atread a state with none, thecited-bytarget has none or the whole scan has none, a pinned commit missing in any form, a--knownrepository with no folder, a name cited with a commit outside the list), rc 4 only inpins, on a complete scan, when something isambiguousorunchecked, and rc 2 without a declared list.--known NAMEdeclares the network, with a folder or without one: with it, the names outside go tomentioned_undeclaredand their citations with an id tooutside_citations, without changing the rc.cited-byalso lists who cites the whole file that contains the node (contains_file). kg_trace checkcountsprovenance.sourcein the formrepo#id@commit, without changing any rc: when the form occurs, a new line in the human output andschemes.repo_refsin the JSON, which is thenkg-ssot.trace/2.--strict-sourceslists each source with a scheme or a sha256 that is absent and gives its own rc, 4, withkg-ssot.trace/2too; a source with neither is not judged, and that includes a URL with a commit and no hash.- The limit: it finds a wrong citation, not a missing one. It reads node fields and the folders you list, and it says nothing about a repository you have no folder for; it has not been tried by a third party without access to them. Why: the five losses measured in 157 citations across three repositories were an answer nobody looked up, not a broken file.
[4.43.0] - 2026-11-04¶
No contract change (still v4.18), and no verdict changes. A query that worked in 4.42.0 prints the same kg-ssot.query/1, except for the two new refusals at the end of this list.
kg_query --due [--as-of YYYY-MM-DD]lists the nodes of any type whoseverified_atplusreverify_everyis due, the most overdue first, withdue_onanddays_late. The sum is that ofkg_validate --due, called, not copied; what that report adds forx_term,x_mirrorandx_doc_cyclestays there. Only this query printskg-ssot.query/2: the schema is closed and the new fields need a new version. A consumer that uses--dueand checksschema_versionshould accept/2.- The node
--whereand--group-bytake the degree derived from the internal edges of the node's graph:degree.in,degree.out,degree.total, with an optional edge type.--where degree.in.SUPPORTS=0finds the nodes with no internalSUPPORTSedge in their own graph. ASUPPORTSfrom another graph is not counted: complete it with--edges --where edge_type=SUPPORTS --where scope=external; every answer with the degree says so in itshint. An edge type that no internal edge has counts 0 with ahint, and in--revisit, which has nohint, it is refused (rc 2). --has FIELDand--missing FIELD(repeatable, a dot for a nested key) test any field of a node. A missing field andnullare the same; empty text and0have a value. Only when asked, the request echo gainshasandmissing. A path that no node of the corpus has with a value gets ahint, and a path that starts withdegreeis refused (rc 2).--group-bytakeslabelandnarrative, with the whole text: with--min-count 2, the nodes with the same text.--wheretakesKEY^=VALUE(starts with) andKEY*=VALUE(contains), for nodes and edges: case-sensitive, in NFC, and only text matches, as Cypher'sSTARTS WITHandCONTAINS. After the=, the value is literal.--chainis repeatable, up to 50 ids, and more than one prints the newkg-ssot.chains/1(spec/chains.schema.json): onekg-ssot.chain/1per id, with the status of each origin. One id still printskg-ssot.chain/1.- The MCP
kg_querytool takesdue,as_of,has,missingandchain, and its description gives the new grammar. - The
kg_query --helpno longer describes the tool as of release 4.17. - New refusal: a
--wherekey that still ends in^ * $ ! < > ~ |after the operator is read is rc 2, for nodes and edges.x_n!=12used to look for a key namedx_n!and answer 0 without a word; no real key ended that way in the corpora measured. - New refusal: the MCP
kg_querytool withedgesno longer takesopen,in_narrativeornarrative, which it used to ignore. Why: how many finished decisions have no internalSUPPORTSedge, a question no single query answered, now comes out of one, with the same 14 as an answer key and an independent count on a real corpus.
[4.42.0] - 2026-11-04¶
No contract change (still v4.18), and no verdict changes. kg-ssot.query/1 does not change.
kg_query --edgescounts and lists edges instead of nodes: the internal and the external ones.--whereand--group-bytakeedge_type,scope,file,meta.<key>, andfrom.<field>andto.<field>(id,file,found,node_type,status,plane,layer,owner,x_*orprovenance.<key>). Without--group-byit prints the paged listkg-ssot.edges/1(spec/edges.schema.json).--count-distinct FIELD(repeatable) counts the distinct values or combinations in each group; a null value does not count, as in Cypher'scount(DISTINCT), and neither does a combination with any null, as in SQL'sCOUNT(DISTINCT a, b);--min-count Nkeeps the groups from N, as SPARQL'sHAVING. Both work for nodes and edges. Together they give the in-degree of a node from distinct sources.- The node
--whereand--group-bytakeprovenance.<key>, such asprovenance.locality. - The grouped output moves to
kg-ssot.groups/2: it gainssubject(nodesoredges) andcounted(rowsordistinct:<fields>), and a distinct count can be 0. A consumer that checksschema_versionshould accept/2. - The MCP
kg_querytool takesedges,count_distinctandmin_count. Why: in the curation's bench of 15 counting questions over a corpus, five more now come out ofkg_queryalone, equal to the answer key (counts by edge type, in-degree, external targets, provenance and supersession).
[4.41.0] - 2026-11-03¶
No contract change (still v4.18), and no verdict changes. kg-ssot.query/1 does not change.
kg_query --group-by FIELDcounts the nodes a query finds, by the value of a field, instead of listing them:file,node_type,status,plane,layer,owner, anx_*field (a dot for a nested key) ormeta.<key>of the node's graph, repeatable up to 5 for the combination. The filters apply first, every group comes out without pages, a missing field is its ownnullgroup, and the counts add up tototal. The output is the newkg-ssot.groups/1, with its schema inspec/groups.schema.json. The MCPkg_querytool takes the same fields ingroup_by. It does not combine with--semantic,--cursor,--decision,--chainor--revisit(rc 2). Why: in a test where an agent answered 15 counting and filtering questions over a corpus, it wrote code over the YAML instead of usingkg_query, which skips the reader of the contract. The in-degree of a node is not covered yet.
[4.40.2] - 2026-11-03¶
No contract change (still v4.18), and no verdict changes.
- The manual shows how to count every owner's queue from one query: the JSON output of
kg_query --opencarriesowneronly when asked, with--fields owner, which returns it in each node'sextra, with--limitup to 1000 andnext_cursorpast that. - A term in force is
confirmedwith theprovenanceof its source. The term example in the manual and the terms ofspec/vocab-examples/cmed-dictionary.kg.yamlwereopen, which put settled definitions in the queue of open nodes.
[4.40.1] - 2026-10-30¶
No contract change (still v4.18), and no verdict changes.
- The README and the package descriptions on PyPI, npm, the MCP Registry and the MCPB bundle now say what KG-SSOT is for: the trust layer for a company brain, where a confirmed claim cites its source, a decision records what it replaced and what it rests on, and a decision comes back for review when a source it pinned by hash changes. A new section maps each need to the command that answers it.
- The manual has a recipe for an external event (an e-mail, a chat message, a wiki edit) that arrived by a signed
message: an
evidencenode with the CloudEvents 1.0.2 names inx_event, pinned to the accepted inbox document. kg-query --revisit --helpnow names the reasons it really uses, includingpin-changed; it named a refuted or superseded support, which was never a reason (4.36.0 left it out before the release).
[4.40.0] - 2026-10-30¶
No contract change (still v4.18). An approved document now comes back to the review queue when it changes.
kg_query --revisitgains thepin-changedreason: a live decision comes back when a support, or the node that support cites bykg-nodeone step away, no longer matches the version it was pinned to, byprovenance.node_sha256(the node was rewritten) or byprovenance.sha256(the text of the file changed without touching the graph). It is the same check askg_trace check, and the item says what changed, which pin, the recorded hash and the current one; a pinned node that disappeared from its document fires too, withcurrentnull. A pin that cannot be checked goes tounresolved(pin_malformed,pin_source_missing,pin_unchecked). Pins are checked with--root; a support read through--withis not checked yet. This is the approval of a controlled document: the decision is the testimony, and an evidence that supports it pins the approved version. The JSON is nowkg-ssot.revisit/2.kg_trace.py digesttakes a batch: a file that does not exist isfile_missing, and a file that is not UTF-8 isunreadablewithcodeencoding.not-utf8(orio.unreadable), each as the result of its own reference, and the batch goes on (rc 1). Only a path outside the root, or a reference withoutFILE#ID, stops the batch with rc 2. The schema now tiesnode_sha256took. The JSON is nowkg-ssot.digest/2.- From now on, a JSON output with a closed schema that gains a value or a field moves to a new schema version. The
4.39.1 widened
kg-ssot.digest/1in place, which broke a consumer that validated strictly against the 4.39.0 schema; it stays as published, and this release names the change.
[4.39.1] - 2026-10-29¶
No contract change (still v4.18).
kg-trace digest: a graph that does not parse is nowunreadable, with the reader'scode(text:UNREADABLE FILE#ID CODE), and no longernot_found. The node was not looked for, so it is not missing. The exit code stays 1. Found in a field test of 4.39.0.
[4.39.0] - 2026-10-29¶
No contract change (still v4.18). When several long-lived sessions share one coordination graph, each with its
owner, each one opens with its own queue.
kg_query --where KEY=VALUE: only the nodes whereowneror anx_*field (a dot for a nested key, such asx_raci.responsible) has the value; a list matches when one of its items does, and the conditions add up. It works with--open, the other filters and--revisit, which then judges only the matching decisions; the JSON echoes the conditions inwhere, and the MCPkg_querytool takes the same filter. On the Onion core, the queue of each of its five owners matches an independent count exactly.kg-trace digest --format jsonreturnskg-ssot.digest/1(spec/digest.schema.json), withnode_sha256and astatusper reference, so nobody has to split the text line. With a broken input,digestnow stops before printing any result, in both formats (it used to print the good lines before the error).- Left out before any code, with the trigger written: turning a REFUTES between graphs of different verticals into a
questionwith an owner (no such edge exists yet in the corpus it was meant for), and a--duelisting of overdue nodes of any type (no overdue cadence there yet).
[4.38.0] - 2026-10-28¶
Contract v4.18, SHOULD only: nothing that passed fails. A company brain spans several repositories, and a decision in one rests on a seal in another. This release reads that support where it was pinned, says when its source moved, and gives a bridge to a node a hash that only that node can break.
kg_query --revisitand--decisionaccept--with PREFIX=FOLDER: a support given by aprovenance.sourceURL is read at its pinned commit from a local clone.--revisitjudges only local decisions, and a support that does not resolve carries its reason (no_with,not_found,not_pinned_url,unpinned,outside_corpus); a support read without a real pin is never judged against the working tree.upstream_changedlists, apart and never as a reason to revisit, the local decisions whose support has another status, or is gone, at the head of the clone;--decisionshows that status ashead_status.provenance.node_sha256is the SHA-256 of the RFC 8785 (JCS) canonical form of the cited node, withscheme: kg-node: a change in a neighbor node of the same file does not break it.kg_trace checkcompares it (integrity.trace-node-sha256-mismatch),kg_trace.py digest FILE#IDprints it, and the JavaScript reader exportsnodeCanonical. There is one JCS in the product, the strict one ofkg_criterion, and a node with no JCS form (a repeated id, an integer value outside ±(2^53−1)) counts asnot_canonicalinstead of failing.kg_traceno longer emits the Python 3.15DeprecationWarningof a positionalmaxsplit.- The conformance suite goes from 508 to 512 cases.
[4.37.0] - 2026-10-28¶
Contract v4.17, SHOULD only: nothing that passed fails. A claim already says where it came from; now it can say exactly where in the source, and keep a hash of what it read, so a tool can check both.
provenance.schemesays how to read the locator:json-pointer(RFC 6901),text(RFC 5147, positions from 0, characters as code points and each line ending as one),csv(RFC 7111, one kind of selection, from 1),pdf(RFC 8118, the physical page from 1),media(Media Fragments, npt, the end excluded),quote(the exact text),html(#id) andkg-node(a node of the graph named insource). The W3C Web AnnotationconformsToURIs count as aliases, including the RFC 3778 one. A locator that does not fit its scheme warns asform.pattern.node.provenance.locator.provenance.sha256is the SHA-256 of what was read, in its own field (form.pattern.node.provenance.sha256);kg_tracehashes a repository source as git stores it, so the hash is the same on Windows and on Linux.kg_trace checkreads the source by its scheme and hash: the line, character, row, column, quote, node or JSON Pointer exists, and the hash matches (integrity.trace-locator-out-of-range,integrity.trace-quote-missing,integrity.trace-sha256-mismatch). With--with PREFIX=FOLDER, a source given by URL is read at its pinned commit from the root of a local git clone; a commit the clone does not have, or a folder that is not the root of a clone, is never read from the working tree, and a branch name (main) counts asunpinned. What is not judged is counted (form_only,unpinned,malformed,not_json,not_text), never as ok. The JSON output gainsschemes.- The JS reader exports
textInRange, which counts code points and line endings like the Python reader. The conformance suite goes from 463 to 508 cases. - The open tension between two claims, planned for this release, was measured against its pre-registered criterion and left out (2 of 10 with a blind evaluator, against a floor of 3).
[4.36.0] - 2026-10-28¶
Contract v4.16, unchanged. A graph already keeps the history of its decisions: a new decision supersedes the old one, a refutation keeps the refuted node, and nothing is deleted. This release makes that history something you can ask.
kg_query --sort attentionorders nodes by their weight on the radar,impact × confidence × status factor × (1 + degree), with the degree scoped to the node's file and ties broken by id in descending order, so it gives the same queue as the radar of the framework where the format was born on a graph that passes the MUST.kg_query --decision <id>gives the history of a decision: what it replaced and what replaced it along the wholeSUPERSEDESline, what refutes and supports it, and what it rests on, with the status of each (kg-ssot.decision/1).kg_query --revisit [--as-of DATE]lists the live decisions to revisit, by attention, with the reason:evidence-due,review-dueorcontested, where the support is what reaches the decision bySUPPORTSand what it traces to or depends on; a support that was refuted or superseded is history shown by--decision, not a reason; decisions that wait for an event, have a cadence but no stamp, or rest on something outside the corpus are listed apart (kg-ssot.revisit/1).kg_trace.py projection: an id of a page whose declared graph does not read is no longermissing; the graph error still fails the page.
[4.35.0] - 2026-10-27¶
Contract v4.16, unchanged. Python 3.12 or later (it was 3.11): the package, the MCPB bundle and the docs say so, CI
runs 3.12, 3.14 and 3.15, and the optional vector extra stops at 3.14 until onnxruntime ships for 3.15.
kg_query --chain --with PREFIX=FOLDERfollows aprovenance.sourceURL into a graph of another repository, read only, at the commit the URL cites when the folder has it (pinned), otherwise from the working tree with a warning. A ref that is absent, or a file that is not at it, gives a broken link; a ref that starts with-is refused.kg_trace.py projectionchecks Markdown pages that declareprojection_ofin their front matter: each node id they cite between backticks isresolved,elsewhere(a warning),outside(in another repository not read) ormissing(integrity.projection-id-missing, rc 1). Contract words andprojection_ignoreare counted apart.--format jsongiveskg-ssot.projection/1(spec/projection.schema.json).kg_trace checkfollows a JSON Pointerprovenance.locator(#/a/b) inside the.jsonfile the node's source names:integrity.trace-locator-missingandintegrity.trace-locator-unreadable, rc 1. The output gainslocators, additive.kg_hygienefails a release file that still cites the old Python floor.
[4.34.0] - 2026-10-27¶
Contract v4.16, unchanged. kg_query --chain follows provenance.source first: a node with a provenance.source
takes only its #<id> fragment as the next step, and a source that names no graph node makes the node the origin,
whatever edges leave it. Edges (TRACES_TO, or --edge-type) are followed only for a node with no
provenance.source, and the ones a node's provenance leaves out are named (edges_not_followed). An origin with no
provenance.source comes out with declared: false and makes the chain weak in both modes. Found in a field test
on real graphs: a decision sealed by its owner that traces to the question it answers showed up as the first link of
the question's history instead of the origin.
[4.33.0] - 2026-10-27¶
Contract v4.16, unchanged. kg_query --chain <id> projects the transmission chain from a node back to its origin: each
path in edge order (TRACES_TO by default, --edge-type for others), and the provenance.source fragment when it
names a node of a graph. Each path carries a status (complete, broken, cycle, too_long), the weakest link and
the strength of the origin on separate axes, and two grades: strict, which counts the origin, and lenient, which leaves
it out. A broken, cyclic or too long chain is weak in both modes, and a link with no numeric confidence is the
weakest one. The output is human or --format json (kg-ssot.chain/1), with caps of 200 steps and 1000 paths. The
criteria were committed before the code, and the projection passes them on the probes of an outside session.
[4.32.0] - 2026-10-27¶
Contract v4.16: the class of provenance.method has a canonical English form, measurement, reading, judges,
derived and testimony, and the five Portuguese words (medição, leitura, juízes, derivado, testemunho)
stay accepted as aliases, with no warning. testimony and testemunho count the same for
integrity.testimony-in-prod, in the Python and the JavaScript readers. The examples, the agent skill, the
kg_schema_help example (now spec/examples/minimal.kg.yaml), kg_capture.py and the demo use the English form; six
new conformance cases (463 in latest/). No existing graph changes its verdict, except a PROD node that already
used testimony:, which now gets integrity.testimony-in-prod (see ADOPTING.md).
[4.31.1] - 2026-10-27¶
The documentation site renders code blocks and diagrams: without declared Markdown extensions, Zensical left the fenced
code of the README and the manual as raw backticks. zensical.toml now declares them, and the site check in CI fails on
raw Markdown in the HTML. The README's first links are now one Documentation link to the site, in place of a separate docs-site
link and a manual link that opened the same pages. Contract v4.15, unchanged.
[4.31.0] - 2026-10-26¶
A documentation site at https://marciocar.github.io/kg-ssot/, built from the README, the adoption guide, the changelog
and the manual by tools/kg_site.py and Zensical, and published to GitHub Pages by the release workflow; a 40-second
demo in the README, recorded from docs/demo/demo.sh and listed in the new mirror_only of spec/release.json, so it is
published in the public repository and on the site but not vendored by adopters. The README and the manual counted the conformance suite as 438
cases while latest/ had 457; the count is fixed and the hygiene check now ties it to the suite manifests. Contract
v4.15, unchanged.
[4.30.0] - 2026-10-26¶
The release carries the agent skill skills/kg-ssot/ (Agent Skills format) and a Claude Code plugin marketplace in
.claude-plugin/. In a pre-registered test, agents with the skill matched the agents without it (12 of 12) with 45%
of the output tokens and 54% of the tool calls. Contract v4.15, unchanged.
[4.29.1] - 2026-10-26¶
kg_prov_export.py no longer emits a broken IRI for a malformed edge end. Contract v4.15, unchanged.
Fixed¶
kg_prov_export.pyno longer emits a broken IRI for a malformed edge end. With--edgesor--full, an end that was neither a valid node id nor an external reference (an absolute path, for example, which the contract rejects) became a local IRI with/or a second#. rdflib kept that triple, but pyoxigraph and pyrudof dropped it without an error, so the same output became different graphs depending on the reader. Such an end now goes as the literalkg:fromReforkg:toRef. Found by a dissection of the RDF readers in another repository that uses KG-SSOT.
[4.29.0] - 2026-10-26¶
The rdf extra brings rdflib to read the PROV export, with a CI job that rebuilds every edge from the RDF; and
kg_prov_export.py --full exports the whole document, so the loaded YAML can be rebuilt from the RDF. Contract v4.15,
unchanged.
Added¶
kg_prov_export.py --fullexports the whole document. It implies--edgesand also carries every field of every node and ofmetaaskg:<key>(text as a literal, anything else as JSON typedkg:json). So the loaded YAML can be rebuilt from the RDF, for SPARQL or SHACL over the same data. Measured against criteria registered in advance:- Correctness: structural equality of the loaded YAML in 209 graphs.
- Cost: 0.25 s and 19.7 MB for the whole core corpus on a CI runner, against ceilings of 10 s and 100 MB.
A first series failed on a ceiling of 3 times the plain export: the real ratio is about 3.6. That ceiling was
replaced, after the result, by absolute ceilings set knowing the cost was about 1 s. The measurement page says so,
and the code was frozen before the second series ran.
- The rdf extra. pip install "kg-ssot[rdf]", or tools/requirements-rdf.txt with hashes in the vendor,
brings rdflib, to read the kg-prov-export --edges output in RDF tools. It stays out of the gate profile. A new CI
job reads the --edges output of every valid suite case and of the repository's graphs, rebuilds every edge from
the RDF alone, checks the standard triples, and serializes to N-Triples.
[4.28.0] - 2026-10-25¶
kg_prov_export.py --edges exports the edges, with cito:supports, cito:refutes and dcterms:replaces where a
standard term exists; without the option the output does not change. Contract v4.15, unchanged.
Added¶
kg_prov_export.py --edgesexports the edges. By default the exporter still leaves the edges out, since derivation in PROV is production, not evidential support, and PROV has no pair forREFUTES. With--edges, every edge, fromedgesandexternal_edges, becomes akg:Edgeresource with its type, ends and every key, so it can be rebuilt from the RDF. Between two local nodes,SUPPORTS,REFUTESandSUPERSEDESalso get the standardcito:supports,cito:refutesanddcterms:replacestriples. No edge producesprov:wasDerivedFrom, and without the option the output does not change.- How it was measured: against criteria registered before the measurement, on 213 graphs and 9,988 edges. Every edge came back from the RDF read by rdflib 7.6.0, every standard triple had the right direction, and the cost was 1.7 times the plain export.
[4.27.0] - 2026-10-25¶
Contract v4.15: an external end can live in a dot folder such as .github/, while . and .. still fail; the git
store judges with the same corpus as the gate; kg_trace exits 3 on an empty corpus.
Changed¶
- Contract v4.15: an external end can live in a dot folder.
<path>#<id>used to require every segment to start with a letter, a digit, or_, so.github/and.config/, where a repository keeps its machinery, could not be pointed at. A segment can now start with a dot followed by one of those; the segments.and..still fail, since the rule exists to stop traversal. Nothing that passed fails. Both readers change together, for the outgoing and the incoming end, with eight new cases in the suite (five of form, three of corpus with a target in a dot folder). Found by an adopter whose studies point to a registry under a dot folder. - The git store judges with the same corpus as the gate.
kg_store(GitStore) now reads the tracked graphs in dot folders when it checks external ends, so a graph that points there can be written, and a change there can stop a write that would leave an end dangling. It still never writes in a dot folder, andlist(),read()and the MCPkg_queryandkg_termstools see only the store's own names. kg_trace.py checkexits 3 when no graph was read. It used to print "no graph read" with rc 0, which a CI that reads only the rc took as "every trace resolves". The JSON already saidgraphs: 0.
[4.26.0] - 2026-10-25¶
kg_query --semantic finds the node when the question uses other words than the label, fusing the text search and a
local embedding model; optional, and outside every verdict. Contract v4.14, unchanged.
Added¶
kg_query --semantic, the search by meaning. A question with other words than the label used to find nothing:--textmatches words.--semantic "<question>"orders the nodes that pass the other filters by similarity, using a local multilingual embedding model; with--text <term>, both lists are fused by RRF (k=60). On a curator's pre-registered bench of 20 questions, the fusion found 14, the vector alone 12 and the text alone 9. The JSON gainssemantic(mode, model with revision and sha256, index use) and arankper node; rc 3 says the vector cannot run here, so a caller falls back to--textand says so. Without--semantic, the output does not change. The MCPkg_querytool takessemantictoo.kg_vector.py, the model and the index.fetchdownloads the ONNX model pinned by revision and sha256, andindexkeeps a cache in.kg-ssot/vector/that redoes only the nodes that changed. Optional:pip install "kg-ssot[vector]"(onnxruntime, tokenizers, numpy), ortools/requirements-vector.txtwith hashes in the vendor. Nothing here enters a verdict. Also on PyPI askg-vector. Thegateprofile carriestools/kg_vector.pyandtools/requirements-vector.txt(17 files), becausekg_queryimports the module for--semantic.
Fixed¶
kg_query,kg_diffandkg_migratewrite UTF-8 when the output goes to a file or a pipe. On Windows the default was cp1252, and a label with→or a character outside it crashed the command; an accented label came out in another encoding. Found by the new CI job, which saved a query's JSON to a file.
[4.25.1] - 2026-10-24¶
kg_trace.py now says where each trace resolves, and the gate profile carries what it needs to run. Contract v4.14,
unchanged.
Added¶
kg_trace.pylists where each trace resolves. The JSON (kg-ssot.trace/1) gainsresolved_targets: for each trace that resolves, the file, the node id and type, the judged target, thepathof what the check saw exist, relative to the root and with links followed, and itskind(fileordir). An adopter that builds a read index from the traces no longer needs its own resolver. The field is additive and not required, so the output of 4.25.0 stays valid.
Fixed¶
- The
gateprofile carries whatkg_trace.pyimports. The profile now has 15 files:tools/kg_trace.py,tools/kg_query.py, which it imports, andspec/trace.schema.json, which defines its output, come along. Before, an adopter on--profile gatecould not run the trace check. The release check now requires every tool of a profile to bring thekg_*modules it imports.
[4.25.0] - 2026-10-24¶
A node's trace: that points to a moved file no longer passes in silence: kg_trace.py checks each anchor against the
files of the repository. Contract v4.14, unchanged.
Added¶
kg_trace.py, the trace that does not resolve. A node'strace:that points to a moved or renamed file passed the gate in silence: the reader does not touch the disk.kg_trace.py check --root .checks each anchor from the repository root and reportsintegrity.trace-target-missing(kg-ssot.trace/1). It judges only the text that looks like a path of the repository, and counts apart what it does not judge (absolute or URL, a declared--external-root, not a path). A name without a slash comes in only as a file tracked at the root with the exact name. It resolves at the root, in the graph's folder, or in its parent, and a target that leaves the root does not resolve. The rules are an adopter's resolver, measured on thousands of traces; on that adopter's corpus the tool judges the same 2,500 nodes, with 0 missing. Also on PyPI askg-trace.
[4.24.0] - 2026-10-24¶
An adopter can bench a commit before its tag without retagging it, and the bench vendor never passes for a release. Contract v4.14, unchanged.
Added¶
kg_vendor.py update --ref <commit>, for the bench before a tag. An adopter that tests a release before it exists had to retag a commit locally, because--tagrightly refuses a commit whosespec/release.jsondeclares another version.--refbrings the tree of a commit as it is; the stamp saysunreleasedand keeps the commit, andcheckfails on that vendor (UNRELEASED VENDOR, rc 1) unless--allow-unreleasedis given, so a bench vendor never passes for a release in CI.kg_gate.pyrefuses one the same way (--allow-unreleased, and never--updatefrom a bench vendor), so a CI that runs only the gate does not take the bench for a release. A laterupdate --tagreplaces it without--force. Requested by an adopter's bench.
[4.23.0] - 2026-10-23¶
A short code gets a readable name in its own vocabulary, and the codes in prose are checked against it, with a
ratchet. Contract v4.14: x_term.scheme and one warning in the SHOULD, nothing that passed fails.
Added¶
- A short code with a readable name (contract v4.14). A code like
O1orr16keeps its short, stable id and gets a name in a vocabulary: the code is thenotationof anx_term, the name is thepref, and the newx_term.scheme(a slug) says which vocabulary it belongs to, asskos:notation,skos:prefLabel, andskos:inSchemedo in SKOS. Without a scheme, the scheme is the graph. Both readers warnintegrity.term-notation-duplicatewhen two live terms share a notation in the same scheme (SKOS §6.5.3). SHOULD only: nothing that passed fails. Measured before the design: 71 opaque codes in the product's own graphs, with two real collisions (O1andV1each meant two things), and hundreds in an adopter's. kg_vocab.pyresolves codes and checks them in prose.lookupmatches anotationexactly, alone or asscheme:notation;termsgives each term its effectivescheme;export --to skoswritesskos:inSchemeand theskos:ConceptScheme. The newlintreads labels and narratives, finds codes by a regex (--pattern), and reportsvocab.notation-duplicateacross the corpus,vocab.code-unresolved, andvocab.code-ambiguous(a code in two schemes, none of them the graph's own).--external soc2='CC[0-9]+'declares an outside code system, and--baseline <file> [--update]is the gate's ratchet: the codes that exist do not block, a new one must be born with a name, and naming the first term of a colliding code does not charge for the old mentions (reclassified).
Changed¶
kg_query.py --textno longer gives a silent zero. It compares without case or accents (verificacaomatchesVerificação); when the whole text is a term of the vocabulary (itspref, a current synonym, or itsnotation), the term and every node with any of its forms as a whole word match too (expanded; a case-scoped or retired synonym does not expand, and a notation matches exactly); and an empty result carries ahintthat says when the zero comes from the other filters. Requested by an adopter that measured «PDF digitalizado», «reconhecimento óptico», and «verificacao» each giving 0 in silence.
[4.22.0] - 2026-10-23¶
The audit package by question: one row per questionnaire item, with the answer, the dated evidence, and the gaps it
computes, in CSV, XLSX, or JSON. Contract v4.13: x_questionnaire.role in the SHOULD, nothing that passed fails.
Added¶
- The audit package by question (
kg_audit.py). An auditor asks for the package by question; until now an adopter kept it by hand next to the graph. The tool exports one row per questionnaire item (the pair of name and number thatx_questionnairegives): the question, the answer (by the profile, and a node without the profile byDEPENDS_ONin either direction), the verdict, the required evidence, the evidence (SUPPORTSto the answer, orSUPPORTSorTRACES_TOto the question, a request, or a requirement, also from another graph) with its date, scope, and due date, the contradictions (REFUTES), the evidence requests, the owner, and the gaps it computes:no-answer(no live answer: arefutedorsupersededone does not count, nor does its evidence),contradicted,no-confirmed-evidence,evidence-unstamped,evidence-cadence-invalid,evidence-due, andverdict-open. What it cannot use (aroleoutside the enum, a question number that is not text, a malformed profile) comes out inskipped, in the package itself, never in silence. Formats: - CSV, with the questions or the evidence, and a leading apostrophe on a cell that would run as a formula;
- XLSX with the questions, the evidence, and what was skipped, written with the standard library only, with stable bytes;
- JSON,
kg-ssot.audit/1(spec/audit.schema.json).
--column brings an adopter's own key of the answer as a column, --as-of sets the day the evidence is due against,
and --fail-on-gap gives rc 1 for CI. A graph that does not read gives rc 1, because the package would be incomplete
in silence. Also on PyPI as kg-audit. Measured on an adopter's graphs before the cut.
- x_questionnaire.role (contract v4.13). item, the default; evidence_request, a request to a person for
evidence; or evidence_requirement, the evidence an item requires. Without it, only an id prefix told them apart.
SHOULD only: another value warns as form.enum.node.x_questionnaire.role, and nothing that passed fails.
[4.21.1] - 2026-10-23¶
kg_criterion.py check reports what it cannot check instead of skipping it. Contract v4.12, unchanged.
Fixed¶
kg_criterion.py checkno longer skips a criterion it cannot check. Asha256of digits alone without quotes reads as a number in YAML, and the version left the check with rc 0 and no finding. Now a version whosesha256is not text, or that lacks exactly one ofrefandtextin text, anx_measured_underthat is not a map with a list of citation maps, and a citation of another graph whosesha256is not text (even when that graph was not given) come out asintegrity.criterion-malformed, with rc 1. The reader already warned about the type (form.type); only the check passed in silence. Found by an adopter's test bench.
[4.21.0] - 2026-10-23¶
A yardstick fixed before the data becomes part of the contract, and an adopter's own x_ keys move into the
canonical profiles. Contract v4.12: two SHOULD profiles and five warnings, nothing that passed fails.
Added¶
- The pre-registered criterion (contract v4.12). Changing a yardstick after seeing the data becomes a dated
amendment instead of a rewrite, as in OSF Preregistration and the versioned amendments of a clinical trial registry.
Two SHOULD profiles:
x_criterion, one node per version of a criterion (id,version,registered_at,sha256, and eitherrefortext;amendswithreasonfor an amendment), andx_measured_under, on the result (measured_at, andcriteria, a list of{criterion, sha256}). Both readers, Python and JavaScript, warn about five things between nodes of the same graph, without computing a hash: integrity.criterion-unknown: the cited criterion, or theamends, is not a criterion node of the graph;integrity.criterion-amends-mismatch: the amendment points to a version of another criterion, or comes back to itself;integrity.criterion-sha-mismatch: the citedsha256is not the one of the version;integrity.measured-post-hoc: measured in the round that motivated the version, or before it was registered;integrity.measured-order-unknown: the dates cannot tell the order (the same day, or a day apart when one of them has no time zone).
ref and text together warn as form.exclusive.node.x_criterion, a new form kind. Requested by an adopter whose
own reviewer caught a yardstick changed after the data.
- kg_criterion.py prints the sha256 of a criterion (digest):
- for text: UTF-8, LF, without trailing spaces;
- for a whole file: the bytes, with CRLF turned into LF;
- for a list item in a JSON or YAML file: its JCS form (RFC 8785), selected by JSON Pointer or by id.
check verifies every version against its text or file, and every <path>#<id> citation of another graph
(kg-ssot.criterion/1, spec/criterion.schema.json); it fails on a finding or on a graph that does not read, and
belongs next to the gate in CI, because the reader computes no hash. It is also the kg-criterion command of the PyPI package.
- The suite goes from 390 to 438 cases (latest/form/profiles/ and the new latest/integrity/criterion/).
- kg_migrate.py nest moves an adopter's own x_ keys into a canonical node profile (x_mirror, x_doc_cycle,
x_questionnaire), as a map says (spec/kg-migrate-nest.schema.json): each field from a constant, from an x_ key
that leaves the node, or from a key that stays, through an optional regex and value table. A key moved through a
regex or a prefix needs raw: true, which keeps its original value in x_raw, so no text is lost. A value with no
code, a missing required source, a profile that fails its schema, an alias, or a node in flow style goes to the
reviewer in the plan, never guessed. Requested by an adopter; on a copy of their two graphs (108 nodes, 168 keys),
106 profiles built and 2 left for review.
Fixed¶
- A missing required field counts once. In the reference reader, an object with N required fields missing counted
each of them N times: a
metawith neitherschema_versionnoridshowedform.required.meta.id ×2, and a node with three missing fields,×3for each. The JavaScript reader already counted right. The verdict, the set of codes, and the suite do not change; the counts in the summary, in--where, in the JSON verdict, and in the gate's per-occurrence baseline go down to the real number. Found by an adopter's test bench.
[4.20.0] - 2026-10-23¶
The store stops re-judging graphs that did not change, the vendor stamp becomes a public rule in every tool, and
kg_diff names the working tree. Contract v4.11, unchanged.
Added¶
kg_diff.pycompares with the working tree by name.--to WORKTREEis the same as leaving--toout, andWORKTREE:PATHreads the working-tree file, soHEAD:x.kg.yaml WORKTREE:x.kg.yamlreviews an edit before the commit (requested by an adopter;--to WORKTREEwas a bad revision).WORKTREE:PATHstarts from the repository root, likeREV:PATH, and a path that leaves the repository is broken input. In the JSON,afteris nowWORKTREEinstead of the Portuguese label the working tree had. A branch or tag namedWORKTREEis now reached asrefs/heads/WORKTREEorrefs/tags/WORKTREE.
Changed¶
- The store no longer re-judges a graph that points to the written one and has not changed.
kg_storekeeps the verdict of each judged graph in memory (kg_validate.JudgeCache), keyed by the text, both schemas, the language, the limit, and whether each external end resolves. With 50 pointing graphs of 820 KB, the second write of the target went from 41 s to 3.3 s, and writing the same text again from 44 s to 0.35 s; the first write in a process costs the same. A broken external end still fails, and a new contract judges everything again.kg_validate.judge()takes the cache as an optionalcache=argument. - A folder with the
.kg-ssot-versionstamp is the vendor, in every tool that reads a repository. Since 4.19.1 onlykg_gate.pyleft it out of the corpus;kg_query.py,kg_vocab.py,kg_migrate.py, andkg_diff.py --fromexcluded onlyvendor/*at the root, so a vendor elsewhere brought its example graphs in. A stamp at the root of what is read does not count, in every tool, the gate included: reading a folder that is the vendor reads its graphs. The rule is now public (kg_validate.VENDOR_STAMP,vendor_dirs,in_vendor,find_vendor_dirs) and documented in ADOPTING, for an adopter's own tools to follow (requested by an adopter whose trace resolver counted the vendor's example graph).
Fixed¶
- A gate test depended on the date. Each accepted regression lives in
accepted/<hash>.json, and the hash includes the date, so the reading order changed from one day to the next. The test now checks that the regressions accumulate, not their order.
4.19.1 - 2026-10-22¶
The gate leaves the KG-SSOT vendor out of the adopter's corpus. Contract v4.11, unchanged.
Fixed¶
kg_gate.pycounted the vendor's example graph as an adopter graph. The 4.19.0 vendor bringsspec/vocab-examples/cmed-dictionary.kg.yamloutsidefixtures/, and a--updatewould write it into the baseline (found by an adopter before any--update). The gate now leaves out, by default, every file in a folder with the.kg-ssot-versionstamp, wherever the vendor lives;--no-default-excludesbrings it back.
4.19.0 - 2026-10-22¶
A domain vocabulary you can verify, synonym by synonym, and a shape for the x_ keys that adopters repeat. Contract
v4.11.
Added¶
- The
x_termprofile (contract v4.11, SHOULD): a term is a node with the preferred label (pref) and its synonyms, each withkind(altorhidden, as in SKOS),source,locator, theversionof the document,verified_at,reverify_every,scope(domain, orcasewith the case named, and an optional jurisdiction) andretired(truefor the old name). A retired term is the node withstatus: supersededand aSUPERSEDESedge from the term that replaces it. A real example from the open data dictionary of the Brazilian drug price regulator ships inspec/vocab-examples/cmed-dictionary.kg.yaml. kg_vocab.py:termslists the terms and what is due,lookupresolves a text to its term (without case, accents or repeated spaces; a case-scoped synonym only with--case), andexportwrites SKOS-XL with the ISO 25964 names in Turtle (--to skos) or one item per term for Apache Ossie, withai_context.synonymsand the provenance of each synonym incustom_extensions(--to ossie). JSON output:kg-ssot.vocab/1(spec/vocab.schema.json). It also ships as thekg-vocabcommand in the PyPI package.- The
kg_termstool in the MCP server: the same lookup over the store, read-only, with the synonyms' provenance and the term that replaces a superseded one. - The other canonical
x_profiles (SHOULD, judged only when the key is present):x_mirror(a record copied from another system, withsynced_atandresync_every),x_doc_cycle(the life cycle of an external document),x_questionnaire(the link to the questions of an external questionnaire),x_severity(criticaltoinfo), andx_confidenceandx_provisionalon the edge. kg_migrate.py keys: renames each key the contract does not know tox_<key>, by text and with invariance checks; the known keys come from the MUST schema. Measured on a copy of the core's 15 graphs that failed the MUST on a foreign key: 14 pass afterwards.meta.titleandmeta.slugbecome known keys (MUST, a widening: nothing that passed fails).
Changed¶
- The due report (
kg_validate.py --due) also lists the synonyms ofx_term,x_mirrorandx_doc_cycle, withsubjectanddetail; a node's entry andschema_versionstay the same. - A list item inside a node keeps its path in the code (
form.type.node.x_term.labels.item), in both readers. - Upgrade note: if you already use one of the profile names with another shape, the gate shows a new SHOULD code.
Measured before the tag on the three adopters: one does (
x_severitywith values in Portuguese, already converted). Convert the values or accept it once with--accept-regression. The suite goes to 390 cases.
4.18.0 - 2026-10-22¶
KG-SSOT now speaks English first. The tools, the documentation and the listings are in English, and the full Brazilian Portuguese translation ships next to them. Contract v4.10, unchanged.
Changed¶
- The tools print in English by default.
kg_validate,kg_gate,kg_vendor,kg_store,kg_migrate,kg_query,kg_diff,kg_conformance,kg_mcp,kg_prov_export,kg_release_notesand the npm CLI pick the language the gettext way:KG_SSOT_LANG, thenLANGUAGE,LC_ALL,LC_MESSAGESandLANG. Apt_BRlocale (orKG_SSOT_LANG=pt-BR) gives Brazilian Portuguese;C,POSIXand anything without a catalog give English. - Upgrade note: machine-readable data does not change with the language: JSON keys and structure, error codes,
exit codes, the verdict
place(nó X,aresta #n) andpath, the--wherelocation and thekg_migrateTSV. Human text does: the terminal output and, inside JSON, themessage,hintanderrorstrings, which were always Portuguese before and are now English unless the locale ispt_BR(a CI runner withLANG=C.UTF-8gets English). Compare by code, never by message. If your CI greps the human output (PASSA,✗ PIOROU,ENTRADA QUEBRADA), either setKG_SSOT_LANG=pt-BRor match both languages (PASS,✗ WORSE,BROKEN INPUT); better yet, decide on the exit code. - The MCP server speaks English in every locale: tool descriptions, instructions, errors and the text inside results
are protocol (and prompt for the model), so they no longer depend on the server's environment. An external gate
(
--gate-cmd) run by the server getsKG_SSOT_LANG=enfor the same reason. The Claude Desktop listing is translated by the extension's localization instead. - As in gettext,
LANGUAGEonly applies when the effective locale is notC:LC_ALL=Cforces English even on a desktop withLANGUAGE=pt_BR:en. - The documentation is written in English and translated to Brazilian Portuguese:
README.mdandREADME.pt-BR.md,ADOPTING.mdandADOPTING.pt-BR.md,spec/conformance/README.mdand its.pt-BR.md, and the manual indocs/manual/with the translation indocs/manual/pt-BR/. The manual chapters have English file names now (04-contract.md,05-tools.md, ...); the Portuguese chapters keep their old names underpt-BR/. - The listings are in English: the PyPI and npm READMEs, the MCP Registry description and the Claude Desktop extension
(
.mcpb), which carries a pt-BR localization (mcpb-resources/pt-BR.json) for clients set to Portuguese. llms.txtandllms-full.txtare in English and read the English documentation.
Added¶
tools/kg_i18n.pyand the cataloglocale/pt_BR/LC_MESSAGES/kg-ssot.po(gettext format, 446 messages). They ship in the release, in thegateprofile and in the Python package;kg_vendor.py updatebrings them with the tag. Without them, the vendored tools cannot start.kg_migrate plan --jsonreports broken input as JSON too ({"error": ..., "reason": "broken_input"}, exit 2). A usage error from the argument parser also exits 2 but writes nothing to stdout, so a script can tell them apart without matching text.kg_store.InsideGitWorktree, aStoreErrorsubclass for a disk or versioned root inside a git worktree, so code that picks the backend decides by type instead of by message.
4.17.2 - 2026-10-22¶
The MCP server tells clients which tools only read. Contract v4.10, unchanged.
Added¶
- Every tool in
kg_mcpnow announces MCP tool annotations:kg_validate,kg_get,kg_list,kg_query,kg_historyandkg_schema_helpare read-only and idempotent;kg_writeandkg_deleteare destructive. None reaches outside the local store (openWorldHint: false). A client can skip confirmation for reads and ask before a write or delete. They are hints; the store gate still decides what is written.
4.17.1 - 2026-10-22¶
Field fixes to the query and diff tools, found by adopters on their first real use of 4.17.0. Contract v4.10, unchanged.
Changed¶
kg_query --rootandkg_diff --fromnow leave vendored code and test fixtures out by default (vendor/*,fixtures/*,*/fixtures/*,docs/materials/*). In one adopter,--rootread 411 graphs for 29 real ones.--no-default-excludesreads everything; a path given explicitly is never excluded.kg_queryJSON output now reports the corpusroot.
Added¶
kg_diffmarks a field renamed with the same value as a rename (renamed_from/renamed_to). Both entries stay in the JSON, so thekg-ssot.diff/1format is unchanged; only a non-trivial value that is unique on both sides is paired.- The query and diff tests run in CI.
4.17.0 - 2026-10-21¶
Query, projection and diff for a whole corpus of graphs. Contract v4.10, unchanged.
Added¶
kg_query.py: select nodes across many graphs (id, prefix, type, status, plane, layer, label words, the open queue, nodes not verified since a date, the neighborhood of a node) and project every edge with the label of the other end, local or external, with cursor pagination. Output formatkg-ssot.query/1(spec/query.schema.json).kg_querytool in the MCP server, scoped to the caller's store area, with a byte ceiling per page.kg_diff.py: diff two graphs, or everything that changed between two git revisions, aligned by node id and field by field, read from git without a checkout.--ignoredrops a field a generator re-stamps. Output formatkg-ssot.diff/1(spec/diff.schema.json).- PyPI commands
kg-queryandkg-diff; the MCPB lists 8 tools. - Two conformance cases (356 in
latest/).
4.16.1 - 2026-10-21¶
Contract v4.10: the reference reader and the JavaScript reader agree on three rare YAML cases. No reference verdict changes, and none of the 1,633 real graphs measured in four repositories changes its codes.
Fixed¶
- Error order follows the document: a duplicate key that comes before a duplicate anchor or an undefined alias gives
parse.duplicate-key, as in the JavaScript reader. - JavaScript reader: an empty line after an escaped line break inside double quotes is a line feed, as the YAML spec says; a leading byte-order mark does not count as a column.
- Five conformance cases (354).
4.16.0 - 2026-10-21¶
The frictionless gate. Contract v4.9, unchanged.
Changed¶
- The gate baseline is a folder,
.kg-ssot/gate/, with one record per graph keyed bymeta.id: branches that touch different graphs touch different files, so the baseline merges without conflict. - A contract identity change with no verdict change passes with a notice (rc 0);
--strict-identityrestores the old behavior. The vendor checks the schemas against their stamp.