LearnNewsExamplesServices
Frontmatter
id11190
titleAdd archivePath helper for sealed archive chunks
stateClosed
labels
enhancementaiarchitecture
assigneesneo-gpt
createdAtMay 11, 2026, 10:33 AM
updatedAtMay 12, 2026, 4:08 AM
githubUrlhttps://github.com/neomjs/neo/issues/11190
authorneo-gpt
commentsCount0
parentIssue11187
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[x] 11198 IssueService archive-write path refactor for lazy ordinal chunking (#11187 AC3)
closedAtMay 11, 2026, 1:51 PM

Add archivePath helper for sealed archive chunks

Closedenhancementaiarchitecture
neo-gpt
neo-gpt commented on May 11, 2026, 10:33 AM

Context

Sub-ticket for Epic #11187 Phase 1 AC2 after Cycle 2 parent amendment. #11187 now preserves active issues/ and pulls/ ID-range chunking while moving archive-tier placement to a single-root archive/{type}/vN.M.K/ shape with sealed 100-item ordinal chunks.

This ticket exists because AC3-AC5 service refactors need one shared archive-path primitive before they can safely move issue, pull request, and discussion archive write paths. The prior discussion surfaced a concrete failure mode: archive logic must not leak ordinal chunk-N/ semantics into active issue/pull lookup, because active lookup currently depends on deterministic chunkPath(id) routing.

The Problem

Archive write paths are currently fragmented across syncers:

  • IssueSyncer#getIssuePath() builds issue archive paths with archiveDir / version / chunkPath(id) / issue-N.md.
  • PullRequestSyncer#getPullRequestPath() sends all non-open PRs to pullArchiveDir / chunkPath(id) / pr-N.md.
  • DiscussionSyncer#getDiscussionPath() writes only active discussion files and computes the chunk expression inline.

#11187 changes the archive contract to a single root plus type/release buckets:

resources/content/archive/{issues,pulls,discussions}/vN.M.K/

Archive buckets are flat at <= 100 items and use sealed ordinal chunk-N/ directories only above that threshold. Without a shared helper, each syncer can drift on threshold boundaries, chunk naming, rejected-PR bucket handling, and active/archive separation.

The Architectural Reality

Source surfaces verified before filing:

  • ai/services/github-workflow/shared/chunkPath.mjs is the existing shared path helper sibling and remains canonical for active ID-range chunking.
  • ai/services/github-workflow/sync/IssueSyncer.mjs currently imports chunkPath() and applies it to both active and archive paths.
  • ai/services/github-workflow/sync/PullRequestSyncer.mjs currently imports chunkPath() and archives non-open PRs under pullArchiveDir / chunkPath(id).
  • ai/services/github-workflow/sync/DiscussionSyncer.mjs imports chunkPath() but still duplicates the chunk expression inline for active discussions.
  • ai/services/github-workflow/LocalFileService.mjs computes active issue lookup via issuesDir / chunkPath(id) / issue-N.md; this must remain unaffected.
  • #11187 Cycle 2 explicitly scopes ordinal chunk-N/ to sealed archive chunks only.

Structural pre-flight: new helper ai/services/github-workflow/shared/archivePath.mjs matches sibling utility pattern of ai/services/github-workflow/shared/chunkPath.mjs; sibling-file-lift fast-path applies. No novel directory choice.

The Fix

Add a shared archive-path helper under ai/services/github-workflow/shared/ that computes archive-tier paths from explicit bucket inputs. The helper should support:

  • archive root + type subdirectory
  • release version folder or non-release bucket such as rejected
  • flat-vs-ordinal chunk mode based on a planned archive bucket size / item ordinal
  • chunk-N naming with 100-item threshold semantics
  • strict archive-tier scope, leaving active chunkPath(id) behavior untouched

The helper should be pure where possible: callers should pass the planned bucket context (itemCount, itemIndex, or equivalent explicit state) instead of the helper independently scanning live directories. If a later service needs filesystem inspection to derive that context, that belongs in the caller or a separately testable planner, not hidden inside a path formatter.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
archivePath() helper #11187 AC2 Compute archive-tier paths under archive/{type}/...; flat at <= 100, ordinal chunk-N/ above 100 Throw on invalid type/version/bucket input rather than guessing JSDoc on helper; #11187 docs consume in later ACs Unit tests for boundary cases
Active path behavior #11187 Cycle 2 amendment; LocalFileService#getIssueById Remains chunkPath(id) for active issues/pulls; helper must not replace active lookup Existing chunkPath() remains available Helper JSDoc explicitly says archive-tier only Regression test or static test proving active helper not touched
Rejected PR bucket path #11180 OQ1 + #11187 Non-release archive/pulls/rejected/ bucket supported by same helper contract Explicit bucket argument; no release inference inside helper JSDoc examples Unit test for rejected bucket

Acceptance Criteria

  • Add ai/services/github-workflow/shared/archivePath.mjs or equivalent shared helper in the existing shared utility directory.
  • Helper exposes a documented API for archive-tier path construction using explicit bucket state.
  • Boundary behavior is covered: 0/1/100 items stay flat; 101st item routes to chunk-2/ when the first 100-item chunk is sealed, or equivalent planned-bucket semantics are documented and tested.
  • chunk-N/ naming is ordinal and not ID-range based.
  • Helper supports type buckets for issues, pulls, and discussions.
  • Helper supports non-release buckets needed by the epic, especially archive/pulls/rejected/.
  • Helper does not modify or replace chunkPath() active-tier behavior.
  • Tests cover flat archive, chunked archive, rejected PR bucket, invalid input, and active-tier non-interference.
  • Implementation does not depend on hidden live-directory scans inside the helper; any filesystem-derived bucket state is caller-owned or planner-owned.

Out of Scope

  • Config refactor AC1 (#11189), except consuming the final config shape once corrected.
  • Issue/PullRequest/Discussion syncer refactors AC3-AC5.
  • Data migration or moving any files.
  • Active issue/pull ordinal chunking; explicitly rejected by #11187 Cycle 2.
  • Discussion active flattening AC6.

Avoided Traps

  • Ordinal chunks for active issues/pulls: rejected because active path lookup must remain deterministic from ID.
  • Hidden filesystem scans in the helper: rejected because it couples path formatting to mutable disk state and makes tests harder to reason about.
  • Per-syncer duplicate logic: rejected because the archive contract is cross-type and will drift if copied into each syncer.
  • Per-type fixed shape config: rejected by amended #11187; archive shape is bucket-size based, not hard-coded issue/pull/discussion shape.

Related

  • Parent Epic: #11187
  • AC1 Config lane: #11189 (must be corrected before implementation consumes config)
  • Discussion: #11180
  • Extended V-B-A discussion: #11188
  • Current helper sibling: ai/services/github-workflow/shared/chunkPath.mjs
  • Consumer guard: ai/services/github-workflow/LocalFileService.mjs

Origin Session ID

Origin Session ID: 22713fa8-23d2-4b31-918b-6e2f48d69c06

Handoff Retrieval Hints

  • query_raw_memories(query="11187 AC2 archivePath helper active tier deterministic chunkPath")
  • query_raw_memories(query="Discussion 11180 lazy ordinal chunking sealed archive LocalFileService")
  • ask_knowledge_base(query="GitHub workflow archive path IssueSyncer PullRequestSyncer DiscussionSyncer chunkPath")
tobiu referenced in commit a7547e8 - "feat(github-workflow): add archive path helper (#11190) (#11193) on May 11, 2026, 1:51 PM
tobiu closed this issue on May 11, 2026, 1:51 PM
tobiu referenced in commit c459245 - "refactor(github-workflow): use archivePath for pull requests and discussions (#11187) (#11199) on May 11, 2026, 2:04 PM