code: func (g *Graph) AddNode(k *Key, props *starlark.Dict, idempotent bool, trace *builtins.CapturedStacktrace) error {
  	if g.finalized {
  		return ErrFinalized
  	}
  	if err := g.validateKey("key", k); err != nil {
  		return err
  	}
  
  	// Only string keys are allowed in 'props'.
  	for _, pk := range props.Keys() {
  		if _, ok := pk.(starlark.String); !ok {
  			return fmt.Errorf("non-string key %s in 'props'", pk)
  		}
  	}
  	propsStruct := starlarkstruct.FromKeywords(starlark.String("props"), props.Items())
  
  	n := g.initNode(k)
  	if !n.Declared() {
  		n.declare(g.nextIndex, propsStruct, idempotent, trace)
  		g.nextIndex++
  		return nil
  	}
  
  	// Only idempotent nodes can be redeclared, and only if all declarations
  	// are marked as idempotent and they specify exact same props.
  	if n.Idempotent && idempotent {
  		switch eq, err := starlark.Equal(propsStruct, n.Props); {
  		case err != nil:
  			return err
  		case eq:
  			return nil
  		}
  	}
  	return &NodeRedeclarationError{Trace: trace, Previous: n}
  }
docs: //// API to mutate the graph before it is finalized.
  // AddNode adds a node to the graph.
  //
  // If such node already exists, either returns an error right away (if
  // 'idempotent' is false), or verifies the existing node has also been marked
  // as idempotent and has exact same props dict as being passed here.
  //
  // Trying to use AddNode after the graph has been finalized is an error.
  //
  // Freezes props.values() as a side effect.