Severity: High (policy is a security boundary; the client must not accept a policy the reference loader would refuse). Lint/serialize/loading defects are in item 25.
Loader accepts what the Rust reference loader (crates/openshell-policy, v0.0.116) refuses
rules: [{}] → L7Rule{Allow: &L7Allow{}} — an allow rule with no constraints. Upstream L7RuleDef{allow: L7AllowDef} is required and fails with "missing field allow". Whether the server treats an empty L7Allow as match-all could not be confirmed from the SDK; the client accepting a shape the reference loader refuses is the dangerous direction regardless.
binaries: [{harness: true}] → Path: ""; credential_binding: {} → Provider: ""; missing version → 0; an empty file → valid policy. pkg/policyyaml/types.go:10 says "version is required" — nothing enforces it.
port: 443, ports: [80] → only 80 (pkg/policyyaml/toproto.go:124-131) although types.go:51 documents them as mutually exclusive.
ToSDK can currently never fail: middlewaresToSDK (toproto.go:242) has a single return out, nil, so the err branch at toproto.go:47-50 is unreachable.
Fix
Post-decode required-field validation (or UnmarshalJSON on L7RuleDef that fails when allow is absent, mirroring serde's "missing field"); make port+ports an error; give ToSDK real error paths and use them.
Test quality (owned here)
pkg/policyyaml/coverage_test.go is padding by name and content (TestParse_InlineJSONError feeds a chan to hit a marshal branch; four nil-path tests). TestToSDK_FullEndpointFields asserts 7 of 25 endpoint fields; Path, WebsocketCredentialRewrite, RequestBodyCredentialRewrite, AllowUninspectedCredentials, PersistedQueries, GraphqlMaxBodyBytes, GraphqlPersistedQueries, SigningService, SigningRegion, StrictToolNames have no assertion anywhere. TestFromSDK_RoundTripStructure checks ~6 fields. 13 tests discard the Parse error (p, _ := Parse(...): toproto_test.go:63,83,106,129,153,179,201,228,249,268; serialize_test.go:84,108,136) so regressions surface as nil-deref panics. Nested unknown-field rejection (alow, deny_rulez, {any:[a], glob:b}, {any: 5}, duplicate keys) works but is untested; denyFromSDK is 0% covered, queryMapFromSDK 33%. pkg/api/v1alpha1/v1alpha1_test.go:85 covers only policy/policyFile mutual exclusion. Every test is a single case; no table tests.
Acceptance
- Golden fixture (the content-guard example, plan Appendix B) →
ToSDK DeepEqual against a hand-written full types.SandboxPolicy covering all 25 endpoint fields.
- Table tests: each rejected shape above → error naming the field; nested unknown keys → error.
- All
Parse errors in tests checked.
Generated by Claude Code
Severity: High (policy is a security boundary; the client must not accept a policy the reference loader would refuse). Lint/serialize/loading defects are in item 25.
Loader accepts what the Rust reference loader (
crates/openshell-policy, v0.0.116) refusesrules: [{}]→L7Rule{Allow: &L7Allow{}}— an allow rule with no constraints. UpstreamL7RuleDef{allow: L7AllowDef}is required and fails with "missing field allow". Whether the server treats an emptyL7Allowas match-all could not be confirmed from the SDK; the client accepting a shape the reference loader refuses is the dangerous direction regardless.binaries: [{harness: true}]→Path: "";credential_binding: {}→Provider: ""; missingversion→ 0; an empty file → valid policy.pkg/policyyaml/types.go:10says "version is required" — nothing enforces it.port: 443, ports: [80]→ only 80 (pkg/policyyaml/toproto.go:124-131) althoughtypes.go:51documents them as mutually exclusive.ToSDKcan currently never fail:middlewaresToSDK(toproto.go:242) has a singlereturn out, nil, so theerrbranch attoproto.go:47-50is unreachable.Fix
Post-decode required-field validation (or
UnmarshalJSONonL7RuleDefthat fails whenallowis absent, mirroring serde's "missing field"); makeport+portsan error; giveToSDKreal error paths and use them.Test quality (owned here)
pkg/policyyaml/coverage_test.gois padding by name and content (TestParse_InlineJSONErrorfeeds achanto hit a marshal branch; four nil-path tests).TestToSDK_FullEndpointFieldsasserts 7 of 25 endpoint fields;Path,WebsocketCredentialRewrite,RequestBodyCredentialRewrite,AllowUninspectedCredentials,PersistedQueries,GraphqlMaxBodyBytes,GraphqlPersistedQueries,SigningService,SigningRegion,StrictToolNameshave no assertion anywhere.TestFromSDK_RoundTripStructurechecks ~6 fields. 13 tests discard theParseerror (p, _ := Parse(...):toproto_test.go:63,83,106,129,153,179,201,228,249,268;serialize_test.go:84,108,136) so regressions surface as nil-deref panics. Nested unknown-field rejection (alow,deny_rulez,{any:[a], glob:b},{any: 5}, duplicate keys) works but is untested;denyFromSDKis 0% covered,queryMapFromSDK33%.pkg/api/v1alpha1/v1alpha1_test.go:85covers only policy/policyFile mutual exclusion. Every test is a single case; no table tests.Acceptance
ToSDKDeepEqual against a hand-written fulltypes.SandboxPolicycovering all 25 endpoint fields.Parseerrors in tests checked.Generated by Claude Code