Skip to content

Solution-name resolution strategy (sources, lifecycle, rename handling) #29

@krisrowe

Description

@krisrowe

Problem

gapp uses the "solution name" as the primary identifier in several
places that create persistent, hard-to-rename external state:

  • GCP project label (gapp-{solution_name}=default, applied by
    gapp setup) — used by gapp list --available and
    discover_project_from_label to locate which project a solution
    is deployed to.
  • GCS bucket name (gapp-{solution_name}-{project_id}, created
    by gapp setup) — the solution's data bucket.
  • Secret Manager IDs ({solution_name}-{secret_short_name}) —
    every secret declared in the manifest is prefixed by the
    solution name.
  • Local registry (~/.config/gapp/solutions.yaml) — keyed by
    solution name.
  • GitHub topic — the gapp-solution topic itself is not
    name-scoped, but the repo→name mapping is implicit.

Current resolution order

In gapp/admin/sdk/manifest.py::get_solution_name:

  1. Top-level name: in gapp.yaml
  2. (was: solution.name: — removed as vestigial in Formal schema validation for gapp.yaml (fail fast with clear errors) #26)
  3. Fallback: the repo's directory name

What's broken

  • The directory name is a poor default. A repo can live under
    any folder — developers check repos out into directories named
    after the repo, shortened, renamed, inside monorepos, etc. The
    solution name deserves to be bound to something more intentional
    than "whatever the user called their clone directory."
  • No first-class way to set the name on bootstrap.
    gapp init takes no --name flag today. Users who want a name
    other than the directory name must hand-edit gapp.yaml after
    init and hope nothing downstream has already captured the wrong
    name.
  • Silent-rename footguns. Because name: is freely editable in
    the yaml and get_solution_name re-reads it on every load, a
    user who changes name: in the yaml (or renames the repo
    directory pre-init) silently triggers:
    • A second gapp-{new_name} label on the GCP project, leaving
      the old gapp-{old_name} label dangling.
    • A second GCS bucket (gapp-{new_name}-{project}) on next
      gapp setup, leaving the old bucket orphaned.
    • A new entry in solutions.yaml under the new name; the old
      entry is never cleaned up.
    • Secret Manager IDs under a new prefix on next deploy; old
      secrets orphaned.
      None of these issues are detected or warned about. A user
      discovers the breakage when gapp list --available starts
      showing phantom duplicates or when lookups start failing.
  • No alternative sources for derivation. Python projects
    already declare an authoritative name in pyproject.toml
    ([project].name), Node projects in package.json, etc. gapp
    ignores all of them and falls back to the directory.

Scope of exploration

This issue does not prescribe a solution — it enumerates the
design space so a decision can be made with full context.

Candidate sources for the name

In order of explicitness:

  1. Explicit CLI arg on bootstrapgapp init --name <X>.
    Writes name: into the newly-created gapp.yaml.
  2. Explicit yaml name: — current primary source.
  3. Project-manifest fallback — if name: is absent, read
    pyproject.toml [project].name (or package.json name,
    etc.) as a more-intentional default than the directory name.
  4. Directory-name fallback — current behavior. Candidate for
    removal.

Candidate rules for the lifecycle

  • Locked after init. Once gapp init has run, the name is
    committed to yaml and changing it requires gapp rename <new_name> which handles the cleanup: remove old label, old
    bucket, old registry entry, old secrets. This is the safe
    default and should probably be the rule regardless of other
    decisions.
  • Schema validation warns on unregistered manifests. If the
    repo has no entry in solutions.yaml and yaml name: differs
    from what init would have derived, warn loudly.
  • Make name: advisory in yaml but canonical in
    solutions.yaml.
    The local registry becomes the source of
    truth after init; yaml is merely informational. Any mismatch
    triggers an error, not a silent rename.
  • Or keep name: canonical and enforce by validator. The
    validator could refuse to load a manifest whose name: differs
    from the registered name without an explicit gapp rename step.

Candidate commands/flows

  • gapp init --name <X> — set on bootstrap.
  • gapp rename <new> — explicit rename with cleanup of label,
    bucket, registry, secrets.
  • Validator check: mismatch between yaml name: and
    solutions.yaml registered name → error with hint to run
    gapp rename.

Relationship to other issues

  • Companion to the gapp init / gapp manifest taxonomy issue.
    That issue decides where name configuration lives in the CLI
    (probably bootstrap-only). This issue decides how the name is
    resolved
    from available sources and what happens when it changes.
  • Downstream of Formal schema validation for gapp.yaml (fail fast with clear errors) #26 (schema validation). The schema already makes
    name: optional; this issue decides whether to keep it optional,
    deprecate it in favor of registry + fallbacks, or add validation
    rules around it.

Open design questions

  • Should pyproject.toml/package.json derivation be built into
    gapp or left as "users who want it set name: explicitly"?
  • Should gapp rename exist? Or is the contract "you don't
    rename — you re-init"? Re-init implies deleting the old
    project state manually.
  • Should the validator refuse to load a manifest whose name:
    has been edited after registration? If yes, with what escape
    hatch?
  • Is the directory-name fallback worth keeping at all, given how
    unreliable it is, or should a missing name: simply be an error
    with a hint to run gapp init --name <X>?

Work breakdown

  • Enumerate every place the solution name is used for persistent
    external state (labels, buckets, registry, secret prefixes,
    anything else). Verify the list above is complete.
  • Decide which candidate sources gapp supports and in what order.
  • Decide the lifecycle rule (locked after init / advisory / etc).
  • Decide whether gapp rename is implemented; if yes, what
    cleanup it performs.
  • Implement decisions. Each behavior change needs a code
    comment or docstring at the point of use explaining the rule.
  • Add tests covering: init with --name flag, rename flow
    (if implemented), validator behavior on name mismatch between
    yaml and registry, fallback behavior when name: is absent.
  • Update CONTRIBUTING.md under design decisions with the
    chosen rule, so future contributors don't re-introduce silent
    rename paths.
  • Update README.md to show how to set the name intentionally
    on bootstrap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions