Skip to content

feat(graph): support multi-stamp execution graphs#250

Open
geoberle wants to merge 1 commit into
mainfrom
stamp-validation
Open

feat(graph): support multi-stamp execution graphs#250
geoberle wants to merge 1 commit into
mainfrom
stamp-validation

Conversation

@geoberle

@geoberle geoberle commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Until now, the graph tooling had no way to represent stamping: one graph could only hold one copy of each service.

This change introduces a ForStampedEntrypoint func that builds a graph that holds duplicated stamped services. This graph can be executed by templatize in a full parallel manner.

Additional: Making Stamped a *bool fixes a gap in user intent detection: with a plain bool, a child explicitly setting stamped: false was indistinguishable from "unset". PropagateStamped would silently override it to true, and the validator couldn't catch the contradiction. With pointer semantics, explicit false is preserved, and validation rejects it with a clear error — so invalid user intent doesn't go unnoticed.

⚠️ This contains breaking changes on the Graph struct so it is able to represent stamped ServiceGroups.
It is necessary to merged these PRs to adapt the the change

What changed:

  • Stamped on Service is now *bool — explicit false preserved, validated
  • Identifier, ResourceGroupKey, and StepKey carry an optional Stamp field to disambiguate per-stamp nodes/metadata
  • Graph.Steps and Graph.ResourceGroups use composite keys instead of nested maps
  • mergeStamped merges per-stamp graphs: deduplicates unstamped nodes, adds stamped copies with correct parent/child rewiring
  • ConfigResolver.GetRegionStampConfiguration re-resolves config with a different stamp value
  • ForStampedEntrypoint ties it together — builds one graph per stamp, merges them

Copilot AI review requested due to automatic review settings June 17, 2026 11:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the pipelines graph tooling to represent and execute multi-stamp topologies by duplicating stamped services per stamp while deduplicating unstamped nodes. It also changes Service.Stamped to *bool to preserve explicit user intent (stamped: false) and introduces per-stamp keying in graph identifiers/metadata.

Changes:

  • Changed topology.Service.Stamped from bool to *bool, updated stamping propagation, and added validation for explicit opt-outs under stamped parents.
  • Added stamp-aware graph identity (Identifier.Stamp) plus composite keys for steps/resource groups, and introduced ForStampedEntrypoint + mergeStamped to merge per-stamp graphs.
  • Added ConfigResolver.GetRegionStampConfiguration to re-resolve configuration templates using a different stamp value.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pipelines/topology/types.go Switches Stamped to pointer semantics and updates propagation/validation logic.
pipelines/topology/types_test.go Adds/updates tests for pointer-based stamped propagation and validation cases.
pipelines/graph/graph.go Introduces Stamp in identifiers and refactors graph lookups to use composite keys; adds ForStampedEntrypoint.
pipelines/graph/merge.go Adds per-stamp graph merge implementation to combine per-stamp graphs into one execution graph.
pipelines/graph/merge_test.go Adds tests for per-stamp merge behavior (node duplication, rewiring, and RG/step selection).
config/config.go Adds API to resolve config for a region using an alternate stamp and stores resolver inputs for re-resolution.
config/config_test.go Adds test coverage for stamp-specific config resolution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pipelines/topology/types.go
Comment thread pipelines/graph/merge.go Outdated
Comment thread pipelines/graph/merge.go Outdated
Copilot AI review requested due to automatic review settings June 17, 2026 13:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread pipelines/graph/graph.go
Comment thread pipelines/graph/graph.go
Comment thread pipelines/graph/graph.go
Copilot AI review requested due to automatic review settings June 17, 2026 16:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Comment thread pipelines/topology/types.go
Comment thread pipelines/graph/graph.go
Comment thread pipelines/graph/graph.go
Comment thread pipelines/graph/graph.go
Until now, the graph tooling had no way to represent stamping: one graph could only hold one copy of each service.

This change introduces a ForStampedEntrypoint func that builds a graph that holds duplicated stamped services. This graph can be executed by templatize in a full parallel manner.

Additional: Making Stamped a *bool fixes a gap in user intent detection: with a plain bool, a child explicitly setting stamped: false was indistinguishable from "unset". PropagateStamped would silently override it to true, and the validator couldn't catch the contradiction. With pointer semantics, explicit false is preserved, and validation rejects it with a clear error — so invalid user intent doesn't go unnoticed.

⚠️  This contains breaking changes on the Graph struct so it is able to represent stamped ServiceGroups. Don't merge this PR before a PR is ready in sdp-pipelines to adapt to the changes (available soon).

What changed:

- Stamped on Service is now *bool — explicit false preserved, validated
- Identifier, ResourceGroupKey, and StepKey carry an optional Stamp field to disambiguate per-stamp nodes/metadata
- Graph.Steps and Graph.ResourceGroups use composite keys instead of nested maps
- mergeStamped merges per-stamp graphs: deduplicates unstamped nodes, adds stamped copies with correct parent/child rewiring
- ConfigResolver.GetRegionStampConfiguration re-resolves config with a different stamp value
- ForStampedEntrypoint ties it together — builds one graph per stamp, merges them
Comment thread pipelines/graph/graph.go

// GetStep returns the step for a node, using the node's Stamp field to select per-stamp steps.
func (c *Graph) GetStep(node Identifier) (types.Step, bool) {
s, exists := c.Steps[StepKey{Stamp: node.Stamp, ServiceGroup: node.ServiceGroup, ResourceGroup: node.ResourceGroup, Step: node.Step}]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to distinguish between StepKey and Identifier? Aren't they the same thing?

Comment thread config/config.go
return cfg, nil
}

func (cr *configResolver) GetRegionStampConfiguration(region, stamp string) (types.Configuration, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer to disallow setting the stamp during GetResolver() and defer it to the GetRegionConfiguration() call where it should be required?

Comment thread pipelines/graph/graph.go
return ForEntrypoints(topo, []*topology.Entrypoint{entrypoint}, pipelines)
}

// ForStampedEntrypoint generates a stamp-aware execution graph. It builds a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would you ever not use this? Why keep the previous ones around?

Comment thread pipelines/graph/graph.go

// GetResourceGroup returns the resource group metadata for a node, using the
// node's Stamp field to select per-stamp metadata.
func (c *Graph) GetResourceGroup(node Identifier) (*types.ResourceGroupMeta, bool) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably wouldn't do this - node Identifier is over-specified to select a resource group (fields are in there that the caller can pass but will be ignored). If you want to refactor the nested maps (I am not sure it is worth it, but feel free), you need strong types for these Get* calls

Comment thread pipelines/graph/merge.go
//
// The stamps map keys are stamp identifiers (e.g. "1", "2", "3"). Each value
// is a graph produced by ForEntrypoint for that stamp's configuration.
func mergeStamped(stampGraphs map[string]*Graph) *Graph {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow I feel like we should be able to insert the correct nodes as necessary from the get-go, as opposed to having to run this de-dupe and merge process

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants