Runtime observability: report the build and canary state the system acts on

The rollout stalled at RUNTIME_UNVERIFIED because two facts were answerable only
as a side effect of a scheduled snapshot writing a row: which build is running,
and whether MLB lineage is effectively enabled. Every state transition therefore
waited on cron rather than on asking the service.

- src/services/lineageCanaryConfig.js — THE canary resolver. Parsed once at
  module load (unchanged semantics), normalised sorted/deduped/trimmed, frozen.
  snapshotService's write gate now delegates to it, and the status probe reads
  the SAME state. A route that parsed the environment itself would be a second
  version of the truth, free to drift from the gate it claims to report.
- GET /api/internal/snapshot/status gains runtime.code_sha (the production
  codeSha resolver — never git, never gitea/main; null when unavailable),
  runtime.started_at (computed ONCE at module load, so it marks a boundary
  rather than reading as now; deliberately not called deployed_at), and
  lineage_canary {enabled, sports, configuration_source}.
- No raw environment value is returned; sports is the normalised set and
  configuration_source says only ENVIRONMENT vs DEFAULT. Router-wide
  requireInternalAuth is unchanged: 200 with key, 401 without.
- Effective lineage config is fixed for the process lifetime, so
  runtime.started_at is a defensible lower bound for how long that state held.

Strictly observational — the handler still only reads Redis.

Model and decision code byte-identical to 8c6aef1: analyzeViaEngine1,
probabilityEstimator, gradeRanking, eventIdentity, gradeSlateService,
retentionService, ledgerService, mlbStatsAdapter.

Suite 381/5,081/0 from the release worktree; web tsc exit 0. Lineage stays OFF.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
This commit is contained in:
Kev
2026-08-27 17:16:05 -04:00
parent 8c6aef1e12
commit ceaa896f77
4 changed files with 338 additions and 3 deletions
+42
View File
@@ -16,6 +16,13 @@
const express = require('express');
const { requireInternalAuth } = require('../middleware/internalAuth');
/**
* The start of THIS Node process, computed ONCE so every call reports the same
* instant for one process lifetime. Deriving it per request from `Date.now()`
* would make it read as "now" and destroy its only use — marking a boundary.
*/
const PROCESS_STARTED_AT = new Date(Date.now() - Math.round(process.uptime() * 1000)).toISOString();
const tank01Prefetch = require('../../scripts/tank01-prefetch');
const quotaTracker = require('../services/quotaTracker');
@@ -133,6 +140,28 @@ router.post('/snapshot/all', async (req, res) => {
* whether the in-process cron is armed, the freshest snapshot per sport, which
* pipeline Redis keys exist, and the ticker item count. Read-only; safe to poll.
* GET vs the POST /snapshot/:sport below — no route collision.
*
* -- RUNTIME IDENTITY (added for the MLB rollout) -------------------------
* The rollout stalled because nothing could answer "which build is running?"
* or "is lineage effectively on?" except as a side effect of a scheduled
* snapshot writing a row — so every state transition waited on cron.
*
* Two read-only fields close that:
*
* runtime.code_sha the SAME resolver production provenance uses
* (`retentionService.codeSha`). NEVER git HEAD, never
* gitea/main, never deployment intent. Unavailable
* returns null rather than a guess.
* runtime.started_at the start of THIS process. Deliberately not named
* `deployed_at` — a restart without a deploy moves it.
* lineage_canary the SAME frozen state the write gate consults
* (`lineageCanaryConfig`), never a second parse.
*
* No raw environment value is returned: `lineage_canary.sports` is the
* normalised set, and `configuration_source` says only whether the value came
* from the environment or the default.
*
* Still strictly observational — the handler only reads Redis.
*/
router.get('/snapshot/status', async (req, res) => {
const { cacheGet } = require('../utils/redis');
@@ -159,9 +188,22 @@ router.get('/snapshot/status', async (req, res) => {
}
const ticker = await cacheGet('ticker:items');
redis_keys['ticker:items'] = !!ticker;
// Same helpers the pipeline itself uses — one build identity, one canary parser.
const { codeSha } = require('../services/retentionService');
const lineageCanary = require('../services/lineageCanaryConfig');
// Session 56 — surface the missed-cron signal in the health probe.
const mlbTs = last_snapshot.mlb && last_snapshot.mlb.updated_at;
return res.json({
runtime: {
code_sha: codeSha(),
// Computed once at module load from process.uptime(), so repeated calls
// report one stable value for one process lifetime.
started_at: PROCESS_STARTED_AT,
},
// The effective lineage state is resolved once at module load and cannot
// change without a new process, so `runtime.started_at` is a defensible
// lower bound for how long this state has held.
lineage_canary: lineageCanary.state(),
cron_armed: process.env.SNAPSHOT_CRON === '1',
cron_hours_utc: HOURS_UTC,
last_snapshot,