Skip to content

Commit 4283d88

Browse files
kaje94xlight05
authored andcommitted
fix: skip Build drawer collect for Registered External names
Preflight emitted external-config while project provision was still unknown, so Continue forced typing org secrets that POST /build ignores.
1 parent 412275a commit 4283d88

4 files changed

Lines changed: 94 additions & 11 deletions

File tree

services/aep-api/internal/app/app.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,8 +1040,9 @@ func Assemble(cfg config.Config, in Infra, seam Seam) (*App, error) {
10401040
// anything already provisioned OR in-flight (buildProvisionStatus collapses the
10411041
// provisioning tri-state onto the "already handled" bool).
10421042
preflightSvc := build.NewPreflightService(build.PreflightDeps{
1043-
Design: designComponents{store: artifactStore},
1044-
Status: buildProvisionStatus{svc: provisioningSvc},
1043+
Design: designComponents{store: artifactStore},
1044+
Status: buildProvisionStatus{svc: provisioningSvc},
1045+
Catalog: buildOrgCatalog{plane: catalogValuePlane},
10451046
})
10461047
// delivery — the Delivery Pipeline domain (P6): the public single-tag build
10471048
// surface, the task read + promote-dispatch surface, and the task-log SSE

services/aep-api/internal/app/build_adapters.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ func (b buildProvisionStatus) Ready(ctx context.Context, orgID, projectID, depNa
9595
return st.Status != "unknown", nil
9696
}
9797

98+
// buildOrgCatalog adapts CatalogValuePlane onto preflight's OrgCatalogReader:
99+
// non-empty org env cells means Registered External — the drawer must not
100+
// collect values. Nil plane is fail-open (HasOrgEnvCells false).
101+
type buildOrgCatalog struct {
102+
plane provisioning.CatalogValuePlane
103+
}
104+
105+
func (b buildOrgCatalog) HasOrgEnvCells(orgID, name string) bool {
106+
if b.plane == nil {
107+
return false
108+
}
109+
return len(b.plane.EnvCells(orgID, name)) > 0
110+
}
111+
98112
// buildGateResolver adapts the provisioning feature onto the build plan path's
99113
// GateResolver port: author the version's dependencies and mint its
100114
// `provision` gates INTO the version's milestone, so the run's dispatch

services/aep-api/internal/delivery/build/preflight.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ type ProvisionStatusReader interface {
4646
Ready(ctx context.Context, orgID, projectID, depName string) (bool, error)
4747
}
4848

49+
// OrgCatalogReader reports whether a logical name already holds values on the
50+
// org catalog plane (Registered External — non-empty env cells). Nil means
51+
// fail-open: treat the name as Project External and still emit external-config.
52+
type OrgCatalogReader interface {
53+
HasOrgEnvCells(orgID, name string) bool
54+
}
55+
4956
// --- wire shapes (names drive the generated schema names — keep them exactly
5057
// --- BuildPreflight / PreflightItem / ConfigKeyView) ------------------------
5158

@@ -86,20 +93,22 @@ type BuildPreflight struct {
8693

8794
// PreflightDeps carries the preflight service's ports.
8895
type PreflightDeps struct {
89-
Design PreflightDesignReader
90-
Status ProvisionStatusReader
96+
Design PreflightDesignReader
97+
Status ProvisionStatusReader
98+
Catalog OrgCatalogReader
9199
}
92100

93101
// PreflightService computes the build dependency-drawer preflight from the
94102
// design at HEAD, filtering out anything already provisioned or in-flight.
95103
type PreflightService struct {
96-
design PreflightDesignReader
97-
status ProvisionStatusReader
104+
design PreflightDesignReader
105+
status ProvisionStatusReader
106+
catalog OrgCatalogReader
98107
}
99108

100109
// NewPreflightService wires the preflight service.
101110
func NewPreflightService(d PreflightDeps) *PreflightService {
102-
return &PreflightService{design: d.Design, status: d.Status}
111+
return &PreflightService{design: d.Design, status: d.Status, catalog: d.Catalog}
103112
}
104113

105114
// Preflight walks every component's dependencies at HEAD — service AND
@@ -116,7 +125,9 @@ func NewPreflightService(d PreflightDeps) *PreflightService {
116125
// computed Status/Reason (spec.ComputeDependencyStatus, via
117126
// dependencyBlocker) says so; this is the dependency-management proceed
118127
// gate Task 1 orphaned, reborn here. Otherwise, an "external-config" item
119-
// (key/secret views only) when the dependency is not yet Ready.
128+
// (key/secret views only) when the dependency is not yet Ready and is not
129+
// a Registered External (org catalog already holds env cells — the drawer
130+
// must not collect secrets that live on the org record).
120131
// - platform-resource: a "platform-resource" item when not yet Ready.
121132
// - org-service: an "org-service" item when Status is one of the three
122133
// non-resolved resolution states (unresolved | blocked | ambiguous);
@@ -172,9 +183,10 @@ func (s *PreflightService) itemsFor(ctx context.Context, orgID, projectID, compo
172183
// still-ambiguous/unresolved dependency has no derived config keys yet). Once
173184
// resolved (or when no resolver was ever wired — the fail-open empty Status),
174185
// the pre-existing external-config item (key/secret views only) is emitted
175-
// when the dependency is not yet Ready — unchanged: config collection stays a
176-
// provisioning-readiness concern local to this preflight, not part of
177-
// ComputeDependencyStatus.
186+
// when the dependency is not yet Ready and is not Registered (org catalog
187+
// env cells). Config collection stays a provisioning-readiness concern local
188+
// to this preflight, not part of ComputeDependencyStatus. Registered names
189+
// still author from org cells at POST /build; they just do not collect here.
178190
func (s *PreflightService) externalItems(ctx context.Context, orgID, projectID, componentName string, d spec.Dependency) ([]PreflightItem, error) {
179191
if kind, desc, blocked := dependencyBlocker(d); blocked {
180192
return []PreflightItem{{
@@ -184,6 +196,11 @@ func (s *PreflightService) externalItems(ctx context.Context, orgID, projectID,
184196
Description: desc,
185197
}}, nil
186198
}
199+
if s.catalog != nil && s.catalog.HasOrgEnvCells(orgID, d.Name) {
200+
// Org cells already hold values; project Ready is irrelevant until
201+
// POST /build authors the instance from those cells.
202+
return nil, nil
203+
}
187204
ready, err := s.status.Ready(ctx, orgID, projectID, d.Name)
188205
if err != nil {
189206
return nil, err

services/aep-api/internal/delivery/build/preflight_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,54 @@ func TestPreflight_ExternalResolved_FallsThroughToConfigItem(t *testing.T) {
303303
require.Len(t, pf.Items, 1)
304304
require.Equal(t, "external-config", pf.Items[0].Kind)
305305
}
306+
307+
// fakeCatalog reports names that already hold org env cells (Registered
308+
// External). Missing / false is Project External — same fail-open as a nil
309+
// Catalog port.
310+
type fakeCatalog map[string]bool
311+
312+
func (f fakeCatalog) HasOrgEnvCells(_, name string) bool { return f[name] }
313+
314+
// A Registered External already holds values on the org plane. Preflight must
315+
// not emit external-config (the Build drawer would force typing secrets that
316+
// POST /build ignores). Project External still collects as today.
317+
func TestPreflight_RegisteredExternal_DoesNotEmitConfigItem(t *testing.T) {
318+
comps := []spec.DesignComponent{{Name: "board", ComponentType: spec.ComponentTypeService,
319+
Dependencies: []spec.Dependency{
320+
{Kind: spec.DependencyKindExternal, Name: "openweathermap",
321+
Config: []spec.ConfigKey{{Key: "api_key", Secret: true}}},
322+
{Kind: spec.DependencyKindExternal, Name: "travel-board-fx",
323+
Config: []spec.ConfigKey{{Key: "api_key", Secret: true}}},
324+
}}}
325+
svc := NewPreflightService(PreflightDeps{
326+
Design: fakeDesign{comps: comps},
327+
Status: fakeStatus{},
328+
Catalog: fakeCatalog{"openweathermap": true},
329+
})
330+
pf, err := svc.Preflight(context.Background(), "acme", "travel-board")
331+
require.NoError(t, err)
332+
kinds := kindsByDep(pf.Items)
333+
_, weather := kinds["openweathermap"]
334+
require.False(t, weather, "Registered name must not raise external-config, got %v", kinds["openweathermap"])
335+
require.Equal(t, []string{"external-config"}, kinds["travel-board-fx"])
336+
require.True(t, pf.NeedsInput)
337+
}
338+
339+
// An unresolved Registered name still raises the blocker — catalog cells do
340+
// not skip dependency resolution.
341+
func TestPreflight_RegisteredExternal_UnresolvedStillEmitsBlocker(t *testing.T) {
342+
comps := []spec.DesignComponent{{Name: "board", ComponentType: spec.ComponentTypeService,
343+
Dependencies: []spec.Dependency{
344+
{Kind: spec.DependencyKindExternal, Name: "openweathermap",
345+
Status: spec.DependencyStatusUnresolved, Reason: spec.DependencyReasonNeedsInput},
346+
}}}
347+
svc := NewPreflightService(PreflightDeps{
348+
Design: fakeDesign{comps: comps},
349+
Status: fakeStatus{},
350+
Catalog: fakeCatalog{"openweathermap": true},
351+
})
352+
pf, err := svc.Preflight(context.Background(), "acme", "travel-board")
353+
require.NoError(t, err)
354+
require.Len(t, pf.Items, 1)
355+
require.Equal(t, "external-unresolved", pf.Items[0].Kind)
356+
}

0 commit comments

Comments
 (0)