fix(ci): telemetry switch trivially satisfied without the row; coverage-lane test fixes
A custom profile that mounts no telemetry-otel row exports nothing, so DSH_TELEMETRY_DISABLED must not fail its boot (CI exports the switch globally, which broke the lifecycle-fixture profile). The web-app dist resolution test accepts the fail-loud unbuilt outcome the CI coverage lane sees before any build, and the headless spec covers the idle-anchor and pre-start skip branches under the per-file gate.
This commit is contained in:
@@ -49,18 +49,16 @@ const PROFILE_ROOT_FILENAME = 'cordis.yml'
|
||||
/**
|
||||
* Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
|
||||
* value (including `'0'`/`'false'`) disables: a privacy switch prefers
|
||||
* off-by-mistake over on-by-mistake. Throws when the switch is set but the
|
||||
* row is absent — a silently no-op "disabled" privacy switch would keep
|
||||
* exporting while the user believes it is off.
|
||||
* off-by-mistake over on-by-mistake. A composition without the telemetry row
|
||||
* exports nothing, so the switch is then trivially satisfied and no patch is
|
||||
* generated — custom profiles need not mount telemetry to run with the
|
||||
* switch set.
|
||||
* @param disabledEnv - the raw `DSH_TELEMETRY_DISABLED` value (`undefined` when unset).
|
||||
* @param hasRow - whether the composition carries the telemetry row.
|
||||
* @returns the disable patch, or `undefined` when telemetry stays enabled.
|
||||
* @returns the disable patch, or `undefined` when telemetry stays enabled or is not mounted.
|
||||
*/
|
||||
export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: boolean): PatchOptions | undefined {
|
||||
if ((disabledEnv ?? '') === '') return undefined
|
||||
if (!hasRow) {
|
||||
throw new Error(`dsh: DSH_TELEMETRY_DISABLED is set but row "${TELEMETRY_ROW_ID}" is not in this composition`)
|
||||
}
|
||||
if ((disabledEnv ?? '') === '' || !hasRow) return undefined
|
||||
return { id: TELEMETRY_ROW_ID, disabled: true }
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,10 @@ describe('resolveTelemetryPatch', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('fails loud when the switch is set but the row is absent', () => {
|
||||
expect(() => resolveTelemetryPatch('1', false)).toThrow('DSH_TELEMETRY_DISABLED is set but row "telemetry-otel" is not in this composition')
|
||||
})
|
||||
|
||||
it('ignores a missing row while the switch is unset', () => {
|
||||
it('is trivially satisfied by a composition without the telemetry row', () => {
|
||||
// A custom profile need not mount telemetry: nothing exports, so the
|
||||
// privacy switch has nothing to disable and generates no patch.
|
||||
expect(resolveTelemetryPatch('1', false)).toBeUndefined()
|
||||
expect(resolveTelemetryPatch(undefined, false)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -67,8 +67,11 @@ async function run(events: ScriptedEvent[], options: { promptFails?: boolean } =
|
||||
ctx.provide('httpServer', { port: 12345 } as never)
|
||||
apply(ctx, { task: 'do the thing' })
|
||||
// Quiescence is out of band: give the scripted stream a beat to drain, then
|
||||
// flip the agent idle exactly as the loop would.
|
||||
// flip the agent idle exactly as the loop would. Foreign agents and
|
||||
// non-idle transitions must not settle the run.
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
ctx.emit('agent/status', { id: 'OTHER' } as Agent, 'idle')
|
||||
ctx.emit('agent/status', { id: 'S1' } as Agent, 'running')
|
||||
ctx.emit('agent/status', { id: 'S1' } as Agent, 'idle')
|
||||
const code = await exited
|
||||
await ctx.fiber.dispose()
|
||||
@@ -86,6 +89,8 @@ const end = (turn: number, reason: string): ScriptedEvent => ({ type: 'turn/end'
|
||||
describe('headless runner', () => {
|
||||
it('aggregates to quiescence: last text wins across turns, final turn-end reason maps to exit 0', async () => {
|
||||
const { code, out, err } = await run([
|
||||
// Frames before the first turn/start are outside the task interval.
|
||||
{ type: 'assistant/message', data: { turn: 0, message: { content: [{ type: 'text', text: 'pre-task noise' }] } } },
|
||||
startupTurn,
|
||||
// Off-session, non-text, and text-empty frames never affect the aggregate.
|
||||
{ type: 'assistant/message', sessionId: 'OTHER', data: { turn: 1, message: { content: [{ type: 'text', text: 'other session' }] } } },
|
||||
|
||||
@@ -163,9 +163,15 @@ describe('web-app runtime glue', () => {
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('resolves the real built frontend dist through the package exports', () => {
|
||||
// The production resolver (not the test seam): this checkout builds the
|
||||
// dist, so the resolved path must be the frontend package's index.html.
|
||||
expect(originalResolve()).toMatch(/dist[/\\]index\.html$/)
|
||||
it('resolves the real built frontend dist through the package exports, failing loud unbuilt', () => {
|
||||
// The production resolver (not the test seam). A built checkout resolves
|
||||
// the frontend package's index.html; a dist-less one (the CI coverage
|
||||
// lane runs before any build) must fail with the build hint, never a
|
||||
// silent fallback.
|
||||
try {
|
||||
expect(originalResolve()).toMatch(/dist[/\\]index\.html$/)
|
||||
} catch (error) {
|
||||
expect((error as Error).message).toContain('frontend dist not built')
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user