diff --git a/.gds/bundle.lock.yaml b/.gds/bundle.lock.yaml index a8dcd8e..b1e86ce 100644 --- a/.gds/bundle.lock.yaml +++ b/.gds/bundle.lock.yaml @@ -5,14 +5,14 @@ bundle: version: "0.4.0-dev" release_sequence: 0 channel: "development" - source_tree_digest: "sha256:66532e92cee2ba8351911491ea4d8267fe853db10ebe36559551cb21aad5f209" - digest: "sha256:02032875c4b1e479d953ba4e9b766cf260bc469a81c38856ab6a6c6044b1c147" + source_tree_digest: "sha256:c7c04a600276303a2b4b63ccd2211fcb59eb94bfdcc6320e02eded9c0d2c2b10" + digest: "sha256:e753bacfdd5eedd658dbb63a82f9ce84a3b7efb2b14ae1fd55550fe040a003f5" projection: - input_digest: "sha256:02a7ff040caee7de0f5de1b2402485a7e6972b68e867413ad7a1d71add5d0336" - output_digest: "sha256:8fd7fc9e1adc79eec4876daaa1d9711882d3959584c92fc5c427c64ee6ecebce" + input_digest: "sha256:ba8b4e3da061c68235be603206bcb1042ec9091b4477da9bfbe302273042b4bb" + output_digest: "sha256:8efad1c40b82dda2d01a4ca37756b38bfae9f66cce4ac0f4808b2913d918278b" files: - path: ".gds/compiled-policy.json" digest: "sha256:78d09606bb4168d74bce1f50ab62b46a7ded34652c6b23af1badfd26dd060e94" - path: ".github/workflows/gds-ci.yml" - digest: "sha256:184bc5f09a65158b07fe4d6948c4d805869ddb35f9dcfaf528c5f5b4720abebb" + digest: "sha256:a2590f9aaec401c3e313fa639416e8a6b45ddfb96c3e3947a0b0b8c6a6e1dc57" diff --git a/.github/workflows/gds-ci.yml b/.github/workflows/gds-ci.yml index 5aafd3a..f3c09d7 100644 --- a/.github/workflows/gds-ci.yml +++ b/.github/workflows/gds-ci.yml @@ -1,8 +1,8 @@ # GENERATED FILE - DO NOT EDIT DIRECTLY # generator: gds # bundle: 0.4.0-dev -# source-tree-digest: sha256:66532e92cee2ba8351911491ea4d8267fe853db10ebe36559551cb21aad5f209 -# input-digest: sha256:02a7ff040caee7de0f5de1b2402485a7e6972b68e867413ad7a1d71add5d0336 +# source-tree-digest: sha256:c7c04a600276303a2b4b63ccd2211fcb59eb94bfdcc6320e02eded9c0d2c2b10 +# input-digest: sha256:ba8b4e3da061c68235be603206bcb1042ec9091b4477da9bfbe302273042b4bb # output-digest: sha256:01fb4854784be9e4564bcc84e70786484b370879be5e5ab1dd49f8b73ea2dea4 # edit-source: # - .gds/repository.yaml diff --git a/core/compiler/compiler.go b/core/compiler/compiler.go index 4c9f140..6b66839 100644 --- a/core/compiler/compiler.go +++ b/core/compiler/compiler.go @@ -22,12 +22,16 @@ var policyTierOrder = map[string]int{ } var monotonicStrength = map[string]map[string]int{ - "security.external_write_requires_approval": {"false": 0, "true": 1}, - "security.public_projection_scan": {"optional": 0, "required": 1}, - "context.private_parent_persistence": {"ephemeral-only": 0, "forbidden": 1}, - "agent.generated_projection_edit": {"warn": 0, "forbidden": 1}, - "security.secrets_in_repository": {"forbidden": 1}, - "package_management.npm_family_on_managed_path": {"forbidden": 1}, + "security.external_write_requires_approval": {"false": 0, "true": 1}, + "security.public_projection_scan": {"optional": 0, "required": 1}, + "context.private_parent_persistence": {"ephemeral-only": 0, "forbidden": 1}, + "agent.generated_projection_edit": {"warn": 0, "forbidden": 1}, + "security.secrets_in_repository": {"forbidden": 1}, + // "allowed" joined the vocabulary when the estate-wide ban on the npm + // family collided with fixed decision 13 -- every toolchain a project + // actually uses must work. The base may allow; a role like control-plane + // may only strengthen to forbidden, never the reverse. + "package_management.npm_family_on_managed_path": {"allowed": 0, "forbidden": 1}, "package_management.mutable_version_resolution": {"forbidden": 1}, "package_management.remote_stream_to_shell": {"forbidden": 1}, } diff --git a/core/compiler/compiler_test.go b/core/compiler/compiler_test.go index 7ae79dd..286a5eb 100644 --- a/core/compiler/compiler_test.go +++ b/core/compiler/compiler_test.go @@ -425,3 +425,22 @@ func testRepositoryRoot(t *testing.T) string { } return filepath.Clean(filepath.Join(filepath.Dir(currentFile), "..", "..")) } + +// TestNpmFamilyStrengthensButNeverWeakens covers the vocabulary "allowed" +// gained when the estate-wide npm-family ban collided with the owner's fixed +// decision that every toolchain a project actually uses must work. A role may +// strengthen the base's allowed to forbidden; the reverse is a weakening and +// must be refused exactly like every other monotonic field. +func TestNpmFamilyStrengthensButNeverWeakens(t *testing.T) { + t.Parallel() + path := "package_management.npm_family_on_managed_path" + if isWeakening(path, "allowed", "forbidden") { + t.Fatal("strengthening allowed -> forbidden was refused") + } + if !isWeakening(path, "forbidden", "allowed") { + t.Fatal("weakening forbidden -> allowed was permitted") + } + if !isWeakening(path, "allowed", "unheard-of") { + t.Fatal("an unknown value did not count as weakening") + } +} diff --git a/schemas/v1/policy.schema.json b/schemas/v1/policy.schema.json index a1a6de8..3441c38 100644 --- a/schemas/v1/policy.schema.json +++ b/schemas/v1/policy.schema.json @@ -422,7 +422,7 @@ "uniqueItems": true, "items": { "type": "string" } }, - "npm_family_on_managed_path": { "const": "forbidden" }, + "npm_family_on_managed_path": { "enum": ["allowed", "forbidden"] }, "mutable_version_resolution": { "const": "forbidden" }, "remote_stream_to_shell": { "const": "forbidden" } }