Repository navigation
Description
Finding
The orchestration section's per-goal gate, isGoalMeasurement in pm-stack/aggregator/src/feed.ts, checks only that linedUp, stalled, and gaps are arrays and that progressedSinceLastRun is a string or an array. It never checks the array elements, and it never checks goal, momentum, summary, or goalId at all — even though GoalMeasurement (model.ts) requires all of them and todayFromSection (derive.ts) dereferences them. The gate's own header comment states its contract (#949): "todayFromSection dereferences linedUp, stalled, and gaps on every goal, so a goal missing any of them must fail validation here — this validator is the only gate on every path, and a rejected section falls back to the derived view by design." A goal that passes this gate with a null element defeats both halves of that contract: the section is not rejected, and the consumer crashes instead of falling back.
Evidence (main f7c942b, verified this run)
feed.ts:122-131isGoalMeasurement:Array.isArrayonlinedUp/stalled/gapsonly; no element checks;goal,momentum,summary,goalIdunchecked. Used byorchestrationSectionProblematfeed.ts:172, the single source of truth behindisOrchestrationSectionanddiagnoseOrchestrationSection.- The contract it under-checks:
model.ts:626-639GoalMeasurementrequiresgoalId: string | null,goal: string,momentum: "moving" | "stalled" | "blocked" | "no-work-lined-up",summary: string,linedUp: LinedUpTask[](element shape atmodel.ts:615-623:title,statusenum,horizon,horizonWhy),stalled: { title: string; since: string; blocker?: string }[],gaps: string[]. - The consumer that trusts the gate:
derive.ts:687todayFromSection—g.linedUp.filter((t) => t.horizon === "today")at :694,g.stalled.filter((s) => s.blocker)at :713,project: g.goalat :717, the momentum ternary at :718 (any non-"moving"/non-"blocked" value silently renders as"stalled"),[g.summary, ...g.gaps].join(" ")at :721. Production path:sources/live.ts:1036normalizeOrchestrationSection(the gate) →server.ts:474todayContext()→deriveToday→todayFromSection, with no catch intodayContext.
Laptop Bun probe (~/git/fleet/probe-2026-11-01-laneB-goalgate/, importing the real feed.ts/derive.ts from the laptop clone at the same HEAD — md5 fb3693f1… / dcdefb5e…, identical to sandbox main f7c942b):
- Control (fully valid goal): gate
true,todayFromSectionrenders correctly. linedUp: [null]: gatetrue(problemnull) →todayFromSectionthrowsTypeError: null is not an object (evaluating 't.horizon').stalled: [null]: gatetrue→ throwsTypeError … (evaluating 's.blocker').- Goal with only the four checked fields (no
goal/momentum/summary/goalId): gatetrue→ renders momentum entry with no project name and state"stalled", objective with no title. gaps: [{ finding: "x" }]: gatetrue→ rendered detail contains the literal text[object Object].momentum: "flying": gatetrue→ silently rendered as state"stalled".linedUp: ["Task A"](bare string): gatetrue→ the task silently vanishes and the fallback objective replaces it.
Why it matters
The orchestration section is published by an automated job into a JSON file the aggregator reads fail-soft everywhere else; this gate is the one place a drifted or hand-edited section is supposed to be diagnosed as "invalid" (with the failing check named, #1140) and demoted to the derived view. As written, one malformed goal element turns that designed degradation into an uncaught TypeError on the today page's request path, and milder drift (missing name, nonsense momentum, non-string gaps) renders falsified state — a goal reported as "stalled" that never said so, a nameless objective, [object Object] in the detail line — with no diagnosis anywhere.
Acceptance criteria
isGoalMeasurementvalidates the fullGoalMeasurementshape:goala non-empty string,momentumone of the four enum values,summarya string,goalIda string or null;linedUpelements are records with a stringtitleand a validhorizon(andstatus/horizonWhyperLinedUpTaskwhen present per the type);stalledelements are records with stringtitleandsince(optional stringblocker);gapselements are strings; aprogressedSinceLastRunarray's elements are strings (the single-string form stays accepted, per the existing tolerance).- A section whose goal fails the tightened gate is diagnosed through the existing machinery:
orchestrationSectionProblemstill reports"goals (per-goal shape)", the section renders as invalid/falls back to the derived view, andtodayFromSectioncan no longer be reached with an element it would throw on. - Regression tests beside the existing feed gate tests cover at least:
linedUp: [null],stalled: [null], a goal missinggoal, a nonsensemomentum, and a non-stringgapselement — all rejected — plus the valid-goal control still passing. - Existing feed/derive tests stay green.
Dedup: #949 is the original gate-design issue whose comment states the contract above — it predates the element-shape gap and does not name it. #3458 covers the charge gates (isChargeRow/isUnmatchedCharge amount/date semantics), #3766 the project-config registry validator, #3427/#3428 the quota window/burn gates — different validators, fields, and consumers. No open or closed issue covers isGoalMeasurement's missing element and field checks.
Filed by new-issue-creator-code-b (Lane B) from a verified laptop probe; filed only, not self-solved — the element rules must match the publisher's real shapes, which is a triage decision, not a trivial fix.
Activity
dezren39 commentedon Nov 1, 2026
🤖 Agent note · filed
isGoalMeasurement(pm-stack/aggregator/src/feed.ts:122-131) checks only Array.isArray on linedUp/stalled/gaps — elements and goal/momentum/summary/goalId are never checked, so a goal with linedUp:[null] or stalled:[null] passes the gate and todayFromSection (derive.ts:694/:713) throws, while milder drift renders falsified state (nonsense momentum → "stalled", object gap → "[object Object]"). Verified with a laptop Bun probe importing the real feed.ts/derive.ts @f7c942b1 (~/git/fleet/probe-2026-11-01-laneB-goalgate/).dezren39 commentedon Nov 1, 2026
🤖 Agent note · sibling finding from the same run (new-issue-creator-code-b)
Same array-only pattern, one field over:
orchestrationSectionProblemchecksactionswith onlyArray.isArray(feed.ts), andtodayFromSectionpasses the elements through untouched (section.actions.slice(-5).reverse()). The today renderer dereferences every element —render.ts:2731-2732mapsa.kind,a.target,a.summary,fmtDate(a.at)— soactions: [null]crashes the render exactly as a null goal element crashes the derive.Verified in the same laptop probe dir (
~/git/fleet/probe-2026-11-01-laneB-goalgate/actions-probe.ts, real feed.ts/derive.ts @f7c942b1): gatetrueforactions: [null];todayFromSectionreturnsactions: [null]; the render.ts map expression throwsTypeError: null is not an object (evaluating 'a.kind').Not filed as a separate issue — it is the same gate and the same fix shape. Triage call: widen this issue's fix to validate
actionselements ({ at, kind, target, summary }strings per model.ts) in the same pass, or split it out explicitly.dezren39 commentedon Nov 2, 2026
agent-note:v1 — cross-link from new-issue-creator-code-a (Lane A), 2026-11-02: the #2417 fix merged today (c611bf4, PR #4176) partially addresses this issue —
linedUp/stalledelements are nowisRecord-checked, so the[null]element crashes in the original evidence no longer reproduce. One crash class this issue's enumerated criteria do not name was verified still live on c611bf4 and filed separately as #4316: a lined-up task'sblockedBy(string / [null]) passes the gate andtodayFromSectionthrows inblockedText. The remaining scope here (goal/momentum/summary/goalId, element title/horizon/status fields,gapselement types) is unchanged.dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
16 remaining items
dezren39 commentedon Nov 2, 2026
agent-note:v1 — task 6235 picked up by lane code-f (repo-worker-code), direct execution lane. Fresh check: issue #3897 OPEN, no hard-stop labels; PR #4176 MERGED (partial gate). Implementing the remaining GoalMeasurement field/element validation in isGoalMeasurement (pm-stack/aggregator/src/feed.ts) with regression tests; nested linedUp blockedBy stays out of scope per #4316.
dezren39 commentedon Nov 2, 2026
agent-note:v1 — task 6235 done by lane code-f: PR #4499 opened (branch queue/6235-3897-goalmeasurement-validation, head e84f8af). isGoalMeasurement now validates goal/momentum/summary/goalId, linedUp element title/horizon (+status/horizonWhy when present), stalled title/since (+optional blocker), string gaps elements, and string elements in a progressedSinceLastRun array. Regression tests added; orchestration suite 34/34 pass on the laptop; full-suite failures verified pre-existing on clean main. PR left open pending the running Codex review — merge after its findings are dispositioned.
developing-today commentedon Nov 3, 2026
agent-note:v1
state: triaged (coverage pass)
Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as small_fix at priority 50 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
Found via: coverage pass 2026-11-03
Merge pull request #4499 from developing-today/queue/6235-3897-goalme…
dezren39 commentedon Nov 4, 2026
agent-note:v1
state: done
queue: repo-health-code-pilot
task: 8291 (small_fix, worker repo-worker-code, lane code-g, direct lane)
by: repo-queue-worker-shift-g
at: 2026-11-04T02:04Z
Closed with evidence: the #3897 fix was already implemented by task 6235 in PR #4499 (branch queue/6235-3897-goalmeasurement-validation) and left open pending Codex review. This shift re-verified it fresh: Codex review completed at head e84f8af with has_any_findings=false / has_unresolved_findings=false, the listed check passed (basic: OpenCode metered-provider guard), the diff is exactly the scoped change (feed.ts isGoalMeasurement now validates goal/momentum/summary/goalId, linedUp element title/horizon/status/horizonWhy, stalled title/since/blocker, string gaps and progressedSinceLastRun elements; orchestration.test.ts regression tests; task 6235 recorded the orchestration suite at 34/34 on the laptop), and the tightened symbols are present on main. PR #4499 was merged this shift: merge commit b519288. No competing PR was created. Closing this issue on the merge evidence (the PR body wording did not trigger auto-close).