Skip to main content

add-node-type

Define a new node schema under cartography/models/MODULE_NAME/, including required properties, sub-resource relationships, extra labels, conditional labels, scoped cleanup, and one-to-many transforms. Use when the user asks to add a node type, model a new resource, configure extra Neo4j labels (Identity, Asset, UserAccount, Tenant), or wire scoped vs global cleanup.

معلومات المصدر

المستودع
cartography-cncf/cartography
آخر نشاط في المصدر
٢٨ يوليو ٢٠٢٦ في ٢١:٠٠
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٤٬١٤٢
التفرعات
٥٨٧

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
2 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
add-node-type
description
Define a new node schema under cartography/models/MODULE_NAME/, including required properties, sub-resource relationships, extra labels, conditional labels, scoped cleanup, and one-to-many transforms. Use when the user asks to add a node type, model a new resource, configure extra Neo4j labels (Identity, Asset, UserAccount, Tenant), or wire scoped vs global cleanup.
# add-node-type Add a new `CartographyNodeSchema` (and its `CartographyNodeProperties`) to a Cartography module. This skill assumes the surrounding module already exists; for end-to-end module creation, see the `create-module` skill. ## Critical rules 1. **`@dataclass(frozen=True)` everywhere**, with type annotations on every field (`x: PropertyRef = PropertyRef("x")`). A missing annotation triggers `PropertyRef validation failed`. 2. **Only the standard schema fields are recognised.** Custom fields on `CartographyNodeSchema` / `CartographyRelSchema` subclasses are silently ignored — see "Standard fields" below. 3. **`sub_resource_relationship` always points to a tenant-like node** (`AWSAccount`, `AzureSubscription`, `GCPProject`, `GitHubOrganization`, your `<Service>Tenant`). 4. **`scoped_cleanup` defaults to `True`.** Override to `False` only for genuinely global data (CVE feeds, public threat intel, public DNS). ## Instructions ### Step 1 — Required properties Every node type needs at least: ```python @dataclass(frozen=True) class YourNodeProperties(CartographyNodeProperties): id: PropertyRef = PropertyRef("id") # REQUIRED lastupdated: PropertyRef = PropertyRef("lastupdated", set_in_kwargs=True) # REQUIRED # business properties... ``` `PropertyRef` parameters: - First arg: per-record dict key, **or** kwarg name when `set_in_kwargs=True`. - `extra_index=True` — add a database index for query performance. - `set_in_kwargs=True` — value comes from `load(..., KWARG=...)`, not the per-record dict. - `one_to_many=True` — value is a list; expand to many edges (see "One-to-many" below). ### Step 2 — Schema with sub-resource relationship ```python @dataclass(frozen=True) class YourServiceUserSchema(CartographyNodeSchema): label: str = "YourServiceUser" properties: YourServiceUserNodeProperties = YourServiceUserNodeProperties() sub_resource_relationship: YourServiceTenantToUserRel = YourServiceTenantToUserRel() other_relationships: OtherRelationships = OtherRelationships([ YourServiceUserToHumanRel(), ]) ``` ### Step 3 — Pick the right standard fields `CartographyNodeSchema` standard fields: | Field | Type | Required | Notes | | --------------------------- | --------------------------------- | :------: | ----------------------------------------------------------- | | `label` | `str` | Yes | Neo4j node label | | `properties` | `CartographyNodeProperties` subclass | Yes | | | `sub_resource_relationship` | `CartographyRelSchema` subclass | Yes | Tenant-like target. `None` only if `scoped_cleanup=False`. | | `other_relationships` | `OtherRelationships` | No | Additional business relationships | | `extra_node_labels` | `ExtraNodeLabels` | No | Additional Neo4j labels (e.g. `Identity`, `UserAccount`) | | `scoped_cleanup` | `bool` | No | Defaults to `True`. Almost never override. | `CartographyRelSchema` standard fields: `target_node_label`, `target_node_matcher`, `direction`, `rel_label`, `properties`. Custom fields **do nothing** — handle conditional behaviour in `transform()` by setting fields to `None` or filtering before `load()`. ### Step 4 — Extra labels Add additional Neo4j labels by importing reusable uppercase constants and wrapping them in `ExtraNodeLabels`: ```python from cartography.models.core.nodes import ExtraNodeLabels from cartography.models.ontology.labels import USER_ACCOUNT extra_node_labels: ExtraNodeLabels = ExtraNodeLabels([USER_ACCOUNT]) ``` Produces `(:YourServiceUser:UserAccount)`. `ExtraNodeLabel` is one immutable value type, not a base class for one subclass per label. Label definitions are exported uppercase constants; their `description` fields are metadata for introspection and generated documentation. Do not rely on class docstrings. `ExtraNodeLabels` stores the supplied labels as an immutable tuple. For ontology-driven labels (`UserAccount`, `Tenant`, `Database`, ...) see the `enrich-ontology` skill. ### Step 5 — Decide on `scoped_cleanup` Default behaviour `scoped_cleanup=True` is correct for almost everything: user accounts, infrastructure resources, application assets — anything scoped to a tenant. Override to `False` only for genuinely global data with no tenant: ```python @dataclass(frozen=True) class VulnerabilitySchema(CartographyNodeSchema): label: str = "Vulnerability" properties: VulnerabilityNodeProperties = VulnerabilityNodeProperties() sub_resource_relationship: None = None scoped_cleanup: bool = False ``` Examples that justify `scoped_cleanup=False`: CVE databases, threat intel feeds, public certificate transparency logs, global DNS / domain info. ### Step 6 — One-to-many When one source record points to many targets, flatten the IDs in `transform()` and use `one_to_many=True` on the relationship matcher: ```python # transform def transform_route_tables(route_tables): out = [] for rt in route_tables: out.append({ "id": rt["RouteTableId"], "subnet_ids": [a["SubnetId"] for a in rt.get("Associations", []) if "SubnetId" in a], }) return out # relationship @dataclass(frozen=True) class RouteTableToSubnetRel(CartographyRelSchema): target_node_label: str = "AWSEC2Subnet" target_node_matcher: TargetNodeMatcher = make_target_node_matcher({ "subnet_id": PropertyRef("subnet_ids", one_to_many=True), }) direction: LinkDirection = LinkDirection.OUTWARD rel_label: str = "ASSOCIATED_WITH" properties: RouteTableToSubnetRelProperties = RouteTableToSubnetRelProperties() ``` ### Step 7 — Date handling Neo4j 4+ supports native datetimes and ISO 8601 strings. Pass values through directly — do not convert to epoch milliseconds. ```python # DO "created_at": user_data.get("created_at") # DON'T "created_at": int(dt_parse.parse(user_data["created_at"]).timestamp() * 1000) ``` ### Step 8 — Loading ```python from cartography.client.core.tx import load load(neo4j_session, YourServiceTenantSchema(), [{"id": tenant_id}], lastupdated=update_tag) load( neo4j_session, YourServiceUserSchema(), data, lastupdated=update_tag, TENANT_ID=tenant_id, ) ``` ## Common issues - `PropertyRef validation failed` — missing `frozen=True` or missing type annotation. - Custom field on a `Schema` is "ignored" — only standard fields are recognised. - Cleanup deleting too much / too little — verify `scoped_cleanup` and `common_job_parameters["TENANT_ID"]`. For the full troubleshooting list, see the `troubleshooting` skill. ## References (load on demand) - `references/advanced-properties.md` — conditional labels (ECR images / attestations), MatchLink targets, deeper sub-resource rationale, ECS example.
عرض على GitHub