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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user