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:
Turtle
2026-08-06 07:30:32 +08:00
parent af5f528903
commit 273f27260d
4 changed files with 26 additions and 18 deletions
+6 -8
View File
@@ -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 }
}
+4 -5
View File
@@ -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' }] } } },
+10 -4
View File
@@ -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')
}
})
})