Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/cli/src/commands/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ import UI from "../userInteraction";
import { editDistance, getSystemInputs } from "../utils";
import { helper } from "./helper";

// Tools permitted to identify themselves as the atk caller via ATK_CALLER.
// Any other value is mapped to "other" so the caller-source telemetry
// dimension stays low-cardinality and cannot leak free-form input (e.g. PII).
const KNOWN_CALLER_SOURCES = new Set(["wiqd"]);
const OTHER_CALLER_SOURCE = "other";

class CLIEngine {
/**
* @description cached debug logsd
Expand Down Expand Up @@ -539,12 +545,28 @@ class CLIEngine {
context.telemetryProperties[TelemetryProperty.Skill] = "true";
CliTelemetry.reporter?.addSharedProperty(TelemetryProperty.Skill, "true");
}
// Tag the invoking tool (e.g. wiqd) so wrapper-driven runs are distinguishable.
const caller = this.resolveCallerSource(process.env.ATK_CALLER);
if (caller) {
context.telemetryProperties[TelemetryProperty.CallerSource] = caller;
CliTelemetry.reporter?.addSharedProperty(TelemetryProperty.CallerSource, caller);
}
// context.telemetryProperties[TelemetryProperty.CorrelationId] =
// context.optionValues.correlationId;

return ok(undefined);
}

// ATK_CALLER is free-form env input, so normalize for matching and resolve
// against a known-caller allowlist; any recognized value maps to itself, any
// other declared value collapses to "other" to keep the telemetry dimension
// low-cardinality and free of PII. Blank/unset stays untagged.
private resolveCallerSource(raw?: string): string | undefined {
const value = (raw ?? "").trim().toLowerCase();
if (!value) return undefined;
return KNOWN_CALLER_SOURCES.has(value) ? value : OTHER_CALLER_SOURCE;
}

validateOptionsAndArguments(
command: CLIFoundCommand
): Result<
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/telemetry/cliTelemetryEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export enum TelemetryProperty {
CommandVerbose = "command-verbose",
CommandInteractive = "command-interactive",
BinName = "bin-name", // the input binary name: atk
CallerSource = "caller-source", // invoking tool/surface (e.g. "wiqd"), from ATK_CALLER
}

export enum TelemetrySuccess {
Expand Down
112 changes: 112 additions & 0 deletions packages/cli/tests/unit/engine.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,4 +746,116 @@ describe("CLI Engine", () => {
mockedEnvRestore();
});
});

describe("ATK_CALLER env var", () => {
it("sets CallerSource to a known caller from ATK_CALLER", async () => {
const mockedEnvRestore = mockedEnv({
ATK_CALLER: " WIQD ",
});
const command: CLIFoundCommand = {
name: "test",
fullName: "test",
description: "test command",
};
const ctx: CLIContext = {
command: command,
optionValues: {},
globalOptionValues: {},
argumentValues: [],
telemetryProperties: {},
};
const result = engine.parseArgs(ctx, rootCommand, []);
assert.isTrue(result.isOk());
assert.equal(ctx.telemetryProperties[TelemetryProperty.CallerSource], "wiqd");
mockedEnvRestore();
});

it("does not set CallerSource when ATK_CALLER is not set", async () => {
const mockedEnvRestore = mockedEnv({
ATK_CALLER: undefined,
});
const command: CLIFoundCommand = {
name: "test",
fullName: "test",
description: "test command",
};
const ctx: CLIContext = {
command: command,
optionValues: {},
globalOptionValues: {},
argumentValues: [],
telemetryProperties: {},
};
const result = engine.parseArgs(ctx, rootCommand, []);
assert.isTrue(result.isOk());
assert.notProperty(ctx.telemetryProperties, TelemetryProperty.CallerSource);
mockedEnvRestore();
});

it('maps an unknown ATK_CALLER to "other"', async () => {
const mockedEnvRestore = mockedEnv({
ATK_CALLER: "johnsmith",
});
const command: CLIFoundCommand = {
name: "test",
fullName: "test",
description: "test command",
};
const ctx: CLIContext = {
command: command,
optionValues: {},
globalOptionValues: {},
argumentValues: [],
telemetryProperties: {},
};
const result = engine.parseArgs(ctx, rootCommand, []);
assert.isTrue(result.isOk());
assert.equal(ctx.telemetryProperties[TelemetryProperty.CallerSource], "other");
mockedEnvRestore();
});

it('maps a near-miss of a known caller to "other" (no partial matching)', async () => {
const mockedEnvRestore = mockedEnv({
ATK_CALLER: "wiqd-cli",
});
const command: CLIFoundCommand = {
name: "test",
fullName: "test",
description: "test command",
};
const ctx: CLIContext = {
command: command,
optionValues: {},
globalOptionValues: {},
argumentValues: [],
telemetryProperties: {},
};
const result = engine.parseArgs(ctx, rootCommand, []);
assert.isTrue(result.isOk());
assert.equal(ctx.telemetryProperties[TelemetryProperty.CallerSource], "other");
mockedEnvRestore();
});

it("does not set CallerSource when ATK_CALLER is blank", async () => {
const mockedEnvRestore = mockedEnv({
ATK_CALLER: " ",
});
const command: CLIFoundCommand = {
name: "test",
fullName: "test",
description: "test command",
};
const ctx: CLIContext = {
command: command,
optionValues: {},
globalOptionValues: {},
argumentValues: [],
telemetryProperties: {},
};
const result = engine.parseArgs(ctx, rootCommand, []);
assert.isTrue(result.isOk());
assert.notProperty(ctx.telemetryProperties, TelemetryProperty.CallerSource);
mockedEnvRestore();
});
});
});
Loading