Bug
plugin/external/convert.go:20-30 mapToStruct falls back to an empty *structpb.Struct{} when structpb.NewStruct(m) returns an error:
```go
func mapToStruct(m map[string]any) *structpb.Struct {
if m == nil {
return nil
}
s, err := structpb.NewStruct(m)
if err != nil {
return &structpb.Struct{} // silent drop — should propagate
}
return s
}
```
Result: providers whose ResourceOutput.Outputs or DiffResult.Changes carry types structpb cannot represent (chan, func, *time.Time without MarshalJSON, custom struct without map representation) silently drop those fields on the wire. Downstream consumers (state persistence, drift detection, cross-module substitution) see an empty Outputs map and treat it as "no changes" — masking real provider bugs.
Surfaced by
W-7 conformance suite — Scenario_DiffSurvivesGRPCRoundTrip (in iac/conformance/scenario_grpc_roundtrip.go) is intentionally stricter than this production wire so the underlying provider bug surfaces before the wire layer hides it. Reviewed in PR #535 by Copilot:
#535 (comment)
Fix sketch
Propagate the error rather than swallowing it:
```go
func mapToStruct(m map[string]any) (*structpb.Struct, error) {
if m == nil {
return nil, nil
}
s, err := structpb.NewStruct(m)
if err != nil {
return nil, fmt.Errorf("structpb.NewStruct: %w", err)
}
return s, nil
}
```
This is a signature-changing fix; the same shape exists in plugin/external/sdk/grpc_server.go:575. Both call sites need a coordinated bump. Estimate: small (5-10 file diff), but every caller needs to handle the error path. Not appropriate for W-7 scope; tracked here as the architecturally-correct counterpart to the conformance-suite strictness.
Bug
plugin/external/convert.go:20-30mapToStructfalls back to an empty*structpb.Struct{}whenstructpb.NewStruct(m)returns an error:```go
func mapToStruct(m map[string]any) *structpb.Struct {
if m == nil {
return nil
}
s, err := structpb.NewStruct(m)
if err != nil {
return &structpb.Struct{} // silent drop — should propagate
}
return s
}
```
Result: providers whose
ResourceOutput.OutputsorDiffResult.Changescarry typesstructpbcannot represent (chan, func,*time.Timewithout MarshalJSON, custom struct without map representation) silently drop those fields on the wire. Downstream consumers (state persistence, drift detection, cross-module substitution) see an empty Outputs map and treat it as "no changes" — masking real provider bugs.Surfaced by
W-7 conformance suite —
Scenario_DiffSurvivesGRPCRoundTrip(iniac/conformance/scenario_grpc_roundtrip.go) is intentionally stricter than this production wire so the underlying provider bug surfaces before the wire layer hides it. Reviewed in PR #535 by Copilot:#535 (comment)
Fix sketch
Propagate the error rather than swallowing it:
```go
func mapToStruct(m map[string]any) (*structpb.Struct, error) {
if m == nil {
return nil, nil
}
s, err := structpb.NewStruct(m)
if err != nil {
return nil, fmt.Errorf("structpb.NewStruct: %w", err)
}
return s, nil
}
```
This is a signature-changing fix; the same shape exists in
plugin/external/sdk/grpc_server.go:575. Both call sites need a coordinated bump. Estimate: small (5-10 file diff), but every caller needs to handle the error path. Not appropriate for W-7 scope; tracked here as the architecturally-correct counterpart to the conformance-suite strictness.