SKILL.md v3 compliance
Fuels Electricity Commit
Commit approved fuels-and-electricity bills so GHG emissions processing actually starts.
No reviews yet 0 installs
emissionsfuelselectricityghgcommitingestcompliance
Versions
- v3 # Changelog ## v2 - **Local validation now delegates to the shared `resource_import` module** rather than duplicating required-field and enum checking. The behaviour for this skill's flat schema is unchanged — the existing suite is the regression gate — but the shared module also descends into nested `required` lists, which this schema does not use and the supplier schema does. - **A bill may now carry `tags`.** Labels are promoted to the RECORD level (alongside `reference`), never left in `properties` where `additionalProperties: false` would reject them. The server resolves each label against the org's tag definitions and creates it when missing. A tag failure is reported as a warning, not a hard stop — the bill itself is still written, and `summarise_report` names the failed reference in the same words `resource-import` uses, so the two skills read consistently. - `lib/resource_import.py` is a byte-identical copy of the module in the `resource-import` skill. Skill bundles are copied into the sandbox independently and there is no cross-skill import path, so the duplication is deliberate; `apps/community/curator/src/app/resourceImportModuleCopies.spec.ts` compares the two files byte-for-byte, so the claim is enforced by a suite rather than by having been checked once by hand. #### Behavioural divergences from the validator this port replaced Surfacing these is the stated purpose of the port. Every one is harmless for this skill's flat schema — but none is strictly a no-op, so all are listed rather than only the ones that bite. Compared against the deleted inline `_validate_record`. 1. **Nested `required` lists are descended into** (noted above). This schema has none; the supplier schema does. 2. **Enum checking descends into object sub-schemas** too, where the old `_enum_by_property` read top-level `properties` only. Again, no nested object here. 3. **`_is_blank` treats a whitespace-only string as missing**, where the old inline check (`properties.get(field) in (None, "")`) did not. A bill whose required field is `" "` is now reported `local_validation_failed` rather than committed. Arguably the better answer, but it is a change. 4. **`_missing_required` treats an empty dict `{}` as missing**, where `properties.get(field) in (None, "")` did not. Reachable only for an object-valued required field: this flat schema has none, the supplier schema (`address`) does. A fifth difference exists but is unreachable here: `validate_record` also rejects a record with a falsy `reference` or `name`, which the old inline check never looked at. `_reference_for` always returns at least `"|||"` and `_bill_name` always returns a non-empty string, so no bill built by this module can trigger it. ## v1 - Initial release: `lib/commit.py` builds the `qb.commit_ingest` payload for bills the user has approved in `/workspace/work/outputs/bills.json`, validates every record locally, calls the broker, and reports the result. - **`properties` is NOT the bill itself** (correction to this task's own brief, which described it that way — committing a bill unmodified would be REJECTED). The `fuels-and-electricity-bills` resource type sets `additionalProperties: false`, and an extracted bill carries two keys outside that schema: `reference` (record-level, used for idempotent upsert) and `derived` (triage-only, marks which fields were computed rather than read from source). `build_commit_payload` strips both, and anything else the schema doesn't recognise, via a **whitelist read live from the resource type's own `propertiesSchemaExtension.properties` keys** — never a hand-maintained duplicate list that could drift out of sync with the schema it must satisfy. - **`reference` is always recomputed, never trusted verbatim.** A triaged bill's `reference` (`siteId|type|billingDate|billingIdentifier`, matching the extract skill's own formula) is computed fresh from the bill's own fields on every call — a hand-typed or stale `reference` left over from triage can never silently break the idempotent upsert with the wrong key, and a bill missing `reference` entirely no longer raises a bare `KeyError`. - **Every record is validated locally — required fields and enum values — before any network call.** `validate_records` checks each record against the SAME schema `build_commit_payload` reads (again, no duplicated list): a triaged row still missing a required field, or still carrying an unrecognised `type`/`unit` value (e.g. `"light oil / diesel"` instead of the closed enum's `"Diesel"`), stops the WHOLE atomic batch immediately with a specific per-bill reason — never reaches the broker as an opaque 502 that would otherwise fail every other, genuinely valid bill in the same commit alongside it. - **The broker call uses the real SDK signature.** An earlier version of this skill called `qb.commit_ingest(**payload)` with the HTTP route's camelCase shape (`orgId`, `workspaceId`, `projectToCortex`, ...) — this does not match the actual Python SDK (`cortex-sandbox`'s `qonstrue_broker.commit_ingest(resource_types, relationship_types, records, *, atomic=None, project_to_cortex=None)`, which takes snake_case keywords and NO `orgId`/`workspaceId` at all — "Org and workspace identity are injected by the broker" per the SDK's own docstring) and would have raised `TypeError` on every live run. `_call_commit_ingest` now maps onto the real signature at the one call site that actually touches the broker; `build_commit_payload`'s own return shape (still camelCase, still carrying `orgId`/`workspaceId` for backward compatibility with its existing tests) is unchanged. - **A hard failure from the broker (a rejected atomic batch, a timeout, an unreachable sidecar) is caught and folded into the same report shape** `summarise_report`/`commit_succeeded` already know how to render — never a raw traceback reaching the user. Since `atomic: true` means the server rolls back the WHOLE transaction on any bad row, every record in the attempt is reported failed, not just whichever one triggered the exception. - **The resource type definition is only sent to the broker when it isn't already installed.** `commit_ingest`'s own resource-type upsert REPLACES whatever schema it's given rather than merging it, but the org's live schema may have been additively merged since blueprint install (`installBlueprintResourceTypesToOrg` uses `mergeProperties: true`) — resending our locally-bundled copy on every call would silently clobber any such addition. `_call_commit_ingest` checks `list_resource_types()` first and sends `resource_types: []` once the type is confirmed present — the same pattern the sanusi `batch-process` skill uses. - `atomic: true` and `projectToCortex: true` are always set — a partial failure must never leave half a client's bills committed, and a successful commit must be immediately visible under `/workspace/local/fuels-and-electricity-bills`. - `commit_succeeded(report)` is `False` whenever `report.records.failed` is non-empty (locally caught or server-reported). A failed commit is a hard stop: every failed `reference`/`error`/`detail` is surfaced via `summarise_report`, never silently dropped or reported as a success. When nothing was committed at all (a local validation stop, or a full atomic rollback), the summary says so plainly rather than claiming records were written. - **Cortex projection failures are surfaced as a distinct warning, not silence.** `report.cortex.failed` is non-empty when a bill was genuinely committed (the resource record was written, `records.failed` is empty) but failed to project into the Cortex view — `summarise_report` now includes an explicit warning for this case rather than only the (accurate but incomplete) "committed" headline, so a user is never told a bill is visible under `/workspace/local/fuels-and-electricity-bills` when it isn't yet. This does not, by itself, flip `commit_succeeded` to `False` — the underlying record write did succeed. - `summarise_report(report)` never quotes an emissions figure — `qb.commit_ingest` returns once records are written and `Integrations.Process.SiteEmissions` has been published, not once emissions are actually calculated. - `SKILL.md` documents the triage gate this skill's bundle assumes has already happened: parked rows from `bills.json`'s `issues` array grouped by _reason_ (not by row), naming the real issue fields (`parked_row`, `sourceSheet`/`sourceCell`, `typeCandidates`/`unitCandidates`) so the agent can quote actual source values and suggestions to the user, with sheet-level issues (`"row": null`) grouped as their own reason rather than folded into "missing everything." It also states the bill shape a triaged row must have (the resource type's required/optional fields) and makes clear `reference` never needs to be hand-computed. ### Known limitations / assumptions flagged for review - Locating this blueprint's bundled `resource-types/`/`relationship-types/` folders at runtime (the fallback path, used only when `list_resource_types()` can't confirm the type is already installed) globs specifically for `resource-types/fuels-and-electricity-bills.json` and verifies the loaded JSON's own `slug`, raising loudly if more than one installed blueprint's bundle matches — but this sandbox-glob branch is necessarily unexercised by any test in this repo checkout (`/workspace` doesn't exist here). The local dev/test fallback (the human slug `fuels-and-electricity-emissions-service`, a real sibling directory in this checkout) is what every test actually runs against. - Two triaged rows that end up sharing an identical recomputed `reference` (no `billingIdentifier` to distinguish them) are not detected by this skill — the extract skill already prevents this among its own output (`_split_duplicate_references`), but a triage decision could theoretically reintroduce a collision (e.g. two different parked rows both corrected to the same site/type/date with no identifier). Not implemented here; flagged as a residual gap rather than silently accepted. 8/28/2026
- v2 # Changelog ## v2 - **Local validation now delegates to the shared `resource_import` module** rather than duplicating required-field and enum checking. The behaviour for this skill's flat schema is unchanged — the existing suite is the regression gate — but the shared module also descends into nested `required` lists, which this schema does not use and the supplier schema does. - **A bill may now carry `tags`.** Labels are promoted to the RECORD level (alongside `reference`), never left in `properties` where `additionalProperties: false` would reject them. The server resolves each label against the org's tag definitions and creates it when missing. A tag failure is reported as a warning, not a hard stop — the bill itself is still written, and `summarise_report` names the failed reference in the same words `resource-import` uses, so the two skills read consistently. - `lib/resource_import.py` is a byte-identical copy of the module in the `resource-import` skill. Skill bundles are copied into the sandbox independently and there is no cross-skill import path, so the duplication is deliberate; `apps/community/curator/src/app/resourceImportModuleCopies.spec.ts` compares the two files byte-for-byte, so the claim is enforced by a suite rather than by having been checked once by hand. #### Behavioural divergences from the validator this port replaced Surfacing these is the stated purpose of the port. Every one is harmless for this skill's flat schema — but none is strictly a no-op, so all are listed rather than only the ones that bite. Compared against the deleted inline `_validate_record`. 1. **Nested `required` lists are descended into** (noted above). This schema has none; the supplier schema does. 2. **Enum checking descends into object sub-schemas** too, where the old `_enum_by_property` read top-level `properties` only. Again, no nested object here. 3. **`_is_blank` treats a whitespace-only string as missing**, where the old inline check (`properties.get(field) in (None, "")`) did not. A bill whose required field is `" "` is now reported `local_validation_failed` rather than committed. Arguably the better answer, but it is a change. 4. **`_missing_required` treats an empty dict `{}` as missing**, where `properties.get(field) in (None, "")` did not. Reachable only for an object-valued required field: this flat schema has none, the supplier schema (`address`) does. A fifth difference exists but is unreachable here: `validate_record` also rejects a record with a falsy `reference` or `name`, which the old inline check never looked at. `_reference_for` always returns at least `"|||"` and `_bill_name` always returns a non-empty string, so no bill built by this module can trigger it. ## v1 - Initial release: `lib/commit.py` builds the `qb.commit_ingest` payload for bills the user has approved in `/workspace/work/outputs/bills.json`, validates every record locally, calls the broker, and reports the result. - **`properties` is NOT the bill itself** (correction to this task's own brief, which described it that way — committing a bill unmodified would be REJECTED). The `fuels-and-electricity-bills` resource type sets `additionalProperties: false`, and an extracted bill carries two keys outside that schema: `reference` (record-level, used for idempotent upsert) and `derived` (triage-only, marks which fields were computed rather than read from source). `build_commit_payload` strips both, and anything else the schema doesn't recognise, via a **whitelist read live from the resource type's own `propertiesSchemaExtension.properties` keys** — never a hand-maintained duplicate list that could drift out of sync with the schema it must satisfy. - **`reference` is always recomputed, never trusted verbatim.** A triaged bill's `reference` (`siteId|type|billingDate|billingIdentifier`, matching the extract skill's own formula) is computed fresh from the bill's own fields on every call — a hand-typed or stale `reference` left over from triage can never silently break the idempotent upsert with the wrong key, and a bill missing `reference` entirely no longer raises a bare `KeyError`. - **Every record is validated locally — required fields and enum values — before any network call.** `validate_records` checks each record against the SAME schema `build_commit_payload` reads (again, no duplicated list): a triaged row still missing a required field, or still carrying an unrecognised `type`/`unit` value (e.g. `"light oil / diesel"` instead of the closed enum's `"Diesel"`), stops the WHOLE atomic batch immediately with a specific per-bill reason — never reaches the broker as an opaque 502 that would otherwise fail every other, genuinely valid bill in the same commit alongside it. - **The broker call uses the real SDK signature.** An earlier version of this skill called `qb.commit_ingest(**payload)` with the HTTP route's camelCase shape (`orgId`, `workspaceId`, `projectToCortex`, ...) — this does not match the actual Python SDK (`cortex-sandbox`'s `qonstrue_broker.commit_ingest(resource_types, relationship_types, records, *, atomic=None, project_to_cortex=None)`, which takes snake_case keywords and NO `orgId`/`workspaceId` at all — "Org and workspace identity are injected by the broker" per the SDK's own docstring) and would have raised `TypeError` on every live run. `_call_commit_ingest` now maps onto the real signature at the one call site that actually touches the broker; `build_commit_payload`'s own return shape (still camelCase, still carrying `orgId`/`workspaceId` for backward compatibility with its existing tests) is unchanged. - **A hard failure from the broker (a rejected atomic batch, a timeout, an unreachable sidecar) is caught and folded into the same report shape** `summarise_report`/`commit_succeeded` already know how to render — never a raw traceback reaching the user. Since `atomic: true` means the server rolls back the WHOLE transaction on any bad row, every record in the attempt is reported failed, not just whichever one triggered the exception. - **The resource type definition is only sent to the broker when it isn't already installed.** `commit_ingest`'s own resource-type upsert REPLACES whatever schema it's given rather than merging it, but the org's live schema may have been additively merged since blueprint install (`installBlueprintResourceTypesToOrg` uses `mergeProperties: true`) — resending our locally-bundled copy on every call would silently clobber any such addition. `_call_commit_ingest` checks `list_resource_types()` first and sends `resource_types: []` once the type is confirmed present — the same pattern the sanusi `batch-process` skill uses. - `atomic: true` and `projectToCortex: true` are always set — a partial failure must never leave half a client's bills committed, and a successful commit must be immediately visible under `/workspace/local/fuels-and-electricity-bills`. - `commit_succeeded(report)` is `False` whenever `report.records.failed` is non-empty (locally caught or server-reported). A failed commit is a hard stop: every failed `reference`/`error`/`detail` is surfaced via `summarise_report`, never silently dropped or reported as a success. When nothing was committed at all (a local validation stop, or a full atomic rollback), the summary says so plainly rather than claiming records were written. - **Cortex projection failures are surfaced as a distinct warning, not silence.** `report.cortex.failed` is non-empty when a bill was genuinely committed (the resource record was written, `records.failed` is empty) but failed to project into the Cortex view — `summarise_report` now includes an explicit warning for this case rather than only the (accurate but incomplete) "committed" headline, so a user is never told a bill is visible under `/workspace/local/fuels-and-electricity-bills` when it isn't yet. This does not, by itself, flip `commit_succeeded` to `False` — the underlying record write did succeed. - `summarise_report(report)` never quotes an emissions figure — `qb.commit_ingest` returns once records are written and `Integrations.Process.SiteEmissions` has been published, not once emissions are actually calculated. - `SKILL.md` documents the triage gate this skill's bundle assumes has already happened: parked rows from `bills.json`'s `issues` array grouped by _reason_ (not by row), naming the real issue fields (`parked_row`, `sourceSheet`/`sourceCell`, `typeCandidates`/`unitCandidates`) so the agent can quote actual source values and suggestions to the user, with sheet-level issues (`"row": null`) grouped as their own reason rather than folded into "missing everything." It also states the bill shape a triaged row must have (the resource type's required/optional fields) and makes clear `reference` never needs to be hand-computed. ### Known limitations / assumptions flagged for review - Locating this blueprint's bundled `resource-types/`/`relationship-types/` folders at runtime (the fallback path, used only when `list_resource_types()` can't confirm the type is already installed) globs specifically for `resource-types/fuels-and-electricity-bills.json` and verifies the loaded JSON's own `slug`, raising loudly if more than one installed blueprint's bundle matches — but this sandbox-glob branch is necessarily unexercised by any test in this repo checkout (`/workspace` doesn't exist here). The local dev/test fallback (the human slug `fuels-and-electricity-emissions-service`, a real sibling directory in this checkout) is what every test actually runs against. - Two triaged rows that end up sharing an identical recomputed `reference` (no `billingIdentifier` to distinguish them) are not detected by this skill — the extract skill already prevents this among its own output (`_split_duplicate_references`), but a triage decision could theoretically reintroduce a collision (e.g. two different parked rows both corrected to the same site/type/date with no identifier). Not implemented here; flagged as a residual gap rather than silently accepted. 8/27/2026
- v1 # Changelog ## v1 - Initial release: `lib/commit.py` builds the `qb.commit_ingest` payload for bills the user has approved in `/workspace/work/outputs/bills.json`, validates every record locally, calls the broker, and reports the result. - **`properties` is NOT the bill itself** (correction to this task's own brief, which described it that way — committing a bill unmodified would be REJECTED). The `fuels-and-electricity-bills` resource type sets `additionalProperties: false`, and an extracted bill carries two keys outside that schema: `reference` (record-level, used for idempotent upsert) and `derived` (triage-only, marks which fields were computed rather than read from source). `build_commit_payload` strips both, and anything else the schema doesn't recognise, via a **whitelist read live from the resource type's own `propertiesSchemaExtension.properties` keys** — never a hand-maintained duplicate list that could drift out of sync with the schema it must satisfy. - **`reference` is always recomputed, never trusted verbatim.** A triaged bill's `reference` (`siteId|type|billingDate|billingIdentifier`, matching the extract skill's own formula) is computed fresh from the bill's own fields on every call — a hand-typed or stale `reference` left over from triage can never silently break the idempotent upsert with the wrong key, and a bill missing `reference` entirely no longer raises a bare `KeyError`. - **Every record is validated locally — required fields and enum values — before any network call.** `validate_records` checks each record against the SAME schema `build_commit_payload` reads (again, no duplicated list): a triaged row still missing a required field, or still carrying an unrecognised `type`/`unit` value (e.g. `"light oil / diesel"` instead of the closed enum's `"Diesel"`), stops the WHOLE atomic batch immediately with a specific per-bill reason — never reaches the broker as an opaque 502 that would otherwise fail every other, genuinely valid bill in the same commit alongside it. - **The broker call uses the real SDK signature.** An earlier version of this skill called `qb.commit_ingest(**payload)` with the HTTP route's camelCase shape (`orgId`, `workspaceId`, `projectToCortex`, ...) — this does not match the actual Python SDK (`cortex-sandbox`'s `qonstrue_broker.commit_ingest(resource_types, relationship_types, records, *, atomic=None, project_to_cortex=None)`, which takes snake_case keywords and NO `orgId`/`workspaceId` at all — "Org and workspace identity are injected by the broker" per the SDK's own docstring) and would have raised `TypeError` on every live run. `_call_commit_ingest` now maps onto the real signature at the one call site that actually touches the broker; `build_commit_payload`'s own return shape (still camelCase, still carrying `orgId`/`workspaceId` for backward compatibility with its existing tests) is unchanged. - **A hard failure from the broker (a rejected atomic batch, a timeout, an unreachable sidecar) is caught and folded into the same report shape** `summarise_report`/`commit_succeeded` already know how to render — never a raw traceback reaching the user. Since `atomic: true` means the server rolls back the WHOLE transaction on any bad row, every record in the attempt is reported failed, not just whichever one triggered the exception. - **The resource type definition is only sent to the broker when it isn't already installed.** `commit_ingest`'s own resource-type upsert REPLACES whatever schema it's given rather than merging it, but the org's live schema may have been additively merged since blueprint install (`installBlueprintResourceTypesToOrg` uses `mergeProperties: true`) — resending our locally-bundled copy on every call would silently clobber any such addition. `_call_commit_ingest` checks `list_resource_types()` first and sends `resource_types: []` once the type is confirmed present — the same pattern the sanusi `batch-process` skill uses. - `atomic: true` and `projectToCortex: true` are always set — a partial failure must never leave half a client's bills committed, and a successful commit must be immediately visible under `/workspace/local/fuels-and-electricity-bills`. - `commit_succeeded(report)` is `False` whenever `report.records.failed` is non-empty (locally caught or server-reported). A failed commit is a hard stop: every failed `reference`/`error`/`detail` is surfaced via `summarise_report`, never silently dropped or reported as a success. When nothing was committed at all (a local validation stop, or a full atomic rollback), the summary says so plainly rather than claiming records were written. - **Cortex projection failures are surfaced as a distinct warning, not silence.** `report.cortex.failed` is non-empty when a bill was genuinely committed (the resource record was written, `records.failed` is empty) but failed to project into the Cortex view — `summarise_report` now includes an explicit warning for this case rather than only the (accurate but incomplete) "committed" headline, so a user is never told a bill is visible under `/workspace/local/fuels-and-electricity-bills` when it isn't yet. This does not, by itself, flip `commit_succeeded` to `False` — the underlying record write did succeed. - `summarise_report(report)` never quotes an emissions figure — `qb.commit_ingest` returns once records are written and `Integrations.Process.SiteEmissions` has been published, not once emissions are actually calculated. - `SKILL.md` documents the triage gate this skill's bundle assumes has already happened: parked rows from `bills.json`'s `issues` array grouped by _reason_ (not by row), naming the real issue fields (`parked_row`, `sourceSheet`/`sourceCell`, `typeCandidates`/`unitCandidates`) so the agent can quote actual source values and suggestions to the user, with sheet-level issues (`"row": null`) grouped as their own reason rather than folded into "missing everything." It also states the bill shape a triaged row must have (the resource type's required/optional fields) and makes clear `reference` never needs to be hand-computed. ### Known limitations / assumptions flagged for review - Locating this blueprint's bundled `resource-types/`/`relationship-types/` folders at runtime (the fallback path, used only when `list_resource_types()` can't confirm the type is already installed) globs specifically for `resource-types/fuels-and-electricity-bills.json` and verifies the loaded JSON's own `slug`, raising loudly if more than one installed blueprint's bundle matches — but this sandbox-glob branch is necessarily unexercised by any test in this repo checkout (`/workspace` doesn't exist here). The local dev/test fallback (the human slug `fuels-and-electricity-emissions-service`, a real sibling directory in this checkout) is what every test actually runs against. - Two triaged rows that end up sharing an identical recomputed `reference` (no `billingIdentifier` to distinguish them) are not detected by this skill — the extract skill already prevents this among its own output (`_split_duplicate_references`), but a triage decision could theoretically reintroduce a collision (e.g. two different parked rows both corrected to the same site/type/date with no identifier). Not implemented here; flagged as a residual gap rather than silently accepted. 8/11/2026