Design: deterministic migration off legacy central modules#374
Draft
anshulsao wants to merge 3 commits into
Draft
Design: deterministic migration off legacy central modules#374anshulsao wants to merge 3 commits into
anshulsao wants to merge 3 commits into
Conversation
Deterministic pipeline to move a project off the old central-module architecture (facets-iac module.json, is_old addressing) onto new-style registry modules, via a zero-change import into a new project. Key findings that shaped the design: - The predicate is generate.py's `is_old` flag, not module.json presence. is_old resources address as module.level2.module.<dirname>_<resource>; everything else as <intent>_<resource>. Migration always moves the address, and the translation (dirname -> provides) is deterministic. - Scope is 9 modules across 5 intents, not the 238 module.json files in the tree. 15 more already carry is_old: false. - Only 2 of 171 legacy (intent, flavor) pairs exist in facets-v3-modules or facets-modules-redesign, so mapping onto the v3 catalog cannot be the default strategy. Modules are rewrapped old-posture instead: main.tf copied byte-identical, the three engine-injected templates materialized, facets.yaml synthesized from module.json. - Renaming the flavor to avoid a registry collision silently drops the advanced block, because main.tf.mustache reads it by flavor key. The script rewrites flavor and advanced.<flavor> in lockstep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Corrections to the first draft: - The predicate is module_source == "local" (the module resolves out of the facets-iac repo), NOT is_old. is_old only decides how the terraform address is composed: <dirname>_<name> when true, <intent>_<name> when false. Both are legacy. Scope is 192 non-deprecated modules, of which 8 move address. - The 29 modules under 0_input_config are system modules present in every project. They are legacy too. Adds: - project-type/old-style/project-type.yml — starts from cloud account only, no baseTemplatePath. Module list accretes per adopted project; seeded with the 24 (intent, flavor) pairs the capillary-cloud pilot actually uses. Flavors carry a _legacy suffix; output types namespaced @legacy/*. - Module-reuse evaluation. Comparing terraform resource+module address sets (excluding actions.tf boilerplate): 18 of the pilot's 19 legacy module directories must be written as rewraps. The one candidate, helm_simple vs common/helm/k8s_standard/1.0, shares helm_release.external_helm_charts but its body differs — the gate decides, not inspection. - Vendoring constraint. Legacy modules reference ../3_utility/* and even other legacy modules via relative sources. They only work in-tree. The rewrap must vendor the transitive closure and rewrite source paths; module block names stay fixed so addresses survive. - Script inventory and per-environment adoption procedure. First environment is facetsdemo (RUNNING, a demo CP, not a customer's). 68 variables/secrets carry across; values never enter a transcript. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drops the bin/ script plan. The pipeline is now run by hand per
(project, environment) from docs/sop-legacy-module-migration.md, which is
shaped after the existing facets-gcp-zero-change-import sop-template.
Commands verified against the installed raptor rather than assumed:
raptor create project-type NAME --description
raptor create resource-type-mapping PROJECT_TYPE --resource-type TYPE/FLAVOR
raptor create environment NAME -p PROJECT --release-stream
raptor create variable NAME -p PROJECT [--secret|--global|--env-values|-f]
raptor create output-type @NAMESPACE/NAME -f FILE
raptor create iac-module -f DIR --dry-run --block-remote-modules
raptor create release -p P -e E --target TYPE/NAME --plan
raptor apply resource KIND/FLAVOR/VERSION -n NAME --spec-file --input
raptor apply override -p P -e E
--block-remote-modules turns out to be the machine check for the vendoring
closure: it rejects any non-local module source, so a passing dry-run proves
the ../3_utility/* graph was fully vendored.
Removing the scripts makes three failures silent rather than impossible, so
the SOP carries them as explicit guardrails G1-G5:
G1 main.tf byte-identical to the legacy source (diff)
G2 advanced.<flavor> renamed in lockstep with flavor (grep) — a miss
resolves the block to {} with no error and possibly no plan diff
G3 no `source = "../"` escapes the module dir (grep + --block-remote-modules)
G4 is_old address translation recorded per resource
G5 the gate is never loosened
mapping.yaml stays a checked-in, reviewed artifact — it is the state that
outlives any single migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Design spec only — no code, no modules yet. Opening as a draft so the approach can be reviewed before implementation.
The problem, restated precisely
"Old identification" turned out to be a specific flag in the IaC engine:
So migration always moves the terraform address, and the translation (
dirname → provides) is deterministic frommodule.json. Flavor and version never enter the address.Scope is smaller than it looks
module.jsonfiles in the treeis_old: trueis_old: false(already migrated in-repo)Three findings that shaped the design
The v3 catalog is not a migration target. Only 2 of 171 legacy
(intent, flavor)pairs exist infacets-v3-modules+facets-modules-redesign. Modules are rewrapped old-posture instead:main.tfcopied byte-identical, the three engine-injected templates materialized,facets.yamlsynthesized frommodule.json. Import is then 0-diff by construction, not by verification.The in-repo precedent is only half the job. The 15
is_old: falsemodules bumped version and kept flavor (memorystore→memorystore_redis_alpha@ 0.2). But theirmain.tfdiffers from the original, so they carry no zero-diff guarantee — and they remainmodule_source == "local", so they never touched registration.Renaming the flavor silently drops config.
main.tf.mustachereads the advanced block by flavor key:The script must rewrite
flavorandadvanced.<flavor>in lockstep, and assert no pre-rename key survives.Pipeline
SCAN → JOIN → MAP → REWRAP → EMIT → GATE, where the gate is the existingpraxis-zero-change-importbar (0 to change, 0 to destroy, nothingmust be replaced) and is not modified. The mapping table accretes one project at a time rather than being built up front.Pilot is the capillarycloud project on the root CP.
🤖 Generated with Claude Code