One evaluator for the shadow, so the probe cannot report a mode the pipeline is not in
"Is the shadow effective?" was only answerable by waiting for a snapshot to write a row. That leaves a blind spot with real cost: a variable SET IN COOLIFY BUT NOT YET APPLIED to the running process is indistinguishable from an unset one, and the runtime probe already proves the distinction matters — code_sha22cf51cwith started_at 01:45:44Z means anything set after that is not in this process's environment. probabilityContract.shadowState() is now the single evaluator. snapshotService calls it and the protected status probe calls it, and a test asserts NEITHER reads process.env directly — the same rule that keeps lineage_write_mode honest. Reading the env in two places is how a status page and a gate come to disagree. Strict by construction: only the exact string '1' enables it. 'true', 'yes', 'on', '01', ' 1 ' and '' are all OFF, because a loose parse turns a typo into an activation. `configuration_source` separates an unset variable from one explicitly set to '0', and `live_serving` is reported as its own switch so the shadow can never be read as implying serving. No behaviour changes. The shadow still defaults OFF, CALIBRATION_DEPLOYED is still [], and served fields are untouched. Frontend byte-identical to the last green build (git reports zero changes under web/), so the build from22cf51cstands. Suite 401/401, 5,597 passed, 4 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
This commit is contained in:
@@ -128,7 +128,7 @@ describe('the flag is OFF by default', () => {
|
||||
it('snapshotService reads PROBABILITY_CONTRACT_SHADOW and defaults to off', () => {
|
||||
const src = require('fs').readFileSync(
|
||||
require('path').join(__dirname, '../../src/services/snapshotService.js'), 'utf8');
|
||||
expect(src).toContain("process.env.PROBABILITY_CONTRACT_SHADOW || '') === '1'");
|
||||
expect(src).toContain("probabilityContract').shadowState().shadow === 'ON'");
|
||||
// and it must not be able to write a served field
|
||||
const block = src.slice(src.indexOf('PROBABILITY CONTRACT SHADOW'), src.indexOf('let lineageIndex'));
|
||||
for (const served of ['g.p_win =', 'g.confidence =', 'g.grade =', 'g.ev_pct =', 'g.value =']) {
|
||||
@@ -183,3 +183,37 @@ describe('the artifact must agree with the contract, not merely accompany it', (
|
||||
expect(r.probability_contract.raw_model_probability).toBe(0.65);
|
||||
});
|
||||
});
|
||||
|
||||
describe('one evaluator for the shadow mode', () => {
|
||||
it('only the exact string 1 enables it — a loose parse turns a typo into an activation', () => {
|
||||
expect(pc.shadowState({ PROBABILITY_CONTRACT_SHADOW: '1' }).shadow).toBe('ON');
|
||||
// process.env values are always strings, so only string inputs are asserted;
|
||||
// claiming a numeric 1 should be OFF would be asserting a case the parse
|
||||
// never sees, and one where OFF is not obviously the right answer anyway.
|
||||
for (const v of ['true', 'yes', 'on', '0', ' 1 ', '', '01', 'TRUE']) {
|
||||
expect(pc.shadowState({ PROBABILITY_CONTRACT_SHADOW: v }).shadow).toBe('OFF');
|
||||
}
|
||||
expect(pc.shadowState({}).shadow).toBe('OFF');
|
||||
});
|
||||
|
||||
it('distinguishes an unset variable from one explicitly set', () => {
|
||||
expect(pc.shadowState({}).configuration_source).toBe('default');
|
||||
expect(pc.shadowState({ PROBABILITY_CONTRACT_SHADOW: '0' }).configuration_source).toBe('environment');
|
||||
});
|
||||
|
||||
it('the shadow never implies live serving', () => {
|
||||
expect(pc.shadowState({ PROBABILITY_CONTRACT_SHADOW: '1' }).live_serving).toBe('OFF');
|
||||
});
|
||||
|
||||
it('the pipeline and the status probe call the SAME evaluator, never a second parse', () => {
|
||||
const fs = require('fs'); const path = require('path');
|
||||
const snap = fs.readFileSync(path.join(__dirname, '../../src/services/snapshotService.js'), 'utf8');
|
||||
const route = fs.readFileSync(path.join(__dirname, '../../src/routes/internal.js'), 'utf8');
|
||||
expect(snap).toContain("probabilityContract').shadowState().shadow === 'ON'");
|
||||
expect(route).toContain("probabilityContract').shadowState()");
|
||||
// and NEITHER may read the raw env directly
|
||||
for (const src of [snap, route]) {
|
||||
expect(src.includes('process.env.PROBABILITY_CONTRACT_SHADOW')).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user