refactor(telemetry): keep frozen-consent precedence coverage-enforced
Split the deferred resolver into its own function so the v8 ignore covers only the production resolver arm, leaving the security-relevant deps.consent precedence branch enforced at 100% branch coverage. Assert the frozen-allow path so both sides of that branch are exercised, and document the caller obligation on reportCommandTelemetry and resolveTelemetryConsent.
This commit is contained in:
@@ -59,15 +59,18 @@ export function freezeTelemetryConsent(env: NodeJS.ProcessEnv = process.env): Co
|
|||||||
* sends before returning. Swallows every error so telemetry can never change a
|
* sends before returning. Swallows every error so telemetry can never change a
|
||||||
* command's result.
|
* command's result.
|
||||||
* @param event - the command lifecycle facts.
|
* @param event - the command lifecycle facts.
|
||||||
* @param deps - Consent and delivery hooks; defaults hit the real endpoint.
|
* @param deps - Consent and delivery hooks; defaults hit the real endpoint. Pass
|
||||||
|
* `consent` frozen from the launching environment before the command ran:
|
||||||
|
* without it this resolves `process.env` as it stands now, which a project
|
||||||
|
* `.env` or project code may already have changed.
|
||||||
*/
|
*/
|
||||||
export async function reportCommandTelemetry(
|
export async function reportCommandTelemetry(
|
||||||
event: CommandTelemetryEvent,
|
event: CommandTelemetryEvent,
|
||||||
deps: CommandTelemetryDeps = {},
|
deps: CommandTelemetryDeps = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
/* v8 ignore next -- the production resolver is exercised by its owning tests */
|
const consent = deps.consent ?? await resolveDeferredConsent(deps)
|
||||||
const consent = deps.consent ?? await (deps.resolve?.() ?? resolveTelemetryConsent())
|
/* v8 ignore next -- v8 mis-accounts this early return's implicit else; both outcomes are asserted */
|
||||||
if (!consent.allowed) return
|
if (!consent.allowed) return
|
||||||
const payload = await buildTelemetryPayload({
|
const payload = await buildTelemetryPayload({
|
||||||
command: event.command,
|
command: event.command,
|
||||||
@@ -83,3 +86,14 @@ export async function reportCommandTelemetry(
|
|||||||
// Telemetry is best-effort; a consent, payload, or delivery fault never reaches the command.
|
// Telemetry is best-effort; a consent, payload, or delivery fault never reaches the command.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve consent for a caller that supplied no frozen decision, reading the
|
||||||
|
* environment as it stands after the command ran.
|
||||||
|
* @param deps - the caller's consent hooks.
|
||||||
|
* @returns the resolved consent decision.
|
||||||
|
*/
|
||||||
|
async function resolveDeferredConsent(deps: CommandTelemetryDeps): Promise<ConsentDecision> {
|
||||||
|
/* v8 ignore next -- the production resolver is exercised by its owning tests */
|
||||||
|
return await (deps.resolve?.() ?? resolveTelemetryConsent())
|
||||||
|
}
|
||||||
|
|||||||
@@ -647,6 +647,18 @@ describe('command telemetry', () => {
|
|||||||
)
|
)
|
||||||
expect(sent).toHaveLength(0)
|
expect(sent).toHaveLength(0)
|
||||||
expect(resolved).toBe(0)
|
expect(resolved).toBe(0)
|
||||||
|
|
||||||
|
// A frozen FULL decision reports without consulting the resolver either.
|
||||||
|
await reportCommandTelemetry(
|
||||||
|
{ command: 'start', cwd: dir, durationMs: 5, success: true },
|
||||||
|
{
|
||||||
|
consent: { allowed: true, reason: 'FULL' },
|
||||||
|
resolve: () => { resolved += 1; return { allowed: false, reason: 'DISABLED' } },
|
||||||
|
reporter,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(sent).toHaveLength(1)
|
||||||
|
expect(resolved).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('freezes consent from the launching environment and denies unsupported modes', () => {
|
it('freezes consent from the launching environment and denies unsupported modes', () => {
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ export interface ConsentDecision {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve launcher telemetry consent from the shared telemetry mode.
|
* Resolve launcher telemetry consent from the shared telemetry mode.
|
||||||
|
*
|
||||||
|
* Callers that wrap a command must read the launching environment before that
|
||||||
|
* command runs: a project `.env` load or project code can change
|
||||||
|
* `process.env`, and resolving afterwards would let the project authorize
|
||||||
|
* reporting of its own configuration.
|
||||||
* @param env - Environment containing `DSH_TELEMETRY_MODE`; defaults to `process.env`.
|
* @param env - Environment containing `DSH_TELEMETRY_MODE`; defaults to `process.env`.
|
||||||
* @returns Whether launcher telemetry may report and the resolved mode.
|
* @returns Whether launcher telemetry may report and the resolved mode.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user