agentleFS
cognitedatacognitedata/builder-skills/skills/dm-graph-traversal/SKILL.md

dm-graph-traversal

Skill6 starsChanged 9 days agoApache-2.0View on GitHub

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).

The file

Show its rules and text

We split this skill into the rules it gives an agent

26 rules. About 0 are common, 12 are distinctive, and 14 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈12

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 14

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use instances/query when any is true:

    line 25
  2. Very specific:

    Use instances/list when all are true:

    line 32
  3. Distinctive:

    When user intent is discovery/ranking (for example free-text name matching), prefer:

    line 46
  4. Distinctive:

    Search APIs are better for ranking and fuzzy discovery.

    line 53
  5. Very specific:

    Default to Node.js/TypeScript workflows for parity checks, examples, and validator tooling.

    line 61
  6. Very specific:

    Use code/validate-query-parity.cjs for payload validation in all normal cases.

    line 62
  7. Distinctive:

    Do not introduce Python parity scripts by default in TypeScript repositories.

    line 63
  8. Very specific:

    Use Python-based validation only if the user explicitly requests Python or no viable Node.js path exists.

    line 64
  9. Very specific:

    If a step uses select.<step>.sources, each source entry includes explicit properties.

    line 90
  10. Distinctive:

    When combining multi-step outputs:

    line 113
  11. Very specific:

    Start with strict server-side constraints (space, exact filters, scoped predicates).

    line 120
  12. Distinctive:

    Only broaden filters when needed, and keep fallback stages explicit and ordered.

    line 121
  13. Distinctive:

    When a use case asks for graph relationships plus the latest numeric value, use a two-phase read:

    line 127
  14. Very specific:

    Use instances.query to traverse and collect the relevant time-series node IDs.

    line 129
  15. Very specific:

    Use datapoints.retrieveLatest on those IDs in batches.

    line 130
  16. Very specific:

    Use ignoreUnknownIds: true to tolerate stale references.

    line 144
  17. Distinctive:

    Keep aggregation logic deterministic when many series map to one entity.

    line 145
  18. Distinctive:

    Use this pattern when relationship edges carry business data (for example risk, confidence, allocation, ownership, status, weight).

    line 158
  19. Distinctive:

    Start from the primary node set (the page/entity context).

    line 162
  20. Very specific:

    Read edge properties explicitly from select.<edgeStep>.sources.

    line 164
  21. Distinctive:

    Keep aggregation deterministic and test it directly.

    line 223
  22. Distinctive:

    Add deterministic join/dedupe logic.

    line 253
  23. Very specific:

    Add bounded retries for transient failures (408/425/429/5xx).

    line 255
  24. Distinctive:

    Use the TypeScript SDK to validate query shape and traversal semantics directly in frontend/backend JavaScript tooling:

    line 285
  25. Very specific:

    Add --schema-hints <path-to-schema-hints.json> when running schema-aware relation checks.

    line 328
  26. Very specific:

    Prefer server-side filtering and explicit property lists.

    line 332

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

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:

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 / SymptomLikely CauseFix
Unexpected field - nodes.limitlimit nested under nodesmove to with.<step>.limit
Unexpected field - edges.limitlimit nested under edgesmove to with.<step>.limit
properties must not be nullsources without propertiesadd explicit properties: [...]
Unexpectedly slow latest-value endpointtrying to read latest values via traversal-only flowsplit into instances.query + batched retrieveLatest
Query path intermittently fails with 429/5xx/timeoutmissing transient failure handlingadd bounded retries with exponential backoff + jitter
Edge properties missing in outputtraversed edges but did not project edge propertiesadd explicit select.<edgeStep>.sources[*].properties for edge view
Aggregates inflated after edge traversalmultiple edges per endpoint without dedupe policydedupe by stable key and apply explicit tie-break rule
Traversal step returns empty, no errornon-versioned traversal refuse View/version in property refs
Cannot traverse lists of direct relations inwards.inwards traversal through list direct relationtraverse from owning node with outwards, or remodel as edge
Traversal step empty despite datamissing hasData or wrong direction/identifieradd hasData; verify direction + through.identifier
First page works, later missingcursor loop not step-scopediterate nextCursor.<step>
Inflated totalsdedupe policy missingdedupe 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:

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:

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

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.