What
The six Import<Asset> helpers in internal/asset/ currently choose different fields for the upsert key passed to the underlying Update<Asset>(ctx, originOrId, ...) API call:
| Asset |
Upsert key today |
Field source |
| Dashboard |
ID |
metadata.dash0extensions.id |
| Check rule |
ID |
top-level id field |
| Synthetic check |
ID |
metadata.labels."dash0.com/id" |
| View |
ID |
metadata.labels."dash0.com/id" |
| Notification channel |
origin |
metadata.labels."dash0.com/origin" |
| Spam filter (v1alpha1+2) |
origin (ID as fallback) |
metadata.labels."dash0.com/origin" |
Four use ID, two use origin. The server URL parameter is uniformly originOrId, so it accepts either at the wire level — but whether a particular endpoint honors a brand-new user-supplied UUID on PUT (vs. silently generating one server-side) varies. The spam filter and notification channel endpoints reject user-supplied IDs and only accept origin as the user-controlled upsert key; the dashboard, view, check rule, and synthetic check endpoints currently accept both.
This issue tracks the audit and the alignment work.
Why
dash0 apply is a declarative tool; users expect "apply twice → same end state". Today that contract is upheld only by accident — for assets where the backend happens to honor user-supplied IDs on PUT. The spam filter divergence (discovered while wiring v1alpha2 into apply) showed that this assumption is not portable.
The CLAUDE.md note already articulates the semantic split: origin is "which system owns this asset" (a user-controlled label), while ID is the "external identifier used for upsert" — but the latter has just been observed to be server-managed for some asset types. If origin is the canonical upsert key across the board, the CLI should match that uniformly.
Aligning every Import* helper on a single rule (preferred: origin first, ID fallback) would:
- make
dash0 apply idempotent on the same field for every asset, removing per-asset gotchas;
- match the principle that the user-controlled label, not the server-controlled id, is the right key for declarative workflows;
- let the parity rule in
docs/cli-naming-conventions.md be enforced uniformly across asset types.
Prerequisites
- Confirm the backend contract via the dash0 monorepo. Specifically: for each asset type, does
PUT /api/<asset>/{originOrId} honor a brand-new user-supplied UUID, or does it only honor origins / pre-existing IDs?
- Once the contract is known per asset, pick the canonical key (most likely origin-first with ID fallback) and document the rule in
docs/cli-naming-conventions.md next to the parity rule.
Acceptance criteria
- One documented upsert-key rule per asset, justified against the backend contract.
ImportDashboard, ImportCheckRule, ImportSyntheticCheck, ImportView, ImportNotificationChannel, ImportSpamFilter, ImportSpamFilterV1Alpha2 all follow the same precedence (origin first, ID fallback, or the documented alternative — but uniform).
- Existing apply roundtrip tests (
test_apply_*_idempotency.sh) keep passing; new origin-keyed cases added to every asset so the contract is exercised.
docs/adding-commands.md step 4 (apply wiring) names the chosen upsert key so future asset additions follow the same rule by default.
What
The six
Import<Asset>helpers ininternal/asset/currently choose different fields for the upsert key passed to the underlyingUpdate<Asset>(ctx, originOrId, ...)API call:metadata.dash0extensions.ididfieldmetadata.labels."dash0.com/id"metadata.labels."dash0.com/id"metadata.labels."dash0.com/origin"metadata.labels."dash0.com/origin"Four use ID, two use origin. The server URL parameter is uniformly
originOrId, so it accepts either at the wire level — but whether a particular endpoint honors a brand-new user-supplied UUID on PUT (vs. silently generating one server-side) varies. The spam filter and notification channel endpoints reject user-supplied IDs and only accept origin as the user-controlled upsert key; the dashboard, view, check rule, and synthetic check endpoints currently accept both.This issue tracks the audit and the alignment work.
Why
dash0 applyis a declarative tool; users expect "apply twice → same end state". Today that contract is upheld only by accident — for assets where the backend happens to honor user-supplied IDs on PUT. The spam filter divergence (discovered while wiring v1alpha2 into apply) showed that this assumption is not portable.The CLAUDE.md note already articulates the semantic split: origin is "which system owns this asset" (a user-controlled label), while ID is the "external identifier used for upsert" — but the latter has just been observed to be server-managed for some asset types. If origin is the canonical upsert key across the board, the CLI should match that uniformly.
Aligning every
Import*helper on a single rule (preferred: origin first, ID fallback) would:dash0 applyidempotent on the same field for every asset, removing per-asset gotchas;docs/cli-naming-conventions.mdbe enforced uniformly across asset types.Prerequisites
PUT /api/<asset>/{originOrId}honor a brand-new user-supplied UUID, or does it only honor origins / pre-existing IDs?docs/cli-naming-conventions.mdnext to the parity rule.Acceptance criteria
ImportDashboard,ImportCheckRule,ImportSyntheticCheck,ImportView,ImportNotificationChannel,ImportSpamFilter,ImportSpamFilterV1Alpha2all follow the same precedence (origin first, ID fallback, or the documented alternative — but uniform).test_apply_*_idempotency.sh) keep passing; new origin-keyed cases added to every asset so the contract is exercised.docs/adding-commands.mdstep 4 (apply wiring) names the chosen upsert key so future asset additions follow the same rule by default.