@unified-product-graph/core 0.9.21 → 0.9.23
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.
- package/CHANGELOG.md +25 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.9.23] - 2026-06-11
|
|
11
|
+
|
|
12
|
+
**Closes the duplicate-delivery hole that 0.9.22 only half-fixed, and stops a cross-product write from duplicating on retry.** 0.9.22 deduped a re-delivered mutating call by its JSON-RPC request id, but the real re-delivery carries a *fresh* request id (a client-level re-issue, invisible to a request-id ledger): each create handler re-executed and minted a second copy with new ids. Because the replay lands on the *next* mutating call and never on a read, a "write then recount" check passed falsely — which is why 0.9.22 looked fixed.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Content-level idempotency for mutating MCP calls (`@unified-product-graph/mcp-server`).** The local server adds a second dedup layer keyed on the call's payload (tool + active product + normalised arguments), independent of the request id. A re-delivered mutating call whose payload matches a recent one replays the original result instead of writing a duplicate. The window is bounded by COUNT (the most recent 64 distinct mutating payloads), not wall-clock: the replay lands on the next mutating call, so a sliding count window catches it without timing guesswork. Only successful results are recorded, so a transient error stays retryable. Pass `allow_duplicate: true` on a mutating call to opt out for a deliberate identical re-create. The store was never the source: it dedupes by id and never mints ids, so a fresh-id duplicate can only mean the create path re-ran.
|
|
16
|
+
- **Cross-product edge writes are idempotent and no longer self-corrupt on a tmp-rename race (`@unified-product-graph/sdk`).** `create_cross_product_edge` could throw `ENOENT: rename .../portfolio.upg.tmp -> .../portfolio.upg` even though the edge had already persisted (a debounced save racing an explicit flush on a SHARED tmp path), and a naive retry then appended a second identical edge. The portfolio writer now uses a per-write unique tmp path (cleaned up on any failure), and `addCrossEdge` collapses an identical `(source, target, type)` re-create onto the existing edge, so a retry is a safe no-op.
|
|
17
|
+
|
|
18
|
+
No entity, domain, region, edge, or tool-count change (entities 315, edges 980, local tools 123). The cloud server (Postgres-backed, separate write path, no file watcher) is unaffected.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [0.9.22] - 2026-06-11
|
|
23
|
+
|
|
24
|
+
**Stops silent data duplication from re-delivered MCP writes, and stops dedupe from destroying structure.** Three fixes for a HIGH-severity report where a mutating call was applied twice (the duplicate landing a few calls later, past any immediate recount).
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Idempotent MCP writes (primary).** A re-delivered mutating tool call (the same JSON-RPC request id, e.g. a transport-level resend) re-ran the handler, minted fresh ids, and wrote a second copy. The local MCP server now memoises the result per request id and replays it, so a re-delivery (in flight or already finished) is a no-op that returns the original response instead of duplicating the write.
|
|
28
|
+
- **Watcher self-write guard (`@unified-product-graph/sdk`).** The file watcher's 150ms self-write flag expired before chokidar's 200ms `awaitWriteFinish` fired, so every server write looked like an external change and triggered a needless reload/merge. The guard is now a content-hash check: if the changed file equals what we last wrote, it is not external. Timing-independent.
|
|
29
|
+
- **`deduplicate_nodes` no longer drops structural edges.** The merge used a constant sort comparator (not a real `created_at` sort) and redirected inbound edges best-effort with a silent catch, then cascade-removed the originals, so a kept node could lose its inbound containment edge and vanish from the tree. Rewritten: a real keeper sort, a group-wide redirect map that re-homes every edge onto the keeper before removing any node (identical edges union, not multiply), and a structural-parent assertion that surfaces any inbound parent edge not preserved (`structural_warnings`).
|
|
30
|
+
|
|
31
|
+
No entity, domain, region, edge, or tool-count change (entities 315, edges 980, local tools 123). The cloud server (Postgres-backed, no file watcher, separate SQL dedupe) is unaffected.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
10
35
|
## [0.9.21] - 2026-06-11
|
|
11
36
|
|
|
12
37
|
**The `get_tree` fallback message no longer contradicts the tree it renders.**
|
package/dist/index.cjs
CHANGED
|
@@ -28431,7 +28431,7 @@ function isCanonical(text) {
|
|
|
28431
28431
|
}
|
|
28432
28432
|
|
|
28433
28433
|
// src/index.ts
|
|
28434
|
-
var UPG_VERSION = "0.9.
|
|
28434
|
+
var UPG_VERSION = "0.9.23";
|
|
28435
28435
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
28436
28436
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
28437
28437
|
var UPG_TYPES = getTypes();
|