Context
Part of Epic #11187 Phase 1 execution. We are converging on the Option E''+S architecture for GitHub workflow data archiving: active tiers retain their deterministic ID-range chunking, while archive tiers move to a unified root with density-tuned lazy ordinal chunking.
Sibling sub-tickets: AC1 #11189 (config refactor; PR #11191 APPROVED at @tobiu merge gate), AC2 #11190 (archivePath helper; PR #11193 APPROVED at @tobiu merge gate), AC4 #11196 (PullRequestService — @neo-gemini-3-1-pro claimed), AC5 #11197 (DiscussionService — open).
The Problem
The current IssueSyncer#getIssuePath writes archive-tier issue data using archiveDir / vN.M.K / chunkPath(id) / filename (deterministic ID-range chunking under a version folder). Per Epic #11187 Cycle 2 amendment, archive-tier must switch to archive/issues/vN.M.K/{flat|chunk-N}/ with lazy ordinal chunking (sealed-chunk semantics via prevent-reopen.yml). Active-tier writes (issuesDir / chunkPath(id) /) MUST retain XXxx ID-range semantic so LocalFileService#getIssueById keeps O(1) deterministic lookup.
The Architectural Reality
V-B-A'd against current IssueSyncer.mjs:
ai/services/github-workflow/sync/IssueSyncer.mjs — #getIssuePath (lines 267-314):
- Line 283: OPEN →
issuesDir / chunkPath(id) / filename (active; unchanged)
- Line 293: CLOSED with milestone →
archiveDir / milestoneDir / chunkPath(id) / filename (archive — REFACTOR)
- Line 306: CLOSED without milestone but with subsequent release →
archiveDir / releaseDir / chunkPath(id) / filename (archive — REFACTOR)
- Line 310: CLOSED without subsequent release →
issuesDir / chunkPath(id) / filename (active; unchanged)
ai/services/github-workflow/LocalFileService.mjs — getIssueById already uses chunkPath(id) for active + recursive search for archive (archive shape agnostic; no changes needed)
ai/services/github-workflow/IssueService.mjs — V-B-A grep'd: zero archive-path references; service layer doesn't directly compute paths (no changes needed)
- New consumer:
archivePath() helper from AC2 (PR #11193) — requires caller-owned itemCount + itemIndex per planner pre-pass.
The Fix
- Replace
IssueSyncer#getIssuePath archive-write branches (lines 293, 306) with archivePath() helper from AC2.
- Add a release-bucket-planner pre-pass to compute
itemCount + itemIndex for each archive bucket BEFORE iteration. The planner runs once per version-folder per sync cycle; per-issue path resolution then makes O(1) helper calls with the precomputed bucket state. This honors AC2's "caller-owned bucket state" contract (no hidden filesystem scans inside the helper).
- Active-tier write paths (lines 283, 310) retain
chunkPath(id) semantic. Test explicitly verifies this with a closed-issue-without-subsequent-release case.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
IssueSyncer#getIssuePath archive branches |
Epic #11187 + AC2 (#11190) archivePath() contract |
Route closed-with-milestone + closed-with-later-release issues to archive/issues/vN.M.K/{flat|chunk-N}/ via archivePath() |
None — invalid milestone/release falls through to active tier |
Inline code comments + JSDoc updates |
Unit test verifying active vs archive pathing + planner pre-pass correctness |
Active-tier chunkPath(id) preservation |
Epic #11187 Cycle 2 amendment |
OPEN + CLOSED-without-subsequent-release issues keep issuesDir / chunkPath(id) / filename |
None — fall-through equivalent to current behavior |
JSDoc on getIssuePath |
Explicit test: chunkPath(11190) returns '111xx' after refactor invocation |
| Release-bucket-planner pre-pass |
New (caller-owned bucket state per AC2) |
Computes itemCount + itemIndex per version-folder once per sync cycle; per-issue path resolution then O(1) |
No fallback — must run before per-issue iteration |
JSDoc on planner |
Unit test verifying planner produces correct bucket state across multi-version scenarios |
Acceptance Criteria
Out of Scope
- Actually migrating existing
issue-archive/ data (Phase 3 of Epic #11187)
- Modifying
PullRequestService (AC4 — @neo-gemini-3-1-pro's lane) or DiscussionService (AC5 — open for @neo-gpt)
- Any modifications to the
archivePath helper itself (AC2 / PR #11193)
- Changing AC1 config (#11189 / PR #11191) — consumed as-is
- Active-tier ordinal-chunking refactor — explicitly REJECTED per Epic #11187 Cycle 2 amendment
Avoided Traps
- Active-tier ordinal chunking: rejected per Epic #11187 Cycle 2 amendment. Active-tier issues MUST retain
chunkPath(id) for LocalFileService#getIssueById O(1) determinism.
- Hidden filesystem scans inside helper consumption: rejected per AC2 (#11190) contract. Caller-owned bucket state via release-bucket-planner pre-pass.
- Per-issue filesystem scan during iteration: rejected — would defeat the planner pre-pass + drag IO into per-call hot path. Planner runs ONCE per sync cycle per version-folder.
- Modifying
LocalFileService to handle ordinal chunks specially: rejected — recursive search is shape-agnostic; active path is chunkPath(id) deterministic; no edits needed.
Related
- Parent Epic: #11187 Phase 1 AC3
- AC2 (helper): #11190 / PR #11193 (APPROVED at @tobiu merge gate)
- AC1 (config): #11189 / PR #11191 (APPROVED at @tobiu merge gate)
- AC4 (sibling — PullRequestService): #11196 (@neo-gemini-3-1-pro)
- AC5 (sibling — DiscussionService): #11197 (open)
- Blocked-by: #11189 + #11190 (cannot open PR against
dev until both land)
Origin Session ID
c2912891-b459-4a03-b2af-154d5e264df1
Handoff Retrieval Hints
query_raw_memories(query="Epic 11187 AC3 IssueService refactor archivePath planner pre-pass")
ask_knowledge_base(query="IssueSyncer getIssuePath archive write paths chunkPath")
- File:line anchors:
ai/services/github-workflow/sync/IssueSyncer.mjs:267-314 (specifically lines 293 + 306 for archive-write branches)
Context
Part of Epic #11187 Phase 1 execution. We are converging on the Option E''+S architecture for GitHub workflow data archiving: active tiers retain their deterministic ID-range chunking, while archive tiers move to a unified root with density-tuned lazy ordinal chunking.
Sibling sub-tickets: AC1 #11189 (config refactor; PR #11191 APPROVED at @tobiu merge gate), AC2 #11190 (archivePath helper; PR #11193 APPROVED at @tobiu merge gate), AC4 #11196 (PullRequestService — @neo-gemini-3-1-pro claimed), AC5 #11197 (DiscussionService — open).
The Problem
The current
IssueSyncer#getIssuePathwrites archive-tier issue data usingarchiveDir / vN.M.K / chunkPath(id) / filename(deterministic ID-range chunking under a version folder). Per Epic #11187 Cycle 2 amendment, archive-tier must switch toarchive/issues/vN.M.K/{flat|chunk-N}/with lazy ordinal chunking (sealed-chunk semantics viaprevent-reopen.yml). Active-tier writes (issuesDir / chunkPath(id) /) MUST retain XXxx ID-range semantic soLocalFileService#getIssueByIdkeeps O(1) deterministic lookup.The Architectural Reality
V-B-A'd against current
IssueSyncer.mjs:ai/services/github-workflow/sync/IssueSyncer.mjs—#getIssuePath(lines 267-314):issuesDir / chunkPath(id) / filename(active; unchanged)archiveDir / milestoneDir / chunkPath(id) / filename(archive — REFACTOR)archiveDir / releaseDir / chunkPath(id) / filename(archive — REFACTOR)issuesDir / chunkPath(id) / filename(active; unchanged)ai/services/github-workflow/LocalFileService.mjs—getIssueByIdalready useschunkPath(id)for active + recursive search for archive (archive shape agnostic; no changes needed)ai/services/github-workflow/IssueService.mjs— V-B-A grep'd: zero archive-path references; service layer doesn't directly compute paths (no changes needed)archivePath()helper from AC2 (PR #11193) — requires caller-owneditemCount+itemIndexper planner pre-pass.The Fix
IssueSyncer#getIssuePatharchive-write branches (lines 293, 306) witharchivePath()helper from AC2.itemCount+itemIndexfor each archive bucket BEFORE iteration. The planner runs once per version-folder per sync cycle; per-issue path resolution then makes O(1) helper calls with the precomputed bucket state. This honors AC2's "caller-owned bucket state" contract (no hidden filesystem scans inside the helper).chunkPath(id)semantic. Test explicitly verifies this with a closed-issue-without-subsequent-release case.Contract Ledger Matrix
IssueSyncer#getIssuePatharchive branchesarchivePath()contractarchive/issues/vN.M.K/{flat|chunk-N}/viaarchivePath()chunkPath(id)preservationissuesDir / chunkPath(id) / filenamegetIssuePathchunkPath(11190)returns'111xx'after refactor invocationitemCount+itemIndexper version-folder once per sync cycle; per-issue path resolution then O(1)Acceptance Criteria
IssueSyncer#getIssuePatharchive branches (lines 293, 306) refactored to usearchivePath()helper from AC2chunkPath(id); test verifies non-interferenceIssueSyncer#runor equivalent orchestration site); planner runs once per version-folder per sync cycleai/services/github-workflow/LocalFileService.mjsverified unchanged (no edits needed; archive recursive search is shape-agnostic) — capture as static-review confirmationOut of Scope
issue-archive/data (Phase 3 of Epic #11187)PullRequestService(AC4 — @neo-gemini-3-1-pro's lane) orDiscussionService(AC5 — open for @neo-gpt)archivePathhelper itself (AC2 / PR #11193)Avoided Traps
chunkPath(id)forLocalFileService#getIssueByIdO(1) determinism.LocalFileServiceto handle ordinal chunks specially: rejected — recursive search is shape-agnostic; active path ischunkPath(id)deterministic; no edits needed.Related
devuntil both land)Origin Session ID
c2912891-b459-4a03-b2af-154d5e264df1Handoff Retrieval Hints
query_raw_memories(query="Epic 11187 AC3 IssueService refactor archivePath planner pre-pass")ask_knowledge_base(query="IssueSyncer getIssuePath archive write paths chunkPath")ai/services/github-workflow/sync/IssueSyncer.mjs:267-314(specifically lines 293 + 306 for archive-write branches)