@homeflare/alchemy 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/grafana/alerting-provenance.d.ts +80 -0
  2. package/dist/grafana/alerting-provenance.d.ts.map +1 -0
  3. package/dist/grafana/contact-point-fixtures.d.ts +28 -0
  4. package/dist/grafana/contact-point-fixtures.d.ts.map +1 -0
  5. package/dist/grafana/contact-point.d.ts +99 -0
  6. package/dist/grafana/contact-point.d.ts.map +1 -0
  7. package/dist/grafana/dashboard-model.d.ts +8 -38
  8. package/dist/grafana/dashboard-model.d.ts.map +1 -1
  9. package/dist/grafana/datasource.d.ts +2 -9
  10. package/dist/grafana/datasource.d.ts.map +1 -1
  11. package/dist/grafana/index.d.ts +10 -4
  12. package/dist/grafana/index.d.ts.map +1 -1
  13. package/dist/grafana/index.js +198 -41
  14. package/dist/grafana/index.js.map +12 -6
  15. package/dist/grafana/message-template.d.ts +70 -0
  16. package/dist/grafana/message-template.d.ts.map +1 -0
  17. package/dist/grafana/mute-timing.d.ts +112 -0
  18. package/dist/grafana/mute-timing.d.ts.map +1 -0
  19. package/dist/grafana/providers.d.ts +1 -1
  20. package/dist/grafana/providers.d.ts.map +1 -1
  21. package/dist/grafana/secret-refs.d.ts +19 -0
  22. package/dist/grafana/secret-refs.d.ts.map +1 -0
  23. package/dist/grafana/subset-match.d.ts +2 -0
  24. package/dist/grafana/subset-match.d.ts.map +1 -0
  25. package/dist/linux/index.d.ts +1 -1
  26. package/dist/linux/index.d.ts.map +1 -1
  27. package/dist/linux/index.js +3 -1
  28. package/dist/linux/index.js.map +1 -1
  29. package/docs/grafana-alerting.md +145 -0
  30. package/docs/grafana.md +4 -3
  31. package/docs/upstream-conformance.md +26 -4
  32. package/package.json +1 -1
  33. package/src/grafana/alerting-provenance.ts +109 -0
  34. package/src/grafana/contact-point-fixtures.ts +25 -0
  35. package/src/grafana/contact-point-provenance.test.ts +113 -0
  36. package/src/grafana/contact-point-secrets.test.ts +117 -0
  37. package/src/grafana/contact-point.test.ts +123 -0
  38. package/src/grafana/contact-point.ts +179 -0
  39. package/src/grafana/dashboard-model.ts +8 -57
  40. package/src/grafana/datasource.ts +5 -27
  41. package/src/grafana/index.ts +24 -4
  42. package/src/grafana/message-template.test.ts +212 -0
  43. package/src/grafana/message-template.ts +114 -0
  44. package/src/grafana/mute-timing.test.ts +227 -0
  45. package/src/grafana/mute-timing.ts +160 -0
  46. package/src/grafana/providers.ts +6 -0
  47. package/src/grafana/secret-refs.ts +48 -0
  48. package/src/grafana/subset-match.ts +71 -0
  49. package/src/linux/index-export.test.ts +25 -0
  50. package/src/linux/index.ts +15 -1
@@ -1,17 +1,23 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/grafana/datasource.ts", "../src/grafana/resource.ts", "../src/grafana/dashboard.ts", "../src/grafana/dashboard-model.ts", "../src/grafana/provisioned.ts", "../src/grafana/folder.ts", "../src/grafana/credentials.ts", "../src/grafana/providers.ts"],
3
+ "sources": ["../src/grafana/datasource.ts", "../src/grafana/resource.ts", "../src/grafana/secret-refs.ts", "../src/grafana/dashboard.ts", "../src/grafana/subset-match.ts", "../src/grafana/dashboard-model.ts", "../src/grafana/provisioned.ts", "../src/grafana/folder.ts", "../src/grafana/contact-point.ts", "../src/grafana/alerting-provenance.ts", "../src/grafana/message-template.ts", "../src/grafana/mute-timing.ts", "../src/grafana/credentials.ts", "../src/grafana/providers.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * `Grafana.Datasource` — one Grafana data source, keyed by `uid`.\n *\n * ★ WHY `uid` IS REQUIRED, NOT GENERATED. `getDataSourceByUID`/`updateDataSourceByUID`/\n * `deleteDataSourceByUID` are all UID-keyed, and `getDataSources` (list) takes no filter — so\n * locating a datasource this resource didn't create means listing the whole org and matching by\n * name, the same ambiguity netbox/resource.ts's `soleMatch` exists to catch. Requiring `uid` up\n * front (Grafana accepts a caller-chosen one on create — `AddDataSourceRequest.uid`) sidesteps\n * it entirely: `read`/`reconcile`/`destroy` all key on the one field the API itself is keyed on.\n * `teslamate-grafana`'s live datasource (provisioned from a file, not this resource) has a\n * Grafana-generated uid; the census in the handoff script records it so a first declaration can\n * name it explicitly.\n *\n * ⛔ `secureJsonData` (e.g. a datasource password) NEVER APPEARS AS A LITERAL PROP (S25).\n * `secureJsonDataRefs` takes env var NAMES instead; `resolveSecureJsonData` reads them from\n * `process.env` fresh inside `create`/`update`'s own effect, never at plan time and never\n * returned in `attributes` — Grafana's own read API agrees: `DataSource.secureJsonFields` on a\n * GET is a map of booleans (is-it-set), never the value.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\n\nexport interface DatasourceProps {\n uid: string;\n name: string;\n /** e.g. `postgres`, `prometheus`, `influxdb` — the Grafana plugin id. */\n type: string;\n url?: string;\n access?: 'proxy' | 'direct';\n database?: string;\n user?: string;\n basicAuth?: boolean;\n basicAuthUser?: string;\n isDefault?: boolean;\n withCredentials?: boolean;\n jsonData?: Record<string, unknown>;\n /** Secure field name (e.g. `password`) -> env var NAME holding its value. Never the value. */\n secureJsonDataRefs?: Record<string, string>;\n}\n\nexport interface DatasourceAttributes {\n uid: string;\n id: number;\n name: string;\n type: string;\n url: string;\n access: string;\n orgId: number;\n isDefault: boolean;\n readOnly: boolean;\n version: number;\n}\n\nexport interface GrafanaDatasource extends Resource<\n 'Grafana.Datasource',\n DatasourceProps,\n DatasourceAttributes,\n never\n> {}\n\nexport const GrafanaDatasource = Resource<GrafanaDatasource>('Grafana.Datasource');\n\n/** A named secure-field env var is unset — a domain refusal, not a distilled error. */\nexport class GrafanaSecretRefUnsetError extends Data.TaggedError('GrafanaSecretRefUnsetError')<{\n readonly message: string;\n}> {}\n\n/** Reads every named env var fresh, inside the caller's effect (S24) — never at plan time. */\nconst resolveSecureJsonData = (\n refs: Record<string, string> | undefined,\n): Effect.Effect<Record<string, string> | undefined, GrafanaSecretRefUnsetError> =>\n Effect.gen(function* () {\n if (refs === undefined) return undefined;\n const out: Record<string, string> = {};\n for (const [field, envVar] of Object.entries(refs)) {\n const raw = process.env[envVar];\n if (raw === undefined || raw.trim() === '') {\n return yield* Effect.fail(\n new GrafanaSecretRefUnsetError({\n message:\n `${envVar} is unset. Export the value of secureJsonData.${field} there — ` +\n 'never as an Alchemy prop.',\n }),\n );\n }\n out[field] = raw.trim();\n }\n return out;\n });\n\n/** `exactOptionalPropertyTypes` means an undeclared field must be OMITTED, never sent as `undefined`. */\nconst fields = (props: DatasourceProps) => ({\n name: props.name,\n type: props.type,\n ...(props.access === undefined ? {} : { access: props.access }),\n ...(props.basicAuth === undefined ? {} : { basicAuth: props.basicAuth }),\n ...(props.basicAuthUser === undefined ? {} : { basicAuthUser: props.basicAuthUser }),\n ...(props.database === undefined ? {} : { database: props.database }),\n ...(props.isDefault === undefined ? {} : { isDefault: props.isDefault }),\n ...(props.jsonData === undefined ? {} : { jsonData: props.jsonData }),\n ...(props.url === undefined ? {} : { url: props.url }),\n ...(props.user === undefined ? {} : { user: props.user }),\n ...(props.withCredentials === undefined ? {} : { withCredentials: props.withCredentials }),\n});\n\nexport const spec: GrafanaSpec<\n DatasourceProps,\n grafana.DataSource,\n DatasourceAttributes,\n | grafana.AddDataSourceError\n | grafana.GetDataSourceByUIDError\n | grafana.UpdateDataSourceByUIDError\n | grafana.DeleteDataSourceByUIDError\n | GrafanaSecretRefUnsetError\n> = {\n attributes: (live) => ({\n access: live.access ?? '',\n id: live.id ?? 0,\n isDefault: live.isDefault ?? false,\n name: live.name ?? '',\n orgId: live.orgId ?? 0,\n readOnly: live.readOnly ?? false,\n type: live.type ?? '',\n uid: live.uid ?? '',\n url: live.url ?? '',\n version: live.version ?? 0,\n }),\n create: (props) =>\n Effect.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) =>\n grafana.addDataSource({\n ...fields(props),\n ...(secureJsonData === undefined ? {} : { secureJsonData }),\n uid: props.uid,\n }),\n ),\n destroy: (props) => grafana.deleteDataSourceByUID({ uid: props.uid }),\n fetchLive: (props) =>\n grafana\n .getDataSourceByUID({ uid: props.uid })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n /**\n * ⛔ AN UNDECLARED FIELD IS NEITHER COMPARED NOR SENT — the same rule every family in this\n * estate follows. `secureJsonDataRefs` is never compared: Grafana never returns the value to\n * diff against, only whether a field is set (`secureJsonFields`), so this resource cannot\n * detect drift in a secret's value — only in whether one is configured at all.\n */\n matches: (attributes, props) =>\n (props.name === undefined || attributes.name === props.name) &&\n (props.type === undefined || attributes.type === props.type) &&\n (props.url === undefined || attributes.url === props.url) &&\n (props.access === undefined || attributes.access === props.access) &&\n (props.isDefault === undefined || attributes.isDefault === props.isDefault),\n update: (props, live) =>\n Effect.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) =>\n grafana.updateDataSourceByUID({\n ...fields(props),\n ...(secureJsonData === undefined ? {} : { secureJsonData }),\n ...(live.version === undefined ? {} : { version: live.version }),\n uid: props.uid,\n }),\n ),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaDatasourceProvider = () =>\n Provider.effect(GrafanaDatasource, Effect.succeed(GrafanaDatasource.Provider.of(handlers)));\n",
5
+ "/**\n * `Grafana.Datasource` — one Grafana data source, keyed by `uid`.\n *\n * ★ WHY `uid` IS REQUIRED, NOT GENERATED. `getDataSourceByUID`/`updateDataSourceByUID`/\n * `deleteDataSourceByUID` are all UID-keyed, and `getDataSources` (list) takes no filter — so\n * locating a datasource this resource didn't create means listing the whole org and matching by\n * name, the same ambiguity netbox/resource.ts's `soleMatch` exists to catch. Requiring `uid` up\n * front (Grafana accepts a caller-chosen one on create — `AddDataSourceRequest.uid`) sidesteps\n * it entirely: `read`/`reconcile`/`destroy` all key on the one field the API itself is keyed on.\n * `teslamate-grafana`'s live datasource (provisioned from a file, not this resource) has a\n * Grafana-generated uid; the census in the handoff script records it so a first declaration can\n * name it explicitly.\n *\n * ⛔ `secureJsonData` (e.g. a datasource password) NEVER APPEARS AS A LITERAL PROP (S25).\n * `secureJsonDataRefs` takes env var NAMES instead; `resolveSecureJsonData` reads them from\n * `process.env` fresh inside `create`/`update`'s own effect, never at plan time and never\n * returned in `attributes` — Grafana's own read API agrees: `DataSource.secureJsonFields` on a\n * GET is a map of booleans (is-it-set), never the value.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Effect from 'effect/Effect';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\nimport { GrafanaSecretRefUnsetError, resolveSecretRefs } from './secret-refs.ts';\n\nexport { GrafanaSecretRefUnsetError };\n\nexport interface DatasourceProps {\n uid: string;\n name: string;\n /** e.g. `postgres`, `prometheus`, `influxdb` — the Grafana plugin id. */\n type: string;\n url?: string;\n access?: 'proxy' | 'direct';\n database?: string;\n user?: string;\n basicAuth?: boolean;\n basicAuthUser?: string;\n isDefault?: boolean;\n withCredentials?: boolean;\n jsonData?: Record<string, unknown>;\n /** Secure field name (e.g. `password`) -> env var NAME holding its value. Never the value. */\n secureJsonDataRefs?: Record<string, string>;\n}\n\nexport interface DatasourceAttributes {\n uid: string;\n id: number;\n name: string;\n type: string;\n url: string;\n access: string;\n orgId: number;\n isDefault: boolean;\n readOnly: boolean;\n version: number;\n}\n\nexport interface GrafanaDatasource extends Resource<\n 'Grafana.Datasource',\n DatasourceProps,\n DatasourceAttributes,\n never\n> {}\n\nexport const GrafanaDatasource = Resource<GrafanaDatasource>('Grafana.Datasource');\n\n/** Reads every named env var fresh, inside the caller's effect (S24) — never at plan time. */\nconst resolveSecureJsonData = (refs: Record<string, string> | undefined) =>\n resolveSecretRefs(refs, (field) => `secureJsonData.${field}`);\n\n/** `exactOptionalPropertyTypes` means an undeclared field must be OMITTED, never sent as `undefined`. */\nconst fields = (props: DatasourceProps) => ({\n name: props.name,\n type: props.type,\n ...(props.access === undefined ? {} : { access: props.access }),\n ...(props.basicAuth === undefined ? {} : { basicAuth: props.basicAuth }),\n ...(props.basicAuthUser === undefined ? {} : { basicAuthUser: props.basicAuthUser }),\n ...(props.database === undefined ? {} : { database: props.database }),\n ...(props.isDefault === undefined ? {} : { isDefault: props.isDefault }),\n ...(props.jsonData === undefined ? {} : { jsonData: props.jsonData }),\n ...(props.url === undefined ? {} : { url: props.url }),\n ...(props.user === undefined ? {} : { user: props.user }),\n ...(props.withCredentials === undefined ? {} : { withCredentials: props.withCredentials }),\n});\n\nexport const spec: GrafanaSpec<\n DatasourceProps,\n grafana.DataSource,\n DatasourceAttributes,\n | grafana.AddDataSourceError\n | grafana.GetDataSourceByUIDError\n | grafana.UpdateDataSourceByUIDError\n | grafana.DeleteDataSourceByUIDError\n | GrafanaSecretRefUnsetError\n> = {\n attributes: (live) => ({\n access: live.access ?? '',\n id: live.id ?? 0,\n isDefault: live.isDefault ?? false,\n name: live.name ?? '',\n orgId: live.orgId ?? 0,\n readOnly: live.readOnly ?? false,\n type: live.type ?? '',\n uid: live.uid ?? '',\n url: live.url ?? '',\n version: live.version ?? 0,\n }),\n create: (props) =>\n Effect.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) =>\n grafana.addDataSource({\n ...fields(props),\n ...(secureJsonData === undefined ? {} : { secureJsonData }),\n uid: props.uid,\n }),\n ),\n destroy: (props) => grafana.deleteDataSourceByUID({ uid: props.uid }),\n fetchLive: (props) =>\n grafana\n .getDataSourceByUID({ uid: props.uid })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n /**\n * ⛔ AN UNDECLARED FIELD IS NEITHER COMPARED NOR SENT — the same rule every family in this\n * estate follows. `secureJsonDataRefs` is never compared: Grafana never returns the value to\n * diff against, only whether a field is set (`secureJsonFields`), so this resource cannot\n * detect drift in a secret's value — only in whether one is configured at all.\n */\n matches: (attributes, props) =>\n (props.name === undefined || attributes.name === props.name) &&\n (props.type === undefined || attributes.type === props.type) &&\n (props.url === undefined || attributes.url === props.url) &&\n (props.access === undefined || attributes.access === props.access) &&\n (props.isDefault === undefined || attributes.isDefault === props.isDefault),\n update: (props, live) =>\n Effect.flatMap(resolveSecureJsonData(props.secureJsonDataRefs), (secureJsonData) =>\n grafana.updateDataSourceByUID({\n ...fields(props),\n ...(secureJsonData === undefined ? {} : { secureJsonData }),\n ...(live.version === undefined ? {} : { version: live.version }),\n uid: props.uid,\n }),\n ),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaDatasourceProvider = () =>\n Provider.effect(GrafanaDatasource, Effect.succeed(GrafanaDatasource.Provider.of(handlers)));\n",
6
6
  "/**\n * One shape for every Grafana object this family declares — mirroring `../forgejo/resource.ts`\n * and `../netbox/resource.ts` exactly, minus the credentials wrapping those two bake in.\n *\n * ★ CREDENTIALS ARE NOT PROVIDED HERE. Forgejo and NetBox each manage exactly one instance for\n * this house, so their engines close over a hardcoded `CredentialsFromEnv`. Grafana does not —\n * see `credentials.ts`'s note on `grafana.homeflare.dev` vs `teslamate-grafana` — so `Credentials`\n * stays a context requirement on every handler here, and `providers.ts`'s `grafanaProviders`\n * supplies it per target with `Layer.provide`, the same composition `litellm/providers.ts` uses.\n *\n * ⚠️ `NotFound` IS FOLDED TO \"ABSENT\" INSIDE EACH RESOURCE FILE'S OWN `fetchLive`, NOT HERE — the\n * same split forgejo's and netbox's engines use. `Effect.catchTag`'s tag-literal inference needs\n * a concrete error union to resolve `\"NotFound\"` against; a spec's `E` is only concrete at each\n * resource file's own call site.\n */\nimport { isResolved } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport type { GrafanaOpContext } from '@distilled.cloud/grafana/Protocol';\nimport * as Effect from 'effect/Effect';\n\n/**\n * One Grafana object's distilled calls. `Live` is whatever the SDK decodes (a `DataSource`, …) —\n * no untyped `Record<string, unknown>`. `E` is left to each resource file to declare (the union\n * of the distilled operations it actually calls plus any domain refusal, e.g. an unset secret-ref\n * env var), so a call site's precise error union flows straight through instead of being widened\n * by hand here.\n */\nexport type GrafanaSpec<Props extends object, Live, Attributes, E> = {\n /** Already folds the SDK's `NotFound` to `undefined` via its own `catchTag` — see the note above. */\n readonly fetchLive: (props: Props) => Effect.Effect<Live | undefined, E, GrafanaOpContext>;\n readonly attributes: (live: Live, props: Props) => Attributes | undefined;\n readonly matches: (attributes: Attributes, props: Props) => boolean;\n readonly create: (props: Props) => Effect.Effect<unknown, E, GrafanaOpContext>;\n readonly update?: (props: Props, live: Live) => Effect.Effect<unknown, E, GrafanaOpContext>;\n readonly destroy: (props: Props, live: Live) => Effect.Effect<unknown, E, GrafanaOpContext>;\n};\n\nexport const grafanaOperations = <Props extends object, Live, Attributes, E>(\n spec: GrafanaSpec<Props, Live, Attributes, E>,\n) => {\n const read = (props: Props) =>\n spec\n .fetchLive(props)\n .pipe(Effect.map((live) => (live === undefined ? undefined : spec.attributes(live, props))));\n\n return {\n read,\n\n diff: (news: Input<Props>, output: Attributes | undefined) =>\n Effect.gen(function* () {\n if (output === undefined || !isResolved(news)) return undefined;\n const live = yield* read(news);\n if (live === undefined) return { action: 'update' } as const;\n if (spec.matches(live, news)) return { action: 'noop' } as const;\n return spec.update === undefined\n ? ({ action: 'replace' } as const)\n : ({ action: 'update' } as const);\n }),\n\n /**\n * ★ OBSERVES BEFORE IT WRITES (S9, S10) — THE WHOLE OF \"ADOPT\" FOR THIS FAMILY. A `uid` this\n * Grafana instance already has is bound and, when it already matches the declaration,\n * left alone; only a genuinely absent `uid` is created. Declaring exactly what is live\n * changes nothing on `reconcile`, the same posture netbox/resource.ts documents as\n * \"adopt is the default posture, not a flag\".\n */\n reconcile: (news: Props) =>\n Effect.gen(function* () {\n const before = yield* spec.fetchLive(news);\n if (before === undefined) {\n yield* spec.create(news);\n } else if (spec.update !== undefined) {\n const mapped = spec.attributes(before, news);\n if (mapped === undefined || spec.matches(mapped, news)) {\n /* adoption or already converged — no write */\n } else {\n yield* spec.update(news, before);\n }\n }\n const after = yield* read(news);\n if (after === undefined) {\n return yield* Effect.die(\n new Error(\n 'grafanaOperations: the write returned no error but the object is still absent. ' +\n 'Read back rather than trusting the response status.',\n ),\n );\n }\n return after;\n }),\n\n destroy: (olds: Props) =>\n Effect.gen(function* () {\n const live = yield* spec.fetchLive(olds);\n if (live === undefined) return;\n yield* spec.destroy(olds, live);\n }),\n };\n};\n\n/**\n * The five provider handlers for a spec'd Grafana object, wired once. `Credentials` and\n * `HttpClient.HttpClient` stay ambient context requirements — see the file header.\n *\n * ⚠️ `list` answers empty — an org's full datasource list is not a page this family sweeps for\n * adoption; adoption stays explicit, the same reasoning forgejo's and netbox's engines give.\n */\nexport const grafanaHandlers = <Props extends object, Live, Attributes extends object, E>(\n spec: GrafanaSpec<Props, Live, Attributes, E>,\n) => {\n const ops = grafanaOperations(spec);\n return {\n list: () => Effect.succeed([]),\n read: ({ olds }: { olds: Props }) => ops.read(olds),\n diff: ({ news, output }: { news: Input<Props>; output: Attributes | undefined }) =>\n ops.diff(news, output),\n reconcile: ({ news }: { news: Props }) => ops.reconcile(news),\n delete: ({ olds }: { olds: Props }) => ops.destroy(olds),\n };\n};\n",
7
+ "/**\n * Shared \"secret stays in env, never in state\" resolution (S25) for this family —\n * `Grafana.Datasource`'s `secureJsonDataRefs` and `Grafana.ContactPoint`'s `secureSettingsRefs`\n * are the identical shape: a map of field name to an env var NAME, resolved fresh from\n * `process.env` inside the caller's own create/update effect (S24), never at plan time and never\n * returned in `attributes`. Extracted here once a second family needed the exact same loop\n * `datasource.ts` already had — see that file's original note for why this exists at all:\n * Grafana's own read APIs never return a secret value, only whether one is set\n * (`DataSource.secureJsonFields`'s booleans; a contact point's `settings` comes back with secure\n * keys replaced by a literal redaction placeholder instead — see contact-point.ts).\n */\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\n\n/** A named secure-field env var is unset — a domain refusal, not a distilled error. */\nexport class GrafanaSecretRefUnsetError extends Data.TaggedError('GrafanaSecretRefUnsetError')<{\n readonly message: string;\n}> {}\n\n/**\n * Reads every named env var fresh, inside the caller's effect (S24) — never at plan time.\n * `describeField` builds the refusal message's own field-specific phrase — each caller names its\n * declaration path differently (`secureJsonData.<field>` for a datasource,\n * `settings.<field>` for a contact point) — so the message stays accurate to what the caller\n * itself declares.\n */\nexport const resolveSecretRefs = (\n refs: Record<string, string> | undefined,\n describeField: (field: string) => string,\n): Effect.Effect<Record<string, string> | undefined, GrafanaSecretRefUnsetError> =>\n Effect.gen(function* () {\n if (refs === undefined) return undefined;\n const out: Record<string, string> = {};\n for (const [field, envVar] of Object.entries(refs)) {\n const raw = process.env[envVar];\n if (raw === undefined || raw.trim() === '') {\n return yield* Effect.fail(\n new GrafanaSecretRefUnsetError({\n message:\n `${envVar} is unset. Export the value of ${describeField(field)} there — ` +\n 'never as an Alchemy prop.',\n }),\n );\n }\n out[field] = raw.trim();\n }\n return out;\n });\n",
7
8
  "/**\n * `Grafana.Dashboard` — one Grafana dashboard, keyed by `uid`. Same uid-required doctrine as\n * `datasource.ts`/`folder.ts` (`getDashboard`/`postDashboard`/`deleteDashboard` are all UID-keyed\n * on the wire — `postDashboard`'s own `uid` travels INSIDE the `dashboard` JSON body, not as a\n * request field, because Grafana's classic dashboard-save API has never had one; see\n * `dashboard-model.ts`'s `normalizeModel`, which is what puts it there).\n *\n * ★ THE DECLARED CONTENT IS THE DASHBOARD JSON MODEL — `DashboardProps.dashboard`, the same shape\n * Grafana's own \"Export as JSON\" produces. `dashboard-model.ts`'s `normalizeModel` strips the\n * volatile fields (`id`, `version`, `iteration`) and forces `uid` to this resource's own value\n * before ANY comparison, so a plan against an unchanged dashboard is a true noop despite those\n * fields moving on every live save — see that file for the full reasoning, including which\n * fields (`panels`, `targets`) are array-ORDER-significant and compared as such.\n *\n * ⛔ `matches` COMPARES \"IS THE DECLARATION A SUBSET OF WHAT IS LIVE\", NOT PLAIN EQUALITY —\n * `dashboard-model.ts`'s `declaredContentMatches`, added after an adversarial review of this PR\n * found plain `deepEqual` made an ordinary dashboard show `update` FOREVER: Grafana's own\n * schema-migration on save decorates any panel/target whose `datasource` is absent with an\n * explicit reference, so a declaration that never sets one would permanently disagree with what\n * it reads back. See that function's own header for the full reasoning and the trade-off it\n * accepts (an omitted field can no longer be used to CLEAR a previously-set one).\n *\n * ⛔ VERSION CONFLICTS (412) FAIL LOUDLY — NEVER OVERWRITTEN BLINDLY. `update` injects the\n * `version` this resource just read (`live.meta.version`, from the SAME `fetchLive` call\n * `resource.ts`'s `reconcile` already made before calling `update`) into the model it sends, and\n * `overwrite` is never set. If Grafana's own version check then still fails — someone else saved\n * in the gap between that read and this write — `postDashboard` answers `412` and\n * `PreconditionFailed` is left IN the error union below, uncaught: the whole reconcile fails, the\n * deploy stops, and nothing is written. The next run's `fetchLive` re-reads the new live version\n * fresh, which is the \"re-read\" half of \"re-read and fail loudly\" — there is no separate retry\n * loop in this file, because the next `plan`/`deploy` invocation already IS that re-read.\n *\n * ⛔ A PROVISIONED DASHBOARD (`meta.provisioned` — see provisioned.ts) REFUSES EVERY UPDATE AND\n * DESTROY, naming `meta.provisionedExternalId` (the file) when Grafana reports one. `matches`\n * still compares structurally regardless, so a mismatched declaration against a provisioned\n * dashboard shows `update` in a plan rather than a silent, permanently-hidden noop.\n *\n * ⚠️ NO RETAIN DEFAULT, UNLIKE `Grafana.Folder`. Deleting one dashboard is a single-object blast\n * radius — the same class `Grafana.Datasource` already accepts without a retain guard — not\n * folder.ts's multi-object cascade (a folder's delete takes its dashboards AND their alert rules\n * with it). See folder.ts's file header for the full contrast.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Effect from 'effect/Effect';\nimport {\n type DashboardModel,\n declaredContentMatches,\n modelTitle,\n normalizeModel,\n} from './dashboard-model.ts';\nimport { type GrafanaProvisionedObjectError, refuseIfProvisioned } from './provisioned.ts';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\n\nexport type { DashboardModel };\n\nexport interface DashboardProps {\n uid: string;\n /** Omit for the General/root folder. */\n folderUid?: string;\n /** The dashboard JSON model — see the file header. `id`/`version`/`iteration`/`uid` inside it\n * are ignored; `uid` above is the single source of truth. */\n dashboard: DashboardModel;\n}\n\nexport interface DashboardAttributes {\n uid: string;\n title: string;\n version: number;\n folderUid: string;\n url: string;\n /** True when Grafana reports this dashboard as file-provisioned (read-only — see provisioned.ts). */\n provisioned: boolean;\n provisionedExternalId: string;\n /** The live model, normalized the same way `matches` compares it. */\n dashboard: DashboardModel;\n}\n\nexport interface GrafanaDashboard extends Resource<\n 'Grafana.Dashboard',\n DashboardProps,\n DashboardAttributes,\n never\n> {}\n\nexport const GrafanaDashboard = Resource<GrafanaDashboard>('Grafana.Dashboard');\n\nconst modelOf = (live: grafana.DashboardFullWithMeta): DashboardModel =>\n live.dashboard !== null && typeof live.dashboard === 'object'\n ? (live.dashboard as DashboardModel)\n : {};\n\nexport const spec: GrafanaSpec<\n DashboardProps,\n grafana.DashboardFullWithMeta,\n DashboardAttributes,\n | grafana.GetDashboardError\n | grafana.PostDashboardError\n | grafana.DeleteDashboardError\n | GrafanaProvisionedObjectError\n> = {\n attributes: (live, props) => {\n const model = normalizeModel(modelOf(live), props.uid);\n return {\n dashboard: model,\n folderUid: live.meta?.folderUid ?? '',\n provisioned: live.meta?.provisioned ?? false,\n provisionedExternalId: live.meta?.provisionedExternalId ?? '',\n title: modelTitle(model),\n uid: props.uid,\n url: live.meta?.url ?? '',\n version: live.meta?.version ?? 0,\n };\n },\n create: (props) =>\n grafana.postDashboard({\n dashboard: normalizeModel(props.dashboard, props.uid),\n ...(props.folderUid === undefined ? {} : { folderUid: props.folderUid }),\n }),\n destroy: (props, live) =>\n Effect.gen(function* () {\n if (live.meta?.provisioned) {\n return yield* refuseIfProvisioned(\n 'Grafana.Dashboard',\n props.uid,\n live.meta.provisionedExternalId,\n );\n }\n yield* grafana.deleteDashboard({ uid: props.uid });\n }),\n fetchLive: (props) =>\n grafana\n .getDashboard({ uid: props.uid })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) =>\n (props.folderUid === undefined || attributes.folderUid === props.folderUid) &&\n declaredContentMatches(normalizeModel(props.dashboard, props.uid), attributes.dashboard),\n update: (props, live) =>\n Effect.gen(function* () {\n if (live.meta?.provisioned) {\n return yield* refuseIfProvisioned(\n 'Grafana.Dashboard',\n props.uid,\n live.meta.provisionedExternalId,\n );\n }\n return yield* grafana.postDashboard({\n dashboard: {\n ...normalizeModel(props.dashboard, props.uid),\n ...(live.meta?.version === undefined ? {} : { version: live.meta.version }),\n },\n ...(props.folderUid === undefined ? {} : { folderUid: props.folderUid }),\n });\n }),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaDashboardProvider = () =>\n Provider.effect(GrafanaDashboard, Effect.succeed(GrafanaDashboard.Provider.of(handlers)));\n",
8
- "/**\n * Pure functions over a dashboard JSON model — no SDK call, no Effect. Split out of dashboard.ts\n * so that file stays under the house's 250-line cap, and so `normalizeModel`'s no-change-is-noop\n * behavior is exercised directly in dashboard-model.test.ts without a fake Grafana in the way.\n */\nexport type DashboardModel = Record<string, unknown>;\n\n/** Grafana injects or increments these on every save; a declared model never sets them meaningfully. */\nconst VOLATILE_FIELDS = ['id', 'version', 'iteration'] as const;\n\n/**\n * ★ NORMALIZES BOTH SIDES THE SAME WAY, so a no-change plan is a true noop\n * (docs/grafana-folder-dashboard.md). Strips `id`/`version`/`iteration` (Grafana-managed, never\n * declared content) and forces `uid` to this resource's OWN canonical value, wherever — or\n * whether — the source object carried one. That second part is \"uid placement\": a dashboard\n * pasted from Grafana's own \"Export as JSON\" always embeds the OLD live uid (or none, for a\n * never-saved dashboard), while the live model this resource reads back always carries the\n * CURRENT one; forcing both to `props.uid` before comparing is what makes those two shapes equal\n * instead of permanently disagreeing on one field neither side is meant to declare independently\n * (`DashboardProps.uid` — see dashboard.ts — is the single source of truth).\n *\n * ⚠️ EVERY OTHER DECLARED FIELD COMPARES STRUCTURALLY, ARRAY ORDER INCLUDED — dashboard.ts's\n * `matches` runs this output through THIS FILE's own `declaredContentMatches` (below), which\n * never reorders an array. That is correct here: `panels` is position-significant (screen layout\n * via its own `gridPos`, but the array order also drives Grafana's internal panel indexing and\n * any `repeat` behavior) and each panel's `targets` is execution/legend order. Nothing in a\n * dashboard model is a genuinely unordered SET the way UniFi's `trustedDhcpServerIpAddresses` is\n * (unifi/network-form.ts's `sortedSet`) — this family has no field that needs one.\n * ⚠️ `schemaVersion` IS DELIBERATELY **NOT** NORMALIZED — NOT MEASURED against a live instance (no\n * live calls in this PR). Community reports say Grafana can rewrite an old dashboard's\n * `schemaVersion` upward on load; IF that also happens on every plain save of an\n * already-current-schema dashboard, every plan against it would show a false `update` forever —\n * but silently normalizing it away on a guess would just as easily hide a genuine schema\n * mismatch. Left comparing until measured: add it to `VOLATILE_FIELDS` with a dated, measured\n * comment the day it is seen live, the same standard every other row in this file meets.\n */\nexport const normalizeModel = (model: DashboardModel, uid: string): DashboardModel => {\n const normalized: DashboardModel = {};\n for (const [key, value] of Object.entries(model)) {\n if (key === 'uid') continue;\n if ((VOLATILE_FIELDS as readonly string[]).includes(key)) continue;\n normalized[key] = value;\n }\n normalized.uid = uid;\n return normalized;\n};\n\n/** The model's own declared title — reporting only. It is never compared on its own; the whole\n * normalized model is, in dashboard.ts's `matches`, so a title-only mismatch still shows `update`. */\nexport const modelTitle = (model: DashboardModel): string =>\n typeof model.title === 'string' ? model.title : '';\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * ★ `declared` NEED ONLY BE A SUBSET OF `live` — an adversarial review of this PR (2026-09-24)\n * found that plain structural equality made an ordinary, never-edited dashboard show `update`\n * FOREVER: Grafana's own dashboard-save schema migration rewrites any panel or target whose\n * `datasource` is absent into an explicit `{type, uid}` reference to the resolved default\n * datasource — documented Grafana schema-migration behavior (not re-measured against a live\n * instance in this PR — no live calls). A declared panel that never sets `datasource` (the\n * common case: hand-authored JSON, or anything relying on the dashboard-level default) would,\n * after the FIRST create, read back with that field populated; plain `deepEqual` (this file used\n * `alchemy/Diff`'s before this fix) then disagreed with the declaration on every subsequent plan,\n * `update` sent the undecorated model back, and Grafana redecorated it again on the next read —\n * non-convergent.\n *\n * This function instead asks only \"does every field the DECLARATION mentions, at any depth, still\n * match what is live\" — `live` may carry additional fields, at any depth, that `declared` never\n * mentioned (the datasource auto-decoration above, or any future Grafana-injected default,\n * `fieldConfig` defaults and `pluginVersion` included) without being compared at all. A field\n * `declared` DOES mention that is missing, or different, in `live` still fails the match — real\n * content drift is still caught exactly as before; only fields the declaration is silent about are\n * now tolerated rather than compared.\n *\n * ⚠️ THE TRADE-OFF THIS ACCEPTS, DELIBERATELY: removing a field from the declaration (rather than\n * changing its value) can no longer force Grafana to drop it. If a panel once declared an\n * explicit `datasource` and a later declaration deletes that key entirely (rather than setting a\n * different one), `live` keeps whatever the last write set — the declaration is silent about that\n * key now, so nothing compares it, so no `update` ever fires to change it. This is the same\n * \"undeclared means don't care, never means force-to-default\" convention already used everywhere\n * else in this kit for a plain optional prop (e.g. `datasource.ts`'s `matches` never treats an\n * undeclared `access` as \"must be unset\"); it now also applies inside the JSON model, not just at\n * the props level. Declare an explicit replacement value to change a field; omission never clears\n * one that was set by a previous declaration or by Grafana itself.\n *\n * ⚠️ ARRAYS COMPARE BY LENGTH, THEN ELEMENT-WISE SUBSET — never by a looser \"declared has fewer\n * items\" rule. `panels`/`targets` are position-significant (see `normalizeModel`'s header); an\n * array Grafana pads or reorders on its own would still need normalizing here explicitly, the\n * same as any other newly-discovered auto-decoration — this function does not paper over that.\n * ⚠️ `null` ON EITHER SIDE OF A LEAF NEVER MISMATCHES (mirrors `alchemy/Diff`'s `deepEqual` with\n * `stripNullish: true`, which this function replaces for the dashboard model specifically).\n */\nexport const declaredContentMatches = (declared: unknown, live: unknown): boolean => {\n if (declared == null) return true;\n if (Array.isArray(declared)) {\n return (\n Array.isArray(live) &&\n declared.length === live.length &&\n declared.every((item, index) => declaredContentMatches(item, live[index]))\n );\n }\n if (isPlainObject(declared)) {\n return (\n isPlainObject(live) &&\n Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]))\n );\n }\n return declared === live;\n};\n",
9
+ "/**\n * `declaredContentMatches` — \"is the declaration a subset of what is live\", not plain equality.\n * Extracted out of `dashboard-model.ts` (where it was introduced, after an adversarial review of\n * that PR found plain structural equality made an ordinary, never-edited dashboard show `update`\n * FOREVER) once a second shape needed the identical rule: `Grafana.ContactPoint`'s `settings` is\n * the same story — Grafana can inject default fields into a contact point's settings that the\n * declaration never mentioned, and `Grafana.AlertRuleGroup`'s per-rule `data`/`annotations`/\n * `labels` maps carry the same risk. Nothing about the reasoning below is dashboard-specific;\n * `dashboard-model.ts` now just re-exports this.\n *\n * ★ `declared` NEED ONLY BE A SUBSET OF `live`. Grafana's own save/decode path can decorate an\n * object with fields the declaration never set (a dashboard panel's auto-resolved `datasource`\n * reference is the documented example dashboard-model.ts found — Grafana's schema migration\n * decorates any panel/target whose `datasource` is absent with an explicit `{type, uid}`\n * reference on save). This function instead asks only \"does every field the DECLARATION\n * mentions, at any depth, still match what is live\" — `live` may carry additional fields, at any\n * depth, that `declared` never mentioned, without being compared at all. A field `declared` DOES\n * mention that is missing, or different, in `live` still fails the match — real content drift is\n * still caught exactly as before; only fields the declaration is silent about are now tolerated.\n *\n * ⚠️ THE TRADE-OFF THIS ACCEPTS, DELIBERATELY: removing a field from the declaration (rather than\n * changing its value) can no longer force Grafana to drop it. If a previous declaration set a\n * field and a later one deletes that key entirely, `live` keeps whatever the last write set — the\n * declaration is silent about that key now, so nothing compares it, so no `update` ever fires to\n * change it. This is the same \"undeclared means don't care, never means force-to-default\"\n * convention already used everywhere else in this kit for a plain optional prop; it now also\n * applies inside a nested JSON value, not just at the top-level props. Declare an explicit\n * replacement value to change a field; omission never clears one that was set by a previous\n * declaration or by Grafana itself.\n *\n * ⚠️ ARRAYS COMPARE BY LENGTH, THEN ELEMENT-WISE SUBSET — never a looser \"declared has fewer\n * items\" rule. A caller whose array IS order-significant (dashboard `panels`/`targets`) or\n * position-keyed (an alert rule group's `rules`) gets that for free; a caller with a genuinely\n * unordered array would need its own normalization first (this file has no `sortedSet`, and\n * nothing in this kit's Grafana family needs one — see dashboard-model.ts's own note).\n *\n * ⛔ NOT RE-MEASURED FOR EVERY CALLER — flagged by an adversarial review of this PR. The exact\n * length rule is confirmed for dashboards (the original, documented case), but whether Grafana\n * ever PADS or APPENDS a default entry to an array the way it decorates an object field (the\n * `datasource` auto-decoration above) is unverified for `Grafana.MuteTiming`'s `time_intervals`\n * specifically — no live mute timing exists in this house to observe (grafana.md's census), and\n * `mute-timing.test.ts`'s own fixtures are hand-authored, not captured. If a future live\n * observation finds Grafana appending an entry (e.g. a normalized default time range) to a\n * declared `time_intervals` array, the length check here would show a permanent false `update` —\n * the exact regression this whole file exists to prevent for OBJECT fields already. `mute-timing.\n * test.ts`'s \"array length is exact, not tolerant\" test pins today's behavior so that fix has an\n * obvious, single place to change (relax this rule, or give the mute-timing caller its own\n * length-tolerant wrapper) rather than being rediscovered from scratch.\n * ⚠️ `null` ON EITHER SIDE OF A LEAF NEVER MISMATCHES (mirrors `alchemy/Diff`'s `deepEqual` with\n * `stripNullish: true`, which this function replaces wherever a caller needs subset semantics).\n */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nexport const declaredContentMatches = (declared: unknown, live: unknown): boolean => {\n if (declared == null) return true;\n if (Array.isArray(declared)) {\n return (\n Array.isArray(live) &&\n declared.length === live.length &&\n declared.every((item, index) => declaredContentMatches(item, live[index]))\n );\n }\n if (isPlainObject(declared)) {\n return (\n isPlainObject(live) &&\n Object.entries(declared).every(([key, value]) => declaredContentMatches(value, live[key]))\n );\n }\n return declared === live;\n};\n",
10
+ "/**\n * Pure functions over a dashboard JSON model — no SDK call, no Effect. Split out of dashboard.ts\n * so that file stays under the house's 250-line cap, and so `normalizeModel`'s no-change-is-noop\n * behavior is exercised directly in dashboard-model.test.ts without a fake Grafana in the way.\n */\nexport type DashboardModel = Record<string, unknown>;\n\n/** Grafana injects or increments these on every save; a declared model never sets them meaningfully. */\nconst VOLATILE_FIELDS = ['id', 'version', 'iteration'] as const;\n\n/**\n * ★ NORMALIZES BOTH SIDES THE SAME WAY, so a no-change plan is a true noop\n * (docs/grafana-folder-dashboard.md). Strips `id`/`version`/`iteration` (Grafana-managed, never\n * declared content) and forces `uid` to this resource's OWN canonical value, wherever — or\n * whether — the source object carried one. That second part is \"uid placement\": a dashboard\n * pasted from Grafana's own \"Export as JSON\" always embeds the OLD live uid (or none, for a\n * never-saved dashboard), while the live model this resource reads back always carries the\n * CURRENT one; forcing both to `props.uid` before comparing is what makes those two shapes equal\n * instead of permanently disagreeing on one field neither side is meant to declare independently\n * (`DashboardProps.uid` — see dashboard.ts — is the single source of truth).\n *\n * ⚠️ EVERY OTHER DECLARED FIELD COMPARES STRUCTURALLY, ARRAY ORDER INCLUDED — dashboard.ts's\n * `matches` runs this output through THIS FILE's own `declaredContentMatches` (below), which\n * never reorders an array. That is correct here: `panels` is position-significant (screen layout\n * via its own `gridPos`, but the array order also drives Grafana's internal panel indexing and\n * any `repeat` behavior) and each panel's `targets` is execution/legend order. Nothing in a\n * dashboard model is a genuinely unordered SET the way UniFi's `trustedDhcpServerIpAddresses` is\n * (unifi/network-form.ts's `sortedSet`) — this family has no field that needs one.\n * ⚠️ `schemaVersion` IS DELIBERATELY **NOT** NORMALIZED — NOT MEASURED against a live instance (no\n * live calls in this PR). Community reports say Grafana can rewrite an old dashboard's\n * `schemaVersion` upward on load; IF that also happens on every plain save of an\n * already-current-schema dashboard, every plan against it would show a false `update` forever —\n * but silently normalizing it away on a guess would just as easily hide a genuine schema\n * mismatch. Left comparing until measured: add it to `VOLATILE_FIELDS` with a dated, measured\n * comment the day it is seen live, the same standard every other row in this file meets.\n */\nexport const normalizeModel = (model: DashboardModel, uid: string): DashboardModel => {\n const normalized: DashboardModel = {};\n for (const [key, value] of Object.entries(model)) {\n if (key === 'uid') continue;\n if ((VOLATILE_FIELDS as readonly string[]).includes(key)) continue;\n normalized[key] = value;\n }\n normalized.uid = uid;\n return normalized;\n};\n\n/** The model's own declared title — reporting only. It is never compared on its own; the whole\n * normalized model is, in dashboard.ts's `matches`, so a title-only mismatch still shows `update`. */\nexport const modelTitle = (model: DashboardModel): string =>\n typeof model.title === 'string' ? model.title : '';\n\n/**\n * `declared` need only be a subset of `live` — dashboard.ts's `matches` runs `normalizeModel`'s\n * output through this. Moved to `subset-match.ts` once `Grafana.ContactPoint`'s `settings` needed\n * the identical \"declaration silent about a field means don't compare it\" rule; re-exported here\n * under its original name so this file's own callers and dashboard-model.test.ts are unchanged.\n * See subset-match.ts for the full reasoning, including the regression this exists to fix\n * (Grafana's dashboard-save schema migration auto-decorating a panel's `datasource`) and the\n * trade-off it accepts (an omitted field can no longer force-clear a previously-set one).\n */\nexport { declaredContentMatches } from './subset-match.ts';\n",
9
11
  "/**\n * The shared refusal for a file-provisioned Grafana object — `Grafana.Folder` and\n * `Grafana.Dashboard` both carry a live signal Grafana itself treats as read-only through the\n * classic write API this family calls: `Folder.managedBy` (non-empty when something other than\n * this API manages the folder — provisioning is the case this house cares about, but the field\n * itself is Grafana's general \"who owns this\" marker) and `DashboardMeta.provisioned` (paired\n * with `provisionedExternalId`, the file path). A declaration must never attempt a write against\n * either — see docs/grafana-folder-dashboard.md's \"provisioned objects\" section.\n *\n * ⚠️ THE EXACT `managedBy` VALUES ARE INFERRED FROM THE SDK's SCHEMA, NOT MEASURED. No live\n * instance in this house currently has a file-provisioned FOLDER to read (docs/grafana.md:\n * `teslamate-grafana` has none at all; the mini's Grafana provisions dashboards, not folders) and\n * this PR makes no live calls. `Folder.managedBy` and `DashboardMeta.provisioned` are exactly\n * the fields the OpenAPI schema types for this purpose — treat any non-empty `managedBy` as\n * provisioned rather than special-casing specific string values that were never observed live.\n *\n * ⛔ ADOPTING A PROVISIONED OBJECT IS STILL FINE. `read`/`reconcile`'s no-write \"already converged\"\n * path (resource.ts) never calls `create`/`update`/`destroy` at all when the declaration already\n * matches what is live — only an actual attempted WRITE against a provisioned object is refused,\n * by the two callers of this function inside folder.ts/dashboard.ts's own `update`/`destroy`.\n */\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\n\nexport class GrafanaProvisionedObjectError extends Data.TaggedError(\n 'GrafanaProvisionedObjectError',\n)<{\n readonly message: string;\n}> {}\n\nexport const refuseIfProvisioned = (\n kind: 'Grafana.Folder' | 'Grafana.Dashboard',\n uid: string,\n owner: string | undefined,\n): Effect.Effect<never, GrafanaProvisionedObjectError> => {\n const ownerClause =\n owner === undefined || owner === ''\n ? `(this Grafana instance did not report an owning file)`\n : `by ${owner}`;\n return Effect.fail(\n new GrafanaProvisionedObjectError({\n message:\n `${kind} '${uid}' is file-provisioned ${ownerClause} and read-only through the classic ` +\n `write API this family calls. Edit the provisioning file instead — declaring this ` +\n `object read-only (a declaration matching what is live) is safe and will never reach ` +\n `this refusal.`,\n }),\n );\n};\n",
10
12
  "/**\n * `Grafana.Folder` — one Grafana folder, keyed by `uid`. Mirrors `datasource.ts`'s shape and\n * doctrine (uid required, not generated — same reasoning, `getFolderByUID`/`updateFolder`/\n * `deleteFolder` are all UID-keyed and `getFolders` (list) has no per-uid filter).\n *\n * ⛔ `description` IS WRITE-ONLY — MEASURED against `@distilled.cloud/grafana`'s generated types\n * (`Folder` in services/grafana.ts has no `description` field at all, though\n * `CreateFolderRequest`/`UpdateFolderRequest` both accept one). Grafana's classic folder GET\n * never returns it, not even as an is-it-set flag the way `DataSource.secureJsonFields` reports\n * for a datasource's secure fields. So `description` is sent on every create/update that\n * declares it but is NEVER part of `matches` — this resource cannot detect drift in a value it\n * is never shown, the same treatment `datasource.ts` gives `secureJsonDataRefs`.\n *\n * ⚠️ NESTING (`parentUid`) IS CREATE-ONLY — MEASURED against the same generated types.\n * `CreateFolderRequest.parentUid` exists; `UpdateFolderRequest` has no `parentUid` field at all\n * (`folder_uid`, `description`, `overwrite`, `title`, `version` — reparenting is simply not a\n * thing `PUT /folders/{uid}` can do). A declaration whose `parentUid` no longer matches the live\n * folder therefore REFUSES with `GrafanaFolderReparentError` rather than either silently doing\n * nothing (which would lie about the declaration taking effect) or deleting and recreating the\n * folder under the new parent (which — see the ⛔ below — takes every dashboard and alert rule in\n * it with it). Move a folder by hand in Grafana, then adjust the declaration to match.\n *\n * ⛔ DELETE DEFAULTS TO `retain` — DELETING A FOLDER DELETES ITS DASHBOARDS AND ALERT RULES IN\n * GRAFANA, per `deleteFolder`'s own operation description (\"along with all dashboards (and their\n * alerts) stored in the folder... cannot be reverted... also deletes all the subfolders\"). That\n * is the same class of multi-object cascade `openbao/mount.ts` guards with\n * `defaultRemovalPolicy: 'retain'` (\"disabling a mount destroys every secret under it\") — VERIFIED\n * there against Alchemy's own `Apply.ts` (not re-verified here; same engine, same option). A\n * stack that explicitly wants the cascade opts in with `.pipe(RemovalPolicy.destroy())` on the\n * declared instance; `destroy` below still implements the real DELETE in full either way (S11).\n * `Grafana.Dashboard` does NOT default to retain — deleting one dashboard is the same\n * single-object blast radius `Grafana.Datasource` already accepts without a retain guard, not\n * this family's multi-object cascade.\n *\n * ⛔ A PROVISIONED FOLDER (`managedBy` set — see provisioned.ts) REFUSES EVERY UPDATE AND DESTROY.\n * `matches` still compares structurally either way, so a mismatched declaration against a\n * provisioned folder shows `update` in a plan (never a silent, permanently-hidden noop) and then\n * fails loudly, by design, when reconcile actually tries to write.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\nimport { type GrafanaProvisionedObjectError, refuseIfProvisioned } from './provisioned.ts';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\n\nexport interface FolderProps {\n uid: string;\n title: string;\n /** Write-only — see the file header. Sent on every create/update that declares it; never diffed. */\n description?: string;\n /** Parent folder UID, for nested folders. Create-only — see the file header. */\n parentUid?: string;\n}\n\nexport interface FolderAttributes {\n uid: string;\n id: number;\n title: string;\n parentUid: string;\n /** Non-empty when Grafana reports this folder as externally managed (e.g. provisioning). */\n managedBy: string;\n url: string;\n version: number;\n}\n\nexport interface GrafanaFolder extends Resource<\n 'Grafana.Folder',\n FolderProps,\n FolderAttributes,\n never\n> {}\n\nexport const GrafanaFolder = Resource<GrafanaFolder>('Grafana.Folder', {\n defaultRemovalPolicy: 'retain',\n});\n\n/** A declared `parentUid` disagrees with the live folder's — `updateFolder` cannot reparent. */\nexport class GrafanaFolderReparentError extends Data.TaggedError('GrafanaFolderReparentError')<{\n readonly message: string;\n}> {}\n\nexport const spec: GrafanaSpec<\n FolderProps,\n grafana.Folder,\n FolderAttributes,\n | grafana.CreateFolderError\n | grafana.GetFolderByUIDError\n | grafana.UpdateFolderError\n | grafana.DeleteFolderError\n | GrafanaProvisionedObjectError\n | GrafanaFolderReparentError\n> = {\n attributes: (live) => ({\n id: live.id ?? 0,\n managedBy: live.managedBy ?? '',\n parentUid: live.parentUid ?? '',\n title: live.title ?? '',\n uid: live.uid ?? '',\n url: live.url ?? '',\n version: live.version ?? 0,\n }),\n create: (props) =>\n grafana.createFolder({\n uid: props.uid,\n title: props.title,\n ...(props.description === undefined ? {} : { description: props.description }),\n ...(props.parentUid === undefined ? {} : { parentUid: props.parentUid }),\n }),\n destroy: (props, live) =>\n Effect.gen(function* () {\n if (live.managedBy) {\n return yield* refuseIfProvisioned('Grafana.Folder', props.uid, live.managedBy);\n }\n yield* grafana.deleteFolder({ folder_uid: props.uid });\n }),\n fetchLive: (props) =>\n grafana\n .getFolderByUID({ folder_uid: props.uid })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) =>\n attributes.title === props.title &&\n (props.parentUid === undefined || attributes.parentUid === props.parentUid),\n update: (props, live) =>\n Effect.gen(function* () {\n if (live.managedBy) {\n return yield* refuseIfProvisioned('Grafana.Folder', props.uid, live.managedBy);\n }\n if (props.parentUid !== undefined && props.parentUid !== (live.parentUid ?? undefined)) {\n return yield* Effect.fail(\n new GrafanaFolderReparentError({\n message:\n `Grafana.Folder '${props.uid}' declares parentUid '${props.parentUid}' but is ` +\n `live under '${live.parentUid ?? '(root)'}'. PUT /folders/{uid} cannot reparent a ` +\n `folder — move it by hand in Grafana, then update the declaration to match.`,\n }),\n );\n }\n // ⚠️ UNLIKE DASHBOARD.TS's 412, THIS HAS NO VERIFIED CONFLICT SIGNAL. `version` is sent\n // optimistically, but the SDK's own doc comment on `UpdateFolderRequest.version` says\n // \"only used by the legacy folder implementation\" — on Grafana's newer unified-storage\n // folder backend it is plausibly ignored outright, meaning two concurrent title/description\n // writes could silently last-write-win with no error, unlike a dashboard save. NOT\n // MEASURED against a live instance either way (no live calls in this PR; flagged by an\n // adversarial review) — sending it is harmless (a no-op field on a backend that ignores it)\n // and correct on one that still honors it, so it stays, but do not read its PRESENCE here\n // as proof this resource detects folder update conflicts the way dashboard.ts does.\n return yield* grafana.updateFolder({\n folder_uid: props.uid,\n title: props.title,\n ...(props.description === undefined ? {} : { description: props.description }),\n ...(live.version === undefined ? {} : { version: live.version }),\n });\n }),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaFolderProvider = () =>\n Provider.effect(GrafanaFolder, Effect.succeed(GrafanaFolder.Provider.of(handlers)));\n",
13
+ "/**\n * `Grafana.ContactPoint` — one Grafana alerting contact point, keyed by `uid`.\n *\n * ⚠️ NO GET-BY-UID ROUTE EXISTS — MEASURED against the SDK's generated operations (grep across\n * `services/grafana.ts` for `routeGet.*[Cc]ontactpoint`: only `routeGetContactpoints` (list,\n * with an optional `name` filter) and `routeGetContactpointsExport`). Unlike\n * `Grafana.Datasource`/`Grafana.Folder`/`Grafana.Dashboard`, `fetchLive` cannot key its read\n * directly on `uid`. Filtering server-side by `name` is NOT a substitute: `name` is a grouping\n * label a declaration can rename (`uid` is the only field this resource keys on, and Grafana's\n * own doc calls `name` \"used as grouping key in the UI\" — several contact points can share one),\n * so narrowing the GET to the DECLARED name would miss a live object still filed under an OLDER\n * name mid-rename, reporting it absent and attempting a duplicate `create`. `fetchLive` instead\n * lists every contact point (`routeGetContactpoints({})`, no filter) and matches `uid`\n * client-side — the same ambiguity `resource.ts`'s header and netbox's `soleMatch` name, resolved\n * the same way: requiring the one field the API is actually keyed on.\n *\n * ⛔ SECRET SETTINGS NEVER APPEAR AS A LITERAL PROP (S25) — same shape as `Grafana.Datasource`'s\n * `secureJsonDataRefs` (`secret-refs.ts`, shared). Grafana does not use a separate top-level\n * \"secure settings\" field for contact points the way it does for datasources; a secure key (a\n * webhook URL with an embedded token, an API key, …) lives INSIDE `settings` on write, and comes\n * back inside `settings` REDACTED on read — Grafana's own docs for the contact-points EXPORT\n * route say as much explicitly (\"Redacted settings will contain RedactedValue instead\"; the\n * plain, non-export GET this resource calls has no `decrypt` option at all, so it is redacted\n * unconditionally). `secureSettingsRefs` therefore names which keys of `settings` are secret —\n * value resolved from an env var NAME fresh inside `create`/`update`'s own effect (S24), merged\n * into `settings` only at that point, never stored and never diffed (see `matches` below).\n *\n * ⛔ A FOREIGN-PROVENANCE CONTACT POINT REFUSES EVERY UPDATE AND DESTROY — `alerting-provenance.ts`,\n * shared with `Grafana.MessageTemplate`/`Grafana.AlertRuleGroup`/`Grafana.NotificationPolicy`.\n * See that file's header for the full reasoning and citations. `create` is never at risk: it only\n * runs when `fetchLive` already found nothing with this `uid`.\n *\n * ⚠️ `X-Disable-Provenance` IS NEVER SET, BY DESIGN. Every create/update through this resource\n * therefore leaves the contact point with `provenance: \"api\"` (Grafana's own default for an\n * object this API just wrote) — which the docs describe as blocking that same object from being\n * edited in the Grafana UI afterward. That is the intended outcome, not an oversight: an object\n * this family declares is meant to be owned by the declaration, the same \"one owner per resource\"\n * posture `provisioned.ts` establishes for folders/dashboards — handing it back to UI-editability\n * by default would silently create the two-owner situation this whole file exists to prevent.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Effect from 'effect/Effect';\nimport {\n type GrafanaProvisionedObjectError,\n isForeignProvenance,\n refuseIfForeignProvenance,\n} from './alerting-provenance.ts';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\nimport { GrafanaSecretRefUnsetError, resolveSecretRefs } from './secret-refs.ts';\nimport { declaredContentMatches } from './subset-match.ts';\n\nexport { GrafanaSecretRefUnsetError };\n\nexport interface ContactPointProps {\n uid: string;\n name: string;\n /** Grafana integration id — `slack`, `webhook`, `email`, `pagerduty`, `opsgenie`, … */\n type: string;\n /** Non-secret settings — shape depends on `type`. Never put a secret VALUE here — declare it\n * in `secureSettingsRefs` instead; see the file header. */\n settings: Record<string, unknown>;\n /** Secure field name (e.g. `url`, `token`, `password`) -> env var NAME holding its value. */\n secureSettingsRefs?: Record<string, string>;\n disableResolveMessage?: boolean;\n}\n\nexport interface ContactPointAttributes {\n uid: string;\n name: string;\n type: string;\n /** As Grafana returns it — any secure key is a redaction placeholder, never the real value. */\n settings: Record<string, unknown>;\n disableResolveMessage: boolean;\n /** `\"\"`/`\"api\"` (this family may write); anything else, INCLUDING `undefined` (Grafana didn't\n * report the field), refuses — see alerting-provenance.ts's \"fails open\" note. */\n provenance: string | undefined;\n}\n\nexport interface GrafanaContactPoint extends Resource<\n 'Grafana.ContactPoint',\n ContactPointProps,\n ContactPointAttributes,\n never\n> {}\n\nexport const GrafanaContactPoint = Resource<GrafanaContactPoint>('Grafana.ContactPoint');\n\n/** `settings` minus every key `secureSettingsRefs` names — the only part `matches` ever compares\n * (a secure key's live value is always a redaction placeholder, never comparable). */\nconst nonSecretSettings = (props: ContactPointProps): Record<string, unknown> => {\n const refs = props.secureSettingsRefs;\n if (refs === undefined) return props.settings;\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(props.settings)) {\n if (!(key in refs)) out[key] = value;\n }\n return out;\n};\n\nconst resolveSettings = (props: ContactPointProps) =>\n Effect.map(\n resolveSecretRefs(props.secureSettingsRefs, (field) => `settings.${field}`),\n (secrets) => (secrets === undefined ? props.settings : { ...props.settings, ...secrets }),\n );\n\nexport const spec: GrafanaSpec<\n ContactPointProps,\n grafana.EmbeddedContactPoint,\n ContactPointAttributes,\n | grafana.RouteGetContactpointsError\n | grafana.RoutePostContactpointsError\n | grafana.RoutePutContactpointError\n | grafana.RouteDeleteContactpointsError\n | GrafanaProvisionedObjectError\n | GrafanaSecretRefUnsetError\n> = {\n attributes: (live) => ({\n disableResolveMessage: live.disableResolveMessage ?? false,\n name: live.name ?? '',\n // ⛔ NEVER `?? ''` — an absent field must stay distinguishable from an explicit `\"\"`\n // (ProvenanceNone); see alerting-provenance.ts's \"fails open\" note.\n provenance: live.provenance,\n settings: (live.settings ?? {}) as Record<string, unknown>,\n type: live.type,\n uid: live.uid ?? '',\n }),\n create: (props) =>\n Effect.flatMap(resolveSettings(props), (settings) =>\n grafana.routePostContactpoints({\n name: props.name,\n settings,\n type: props.type,\n uid: props.uid,\n ...(props.disableResolveMessage === undefined\n ? {}\n : { disableResolveMessage: props.disableResolveMessage }),\n }),\n ),\n destroy: (props, live) =>\n Effect.gen(function* () {\n if (isForeignProvenance(live.provenance)) {\n return yield* refuseIfForeignProvenance('Grafana.ContactPoint', props.uid, live.provenance);\n }\n yield* grafana.routeDeleteContactpoints({ UID: props.uid });\n }),\n fetchLive: (props) =>\n grafana\n .routeGetContactpoints({})\n .pipe(Effect.map((points) => points.find((point) => point.uid === props.uid))),\n matches: (attributes, props) =>\n attributes.name === props.name &&\n attributes.type === props.type &&\n attributes.disableResolveMessage === (props.disableResolveMessage ?? false) &&\n declaredContentMatches(nonSecretSettings(props), attributes.settings),\n update: (props, live) =>\n Effect.gen(function* () {\n if (isForeignProvenance(live.provenance)) {\n return yield* refuseIfForeignProvenance('Grafana.ContactPoint', props.uid, live.provenance);\n }\n const settings = yield* resolveSettings(props);\n yield* grafana.routePutContactpoint({\n UID: props.uid,\n name: props.name,\n settings,\n type: props.type,\n uid: props.uid,\n ...(props.disableResolveMessage === undefined\n ? {}\n : { disableResolveMessage: props.disableResolveMessage }),\n });\n }),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaContactPointProvider = () =>\n Provider.effect(GrafanaContactPoint, Effect.succeed(GrafanaContactPoint.Provider.of(handlers)));\n",
14
+ "/**\n * The shared refusal for a foreign-provenance ALERTING-PROVISIONING object — `Grafana.ContactPoint`,\n * `Grafana.MessageTemplate`, `Grafana.AlertRuleGroup` and `Grafana.NotificationPolicy` each read a\n * `provenance` string off what Grafana returns (`EmbeddedContactPoint.provenance`,\n * `NotificationTemplate.provenance`, `ProvisionedAlertRule.provenance` per rule, `Route.provenance`\n * on the policy tree — all measured against `@distilled.cloud/grafana`'s generated types,\n * `packages/distilled-grafana/src/services/grafana.ts`) and refuse a write when it disagrees with\n * what this family is allowed to own. Sibling to `provisioned.ts`, not a rename of it — the two\n * refusals guard different fields with different value shapes (`managedBy`/`provisioned` there,\n * `provenance` here) for two different Grafana API surfaces (classic folder/dashboard vs. alerting\n * provisioning), but both end in the SAME tagged error, reused from `provisioned.ts` rather than\n * declared twice: \"a write was refused because something else owns this object\" is one condition\n * across the whole family, the spirit PR 245's provisioned refusal already established.\n *\n * ★ THE ALLOWLIST, MEASURED AGAINST GRAFANA'S OWN SOURCE (not this SDK, which only echoes the\n * value back on read — never re-fetched live in this PR): Grafana's `pkg/services/ngalert/\n * models` package defines a `Provenance` type with `ProvenanceNone = \"\"` (nothing has claimed\n * the object — fresh, or written through the classic non-provisioning path), `ProvenanceAPI =\n * \"api\"` (created or last written through THIS provisioning API — what this family's own\n * create/update leaves behind), `ProvenanceFile = \"file\"` (Grafana's file-based provisioning),\n * and `ProvenanceConvertedPrometheus = \"converted_prometheus\"` (the Prometheus-rules importer).\n * Corroborated by Grafana's own docs (grafana.com/docs/grafana/v13.1/alerting/set-up/\n * provision-alerting-resources/http-api-provisioning/), whose example payloads show\n * `\"provenance\": \"api\"` on an alert rule this API created and `\"provenance\": \"file\"` on a\n * file-provisioned mute timing and template.\n *\n * `WRITABLE_PROVENANCES` is therefore an ALLOWLIST of `{\"\", \"api\"}`, not a denylist of the two\n * named foreign values — a provenance source not yet named above (a plugin importer, a future\n * Grafana release) refuses by default instead of silently being treated as writable, the safer\n * failure direction for a control-plane write.\n *\n * ⛔ A MISSING `provenance` FIELD IS TREATED AS FOREIGN, NEVER AS `ProvenanceNone` — fixed after an\n * adversarial review of this PR found the first version failed OPEN: every resource mapped\n * `live.provenance ?? ''`, and `''` (`ProvenanceNone`, writable) is indistinguishable from a\n * field the response simply never included once both collapse through `??`. Every `provenance`\n * field in this family is optional on the wire (`EmbeddedContactPoint.provenance?: string`, etc.\n * — genuinely absent is a real, typed possibility, not a hypothetical), and `Grafana.MuteTiming`'s\n * depends on an unmodeled-key passthrough this family has never observed against a real Grafana\n * response (see its own file header) — a version, edge case or bug in that decode path that drops\n * the field would, under the old `?? ''` rule, silently make a file-provisioned object look\n * writable. `isForeignProvenance`/`refuseIfForeignProvenance` below keep `undefined` as its own\n * value all the way through: ONLY a field Grafana explicitly reported as `\"\"` or `\"api\"` is\n * writable; missing entirely refuses, with a message that says so rather than naming a fabricated\n * provenance value.\n *\n * ⚠️ CLIENT-SIDE DEFENSE, NOT THE ONLY GUARD — AND NOT RE-MEASURED LIVE IN THIS PR. Grafana's own\n * alerting provisioning API server ALSO enforces this (unlike the classic folder/dashboard write\n * API `provisioned.ts` guards, which has no such server-side check at all): grafana/grafana#103597\n * (\"Unable to delete file provisioned alert rules\") reports exactly this block firing for a\n * `file`-provenance object, and the `X-Disable-Provenance` header's own documented purpose is \"by\n * default, you cannot edit API-provisioned alerting resources in Grafana\" (same doc page). This\n * function exists anyway, for the same reason `provisioned.ts` does: a clear, typed, house error\n * instead of whatever 400-with-a-string-message Grafana's server happens to return this version —\n * and as a defense against the known server-side bug that same issue documents\n * (`X-Disable-Provenance` failing to actually unlock a `file`-provenance object), which could\n * otherwise leave a write silently rejected with only a generic `BadRequest` and no explanation\n * of why.\n *\n * ⚠️ `Grafana.MuteTiming` ALSO USES THIS FUNCTION, BUT THROUGH A WIDENED LIVE TYPE — the SDK's\n * generated `MuteTimeInterval` interface (the TS type for both `GET .../mute-timings` and\n * `GET .../mute-timings/{name}`) declares no `provenance` field at all, unlike every other type\n * named above. A first pass at this family assumed that meant the DATA was gone; it is not —\n * MEASURED (see mute-timing.ts's own file header for the full account): `@distilled.cloud/core`'s\n * response decoding never runs a strict schema decode, only `JSON.parse` + a key-rename pass that\n * passes any key a type doesn't model through verbatim, so `provenance` survives on the actual\n * runtime value even though the TS type doesn't know about it. `mute-timing.ts` reads it through\n * a small local `& { provenance?: string }` intersection type rather than the SDK's own.\n */\nimport * as Effect from 'effect/Effect';\nimport { GrafanaProvisionedObjectError } from './provisioned.ts';\n\nexport { GrafanaProvisionedObjectError };\n\nconst WRITABLE_PROVENANCES = new Set(['', 'api']);\n\n/**\n * True when `provenance` names an owner other than \"this API\" — INCLUDING when Grafana did not\n * report the field at all (`undefined`). `undefined` is deliberately NEVER folded into `''`\n * (`ProvenanceNone`) here — see the file header's \"fails open\" note. Only a value Grafana\n * explicitly reported as `\"\"` or `\"api\"` is writable.\n */\nexport const isForeignProvenance = (provenance: string | undefined): boolean =>\n provenance === undefined || !WRITABLE_PROVENANCES.has(provenance);\n\nexport const refuseIfForeignProvenance = (\n kind:\n | 'Grafana.ContactPoint'\n | 'Grafana.MessageTemplate'\n | 'Grafana.MuteTiming'\n | 'Grafana.AlertRuleGroup'\n | 'Grafana.NotificationPolicy',\n id: string,\n provenance: string | undefined,\n): Effect.Effect<never, GrafanaProvisionedObjectError> => {\n const reason =\n provenance === undefined\n ? `did not report a provenance at all — this family cannot tell whether it is safe to own`\n : `has provenance '${provenance}', not '(none)' or 'api'`;\n return Effect.fail(\n new GrafanaProvisionedObjectError({\n message:\n `${kind} '${id}' ${reason} — something other than this API may own it (file ` +\n `provisioning, the Prometheus-rules importer, or similar), and writing here could take ` +\n `over or corrupt an object this family does not actually control. Edit it at its actual ` +\n `source instead — declaring this object read-only (a declaration matching what is live) ` +\n `is safe and will never reach this refusal.`,\n }),\n );\n};\n",
15
+ "/**\n * `Grafana.MessageTemplate` — one Grafana notification-template group, keyed by `name`.\n *\n * ⚠️ CREATE AND UPDATE ARE THE SAME WIRE CALL — MEASURED: `services/grafana.ts` has no\n * `routePostTemplate` at all, only `routePutTemplate` (`PUT /v1/provisioning/templates/{name}`,\n * upsert by name), `routeGetTemplate`/`routeGetTemplates` and `routeDeleteTemplate`. `spec.create`\n * and `spec.update` both call it — kept as two separate functions anyway (rather than omitting\n * `update` and letting them collapse into one) so the shared engine's `diff` reports a content\n * change as `update`, not the `replace` it would show for any resource whose `spec.update` is\n * undefined (`resource.ts`).\n *\n * ⛔ A FOREIGN-PROVENANCE TEMPLATE REFUSES EVERY UPDATE AND DESTROY — `alerting-provenance.ts`,\n * shared with `Grafana.ContactPoint`/`AlertRuleGroup`/`NotificationPolicy`; see that file's\n * header. `create` is never at risk: it only runs when `fetchLive` already found nothing.\n *\n * ⚠️ `version` IS SENT FOR OPTIMISTIC CONCURRENCY WHEN GRAFANA REPORTS ONE — `RoutePutTemplateRequest.\n * version`'s own doc comment: \"Version of template to use for optimistic concurrency. Leave empty\n * to disable validation.\" Unlike `Grafana.Dashboard`'s 412, no live call confirms what error this\n * produces on a genuine mismatch (no live calls in this PR) — whatever typed error the SDK\n * declares for `routePutTemplate` is left uncaught either way, per the house rule against\n * folding a non-not-found error into absent.\n *\n * ⚠️ `X-Disable-Provenance` IS NEVER SET — same reasoning as `contact-point.ts`'s file header.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Effect from 'effect/Effect';\nimport {\n type GrafanaProvisionedObjectError,\n isForeignProvenance,\n refuseIfForeignProvenance,\n} from './alerting-provenance.ts';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\n\nexport interface MessageTemplateProps {\n name: string;\n /** The Go-template body, e.g. Grafana's own \"Export as JSON/YAML\" template text. */\n template: string;\n}\n\nexport interface MessageTemplateAttributes {\n name: string;\n template: string;\n /** `\"\"`/`\"api\"` (this family may write); anything else, INCLUDING `undefined` (Grafana didn't\n * report the field), refuses — see alerting-provenance.ts's \"fails open\" note. */\n provenance: string | undefined;\n}\n\nexport interface GrafanaMessageTemplate extends Resource<\n 'Grafana.MessageTemplate',\n MessageTemplateProps,\n MessageTemplateAttributes,\n never\n> {}\n\nexport const GrafanaMessageTemplate = Resource<GrafanaMessageTemplate>('Grafana.MessageTemplate');\n\nexport const spec: GrafanaSpec<\n MessageTemplateProps,\n grafana.NotificationTemplate,\n MessageTemplateAttributes,\n | grafana.RouteGetTemplateError\n | grafana.RoutePutTemplateError\n | grafana.RouteDeleteTemplateError\n | GrafanaProvisionedObjectError\n> = {\n attributes: (live) => ({\n name: live.name ?? '',\n // ⛔ NEVER `?? ''` — see alerting-provenance.ts's \"fails open\" note.\n provenance: live.provenance,\n template: live.template ?? '',\n }),\n create: (props) => grafana.routePutTemplate({ name: props.name, template: props.template }),\n destroy: (props, live) =>\n Effect.gen(function* () {\n if (isForeignProvenance(live.provenance)) {\n return yield* refuseIfForeignProvenance(\n 'Grafana.MessageTemplate',\n props.name,\n live.provenance,\n );\n }\n yield* grafana.routeDeleteTemplate({ name: props.name });\n }),\n fetchLive: (props) =>\n grafana\n .routeGetTemplate({ name: props.name })\n .pipe(Effect.catchTag('NotFound', () => Effect.succeed(undefined))),\n matches: (attributes, props) => attributes.template === props.template,\n update: (props, live) =>\n Effect.gen(function* () {\n if (isForeignProvenance(live.provenance)) {\n return yield* refuseIfForeignProvenance(\n 'Grafana.MessageTemplate',\n props.name,\n live.provenance,\n );\n }\n yield* grafana.routePutTemplate({\n name: props.name,\n template: props.template,\n ...(live.version === undefined ? {} : { version: live.version }),\n });\n }),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaMessageTemplateProvider = () =>\n Provider.effect(\n GrafanaMessageTemplate,\n Effect.succeed(GrafanaMessageTemplate.Provider.of(handlers)),\n );\n",
16
+ "/**\n * `Grafana.MuteTiming` — one Grafana alerting mute timing, keyed by `name` (there is no separate\n * `uid` — `routeGetMuteTiming`/`routePutMuteTiming`/`routeDeleteMuteTiming` are all name-keyed on\n * the wire, and `name` is the field a notification policy's own `mute_time_intervals` references).\n *\n * ★ `provenance`/`version` ARE READ THROUGH A WIDENED TYPE, NOT THE SDK's OWN `MuteTimeInterval` —\n * MEASURED, and a genuinely surprising result worth recording in full. The SDK's generated\n * `MuteTimeInterval` interface (`packages/distilled-grafana/src/services/grafana.ts`) declares\n * only `name`/`time_intervals` — no `provenance`, no `version` — unlike `EmbeddedContactPoint`/\n * `NotificationTemplate`/`ProvisionedAlertRule`/`Route`, which all carry `provenance`. A first\n * pass at this file assumed that meant the data was gone: decoding a raw payload containing both\n * fields through `S.decodeUnknownSync(MuteTimeInterval)` directly DOES strip them. But that is\n * the wrong code path — it is not what `routeGetMuteTiming` itself does. Proven empirically by\n * calling the REAL operation (through `fake-grafana.ts`'s harness, the exact path this file's own\n * `fetchLive` uses) with a response body containing `provenance`/`version`: both come through on\n * the decoded value intact. The mechanism, read from `@distilled.cloud/core@1.0.0-rc.12`'s\n * `protocol-rest.ts`/`protocol-http.ts` (the runtime this SDK's every operation shares): response\n * decoding is `JSON.parse` + `mapKeys` (renames only the field names a schema DOES declare) +\n * `wrapSensitive` (wraps only fields a schema marks sensitive in `Redacted`) — never a strict\n * `Schema.decode`. `protocol-http.ts`'s `mapKeys`, object branch: every response key NOT among a\n * type's declared `props` is copied to the output verbatim in a second pass\n * (`for (const [k, v] of Object.entries(value)) { if (consumed.has(k)) continue; out[k] = v; }`,\n * line ~297-311) — the doc comment on the sibling `wrapSensitive` states the same rule plainly:\n * \"Keys the schema doesn't model pass through verbatim.\" So the TS type is simply narrower than\n * the runtime value; `MuteTimeIntervalWire` below is that same runtime value, typed honestly.\n * This is a real, worth-fixing SDK type-generation gap (tracked in `docs/upstream-conformance.md`,\n * same S22 route as the other named gaps) but NOT a data-loss gap — unlike, say, a field the wire\n * protocol itself never sent, this one is fully recoverable without touching vendored `src/`.\n *\n * The same test proved the outbound direction too: an object with extra keys (`weekdays`, `times`\n * — the real Alertmanager time-interval fields Grafana's OpenAPI's own `TimeInterval` schema\n * apparently fails to model either, for the same reason) passed to `routePutMuteTiming` reaches\n * the wire body unchanged — `mapKeys`'s encode direction is the same verbatim-passthrough rule.\n * `MuteTimingProps.timeIntervals` is therefore typed as the opaque `Record<string, unknown>`\n * shape actually needed to declare a real mute timing (weekdays/times/months/etc.), not the\n * SDK's own too-narrow `TimeInterval`, and cast at the two call sites below.\n *\n * ⚠️ THE EVIDENCE ABOVE IS A HAND-AUTHORED FIXTURE, NOT A CAPTURED REAL RESPONSE — flagged by an\n * adversarial review of this PR. The estate has zero live mute timings to observe (grafana.md's\n * census), so nothing confirms Grafana always includes `provenance` on this route in practice,\n * or that some other SDK/version edge case never drops it. That is exactly why `attributes()`\n * below never coerces a missing `live.provenance` to `''` — see alerting-provenance.ts's \"fails\n * open\" note — an absent field refuses a write rather than being trusted as `ProvenanceNone`.\n *\n * ⛔ A FOREIGN-PROVENANCE MUTE TIMING REFUSES EVERY UPDATE AND DESTROY — `alerting-provenance.ts`,\n * shared with `Grafana.ContactPoint`/`MessageTemplate`/`AlertRuleGroup`/`NotificationPolicy`; see\n * that file's header. `create` is never at risk: it only runs when `fetchLive` already found\n * nothing with this `name`.\n *\n * ⚠️ `version` IS SENT ON DESTROY, NOT ON UPDATE — MEASURED: `RouteDeleteMuteTimingRequest.version`\n * (\"Version of mute timing to use for optimistic concurrency. Leave empty to disable validation\")\n * exists; `RoutePutMuteTimingRequest` has no `version` field at all, so an update has nothing to\n * send for it — a real Grafana API asymmetry between the two write routes, not an omission here.\n *\n * ⚠️ `X-Disable-Provenance` IS NEVER SET — same reasoning as `contact-point.ts`'s file header.\n */\nimport { Resource } from 'alchemy';\nimport * as Provider from 'alchemy/Provider';\nimport * as grafana from '@distilled.cloud/grafana';\nimport * as Effect from 'effect/Effect';\nimport {\n type GrafanaProvisionedObjectError,\n isForeignProvenance,\n refuseIfForeignProvenance,\n} from './alerting-provenance.ts';\nimport { type GrafanaSpec, grafanaHandlers } from './resource.ts';\nimport { declaredContentMatches } from './subset-match.ts';\n\nexport interface MuteTimingProps {\n name: string;\n /** Grafana's Alertmanager-style time-interval entries (`weekdays`/`times`/`months`/…), passed\n * through opaquely — see the file header for why this is not the SDK's own `TimeInterval`. */\n timeIntervals: ReadonlyArray<Record<string, unknown>>;\n}\n\nexport interface MuteTimingAttributes {\n name: string;\n timeIntervals: ReadonlyArray<Record<string, unknown>>;\n /** `\"\"`/`\"api\"` (this family may write); anything else, INCLUDING `undefined` (Grafana didn't\n * report the field, or the widened-type passthrough this file depends on turns out not to hold\n * for some response shape not yet observed), refuses — see alerting-provenance.ts's \"fails\n * open\" note. This is the field where that default matters most: unlike the other resources in\n * this family, `provenance` here is not even in the SDK's declared type. */\n provenance: string | undefined;\n}\n\nexport interface GrafanaMuteTiming extends Resource<\n 'Grafana.MuteTiming',\n MuteTimingProps,\n MuteTimingAttributes,\n never\n> {}\n\nexport const GrafanaMuteTiming = Resource<GrafanaMuteTiming>('Grafana.MuteTiming');\n\n/** The real runtime shape of a `GET`/list response — see the file header's \"measured\" note. */\ntype MuteTimeIntervalWire = grafana.MuteTimeInterval & {\n provenance?: string;\n version?: string;\n};\n\nexport const spec: GrafanaSpec<\n MuteTimingProps,\n MuteTimeIntervalWire,\n MuteTimingAttributes,\n | grafana.RouteGetMuteTimingError\n | grafana.RoutePostMuteTimingError\n | grafana.RoutePutMuteTimingError\n | grafana.RouteDeleteMuteTimingError\n | GrafanaProvisionedObjectError\n> = {\n attributes: (live) => ({\n name: live.name ?? '',\n // ⛔ NEVER `?? ''` — see alerting-provenance.ts's \"fails open\" note, and this file's own\n // Attributes doc comment on why it matters most here.\n provenance: live.provenance,\n timeIntervals: (live.time_intervals ?? []) as unknown as ReadonlyArray<Record<string, unknown>>,\n }),\n create: (props) =>\n grafana.routePostMuteTiming({\n name: props.name,\n time_intervals:\n props.timeIntervals as unknown as grafana.RoutePostMuteTimingRequestTimeIntervalsList,\n }),\n destroy: (props, live) =>\n Effect.gen(function* () {\n if (isForeignProvenance(live.provenance)) {\n return yield* refuseIfForeignProvenance('Grafana.MuteTiming', props.name, live.provenance);\n }\n yield* grafana.routeDeleteMuteTiming({\n name: props.name,\n ...(live.version === undefined ? {} : { version: live.version }),\n });\n }),\n fetchLive: (props) =>\n grafana.routeGetMuteTiming({ name: props.name }).pipe(\n Effect.map((live) => live as MuteTimeIntervalWire),\n Effect.catchTag('NotFound', () => Effect.succeed(undefined)),\n ),\n /** Subset match (subset-match.ts), not plain equality — tolerates a default Grafana might\n * inject into a time-interval entry the declaration never set. */\n matches: (attributes, props) =>\n declaredContentMatches(props.timeIntervals, attributes.timeIntervals),\n update: (props, live) =>\n Effect.gen(function* () {\n if (isForeignProvenance(live.provenance)) {\n return yield* refuseIfForeignProvenance('Grafana.MuteTiming', props.name, live.provenance);\n }\n yield* grafana.routePutMuteTiming({\n name: props.name,\n time_intervals:\n props.timeIntervals as unknown as grafana.RoutePutMuteTimingRequestTimeIntervalsList,\n });\n }),\n};\n\nexport const handlers = grafanaHandlers(spec);\n\nexport const GrafanaMuteTimingProvider = () =>\n Provider.effect(GrafanaMuteTiming, Effect.succeed(GrafanaMuteTiming.Provider.of(handlers)));\n",
11
17
  "/**\n * Grafana credentials for this family — a URL plus a TOKEN REFERENCE (an env var NAME), never a\n * literal secret. Wraps `@distilled.cloud/grafana`'s own `Credentials` context directly (S23 —\n * reuse the SDK's own client rather than inventing a second one); this file only adds the house's\n * lazy, per-instance resolution on top of it.\n *\n * ★ ONE `GRAFANA_TOKEN`/`GRAFANA_URL` PAIR ISN'T ENOUGH FOR THIS HOUSE. Unlike Forgejo (one\n * instance), the estate runs at least two Grafanas — `grafana.homeflare.dev` and\n * `teslamate-grafana` on CT100, which the unit's own comment says has \"Own Access SaaS OIDC\n * client, never grafana.homeflare.dev's\". The SDK's `CredentialsFromEnv` reads one fixed pair\n * of names, so a stack declaring against more than one instance needs a target per instance —\n * `grafanaCredentials(target)` below, parameterized the way `litellm/credentials.ts` and\n * `proxmox/credentials.ts` already are in this kit.\n *\n * ⛔ THE TOKEN NEVER TOUCHES A PROP OR AN ATTRIBUTE (S25). `tokenEnv` is a NAME, resolved from\n * `process.env` fresh inside the credentials effect on every request (S24) — a layer built once\n * still re-reads the environment each time an operation runs, the same laziness\n * `litellm/credentials.ts` and the SDK's own `forgejo`/`netbox` `CredentialsFromEnv` have.\n *\n * ⚠️ THE SDK's `Credentials` SERVICE VALUE TYPE IS `Effect.Effect<Config, never, never>` — the\n * error channel is `never` (credentials.d.ts). A missing env var can only surface as a defect\n * here, via `Effect.orDie`, the same trade-off `forgejo/resource.ts` and `netbox/resource.ts`\n * already accept for their own `CredentialsFromEnv`; it is not a choice this file introduces.\n */\nimport { type Config, Credentials } from '@distilled.cloud/grafana/Credentials';\nimport { ConfigError } from '@distilled.cloud/grafana/Errors';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport * as Redacted from 'effect/Redacted';\n\nexport interface GrafanaTarget {\n /** Instance origin, e.g. `https://tesla.homeflare.dev/dash` — never `/api`; the SDK appends it. */\n readonly baseUrl: string;\n /** Env var NAME holding a Grafana service-account token. Never the value itself. */\n readonly tokenEnv: string;\n /** Selects the org a call applies to, via `X-Grafana-Org-Id`. Omit for the default org. */\n readonly orgId?: number | string;\n}\n\nconst resolve = (target: GrafanaTarget): Effect.Effect<Config, never, never> =>\n Effect.sync(() => process.env[target.tokenEnv]).pipe(\n Effect.flatMap((raw) =>\n raw === undefined || raw.trim() === ''\n ? Effect.fail(\n new ConfigError({\n message:\n `${target.tokenEnv} is not set. Export the Grafana service-account token there ` +\n `— never as an Alchemy prop — before running a stack that declares a resource ` +\n `against ${target.baseUrl}.`,\n }),\n )\n : Effect.succeed({\n apiBaseUrl: target.baseUrl.replace(/\\/+$/, ''),\n token: Redacted.make(raw.trim()),\n ...(target.orgId === undefined ? {} : { orgId: target.orgId }),\n } satisfies Config),\n ),\n Effect.orDie,\n );\n\n/** The lazy, per-instance layer `grafanaProviders(target)` composes by default. */\nexport const grafanaCredentials = (target: GrafanaTarget): Layer.Layer<Credentials> =>\n Layer.succeed(Credentials, resolve(target));\n\nexport type { Credentials };\n",
12
- "/**\n * The Grafana providers for one instance, with its credentials, as one layer for a stack.\n *\n * Layer.mergeAll(grafanaProviders({ baseUrl: '...', tokenEnv: 'GRAFANA_TOKEN' }), …)\n *\n * ★ ONE CALL PER INSTANCE. A stack declaring resources against both `grafana.homeflare.dev` and\n * `teslamate-grafana` composes `grafanaProviders(targetA)` and `grafanaProviders(targetB)` —\n * each resource type's `Resource` constructor is a single global registration, so the SAME\n * `Grafana.Datasource` type serves both; only the credential layer differs per call.\n * ⚠️ `HttpClient` is NOT provided here. It is ambient in every Alchemy runtime, the same as\n * `litellmProviders`/`netboxProviders`-style families in this kit assume; a stack that already\n * deploys anything over HTTP already has one.\n */\nimport * as Layer from 'effect/Layer';\nimport { type GrafanaTarget, grafanaCredentials } from './credentials.ts';\nimport { GrafanaDashboardProvider } from './dashboard.ts';\nimport { GrafanaDatasourceProvider } from './datasource.ts';\nimport { GrafanaFolderProvider } from './folder.ts';\n\nexport const grafanaProviders = (target: GrafanaTarget) =>\n Layer.mergeAll(\n GrafanaDatasourceProvider(),\n GrafanaFolderProvider(),\n GrafanaDashboardProvider(),\n ).pipe(Layer.provide(grafanaCredentials(target)));\n"
18
+ "/**\n * The Grafana providers for one instance, with its credentials, as one layer for a stack.\n *\n * Layer.mergeAll(grafanaProviders({ baseUrl: '...', tokenEnv: 'GRAFANA_TOKEN' }), …)\n *\n * ★ ONE CALL PER INSTANCE. A stack declaring resources against both `grafana.homeflare.dev` and\n * `teslamate-grafana` composes `grafanaProviders(targetA)` and `grafanaProviders(targetB)` —\n * each resource type's `Resource` constructor is a single global registration, so the SAME\n * `Grafana.Datasource` type serves both; only the credential layer differs per call.\n * ⚠️ `HttpClient` is NOT provided here. It is ambient in every Alchemy runtime, the same as\n * `litellmProviders`/`netboxProviders`-style families in this kit assume; a stack that already\n * deploys anything over HTTP already has one.\n */\nimport * as Layer from 'effect/Layer';\nimport { GrafanaContactPointProvider } from './contact-point.ts';\nimport { type GrafanaTarget, grafanaCredentials } from './credentials.ts';\nimport { GrafanaDashboardProvider } from './dashboard.ts';\nimport { GrafanaDatasourceProvider } from './datasource.ts';\nimport { GrafanaFolderProvider } from './folder.ts';\nimport { GrafanaMessageTemplateProvider } from './message-template.ts';\nimport { GrafanaMuteTimingProvider } from './mute-timing.ts';\n\nexport const grafanaProviders = (target: GrafanaTarget) =>\n Layer.mergeAll(\n GrafanaDatasourceProvider(),\n GrafanaFolderProvider(),\n GrafanaDashboardProvider(),\n GrafanaContactPointProvider(),\n GrafanaMuteTimingProvider(),\n GrafanaMessageTemplateProvider(),\n ).pipe(Layer.provide(grafanaCredentials(target)));\n"
13
19
  ],
14
- "mappings": "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",
15
- "debugId": "D031E5F2BA23E8BC64756E2164756E21",
20
+ "mappings": "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",
21
+ "debugId": "D93AD79265681B5F64756E2164756E21",
16
22
  "names": []
17
23
  }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `Grafana.MessageTemplate` — one Grafana notification-template group, keyed by `name`.
3
+ *
4
+ * ⚠️ CREATE AND UPDATE ARE THE SAME WIRE CALL — MEASURED: `services/grafana.ts` has no
5
+ * `routePostTemplate` at all, only `routePutTemplate` (`PUT /v1/provisioning/templates/{name}`,
6
+ * upsert by name), `routeGetTemplate`/`routeGetTemplates` and `routeDeleteTemplate`. `spec.create`
7
+ * and `spec.update` both call it — kept as two separate functions anyway (rather than omitting
8
+ * `update` and letting them collapse into one) so the shared engine's `diff` reports a content
9
+ * change as `update`, not the `replace` it would show for any resource whose `spec.update` is
10
+ * undefined (`resource.ts`).
11
+ *
12
+ * ⛔ A FOREIGN-PROVENANCE TEMPLATE REFUSES EVERY UPDATE AND DESTROY — `alerting-provenance.ts`,
13
+ * shared with `Grafana.ContactPoint`/`AlertRuleGroup`/`NotificationPolicy`; see that file's
14
+ * header. `create` is never at risk: it only runs when `fetchLive` already found nothing.
15
+ *
16
+ * ⚠️ `version` IS SENT FOR OPTIMISTIC CONCURRENCY WHEN GRAFANA REPORTS ONE — `RoutePutTemplateRequest.
17
+ * version`'s own doc comment: "Version of template to use for optimistic concurrency. Leave empty
18
+ * to disable validation." Unlike `Grafana.Dashboard`'s 412, no live call confirms what error this
19
+ * produces on a genuine mismatch (no live calls in this PR) — whatever typed error the SDK
20
+ * declares for `routePutTemplate` is left uncaught either way, per the house rule against
21
+ * folding a non-not-found error into absent.
22
+ *
23
+ * ⚠️ `X-Disable-Provenance` IS NEVER SET — same reasoning as `contact-point.ts`'s file header.
24
+ */
25
+ import { Resource } from 'alchemy';
26
+ import * as Provider from 'alchemy/Provider';
27
+ import * as grafana from '@distilled.cloud/grafana';
28
+ import * as Effect from 'effect/Effect';
29
+ import { type GrafanaProvisionedObjectError } from './alerting-provenance.ts';
30
+ import { type GrafanaSpec } from './resource.ts';
31
+ export interface MessageTemplateProps {
32
+ name: string;
33
+ /** The Go-template body, e.g. Grafana's own "Export as JSON/YAML" template text. */
34
+ template: string;
35
+ }
36
+ export interface MessageTemplateAttributes {
37
+ name: string;
38
+ template: string;
39
+ /** `""`/`"api"` (this family may write); anything else, INCLUDING `undefined` (Grafana didn't
40
+ * report the field), refuses — see alerting-provenance.ts's "fails open" note. */
41
+ provenance: string | undefined;
42
+ }
43
+ export interface GrafanaMessageTemplate extends Resource<'Grafana.MessageTemplate', MessageTemplateProps, MessageTemplateAttributes, never> {
44
+ }
45
+ export declare const GrafanaMessageTemplate: import("alchemy").ResourceClass<GrafanaMessageTemplate>;
46
+ export declare const spec: GrafanaSpec<MessageTemplateProps, grafana.NotificationTemplate, MessageTemplateAttributes, grafana.RouteGetTemplateError | grafana.RoutePutTemplateError | grafana.RouteDeleteTemplateError | GrafanaProvisionedObjectError>;
47
+ export declare const handlers: {
48
+ list: () => Effect.Effect<never[], never, never>;
49
+ read: ({ olds }: {
50
+ olds: MessageTemplateProps;
51
+ }) => Effect.Effect<MessageTemplateAttributes | undefined, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
52
+ diff: ({ news, output }: {
53
+ news: import("alchemy").Input<MessageTemplateProps>;
54
+ output: MessageTemplateAttributes | undefined;
55
+ }) => Effect.Effect<{
56
+ readonly action: 'update';
57
+ } | {
58
+ readonly action: 'noop';
59
+ } | {
60
+ readonly action: 'replace';
61
+ } | undefined, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
62
+ reconcile: ({ news }: {
63
+ news: MessageTemplateProps;
64
+ }) => Effect.Effect<MessageTemplateAttributes, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
65
+ delete: ({ olds }: {
66
+ olds: MessageTemplateProps;
67
+ }) => Effect.Effect<void, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
68
+ };
69
+ export declare const GrafanaMessageTemplateProvider: () => import("effect/Layer").Layer<Provider.Provider<GrafanaMessageTemplate>, never, grafana.GrafanaOpContext>;
70
+ //# sourceMappingURL=message-template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-template.d.ts","sourceRoot":"","sources":["../../src/grafana/message-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,KAAK,6BAA6B,EAGnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,eAAe,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB;uFACmF;IACnF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,sBAAuB,SAAQ,QAAQ,CACtD,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,KAAK,CACN;CAAG;AAEJ,eAAO,MAAM,sBAAsB,yDAA8D,CAAC;AAElG,eAAO,MAAM,IAAI,EAAE,WAAW,CAC5B,oBAAoB,EACpB,OAAO,CAAC,oBAAoB,EAC5B,yBAAyB,EACvB,OAAO,CAAC,qBAAqB,GAC7B,OAAO,CAAC,qBAAqB,GAC7B,OAAO,CAAC,wBAAwB,GAChC,6BAA6B,CAwChC,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAwB,CAAC;AAE9C,eAAO,MAAM,8BAA8B,gHAIxC,CAAC"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * `Grafana.MuteTiming` — one Grafana alerting mute timing, keyed by `name` (there is no separate
3
+ * `uid` — `routeGetMuteTiming`/`routePutMuteTiming`/`routeDeleteMuteTiming` are all name-keyed on
4
+ * the wire, and `name` is the field a notification policy's own `mute_time_intervals` references).
5
+ *
6
+ * ★ `provenance`/`version` ARE READ THROUGH A WIDENED TYPE, NOT THE SDK's OWN `MuteTimeInterval` —
7
+ * MEASURED, and a genuinely surprising result worth recording in full. The SDK's generated
8
+ * `MuteTimeInterval` interface (`packages/distilled-grafana/src/services/grafana.ts`) declares
9
+ * only `name`/`time_intervals` — no `provenance`, no `version` — unlike `EmbeddedContactPoint`/
10
+ * `NotificationTemplate`/`ProvisionedAlertRule`/`Route`, which all carry `provenance`. A first
11
+ * pass at this file assumed that meant the data was gone: decoding a raw payload containing both
12
+ * fields through `S.decodeUnknownSync(MuteTimeInterval)` directly DOES strip them. But that is
13
+ * the wrong code path — it is not what `routeGetMuteTiming` itself does. Proven empirically by
14
+ * calling the REAL operation (through `fake-grafana.ts`'s harness, the exact path this file's own
15
+ * `fetchLive` uses) with a response body containing `provenance`/`version`: both come through on
16
+ * the decoded value intact. The mechanism, read from `@distilled.cloud/core@1.0.0-rc.12`'s
17
+ * `protocol-rest.ts`/`protocol-http.ts` (the runtime this SDK's every operation shares): response
18
+ * decoding is `JSON.parse` + `mapKeys` (renames only the field names a schema DOES declare) +
19
+ * `wrapSensitive` (wraps only fields a schema marks sensitive in `Redacted`) — never a strict
20
+ * `Schema.decode`. `protocol-http.ts`'s `mapKeys`, object branch: every response key NOT among a
21
+ * type's declared `props` is copied to the output verbatim in a second pass
22
+ * (`for (const [k, v] of Object.entries(value)) { if (consumed.has(k)) continue; out[k] = v; }`,
23
+ * line ~297-311) — the doc comment on the sibling `wrapSensitive` states the same rule plainly:
24
+ * "Keys the schema doesn't model pass through verbatim." So the TS type is simply narrower than
25
+ * the runtime value; `MuteTimeIntervalWire` below is that same runtime value, typed honestly.
26
+ * This is a real, worth-fixing SDK type-generation gap (tracked in `docs/upstream-conformance.md`,
27
+ * same S22 route as the other named gaps) but NOT a data-loss gap — unlike, say, a field the wire
28
+ * protocol itself never sent, this one is fully recoverable without touching vendored `src/`.
29
+ *
30
+ * The same test proved the outbound direction too: an object with extra keys (`weekdays`, `times`
31
+ * — the real Alertmanager time-interval fields Grafana's OpenAPI's own `TimeInterval` schema
32
+ * apparently fails to model either, for the same reason) passed to `routePutMuteTiming` reaches
33
+ * the wire body unchanged — `mapKeys`'s encode direction is the same verbatim-passthrough rule.
34
+ * `MuteTimingProps.timeIntervals` is therefore typed as the opaque `Record<string, unknown>`
35
+ * shape actually needed to declare a real mute timing (weekdays/times/months/etc.), not the
36
+ * SDK's own too-narrow `TimeInterval`, and cast at the two call sites below.
37
+ *
38
+ * ⚠️ THE EVIDENCE ABOVE IS A HAND-AUTHORED FIXTURE, NOT A CAPTURED REAL RESPONSE — flagged by an
39
+ * adversarial review of this PR. The estate has zero live mute timings to observe (grafana.md's
40
+ * census), so nothing confirms Grafana always includes `provenance` on this route in practice,
41
+ * or that some other SDK/version edge case never drops it. That is exactly why `attributes()`
42
+ * below never coerces a missing `live.provenance` to `''` — see alerting-provenance.ts's "fails
43
+ * open" note — an absent field refuses a write rather than being trusted as `ProvenanceNone`.
44
+ *
45
+ * ⛔ A FOREIGN-PROVENANCE MUTE TIMING REFUSES EVERY UPDATE AND DESTROY — `alerting-provenance.ts`,
46
+ * shared with `Grafana.ContactPoint`/`MessageTemplate`/`AlertRuleGroup`/`NotificationPolicy`; see
47
+ * that file's header. `create` is never at risk: it only runs when `fetchLive` already found
48
+ * nothing with this `name`.
49
+ *
50
+ * ⚠️ `version` IS SENT ON DESTROY, NOT ON UPDATE — MEASURED: `RouteDeleteMuteTimingRequest.version`
51
+ * ("Version of mute timing to use for optimistic concurrency. Leave empty to disable validation")
52
+ * exists; `RoutePutMuteTimingRequest` has no `version` field at all, so an update has nothing to
53
+ * send for it — a real Grafana API asymmetry between the two write routes, not an omission here.
54
+ *
55
+ * ⚠️ `X-Disable-Provenance` IS NEVER SET — same reasoning as `contact-point.ts`'s file header.
56
+ */
57
+ import { Resource } from 'alchemy';
58
+ import * as Provider from 'alchemy/Provider';
59
+ import * as grafana from '@distilled.cloud/grafana';
60
+ import * as Effect from 'effect/Effect';
61
+ import { type GrafanaProvisionedObjectError } from './alerting-provenance.ts';
62
+ import { type GrafanaSpec } from './resource.ts';
63
+ export interface MuteTimingProps {
64
+ name: string;
65
+ /** Grafana's Alertmanager-style time-interval entries (`weekdays`/`times`/`months`/…), passed
66
+ * through opaquely — see the file header for why this is not the SDK's own `TimeInterval`. */
67
+ timeIntervals: ReadonlyArray<Record<string, unknown>>;
68
+ }
69
+ export interface MuteTimingAttributes {
70
+ name: string;
71
+ timeIntervals: ReadonlyArray<Record<string, unknown>>;
72
+ /** `""`/`"api"` (this family may write); anything else, INCLUDING `undefined` (Grafana didn't
73
+ * report the field, or the widened-type passthrough this file depends on turns out not to hold
74
+ * for some response shape not yet observed), refuses — see alerting-provenance.ts's "fails
75
+ * open" note. This is the field where that default matters most: unlike the other resources in
76
+ * this family, `provenance` here is not even in the SDK's declared type. */
77
+ provenance: string | undefined;
78
+ }
79
+ export interface GrafanaMuteTiming extends Resource<'Grafana.MuteTiming', MuteTimingProps, MuteTimingAttributes, never> {
80
+ }
81
+ export declare const GrafanaMuteTiming: import("alchemy").ResourceClass<GrafanaMuteTiming>;
82
+ /** The real runtime shape of a `GET`/list response — see the file header's "measured" note. */
83
+ type MuteTimeIntervalWire = grafana.MuteTimeInterval & {
84
+ provenance?: string;
85
+ version?: string;
86
+ };
87
+ export declare const spec: GrafanaSpec<MuteTimingProps, MuteTimeIntervalWire, MuteTimingAttributes, grafana.RouteGetMuteTimingError | grafana.RoutePostMuteTimingError | grafana.RoutePutMuteTimingError | grafana.RouteDeleteMuteTimingError | GrafanaProvisionedObjectError>;
88
+ export declare const handlers: {
89
+ list: () => Effect.Effect<never[], never, never>;
90
+ read: ({ olds }: {
91
+ olds: MuteTimingProps;
92
+ }) => Effect.Effect<MuteTimingAttributes | undefined, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
93
+ diff: ({ news, output }: {
94
+ news: import("alchemy").Input<MuteTimingProps>;
95
+ output: MuteTimingAttributes | undefined;
96
+ }) => Effect.Effect<{
97
+ readonly action: 'update';
98
+ } | {
99
+ readonly action: 'noop';
100
+ } | {
101
+ readonly action: 'replace';
102
+ } | undefined, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
103
+ reconcile: ({ news }: {
104
+ news: MuteTimingProps;
105
+ }) => Effect.Effect<MuteTimingAttributes, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
106
+ delete: ({ olds }: {
107
+ olds: MuteTimingProps;
108
+ }) => Effect.Effect<void, grafana.BadGateway | import("@distilled.cloud/caddy").BadRequest | grafana.BadRequest | grafana.ConfigError | import("@distilled.cloud/caddy").Conflict | grafana.Conflict | import("@distilled.cloud/caddy").Forbidden | grafana.Forbidden | grafana.GatewayTimeout | GrafanaProvisionedObjectError | import("effect/unstable/http/HttpClientError").HttpClientError | grafana.InternalServerError | grafana.Locked | import("@distilled.cloud/caddy").NotFound | grafana.NotFound | grafana.ServiceUnavailable | grafana.TooManyRequests | grafana.Unauthorized | grafana.UnknownGrafanaError | import("@distilled.cloud/caddy").UnprocessableEntity, grafana.GrafanaOpContext>;
109
+ };
110
+ export declare const GrafanaMuteTimingProvider: () => import("effect/Layer").Layer<Provider.Provider<GrafanaMuteTiming>, never, grafana.GrafanaOpContext>;
111
+ export {};
112
+ //# sourceMappingURL=mute-timing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mute-timing.d.ts","sourceRoot":"","sources":["../../src/grafana/mute-timing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,0BAA0B,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,KAAK,6BAA6B,EAGnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,eAAe,CAAC;AAGlE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb;mGAC+F;IAC/F,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACtD;;;;iFAI6E;IAC7E,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,iBAAkB,SAAQ,QAAQ,CACjD,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,KAAK,CACN;CAAG;AAEJ,eAAO,MAAM,iBAAiB,oDAAoD,CAAC;AAEnF,+FAA+F;AAC/F,KAAK,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,GAAG;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,IAAI,EAAE,WAAW,CAC5B,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EAClB,OAAO,CAAC,uBAAuB,GAC/B,OAAO,CAAC,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,GAC/B,OAAO,CAAC,0BAA0B,GAClC,6BAA6B,CA6ChC,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAwB,CAAC;AAE9C,eAAO,MAAM,yBAAyB,2GACuD,CAAC"}
@@ -13,5 +13,5 @@
13
13
  */
14
14
  import * as Layer from 'effect/Layer';
15
15
  import { type GrafanaTarget } from './credentials.ts';
16
- export declare const grafanaProviders: (target: GrafanaTarget) => Layer.Layer<import("alchemy").Provider<import("./dashboard.ts").GrafanaDashboard> | import("alchemy").Provider<import("./datasource.ts").GrafanaDatasource> | import("alchemy").Provider<import("./folder.ts").GrafanaFolder>, never, import("effect/unstable/http/HttpClient").HttpClient>;
16
+ export declare const grafanaProviders: (target: GrafanaTarget) => Layer.Layer<import("alchemy").Provider<import("./contact-point.ts").GrafanaContactPoint> | import("alchemy").Provider<import("./dashboard.ts").GrafanaDashboard> | import("alchemy").Provider<import("./datasource.ts").GrafanaDatasource> | import("alchemy").Provider<import("./folder.ts").GrafanaFolder> | import("alchemy").Provider<import("./message-template.ts").GrafanaMessageTemplate> | import("alchemy").Provider<import("./mute-timing.ts").GrafanaMuteTiming>, never, import("effect/unstable/http/HttpClient").HttpClient>;
17
17
  //# sourceMappingURL=providers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/grafana/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAK1E,eAAO,MAAM,gBAAgB,WAAY,aAAa,gSAKH,CAAC"}
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/grafana/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAO1E,eAAO,MAAM,gBAAgB,WAAY,aAAa,+gBAQH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import * as Effect from 'effect/Effect';
2
+ declare const GrafanaSecretRefUnsetError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
3
+ readonly _tag: "GrafanaSecretRefUnsetError";
4
+ } & Readonly<A>;
5
+ /** A named secure-field env var is unset — a domain refusal, not a distilled error. */
6
+ export declare class GrafanaSecretRefUnsetError extends GrafanaSecretRefUnsetError_base<{
7
+ readonly message: string;
8
+ }> {
9
+ }
10
+ /**
11
+ * Reads every named env var fresh, inside the caller's effect (S24) — never at plan time.
12
+ * `describeField` builds the refusal message's own field-specific phrase — each caller names its
13
+ * declaration path differently (`secureJsonData.<field>` for a datasource,
14
+ * `settings.<field>` for a contact point) — so the message stays accurate to what the caller
15
+ * itself declares.
16
+ */
17
+ export declare const resolveSecretRefs: (refs: Record<string, string> | undefined, describeField: (field: string) => string) => Effect.Effect<Record<string, string> | undefined, GrafanaSecretRefUnsetError>;
18
+ export {};
19
+ //# sourceMappingURL=secret-refs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-refs.d.ts","sourceRoot":"","sources":["../../src/grafana/secret-refs.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;;;;AAExC,uFAAuF;AACvF,qBAAa,0BAA2B,SAAQ,gCAA+C;IAC7F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,SACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,iBACzB,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,KACvC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAAE,0BAA0B,CAkB3E,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const declaredContentMatches: (declared: unknown, live: unknown) => boolean;
2
+ //# sourceMappingURL=subset-match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subset-match.d.ts","sourceRoot":"","sources":["../../src/grafana/subset-match.ts"],"names":[],"mappings":"AAsDA,eAAO,MAAM,sBAAsB,aAAc,OAAO,QAAQ,OAAO,KAAG,OAgBzE,CAAC"}
@@ -27,6 +27,6 @@ export type { SystemdUnitAttributes, SystemdUnitProps, UnitSection } from './uni
27
27
  export { DEFAULT_UNIT_DIRECTORY, renderUnit } from './unit-form.ts';
28
28
  export { SystemdTimer, SystemdTimerProvider, SystemdUnit, SystemdUnitProvider } from './unit.ts';
29
29
  export type { ContainerSection, PodmanContainerAttributes, PodmanContainerProps, PodmanUnitLine, } from './container.ts';
30
- export { QUADLET_DEFAULT_DIRECTORY, QUADLET_SEARCH_DIRECTORIES } from './container-form.ts';
30
+ export { containerPathFor, QUADLET_DEFAULT_DIRECTORY, QUADLET_SEARCH_DIRECTORIES, renderContainerFile, } from './container-form.ts';
31
31
  export { PodmanContainer, PodmanContainerProvider } from './container.ts';
32
32
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/linux/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,YAAY,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EACV,UAAU,EACV,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,YAAY,GACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACxF,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACjG,YAAY,EACV,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,cAAc,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAC5F,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/linux/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,YAAY,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACnF,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EACV,UAAU,EACV,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,YAAY,GACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACxF,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACjG,YAAY,EACV,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,cAAc,GACf,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}