Skip to content

Commit 2d6a71d

Browse files
committed
Give the pipeline stage ledger a reader
Nothing outside internal/coordinator could read pipeline_stage_records -- no RPC, no session-event kind -- so the per-stage verdicts, timings and skip counts the whole Pipeline Refinement milestone produces were invisible to any interface. The evidence existed and had no reader. PipelineStageService.ListPipelineStages returns one task attempt's recorded flow. An RPC rather than a durable session event, decided on the shape of the data: the rows are immutable and fully written before any interface could announce them, they are addressed by task and attempt rather than delivered in one session's live order, and the session snapshot is correctness-bearing base state rather than a stage-by-stage audit trail. Live progress is a real but different concern the existing tool and plan events already serve. Migration 000036 adds elapsed_measured. StartedAt and FinishedAt alone cannot distinguish an untracked start from a genuinely instant stage -- both store identical equal timestamps -- and inferring it from that equality is exactly the overstatement this ledger exists to avoid. The bit is decided at write time, the only point that knows why the timestamps match, and the handler leaves elapsed unset rather than an explicit zero when unmeasured, so a client reading only that field cannot quietly treat "not measured" as "instant". Named elapsed rather than duration, matching TaskView.elapsed, and documented at the proto as wall clock from the previous stage's record rather than the check's own processing time. Bounded at LIMIT 128 as defence in depth -- the schema already caps an attempt at 64 rows -- with a test proving the bound does not clip a real 38-stage flow. Proven to discriminate twice: sabotaging the storage write to infer measured-ness from timestamp inequality failed the re-read assertion, and sabotaging the handler to always set elapsed failed the wire test. Unblocks PIPE-006a and PIPE-044. Committed with --no-verify at the user's explicit request. TODO: PIPE-006b Change-Log: CL-20260802-064 Dev-Log: DL-20260802-074 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent c78b03b commit 2d6a71d

15 files changed

Lines changed: 2139 additions & 712 deletions

CHANGELOG

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,43 @@ Dev-Log:
4242

4343
Entries
4444
-------
45+
Change-ID: CL-20260802-064
46+
Commit: pending
47+
Date: 2026-08-02
48+
Type: Feature -- the pipeline stage ledger becomes readable
49+
Request-or-TODO: PIPE-006b
50+
Outcome: PipelineStageService.ListPipelineStages exposes one task attempt's
51+
recorded flow -- stage number, name, verdict, and elapsed timing with an
52+
explicit measured signal. Nothing outside internal/coordinator could read
53+
pipeline_stage_records before this, so the evidence the whole Pipeline
54+
Refinement milestone produces had no reader at all.
55+
An RPC rather than a durable session event: the rows are immutable and fully
56+
written before any interface could announce them, they are addressed by task
57+
and attempt rather than delivered in one session's live order, and the session
58+
snapshot is correctness-bearing base state rather than a stage-by-stage audit
59+
trail. An event would have bought live per-stage animation at the cost of
60+
threading a payload through every write site and multiplying session-event
61+
volume by up to 38 per attempt for data already durable in SQL.
62+
Migration 000036 adds elapsed_measured, because StartedAt and FinishedAt alone
63+
cannot distinguish an untracked start from a genuinely instant stage -- both
64+
store identical equal timestamps. The bit is decided once at write time, in
65+
the only place that knows why the two are equal, and the handler leaves
66+
elapsed unset rather than an explicit zero when unmeasured
67+
Affected-behavior: A new authenticated, bounded read surface. No existing
68+
behavior changes
69+
Compatibility-or-migration: Forward migration 000036 adds one column;
70+
migration-check accepts it and generate-check accepts the regenerated protobuf
71+
Verification: Proven to discriminate twice. Sabotaging the storage write to
72+
infer measured-ness from timestamp inequality made the re-read assertion fail;
73+
sabotaging the handler to always set elapsed made the wire test fail. Both
74+
reverted and re-verified. A further test proves the LIMIT 128 read bound does
75+
not clip a real 38-stage flow, and TestAUDIT008 confirms the new service is
76+
registered and refuses an unauthenticated call
77+
Known-limitations: attempt=0 resolves to attempt 1, matching
78+
NextPipelineAttempt's convention. There is no "latest attempt" concept
79+
anywhere in the product and this surface does not invent one
80+
Dev-Log: DL-20260802-074
81+
4582
Change-ID: CL-20260802-063
4683
Commit: pending
4784
Date: 2026-08-02

DEVLOG

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,45 @@ Next-safe-step:
2525

2626
Entries
2727
-------
28+
Dev-Log: DL-20260802-074
29+
Date: 2026-08-02
30+
Status: Read path landed; PIPE-006a and PIPE-044 unblocked
31+
Change-ID: CL-20260802-064
32+
Commit: pending
33+
Request-or-TODO: PIPE-006b
34+
Goal: Give the pipeline stage ledger a reader
35+
Assumptions: Evidence nobody can read is not evidence
36+
Decisions: Chose an RPC over a session event on the shape of the data rather
37+
than on convenience -- immutable rows, already durable, addressed by task and
38+
attempt, read retrospectively by a reviewer rather than watched live. Live
39+
progress is a real but different concern that the existing tool and plan
40+
events already serve.
41+
Added an explicit elapsed_measured bit instead of inferring it. Inference from
42+
timestamp equality is exactly wrong here: a stage that genuinely took zero
43+
microseconds and a stage whose start was never tracked are indistinguishable
44+
that way, and the ledger's whole purpose is to not overstate what it knows.
45+
Decided the bit at write time, since that is the only point that knows why the
46+
timestamps match.
47+
Left elapsed unset rather than zero on the wire when unmeasured, so a client
48+
reading only that field cannot quietly treat "not measured" as "instant".
49+
Named the field elapsed rather than duration, matching TaskView.elapsed, and
50+
documented at the proto that it is wall clock from the previous stage's record
51+
rather than the check's own processing time
52+
Files-or-schemas: api/proto and its regenerated output,
53+
internal/storage/pipeline_stage_repository.go and a new test,
54+
migrations/000036_pipeline_stage_elapsed_measured.sql,
55+
internal/transport/pipeline_stage_service.go and test,
56+
internal/coordinator/pipeline_stage_reader.go and test, application.go
57+
Validation: build, gofmt, generate-check, migration-check, the storage package
58+
in full, the transport package, a SQLite end-to-end gRPC test, TestAUDIT008,
59+
and lint -- all clean
60+
Failures-or-discarded-approaches: The session-event design, above. Also rejected
61+
inferring measured-ness in the transport layer, which would have put the
62+
judgement furthest from the knowledge
63+
Known-limitations: No latest-attempt resolution, deliberately
64+
Next-safe-step: PIPE-006a can render this, once PIPE-042 supplies the skip ratio
65+
PIPE-044 needs
66+
2867
Dev-Log: DL-20260802-073
2968
Date: 2026-08-02
3069
Status: Closing at the decision site

0 commit comments

Comments
 (0)