Skip to content

refactor: consolidate duplicated policy YAML struct hierarchies in navigator-policy #96

@johntmyers

Description

@johntmyers

Problem Statement

The sandbox policy has three separate Rust struct hierarchies representing the same YAML shape: Deserialize-only types in navigator-policy for parsing user-authored YAML, prost-generated types from sandbox.proto for wire/storage, and Serialize-only types in navigator-cli for ncl sandbox policy get --full output. These were written independently and have silently diverged — the input YAML uses filesystem_policy but the output YAML writes filesystem (matching the proto field name), breaking round-tripping. The output path also silently drops allowed_ips and the name field on network policy rules.

Technical Context

The proto layer in the middle is unavoidable (it serves as the wire/storage format), but the Input and Output struct hierarchies are mirrors of each other — one implements Deserialize, the other Serialize — with manual field-by-field conversion functions on each side. A single set of structs in navigator-policy that derive both Serialize and Deserialize would eliminate the duplication and make round-tripping correct by construction. The filesystem_policy naming also flows into the OPA/Rego layer (data.filesystem_policy in rego rules and proto_to_opa_data_json()), so the canonical YAML key needs to be preserved.

Affected Components

Component Key Files Role
navigator-policy crates/navigator-policy/src/lib.rs Input YAML structs (PolicyFile, FilesystemDef, etc.) and YAML→proto conversion
navigator-cli crates/navigator-cli/src/run.rs Output YAML structs (PolicyYaml, FilesystemYaml, etc.) and proto→YAML conversion
proto proto/sandbox.proto Wire format — field named filesystem (not filesystem_policy)
navigator-sandbox (OPA) crates/navigator-sandbox/src/opa.rs Manual proto→JSON with "filesystem_policy" key for Rego evaluation
Rego rules dev-sandbox-policy.rego References data.filesystem_policy

Proposed Approach

Consolidate the Input (Hierarchy A) and Output (Hierarchy C) structs into a single set of types in navigator-policy that derive both Serialize and Deserialize. Use #[serde(rename = "filesystem_policy")] (or keep the struct field named filesystem_policy) so the canonical YAML key is preserved. Move the proto→YAML conversion (policy_to_yaml) into navigator-policy alongside the existing YAML→proto conversion, so both directions live in one place. The CLI then just calls the shared serializer. The OPA JSON builder (proto_to_opa_data_json) is a separate concern and should not be changed in this work — it already uses the correct key name.

Scope Assessment

  • Complexity: Medium
  • Confidence: High — clear path, no design ambiguity
  • Estimated files to change: 3-4 (navigator-policy/src/lib.rs, navigator-cli/src/run.rs, Cargo.toml if needed, tests)
  • Issue type: refactor

Risks & Open Questions

  • allowed_ips and name field data loss: The output structs silently drop these. The consolidated types must include all fields from both the input and proto definitions. Verify no other fields are missing.
  • Stale checked-in generated proto code: crates/navigator-core/src/proto/navigator.sandbox.v1.rs is completely out of date (old schema with NetworkPolicy/NetworkMode that no longer exist). Not used at runtime but misleads developers. Consider deleting or regenerating as a follow-up.
  • InferencePolicy.api_patterns: The proto has this field but it has no YAML representation in either direction today. The consolidated types should include it for completeness, but it can be #[serde(default, skip_serializing_if)] for now.
  • BTreeMap vs HashMap: Output uses BTreeMap for deterministic ordering; input uses HashMap. The consolidated type should use BTreeMap for stable YAML output.
  • deny_unknown_fields: The input types use #[serde(deny_unknown_fields)]. This is strict and good for input validation but means adding new optional fields requires a version bump or feature flag. Keep this behavior.

Test Considerations

  • Round-trip test: parse dev-sandbox-policy.yaml → proto → YAML string → parse again → assert equality
  • Ensure allowed_ips and name fields survive the round-trip
  • Existing OPA tests in navigator-sandbox/src/opa.rs should continue passing (they use inline YAML with filesystem_policy key)
  • CLI policy get --full output should be parseable by parse_sandbox_policy()

Created by spike investigation. Use build-from-issue to plan and implement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cliCLI-related workarea:policyPolicy engine and policy lifecycle workspikestate:agent-readyApproved for agent implementationstate:pr-openedPR has been opened for this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions