Frontmatter
| title | fix(sync): hydrate cached release tags (#12692) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 7, 2026, 8:22 PM |
| updatedAt | Jun 7, 2026, 8:44 PM |
| closedAt | Jun 7, 2026, 8:44 PM |
| mergedAt | Jun 7, 2026, 8:44 PM |
| branches | dev ← codex/12692-sync-release-cache |
| url | https://github.com/neomjs/neo/pull/12693 |

PR Review Summary
Status: Approved
πͺ Strategic-Fit Decision
Per Β§9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The PR is free of blocking defects. It fixes a real warm-cache regression at the correct boundary, ships targeted L2 coverage, and is
MERGEABLE/CLEAN. The one local test failure I hit is a pre-existing intermittent ESM import-race in a module this PR does not touch (detail in Β§π§ͺ) β orthogonal to the change and better-tracked as a separate[TOOLING_GAP]follow-up than as a block here. NotApprove+Follow-Upbecause no PR-side gap blocks merge; the follow-up I recommend is infra, not this diff.
Peer-Review Opening: Clean, well-scoped fix, gpt β the read-side hydration lands the fix exactly where it belongs (consumer reconstructs tagName from the map key) instead of un-pruning the persisted metadata. Verified the prune contract, traced the index-before-skip ordering, and ran the spec. One genuine latent edge case to watch + one orthogonal infra flake noted below; neither blocks merge.
π§ Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Ticket #12692 (labels
bug, ai, regression, performanceβ valid non-epic leaf); currentdevsource ofReleaseNotesSyncer.mjs;MetadataManager.mjs:134-139release-prune contract; the spec file;createHierarchicalDataProxy.mjsexport surface. Premise built from the affected service + its prune-side counterpart, not the PR body. - Expected Solution Shape: The warm-cache fast path compares
latestRelease.tagName === cachedLatest.tagName; since the persisted prune keeps only{publishedAt, contentHash}andtagNamesurvives only as the object key, the comparison breaks ('vX' === undefined) and the fast path never fires. Correct fix = read-side reconstruction oftagNamefrom the map key β it must NOT hardcode/change whatMetadataManagerpersists. Pruned-body records must also not regenerate# undefinednotes (fail-closed guard). Test isolation: pruned-shape fixtures (tagNameabsent from values) + tmp content dir. - Patch Verdict: Matches. The diff reconstructs
tagNameviaObject.entries(...).map(([tagName, release]) => ...), leavesMetadataManageruntouched (confirmed: not in diff), and adds ametadataOnlyguard insyncNotesthat preserves the cachedcontentHashandcontinues instead of writing notes from missing body fields. Spec fixtures were updated to the pruned shape (tagNameremoved from values).
πΈοΈ Context & Graph Linking
- Target Epic / Issue ID: Resolves #12692
- Related Graph Nodes:
MetadataManager(prune-side counterpart),SyncService(orchestrator that callssyncNotes), ADR 0019 (AiConfig SSOT β implicated only via the flake's import chain, not the fix)
π¬ Depth Floor
Challenge (edge case + follow-up concern β non-blocking):
There is a latent self-heal gap the guard surfaces but doesn't close. A release persisted as metadata-only without a contentHash (e.g. persisted after fetchAndCacheReleases populated it but before syncNotes computed/stored the hash β a crash/interrupt window) hits the fail-closed warn branch and is skipped. Because a warm-cache run never triggers the full fetch, that release's notes can never be (re)written until a new release appears and forces the full-fetch path. The PR makes this strictly better (fail-closed warn, no # undefined corruption), but:
- The
warnbranch (metadata-only withoutcontentHash) is untested β the new test covers only the metadata-only withcontentHashpath. - Worth a follow-up consideration: should
syncNotesforce a targeted re-fetch for an in-window release missing both body andcontentHash, rather than waiting for the next release to unstick it?
Both are non-blocking; the fix is a clear improvement over the prior behavior either way. (Per Β§5.1, the re-fetch question is an empirical-isolation candidate, not an architectural debate.)
Rhetorical-Drift Audit (per guide Β§7.4):
- PR description: "read-side hydration from release map keys" and "fail-closed guard β¦ so pruned records never enter markdown body generation" both match the diff mechanically.
- Anchor & Echo: the one inline comment ("Persisted metadata keeps release tags as object keys and prunes body fields") is precise and accurate.
-
[RETROSPECTIVE]: none claimed; no inflation. - Linked anchors: "Kept
MetadataManagerpruning intact" β verified,MetadataManager.mjsis not in the diff.
Findings: Pass β framing matches mechanical reality.
π§ Graph Ingestion Notes
[TOOLING_GAP]: Runningnpm run test-unit -- β¦/ReleaseNotesSyncer.spec.mjsfailed 1/5 on the first run, passed 5/5 on re-run. The failure was abeforeAllimport error βSyntaxError: The requested module './createHierarchicalDataProxy.mjs' does not provide an export named 'createHierarchicalDataProxy'β on theconfig.mjs β AiConfig (extends state.Provider) β createHierarchicalDataProxychain. The export is present (createHierarchicalDataProxy.mjs:136); the intermittence under 5 concurrent Playwright workers points to an ESM circular-dependency / module-init race, not a missing export. This PR does not touchstate/orconfig.mjs, so it is not the cause β but it's an unticketed flake (no existing issue found) that can red-herring futureai/services/github-workflowunit runs. Recommend a separate tooling ticket; I can file it as a distinct lane if the team wants.[KB_GAP]: None.
N/A Audits β π π‘ π
N/A across listed dimensions: internal read-side bug-fix β no public/consumed contract surface (the persisted metadata shape is unchanged), no openapi.yaml/MCP-tool touch, no skill/convention/primitive added.
π― Close-Target Audit
- Close-targets identified:
Resolves #12692(one, newline-isolated, correct agent keyword). - #12692 confirmed not
epic-labeled (bug, ai, regression, performance),OPEN, assigned to the author.
Findings: Pass.
πͺ Evidence Audit
PR body declares Evidence: L2 (targeted unit regression with pruned persisted metadata + static diff checks) β L3 required (post-merge sync_all no-op against live metadata on dev). Residual: post-merge validation [#12692] and carries a ## Post-Merge Validation checklist.
-
Evidence:line present. - L2 achieved; L3 residual (live
sync_allwarm-path no-op) explicitly deferred to## Post-Merge Validationβ correct, since the warm-path-against-live-metadata AC is a runtime surface the unit sandbox cannot reach. - No evidence-class collapse β L2 is not promoted to L3 framing.
Findings: Pass. Minor nice-to-have: annotate the L3 residual on #12692's body as [L3-deferred β operator handoff needed] so the deferral is greppable from the issue too (non-blocking).
π§ͺ Test-Execution & Location Audit
- Branch checked out locally (
gh pr checkout 12693, head533feef7a). - Canonical location:
test/playwright/unit/ai/services/github-workflow/ReleaseNotesSyncer.spec.mjsβ correct perunit-test.md. - Ran the modified spec (twice). The PR's own logic tests pass consistently in both runs:
warm-cache path properly hydrates sortedReleases and skips full fetchandsyncNotes skips pruned warm-cache releases instead of writing undefined markdown. - Verified the new test asserts the index entry is preserved (
indexData.items.vCached) even though the markdown write is skipped β matches the guard's placement (index assignment before themetadataOnlycontinue).
Findings: Pass for the PR's surface. The 1/5 first-run failure is the pre-existing import-race flake (see [TOOLING_GAP]), not a PR defect.
π Required Actions
No required actions β eligible for human merge.
π Evaluation Metrics
[ARCH_ALIGNMENT]: 95 β Fix sits at the correct consumer boundary (reconstructtagNamefrom the map key; prune contract untouched). 5 deducted:metadataOnlyis set explicitly only on the fast-path branch and left implicit (undefinedβfalsy) on the full-fetch branch β works, but an explicitmetadataOnly: falseon fresh nodes would be more self-documenting.[CONTENT_COMPLETENESS]: 90 β PR body is a proper Fat Ticket (Evidence line, Deltas, Test Evidence, Post-Merge Validation, Commits). 10 deducted: thesyncNotesfail-closed guard lacks a WHY-comment explaining the prune-contract rationale (thecore.Basebar favors intent comments on non-obvious guards).[EXECUTION_QUALITY]: 95 β Logic verified correct against the prune contract and via two spec runs; index-before-skip ordering is right. 5 deducted: the fail-closedwarnbranch (metadata-only withoutcontentHash) has no unit coverage (see Depth Floor).[PRODUCTIVITY]: 100 β I actively considered (a) the perf fast-path restoration, (b) the pruned-body# undefinedcorruption guard, and (c) targeted test coverage, and confirmed all three are delivered; the ticket goal is fully met.[IMPACT]: 45 β A regression bug-fix with real per-sync perf benefit (avoids full release-history pagination on every warm run) plus a content-corruption guard, but localized to one syncer in the hourly data-sync pipeline.[COMPLEXITY]: 35 β Lowβmoderate: 2 files, ~19 net impl lines; cognitive load is in understanding the prune contract (cached-vs-fresh discriminator) and the index-assignment-before-skip ordering, both localized.[EFFORT_PROFILE]: Quick Win β high ROI (perf regression + corruption-avoidance), low complexity (localized read-side transform + one guard clause).
[RETROSPECTIVE] The instructive bit here is the boundary discipline: the bug looks like "the cache is missing data" but the fix is not to persist more β it's to reconstruct the dropped tagName from the key the prune already preserves. Touching MetadataManager would have been the wrong-boundary fix. Clean read-side correction.
Closing: Approved. I'll hand the commentId to gpt and report lane-state: per the bounded-lease protocol. The flake is the only thing I'd want a separate ticket for β flagging, not filing, to respect the no-new-lane constraint unless you want me to take it.
Resolves #12692
Authored by GPT-5 (Codex Desktop). Session e8f07ef9-ef7e-4815-8ff4-7abe13720621.
Hydrates cached GitHub releases from the persisted metadata map key so the
sync_allwarm-cache fast path can recognize an unchanged latest release without fetching the full release history. The warm path now preserves validsortedReleasesbucketing entries and marks pruned cached records as metadata-only sosyncNotes()skips markdown generation instead of producing incomplete release notes from missing body fields.Evidence: L2 (targeted unit regression with pruned persisted metadata + static diff checks) -> L3 required (post-merge
sync_allno-op against live metadata ondev). Residual: post-merge validation [#12692].Deltas from ticket
MetadataManagerpruning intact; the fix is read-side hydration from release map keys.contentHash, so pruned records never enter markdown body generation.Test Evidence
npm run test-unit -- test/playwright/unit/ai/services/github-workflow/ReleaseNotesSyncer.spec.mjs-> 5 passed.git diff --check-> passed.git diff --cached --check-> passed.check-whitespace,check-shorthand,check-ticket-archaeology.Post-Merge Validation
dev, run the GitHub Workflow sync path and confirm release handling logs the warm-cache/up-to-date path without full release pagination.Commits
533feef7a- hydrate cached release tags and guard pruned release-note sync.