Repository navigation
Description
Implement GitHub issue #6224 in developing-today/code: pm-stack(aggregator): a pool item with an invalid blockedBy/blocks edge 500s the project page and throws the brief — isProjectPoolItem validates edges by shape only, brief + pool-graph + epic-graph call refUrl unguarded
Summary
A Project-pool item whose blockedBy / blocks edge carries a ref parseRef rejects breaks three aggregator surfaces at render/derive time. The chain has two mismatched layers:
- The shared pool gate admits it.
isProjectPoolItem(pm-stack/aggregator/src/projects.ts:280-283) checks the edge fields only by shape:parentRef/epicRefare any string,blockedBy/blocksare any array of strings. There is noisValidRefcheck — exactly the disagreement model.ts:463-466 documents for the shared rule (pm-stack(aggregator): validateNewComment/validateNewAction accept ref keys parseRef rejects (GH_REF number unbounded) #4952): a boundary validator must route throughisValidRefinstead of a local shape check whose valuesparseRef(andrefUrl) then throw on. Sodeveloping-today/code#99999999999999999999(unsafe integer) and812(not repo-qualified) both pass the gate. - The feed path trusts it.
poolFromFeed(pm-stack/aggregator/src/feed.ts:466-470) keeps any item passing that gate, so a feed-published pool section carrying such an edge reaches every consumer verbatim. - Three consumers call
refUrlunguarded on those edges:- Per-item brief:
buildItemBrief(pm-stack/aggregator/src/brief.ts:92-96) maps eachblockedByref totarget?.issueUrl ?? refUrl(ref, "issue")— an out-of-pool bad blocker throws, so the brief (and any page/API building briefs for the pool) fails. - Project page, pool graph section:
blockedSummary(graph.ts) followsblockedByedges and reports the bad ref inlongestChain;poolGraphHtml(render.ts:6479) renders the chain with unguardedrefUrl(r)— one bad edge 500s the whole project page. - Project page, per-epic graph: the ranked list (render.ts:6590) and the dependency list (render.ts:6645
blocked by:, :6648blocks:) renderblockedBy/blocksrefs with unguardedrefUrl(b)— a member item with a bad edge 500s that epic's project page.
- Per-item brief:
Evidence (verified this run, laptop, aggregator sources at origin/main 04f7869)
Probe ~/git/fleet/probe-pool-edges/probe.ts (bun, real aggregator sources, run synchronously):
isValidRef BAD_UNSAFE: false | BAD_PLAIN: false | GOOD: true
gate blockedBy=[BAD_UNSAFE]: true
gate blockedBy=[BAD_PLAIN]: true
gate blocks=[BAD_UNSAFE], parentRef=BAD_PLAIN: true
poolFromFeed keeps bad item: 1
brief BAD_UNSAFE blocker (out of pool): THREW: ref number is not a safe integer: developing-today/code#99999999999999999999
brief GOOD blocker control: OK len=49
blockedSummary chain with BAD: ["developing-today/code#1","developing-today/code#99999999999999999999"]
projectPage graph BAD_UNSAFE chain: THREW: ref number is not a safe integer: developing-today/code#99999999999999999999
projectPage graph GOOD chain control: OK len=34510
projectPage epic BAD_PLAIN blocker: THREW: not a repo-qualified ref: 812
projectPage epic GOOD blocker control: OK len=38578
BAD_UNSAFE is developing-today/code#99999999999999999999; BAD_PLAIN is 812; controls use developing-today/code#3 in the same positions and render/derive cleanly.
Why it matters
The pool's edge fields are the native dependency graph the dashboard is built on (#2399 contract): the collector writes them from GitHub's native relations, but the feed is a published artifact other producers can write, and the gate is explicitly the shared contract both sides must not drift from (feed.ts:440-446). One malformed edge in one feed item — a hand-edited feed, a buggy producer, a non-GitHub dependency written as a bare number — permanently breaks the brief for that item and 500s the affected project pages until the feed is fixed by hand. Siblings already filed cover other producers of the same throw (projector registry links #6211 — which covers render.ts:6741 roadmap rows and /search, not these sites; lined-up titles #6210/#6195; objectives #6174; overview #6205), and #5202 covers a different field of this same gate (updatedAt). None covers the pool edge fields or the brief / pool-graph / epic-graph consumers. The session-record path (render.ts:1912) already shows the house pattern for untrusted refs: try/catch and omit only the bad link.
Acceptance criteria
- A pool item whose
blockedBy/blocks(and, for consistency,parentRef/epicRef) edge failsisValidRefnever reaches a renderer: eitherisProjectPoolItemvalidates edge refs throughisValidRef(bad item drops individually inpoolFromFeed, per the gate's existing fail-soft contract), or the three consumer sites guardrefUrlthe waysessionRecordLine(render.ts:1912) does. The gate layer is preferred — it is the shared contract — but stored/in-memory pools that bypass the feed must not stay lethal, so a consumer-side guard in addition is welcome. - The brief, the project page's pool-graph section, and the per-epic graph all survive a pool item carrying an unsafe-integer or non-repo-qualified edge: the item renders, the bad edge is omitted or shown unlinked, valid edges still render as links.
- Regression tests alongside the existing aggregator suites (graph / epic-graph / brief / projects gate tests) pinning: unsafe-integer
blockedBy, non-repo-qualifiedblockedBy, and unsafeblocks, each with a valid-ref control. - Existing behavior unchanged for valid refs (controls above).
Related: #6211 (projector registry links; render.ts:6741 + /search), #6210, #6195, #6174, #6205, #5202 (same gate, updatedAt field), #4952 (the shared ref-validity rule), #2399 (pool edge contract).
Rules from the repo owner: (1) Deliver the finished work as a pull request in this run; put "Fixes #6224" in the PR description so the issue closes on merge. (2) Cover the whole set in the one PR: if the issue names a set (files, subcommands, cases), fix every member, and batch any directly related dependency updates into the same PR — never one PR per item or per dependency. (3) Run the relevant tests and checks before opening the PR. (4) Merging is permitted but never required: merge only when checks pass and you are reasonably certain the PR is correct and low-risk — otherwise leave it open, and state your confidence and what a human should check. (5) If you hit a genuine blocker (missing credentials, a decision only the owner can make), stop and say exactly what is needed in your final summary instead of guessing.
Launched by pm-dispatch for developing-today/code. Queue task and OpenCode session ids follow in a comment below.
Activity
developing-today commentedon Nov 5, 2026
Dispatch launch confirmed.
dezren39 commentedon Nov 5, 2026
🤖 Agent note · phase pin for the implementation of #6224
files/designs/code-aggregator-ref-contract.md(keep-the-four-repos-healthy goal workspace).developing-today commentedon Nov 5, 2026
🤖 Agent note · picked up
queue/10537-6224-pool-edge-validation(laptop worktree~/git/fleet/task-10537) was verified fresh; merging current main in, re-running aggregator tests, then opening the PR with "Fixes pm-stack(aggregator): a pool item with an invalid blockedBy/blocks edge 500s the project page and throws the brief — isProjectPoolItem validates edges by shape only, brief + pool-graph + epic-graph call refUrl unguarded #6224".queue/10537-6224-pool-edge-validation; issue pm-stack(aggregator): a pool item with an invalid blockedBy/blocks edge 500s the project page and throws the brief — isProjectPoolItem validates edges by shape only, brief + pool-graph + epic-graph call refUrl unguarded #6224developing-today commentedon Nov 5, 2026
🤖 Agent note · parked
queue/10537-6224-pool-edge-validation1 remaining item
developing-today commentedon Nov 5, 2026
🤖 Agent note · conflict resolved, parked on CI
awaiting_cicheckpoint on PR fix(pm-stack/aggregator): validate project pool graph refs (#6224) #6365~/git/fleet/task-10537: merged origin/main (1dae17e); the single conflict inpm-stack/aggregator/src/render.tswas resolved by keeping main's new downstreamCount/contributingItems ranking text while rendering blocker and contributing refs through the PR'spoolRefLinkguard instead of main's unguardedrefUrl(unguarded refUrl is exactly the pm-stack(aggregator): a pool item with an invalid blockedBy/blocks edge 500s the project page and throws the brief — isProjectPoolItem validates edges by shape only, brief + pool-graph + epic-graph call refUrl unguarded #6224 throw). Merge commit 8a69dfe pushed; PR is MERGEABLE again. No Codex review findings exist on PR fix(pm-stack/aggregator): validate project pool graph refs (#6224) #6365 (no codex-connector reviews or inline comments). After ~4 minutes of polling, the aggregator suite check had not yet registered for the new HEAD (only CodeRabbit: skipped) — parked per the 5-minute CI rule instead of holding the claim.dezren39 commentedon Nov 5, 2026
🤖 Agent note · done — PR #6365 merged
queue/10537-6224-pool-edge-validation, opened by the earlier attempt of task 10537) was resumed, verified, and merged at 2026-11-05T20:24:17Z, merge commit 03d1a21. Issue pm-stack(aggregator): a pool item with an invalid blockedBy/blocks edge 500s the project page and throws the brief — isProjectPoolItem validates edges by shape only, brief + pool-graph + epic-graph call refUrl unguarded #6224 closed by the PR'sFixes #6224. The fix:isProjectPoolItemvalidates all present edge refs (parentRef/epicRef/blockedBy/blocks) throughisValidRef; brief / epic-graph / graph / render consumers filter or guard malformed refs (bad edge omitted/unlinked, valid edges still link).bun test test/brief.test.ts test/epic-graph.test.ts test/feed.test.ts test/graph.test.ts test/projects.test.ts— 141 pass, 0 fail. Full aggregator suite fail set is byte-identical to a clean-main run (pre-existing failures in events/board/source-gate/program areas, none in the touched areas). No Codex review findings existed on PR fix(pm-stack/aggregator): validate project pool graph refs (#6224) #6365 to disposition (no Codex review submitted). Closing this tracking issue as completed.