Skip to content

Wayfinder: consolidate the service submodules into a single repo #138

Description

@mosoriob

Destination

An executable migration spec. It moves model-catalog-api, mint-ensemble-manager and
graphql_engine into this repo as plain directories. It retires the submodule superproject.

The spec must contain:

  • the history import method for each service;
  • the CI workflow shape for each imported service;
  • the image tag scheme after consolidation, and the rewrite of update-helm-image-tags,
    dynamo-bump-from-branch and test-on-k8s-dev;
  • how dynamo and helm-charts pin images after the change;
  • the disposition of the four dead submodules and of the four source repositories;
  • where the agent instruction files land, and which references must be fixed;
  • the cutover order and the rollback.

The map is complete when nothing is left to decide. The spec is then handed off for execution.

The map is complete. It was closed on 2026-08-31. The spec is
docs/runbook-single-repo-cutover.md,
with ADR-0003
for the reasoning. Nothing was left to decide.

Execution then followed the spec and reached Phase 10 on 2026-08-30. Phase 11, the
dynamo pin, remains. It is execution, not a decision, so it sits past this map's
destination. This map does not track it.

Notes

Terms. The end state is the single-repo. Today's layout is the superproject. Do not
write "monorepo" in this map. The repo is named monorepo and the word means both things.

Drivers. Daily friction and agent navigability. Release coordination is the cost of the
change, not the reason for it.

Skills: use /grilling and /domain-modeling on each grilling ticket.

Decisions already made by the dev (do not re-open them):

  1. The map ends at a spec. It does not execute the migration.
  2. Three services come in: model-catalog-api, mint-ensemble-manager, graphql_engine.
  3. helm-charts stays a separate repo. It publishes a Helm chart repository (committed
    index.yaml and releases/*.tgz up to MINT-9.0.0-beta.6, measured 2026-08-30), and dynamo
    pins chart versions from it. global.imageTag does not exist in the chart yet; adding it is a
    mintproject/mint pull request, sequenced by
    #147.
  4. ui is not imported. Its submodule stub stays until
    #81 removes Lit from TACC. This map
    does not wait for that.
  5. Four submodules are dropped, not imported: model-catalog-ontology (3 years stale),
    MINT_USERGUIDE (1 year stale), dynamo-experiment-may (unrelated),
    model-catalog-fetch-api-client (npm package whose only consumer is ui).
  6. Workspace tooling is out of scope. Each service keeps its own package.json, lockfile and
    build.
  7. Agent instruction files must land somewhere and must not point at missing files. Rewriting
    their content is out of scope.
  8. All images publish to GHCR. Docker Hub is dropped. This settles the registry half of
    #142; only the meaning of the tag is still
    open. ensemble-manager, mint-ui-lit and mint-ui-react move off Docker Hub. mint-ui-lit
    moves even though ui is not imported.

Facts measured on 2026-08-29 at 08cdae9:

Fact Value
Superproject commits, last 12 months 209, all by one author
Superproject commits that only move a pointer, last 6 months 50 of 191
Submodules declared in .gitmodules 9
Submodules with a commit in the last 12 months 6
.git/modules total ~120 MB, of which ui is 40 MB
Human pull requests open across all submodule repos 2
Dependabot pull requests open on mint-ui-lit 8

The release path — measured, not guessed. See
#140 for the full table. Two corrections to
what this map first assumed:

  • Every service already writes the same three tags: <sha>, <safe-branch>, and latest on
    the default branch. The convention is not three-ways-split.
  • graphql_engine publishes to GHCR, not Docker Hub. The split was 2 GHCR / 3 Docker Hub.
    Decision 8 below now removes it.
  • ui-react is already in-repo and path-filtered (.github/workflows/ui-react.yml). It is the
    template the imported services should follow.

Both bump skills break, they do not degrade. update-helm-image-tags/SKILL.md:41 and
dynamo-bump-from-branch/SKILL.md:38 both resolve a tag with
git ls-remote https://github.com/<owner>/<repo>.git refs/heads/<branch>. After consolidation
there is no per-repo remote to query.

The failure is already live. #70
recorded that ui_react's image tag is a superproject SHA which the bump automation silently
skips, so it is hand-set. This map makes that special case the normal case for every service.

Docs are already stale. CLAUDE.md lists model-catalog-fastapi/ and
model-catalog-endpoint/. Neither directory exists. CLAUDE.md also opens with
@.wolf/OPENWOLF.md and .claude/rules/openwolf.md requires .wolf/anatomy.md. There is no
.wolf/ directory.

Production risk. A Helm upgrade does not run the Hasura migrations. The migration job is a
post-install hook. See #117.

Decisions so far

  • Inventory the release path — every service
    already writes the same three tags; the real split is two registries, and the chart already pins
    ui_react to a superproject SHA.

  • Inventory every reference that breaks — six
    break on import, three of them silently; eight more are already broken today.

  • Decide the image tag scheme for the single-repo —
    tags are total: every single-repo SHA builds an image for every service, because the repo is
    public and Actions minutes are free. The chart pins one global.imageTag, and the two bump skills
    merge into one.

  • Decide the history import method for each service —
    all three import the same way: git filter-repo --to-subdirectory-filter then merge
    --allow-unrelated-histories, taking each service's main, mainline only, with one shared
    mailmap. Branches and tags do not cross, and saving them is not a cutover precondition.

  • Decide what happens to the source repositories at cutover —
    all four source repos are archived, never deleted: the archive is the only copy of the
    unmerged branches, and archiving is what stops a stray push republishing latest. The 12 open
    issues transfer out first; every write happens before the archive.

  • Decide the CI workflow shape for each imported service —
    one workflow per service, no path filter, and publish keeps needs: [test]. Filtering would
    cost the publish gate, because GitHub path filters are workflow-level. Each service runs only the
    checks it already has. Three cutover preconditions land on mint-ensemble-manager.

  • Decide where the agent instruction files land —
    nothing breaks and nothing moves: nested CLAUDE.md and directory-scoped skills are path-based,
    not repo-based. The root file becomes the index instead, the dead OpenWolf block is deleted, and
    root skills go from three to two.

  • Decide what happens to per-service repo-scoped tooling —
    husky is mutual exclusion, not wrong scope: core.hooksPath holds one value and ui-react holds
    it, so mint-ensemble-manager drops husky and lint-staged. .release-it.json configures a tool that
    was never installed; it and VERSION are deleted. Releases return, cut by the single-repo only,
    starting at 0.1.0 via release-please, and a release publishes a <version> image tag. Amends
    #142 decisions 2 and 5.

  • Design the cutover order and the rollback —
    twelve phases, three import pull requests, develop merged into main first. The point of no
    return is the first dynamo pin, not the archive. The spec is now
    docs/runbook-single-repo-cutover.md.

Not yet specified

Empty. Both patches were resolved while closing
#147 and moved to Out of scope. The frontier
reached the destination.

Out of scope

  • Workspace tooling. npm workspaces, a shared tsconfig, a task runner, hoisted
    node_modules, shared lint config. The repo move is mechanical; tooling unification is
    open-ended. It needs its own effort and its own evidence.

  • Rewriting agent instruction content. This map fixes references that break on import, and
    deletes pointers to files that never existed. It does not redesign what the files say. Amended
    while resolving #146: the missing .wolf/
    directory was listed here and is now in scope. Deleting a dangling pointer is not rewriting
    content, because there is no content behind it.

  • A CLAUDE.md for graphql_engine. It has none and gains none. Writing one is content, and
    content is out of scope. Recorded as a known gap while resolving
    #146.

  • Importing ui. Decided above; the stub is removed by
    #81.

  • Folding helm-charts into the single-repo. It is a published chart repository with an
    external consumer.

  • The stale ensemble-manager:latest tag. Found while measuring
    #140, filed as
    #148. A live production-facing defect that
    exists with or without consolidation. It is not on the route.

  • mint-ui-react:latest has never been published. Found while deciding
    #142, filed as
    #151. main is the default branch and is 83
    commits stale, so the latest gate never opens. Same class as MINT chart ships a four-month-stale ensemble-manager by default #148, opposite cause. Live with or
    without consolidation, and not on the route.

  • The 8 Dependabot pull requests on mint-ui-lit. Confirmed still 8, all dev or transitive
    bumps. mint-ui-lit is not a source repository for the import and nothing about it changes at
    cutover. Ruled out while resolving
    #144. Map
    #79 owns it.

  • Required status checks on main and develop. Neither branch is protected, so CI blocks no
    merge today. Found while resolving
    #145. Adding protection is repo governance,
    not a consolidation step, and it would fail merges immediately on mint-ensemble-manager's 475
    lint errors. The spec states the gap; it does not close it.

  • The mint-ensemble-manager lint and format backlog. 475 eslint errors and 74 unformatted
    files, measured on 2026-08-29. Its husky + lint-staged setup only ever formats files as they are
    touched, so the backlog never shrinks. CI runs neither check after cutover. Cleaning it during a
    repo migration is not on the route.

  • A version file for graphql_engine. It has no package.json, so the shared release version
    cannot be written into it. Its version lives in the git tag and the image tag only. Adding a
    package.json to hold one field is new tooling. Recorded while resolving
    #149.

  • Removal of the ui submodule stub. Was fog. It sits past this map's destination: decision 4
    says this map does not wait for #81, and the
    stub survives the cutover untouched. Map #79
    owns it. ui's image still moves to GHCR here, because that is a registry change, not an
    import.

  • Whether helm-charts should come in later. Was fog with a revisit condition. The condition
    fired — #149 lets the chart pin a
    <version> instead of a SHA — and the conclusion held. helm-charts is external because it has
    external consumers, and the pin format does not change that. This duplicates the ruling above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    wayfinder:mapWayfinder map: the shared route for an effort

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions