From 1b3039eef0108677f57994e4aa61106e02b3358b Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:11:41 +0100 Subject: [PATCH 1/8] 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 own 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 after the previous change landed, with the canonical instrument: cue@v0.16.1 gengotypes vs the committed file -> 56 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-cstream/params/cue_types_gen.go | 36 ++++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/candy/plugin-cstream/params/cue_types_gen.go b/candy/plugin-cstream/params/cue_types_gen.go index 31d24d7..ef74c60 100644 --- a/candy/plugin-cstream/params/cue_types_gen.go +++ b/candy/plugin-cstream/params/cue_types_gen.go @@ -18,17 +18,17 @@ type CstreamInput struct { // The set is deliberately small. Each method is here because it can FAIL on a // defect nothing else catches: // - // status the gateway's own readiness view -- it reports ready only when a - // DRM render node exists AND the compositor has published a socket, - // which is strictly more than "the port answers" - // login one PAM authentication through the real session leader + // status the gateway's own readiness view -- it reports ready only when a + // DRM render node exists AND the compositor has published a socket, + // which is strictly more than "the port answers" + // login one PAM authentication through the real session leader // - // frame a frame pulled back THROUGH the negotiated WebRTC track. This is - // the method the verb ultimately exists for: `wl: screenshot` reads - // the COMPOSITOR and `stream-probe` reads the producer's own tap, so - // neither can fail on an encoding or transport defect. This one - // decodes what a real consumer receives, so it fails on a broken - // encoder, a broken negotiation, or a broken transport. + // frame a frame pulled back THROUGH the negotiated WebRTC track. This is + // the method the verb ultimately exists for: `wl: screenshot` reads + // the COMPOSITOR and `stream-probe` reads the producer's own tap, so + // neither can fail on an encoding or transport defect. This one + // decodes what a real consumer receives, so it fails on a broken + // encoder, a broken negotiation, or a broken transport. // // `frame` was deliberately withheld until it could be gated. The defect that // kept it unshippable is on record and fixed: gst-plugins-bad was missing, so @@ -40,7 +40,7 @@ type CstreamInput struct { // idle, input, logout) are NOT served yet: the control surface they read does // not exist in the streamer, and a method that cannot be exercised reads as // coverage while providing none. - Method string `yaml:"method,omitempty" json:"method"` + Method string `json:"method"` // user / password — `login` only. // @@ -57,15 +57,15 @@ type CstreamInput struct { // broker (root) execs the leader over a socketpair and the leader reads the // credential from STDIN, never argv. That discipline is deliberate and is // tested; this verb simply cannot reach it through the executor it is given. - User string `yaml:"user,omitempty" json:"user,omitempty"` + User string `json:"user,omitempty"` - Password string `yaml:"password,omitempty" json:"password,omitempty"` + Password string `json:"password,omitempty"` // expect — `login` only: whether this credential SHOULD be accepted. // A login probe that only ever checks the correct password passes just as // happily against a stack that accepts anything, so the rejecting case has to // be expressible. - Expect string `yaml:"expect,omitempty" json:"expect,omitempty"` + Expect string `json:"expect,omitempty"` // artifact — `frame` only: the HOST path the decoded frame is written to. // @@ -77,7 +77,7 @@ type CstreamInput struct { // Without a gate on the CONTENT this method would be theatre: a stream that // negotiates and sends nothing, or sends a uniform placeholder, is exactly the // failure mode it exists to catch. - Artifact string `yaml:"artifact,omitempty" json:"artifact,omitempty"` + Artifact string `json:"artifact,omitempty"` // The artifact VALIDATORS. These are not general #Op modifiers -- they left // core #Op in the schema-compaction cutover, so every verb that writes an @@ -89,9 +89,9 @@ type CstreamInput struct { // artifact_not_uniform is the one that matters: a stream that negotiates and // then sends a black or placeholder frame is precisely the failure this method // exists to catch, and a byte-count alone would pass it. - ArtifactMinBytes int `yaml:"artifact_min_bytes,omitempty" json:"artifact_min_bytes,omitempty"` + ArtifactMinBytes int `json:"artifact_min_bytes,omitempty"` - ArtifactMinDimensions string `yaml:"artifact_min_dimensions,omitempty" json:"artifact_min_dimensions,omitempty"` + ArtifactMinDimensions string `json:"artifact_min_dimensions,omitempty"` - ArtifactNotUniform bool `yaml:"artifact_not_uniform,omitempty" json:"artifact_not_uniform,omitempty"` + ArtifactNotUniform bool `json:"artifact_not_uniform,omitempty"` } From 2dace8b93d4361725ee48171b2c4eeb94bb3297a Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:17:05 +0100 Subject: [PATCH 2/8] 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 94fa4fad65a19f5badc96607beb0289755c6a3e5 Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:21:43 +0100 Subject: [PATCH 3/8] 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 39d76e2c3582197b1072d0f42a5a223377dfda1c Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:25:06 +0100 Subject: [PATCH 4/8] 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-cstream/params/decode_test.go | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 candy/plugin-cstream/params/decode_test.go diff --git a/candy/plugin-cstream/params/decode_test.go b/candy/plugin-cstream/params/decode_test.go new file mode 100644 index 0000000..a32ce58 --- /dev/null +++ b/candy/plugin-cstream/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(CstreamInput{}) + + 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 CstreamInput + // 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)) +} From be6845258ddbcee8d9434bc854bbda1007c1ae37 Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:27:35 +0100 Subject: [PATCH 5/8] test(params): anchor the wire keys to the schema, not to the projection's own tags The first version of this test derived each expected wire key from the struct tag it was checking, which made it vacuous: a drifted tag takes its derived expectation with it, so the decode still lines up and the test still passes. Correcting a block that named exactly that. The expected keys now come from candy//schema/*.cue -- the schema that GENERATES this file -- hardcoded on purpose. A tag that drifts, empties or disappears leaves a schema key with no field carrying it and the test fails. Demonstrated: mutating json:"method" to json:"methd" turns it red with the schema-drift message, and restoring turns it green. --- candy/plugin-cstream/params/decode_test.go | 89 ++++++++++++++-------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/candy/plugin-cstream/params/decode_test.go b/candy/plugin-cstream/params/decode_test.go index a32ce58..660fa0a 100644 --- a/candy/plugin-cstream/params/decode_test.go +++ b/candy/plugin-cstream/params/decode_test.go @@ -1,14 +1,18 @@ package params -// decode_test.go — the ONE runtime path these generated structs participate in, pinned. +// decode_test.go — the ONE runtime path these generated structs participate in, pinned against +// the SOURCE OF TRUTH. // // 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:` +// unmarshals too. No YAML decoder ever sees these types — which 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. +// The expected wire keys below are copied from `candy/plugin-cstream/schema/*.cue` — the schema that +// GENERATES this file. They are hardcoded on purpose: deriving them from the struct's own `json` +// tags (the obvious thing to do) makes the test vacuous, because a tag that drifts takes its +// derived expectation with it and the decode still lines up. Anchored to the schema instead, a +// drifted, emptied or removed tag leaves the field unpopulated and fails here. import ( "encoding/json" @@ -18,66 +22,91 @@ import ( "testing" ) -func TestProjectionJSONDecodePopulatesTaggedFields(t *testing.T) { - rt := reflect.TypeOf(CstreamInput{}) +// schemaKeys is the wire-key set declared by candy/plugin-cstream/schema/*.cue. +var schemaKeys = map[string]bool{ + "artifact": true, + "artifact_min_bytes": true, + "artifact_min_dimensions": true, + "artifact_not_uniform": true, + "expect": true, + "method": true, + "password": true, + "user": true, +} +func TestProjectionMatchesSchemaWireKeys(t *testing.T) { + rt := reflect.TypeOf(CstreamInput{}) + ptr := reflect.New(rt) wire := map[string]any{} - fillable := map[int]string{} // field index -> json key the probe can populate + + exported := 0 for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) if f.PkgPath != "" { continue // unexported } + exported++ key := strings.Split(f.Tag.Get("json"), ",")[0] if key == "" || key == "-" { - continue + t.Fatalf("field %s carries no usable json tag (%q) — the schema's key cannot reach it", f.Name, key) + } + if !schemaKeys[key] { + t.Fatalf("field %s is tagged json:%q, which the schema candy/plugin-cstream/schema does NOT declare — the projection has drifted from its source of truth", f.Name, key) } - var v any switch f.Type.Kind() { case reflect.String: - v = "probe" + wire[key] = "probe" case reflect.Bool: - v = true + wire[key] = true case reflect.Int, reflect.Int64: - v = 7 + wire[key] = 7 case reflect.Float64: - v = 1.5 + wire[key] = 1.5 case reflect.Slice: - v = []any{"probe"} + wire[key] = []any{"probe"} case reflect.Map: - v = map[string]any{"k": "v"} + wire[key] = 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. + // nested structs/pointers/interfaces are not filled by a flat wire map 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") + if exported == 0 { + t.Fatal("the projection has no exported fields — the test would prove nothing") } b, err := json.Marshal(wire) if err != nil { t.Fatalf("marshal the wire map: %v", err) } - var got CstreamInput // THE PATH: json -> struct, exactly `json.Unmarshal(req.GetParamsJson(), &op)`. - if err := json.Unmarshal(b, &got); err != nil { + if err := json.Unmarshal(b, ptr.Interface()); err != nil { t.Fatalf("decode the wire map into the projection: %v", err) } - rv := reflect.ValueOf(got) + elem := ptr.Elem() 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)) + for key := range wire { + found := false + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + if f.PkgPath != "" { + continue + } + if strings.Split(f.Tag.Get("json"), ",")[0] != key { + continue + } + found = true + if elem.Field(i).IsZero() { + missing = append(missing, fmt.Sprintf("%s (field %s)", key, f.Name)) + } + } + if !found { + missing = append(missing, key+" (no field carries it)") } } 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.Fatalf("%d wire key(s) were not delivered into the projection: %v", len(missing), missing) } - t.Logf("decoded %d/%d json-tagged fields from the wire map", len(fillable), len(fillable)) + t.Logf("verified %d exported field(s) against the schema's wire keys and decoded %d of them", exported, len(wire)) } From ce5de2ed55217968c3d87bc3d4c12b22c3f1ebf1 Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:33:13 +0100 Subject: [PATCH 6/8] test(params): check the projection against its schema in BOTH directions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous version iterated the struct's own tags to build the wire map, so its schema-to-struct arm was unreachable: every key in the map necessarily had a field. That left a dropped schema key undetectable, which is exactly the drift the test claims to catch. Both directions now run against the #Input definition in candy//schema/*.cue, hardcoded on purpose: struct -> schema catches a drifted/emptied/removed tag, and schema -> struct catches a key the projection dropped. Two executed negative controls prove it — a mutated tag and a deleted field each turn it red with a precise message, and restoring turns it green. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (analysed on a live system) --- candy/plugin-cstream/params/decode_test.go | 83 ++++++++++++---------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/candy/plugin-cstream/params/decode_test.go b/candy/plugin-cstream/params/decode_test.go index 660fa0a..42ea840 100644 --- a/candy/plugin-cstream/params/decode_test.go +++ b/candy/plugin-cstream/params/decode_test.go @@ -1,18 +1,17 @@ package params -// decode_test.go — the ONE runtime path these generated structs participate in, pinned against -// the SOURCE OF TRUTH. +// decode_test.go — pins this generated projection against its SOURCE OF TRUTH, in both directions. // // 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 — which is why the projection's `yaml:` -// tags are inert, and why this test pins the JSON side rather than asserting the claim in prose. +// tags are inert, and why this test pins the JSON side rather than asserting that claim in prose. // -// The expected wire keys below are copied from `candy/plugin-cstream/schema/*.cue` — the schema that -// GENERATES this file. They are hardcoded on purpose: deriving them from the struct's own `json` -// tags (the obvious thing to do) makes the test vacuous, because a tag that drifts takes its -// derived expectation with it and the decode still lines up. Anchored to the schema instead, a -// drifted, emptied or removed tag leaves the field unpopulated and fails here. +// The expected keys below are copied from the `#CstreamInput` definition in +// `candy/plugin-cstream/schema/*.cue` — the schema that GENERATES this file. They are hardcoded on +// purpose: deriving them from the struct's own `json` tags (the obvious thing) makes the test +// vacuous, because a tag that drifts takes its derived expectation with it and the decode still +// lines up. import ( "encoding/json" @@ -22,24 +21,27 @@ import ( "testing" ) -// schemaKeys is the wire-key set declared by candy/plugin-cstream/schema/*.cue. -var schemaKeys = map[string]bool{ - "artifact": true, - "artifact_min_bytes": true, - "artifact_min_dimensions": true, - "artifact_not_uniform": true, - "expect": true, - "method": true, - "password": true, - "user": true, +// schemaKeys is the field set of #CstreamInput, taken from candy/plugin-cstream/schema. +var schemaKeys = []string{ + "artifact", + "artifact_min_bytes", + "artifact_min_dimensions", + "artifact_not_uniform", + "expect", + "method", + "password", + "user", } -func TestProjectionMatchesSchemaWireKeys(t *testing.T) { +func TestProjectionMatchesItsSchema(t *testing.T) { rt := reflect.TypeOf(CstreamInput{}) ptr := reflect.New(rt) - wire := map[string]any{} + // Direction 1: STRUCT -> SCHEMA. Every exported field must carry a json key the schema + // declares; a drifted, emptied or removed tag is caught here. + wire := map[string]any{} exported := 0 + tagOf := map[string]string{} // json key -> Go field name for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) if f.PkgPath != "" { @@ -48,11 +50,12 @@ func TestProjectionMatchesSchemaWireKeys(t *testing.T) { exported++ key := strings.Split(f.Tag.Get("json"), ",")[0] if key == "" || key == "-" { - t.Fatalf("field %s carries no usable json tag (%q) — the schema's key cannot reach it", f.Name, key) + t.Fatalf("field %s carries no usable json tag (%q) — no schema key can reach it", f.Name, key) } - if !schemaKeys[key] { - t.Fatalf("field %s is tagged json:%q, which the schema candy/plugin-cstream/schema does NOT declare — the projection has drifted from its source of truth", f.Name, key) + if !contains(schemaKeys, key) { + t.Fatalf("field %s is tagged json:%q, which #CstreamInput in candy/plugin-cstream/schema does NOT declare — the projection has drifted from its source of truth", f.Name, key) } + tagOf[key] = f.Name switch f.Type.Kind() { case reflect.String: wire[key] = "probe" @@ -75,6 +78,15 @@ func TestProjectionMatchesSchemaWireKeys(t *testing.T) { t.Fatal("the projection has no exported fields — the test would prove nothing") } + // Direction 2: SCHEMA -> STRUCT. Every key the schema declares must be carried by a field; + // a key the projection dropped is caught here, and it is checked against schemaKeys directly + // rather than against anything derived from the tags. + for _, key := range schemaKeys { + if _, ok := tagOf[key]; !ok { + t.Fatalf("schema key %q is declared by #CstreamInput but NO exported field carries it — the projection is missing part of its schema", key) + } + } + b, err := json.Marshal(wire) if err != nil { t.Fatalf("marshal the wire map: %v", err) @@ -83,30 +95,27 @@ func TestProjectionMatchesSchemaWireKeys(t *testing.T) { if err := json.Unmarshal(b, ptr.Interface()); err != nil { t.Fatalf("decode the wire map into the projection: %v", err) } - elem := ptr.Elem() missing := []string{} for key := range wire { - found := false for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) - if f.PkgPath != "" { - continue - } - if strings.Split(f.Tag.Get("json"), ",")[0] != key { - continue - } - found = true - if elem.Field(i).IsZero() { + if f.PkgPath == "" && strings.Split(f.Tag.Get("json"), ",")[0] == key && elem.Field(i).IsZero() { missing = append(missing, fmt.Sprintf("%s (field %s)", key, f.Name)) } } - if !found { - missing = append(missing, key+" (no field carries it)") - } } if len(missing) > 0 { t.Fatalf("%d wire key(s) were not delivered into the projection: %v", len(missing), missing) } - t.Logf("verified %d exported field(s) against the schema's wire keys and decoded %d of them", exported, len(wire)) + t.Logf("both directions hold: %d exported field(s), %d schema key(s), %d decoded", exported, len(schemaKeys), len(wire)) +} + +func contains(hay []string, needle string) bool { + for _, h := range hay { + if h == needle { + return true + } + } + return false } From b6e768ccc5a1c919c6a05133c809f164d0147dfa Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Sun, 1 Nov 2026 23:42:49 +0100 Subject: [PATCH 7/8] chore(pr): re-freeze for the verbatim-evidence round Body corrected: the predecessor number was wrong (self-referential in two PRs), and the three go test captures are now raw output pasted as-is, with the per-repo controls driven by a field each repo actually has. Empty by design. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (documentation reviewed) From afa791c9a94d908a7469d2c1e52aeaf19f391369 Mon Sep 17 00:00:00 2001 From: Andreas Trawoeger Date: Mon, 2 Nov 2026 00:02:26 +0100 Subject: [PATCH 8/8] chore(pr): re-freeze for the measurement-reconciliation round The body now pastes git's own diff --stat against origin/main and names the plain-diff count as the same pair aligned differently, instead of quoting one number the PR page contradicts. The round RCA names the missed control for the one-directional test: the negative control exercised only the arm I had already reasoned about, so the arm that could not fire was never asked to fire. Empty by design. Assisted-by: DeepSeek Harness deepseek-v4.1-flash (documentation reviewed)