Let dependency cooldown respect an explicit skip list so first-party /
internal packages that must be installed the moment they are published
(e.g. to sanity-test a freshly released version) are not held back by the
cooldown window.
Per review, this is a per-control skip list — NOT a second definition of
"trusted package". There remains a single top-level `trusted_packages`
(which waives malware analysis); `dependency_cooldown.skip` waives ONLY
the cooldown wait, so a fast-tracked package is still malware-scanned.
Matching:
- a PURL without a version skips cooldown for all versions of the package
(package-level) — the metadata passes through unmodified;
- a PURL with a version skips cooldown for that version only — that
version is preserved during stripping while other recent versions are
still held.
- config: DependencyCooldownConfig.Skip + CooldownSkip()/CooldownSkipInfo.
- npm/pypi interceptors: bypass on package-level skip; thread per-version
exemptions into the cooldown stripper so pinned versions survive.
- docs + config template; unit tests for the matcher (package/version
level, precedence, mismatches) and the skip-vs-trusted independence.
Signed-off-by: dmdhrumilmistry <56185972+dmdhrumilmistry@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PMG was forcing Accept: application/vnd.pypi.simple.v1+json on all
Simple API requests regardless of client capability. Older pip versions
(< 22.3) that only understand text/html would reject the JSON response,
breaking installs entirely.
Now checks the client's original Accept header before applying cooldown.
If PEP 691 is not supported, the request passes through unchanged with
a warning log recommending pip upgrade.
2026-05-26 20:50:10 +05:30
Sahil BansalGitHubClaude Opus 4.6devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>Abhisek Dattadevin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat: emit cloud events for dependency cooldown and host observations (#237)
Wire cooldown blocks and proxy host observations through the cloud sync
pipeline so they appear as telemetry in Control Tower.
- Cooldown blocks emit PACKAGE_DECISION with COOLDOWN_BLOCKED action and
PmgDependencyCooldown context (publish date, cooldown days, days since
publish, days remaining)
- Proxy host observations emit HOST_OBSERVATION with PmgHostObservation
(hostname, method)
- Session summary now includes cooldown_blocked_count
- Updated buf API dependency for new proto schema
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* format file
* fix: add explicit eventlog mapping for EventTypeDependencyCooldown
Follow the existing pattern where every audit event type has an explicit
case in mapEventType and a corresponding constant in the eventlog package.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Apply suggestion from @devin-ai-integration[bot]
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
---------
Signed-off-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat: Show cooldown report for pinned version installs
When a user installs a package with an explicit version (e.g.
npm install foo@1.2.3) and that version falls within the dependency
cooldown window, the cooldown block is now recorded and shown in the
report. Previously, the report only appeared when ALL versions of a
package were in cooldown (remaining == 0), causing pinned version
installs to fail with a confusing "version not found" error from the
package manager instead of a clear cooldown explanation.
Introduces InterceptorContext to carry per-execution data (pinned
versions) from the CLI command through the interceptor layer, keeping
it separate from long-lived dependencies like the analyzer and cache.
* fix: Normalize PyPI pinned version keys for cooldown lookup
CLI-provided package names (e.g. Flask_Cors) don't match the
URL-parsed form (flask-cors). Normalize keys once at construction
time so cooldown lookups match correctly.
* fix: Handle dots in PyPI package name normalization per PEP 503
denormalizePyPIPackageName already documented [-_.] replacement but
only handled underscores. Now also replaces dots with hyphens so
names like zope.interface match the URL-parsed form zope-interface.
* refactor: Extract shared cooldown stats recording into helper
Deduplicate identical stats-recording blocks from npm_cooldown.go
and pypi_cooldown.go into recordCooldownStats in cooldown.go.
* fix: Distinguish explicit version pins from auto-resolved versions
PyPI parsers resolve all packages to concrete versions (even without
a user-specified constraint), so HasVersion() was always true. Add
IsExplicitVersion to PackageInstallTarget, set it only when the user
provided an explicit constraint. Use it in proxy_flow.go to avoid
false pinned-version cooldown reports.
* refactor: Extract shared cooldown helpers to package-level functions
* feat: Add PyPI cooldown handler with PEP 691 file parsing
* feat: Add PyPI cooldown file stripping logic
* feat: Implement PyPI cooldown HandleMetadataRequest with PEP 691 filtering
* feat: Wire PyPI cooldown into pypi_registry interceptor
* update headers for no cache
* fix: Strip conditional GET headers to prevent 304 bypass in cooldown handlers
pip and npm clients cache Simple API / registry responses with ETags. On
subsequent requests they send If-None-Match, which causes the server to
return 304 Not Modified with no body. The cooldown response modifier
received an empty body, failed to parse it, and failed-open — letting
the client use its stale cached (unfiltered) response.
Fix: delete If-None-Match and If-Modified-Since from the request before
forwarding, forcing a full 200 response so the modifier always has a
body to filter.
Also removes the Content-Type guard from the PyPI modifier (the empty
Content-Type on 304 responses was a symptom of the same root cause) and
replaces Cache-Control: no-cache with the more targeted header deletion.
* docs: Add PyPI cooldown limitation for pip < 22.3 to dependency-cooldown docs