-
Notifications
You must be signed in to change notification settings - Fork 2
Hub truth fixes: events-fallback timeline, honest integrity count, Proof Rail names its run (#496, #497, #498) #511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9597891
388e956
5047f89
8022258
d524b5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * #497 — the Diagnostics integrity signal is never a false zero. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * buildDiagnostics aggregated integrity detail over fully-parsed runs only; | ||||||||||||||||||||||||
| * index-served runs carry the has_integrity_errors boolean (persisted at | ||||||||||||||||||||||||
| * INDEX_VERSION 5 by the #316 parity fix) but not the integrity[] detail | ||||||||||||||||||||||||
| * array, so a damaged run served from the rollup index contributed nothing — | ||||||||||||||||||||||||
| * "Data integrity errors: 0" with a damaged run in plain view. Index-served | ||||||||||||||||||||||||
| * damage now counts and names its run; per-file detail honestly states it | ||||||||||||||||||||||||
| * needs a full parse. Full detail persistence rides the next INDEX_VERSION | ||||||||||||||||||||||||
| * bump (noted on the issue). | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * owner: RStack developed by Richardson Gunde | ||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import test from 'node:test'; | ||||||||||||||||||||||||
| import assert from 'node:assert/strict'; | ||||||||||||||||||||||||
| import { mkdtempSync, realpathSync, rmSync } from 'node:fs'; | ||||||||||||||||||||||||
| import { tmpdir } from 'node:os'; | ||||||||||||||||||||||||
| import { join } from 'node:path'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import { buildDiagnostics } from '../src/observability/dashboard/state/layers.js'; | ||||||||||||||||||||||||
| import { buildFullState } from '../src/observability/dashboard/state/index.js'; | ||||||||||||||||||||||||
| import { fixtureMalformedRun } from './helpers/dashboard-fixtures.js'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| test('index-served damaged runs count and are named — never a false zero (#497)', () => { | ||||||||||||||||||||||||
| const diagnostics = buildDiagnostics([ | ||||||||||||||||||||||||
| { runId: 'run-parsed-clean', fromIndex: false, tasks: [], events: [], integrity: [] }, | ||||||||||||||||||||||||
| { runId: 'run-parsed-damaged', fromIndex: false, tasks: [], events: [], integrity: [{ file: 'decisions.json', error: 'invalid JSON' }] }, | ||||||||||||||||||||||||
| { runId: 'run-indexed-damaged', fromIndex: true, hasIntegrityErrors: true, tasks: [], events: [] }, | ||||||||||||||||||||||||
| { runId: 'run-indexed-clean', fromIndex: true, hasIntegrityErrors: false, tasks: [], events: [] }, | ||||||||||||||||||||||||
| ], ['/tmp/root']); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| assert.equal(diagnostics.integrityErrorCount, 2, | ||||||||||||||||||||||||
| 'one parsed detail entry + one index-served damaged run'); | ||||||||||||||||||||||||
| const indexed = diagnostics.integrity.find((issue) => issue.runId === 'run-indexed-damaged'); | ||||||||||||||||||||||||
| assert.ok(indexed, 'the index-served damaged run is named in the problem list'); | ||||||||||||||||||||||||
| assert.match(indexed.error, /full parse|detail/i, | ||||||||||||||||||||||||
| 'the entry says honestly that per-file detail needs a full parse'); | ||||||||||||||||||||||||
| const parsed = diagnostics.integrity.find((issue) => issue.runId === 'run-parsed-damaged'); | ||||||||||||||||||||||||
| assert.equal(parsed.file, 'decisions.json', 'fully-parsed detail is unchanged'); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| test('fully-parsed damaged runs never double-count via their boolean (#497)', () => { | ||||||||||||||||||||||||
| const diagnostics = buildDiagnostics([ | ||||||||||||||||||||||||
| { runId: 'run-parsed-damaged', fromIndex: false, hasIntegrityErrors: true, tasks: [], events: [], integrity: [{ file: 'events.jsonl', error: 'truncated' }] }, | ||||||||||||||||||||||||
| ], ['/tmp/root']); | ||||||||||||||||||||||||
| assert.equal(diagnostics.integrityErrorCount, 1, 'the detail entry is the only count'); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| test('a damaged run stays visible in Diagnostics when served from the rollup index (#497)', async (t) => { | ||||||||||||||||||||||||
| const root = realpathSync(mkdtempSync(join(tmpdir(), 'rstack-integrity-497-'))); | ||||||||||||||||||||||||
| t.after(() => rmSync(root, { recursive: true, force: true })); | ||||||||||||||||||||||||
| await fixtureMalformedRun(root); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // First build parses fully and persists the rollup index; the second build | ||||||||||||||||||||||||
| // serves the (stale, inactive) damaged run from that index — the exact | ||||||||||||||||||||||||
| // path that reported zero. | ||||||||||||||||||||||||
| const first = await buildFullState(root, { includeRegistry: false }); | ||||||||||||||||||||||||
| assert.ok(first.diagnostics.integrityErrorCount >= 1, 'full parse sees the damage'); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const second = await buildFullState(root, { includeRegistry: false }); | ||||||||||||||||||||||||
| const served = second.runs.find((run) => run.runId === 'run-fx-damaged'); | ||||||||||||||||||||||||
| assert.ok(served, 'the damaged run is in scope'); | ||||||||||||||||||||||||
| if (!served.fromIndex) t.skip('run was fully parsed again — index path not exercised in this environment'); | ||||||||||||||||||||||||
| assert.ok(second.diagnostics.integrityErrorCount >= 1, | ||||||||||||||||||||||||
|
Comment on lines
+62
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Do not skip the index-served assertion. This test can pass without exercising the rollup-index path. Make the fixture deterministic, then require Proposed test change- if (!served.fromIndex) t.skip('run was fully parsed again — index path not exercised in this environment');
+ assert.equal(served.fromIndex, true,
+ 'the second build must serve the damaged run from the rollup index');As per coding guidelines, “Run regression tests against the real enforcement path where possible.” 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||||
| 'index-served damage still counts — zero would be a false green'); | ||||||||||||||||||||||||
| assert.ok(second.diagnostics.integrity.some((issue) => issue.runId === 'run-fx-damaged'), | ||||||||||||||||||||||||
| 'and the run is named'); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /** | ||
| * #498 — the Proof Rail names the run it reflects and follows the verdict. | ||
| * | ||
| * At all-runs scope the Command Center hero cited one run's readiness | ||
| * verdict ("blocked, coverage 85%") while the Proof Rail silently rendered a | ||
| * DIFFERENT run's stages — 15 unqualified "Not Started" beside a non-zero | ||
| * coverage verdict read as the dashboard disagreeing with itself. The | ||
| * overview projection now (a) prefers the run the verdict's first blocker | ||
| * cites when choosing the rail's focus run, and (b) exposes stagesSource so | ||
| * the rail labels which run it shows. Single-run scope keeps its selection. | ||
| * | ||
| * owner: RStack developed by Richardson Gunde | ||
| */ | ||
|
|
||
| import test from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
|
|
||
| import { buildOverviewProjection } from '../src/observability/dashboard/state/overview.js'; | ||
| import { commandCenterScript } from '../src/observability/dashboard/ui/pages/command-center.js'; | ||
|
|
||
| function run(runId, goal, extras = {}) { | ||
| return { | ||
| runId, | ||
| projectRoot: '/tmp/p', | ||
| manifest: { goal }, | ||
| tasks: [], | ||
| ...extras, | ||
| }; | ||
| } | ||
|
|
||
| test('the rail follows the run the verdict blocker cites at aggregate scope (#498)', () => { | ||
| const projection = buildOverviewProjection({ | ||
| runs: [ | ||
| run('run-stale', 'Stale fixture', { pipelineRollup: { stale: false } }), | ||
| run('run-blocked', 'Blocked fixture'), | ||
| ], | ||
| readiness: { | ||
| status: 'blocked', | ||
| summary: 'Release is blocked by 6 source-backed conditions.', | ||
| blockers: [{ | ||
| type: 'approval', | ||
| label: 'Pending approvals', | ||
| detail: 'guardrail-override:07-code awaits a manager', | ||
| sourceRef: { kind: 'approval_audit', path: '.rstack/runs/run-blocked/approvals.json', runId: 'run-blocked' }, | ||
| }], | ||
| coverage: { percent: 85 }, | ||
| }, | ||
| }); | ||
| assert.equal(projection.focusRunId, 'run-blocked', | ||
| 'the rail reflects the run the verdict is talking about'); | ||
| assert.deepEqual(projection.stagesSource, { runId: 'run-blocked', goal: 'Blocked fixture' }, | ||
| 'and the projection names the rail’s source run for the label'); | ||
| }); | ||
|
|
||
| test('without blockers the existing focus selection is preserved (#498)', () => { | ||
| const projection = buildOverviewProjection({ | ||
| runs: [ | ||
| run('run-active', 'Active work', { derivedStatus: 'active' }), | ||
| run('run-other', 'Other'), | ||
| ], | ||
| readiness: { status: 'ready', summary: 'All checks passed.', blockers: [], coverage: { percent: 100 } }, | ||
| }); | ||
| assert.equal(projection.focusRunId, 'run-active', 'active-run preference unchanged'); | ||
| assert.equal(projection.stagesSource.runId, 'run-active'); | ||
| }); | ||
|
|
||
| test('a blocker citing an out-of-scope run falls back to the existing selection (#498)', () => { | ||
| const projection = buildOverviewProjection({ | ||
| runs: [run('run-only', 'Only run')], | ||
| readiness: { | ||
| status: 'blocked', | ||
| summary: 'Blocked.', | ||
| blockers: [{ type: 'approval', label: 'x', detail: 'y', sourceRef: { runId: 'run-vanished' } }], | ||
| }, | ||
| }); | ||
| assert.equal(projection.focusRunId, 'run-only', 'never follows a citation outside the scope'); | ||
| }); | ||
|
|
||
| test('no runs means no stages source — nothing invented (#498)', () => { | ||
| const projection = buildOverviewProjection({ runs: [], readiness: { status: 'unknown', blockers: [] } }); | ||
| assert.equal(projection.stagesSource, null); | ||
| }); | ||
|
|
||
| test('the Command Center renders the rail source label from the projection (#498)', () => { | ||
| assert.match(commandCenterScript, /stagesSource/, | ||
| 'the rail header consumes the server-owned stagesSource label'); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /** | ||
| * #496 — the Run Workspace timeline falls back to events.jsonl. | ||
| * | ||
| * The timeline section read only the derived timeline collections | ||
| * (run.timeline / run.activityTimeline, populated for index-served runs | ||
| * since #296). Runs without them — legacy runs, fixture-shaped runs, index | ||
| * entries predating the field — declared "no recorded timeline events" while | ||
| * events.jsonl sat populated in the same snapshot. The projection now | ||
| * derives a minimal timeline from events when the derived collections are | ||
| * empty, labeled with its source; the honest empty state remains only when | ||
| * both sources are empty. | ||
| * | ||
| * owner: RStack developed by Richardson Gunde | ||
| */ | ||
|
|
||
| import test from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
|
|
||
| import { buildRunWorkspace } from '../src/observability/dashboard/state/run-workspace.js'; | ||
| import { commandCenterScript } from '../src/observability/dashboard/ui/pages/command-center.js'; | ||
| import { runWorkspaceScript } from '../src/observability/dashboard/ui/pages/run-workspace.js'; | ||
|
|
||
| function baseRun(overrides = {}) { | ||
| return { | ||
| runId: 'run-fx-blocked', | ||
| projectRoot: '/tmp/p', | ||
| manifest: { goal: 'Blocked fixture', status: 'IN_PROGRESS' }, | ||
| tasks: [{ id: '07-code', title: 'Code', status: 'BLOCKED', stage_id: '07-code' }], | ||
| events: [ | ||
| { ts: '2026-07-01T01:00:00.000Z', type: 'task_started', task_id: '07-code' }, | ||
| { ts: '2026-07-01T02:00:00.000Z', type: 'task_validated', task_id: '07-code', status: 'FAIL' }, | ||
| { ts: '2026-07-01T03:00:00.000Z', type: 'task_started', task_id: '07-code' }, | ||
| { ts: '2026-07-01T04:00:00.000Z', type: 'guardrail_triggered', task_id: '07-code' }, | ||
| { ts: '2026-07-01T04:00:00.000Z', type: 'approval_gate_blocked', task_id: '07-code' }, | ||
| ], | ||
| approvals: [], | ||
| ...overrides, | ||
| }; | ||
| } | ||
|
|
||
| test('a run with events but no derived timeline gets an events-sourced timeline (#496)', () => { | ||
| const workspace = buildRunWorkspace(baseRun()); | ||
| const timeline = workspace.sections.timeline; | ||
| assert.equal(timeline.available, true, 'the timeline is available'); | ||
| assert.equal(timeline.items.length, 5, 'every event becomes an entry'); | ||
| assert.equal(timeline.source, 'events', 'and the section names its fallback source'); | ||
| assert.equal(timeline.items[0].ts, '2026-07-01T01:00:00.000Z', 'entries stay time-ordered'); | ||
| assert.equal(timeline.items[0].type, 'task_started'); | ||
| assert.equal(timeline.items[0].task_id, '07-code'); | ||
| }); | ||
|
|
||
| test('a derived timeline keeps precedence — no event double-merge (#496)', () => { | ||
| const workspace = buildRunWorkspace(baseRun({ | ||
| timeline: [{ ts: '2026-07-01T05:00:00.000Z', type: 'stage_completed', stage_id: '07-code' }], | ||
| })); | ||
| const timeline = workspace.sections.timeline; | ||
| assert.equal(timeline.available, true); | ||
| assert.equal(timeline.items.length, 1, 'derived entries only — events never merge in beside them'); | ||
| assert.equal(timeline.source, 'persisted'); | ||
| }); | ||
|
|
||
| test('a run with neither derived timeline nor events keeps the honest empty state (#496)', () => { | ||
| const workspace = buildRunWorkspace(baseRun({ events: [] })); | ||
| const timeline = workspace.sections.timeline; | ||
| assert.equal(timeline.available, false, 'no invented timeline'); | ||
| }); | ||
|
|
||
| test('the workspace page renders the events-fallback source label (#496)', () => { | ||
| const bundle = commandCenterScript + runWorkspaceScript; | ||
| assert.match(bundle, /events\.jsonl/, 'the renderer names the fallback source in the timeline section'); | ||
| }); | ||
|
Comment on lines
+41
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Test the rendered source note and the cap boundary. Line 70 matches Run the renderer through the dashboard browser or DOM regression path. Assert that the note appears for As per coding guidelines, “Run regression tests against the real enforcement path where possible, including cross-process or browser behavior when those are the affected boundaries.” 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
Uh oh!
There was an error while loading. Please reload this page.