You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a new boolean field policy.dependencies.require_pinned_constraint
to apm.policy.yml. When set to true, apm install (and apm audit)
flags any dependency whose constraint is unbounded — *, a bare >=X.Y.Z without an upper bound, or a bare branch ref — and routes
through the existing enforcement: warn | block | off setting.
Motivation
Governed installs rely on lockfiles to deliver reproducibility, but
the apm.yml manifest itself can express intent that defeats lockfile
discipline on the next apm update. A dep written as:
dependencies:
apm:
- acme-org/some-skills # no ref - tracks default branch
- other-org/lib@>=1.0 # no upper bound - any 99.x is valid
- third-org/pkg#* # any version
Will resolve to whatever exists today, but the next apm update may
pull a major-version jump or a moving branch tip. Enterprise audit
flows want to declare "we only ship deps that pin to a bounded
constraint." That is what this policy field expresses.
A dep's constraint is considered "unbounded" if any of the following
hold:
The ref is missing / empty (resolves to default branch).
The ref is a bare branch name (anything that does not parse as a
semver range and is not a SHA or literal tag).
The semver range is *, x, X, or a single-sided lower bound
without a paired upper bound (e.g. >=1.0.0 alone).
The constraint is a SHA-resolved branch alias under a name that
moves (we keep this case as a follow-up — a SHA is technically
pinned even if it came from a branch).
Conversely, the following count as pinned:
Exact semver (1.2.3).
Caret/tilde ranges (^1.2.3, ~1.2.3).
Bounded ranges (>=1.0.0 <2.0.0, 1.x).
Literal tag refs (v1.2.3).
Full SHA refs.
Local-path deps (source: local) — no version to pin.
The check runs in the policy preflight phase (alongside run_dependency_policy_checks) on the declared constraints, not
the resolved ones, so the policy is meaningful before the install
completes.
Examples
With require_pinned_constraint: true and enforcement: block:
# apm.ymldependencies:
apm:
- acme-org/skills # FAIL: no ref
- other/lib@>=1.0 # FAIL: unbounded upper
- third/lib#^1.2.0 # OK: caret range
- fourth/lib@v1.5.3 # OK: literal tag
Output:
[x] Policy violation: 2 dependencies use unbounded constraints.
- acme-org/skills (no ref; resolves to default branch)
- other/lib@>=1.0 (unbounded upper; pair with '<2.0' or use '^1.0')
Hint: pin to a semver range, literal tag, or SHA.
Enforcement: block. Install aborted.
With enforcement: warn, the same lines render with [!] and install
proceeds.
Considerations
Default false — no behavior change for existing users.
Does not collide with policy.dependencies.require (which is an
allowlist of required dependency names). The new field is a
property check, not an allowlist.
Composes with enforcement the same way every other dependency
policy check does — no new enforcement axis.
Transitive deps. The check runs on direct deps only by design —
the consumer cannot rewrite a transitive dep's constraint. (We could
extend transitively in a follow-up, but the value is at the
authoring surface.)
Out of scope
A separate allowlist of "approved unbounded patterns" (e.g. allow main on internal repos). If demand surfaces, follow up with require_pinned_constraint: {except: [...]}.
Summary
Add a new boolean field
policy.dependencies.require_pinned_constraintto
apm.policy.yml. When set totrue,apm install(andapm audit)flags any dependency whose constraint is unbounded —
*, a bare>=X.Y.Zwithout an upper bound, or a bare branch ref — and routesthrough the existing
enforcement: warn | block | offsetting.Motivation
Governed installs rely on lockfiles to deliver reproducibility, but
the apm.yml manifest itself can express intent that defeats lockfile
discipline on the next
apm update. A dep written as:Will resolve to whatever exists today, but the next
apm updatemaypull a major-version jump or a moving branch tip. Enterprise audit
flows want to declare "we only ship deps that pin to a bounded
constraint." That is what this policy field expresses.
Proposal
Add a single additive field on
DependencyPolicy:YAML:
A dep's constraint is considered "unbounded" if any of the following
hold:
semver range and is not a SHA or literal tag).
*,x,X, or a single-sided lower boundwithout a paired upper bound (e.g.
>=1.0.0alone).moves (we keep this case as a follow-up — a SHA is technically
pinned even if it came from a branch).
Conversely, the following count as pinned:
1.2.3).^1.2.3,~1.2.3).>=1.0.0 <2.0.0,1.x).v1.2.3).source: local) — no version to pin.PR feat(registry): [FEATURE] Add package registry support for dependency… #1471 + feat(deps): resolve semver constraints on git-source dependencies against repo tags #1488).
The check runs in the policy preflight phase (alongside
run_dependency_policy_checks) on the declared constraints, notthe resolved ones, so the policy is meaningful before the install
completes.
Examples
With
require_pinned_constraint: trueandenforcement: block:Output:
With
enforcement: warn, the same lines render with[!]and installproceeds.
Considerations
false— no behavior change for existing users.policy.dependencies.require(which is anallowlist of required dependency names). The new field is a
property check, not an allowlist.
enforcementthe same way every other dependencypolicy check does — no new enforcement axis.
may carry semver ranges. The unbounded-range check applies
uniformly across all three sources (git, marketplace, registry).
the consumer cannot rewrite a transitive dep's constraint. (We could
extend transitively in a follow-up, but the value is at the
authoring surface.)
Out of scope
mainon internal repos). If demand surfaces, follow up withrequire_pinned_constraint: {except: [...]}.via
resolved_hashin the lockfile, per feat(registry): [FEATURE] Add package registry support for dependency… #1471).require_resolutionsemantics.References
RegistrySourcePolicyalongsideDependencyPolicyin
src/apm_cli/policy/schema.py.policy_gate,policy_target_check,run_policy_checks,run_policy_preflight.npm config set save-exact true, pip's--require-hashes,cargoworkspaces' resolver lockstep.Related
applies uniformly across all three resolution sources; feat(deps): resolve semver constraints on git-source dependencies against repo tags #1488 brings
git into that set.
custom_checksframework forapm.policy.yml(pluggablesubprocess validators). This issue is intentionally a built-in
field, not a custom_checks entry — same precedent as the existing
allow/deny/requirefields. Rationale: enterprise-defaultgovernance concern, deterministic constraint-string analysis, no
subprocess required. Enterprises can stack both (built-in pinning
check + custom subprocess validators) once feat(policy): add custom_checks for arbitrary subprocess validation in apm-policy.yml #519 lands.
marketplace doctorwarns when package dependencies bypassmarketplace. Related supply-chain audit, different axis (governance
boundary crossing vs. constraint pinning). Coexist cleanly.