Reserve Promptkit catalog profile names

This commit is contained in:
2026-09-22 12:17:41 +00:00
parent 02fd09955d
commit 4c3828857c
4 changed files with 42 additions and 0 deletions

View File

@@ -72,6 +72,10 @@ manifest, layout, profile ownership, inheritance, and secret-safety rules
before returning raw catalog sources. Internal engine assembly uses those validated
catalogs as the maintained lowest-precedence profile source.
Normalized external catalog profile IDs with the exact `promptkit-` prefix are
rejected at this adapter boundary. That reservation applies only to imported
catalogs; configured consumer sources retain the ordinary profile-ID rules.
The overlay repository consults the next repository only when the
higher-precedence repository reports that a profile is absent. A reliably
selected malformed profile stops fallback, while an unrelated malformed file

View File

@@ -128,6 +128,8 @@ checkouts, workspaces, or replacements.
## Stage 3: Enforce The External Catalog Namespace
**Completed:** 2026-09-22
Repository: Promptkit. Keep current v1 dependencies during this stage;
dependency adoption remains atomic in Stage 5.

View File

@@ -64,6 +64,9 @@ func Load(sources ...Source) (Set, error) {
}
resolvingRepository := profile.NewResolvingRepository(repository)
for _, entry := range metadata {
if strings.HasPrefix(entry.ID, "promptkit-") {
return Set{}, fmt.Errorf("catalog %s: %s uses the reserved promptkit- profile ID prefix", source.Name, entry.Path)
}
if profileIDs[entry.ID] {
return Set{}, fmt.Errorf("catalog %s: %s duplicates an earlier profile ID", source.Name, entry.Path)
}

View File

@@ -193,6 +193,39 @@ func TestLoadRejectsInvalidCatalogProfiles(t *testing.T) {
}
}
func TestLoadReservesPromptkitProfilePrefixForCatalogs(t *testing.T) {
tests := map[string]struct {
profileID string
wantError bool
}{
"maintained name": {
profileID: "promptkit-openrouter-standard",
wantError: true,
},
"other reserved name": {
profileID: "promptkit-example",
wantError: true,
},
"ordinary name": {
profileID: "ordinary-profile",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
fsys := validCatalogFS("one")
fsys["catalog/profiles/one-profile.yml"].Data = []byte(validProfile(test.profileID, "one"))
_, err := Load(testSource("one", fsys))
if (err != nil) != test.wantError {
t.Fatalf("Load() error = %v, want error %t", err, test.wantError)
}
if err != nil && !strings.Contains(err.Error(), "catalog one: one-profile.yml") {
t.Fatalf("Load() error = %v, want catalog source and path context", err)
}
})
}
}
func TestLoadRejectsCrossCatalogConflicts(t *testing.T) {
t.Run("duplicate backend", func(t *testing.T) {
second := catalogFS("one", map[string]string{