SKILL.md v2 operations

Resource Import

Shape rows to a resource type's own schema, then import them — tags included.

No reviews yet 0 installs
Imports rows from a spreadsheet, CSV, or JSON export into any Qonstrue resource type by shaping each row to that type's OWN schema, read live from the organisation's own resource type definition via the broker's `get_resource_type` rather than inferred from the source or copied into the bundle. A type the organisation does not have is reported and the import stops - there is no bundled fallback to validate against. Prints the full schema (including nested field paths and required flags) so the mapping is made against what the type actually accepts — the broker's list_resource_types returns top-level property names only. Columns the schema does not define are dropped and reported, never coerced; rows missing a required field are reported with the exact field path, never defaulted. A column can be mapped to tags to become resource tag labels, resolved by label and created when missing. Never re-declares an already-installed resource type, because commit_ingest replaces rather than merges a schema and built-in types are not exempt. A failed commit is a hard stop; a tag or Cortex projection failure is surfaced as a warning, since the record itself was written.
resourcesimportingestionsupplierscsvspreadsheettags

Versions

  • v2 # Changelog ## v3 - **Add the `# Resource Import` title so the listing can publish at all.** `primary-markdown-heading` requires a top-level `# ` heading; this SKILL.md went straight from frontmatter to `### What this skill does` and was the only one of 23 skill assets without one. Every sync therefore created a version that failed validation and left the listing at `draft`, so the skill never reached an organisation — visible only as a single WARN line in the curator log. ## v1 - Initial release: `lib/resource_import.py` shapes source rows to a target resource type's own schema and `lib/import_cli.py` validates and commits them via `qb.commit_ingest`. - **The schema is read, never inferred.** `--describe <slug>` flattens the blueprint-carried definition to `path: type (required/optional)` rows, including NESTED paths. This exists because `qb.list_resource_types()` returns top-level property _names_ only — nothing in the broker's response reveals that supplier's `address` is an object with a required `countryCode`, so an agent shaping from `list_resource_types` alone would produce rows that fail every time. - **`properties` is built from a whitelist derived from the schema's own keys**, descending into nested objects — never a hand-maintained list and never a blocklist of specific bad columns. A column the schema does not define is dropped for the same structural reason regardless of what it is called, and every dropped column is named in the summary rather than silently discarded. - **Nested required fields are validated locally.** Supplier's `address` object carries its own `required: ["countryCode"]`; a row whose address is present but has no country code is reported as `address.countryCode`, before any network call. With `atomic: true` a single bad row fails the WHOLE batch server-side as an opaque 502, so catching it locally is what makes the failure legible. - **A row missing a required field is never completed by guessing.** For supplier this means a row with no country cannot be imported at all — that is the correct outcome, not a defect to work around. - **An already-installed resource type is never re-declared.** `commit_ingest`'s resource-type upsert REPLACES the schema it is given rather than merging it, and built-in types are NOT exempt from that update path — so re-declaring `supplier` would overwrite the org's live partner schema. `types_to_declare` checks `list_resource_types()` first and sends `[]` once the type is confirmed present. - **A column can be mapped to `tags`.** Its cell value becomes a tag label on the record; the server resolves the label against the org's existing tag definitions (case-insensitively) and creates it when missing. A blank cell produces no tag. - **Tag failures are warnings, not hard stops.** `commit_succeeded` is keyed on `records.failed` only — a tag assignment that fails leaves the resource itself written, so the summary names the failure without calling the import failed. Cortex projection failures are treated the same way. - **The broker call uses the real SDK signature** — `commit_ingest(resource_types, relationship_types, records, *, atomic=None, project_to_cortex=None)`, snake_case and with no `orgId`/`workspaceId` (identity is injected by the broker). A broker exception is folded into the same report shape the summariser already renders, never a raw traceback. ### Known limitations / assumptions flagged for review - **(v1 only - superseded in v2: the resolution ladder and `TestSandboxResolution` were both removed when the schema moved to the broker. Neither the branch nor the test exists any more.)** The sandbox glob branch of `_blueprint_definitions_root` (which locates the installed blueprint's `resource-types/` under `/workspace/.blueprints/`) IS covered, by `TestSandboxResolution` in `test_resource_import.py`. It points `_SANDBOX_BLUEPRINTS_ROOT` at a `tmp_path` tree shaped like `<blueprint-id>/resource-types/<slug>.json` and exercises all three branches: a single matching blueprint, two blueprints that ambiguously define the same slug, and a file whose declared `slug` does not match its filename. The logic only needed a directory _shaped like_ a sandbox, not a real `/workspace`, which does not exist in this checkout. - `lib/resource_import.py` is a byte-identical copy of the module also bundled with `fuels-electricity-commit`. Skill bundles are copied into the sandbox independently and there is no cross-skill import path, so the two copies cannot share a single file — the duplication is deliberate, and `apps/community/curator/src/app/resourceImportModuleCopies.spec.ts` compares the two files byte-for-byte on every run of the `community-curator` suite. (`nx affected` does not see these asset files, so the check deliberately lives in a spec that always runs rather than relying on the assets being detected as changed.) - Two source rows that map to the same `reference` are not detected here; the second silently upserts over the first. ## v2 - **The schema is read from the broker, not from a bundled copy.** `load_resource_type` calls `qb.get_resource_type(slug)` and returns the organisation's LIVE definition. A type can be additively merged into an org at install, so a copy shipped with this skill is what the platform released rather than what the org enforces — validating against it means sometimes rejecting rows the server would accept. - **Custom resource types are now importable.** A customer's own type exists only in their organisation and is in no bundle, so a bundled-schema skill could only ever import types it shipped with. The listing's claim to import into "any resource type — built-in or custom" is now true. - **There is no fallback to a bundled copy.** A type the organisation does not have is reported as `resource_type_unavailable`; the import stops rather than validating against a schema that may not match. - `resource-types/supplier.json`, the blueprint/skill-bundle resolution ladder, and the drift test that policed the duplication are all removed. - **The `types_to_declare` guard is now structurally unreachable, and that is safer, not laxer.** `load_resource_type` only returns for a type the org already has, and `list_resource_types()` includes built-ins by default, so `installed` always contains the slug and the declare list this skill sends is always `[]`. The schema-overwrite path v1 guarded against is therefore closed for this skill rather than merely checked. The guard (and `TestUnreadableTypeList`) stay as defence in depth against a future change to either call. 8/28/2026
Installed bundles are copies — your workspace data stays in your workspace. Qonstrue Community Marketplace