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:
@@ -276,6 +276,11 @@ router.get('/snapshot/status', async (req, res) => {
|
||||
// it from a second parse is how a status surface and a gate come to
|
||||
// disagree, so there is only one.
|
||||
lineage_write_mode: require('../services/lineageWriteMode').state(),
|
||||
// THE PROBABILITY-CONTRACT SHADOW, from the SAME evaluator the pipeline
|
||||
// calls. Without it "is the shadow effective?" is only answerable by
|
||||
// waiting for a snapshot to write, and a set-but-not-restarted variable
|
||||
// is indistinguishable from an unset one.
|
||||
probability_contract: require('../services/model/probabilityContract').shadowState(),
|
||||
// INDEPENDENT COVERAGE. Derived from durable retained state, never from
|
||||
// the writer's own counters — an observer that reads the failing writer's
|
||||
// return value cannot see that writer fail.
|
||||
|
||||
@@ -255,7 +255,36 @@ function confidenceDisplay(res) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* THE ONE EVALUATOR for the shadow's effective state.
|
||||
*
|
||||
* `snapshotService` and the status probe both call this, so the surface cannot
|
||||
* report a mode the pipeline is not in — 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: only the exact string '1' enables it. Anything else is OFF, including
|
||||
* 'true', 'yes' and ' 1 ' — a loose parse turns a typo into an activation.
|
||||
*/
|
||||
const SHADOW_ENV = 'PROBABILITY_CONTRACT_SHADOW';
|
||||
function shadowState(env = process.env) {
|
||||
const raw = env[SHADOW_ENV];
|
||||
const on = String(raw || '') === '1';
|
||||
return Object.freeze({
|
||||
shadow: on ? 'ON' : 'OFF',
|
||||
env_var: SHADOW_ENV,
|
||||
configuration_source: raw === undefined ? 'default' : 'environment',
|
||||
scope: on ? Object.freeze({ sport: 'mlb', stat: 'hits' }) : null,
|
||||
// Serving is a SEPARATE switch and is not implied by the shadow.
|
||||
live_serving: 'OFF',
|
||||
certified_support: MLB_HITS.certified_bands,
|
||||
estimator_version: MLB_HITS.estimator_version,
|
||||
model_version: MLB_HITS.model_version,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
STATE, CERTIFIED_STATES, ESTIMATOR, CONTRACTS, MLB_HITS,
|
||||
STATE, CERTIFIED_STATES, ESTIMATOR, CONTRACTS, MLB_HITS, SHADOW_ENV,
|
||||
contractFor, inCertifiedRawBand, resolve, isCertified, derivedClaims, confidenceDisplay,
|
||||
shadowState,
|
||||
};
|
||||
|
||||
@@ -858,7 +858,7 @@ async function runSnapshot(sport, opts = {}) {
|
||||
// this releases the support with activation off, which is the required order.
|
||||
// A failure here must never cost the snapshot — it is a measurement layer.
|
||||
let probContract = null;
|
||||
if (String(process.env.PROBABILITY_CONTRACT_SHADOW || '') === '1' && sp === 'mlb') {
|
||||
if (require('./model/probabilityContract').shadowState().shadow === 'ON' && sp === 'mlb') {
|
||||
try {
|
||||
const pcs = deps.probabilityContractService || require('./model/probabilityContractService');
|
||||
const sbc = deps.supabase || require('../utils/supabase').getSupabaseServiceClient();
|
||||
|
||||
Reference in New Issue
Block a user