awslabs / awslabs/agentcore-samples
Agent Registry migration tool: runtime.transform.duplicateNames="suffix" is documented but not implemented
- Dominant language
- Python
- Stars
- 3.4k
- Forks
- 1.3k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 30
Description
## Summary
In `01-features/07-centralize-and-govern-your-ai-infrastructure/03-registry/04-migrate-to-new-namespace`, the config setting `runtime.transform.duplicateNames` (values `"fail"` / `"suffix"`, documented in `docs/configuration.md` and `docs/detaileddoc.md`) has no effect when set to `"suffix"`. Records that collide on `name`+`recordVersion` in the target namespace still hard-fail at load time exactly as if the setting were absent or set to `"fail"`.
## Repro
1. Preview (`bedrock-agentcore`) registry has ≥2 records with the same `name` and no/matching `recordVersion` (legal there — `name` was just a display label).
2. `migration.config.json` sets:
```json
"runtime": { "transform": { "duplicateNames": "suffix" } }
```
3. Run `init`/`target-config --create` → `check` → `extract` → `load --dry-run` (or `--live`).
**Expected** (per docs): colliding records are migrated under distinct, deterministically-suffixed target names (suffix "derived from the source record's identity" per `docs/configuration.md`), nothing is dropped.
**Actual**: each colliding record after the first fails with:
```
Preview records '///' and '///' both migrate to the target registry name '' (recordVersion ''). That identity is already claimed in registry ; the new version requires it to be unique, so loading the second would overwrite the first. Rename one of them in the source registry, or give them distinct recordVersions, and re-extract.
```
identical whether `duplicateNames` is `"fail"` (default), `"suffix"`, or absent from the config entirely.
## Root cause
`duplicateNames` is never read by the code that actually claims target names. Searching the whole tool subtree:
- `grep -r duplicateNames lib/ cli/ dist/ glue/common/migration_common/` → **zero matches** in any implementation file.
- It only appears in `docs/configuration.md`, `docs/detaileddoc.md`, and in comments/test scaffolding (`glue/common/tests/test_jobs_end_to_end.py`, `glue/common/tests/test_report_html.py`, `tools/seed_preview_test_registry.py`).
- The actual failure originates in `glue/common/migration_common/jobs/transform_load.py` (`claim_in_order`) → `glue/common/migration_common/registry_api.py` (`claim` / `_claim_target_record`), which raises unconditionally on a name collision with no branch on the configured `duplicateNames` mode.
- Notably, `tools/seed_preview_test_registry.py` (lines ~52-63, ~512-517) documents the *intended* behavior in comments — extract should detect the clash and resolve it once `duplicateNames="suffix"` — and states a duplicate-name fixture was deliberately removed from the seed matrix "because... extract detects the clash... and resolves it once `runtime.transform.duplicateNames` is set to `suffix`." That resolution path does not exist in the shipped code, so the fixture's removal currently masks a live gap in test coverage for this setting.
Default (`"fail"`/unset) behavior otherwise matches its documented contract (the run completes with `PARTIAL_SUCCESS`, colliding records are skipped and listed in the failure report) — this issue is scoped specifically to `"suffix"` mode being a no-op.
## Impact
With the default `runtime.load.failOnRecordError=false`, a migration under an unattended/Glue-managed run can silently skip colliding records while reporting `PARTIAL_SUCCESS` — a user who set `duplicateNames="suffix"` believing it protects against this will not get an error signal distinct from the unset case, and could lose records without realizing it unless they inspect the failure report closely.
## Suggested fix
Wire `duplicateNames` through to `claim_in_order`/`claim` in `migration_common`: when `"suffix"`, assign each colliding record (after the first claimant) a deterministic target `name` suffix derived from the source record's identity, preserve the original name as `displayName`/`previewName` per the existing crosswalk semantics, and continue instead of raising. Happy to open a PR for this — will follow up.
Contributor guide
Research direction
Start with glue/common/migration_common/jobs/transform_load.py at claim_in_order, then follow the target-name flow into registry_api.py at claim and _claim_target_record. Review docs/configuration.md, docs/detaileddoc.md, and the comments in tools/seed_preview_test_registry.py, then run the documented init/target-config, check, extract, and load --dry-run flow. Done means suffix mode preserves every colliding record under deterministic distinct names while fail or unset retains the documented behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- data, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100