From effa9f066dbeb16d1b3df859e249e2623df8738e Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:12:01 +0100 Subject: [PATCH 1/4] fix(params): restore the projection to the canonical cue@v0.16.1 output The previous change regenerated this file with `charly candy params`, which is NOT the instrument this repo is graded on. The org's reusable candy-validate gate and this repo family's CI run plain `cue exp gengotypes` at cue@v0.16.1; the charly wrapper applies a yaml retag and reflows doc comments, so its output differs from the canonical one on the SAME schema. Measured with the canonical instrument against main after that change: cue@v0.16.1 gengotypes vs the committed file -> 10 diff lines (FAIL) Restoring the file to exactly what cue@v0.16.1 emits, so the committed projection matches the gate that grades it. Nothing else changes. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system) --- candy/plugin-media/params/cue_types_gen.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/candy/plugin-media/params/cue_types_gen.go b/candy/plugin-media/params/cue_types_gen.go index 967a76b..ac22a4f 100644 --- a/candy/plugin-media/params/cue_types_gen.go +++ b/candy/plugin-media/params/cue_types_gen.go @@ -32,23 +32,23 @@ type TranscodeInput struct { // capture session flushed). The bed-runner evidence phase threads the entry's // primary artifact here; a direct/plan-step dispatch may set it explicitly. // Empty → the provider falls back to the check env's source_artifact. - SourceArtifact string `yaml:"source_artifact,omitempty" json:"source_artifact,omitempty"` + SourceArtifact string `json:"source_artifact,omitempty"` // to — the CONTAINER FORMAT to transcode the source to. Single-purpose verb: // only "mp4" is supported (the default; an empty/omitted value means mp4). The // transcode is H.264 + yuv420p in an MP4 container, so the artifact plays // everywhere (the ffmpeg argv: -c:v libx264 -pix_fmt yuv420p). - To string `yaml:"to,omitempty" json:"to,omitempty"` + To string `json:"to,omitempty"` // artifact — the HOST path the transcoded file is written to. Empty (the // common case) → derived from the source path by swapping its extension to // ".mp4" (the source MJPEG the capture session flushed is already host-side). - Artifact string `yaml:"artifact,omitempty" json:"artifact,omitempty"` + Artifact string `json:"artifact,omitempty"` // artifact_min_bytes — the post-transcode artifact-reality assertion: the // output MP4 must be at least this many bytes (sdk.RunArtifactValidators, // shared implementation). - ArtifactMinBytes int `yaml:"artifact_min_bytes,omitempty" json:"artifact_min_bytes,omitempty"` + ArtifactMinBytes int `json:"artifact_min_bytes,omitempty"` // artifact_not_uniform — the SOURCE-frame motion assertion (the RDD-3 binding // of the nested-capture plan): evaluated on the SOURCE MJPEG frames (the @@ -57,5 +57,5 @@ type TranscodeInput struct { // source MJPEG stream into its constituent JPEGs, decodes a stream-wide // sample, and requires at least two distinct frame signatures: identical // frames FAIL honestly (a real capture shows motion), differing frames PASS. - ArtifactNotUniform bool `yaml:"artifact_not_uniform,omitempty" json:"artifact_not_uniform,omitempty"` + ArtifactNotUniform bool `json:"artifact_not_uniform,omitempty"` } From f44ae0051887956b37d28ad94674c7dcc49f069e Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:17:10 +0100 Subject: [PATCH 2/4] chore(pr): re-freeze so the validator reads the corrected body Body-only round: the decode-path proof (JSON never YAML), the filed issue, and the untrimmed module gate are now in the PR body. A REST re-run replays the original event payload and re-reads the stale body, so a new head is the way to make the gate read what GitHub serves. Empty by design. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (documentation reviewed) From ae91846187d2c70d17576696486f5fbe04137b2a Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:21:46 +0100 Subject: [PATCH 3/4] chore(pr): re-freeze for the executed-decode evidence round Body now carries executed proof instead of static reading: the same JSON decode run against BOTH projections of the schema, with identical results, plus literal grep output. A REST re-run replays the original payload, so a new head is what makes the gate read the served body. Empty by design. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (documentation reviewed) From 37dcdde9616c9c06e6d862525191c60d642b5c16 Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:25:06 +0100 Subject: [PATCH 4/4] test(params): pin the projection's JSON decode, the only path these structs travel The tag question is a runtime question, so it gets runtime coverage instead of a prose claim. The new test builds a wire map keyed by the struct's json tags, decodes it through json.Unmarshal -- the call every plugin provider makes -- and fails if any json-tagged field comes back empty. That is the failure a generated projection can actually have: a tag drifting from its wire key silently drops configuration. Measured alongside it: the same decode against origin/main's yaml-tagged projection of the same schema populates the identical field set, so the yaml tags are inert on this path. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system) --- candy/plugin-media/params/decode_test.go | 83 ++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 candy/plugin-media/params/decode_test.go diff --git a/candy/plugin-media/params/decode_test.go b/candy/plugin-media/params/decode_test.go new file mode 100644 index 0000000..8c87bf3 --- /dev/null +++ b/candy/plugin-media/params/decode_test.go @@ -0,0 +1,83 @@ +package params + +// decode_test.go — the ONE runtime path these generated structs participate in, pinned. +// +// charly hands a plugin its parameters as JSON (`spec.Op`'s params_json) and the plugin decodes +// that JSON into this projection; the sdk's kit.DecodeInput re-marshals to JSON before it +// unmarshals too. NO YAML decoder ever sees these types — that is why the projection's `yaml:` +// tags are inert, and why this test pins the JSON side rather than asserting the claim in prose. +// +// It fails if a json tag drifts away from the wire key: the field it belongs to would silently +// stop receiving its configured value, which is the failure mode a generated projection can have. + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "testing" +) + +func TestProjectionJSONDecodePopulatesTaggedFields(t *testing.T) { + rt := reflect.TypeOf(TranscodeInput{}) + + wire := map[string]any{} + fillable := map[int]string{} // field index -> json key the probe can populate + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + if f.PkgPath != "" { + continue // unexported + } + key := strings.Split(f.Tag.Get("json"), ",")[0] + if key == "" || key == "-" { + continue + } + var v any + switch f.Type.Kind() { + case reflect.String: + v = "probe" + case reflect.Bool: + v = true + case reflect.Int, reflect.Int64: + v = 7 + case reflect.Float64: + v = 1.5 + case reflect.Slice: + v = []any{"probe"} + case reflect.Map: + v = map[string]any{"k": "v"} + default: + // structs/pointers/interfaces are not filled by a flat wire map; skip them rather than + // assert something this probe cannot exercise. + continue + } + wire[key] = v + fillable[i] = key + } + if len(fillable) == 0 { + t.Fatal("no json-tagged field the probe can populate — the test would prove nothing") + } + + b, err := json.Marshal(wire) + if err != nil { + t.Fatalf("marshal the wire map: %v", err) + } + var got TranscodeInput + // THE PATH: json -> struct, exactly `json.Unmarshal(req.GetParamsJson(), &op)`. + if err := json.Unmarshal(b, &got); err != nil { + t.Fatalf("decode the wire map into the projection: %v", err) + } + + rv := reflect.ValueOf(got) + missing := []string{} + for i, key := range fillable { + if rv.Field(i).IsZero() { + missing = append(missing, fmt.Sprintf("%s (field %s)", key, rt.Field(i).Name)) + } + } + if len(missing) > 0 { + t.Fatalf("%d of %d json-tagged fields were NOT populated by the decode: %v", + len(missing), len(fillable), missing) + } + t.Logf("decoded %d/%d json-tagged fields from the wire map", len(fillable), len(fillable)) +}