Skip to main content

dm-graph-traversal

CDF Data Modeling query-vs-list expert skill. Use for graph-native reads with instances.query, traversal payload design, failure debugging, pagination/dedupe semantics, and regression-proof tests (including Node.js/TypeScript parity checks).

Zur Installation springen

Quellinformationen

Repository
cognitedata/builder-skills
Letzte Quellaktivität
16. Juni 2026 um 10:54
Erkannte Sprache von SKILL.md
Englisch
Sterne
6
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
18 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
dm-graph-traversal
description
CDF Data Modeling query-vs-list expert skill. Use for graph-native reads with instances.query, traversal payload design, failure debugging, pagination/dedupe semantics, and regression-proof tests (including Node.js/TypeScript parity checks).
allowed-tools
Read, Glob, Grep, Edit, Write
# DM Graph Traversal ## Outcome Ship correct, maintainable, graph-native CDF Data Model reads. This skill turns query/list ambiguity into a deterministic workflow: 1. Decide whether the read is graph-native. 2. Build safe `instances/query` payloads. 3. Validate traversal semantics and cursor behavior. 4. Encode merge/dedupe policy explicitly. 5. Lock behavior with payload-shape tests. --- ## Decision Tree: `instances/query` vs `instances/list` Use `instances/query` when **any** is true: - You start from one node type and need related nodes/edges. - You need reverse relation traversal (`direction`, `through`, `from`). - You would otherwise do multiple list calls and stitch results client-side. - You need step-specific pagination and relationship-aware filtering. Use `instances/list` when **all** are true: - Single node/edge type lookup. - No traversal intent. - Flat filtering is sufficient. Heuristic: - "If this read needs graph context, it is a query." --- ## Search-First Entry Pattern When user intent is discovery/ranking (for example free-text name matching), prefer: 1. `instances.search` to find/rank candidate anchors. 2. `instances.query` to hydrate graph-related details for those anchors. Why: - Search APIs are better for ranking and fuzzy discovery. - Query APIs are better for explicit graph traversal and constrained joins. - This avoids broad traversal scans when the first step is actually discovery. --- ## Operating Mode (Hard Rule) - Default to Node.js/TypeScript workflows for parity checks, examples, and validator tooling. - Use `code/validate-query-parity.cjs` for payload validation in all normal cases. - Do not introduce Python parity scripts by default in TypeScript repositories. - Use Python-based validation only if the user explicitly requests Python or no viable Node.js path exists. --- ## Related Skill Boundary This skill is the source of truth for graph query correctness (traversal shape, refs, relation direction, and payload guardrails). For runtime limits and throughput controls, see `dm-limits-and-best-practices` (concurrency budgets, semaphore/task-runner usage, retry policy details, and write batching limits). Practical rule: - Build the correct graph query with this skill first. - Then harden runtime behavior at scale with `dm-limits-and-best-practices`. --- ## Canonical Payload Guardrails ### 1) Step-level limit placement - Valid: `with.<step>.limit` - Invalid: `with.<step>.nodes.limit`, `with.<step>.edges.limit` ### 2) `select.sources` requires `properties` If a step uses `select.<step>.sources`, each source entry includes explicit `properties`. ### 3) Start-step constraints For node start steps, include: - space filter (`['node','space']`) - `hasData` for expected view where relevant ### 4) Versioned traversal refs In traversal-step filters, use: - `[space, 'ViewExternalId/version', property]` ### 5) Step cursor loops Paginate per step with: - `nextCursor.<step>` -> `cursors: { <step>: ... }` ### 6) Deterministic merge semantics When combining multi-step outputs: - dedupe by stable IDs (or business keys) - define tie-breaks (`explicit > fallback`, `max`, `latest`, etc.) ### 7) Strict-to-broad fallback Start with strict server-side constraints (`space`, exact filters, scoped predicates). Only broaden filters when needed, and keep fallback stages explicit and ordered. --- ## Two-Phase Latest Datapoint Rule When a use case asks for graph relationships plus the latest numeric value, use a two-phase read: 1. Use `instances.query` to traverse and collect the relevant time-series node IDs. 2. Use `datapoints.retrieveLatest` on those IDs in batches. 3. Merge latest values back by stable business key. Why: - `instances.query` is best for relationship traversal and filtering. - `retrieveLatest` is the efficient API for last-value reads. - Keeping these responsibilities separate avoids over-fetching and expensive fan-out logic. Efficiency guardrails: - Query only IDs/properties needed for downstream latest reads. - Dedupe node IDs before latest retrieval. - Batch latest calls (for example, max 100 IDs per request). - Use `ignoreUnknownIds: true` to tolerate stale references. - Keep aggregation logic deterministic when many series map to one entity. Reference shape: ```ts const nodeIds = queryResult.items.ptts.map((n) => ({ instanceId: { space: n.space, externalId: n.externalId } })); const latest = await client.datapoints.retrieveLatest(nodeIds, { ignoreUnknownIds: true }); ``` --- ## Edge-With-Properties Query Pattern Use this pattern when relationship edges carry business data (for example risk, confidence, allocation, ownership, status, weight). Mental model: 1. Start from the primary node set (the page/entity context). 2. Traverse to relationship edges as first-class records. 3. Read edge properties explicitly from `select.<edgeStep>.sources`. 4. Join edge rows to endpoint nodes for labels/details. 5. Aggregate with deterministic dedupe/tie-break rules. Why this matters: - Edge properties are business facts; treating edges as transport-only loses critical data. - Querying nodes first and stitching ad-hoc often creates N+1 calls and double counting. - Explicit edge-step design keeps lineage and semantics clear. Generic example: ```ts const result = await client.instances.query({ with: { start: { nodes: { filter: { and: [ { equals: { property: ["node", "space"], value: "my_space" } }, { hasData: [{ type: "view", space: "my_space", externalId: "PrimaryEntity", version: "v1" }] } ] } }, limit: 1000 }, links: { edges: { from: "start", direction: "outwards", filter: { equals: { property: ["edge", "type"], value: { space: "my_space", externalId: "PrimaryToSecondaryLink" } } } }, limit: 1000 }, secondary: { nodes: { from: "links", direction: "outwards" }, limit: 1000 } }, select: { start: { sources: [{ source: { type: "view", space: "my_space", externalId: "PrimaryEntity", version: "v1" }, properties: ["name"] }] }, links: { sources: [{ source: { type: "view", space: "my_space", externalId: "PrimaryToSecondaryLink", version: "v1" }, properties: ["weight", "status"] }] }, secondary: { sources: [{ source: { type: "view", space: "my_space", externalId: "SecondaryEntity", version: "v1" }, properties: ["name"] }] } } }); ``` Edge aggregation guidance: - Dedupe by endpoint business key (or edge ID when edge uniqueness matters). - Define tie-break policy up front (`max(weight)`, latest timestamp, explicit-over-derived, etc.). - Keep aggregation deterministic and test it directly. --- ## Failure Signature Playbook | Error / Symptom | Likely Cause | Fix | |---|---|---| | `Unexpected field - nodes.limit` | limit nested under `nodes` | move to `with.<step>.limit` | | `Unexpected field - edges.limit` | limit nested under `edges` | move to `with.<step>.limit` | | `properties must not be null` | `sources` without `properties` | add explicit `properties: [...]` | | Unexpectedly slow latest-value endpoint | trying to read latest values via traversal-only flow | split into `instances.query` + batched `retrieveLatest` | | Query path intermittently fails with 429/5xx/timeout | missing transient failure handling | add bounded retries with exponential backoff + jitter | | Edge properties missing in output | traversed edges but did not project edge properties | add explicit `select.<edgeStep>.sources[*].properties` for edge view | | Aggregates inflated after edge traversal | multiple edges per endpoint without dedupe policy | dedupe by stable key and apply explicit tie-break rule | | Traversal step returns empty, no error | non-versioned traversal ref | use `View/version` in property refs | | `Cannot traverse lists of direct relations inwards.` | inwards traversal through list direct relation | traverse from owning node with `outwards`, or remodel as edge | | Traversal step empty despite data | missing `hasData` or wrong direction/identifier | add `hasData`; verify `direction` + `through.identifier` | | First page works, later missing | cursor loop not step-scoped | iterate `nextCursor.<step>` | | Inflated totals | dedupe policy missing | dedupe and apply explicit tie-break | --- ## Implementation Workflow 1. Model graph intent (start entity, edge, target entity). 2. Name steps semantically (`initiatives`, `featureLinks`, `commitments`, `customerArr`). 3. Apply payload guardrails (limit placement, properties, refs, `hasData`). 4. Implement step cursor loop(s). 5. Map only required fields. 6. Add deterministic join/dedupe logic. 7. Add payload-shape tests. 8. Add bounded retries for transient failures (408/425/429/5xx). 9. (Optional) Cross-check in TypeScript SDK. --- ## Testing Requirements (Required For Merge) For every critical helper, tests must assert payload shape (not only mapped output): - `with.<step>.limit` exists - nested `nodes.limit` / `edges.limit` absent - each `select.<step>.sources[*].properties` present - traversal refs are versioned where needed - `hasData` present on constrained start steps - unintended fallback to `instances/list` is absent (if query-only design) - retry behavior exists for transient failures in production paths Example assertion style: ```ts const call = (client.instances.query as ReturnType<typeof vi.fn>).mock.calls[0]?.[0]; expect(call?.with?.initiatives?.limit).toBe(1000); expect(call?.with?.initiatives?.nodes?.limit).toBeUndefined(); expect(call?.select?.initiatives?.sources?.[0]?.properties).toContain('title'); ``` --- ## TypeScript SDK Parity Check (Recommended) Use the TypeScript SDK to validate query shape and traversal semantics directly in frontend/backend JavaScript tooling: ```ts const query = { with: { cycles: { nodes: { filter: { and: [ { equals: { property: ["node", "space"], value: "product_portfolio" } }, { hasData: [{ type: "view", space: "product_portfolio", externalId: "PortfolioReviewCycle", version: "v1" }] } ] } }, limit: 200 } }, select: { cycles: { sources: [ { source: { type: "view", space: "product_portfolio", externalId: "PortfolioReviewCycle", version: "v1" }, properties: ["key", "displayName", "periodStart", "periodEnd", "status"] } ] } } }; await client.dataModeling.instances.query(query); ``` Parity checks: - Step names and cursor keys match TS. - Limit placement and `properties` shape are valid. - Traversal filters behave as expected. - Validate any project payload with: - `node skills/dm-graph-traversal/code/validate-query-parity.cjs --query <path-to-query.json> --check all --expect pass` - Validate expected failures (negative tests) with: - `node skills/dm-graph-traversal/code/validate-query-parity.cjs --query <path-to-query.json> --check all --expect fail` - Add `--schema-hints <path-to-schema-hints.json>` when running schema-aware relation checks. - Check modes: - `sources-properties`, `limit-placement`, `start-step-hasdata`, `versioned-traversal-refs`, `cursor-shape`, `inwards-list-direct-relations`, `all` - For latest-value scenarios, apply the two-phase rule (`instances.query` IDs -> batched `retrieveLatest`) instead of forcing latest reads into traversal payloads. - Prefer server-side filtering and explicit property lists; avoid wildcard projection (`"*"`). --- ## Anti-patterns To Avoid 1. Broad cross-space retrieval without explicit scope when not required. 2. Wildcard property projection (`properties: ["*"]`) in production query paths. 3. Client-side filtering when equivalent server-side filters exist. 4. Raw HTTP query payload posting when SDK methods provide equivalent behavior and retries. 5. N+1 relation fetch loops when one traversal query can hydrate the same graph. --- ## Done Criteria - Correct query/list decision documented in PR/code comments. - Query payload follows all guardrails. - Merge/dedupe semantics explicit and tested. - Regression tests cover known failure signatures. - (High-risk changes) TypeScript SDK parity sanity check completed. --- ## References - `references/query-vs-list.md` - `code/validate-query-parity.cjs`
Auf GitHub ansehen