Conversation
…I-02, WI-03)
Refreshes the three release artefacts produced by the final
end-to-end reviewer after the Wave 3 commits landed on 2.2.0:
CHANGELOG-v2.2.0.md (144 L)
Adds Wave 3 entries to Highlights / Added / Changed /
Deprecated / Under-the-hood. Version-bump, BaseCachedProxySlice
SingleFlight migration, pypi/go/composer ProxyCacheWriter
wiring, RequestContext expansion + Deadline + ContextualExecutor,
StructuredLogger 5-tier + LevelPolicy + AuditAction, and the
@deprecated MdcPropagation status — all documented with forensic
and architecture-review section refs.
docs/analysis/v2.2.0-pr-description.md (174 L)
PR #34 body; WI checklist now shows 8 shipped / 6 deferred;
test-run evidence 3,432 tests green; five PR-reviewer focus
points (remaining MdcPropagation callers, lost user_agent sub-
field parsing, audit-logger suppressibility gap in log4j2.xml,
DbIndexExecutorService submit()-path bypass, four-adapter
"any exception → 404" swallow inherited from Maven).
docs/analysis/v2.2-next-session.md (399 L)
Refreshed agent-executable task list. Removes the four
shipped items (WI-post-05, WI-post-07, WI-02, WI-03). Keeps
WI-04 / WI-06 / WI-06b / WI-08 / WI-09 / WI-10 in the same
Goal / Files / Tests / DoD / Depends-on shape. Adds four
WI-post-03 follow-ups surfaced during Wave 3:
a. Hoist DbIndexExecutorService to pantera-core/http/
context/ContextualExecutorService.
b. Re-lift user_agent.name / .version / .os.name parsing
into StructuredLogger.access.
c. Unify the ~110 remaining MdcPropagation call-sites
after WI-06 + WI-08 + the Vert.x-handler migration,
then delete MdcPropagation.java.
d. Migrate 11 Vert.x API handlers (AdminAuth, Artifact,
Auth, Cooldown, Dashboard, Pypi, Repository, Role,
Settings, StorageAlias, User) to a ContextualExecutor-
wrapped worker pool — the single biggest MdcPropagation
debt.
Adds one new concern:
C6. Audit logger inherits log-level config from
com.auto1.pantera parent — §10.4 declares audit as
"non-suppressible" but log4j2.xml has no dedicated
block. Five-line fix tracked separately.
Review verdict: PASS. Every §12 DoD met. Every commit conforms
to type(scope): msg, zero Co-Authored-By trailers across all 11
new commits (verified via git interpret-trailers --only-trailers).
3,432 tests green across pantera-core / pantera-main / every
touched adapter module.
215ffbc to
5b86aa9
Compare
Search + Browse overhaul (bugs A–F) — 6 commits added to this branchUsers filed six discrete complaints against search & browse — investigation collapsed them into four root causes, plus a separate delete-artifacts bug surfaced along the way. Commits in this stack
Schema changes
Both are table rewrites at migration time. On a 10M-row table plan 2–10 minutes; Test coverage262 passing tests in the broader suite. Added:
Also added Deploy notes
Explicit non-goals / follow-ups
🤖 Generated with Claude Code |
|
Follow-up: |
…ast-Modified Replaces the Solr-based MavenCentralSource approach for latency-sensitive lookups. HEAD to repo.maven.apache.org returns accurate Last-Modified in ~67ms with zero indexing lag (vs 11+ month lag from Solr API).
…k for JFrog mirrors
Queries JFrog Artifactory's /api/storage/{repoName}/{path} endpoint to retrieve
the original upstream lastModified date, bypassing the intermittently stale
Last-Modified header JFrog returns for remote/cached artifacts.
…omposition Composes a primary PublishDateSource with a fallback: if primary returns Optional.empty() or completes exceptionally, the fallback is queried. Includes 6 unit tests covering all branches using stub sources (no WireMock).
…own timeout to 2s Replace Solr-based MavenCentralSource with ChainedPublishDateSource (MavenHeadSource → JFrogStorageApiSource) for both "maven" and "gradle" repo types. Reduce cooldown orTimeout from 5s to 2s since HEAD completes in ~67ms.
…ommit Splits write-with-sidecars into verify-then-commit phases so callers can serve from the temp file immediately, committing to storage async.
Replaces sequential thenCompose chain with allOf for ~4x speedup on 4-sidecar artifacts (Maven). Saves ~5% of cold-start overhead.
Uses writeAndVerify → contentFromTempFile for immediate response, commitAsync for background persistence. Eliminates re-read-from-storage latency (~15-20% of cold-start overhead).
Same serve-before-commit pattern as Maven. Go .zip artifacts now stream from the verified temp file immediately.
commitVerified() now reads the temp file eagerly into a byte[] for storage save, avoiding the race where commit() deleted the temp file before the response Flowable opened it. contentFromTempFile() disposer handles cleanup after the response body is fully consumed.
Same serve-before-commit pattern as Maven and Go.
Same serve-before-commit pattern as Maven, Go, and Composer.
Eliminates the 401 challenge-response round-trip on every request. Each Maven artifact fetch was doing 2 HTTP requests (unauthenticated → 401 → retry with auth). This cuts ~20% of cold-start proxy overhead.
fix(composer): guard providers type in ComposerGroupSlice merge
Add release_date column to artifact_cooldowns + history tables (V126), thread the value through JdbcCooldownService -> CooldownRepository -> DbBlockRecord -> CooldownHandler API, and surface it in the admin CooldownView.vue. Migrates Docker from DockerProxyCooldownInspector to the registry-backed inspector so docker tags now persist publish dates the same way other ecosystems do. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove the legacy maven-adapter NegativeCache and the four shim test classes; collapse all callers onto the shared pantera-core implementation keyed by composite (scope, repoType, artifactName, artifactVersion). URL-encode embedded colons in the components, switch pattern matching from contains() to exact-match-with-* wildcard, and update the admin endpoint + UI to render the new key shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add SyncArtifactIndexer SPI in pantera-core and DbSyncArtifactIndexer implementation in pantera-main; thread it through every adapter's upload/push slice so the artifact_index row is written inline with the storage commit before the 201 returns. Closes the 2 s read-after-write window where the async DbConsumer left a freshly-uploaded artifact invisible to group lookups. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Populate go.mod/hello.go/hello_test.go with real module content so the test publish script no longer ships zero-byte uploads. In GoUploadSlice, invalidate the negative cache for the just-uploaded module so a publish followed by an immediate fetch returns 200 instead of stale 404. Tighten GoProxySource and GoSlice handling for the fanout path that the new sync indexer now touches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Read max_connections_per_destination from the http_client YAML block (was hard-coded to 5, the Vertx default), apply a 0.25 factor to the publish-date concurrency budget, and forward each ecosystem's native User-Agent (pip / npm / cargo / etc.) to upstream so we are not throttled or fingerprinted as a generic bot. Renames the old "hcs" setting to a human-readable name and keeps the per-source UA aligned with what real clients send. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MavenGroupSlice and ComposerGroupSlice now run merged maven-metadata.xml and packages.json through the cooldown filter before caching, with the RegistryBackedInspector wiring (slashed -> dotted name conversion; maven-group/gradle-group both routed through the maven-metadata path). Ensures gradle/mvn clients hitting a virtual group do not see versions that the cooldown service has blocked at member level. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two pre-existing bugs surfaced while debugging Gradle latest.release returning 403 against a cooldown-blocked Guava 33.6.0-jre. (1) MavenMetadataFilter.updateLatest only rewrote <latest>; clients still read the stale <release> and tried to fetch the blocked version. updateLatest now recomputes <release> from the filtered version list (latest non-SNAPSHOT). (2) CachedProxySlice.applyMetadataCooldown passed the package name in slashed form (com/google/guava/guava) but MavenHeadSource splits on the last dot to derive groupId/artifactId, so the inspector lookup silently missed and the filter fell open. Convert to dotted form before invoking the metadata service. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PypiSimpleHandler and PypiJsonHandler passed the raw URL package name (Foo_Bar) to the cooldown service, but the publish path stores release dates under the canonical form (foo-bar). The inspector lookup missed the DB row and the filter silently fell open. Apply NormalizedProjectName.Simple at the handler boundary so the request key matches the publish key. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous implementation used String.contains("-") and a handful of
substring checks, so any version with a hyphen was flagged prerelease.
Guava's classifier-style suffixes (-jre, -android) were therefore all
filtered out of the "newest stable" candidate list, and the cooldown
service collapsed <latest>/<release> to a 2011-vintage release (r09)
when 33.6.0-jre was blocked. Tokenize on -/./+, skip the version core
token, and match each remaining token against an explicit set of known
qualifier keywords plus the Maven milestone shorthand. Adds tests
covering Guava classifier suffixes, Spring .RELEASE, and substring
false-positives (-archived, -developer, -macos).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lution
AuditLogger's five methods (upload, download, delete, resolution,
publish) each read client.ip and other identity fields from MDC,
silently emitting nothing when the calling thread never had MDC
bound (Quartz workers, RxJava continuations, any adapter missing a
RequestContextHeaders.bindToMdc call). A full trace across every
format's upload/publish/delete/proxy-fetch path found several
distinct defects beyond the reported client.ip gap:
- download() was dead code: SliceDownload was imported by nine
format adapters, never instantiated in production. Deleted.
- SliceUpload called AuditLogger.upload() directly AND queued an
ArtifactEvent that separately produced AuditLogger.publish() via
DbConsumer -- a double audit line for the same upload, for every
format reusing this generic class (conan, generic files).
- Cooldown-blocked requests had zero audit trail. AuditLogger's
five methods all hardcoded event.outcome=success; there was no
way to express a denial. A blocked fetch returned a correct 403
visible only in the generic HTTP access log.
- A cache-hit proxy serve was never audited at all -- only a
cache-miss-then-fetch reached AuditLogger.publish() via the
async DbConsumer queue.
- Range-based dependency resolution (npm ^2.0.0, unpinned pip
install) has a silent blind spot: when cooldown filters the
newest matching version out of a metadata listing, the resolver
picks the next-allowed version with no record a newer one
existed. Six formats had their own metadata-filter class with
this gap, none auditing it.
New taxonomy -- four events, one AuditContext record carrying
traceId/clientIp as compulsory constructor parameters instead of an
MDC read:
artifact_publish local/hosted repo, actual artifact upload
artifact_access proxy fetch + local reads, actual artifact,
any outcome (cache hit / cache-miss fetch /
cooldown block)
artifact_delete any repo mode
artifact_resolution proxy/group metadata listing view, fires
unconditionally (event.type=allowed|change)
Deliberately out of scope for publish/access: pure metadata requests
carrying no binary artifact bytes (maven-metadata.xml, PyPI
simple-index, Composer's JSON-only AddSlice as opposed to the real
AddArchiveSlice upload path) -- only resolution covers metadata, and
only for the cooldown-visibility case above.
EcsLogger.field() already has MDC-wins deduplication built in (drops
a field value only when ThreadContext already has that key, keeps it
otherwise) -- so passing client.ip/trace.id explicitly via
AuditContext is safe with zero duplicate-key risk either way.
Per-format fixes landed as part of this taxonomy:
- Docker: DockerProxyCooldownSlice's three near-identical
cooldown-block branches consolidated into one audited helper;
DockerManifestByTagHandler now audits access;
DockerTagsListHandler now audits resolution.
- PyPI + Debian: their bespoke DeleteSlice classes never queued a
DB-index-removal event at all -- a functional bug beyond the
audit gap. Both now route through
RepositoryEvents.addDeleteEventByKey and audit delete.
- Maven + npm: share one metadata-filter choke point
(MetadataFilterService.evaluateAndFilter), fixed once for both.
CooldownMetadataService.filterMetadata gained a new 9-arg default
overload delegating from the untouched 7-arg original, so
NoopCooldownMetadataService and every test double kept compiling
unmodified.
- PyPI, Go, Composer, Docker's remaining metadata-filter handlers
all now audit resolution unconditionally on both branches.
- RepositoryEvents gained repoType()/repoName()/artifactName(Key)
accessors and a public VERSION constant so SliceUpload/
SliceDelete build audit calls with the same identity the queued
ArtifactEvent uses.
Verified with a genuinely clean mvn clean install -U -T8 -- mvn
compile / mvn test-compile against a stale target/ initially reported
success while four test files were actually broken (Maven's default
incremental compiler skips recompiling an unchanged test file even
when a method it calls changed shape elsewhere); only a clean build
surfaced them.
Audit logging: structural taxonomy rewrite (commit 7793cf7)Follow-up to the earlier client.ip/trace.id audit-log fix ( Found:
New taxonomy — four events ( Verification note: |
Audit-semantics hardening pass (b73d92c)Squash of four fixes on top of the taxonomy rewrite (7793cf7), each triggered by live log anomalies and closed with regression tests that were verified to fail against the pre-fix code: 1. 2. npm cooldown was structurally bypassed for fresh tarballs — 3. 4. npm audit records were emitted twice per request — same Full |
…he-miss only, resolution on every metadata view, no doubles, full client.ip/trace.id propagation Squash of four fixes hardening the v2.2.0 audit taxonomy (publish/access/delete/resolution) across every wired proxy format. 1. Publish gated to genuine cache-miss; access wired everywhere. Composer, npm, go, and pypi's bespoke proxy slices fired an artifact_publish DB/audit event on every cache-hit re-serve of an already-cached artifact and never bound request context before constructing the ProxyArtifactEvent (client.ip null, trace.id stale-thread leak). All four now enqueue only on cache-miss-then-fetch, bind MDC context first, and emit artifact_access for cache-hit / miss-fetch / cooldown-block outcomes. All four *ProxyPackageProcessor stage-2 jobs now forward trace.id/client.ip onto the final ArtifactEvent via withContext(...) (previously only maven's did). Maven's bespoke primary path gained the missing access wiring. 2. npm cooldown actually enforces on fresh tarballs. NpmProxy.getAsset() conflates the storage check with the upstream fetch-and-save, so DownloadAssetSlice's cooldown gate ran only after the artifact had already been fetched and cached. New NpmProxy#hasAssetInStorageAsync pure existence probe; the slice gates on it. Regression test replayed against the pre-fix code returned HTTP 200 for a blocked version; now 403 with zero upstream contact and empty storage. 3. artifact_resolution fires on EVERY metadata listing view. Three systemic suppression points in the maven/npm choke point (MetadataFilterService.filterMetadata): cooldown-disabled early return, FilteredMetadataCache hits (request #2..N within the 24h TTL were silent — the reported "multiple npm show" symptom), and npm's ETag early-304 exits. Audit moved to the filterMetadata boundary, one record per request at every exit; CacheEntry carries the blocked-version set (L1) so cache hits keep the precise filtered list; new AuditLogger.resolutionDetailUnknown (event.type=["info"]) covers serves where filter state cannot be known (L2 promotion, 304, parse fallbacks); all-blocked denials audit the full hidden list before throwing; the CooldownMetadataService 9-arg interface default audits so Noop deployments audit too. Per-format gaps closed from a branch-by-branch sweep of all 7 dedicated metadata handlers: composer's proxy slice no longer skips its handlers entirely when cooldown is Noop; go's all-blocked 403s, parse fallbacks, and .info/.mod files (now resolution; previously silent or mis-audited as access); docker's /tags/list parse/empty/rewrite fallbacks (the last leaks unfiltered tags — now visible); maven's materialised filtered-output cache hit, null-service branch, group merged-metadata cache hit, and group Noop short-circuit; npm's no-service serves and the /latest raw shortcut. PyPI was already correct. Non-2xx upstream forwards deliberately do not emit resolution (no listing served; HTTP log carries it under the same trace.id). 4. npm audit records no longer doubled. CachedNpmProxySlice's WI-05 signal-based dedup ran the origin slice twice per request on the success path (signal probe with discarded unconsumed body + re-fetch), emitting every audit record twice with the same trace.id. The dedup leader now serves the response from its single origin traversal; followers keep the re-fetch (their single traversal). Synthetic 404/503 signal mappings preserved for RaceSlice's multi-remote fallback; probe body drained on non-success paths. Side benefit: one full origin round-trip per npm request eliminated. BaseCachedProxySlice verified unaffected (signalToResponse reads the cache directly). Every fix carries regression tests verified to fail against the pre-fix code (cooldown-block 200->403, resolution-on-cache-hit / -disabled / -all-blocked, origin-traversal count "was <2>"). Full mvn clean install -T8 green (build + complete test suite).
…reason phrases Under concurrent load a request could observe an npm cache entry whose stream-through save was still mid-commit (the request-dedup gate releases followers when the leader's RESPONSE resolves, which is before the storage tee finishes writing). The follower's read then failed with ValueNotFoundException and surfaced as a 502 that fed the group's per-member circuit breaker. RxNpmProxyStorage.getAsset now degrades a present-but-unreadable entry to a cache miss so callers refetch from upstream — a cache entry we cannot read is not a cache entry. PanteraHttpException.MEANINGS lacked 502/503/504, so upstream Bad Gateway / Service Unavailable / Gateway Timeout were all logged as "PanteraHttpException: Unknown", which materially slowed diagnosis. Both found by running the revived CI perf gate end-to-end locally. Verified: pantera-core + npm-adapter suites green; perf gate 4/4 consecutive local passes at 60s/30rps.
…o-end The performance/*/* gitignore silently kept the whole harness (WireMock mappings + fixture generator, k6 scenarios, repo configs, nginx LB conf, run scripts) out of the tree — both perf workflows had failed at the checkout step since their introduction, and the perf gate never executed anywhere but dev machines. Now committed and bootable from a fresh checkout: - make setup → scripts/setup-harness.sh: renders pantera-config.yml from the template (no envsubst dependency), generates a throwaway RS256 keypair when secrets are absent, seeds fixture files; fully idempotent. - docker-compose-scaling.yml: k6 load-generator service (profile- gated), metrics port published (the gate scrapes /metrics/vertx — metrics were previously not even enabled in the bench config), overridable host ports so the harness can run beside a dev stack, bench auth via PANTERA_USER_NAME/PASS + seed.sql principal (2.2.0 is deny-by-default), WireMock sized for the 10MB body bucket and run with --no-request-journal (the unbounded journal OOMed the mock after ~2 runs and a wounded JVM dripped 500s that tripped Pantera's circuit breaker — the gate's original flakiness), nginx retry of transient backend hiccups, HEAD stub so breaker probes can close. - k6/gate.js: bounded reads-only constant-arrival gate load (default 60s/30rps) with a sequential warmup and a checks threshold so a silent all-401/all-503 run fails instead of handing the invariant check an idle instance. scenario.js (the ~20min 3000rps matrix ramp) stays nightly-only. - perf-gate.yml: correct service names (pantera-sut / mock-upstream — the old ones didn't exist in the compose file), no more `|| true` on the ramp, concurrency cancellation. - perf-baseline.yml deleted (redundant, never passed once); perf-cold-bench.yml is nightly + workflow_dispatch only — wall-clock thresholds are too noisy on shared runners to block PRs. Local verification: mock stack clean at 4x gate rate (5400/5400 at 120rps), then four consecutive full gate runs: 100% checks, zero 429s, amplification ≤0.75, zero breaker trips — 4/4 PASS. The bring-up also surfaced a latent product bug chain (host-shared upstream breaker trips on a single 5xx; synthetic fastfail 502s convict all group members; all-blocked maps to blanket 404 + negative-cache poisoning). RCA with code refs: logs/.analysis/perf-gate-cascade-rca.md (untracked analysis dir) — follow-up fixes recommended.
… dependabot, SHA pins ci.yml targeted the larger-runner label ubuntu-latest-4-cores, which was never provisioned for this repo: every run queued for 24 hours and was auto-cancelled — the repo has had no functioning CI (and no checks on any PR, dependabot included). Public repos get 4-core ubuntu-latest for free, so the label bought nothing but a dead pipeline. - ci.yml: ubuntu-latest; cheap static gates (ECS drift, nginx CVE guard) run first in a ~1min job the build jobs depend on; dorny/ paths-filter skips the backend build on UI-only PRs and vice versa; per-ref concurrency cancellation. - release.yml: publishes a multi-arch (amd64+arm64) image to ghcr.io/auto1-oss/pantera with GITHUB_TOKEN on every tag (latest tag reserved for finals), reusing the built dist instead of rebuilding; attaches SHA256SUMS; attests build provenance for the binaries and the image (id-token/attestations permissions). - nightly-integration.yml: mvn verify -Pitcase nightly — integration tests previously ran ONLY in the tag-time release workflow, the worst possible moment to discover breakage. - dependabot.yml: weekly grouped updates for maven, pantera-ui npm, and github-actions; docker-compose test fixtures deliberately not covered (pinned dependency trees exist to exercise the proxy). - Every third-party action across all workflows pinned by commit SHA.
Also documents the final root cause of the dead pipeline: beyond the unprovisioned runner label, the CI workflow had been manually DISABLED in the repo settings (now re-enabled via gh workflow enable).
The org Actions allowlist permits GitHub-owned actions only (repo-level override returns 409), which startup-failed every run referencing a third-party action. Rather than requesting an org-admin allowlist change, the workflows now use zero third-party actions — the smaller trust surface suits the product anyway: - ci.yml: dorny/paths-filter replaced by plain git-diff change detection against the PR merge-base (fetch-depth: 0 on the cheap static job only). - release.yml: softprops/action-gh-release replaced by the preinstalled gh CLI (gh release create with GITHUB_TOKEN); docker/setup-qemu / setup-buildx / login / build-push replaced by raw docker buildx + tonistiigi/binfmt (a container image, not policy-gated) + docker login --password-stdin. Image digest for the provenance attestation read from buildx --metadata-file.
… tests The revived pipelines' first executions surfaced three Linux-runner issues that macOS development never hit: - seed-files.sh linked ~70k artifacts to one source inode; ext4 caps hardlinks at ~65k per inode (APFS is far higher), failing the gate's harness prep with "Too many links". Links now distribute across a 4-copy pool per size bucket (~17.5k per inode). - RaceSliceTest.returnsFirstSuccessResponseInParallel asserted a 50ms success beats a 150ms one — a 100ms margin that scheduler jitter on loaded shared runners inverted. The assertion is about ordering semantics, not timing precision; margins widened to 10ms vs 2s. - ContextualExecutorIntegrationTest.runnerThreadContextRestored seeded and probed ThreadContext (per-thread state) across a 2-thread pool with no thread-identity guarantee. Now uses a dedicated single-thread executor.
Perf gate: the SUT container runs as uid 2021; Linux bind mounts preserve host ownership, so openssl's 600-perm throwaway JWT key was unreadable (RsaKeyLoader -> 'JWT private key not found') and the seeded data tree was unwritable for proxy-cache stores. setup-harness.sh now chmods the bench keys to 644 and the data tree a+rwX. macOS Docker Desktop remaps ownership, which is why local runs never hit this. CI: standalone 'mvn pmd:check' resolves the build-tools ruleset jar and sibling-module jars from the local repository, which is empty on a clean runner. Fold test + PMD + license into the canonical single-session 'mvn clean install -T8' where reactor resolution and module-by-module install ordering make both resolvable by construction.
Remove perf-gate.yml, perf-cold-bench.yml and nightly-integration.yml —
public CI is now exactly two workflows: ci.yml (build + test backend/UI;
external contributors' runs start only after maintainer approval, which is
already enforced by the repo's all_external_contributors policy) and
release.yml.
release.yml is workflow_dispatch-only: the version is read from the root
POM of the dispatched ref (bump-version.sh stays the single source of
truth), a pre-flight job refuses to reuse an existing v<version> tag, and
'gh release create --target' creates the tag at the dispatched commit.
Artifacts, SHA256SUMS, provenance attestations and the multi-arch GHCR
image are unchanged. Release notes heredoc now actually interpolates the
version (was quoted, rendered literal ${VERSION}).
The perf harness itself stays in the repo for local runs (make setup /
smoke / cell); integration tests still gate every release via -Pitcase.
The docker-build profile auto-activates whenever /var/run/docker.sock exists — true on GitHub runners — and binds a multi-arch 'docker buildx --load' to pantera-main's install phase. Runners have no arm64 binfmt and --load cannot export a manifest list, so the build died at the last module. Image publishing is release.yml's job; CI only needs compile + tests + PMD + license. Verified locally: exec reports 'skipping execute as per configuration', reactor BUILD SUCCESS.
…rmat The 2.2.0 entry carried ~580 unsectioned lines of internal development log (RCAs, bench tables, file lists) above the first section header, plus an invented 'CI & tooling' section. Entry now uses only the house sections with concise attributed bullets. User-facing items that lived only in the dev log (audit taxonomy, unified save bar, group request coalescing, abandoned-body timer, PyPI uv/PEP 691, PyPI cooldown fail-open) are kept as single bullets; stale bullets corrected (bulkhead defaults 40/+4 per V135, removed bullets describing the deleted CI perf workflows and the flipped head-fallback default).
The async subscribe returned before the server registered the subscriber, so an instance logged 'pub/sub started' while not yet subscribed — invalidations broadcast during that window (e.g. while a node joins during a rolling deploy) were silently lost, leaving stale L1 caches. Surfaced as a lost-message race in CacheInvalidationPubSubTest under CI load. Blocking is confined to startup: the only production construction path is VertxMain -> SettingsFromPath -> YamlSettings on the boot thread, which already blocks on connectPubSub() two lines above; this adds one round-trip on an established connection. publish() and message dispatch stay fully async.
Maven: handlebars 4.4.0->4.5.2 (root pom; declared but unreferenced — removal candidate), commons-lang3 3.14.0->3.20.0 (pantera-storage-core, npm-adapter; aligns with pantera-core), protobuf-java 3.21.10->3.25.5 (hexpm-adapter; runtime is gencode-compatible), xmlunit-core/matchers 2.7.0->2.10.0 (rpm-adapter). pantera-ui: axios 1.15.2->1.16.0, vite 8.0.8->8.0.16, vitest 3.2.4->3.2.6 (critical), plus lockfile-only transitive fixes via npm audit fix (js-cookie, form-data, esbuild, ws, vite-node's vite 7.x). npm audit now reports 0 vulnerabilities. Verified: full reactor 'mvn clean install -T8' green (one unrelated VertxSliceServerTest flake under parallel load passed 3x + full module suite standalone); UI type-check/lint/94 tests/build all green. Not addressed here: ini4j 0.5.4 (conan-adapter) has no patched release; needs a replacement decision. Test-fixture alerts (nuspec parser data, uv-compat sample lock, docker-compose demo pom) dismissed as not_used.
ini4j 0.5.4 has an open advisory with no patched release, and it parsed
conaninfo.txt from uploaded/proxied packages — attacker-influenceable
input. The adapter already ships its own IniFile parser; ConansEntity
now uses it. Behavioral parity is pinned by ConansEntityTest's exact
JSON expectations: bare lines (recipe_hash, list-style entries) stay
out of section objects (IniFile stores them as empty-value keys, the
old parser as null values), empty sections still render as {}.
IniFile gains a String constructor; ini4j is removed from the pom.
Root-caused in the 2.2.0 breaker RCA: the outbound HTTP breaker tripped on a SINGLE 5xx for 30s keyed by bare hostname; its synthetic 502s lost the X-Pantera-Circuit-Open marker in the npm/maven adapters, so GroupResolver convicted every member's health window on fabricated evidence; all-members-blocked walks returned 404 and poisoned the shared negative cache beyond the outage. - Marker propagation: UpstreamCircuitOpenException (pantera-core) carries circuit-open + Retry-After through npm and maven adapter error funnels; GroupResolver treats a marker-502 as 'member skipped' — walk continues, no recordFailure(). - All-skipped terminal is 503 + Retry-After + marker, never a negative-cached 404; genuine all-404 walks still cache. - Volume/rate gate: the breaker now trips at >=50% failures across >=10 calls in a 30s sliding window (AutoBlockSettings shape), seed 2s; window cleared on trip; failed probes still re-trip unconditionally. - Keyed per scheme://host:port instead of bare host. - Circuit-open members get one cache-only probe (internal header, honored by BaseCachedProxySlice) so warm cache still serves — Nexus-style serve-cached-while-blocked. - ECS transition logging: trip/probe-failure at WARN, recovery at INFO with fast-fail count; group skip/all-open events logged. 16 new tests across breaker gate, group walk semantics, cache-only probe, and marker survival.
The outbound breaker's gate and backoff (failure-rate threshold, minimum calls, window seconds, seed/max backoff) become DB-backed runtime settings, mirroring the group-member breaker's AutoBlockSettings pattern: upstream_breaker_* keys in auth_settings (seeded by V136), UpstreamBreakerSettingsLoader with DB -> env (PANTERA_UPSTREAM_BREAKER_*) -> hardcoded fallback, invalidated on admin PUT. Breakers read config through a live supplier, so changes apply to existing breakers on the next recorded outcome — window re-allocates and backoff reshapes on change. GET/PUT /api/v1/admin/upstream-breaker-settings with key whitelist and constructor round-trip validation. Admin UI gains an 'Upstream HTTP Circuit Breaker' section directly below the existing one, which is relabeled 'Group Member Circuit Breaker'; both cards name the other to keep the two layers distinct. Full save-bar/dirty/discard integration plus a test pinning independent dirty-tracking.
Audited all dashboards against the live /metrics/vertx exposition and
the registered meter names in code. Fixed three broken references:
pantera_artifact_{download,upload}_bytes_total never existed (now
pantera_repo_bytes_*), and pantera_cooldown_all_blocked is a gauge —
the _total-suffixed rate queries silently rendered nothing.
Added ~25 panels with source-verified tag names: upstream requests/
latency/429s/rate-limit gate/availability, bulkhead permits/overflow/
ramp, proxy cache-write failures (proxy); cache operation latency/
dedup/errors and SWR metadata outcomes (cache-storage); storage
executor pools, events queues, Jetty connection pool (infrastructure);
group resolution latency, member metadata size, drain drops (group);
in-flight requests, handler phases, body sizes (overview); cooldown
evaluate/cache-load latency and admin actions (cooldown). Families
without histogram buckets chart sum/count averages, not quantiles.
The upstream-circuit-breaker and proxy-phase-latency dashboards are now
provisioned into the local compose Grafana (previously only shipped in
src/main/resources and never loaded).
release.yml gains a publish-ui-image job: multi-arch (amd64+arm64) ghcr.io/<repo>-ui image from pantera-ui/Dockerfile, :latest withheld on pre-releases, provenance attested, gated on the build-ui test job. The Dockerfile's node build stage is pinned to $BUILDPLATFORM — the vite output is platform-independent, so it builds once natively instead of again under QEMU. Release notes now list both images. Docs updated for the two release run paths (published images; JAR dist + UI bundle): README quick start, admin installation.md (image/port tables, tag policy, attestation verification, compose example) and ui-deployment.md (image as first option, full 10-variable env table), plus pointers in the user and developer guides. Fixed two stale claims in ui-deployment.md: APP_TITLE defaults to Pantera (not Artipie) and the /api/ proxy block is generated only when API_UPSTREAM is set. Verified locally: UI image builds and serves, entrypoint renders config.json from env defaults.
Panel-by-panel audit of all provisioned dashboards against live
Prometheus (instant-query every target expression):
- pantera-vertx-metrics: every query filtered job="pantera-vertx" but
all series carry job="pantera" (registry commonTags) — the entire
dashboard rendered no data since inception. Label fixed; 26 series
now match the request-rate panel alone.
- pantera-cooldown: cache hits/misses filtered {tier="l2"} but the
counters carry no tier tag — filter removed.
- pantera-infrastructure: HikariCP acquire/usage panels queried
client-side quantile series (quantile="0.95"/"0.99") that HikariCP's
Micrometer binding does not publish — rewritten to sum/count averages,
which the exposition does carry.
- pantera-cache-storage: removed the SWR metadata-cache panel; audit
found SwrMetadataCache is never constructed anywhere, so its
counters can never emit (dead code — flagged separately).
Remaining empty panels are either histogram-quantile queries gated on
PANTERA_METRICS_PERCENTILES_HISTOGRAM=true (documented, default false)
or lazily-registered event counters that are legitimately absent until
their first event (breaker trips, 429s, overflow, errors).
MetadataCacheSwrTest bounded cache serves at 5 ms of wall clock — pure scheduler noise on loaded CI runners (failed at 9 ms), and zero detection power: the fake loaders complete instantly, so even a fully blocking implementation passed the bound. Fresh-window test: the calls==1 assertion is the actual no-upstream proof; the timing bound is dropped. Stale-window test: the refresh loader now parks on a latch — the foreground load() completing while the loader is parked IS the serve-stale-refresh-in-background invariant; a regression to synchronous refresh deadlocks into the new @timeout(10) and fails deterministically. Verified 3x green + full module suite.
pantera_group_member_requests/_latency lost their recording call sites
when the parallel fanout became the sequential walk (2.2.0), and
pantera_artifact_{downloads,uploads}/artifact_size_bytes never had any
callers — the group and repository dashboards' panels charting them
could not render.
GroupResolver now records each member's outcome and wall latency at
every walk step (success / not_found / error, plus a latency-less
circuit_open count for marker skips and a success record when a
circuit-open member serves its warm cache). RepoMetricsSlice records
artifact counts and the size distribution beside the existing per-repo
byte counters — downloads count only GET responses (HEAD carries
Content-Length without a body transfer).
Verified live against the dev stack: member series appear with
group/member/result tags; artifact counters track authenticated GETs.
PANTERA_METRICS_PERCENTILES_HISTOGRAM previously enabled Micrometer's ~70 auto buckets per timer series. It now publishes two curated service-level-objective ladders: - transfer timers (http/proxy/upstream request duration, storage ops, Vert.x server response time): 18 boundaries, 10 ms to 20 min — these timers span full body streaming, and multi-GB artifacts to slow clients legitimately run minutes; without the tail p99 clamps at the top bucket and lies. - control-plane timers (everything else): 16 boundaries, 1 ms to 30 s, denser in the 1-500 ms band where those paths live. Recording cost is bucket-count-insensitive (binary search + lock-free increment); the cost axis is series count at scrape/TSDB time — the ladders emit ~6x fewer series than the auto mode at equal dashboard value (~buckets+1 per tag combination, roughly 6-10k series per instance under heavy traffic). Bucket shape is fixed at meter creation, so ladder changes need a restart. The dev compose sets the flag so every Grafana p95/p99 panel works out of the box; docs updated accordingly. Verified live: transfer ladder on pantera_http_request_duration (19 series incl +Inf), control ladder on pantera_group_member_latency (17 series).
…utorTest The shared HandlerExecutor pool has a bounded queue + AbortPolicy by design (production backpressure must be visible). In the shared surefire JVM, async work leaked by earlier test classes can keep the queue momentarily full on a loaded CI runner, rejecting this test's submits (RejectedExecutionException in poolThreadsAreDaemon on run 28672873815). All five submits now retry briefly until the pool accepts — the tests assert context propagation and thread attributes, not queue capacity. Verified 3x green.
CooldownMetadataServicePerformanceTest.cacheHitLatencyUnder5ms failed CI at 5 ms of scheduler noise (run 28673733622) — the seventh distinct wall-clock flake this branch surfaced serially. Instead of another one-test fix, this sweeps every remaining tight absolute latency assertion in the unit suite (core + npm cooldown perf tests, parallel version evaluation) to ~10x its idle-laptop value. The assertions guard order-of-magnitude regressions (accidental I/O, lost caching, per-version network calls); at 10x they still catch every one of those failure modes while surviving shared-runner load. The in-memory chaos-test budgets (500 ms / 5 s) were already at sane margins and are untouched.
v2.2.0 — target-architecture train + cooldown metadata filtering + publish-date registry + serve-before-commit
Summary
This PR lands the v2.2 target architecture (9 work items), cooldown metadata filtering across all 7 adapters, publish-date registry replacing per-adapter cooldown inspectors, serve-before-commit performance pattern across all proxy adapters, search & browse overhaul, group RaceSlice priority fix, ECS log field compliance, and preemptive Basic auth for Maven/Gradle.
Highlights
writeAndVerify()returns aVerifiedArtifactafter streaming upstream to a verified temp file — HTTP response streams immediately whilecommitAsync()persists to cache in the background. Wired into Maven, Go, Composer, PyPI.CompletableFuture.allOf()replaces sequentialthenComposechain for checksum sidecar writes.DbPublishDateRegistry(Caffeine L1 + Postgres L2 + 6 upstream source implementations) replaces all per-adapterCooldownInspectorclasses.RegistryBackedInspectoris the single inspector.providersfield type guard prevents ClassCastException when Packagist returns[]instead of{}.Target architecture work items (9)
Cooldown metadata filtering (8 phases)
Two-layer enforcement (soft metadata filter + hard 403) for Maven, npm, PyPI, Docker, Go, Composer, Gradle with 5 performance hardenings and SOLID package restructure.
Additional features
Test plan
mvn -T8 test— all modules green (4900+ tests, 0 failures)docker compose up -d && curl -u ayd:ayd http://localhost:8081/maven_group/...curl -u ayd:ayd http://localhost:8081/php_group/packages.jsonreturns 200git log --format='%B' | grep -c '^Co-Authored-By:'returns 0