@intentius/chant-lexicon-fly 0.102.0 → 0.104.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.
@@ -1 +1 @@
1
- {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/codegen/docs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAiB5D;AA0ED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjF"}
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/codegen/docs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAiB5D;AA0FD;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjF"}
@@ -18,7 +18,10 @@
18
18
  * `APP_REVISION` env var (#2834, as chud's fly-site.mjs set it), so the
19
19
  * app's own `/health` can report it. A caller's `env.APP_REVISION`
20
20
  * overrides it. `fly-rollback` restores it for free: it puts back the
21
- * whole recorded config, env included;
21
+ * whole recorded config, env included. A Machine the build stamped with
22
+ * telemetry attribution (#3060) also gets the release's `service.version`
23
+ * and `vcs.ref.head.revision` in its `OTEL_RESOURCE_ATTRIBUTES` (#3061,
24
+ * ws-081; see {@link releaseResourceAttributes});
22
25
  * 2. migrate: each migration runs inside the Machine once per environment,
23
26
  * witnessed by a receipt in chant's lifecycle receipt store
24
27
  * (`@intentius/chant/op/lifecycle-receipt-store`), then the Machine restarts
@@ -45,6 +48,7 @@ import type { ReceiptStore, EffectReceiptRef } from "@intentius/chant/op/receipt
45
48
  import { type MachineFile, type MachineRelease } from "../op/activities/machine-release.js";
46
49
  import { type FlyHttp, type WaitOpts } from "../op/activities/fly-apply.js";
47
50
  import type { MachineConfigStore } from "../release-store.js";
51
+ import { type ReleaseIdentity } from "@intentius/chant/telemetry-attribution";
48
52
  /** One migration, run inside the Machine once per environment. */
49
53
  export interface FlyMigration {
50
54
  /** Its name: the receipt is named after it, so a name fires once. */
@@ -179,6 +183,18 @@ export declare function migrationReceipt(app: string, migration: FlyMigration):
179
183
  ref: EffectReceiptRef;
180
184
  expectation: string;
181
185
  };
186
+ /**
187
+ * A stamped Machine's `OTEL_RESOURCE_ATTRIBUTES` with the release's attributes
188
+ * (#3061, ws-081), or undefined when the build did not stamp it (no
189
+ * `chant.decl` key) or the release knows neither attribute.
190
+ *
191
+ * A Machine's env values are literal, so the deploy writes the full value in
192
+ * place of a `CHANT_RELEASE_ATTRIBUTES` reference the platform would expand.
193
+ * The release's keys replace the same keys in the declared value, as the
194
+ * suffix appended last does on Compose and Kubernetes: a digest-pinned image
195
+ * gave the build a `service.version`, and the release may run another image.
196
+ */
197
+ export declare function releaseResourceAttributes(declared: unknown, release: ReleaseIdentity): string | undefined;
182
198
  /** Build the `fly-release` capability. */
183
199
  export declare function createFlyReleaseCapability(deps?: FlyReleaseDeps): Capability<FlyReleaseInput, FlyReleaseOutput>;
184
200
  /** Build the `fly-rollback` capability. */
@@ -1 +1 @@
1
- {"version":3,"file":"fly-release.d.ts","sourceRoot":"","sources":["../../src/components/fly-release.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AACxF,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EAUL,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAA8C,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACrH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC3B,4HAA4H;IAC5H,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6FAA6F;IAC7F,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;CAC1B;AAED,8GAA8G;AAC9G,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAkB9C,kGAAkG;AAClG,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAW5G;AAID,6CAA6C;AAC7C,MAAM,WAAW,SAAS;IACxB,2FAA2F;IAC3F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kHAAkH;IAClH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,yEAAyE;IACzE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,qDAAqD;IACrD,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACrD;AAED,gGAAgG;AAChG,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,+BAA+B;IAC/B,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;CACjC;AAED,oGAAoG;AACpG,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gGAAgG;IAChG,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACvF,qFAAqF;IACrF,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5E,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAmBD,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG;IAAE,GAAG,EAAE,gBAAgB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAMrH;AASD,0CAA0C;AAC1C,wBAAgB,0BAA0B,CAAC,IAAI,GAAE,cAAmB,GAAG,UAAU,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAsHnH;AAED,2CAA2C;AAC3C,wBAAgB,2BAA2B,CAAC,IAAI,GAAE,cAAmB,GAAG,UAAU,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CA4DtH;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWrH;AAED,4CAA4C;AAC5C,eAAO,MAAM,oBAAoB,+CAA+B,CAAC;AACjE,6CAA6C;AAC7C,eAAO,MAAM,qBAAqB,iDAAgC,CAAC"}
1
+ {"version":3,"file":"fly-release.d.ts","sourceRoot":"","sources":["../../src/components/fly-release.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "363a62d02554474befdfdd8a165f45bc7395ec35d65658ff129b3c4187c66b97",
4
+ "manifest.json": "ef9f817ff0937f9a6c5c361f2c5e576dfc98ba16aa041c931c19dc8bfa54f03d",
5
5
  "meta.json": "273f2a523b66f08ca91642dba2592c44765ab59ddf7329998884a0b08c6c3f24",
6
6
  "types/index.d.ts": "2f01714e1aa5f5695d68416e143f281584efb84457138c93825fea34e2f3fa0a",
7
7
  "rules/guest-sizing.ts": "bc749d218df8f3cc3d8fbe388205ae96e6b204ae0d6d77e8bf82cb936cc53ff6",
@@ -15,5 +15,5 @@
15
15
  "skills/chant-fly-ops.md": "44102f1fdf611b9262c04457258990e44026e83f9e5b19ae86b69322f3a37620",
16
16
  "skills/chant-fly-sprites.md": "3d100680422151b58d082c3a7b30a286edfc52faa4887995c9ad5553873bab95"
17
17
  },
18
- "composite": "08ed005777ae230eeefed6862869eb086998f0fe7b40808a0457532bffc5342b"
18
+ "composite": "ef6351e922f997eed3658b3bb4b94d06ee4af897d6bc2445ff0b110f73a9179e"
19
19
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fly",
3
- "version": "0.102.0",
3
+ "version": "0.104.0",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Fly"
6
6
  }
@@ -1 +1 @@
1
- {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,6BAA6B,CAAC;AAwHhF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UAiJ3B,CAAC"}
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,6BAA6B,CAAC;AA0JhF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,UAwJ3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-fly",
3
- "version": "0.102.0",
3
+ "version": "0.104.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/",
@@ -69,7 +69,7 @@
69
69
  "zod": "^4.3.6"
70
70
  },
71
71
  "peerDependencies": {
72
- "@intentius/chant": "^0.102.0",
72
+ "@intentius/chant": "^0.104.0",
73
73
  "typescript": "^5.9.3",
74
74
  "zod": "^4.3.6"
75
75
  },
@@ -90,6 +90,22 @@ Each declared resource becomes a single flaps request keyed by its logical name:
90
90
 
91
91
  Every serialized machine carries the \`managed-by: chant\` ownership marker in \`config.metadata\`, which the owned-only prune reads back.
92
92
 
93
+ ## Telemetry attribution
94
+
95
+ Inside a workspace, the fly lexicon adds two variables to each Machine's \`config.env\`, so that its spans name the declaration that produced them ([#3060](https://github.com/INTENTIUS/chant/issues/3060), [ws-060](https://github.com/INTENTIUS/chant/blob/main/docs/design/decisions/ws-060-telemetry-attribution.md)).
96
+
97
+ \`\`\`json
98
+ "env": {
99
+ "PORT": "8080",
100
+ "OTEL_SERVICE_NAME": "notes",
101
+ "OTEL_RESOURCE_ATTRIBUTES": "chant.workspace=acme,chant.member=svc,chant.decl=web"
102
+ }
103
+ \`\`\`
104
+
105
+ \`OTEL_SERVICE_NAME\` is the name of the Machine's app on Fly, or the Machine's export name when the build can't tell which app owns it. \`chant.decl\` is the Machine's export name. \`deployment.environment.name\` is added when the build has an environment (\`--env\` or \`ownership.env\`), and an image pinned by digest adds \`service.version\`. A value the Machine already sets is kept, and only the missing attributes are added. An \`App\` has no env, so only Machines are stamped. A project with no \`chant.workspace.json\` builds the same plan as before unless \`telemetry.attribution: true\` opts it in, and \`telemetry.attribution: false\` turns stamping off inside a workspace.
106
+
107
+ A Machine's env values are literal strings, so the plan carries no reference to \`CHANT_RELEASE_ATTRIBUTES\` ([ws-081](https://github.com/INTENTIUS/chant/blob/main/docs/design/decisions/ws-081-release-attributes-at-deploy.md)). The \`fly-release\` step writes the release into the Machine it serves instead: on a stamped Machine, \`OTEL_RESOURCE_ATTRIBUTES\` gets \`service.version\` (the release's digest) and \`vcs.ref.head.revision\` (its commit), the same release its metadata names. A plain apply of the plan carries no release.
108
+
93
109
  ## Applying
94
110
 
95
111
  The output is applied against flaps directly (or the mudflaps emulator offline). Endpoint and auth come from \`FLY_FLAPS_BASE_URL\` and \`FLY_API_TOKEN\`.
@@ -128,6 +128,42 @@ describe("fly-release", () => {
128
128
  expect((m.config.env as Record<string, string>).APP_REVISION).toBe("aaaaaaa");
129
129
  });
130
130
 
131
+ /** The plan with a Machine the build stamped with telemetry attribution (#3060). */
132
+ function stampedPlan(attributes: string) {
133
+ const web = PLAN.web;
134
+ writeFileSync(
135
+ planPath,
136
+ JSON.stringify({
137
+ ...PLAN,
138
+ web: { ...web, body: { ...web.body, config: { ...web.body.config, env: { PORT: "8080", OTEL_SERVICE_NAME: "shop", OTEL_RESOURCE_ATTRIBUTES: attributes } } } },
139
+ }),
140
+ );
141
+ }
142
+
143
+ test("a stamped Machine's resource attributes name the release's digest and commit (#3061, ws-081)", async () => {
144
+ stampedPlan("chant.workspace=acme,chant.decl=web,service.version=sha256%3Aold");
145
+ const s = setup();
146
+ await s.release.run(CTX, input("sha256:aaaaaaaaaaaa"));
147
+ const env = s.fake.machine("shop", "web")!.config.env as Record<string, string>;
148
+ expect(env.OTEL_RESOURCE_ATTRIBUTES).toBe("chant.workspace=acme,chant.decl=web,service.version=sha256%3Aaaaaaaaaaaaa,vcs.ref.head.revision=aaaaaaa");
149
+ expect(env.PORT).toBe("8080");
150
+ // A rollback puts back the attributes of the release it restores.
151
+ await s.release.run(CTX, input("sha256:bbbbbbbbbbbb"));
152
+ await s.rollback.run(CTX, { plan: planPath, endpoint: ENDPOINT, wait: NO_WAIT, verify: { intervalMs: 1, timeoutMs: 20 } });
153
+ expect((s.fake.machine("shop", "web")!.config.env as Record<string, string>).OTEL_RESOURCE_ATTRIBUTES).toContain("service.version=sha256%3Aaaaaaaaaaaaa");
154
+ });
155
+
156
+ test("a Machine the build did not stamp, or a caller's own value, gets no release attributes", async () => {
157
+ stampedPlan("team=core");
158
+ const s = setup();
159
+ await s.release.run(CTX, input("sha256:aaaaaaaaaaaa"));
160
+ expect((s.fake.machine("shop", "web")!.config.env as Record<string, string>).OTEL_RESOURCE_ATTRIBUTES).toBe("team=core");
161
+
162
+ stampedPlan("chant.decl=web");
163
+ await s.release.run(CTX, input("sha256:bbbbbbbbbbbb", { env: { OTEL_RESOURCE_ATTRIBUTES: "mine=1" } }));
164
+ expect((s.fake.machine("shop", "web")!.config.env as Record<string, string>).OTEL_RESOURCE_ATTRIBUTES).toBe("mine=1");
165
+ });
166
+
131
167
  test("each migration fires once per environment, then the Machine restarts", async () => {
132
168
  const s = setup();
133
169
  const migrations = [
@@ -18,7 +18,10 @@
18
18
  * `APP_REVISION` env var (#2834, as chud's fly-site.mjs set it), so the
19
19
  * app's own `/health` can report it. A caller's `env.APP_REVISION`
20
20
  * overrides it. `fly-rollback` restores it for free: it puts back the
21
- * whole recorded config, env included;
21
+ * whole recorded config, env included. A Machine the build stamped with
22
+ * telemetry attribution (#3060) also gets the release's `service.version`
23
+ * and `vcs.ref.head.revision` in its `OTEL_RESOURCE_ATTRIBUTES` (#3061,
24
+ * ws-081; see {@link releaseResourceAttributes});
22
25
  * 2. migrate: each migration runs inside the Machine once per environment,
23
26
  * witnessed by a receipt in chant's lifecycle receipt store
24
27
  * (`@intentius/chant/op/lifecycle-receipt-store`), then the Machine restarts
@@ -60,6 +63,7 @@ import {
60
63
  import { defaultFlyHttp, parsePlan, resolveEndpoint, type FlyHttp, type WaitOpts } from "../op/activities/fly-apply";
61
64
  import type { MachineConfigStore } from "../release-store";
62
65
  import { readFileSync } from "node:fs";
66
+ import { releaseAttributesSuffix, type ReleaseIdentity } from "@intentius/chant/telemetry-attribution";
63
67
 
64
68
  /** One migration, run inside the Machine once per environment. */
65
69
  export interface FlyMigration {
@@ -244,6 +248,28 @@ export function migrationReceipt(app: string, migration: FlyMigration): { ref: E
244
248
  };
245
249
  }
246
250
 
251
+ /**
252
+ * A stamped Machine's `OTEL_RESOURCE_ATTRIBUTES` with the release's attributes
253
+ * (#3061, ws-081), or undefined when the build did not stamp it (no
254
+ * `chant.decl` key) or the release knows neither attribute.
255
+ *
256
+ * A Machine's env values are literal, so the deploy writes the full value in
257
+ * place of a `CHANT_RELEASE_ATTRIBUTES` reference the platform would expand.
258
+ * The release's keys replace the same keys in the declared value, as the
259
+ * suffix appended last does on Compose and Kubernetes: a digest-pinned image
260
+ * gave the build a `service.version`, and the release may run another image.
261
+ */
262
+ export function releaseResourceAttributes(declared: unknown, release: ReleaseIdentity): string | undefined {
263
+ if (typeof declared !== "string") return undefined;
264
+ const pairs = declared.split(",").filter((kv) => kv !== "");
265
+ const key = (kv: string) => kv.split("=")[0]!.trim();
266
+ if (!pairs.some((kv) => key(kv) === "chant.decl")) return undefined;
267
+ const added = releaseAttributesSuffix(release).split(",").filter((kv) => kv !== "");
268
+ if (added.length === 0) return undefined;
269
+ const replaced = new Set(added.map(key));
270
+ return [...pairs.filter((kv) => !replaced.has(key(kv))), ...added].join(",");
271
+ }
272
+
247
273
  function loadTarget(planPath: string, machine?: string) {
248
274
  const plan = parsePlan(readFileSync(planPath, "utf8"));
249
275
  return { plan, target: releaseTarget(plan, machine) };
@@ -280,7 +306,7 @@ export function createFlyReleaseCapability(deps: FlyReleaseDeps = {}): Capabilit
280
306
  async run(ctx, input) {
281
307
  if (!input.digest) throw new Error("fly-release: a release needs its digest");
282
308
  const http = httpFor();
283
- const { plan } = loadTarget(input.plan, input.machine);
309
+ const { plan, target } = loadTarget(input.plan, input.machine);
284
310
  const gitSha = input.gitSha ?? (await (deps.headCommit ?? defaultHeadCommit)());
285
311
  const configs = await (deps.configStore ?? defaultConfigStore)(ctx);
286
312
  // The tree is checked against its digest before the Machine changes.
@@ -289,7 +315,15 @@ export function createFlyReleaseCapability(deps: FlyReleaseDeps = {}): Capabilit
289
315
  // APP_REVISION carries the commit into the Machine's env, as chud's
290
316
  // fly-site.mjs did (chant#2834), so the app's own /health can report
291
317
  // it. A caller's own APP_REVISION (input.env) wins over the commit.
292
- const env = { APP_REVISION: gitSha, ...(input.env ?? {}) };
318
+ // A stamped Machine's resource attributes name the same release as its
319
+ // metadata (#3061, ws-081), unless the caller sets its own.
320
+ const declaredEnv = (target.request.body.config as { env?: Record<string, unknown> } | undefined)?.env;
321
+ const attributes = releaseResourceAttributes(declaredEnv?.OTEL_RESOURCE_ATTRIBUTES, { version: input.digest, revision: gitSha });
322
+ const env = {
323
+ APP_REVISION: gitSha,
324
+ ...(attributes !== undefined ? { OTEL_RESOURCE_ATTRIBUTES: attributes } : {}),
325
+ ...(input.env ?? {}),
326
+ };
293
327
 
294
328
  const released = await flyMachineRelease(
295
329
  {
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Telemetry attribution in the fly serializer (#3060): each Machine's
3
+ * `config.env` gets `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` when the
4
+ * build hands it `context.telemetry`, and nothing changes without it.
5
+ */
6
+ import { describe, expect, it } from "vitest";
7
+ import type { Declarable } from "@intentius/chant";
8
+ import { flySerializer } from "./serializer";
9
+ import { App, Machine, MachineConfig } from "./generated/index";
10
+
11
+ const telemetry = { workspace: "acme", member: "svc", environment: "prod" };
12
+
13
+ function stack(...entries: Array<[string, unknown]>): Map<string, Declarable> {
14
+ return new Map(entries as Array<[string, Declarable]>);
15
+ }
16
+
17
+ const site = (config: Record<string, unknown>) =>
18
+ stack(["app", new App({ name: "notes", org_slug: "acme" })], ["web", new Machine({ name: "web", config: new MachineConfig(config) })]);
19
+
20
+ const build = (entities: Map<string, Declarable>, withTelemetry = true) =>
21
+ JSON.parse(flySerializer.serialize(entities, undefined, withTelemetry ? { telemetry } : undefined) as string);
22
+
23
+ describe("fly telemetry attribution (#3060)", () => {
24
+ it("stamps each Machine with the app's name and the Machine's declaration id", () => {
25
+ const out = build(site({ image: "node:22-slim", env: { PORT: "8080" } }));
26
+ expect(out.web.body.config.env).toEqual({
27
+ PORT: "8080",
28
+ OTEL_SERVICE_NAME: "notes",
29
+ OTEL_RESOURCE_ATTRIBUTES: "chant.workspace=acme,chant.member=svc,chant.decl=web,deployment.environment.name=prod",
30
+ });
31
+ // The App's create body has no env.
32
+ expect(out.app.body).toEqual({ app_name: "notes", org_slug: "acme" });
33
+ });
34
+
35
+ it("adds an env to a Machine that declares none", () => {
36
+ const out = build(site({ image: "node:22-slim" }));
37
+ expect(out.web.body.config.env.OTEL_SERVICE_NAME).toBe("notes");
38
+ });
39
+
40
+ it("keeps the values a Machine sets and appends the missing attributes", () => {
41
+ const out = build(
42
+ site({
43
+ image: "node:22-slim",
44
+ env: { OTEL_SERVICE_NAME: "notes-web", OTEL_RESOURCE_ATTRIBUTES: "chant.member=team-a,team=core" },
45
+ }),
46
+ );
47
+ expect(out.web.body.config.env).toEqual({
48
+ OTEL_SERVICE_NAME: "notes-web",
49
+ OTEL_RESOURCE_ATTRIBUTES: "chant.member=team-a,team=core,chant.workspace=acme,chant.decl=web,deployment.environment.name=prod",
50
+ });
51
+ });
52
+
53
+ it("gives service.version from an image pinned by digest", () => {
54
+ const digest = "sha256:" + "a".repeat(64);
55
+ const out = build(site({ image: `registry.fly.io/notes@${digest}` }));
56
+ expect(out.web.body.config.env.OTEL_RESOURCE_ATTRIBUTES).toBe(
57
+ `chant.workspace=acme,chant.member=svc,chant.decl=web,deployment.environment.name=prod,service.version=${encodeURIComponent(digest)}`,
58
+ );
59
+ });
60
+
61
+ it("names the Machine's app when the stack has several, and falls back to the export name when none is resolved", () => {
62
+ const out = build(
63
+ stack(
64
+ ["a", new App({ name: "alpha" })],
65
+ ["b", new App({ name: "beta" })],
66
+ ["worker", new Machine({ app: "beta", config: new MachineConfig({ image: "x" }) })],
67
+ ["loose", new Machine({ config: new MachineConfig({ image: "x" }) })],
68
+ ),
69
+ );
70
+ expect(out.worker.body.config.env.OTEL_SERVICE_NAME).toBe("beta");
71
+ expect(out.loose.body.config.env.OTEL_SERVICE_NAME).toBe("loose");
72
+ });
73
+
74
+ it("writes the same bytes as before without context.telemetry", () => {
75
+ const entities = site({ image: "node:22-slim", env: { PORT: "8080" } });
76
+ const plain = flySerializer.serialize(entities) as string;
77
+ expect(plain).not.toContain("OTEL_");
78
+ expect(flySerializer.serialize(entities, undefined, {})).toBe(plain);
79
+ });
80
+ });
package/src/serializer.ts CHANGED
@@ -36,6 +36,7 @@ import type { LexiconOutput } from "@intentius/chant/lexicon-output";
36
36
  import { ownershipEntries, OWNERSHIP_MANAGED_BY_VALUE } from "@intentius/chant/ownership";
37
37
  import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
38
38
  import { INTRINSIC_MARKER } from "@intentius/chant/intrinsic";
39
+ import { mergeResourceAttributes, telemetryEnvironment, type TelemetryAttribution } from "@intentius/chant/telemetry-attribution";
39
40
  import { FLY_METADATA_OWNERSHIP_KEYS } from "./ownership";
40
41
 
41
42
  const APP_ENTITY_TYPE = "Fly::Machines::App";
@@ -151,6 +152,39 @@ function appName(entity: Declarable, entityName: string): string {
151
152
  return typeof name === "string" ? name : entityName;
152
153
  }
153
154
 
155
+ // ── Telemetry attribution (#3060, D22) ────────────────────────────
156
+
157
+ /** The digest of an image reference pinned with `@sha256:...`, or undefined. */
158
+ function imageDigest(image: unknown): string | undefined {
159
+ if (typeof image !== "string") return undefined;
160
+ const at = image.lastIndexOf("@");
161
+ return at === -1 ? undefined : image.slice(at + 1);
162
+ }
163
+
164
+ /**
165
+ * Stamp one Machine's attributes as `OTEL_SERVICE_NAME` and
166
+ * `OTEL_RESOURCE_ATTRIBUTES` in its `config.env`, the env the Machine's
167
+ * process gets. `service` is the owning app's name on Fly, `decl` the
168
+ * Machine's declaration id. A value the Machine already sets is kept: its own
169
+ * `OTEL_SERVICE_NAME` wins, and its own resource attributes keep their keys
170
+ * while the missing ones are appended.
171
+ *
172
+ * Machine env values are literal strings, so there is no reference to the
173
+ * release attributes here: `fly-release` writes them into the value it
174
+ * deploys (#3061, ws-081).
175
+ */
176
+ function stampTelemetry(config: Record<string, unknown>, service: string, decl: string, telemetry: TelemetryAttribution): void {
177
+ const version = imageDigest(config.image);
178
+ const vars = telemetryEnvironment(telemetry, { service, decl, ...(version ? { version } : {}) });
179
+ const env: Record<string, unknown> =
180
+ config.env && typeof config.env === "object" && !Array.isArray(config.env) ? { ...(config.env as Record<string, unknown>) } : {};
181
+ if (!("OTEL_SERVICE_NAME" in env)) env.OTEL_SERVICE_NAME = vars.OTEL_SERVICE_NAME;
182
+ const own = env.OTEL_RESOURCE_ATTRIBUTES;
183
+ if (own === undefined) env.OTEL_RESOURCE_ATTRIBUTES = vars.OTEL_RESOURCE_ATTRIBUTES;
184
+ else if (typeof own === "string") env.OTEL_RESOURCE_ATTRIBUTES = mergeResourceAttributes(own, vars.OTEL_RESOURCE_ATTRIBUTES);
185
+ config.env = env;
186
+ }
187
+
154
188
  /**
155
189
  * fly flaps serializer.
156
190
  */
@@ -242,8 +276,15 @@ export const flySerializer: Serializer = {
242
276
  }
243
277
  body.config = config;
244
278
 
279
+ // Telemetry attribution (#3060): service.name is the app's name on
280
+ // Fly, or the Machine's export name when no app is resolved.
281
+ const owningApp = resolveOwningApp(entity);
282
+ if (context?.telemetry) {
283
+ stampTelemetry(config, owningApp === "{app}" ? name : owningApp, name, context.telemetry);
284
+ }
285
+
245
286
  requests[name] = {
246
- endpoint: `/v1/apps/${resolveOwningApp(entity)}/machines`,
287
+ endpoint: `/v1/apps/${owningApp}/machines`,
247
288
  method: "POST",
248
289
  body,
249
290
  };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Telemetry attribution through a real fly build (#3060): inside a workspace
3
+ * each Machine's env names the workspace and the member,
4
+ * `telemetry.attribution: false` turns that off, and outside a workspace
5
+ * nothing is stamped unless `telemetry.attribution: true` opts in.
6
+ */
7
+ import { afterAll, describe, expect, test } from "vitest";
8
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { dirname, join, resolve } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { buildCommand } from "@intentius/chant/cli/commands/build";
13
+ import { loadPlugins, resolveProjectLexicons } from "@intentius/chant/cli";
14
+
15
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
16
+ const made: string[] = [];
17
+ afterAll(() => {
18
+ for (const dir of made) rmSync(dir, { recursive: true, force: true });
19
+ });
20
+
21
+ const APP = `import { App, Machine, MachineConfig } from "@intentius/chant-lexicon-fly";
22
+
23
+ export const app = new App({ name: "notes", org_slug: "acme" });
24
+
25
+ export const web = new Machine({
26
+ name: "web",
27
+ region: "iad",
28
+ config: new MachineConfig({ image: "node:22-slim", env: { PORT: "8080" } }),
29
+ });
30
+ `;
31
+
32
+ /** A fly project, inside a one-member workspace or on its own, built to its flaps plan. */
33
+ async function build(options: { workspace: boolean; attribution?: boolean }): Promise<Record<string, { body: { config?: { env?: Record<string, string> } } }>> {
34
+ const root = mkdtempSync(join(tmpdir(), "chant-3060-"));
35
+ made.push(root);
36
+ mkdirSync(join(root, ".git"));
37
+ const project = options.workspace ? join(root, "svc") : root;
38
+ mkdirSync(join(project, "src"), { recursive: true });
39
+ if (options.workspace) {
40
+ writeFileSync(
41
+ join(root, "chant.workspace.json"),
42
+ JSON.stringify({ name: "acme", schema: 1, members: [{ name: "svc", dir: "svc", kind: "chant" }] }, null, 2),
43
+ );
44
+ }
45
+ const telemetry = options.attribution === undefined ? "" : ` telemetry: { attribution: ${options.attribution} },\n`;
46
+ writeFileSync(join(project, "chant.config.ts"), `export default {\n lexicons: ["fly"],\n${telemetry}};\n`);
47
+ writeFileSync(join(project, "src", "app.ts"), APP);
48
+ symlinkSync(join(repoRoot, "node_modules"), join(project, "node_modules"), "dir");
49
+
50
+ const src = join(project, "src");
51
+ const plugins = await loadPlugins(await resolveProjectLexicons(src));
52
+ const serializers = plugins.map((p) => p.serializer).filter((s) => s.name === "fly");
53
+ const output = join(project, "fly.json");
54
+ const result = await buildCommand({ path: src, output, format: "json", serializers, plugins });
55
+ expect(result.success, result.errors.join("\n")).toBe(true);
56
+ return JSON.parse(readFileSync(output, "utf-8"));
57
+ }
58
+
59
+ describe("telemetry attribution in a fly build (#3060)", () => {
60
+ test("inside a workspace the Machine names the workspace, the member and the declaration", async () => {
61
+ const env = (await build({ workspace: true })).web.body.config!.env!;
62
+ expect(env.OTEL_SERVICE_NAME).toBe("notes");
63
+ expect(env.OTEL_RESOURCE_ATTRIBUTES).toBe("chant.workspace=acme,chant.member=svc,chant.decl=web");
64
+ });
65
+
66
+ test("telemetry.attribution false turns it off inside a workspace", async () => {
67
+ expect(JSON.stringify(await build({ workspace: true, attribution: false }))).not.toContain("OTEL_");
68
+ });
69
+
70
+ test("outside a workspace nothing is stamped, and telemetry.attribution true opts in without workspace or member", async () => {
71
+ expect(JSON.stringify(await build({ workspace: false }))).not.toContain("OTEL_");
72
+ const env = (await build({ workspace: false, attribution: true })).web.body.config!.env!;
73
+ expect(env.OTEL_RESOURCE_ATTRIBUTES).toBe("chant.decl=web");
74
+ });
75
+ });