From cfaac63af6f76beebdc64a1ec228cedc42bb98ec Mon Sep 17 00:00:00 2001 From: RoyLin Date: Mon, 20 Jul 2026 12:15:34 +0800 Subject: [PATCH] feat(contract): type registry credential Secrets --- docs/deep-test-plan.md | 2 +- src/contract/process.rs | 2 ++ src/contract/unit.rs | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/deep-test-plan.md b/docs/deep-test-plan.md index da0d572..2c59418 100644 --- a/docs/deep-test-plan.md +++ b/docs/deep-test-plan.md @@ -198,7 +198,7 @@ schema review; snapshot acceptance is never automatic. | --- | --- | | `SPEC-PROCESS-*` | Entrypoint fallback, command/argument counts and sizes, working directory, environment names/values, and deterministic map order | | `SPEC-MOUNT-*` | Artifact, volume, and tmpfs sources; read-only behavior; zero/overflow size; duplicate names and targets; cross-kind collisions | -| `SPEC-SECRET-*` | Opaque references, environment/file targets, file modes, duplicate names/targets, collision with process environment and mounts, and non-disclosure | +| `SPEC-SECRET-*` | Opaque references, environment/file/registry-credential targets, file modes, duplicate names/targets, collision with process environment and mounts, and non-disclosure | | `SPEC-NET-*` | None/outbound/service modes, TCP/UDP, zero port, duplicate name/socket, 64-port boundary, and service-only publication | | `SPEC-HEALTH-*` | HTTP/TCP/command probes, named-port binding, status ranges, path validation, timing relationships, start period, and thresholds | | `SPEC-RESOURCE-*` | CPU, memory, PIDs, optional ephemeral storage, Task timeout, zero values, numeric maxima, and provider conversion overflow | diff --git a/src/contract/process.rs b/src/contract/process.rs index 34466e0..6e0057d 100644 --- a/src/contract/process.rs +++ b/src/contract/process.rs @@ -61,6 +61,7 @@ pub(crate) fn validate_environment_name(value: &str) -> Result<(), String> { pub enum SecretTarget { Environment { variable: String }, File { path: String, mode: u32 }, + RegistryCredential, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -86,6 +87,7 @@ impl SecretReference { } Ok(()) } + SecretTarget::RegistryCredential => Ok(()), } } } diff --git a/src/contract/unit.rs b/src/contract/unit.rs index b39bbb0..378020d 100644 --- a/src/contract/unit.rs +++ b/src/contract/unit.rs @@ -427,4 +427,26 @@ mod tests { }; assert!(service.validate().is_err()); } + + #[test] + fn registry_credential_is_a_typed_unique_secret_target() { + let mut service = service(); + service.secrets.push(SecretReference { + name: "registry".into(), + reference: "secret://registry/7".into(), + target: super::super::SecretTarget::RegistryCredential, + }); + service.validate().expect("registry credential"); + assert_eq!( + serde_json::to_value(&service.secrets[0].target).expect("target JSON"), + serde_json::json!({"kind": "registry_credential"}) + ); + + service.secrets.push(SecretReference { + name: "other-registry".into(), + reference: "secret://registry/8".into(), + target: super::super::SecretTarget::RegistryCredential, + }); + assert!(service.validate().is_err()); + } }