From aac6f81800c0e8253a46211fe591caedca616d8b Mon Sep 17 00:00:00 2001 From: Jan Rose Date: Wed, 6 May 2026 10:11:32 +0200 Subject: [PATCH] testserver: 404 on permissions GET when V2 parent is gone The recreated_same_name drift test asserted "update permissions" because the testserver returned an empty ACL for any object_id with no entry. The real cloud returns 404 for V2 permissions resources (verified for vector_search_endpoints and experiments), so the planner sees `remoteState == nil` and emits "create" instead. V1 resources (jobs, pipelines) retain ACL data after delete via async/soft delete, which the existing "empty ACL on miss" branch approximates well enough. Make GetPermissions check parent existence by default; for now only vector-search-endpoints has a parent lookup wired up. Update the test assertion and output to expect "create". Co-authored-by: Isaac --- .../drift/recreated_same_name/output.txt | 4 +-- .../drift/recreated_same_name/script | 2 +- libs/testserver/permissions.go | 30 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt index 08afd3157e1..d24c6bea8d1 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt @@ -38,9 +38,9 @@ Remote recreated endpoint UUID: [REMOTE_RECREATED_ENDPOINT_UUID] === Plan detects the UUID change and proposes recreate >>> [CLI] bundle plan recreate vector_search_endpoints.my_endpoint -update vector_search_endpoints.my_endpoint.permissions +create vector_search_endpoints.my_endpoint.permissions -Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged +Plan: 2 to add, 0 to change, 1 to delete, 0 unchanged === Deploy recreates the endpoint and rebinds permissions to the new UUID >>> [CLI] bundle deploy diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script index 0a17aa3152a..8f28189d7f1 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script @@ -33,7 +33,7 @@ if [ "$original_endpoint_uuid" = "$remote_recreated_endpoint_uuid" ]; then fi title "Plan detects the UUID change and proposes recreate" -trace $CLI bundle plan | contains.py "recreate vector_search_endpoints.my_endpoint" "update vector_search_endpoints.my_endpoint.permissions" +trace $CLI bundle plan | contains.py "recreate vector_search_endpoints.my_endpoint" "create vector_search_endpoints.my_endpoint.permissions" title "Deploy recreates the endpoint and rebinds permissions to the new UUID" trace $CLI bundle deploy diff --git a/libs/testserver/permissions.go b/libs/testserver/permissions.go index 3589b9d704f..e7983b1afa7 100644 --- a/libs/testserver/permissions.go +++ b/libs/testserver/permissions.go @@ -107,6 +107,19 @@ func (s *FakeWorkspace) GetPermissions(req Request) any { } responseObjectID := fmt.Sprintf("/%s/%s", requestObjectType, objectId) + + // V2 permissions APIs cascade-delete ACLs with the parent, so the cloud + // returns 404 once the parent is gone. V1 APIs (jobs, pipelines, etc.) + // retain ACL data after delete via async/soft delete; for those, we + // fall through to the "empty ACL on miss" branch below, which is close + // enough. New V2 resources should add a case to permissionsParentExists. + if !s.permissionsParentExists(requestObjectType, objectId) { + return Response{ + StatusCode: 404, + Body: map[string]string{"message": fmt.Sprintf("%s %s not found.", requestObjectType, objectId)}, + } + } + permissions, exists := s.Permissions[responseObjectID] if !exists { @@ -123,6 +136,23 @@ func (s *FakeWorkspace) GetPermissions(req Request) any { } } +// permissionsParentExists reports whether the parent object backing a +// permissions request exists in workspace state. Returns true for resource +// types without a parent-existence check wired up; V1 resources rely on +// that fallback to keep their "empty ACL on miss" behavior. +func (s *FakeWorkspace) permissionsParentExists(requestObjectType, objectId string) bool { + switch requestObjectType { + case "vector-search-endpoints": + for _, ep := range s.VectorSearchEndpoints { + if ep.Id == objectId { + return true + } + } + return false + } + return true +} + func (s *FakeWorkspace) SetPermissions(req Request) any { defer s.LockUnlock()()