From 08dee7285e6f627c9237eb160c11a32ce37bc784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Tue, 11 Aug 2026 18:20:56 +0200 Subject: [PATCH 1/3] Define policy restriction overrides --- registry-v2.md | 37 ++++++++++++++++++++++------ registry/policy.proto | 36 +++++++++++++++++++++------ security/threat-model/mitigations.md | 4 ++- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/registry-v2.md b/registry-v2.md index bfc325f..ebfc2e6 100644 --- a/registry-v2.md +++ b/registry-v2.md @@ -108,27 +108,50 @@ A policy carries a list of [`RepositoryPolicy`](/registry/policy.proto) entries, A matched entry has two parts, evaluated in this order for each candidate release `{repository, package, version}`: -1. **Overrides** (`overrides`) — the final say. An `OVERRIDE_ACTION_ALLOW` override whose `ref` matches the release permits it immediately and **bypasses the restriction**; an `OVERRIDE_ACTION_DENY` override blocks it. When several overrides match, the one with the most specific `requirement` wins (a `requirement`-bearing entry is more specific than a bare-`package` entry). -2. **Restriction** (`restriction`) — applied to every release in the repository, but **never** to a release permitted by an `ALLOW` override. A release is blocked if any limit fires. +1. **Overrides** (`overrides`) — package-scoped decisions and exceptions. A matching `OVERRIDE_ACTION_ALLOW` permits the release and bypasses every policy restriction; `OVERRIDE_ACTION_DENY` blocks it. When several ALLOW or DENY overrides match, the one with the most specific `requirement` wins (a `requirement`-bearing entry is more specific than a bare-package entry). If no final override decides the release, matching ADVISORY, RETIREMENT, and COOLDOWN overrides remove only their selected restriction. +2. **Restriction** (`restriction`) — applied to every release that was not permitted or blocked by a final override. A release is blocked if any remaining limit fires. -A `PackageRef` (used by `Override.ref`) matches a release when its `package` equals the release's package and, if `requirement` is set, the release's version satisfies that requirement. +A `PackageRef` matches a release when its `package` equals the release's package and, if `requirement` is set, the release's version satisfies that requirement using Hex version-requirement semantics. + +Policy editors should populate an ADVISORY override's `requirement` from the advisory's affected ranges when the override is created. Keeping that recorded scope prevents a later expansion of the same advisory from being accepted without another policy change. Each advisory range uses `and` between its bounds and separate ranges use `or`. `and` binds before `or`, and parentheses aren't valid in Hex version requirements. + +Each `Override` action has a fail-closed field contract: + +* `OVERRIDE_ACTION_ALLOW` and `OVERRIDE_ACTION_DENY` set neither selector. ALLOW bypasses all policy restrictions, while DENY blocks the release. +* `OVERRIDE_ACTION_ADVISORY` sets only `advisory_id`. The identifier matches the advisory's primary ID or any alias, case-insensitively. It removes only that advisory, so another advisory affecting the same release is still evaluated. +* `OVERRIDE_ACTION_RETIREMENT` sets only `retirement_reason`. It accepts the release only while its current retirement reason equals that value. A changed reason is evaluated as a new finding. Changing only the retirement message does not change the match. +* `OVERRIDE_ACTION_COOLDOWN` sets neither selector. It bypasses only the cooldown declared by this policy. A project's local cooldown still applies independently. + +Every override may carry a comment that clients surface as the policy's explanation. Comments must contain at most 500 Unicode code points and must be valid UTF-8 without Unicode control, format, line separator, or paragraph separator characters. Comments on a `VISIBILITY_PUBLIC` policy are public. + +Clients ignore an override if it is malformed, has an unknown action or retirement reason, has an invalid package requirement, or sets selector fields that its action does not permit. Ignoring invalid override data keeps the affected release subject to the policy restriction. Older clients continue applying field 3 ALLOW and DENY overrides. They decode newer actions as unknown enum values and ignore their unknown selector and comment fields, so advisory, retirement, and cooldown overrides can't make an older client fail open. #### Restriction limits * `advisory_min_severity` is set and the release's maximum advisory severity is greater than or equal to it. It is an `AdvisorySeverity` (imported from [`package.proto`](/registry/package.proto), `SEVERITY_NONE` … `SEVERITY_CRITICAL`). `SEVERITY_NONE` blocks any release that has any advisory at all. * `retirement_reasons` is non-empty and the release's `retired.reason` is one of the listed values. Each is a `RetirementReason` (imported from [`package.proto`](/registry/package.proto), `RETIRED_OTHER` … `RETIRED_RENAMED`). -* `cooldown` is set and non-zero and the release's `published_at` is more recent than `now - cooldown_duration`. The grammar matches the Hex cooldown configuration grammar: `"Nd"`, `"Nw"`, `"Nmo"`, or `"0"`; `"0"` (or unset) imposes no minimum age. If multiple active policies declare cooldowns, the effective cooldown is the strictest one. +* `cooldown` is set and non-zero and the release's `published_at` is more recent than `now - cooldown_duration`. The grammar matches the Hex cooldown configuration grammar: `"Nd"`, `"Nw"`, `"Nmo"`, or `"0"`; `"0"` (or unset) imposes no minimum age. ### Client behavior A conformant client: -1. **Reads policy references from multiple opt-in sources** (e.g. project file, environment variable, global config) and composes them by intersection: a release must pass every active policy. The active set is deduplicated on `(repository, name)`. -2. **Fetches and verifies each active policy** before resolution, using the configured public key for the repository. +1. **Reads one policy reference from its opt-in sources** (e.g. project file, environment variable, global config), using the client's documented configuration precedence. +2. **Fetches and verifies the active policy** before resolution, using the configured public key for the repository. 3. **Filters the candidate set at resolution time only.** Lockfile entries are trusted at install; filtering does not apply to versions already in the lockfile. 4. **Caches each policy independently** with last-known-good fall-back on fetch failure (network, 5xx, signature mismatch). The maximum staleness window should be at most 30 days, bounding the suppression window for a network adversary. -Across the active set, policies compose by intersection: a release survives only if every active policy permits it and no active policy's restriction blocks it. Cooldowns compose by strictest-wins — the effective cooldown is the longest duration across all active policies, and local cooldown configuration cannot lower it. +The active policy and local cooldown compose by strictest-wins. Local cooldown configuration can increase the effective cooldown but cannot lower a cooldown declared by the policy. + +### Auditing + +Clients expose three audit modes for locked dependencies: + +* The default audit reports all advisory and retirement findings without applying dependency policies. +* The policy-overrides audit starts with all advisory and retirement findings, then reports only findings that are not accepted by a matching ALLOW, ADVISORY, or RETIREMENT override. +* The policy audit reports only findings rejected by the active policy's advisory severity threshold, retirement reasons, and matching overrides. + +The two policy-aware audit modes require an active policy and fail when it cannot be loaded. Project advisory and retirement ignores are additive and are applied after policy evaluation. Policy-aware audit modes cover security advisories and release retirements; they do not audit cooldown restrictions or perform general lockfile validation. ## Links diff --git a/registry/policy.proto b/registry/policy.proto index 66c37b5..d45287e 100644 --- a/registry/policy.proto +++ b/registry/policy.proto @@ -41,10 +41,9 @@ message RepositoryPolicy { // override (those bypass all limits). optional Restriction restriction = 2; - // Per-package final say, evaluated against each release in this repository. - // An ALLOW override permits the release immediately and bypasses - // `restriction`; a DENY override blocks it. When multiple overrides match a - // release, the one with the most specific requirement wins. + // Per-package overrides. ALLOW and DENY provide a final decision for the + // matching release. ADVISORY, RETIREMENT, and COOLDOWN each bypass only the + // selected restriction. Invalid or unknown entries are ignored. repeated Override overrides = 3; } @@ -58,8 +57,8 @@ message Restriction { repeated RetirementReason retirement_reasons = 2 [packed=true]; // Minimum release age. Same duration grammar as the Hex cooldown config - // ("7d", "2w", "1mo", "0"). Unset or "0" = no minimum age. If multiple - // active policies declare cooldowns, the effective cooldown is the strictest. + // ("7d", "2w", "1mo", "0"). Unset or "0" = no minimum age. The active + // policy and local cooldown compose by strictest-wins. optional string cooldown = 3; } @@ -72,16 +71,37 @@ message PackageRef { } message Override { - // Whether this override permits or blocks the matching release. + // The effect of this override. The fields permitted for each action are + // described below. Unknown actions must be ignored. required OverrideAction action = 1; // The package (and optional requirement) the override applies to. required PackageRef ref = 2; + + // ADVISORY requires advisory_id and forbids retirement_reason. Advisory + // identifiers match primary advisory IDs and aliases without regard to + // case. RETIREMENT requires retirement_reason and forbids advisory_id. + // ALLOW, DENY, and COOLDOWN forbid both selector fields. + optional string advisory_id = 3; + optional RetirementReason retirement_reason = 4; + + // Optional UTF-8 explanation surfaced by clients for every override action. + // At most 500 Unicode code points; control, format, line separator, and + // paragraph separator characters are invalid. This is public when the + // policy visibility is VISIBILITY_PUBLIC. + optional string comment = 5; } enum OverrideAction { - // Permit the release and bypass `restriction`. + // Permit the release and bypass every policy restriction. OVERRIDE_ACTION_ALLOW = 0; // Block the release. OVERRIDE_ACTION_DENY = 1; + // Accept only the advisory selected by advisory_id. + OVERRIDE_ACTION_ADVISORY = 2; + // Accept only a retirement with the selected retirement_reason. + OVERRIDE_ACTION_RETIREMENT = 3; + // Bypass only the policy cooldown. Local cooldown configuration still + // applies independently. + OVERRIDE_ACTION_COOLDOWN = 4; } diff --git a/security/threat-model/mitigations.md b/security/threat-model/mitigations.md index 4af0b8a..9b1eae2 100644 --- a/security/threat-model/mitigations.md +++ b/security/threat-model/mitigations.md @@ -266,11 +266,13 @@ Client-side enforcement of organization-defined policies, currently available in | Cooldown rule | Implemented | Blocks newly published versions until they reach a minimum age; effective cooldown is the strictest of local config and the policy | | Advisory rule | Implemented | Blocks versions with security advisories at or above a severity threshold (or with any advisory) | | Retirement rule | Implemented | Blocks versions retired for the configured reasons | -| Package/version overrides | Implemented | Allow/deny exceptions; most specific match wins, an allow exempts the release from the restriction | +| Package/version overrides | Implemented | Package and version scoped allow, deny, advisory, retirement, and policy-cooldown overrides; comments are available on every type | +| Finding-scoped policy overrides | Implemented | Advisory and retirement overrides accept one current identifier or reason without accepting unrelated findings; cooldown overrides don't disable local cooldown configuration | | Lockfile exemption | Implemented | Versions already locked are exempt from filtering, so re-resolution keeps a locked-but-now-blocked entry instead of failing | | Policy visibility | Implemented | Public policies are fetchable anonymously; private policies require authentication to the owning organization | | Fail-closed enforcement | Implemented | Malformed config, fetch failures (without a cached copy), or 404/401 abort resolution rather than resolving unenforced | | `mix hex.policy show` / `why` | Implemented | Summarize the active policy and explain per-version why each is allowed or blocked | +| Policy-aware dependency audit | Implemented | Audits locked advisory and retirement findings against policy overrides and restriction thresholds | ## Ecosystem Health From 9b582304a8ca227c6f488041518db5b3aaab284d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Sat, 15 Aug 2026 15:05:18 +0200 Subject: [PATCH 2/3] Document unsupported override warnings --- registry-v2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry-v2.md b/registry-v2.md index ebfc2e6..a53928c 100644 --- a/registry-v2.md +++ b/registry-v2.md @@ -124,7 +124,7 @@ Each `Override` action has a fail-closed field contract: Every override may carry a comment that clients surface as the policy's explanation. Comments must contain at most 500 Unicode code points and must be valid UTF-8 without Unicode control, format, line separator, or paragraph separator characters. Comments on a `VISIBILITY_PUBLIC` policy are public. -Clients ignore an override if it is malformed, has an unknown action or retirement reason, has an invalid package requirement, or sets selector fields that its action does not permit. Ignoring invalid override data keeps the affected release subject to the policy restriction. Older clients continue applying field 3 ALLOW and DENY overrides. They decode newer actions as unknown enum values and ignore their unknown selector and comment fields, so advisory, retirement, and cooldown overrides can't make an older client fail open. +Clients ignore an override if it is malformed, has an unknown action or retirement reason, has an invalid package requirement, or sets selector fields that its action does not permit. Ignoring invalid override data keeps the affected release subject to the policy restriction. Clients should warn when a loaded policy contains unknown override actions. Older clients continue applying field 3 ALLOW and DENY overrides. They decode newer actions as unknown enum values and ignore their unknown selector and comment fields, so advisory, retirement, and cooldown overrides can't make an older client fail open. #### Restriction limits From ac7eacaf6a94e1dffb2fd3527644944bb3cc4705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Sat, 15 Aug 2026 15:30:40 +0200 Subject: [PATCH 3/3] Keep registry policy docs protocol-focused --- registry-v2.md | 55 +++++++++++++------------------------------ registry/policy.proto | 25 +++++++++----------- 2 files changed, 27 insertions(+), 53 deletions(-) diff --git a/registry-v2.md b/registry-v2.md index a53928c..638bb6d 100644 --- a/registry-v2.md +++ b/registry-v2.md @@ -81,11 +81,11 @@ The name for the official Hex.pm repository is "hexpm", it should not be used by ## Retiring Releases -Individual releases can be retired. A retired release can still be used like any normal release, the only difference is that the particular release will be marked in the UI for the repository and a notice can be displayed to client users that are using that version. +Individual releases can be retired. Retirement is advisory metadata and does not remove the release from the registry. -A release is retired if the `retired` field is set on `Release`. A `RetirementStatus` has a `RetirementReason` enum with the reason for retiring the release, clients need to support future additions to this enum, in the protobuf library that generates the files under `registry/` unknown enum values are decoded as their integer value. A, user set, message can be attached to the `RetirementStatus` clarifying the reason for retirement. +A release is retired if the `retired` field is set on `Release`. A `RetirementStatus` has a `RetirementReason` enum and may include a message that clarifies the reason. Clients MUST accept future additions to `RetirementReason`; the protobuf library that generates the files under `registry/` decodes unknown enum values as their integer value. -It is important that clients still allow users to use retired releases to avoid breaking repeatable builds. +Clients MUST NOT reject a release solely because it is retired. This preserves repeatable builds that already depend on the release. ## Dependency policies @@ -97,61 +97,38 @@ The resource lives at `/repos/REPO/policies/NAME`, served by the same backend as The `visibility` field controls who can fetch the resource: -* `VISIBILITY_PRIVATE` — served only to authenticated callers who can access the repository, the same auth pipeline as `/packages/NAME` on a private repository. -* `VISIBILITY_PUBLIC` — served to any caller, authenticated or not, so projects that are not members of the repository can opt in to the policy. +* `VISIBILITY_PRIVATE` is served only to authenticated callers who can access the repository, using the same authentication rules as `/packages/NAME` on a private repository. +* `VISIBILITY_PUBLIC` is served to any caller, authenticated or not. -The auth decision is made per-object by inspecting the payload's `visibility` field; the path and signing model are identical in both cases. If the payload cannot be decoded — signature mismatch, unknown enum value, missing required field — the edge must fail closed and require authentication. +The auth decision is made per-object by inspecting the payload's `visibility` field; the path and signing model are identical in both cases. If the payload cannot be decoded because of a signature mismatch, unknown enum value, or missing required field, the edge MUST fail closed and require authentication. ### Repository policies -A policy carries a list of [`RepositoryPolicy`](/registry/policy.proto) entries, one per repository it constrains — in practice `hexpm` (public packages) and the organization's own repository. Each candidate release is matched to the entry whose `repository` equals the release's repository. A release from a repository with no matching entry is unconstrained by the policy. +A policy carries a list of [`RepositoryPolicy`](/registry/policy.proto) entries, one per repository it constrains. Each candidate release is matched to the entry whose `repository` equals the release's repository. A release from a repository with no matching entry is unconstrained by the policy. A matched entry has two parts, evaluated in this order for each candidate release `{repository, package, version}`: -1. **Overrides** (`overrides`) — package-scoped decisions and exceptions. A matching `OVERRIDE_ACTION_ALLOW` permits the release and bypasses every policy restriction; `OVERRIDE_ACTION_DENY` blocks it. When several ALLOW or DENY overrides match, the one with the most specific `requirement` wins (a `requirement`-bearing entry is more specific than a bare-package entry). If no final override decides the release, matching ADVISORY, RETIREMENT, and COOLDOWN overrides remove only their selected restriction. -2. **Restriction** (`restriction`) — applied to every release that was not permitted or blocked by a final override. A release is blocked if any remaining limit fires. +1. **Overrides** (`overrides`) are package-scoped decisions and exceptions. A matching `OVERRIDE_ACTION_ALLOW` permits the release and bypasses every policy restriction; `OVERRIDE_ACTION_DENY` blocks it. When several ALLOW or DENY overrides match, the one with the most specific `requirement` wins (a `requirement`-bearing entry is more specific than a bare-package entry). If no final override decides the release, matching ADVISORY, RETIREMENT, and COOLDOWN overrides remove only their selected restriction. +2. **Restriction** (`restriction`) applies to every release that was not permitted or blocked by a final override. A release is blocked if any remaining limit fires. A `PackageRef` matches a release when its `package` equals the release's package and, if `requirement` is set, the release's version satisfies that requirement using Hex version-requirement semantics. -Policy editors should populate an ADVISORY override's `requirement` from the advisory's affected ranges when the override is created. Keeping that recorded scope prevents a later expansion of the same advisory from being accepted without another policy change. Each advisory range uses `and` between its bounds and separate ranges use `or`. `and` binds before `or`, and parentheses aren't valid in Hex version requirements. - Each `Override` action has a fail-closed field contract: -* `OVERRIDE_ACTION_ALLOW` and `OVERRIDE_ACTION_DENY` set neither selector. ALLOW bypasses all policy restrictions, while DENY blocks the release. -* `OVERRIDE_ACTION_ADVISORY` sets only `advisory_id`. The identifier matches the advisory's primary ID or any alias, case-insensitively. It removes only that advisory, so another advisory affecting the same release is still evaluated. -* `OVERRIDE_ACTION_RETIREMENT` sets only `retirement_reason`. It accepts the release only while its current retirement reason equals that value. A changed reason is evaluated as a new finding. Changing only the retirement message does not change the match. -* `OVERRIDE_ACTION_COOLDOWN` sets neither selector. It bypasses only the cooldown declared by this policy. A project's local cooldown still applies independently. +* `OVERRIDE_ACTION_ALLOW` and `OVERRIDE_ACTION_DENY` MUST set neither selector. ALLOW bypasses all policy restrictions, while DENY blocks the release. +* `OVERRIDE_ACTION_ADVISORY` MUST set `advisory_id` and MUST NOT set `retirement_reason`. The identifier matches the advisory's primary ID or any alias, case-insensitively. It removes only that advisory, so another advisory affecting the same release is still evaluated. +* `OVERRIDE_ACTION_RETIREMENT` MUST set `retirement_reason` and MUST NOT set `advisory_id`. It accepts the release only while its current retirement reason equals that value. If the retirement reason changes, the override no longer matches. The retirement message does not affect matching. +* `OVERRIDE_ACTION_COOLDOWN` MUST set neither selector. It bypasses only the cooldown declared by this policy. -Every override may carry a comment that clients surface as the policy's explanation. Comments must contain at most 500 Unicode code points and must be valid UTF-8 without Unicode control, format, line separator, or paragraph separator characters. Comments on a `VISIBILITY_PUBLIC` policy are public. +Every override may carry an optional comment. Comments MUST contain at most 500 Unicode code points and MUST be valid UTF-8 without Unicode control, format, line separator, or paragraph separator characters. Comments on a `VISIBILITY_PUBLIC` policy are public. -Clients ignore an override if it is malformed, has an unknown action or retirement reason, has an invalid package requirement, or sets selector fields that its action does not permit. Ignoring invalid override data keeps the affected release subject to the policy restriction. Clients should warn when a loaded policy contains unknown override actions. Older clients continue applying field 3 ALLOW and DENY overrides. They decode newer actions as unknown enum values and ignore their unknown selector and comment fields, so advisory, retirement, and cooldown overrides can't make an older client fail open. +Clients MUST ignore an override if it is malformed, has an unknown action or retirement reason, has an invalid package requirement, or sets selector fields that its action does not permit. An ignored override MUST NOT remove a restriction or otherwise accept the affected release. #### Restriction limits * `advisory_min_severity` is set and the release's maximum advisory severity is greater than or equal to it. It is an `AdvisorySeverity` (imported from [`package.proto`](/registry/package.proto), `SEVERITY_NONE` … `SEVERITY_CRITICAL`). `SEVERITY_NONE` blocks any release that has any advisory at all. * `retirement_reasons` is non-empty and the release's `retired.reason` is one of the listed values. Each is a `RetirementReason` (imported from [`package.proto`](/registry/package.proto), `RETIRED_OTHER` … `RETIRED_RENAMED`). -* `cooldown` is set and non-zero and the release's `published_at` is more recent than `now - cooldown_duration`. The grammar matches the Hex cooldown configuration grammar: `"Nd"`, `"Nw"`, `"Nmo"`, or `"0"`; `"0"` (or unset) imposes no minimum age. - -### Client behavior - -A conformant client: - -1. **Reads one policy reference from its opt-in sources** (e.g. project file, environment variable, global config), using the client's documented configuration precedence. -2. **Fetches and verifies the active policy** before resolution, using the configured public key for the repository. -3. **Filters the candidate set at resolution time only.** Lockfile entries are trusted at install; filtering does not apply to versions already in the lockfile. -4. **Caches each policy independently** with last-known-good fall-back on fetch failure (network, 5xx, signature mismatch). The maximum staleness window should be at most 30 days, bounding the suppression window for a network adversary. - -The active policy and local cooldown compose by strictest-wins. Local cooldown configuration can increase the effective cooldown but cannot lower a cooldown declared by the policy. - -### Auditing - -Clients expose three audit modes for locked dependencies: - -* The default audit reports all advisory and retirement findings without applying dependency policies. -* The policy-overrides audit starts with all advisory and retirement findings, then reports only findings that are not accepted by a matching ALLOW, ADVISORY, or RETIREMENT override. -* The policy audit reports only findings rejected by the active policy's advisory severity threshold, retirement reasons, and matching overrides. - -The two policy-aware audit modes require an active policy and fail when it cannot be loaded. Project advisory and retirement ignores are additive and are applied after policy evaluation. Policy-aware audit modes cover security advisories and release retirements; they do not audit cooldown restrictions or perform general lockfile validation. +* `cooldown` is set and non-zero and the release's `published_at` is more recent than `now - cooldown_duration`. The grammar is `"Nd"`, `"Nw"`, `"Nmo"`, or `"0"`; `"0"` (or unset) imposes no minimum age. ## Links diff --git a/registry/policy.proto b/registry/policy.proto index d45287e..024096c 100644 --- a/registry/policy.proto +++ b/registry/policy.proto @@ -10,13 +10,13 @@ message Policy { // (matches ^[a-z0-9][a-z0-9_\-\.]*[a-z0-9]$, length 3..64) required string name = 2; - // Optional, free-form description (admin-set, surfaced in CLI/UI) + // Optional, free-form description. optional string description = 3; // Whether the policy is publicly readable or restricted to org members. // Read at the edge to decide whether to enforce auth on the fetch. - // Adding new Visibility values is a breaking change — old clients will - // treat unknown values as PRIVATE per the fail-closed rule. + // Consumers that use this field to authorize access MUST treat unknown + // values as PRIVATE. required Visibility visibility = 4; // One entry per repository the policy constrains (in practice "hexpm" and @@ -43,7 +43,7 @@ message RepositoryPolicy { // Per-package overrides. ALLOW and DENY provide a final decision for the // matching release. ADVISORY, RETIREMENT, and COOLDOWN each bypass only the - // selected restriction. Invalid or unknown entries are ignored. + // selected restriction. Consumers MUST ignore invalid or unknown entries. repeated Override overrides = 3; } @@ -56,9 +56,8 @@ message Restriction { // this set. Empty = no retirement limit. repeated RetirementReason retirement_reasons = 2 [packed=true]; - // Minimum release age. Same duration grammar as the Hex cooldown config - // ("7d", "2w", "1mo", "0"). Unset or "0" = no minimum age. The active - // policy and local cooldown compose by strictest-wins. + // Minimum release age ("7d", "2w", "1mo", "0"). Unset or "0" = no + // minimum age. optional string cooldown = 3; } @@ -72,7 +71,7 @@ message PackageRef { message Override { // The effect of this override. The fields permitted for each action are - // described below. Unknown actions must be ignored. + // described below. Consumers MUST ignore unknown actions. required OverrideAction action = 1; // The package (and optional requirement) the override applies to. @@ -85,10 +84,9 @@ message Override { optional string advisory_id = 3; optional RetirementReason retirement_reason = 4; - // Optional UTF-8 explanation surfaced by clients for every override action. - // At most 500 Unicode code points; control, format, line separator, and - // paragraph separator characters are invalid. This is public when the - // policy visibility is VISIBILITY_PUBLIC. + // Optional UTF-8 explanation. At most 500 Unicode code points; control, + // format, line separator, and paragraph separator characters are invalid. + // This is public when the policy visibility is VISIBILITY_PUBLIC. optional string comment = 5; } @@ -101,7 +99,6 @@ enum OverrideAction { OVERRIDE_ACTION_ADVISORY = 2; // Accept only a retirement with the selected retirement_reason. OVERRIDE_ACTION_RETIREMENT = 3; - // Bypass only the policy cooldown. Local cooldown configuration still - // applies independently. + // Bypass only the policy cooldown. OVERRIDE_ACTION_COOLDOWN = 4; }