Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
d59c4fe
docs(architecture): add v2.1.3 analysis + v2.2 target architecture
aydasraf Apr 16, 2026
4242ea9
fix(hotfix): queue overflow + access-log level policy (WI-00)
aydasraf Apr 16, 2026
08684bc
feat(core): introduce Fault + Result sum types (WI-01)
aydasraf Apr 16, 2026
c165f38
feat(cache): ProxyCacheWriter with atomic primary+sidecar integrity (…
aydasraf Apr 16, 2026
03214a9
refactor(core): unify three coalescers into SingleFlight<K,V> (WI-05)
aydasraf Apr 16, 2026
1677e68
docs(release): v2.2.0 changelog + PR body + next-session task list
aydasraf Apr 16, 2026
9b8e005
chore(release): bump version to 2.2.0
aydasraf Apr 16, 2026
cf79926
refactor(cache): migrate BaseCachedProxySlice to SingleFlight; delete…
aydasraf Apr 16, 2026
0629b54
feat(cache): wire ProxyCacheWriter into pypi/go/composer adapters (WI…
aydasraf Apr 16, 2026
129b0bf
feat(context): expand RequestContext + Deadline + ContextualExecutor …
aydasraf Apr 16, 2026
b8fd2ba
feat(observability): StructuredLogger 5-tier + LevelPolicy + AuditAct…
aydasraf Apr 16, 2026
49f0232
docs(release): update v2.2.0 artefacts after Wave 3 (WI-post-05/07, W…
aydasraf Apr 16, 2026
076ecc8
fix(logging): dedicate log4j2 block for com.auto1.pantera.audit (C6)
aydasraf Apr 16, 2026
8e22ee8
feat(observability): re-lift user_agent sub-field parsing into Struct…
aydasraf Apr 16, 2026
86697a6
refactor(context): hoist DbIndexExecutorService to reusable Contextua…
aydasraf Apr 16, 2026
40f729e
refactor(api): migrate 11 Vert.x handlers to HandlerExecutor; remove …
aydasraf Apr 16, 2026
dcb35b8
feat(group): GroupResolver with 5-path decision tree + IndexOutcome s…
aydasraf Apr 16, 2026
26b36a3
feat(cache): unify NegativeCache with composite NegativeCacheKey; mig…
aydasraf Apr 17, 2026
3de6b8f
refactor(npm): retire RxJava2 from hot-path callers; remove 17 MdcPro…
aydasraf Apr 17, 2026
ab89a36
feat(resilience): per-repo RepoBulkhead; delete static DRAIN_EXECUTOR…
aydasraf Apr 17, 2026
83eb262
feat(release): SLO docs + CI perf baseline + chaos tests + release-ga…
aydasraf Apr 17, 2026
41764fb
feat(admin): negative-cache admin UI + 5 REST endpoints (WI-06b)
aydasraf Apr 17, 2026
36c9475
refactor(trace): delete MdcPropagation.java — context propagation ful…
aydasraf Apr 17, 2026
beaa9a3
fix(ui): add missing repo types — Go local/group, Gradle all, PHP gro…
aydasraf Apr 17, 2026
fe42d82
feat(ui): searchable AutoComplete dropdown for group member selection
aydasraf Apr 17, 2026
e6624ac
docs: update UI guide for new repo types + group member UX
aydasraf Apr 17, 2026
380af92
refactor(cooldown): reorganize to SOLID sub-package layout (Phase 1)
aydasraf Apr 17, 2026
dfb87ec
fix(cooldown): fix inflight-map memory leak on exceptional completion…
aydasraf Apr 17, 2026
13c4595
perf(cooldown): pre-warm release-date cache from metadata on fetch (H1)
aydasraf Apr 17, 2026
b407dc0
perf(cooldown): parallel bounded version evaluation with dedicated ex…
aydasraf Apr 17, 2026
a69b4c1
perf(cooldown): stale-while-revalidate on filtered metadata cache (H3)
aydasraf Apr 17, 2026
db98744
perf(cooldown): increase metadata cache L1 capacity to 50K (H4)
aydasraf Apr 17, 2026
87c6c4c
test(cooldown): update cache-expiry test for SWR semantics (H3 follow…
aydasraf Apr 17, 2026
a48b046
feat(go): cooldown metadata parser/filter/rewriter/detector
aydasraf Apr 17, 2026
596b963
feat(docker): cooldown metadata parser/filter/rewriter/detector
aydasraf Apr 17, 2026
b41e118
feat(maven): cooldown metadata parser/filter/rewriter/detector
aydasraf Apr 17, 2026
3bd159c
feat(composer): cooldown metadata parser/filter/rewriter/detector
aydasraf Apr 17, 2026
dda0285
feat(pypi): cooldown metadata parser/filter/rewriter/detector
aydasraf Apr 17, 2026
d8c7b3a
fix(cooldown): harden unblock→invalidation flow — sync completion + m…
aydasraf Apr 17, 2026
5f62671
feat(cooldown): per-adapter 403 response factories with registry (Pha…
aydasraf Apr 17, 2026
034dff2
feat(cooldown): verify npm + wire Gradle reusing Maven components (Ta…
aydasraf Apr 17, 2026
9325a01
feat(cooldown): register adapter bundles + wire metadata filtering in…
aydasraf Apr 17, 2026
bf3152c
test(cooldown): integration + chaos tests for metadata filtering (Pha…
aydasraf Apr 17, 2026
a927c4d
docs: cooldown metadata filtering documentation + changelog update (P…
aydasraf Apr 17, 2026
22d0c98
perf(release): increase list workers.
aydasraf Apr 18, 2026
2095dda
refactor(ui): purge legacy 'hex' repo-type key in favor of hexpm
aydasraf Apr 18, 2026
97beb28
docs(ui): new repo types (gradle, go-group, go-proxy) and group membe…
aydasraf Apr 18, 2026
9a03561
feat(group): add GroupResolver constructor matching legacy GroupSlice…
aydasraf Apr 18, 2026
dc1e384
chore: clean stale MdcPropagation / CooldownMetadataServiceImpl refer…
aydasraf Apr 18, 2026
4e9ef0d
test(cooldown): add policy-change invalidation and upstream-publish r…
aydasraf Apr 18, 2026
ede4bc9
test(cooldown): add Valkey-staleness and high-cardinality chaos tests
aydasraf Apr 18, 2026
01652ca
refactor(group): wire GroupResolver at all RepositorySlices sites
aydasraf Apr 18, 2026
832a3c5
refactor(group): delete legacy GroupSlice and rename metrics
aydasraf Apr 18, 2026
e7ca20f
refactor(cooldown): migrate 12 CooldownResponses callers to Registry;…
aydasraf Apr 18, 2026
9420c9b
docs: record WI-04 completion and companion cleanups in v2.2 changelog
aydasraf Apr 18, 2026
3697877
perf(hot-path): hoist Pattern.compile out of TrimmedDocker and SubSto…
aydasraf Apr 19, 2026
4804416
refactor(cooldown): add CooldownResponseRegistry.getOrThrow; migrate …
aydasraf Apr 19, 2026
4cbb622
fix(resource-leak): try-with-resources + lazy-open on legacy RPM / De…
aydasraf Apr 19, 2026
1d94d1c
refactor(index): DbArtifactIndex AbortPolicy; delete dead api-workers…
aydasraf Apr 19, 2026
fa365b7
fix(server): propagate client disconnect to slice subscriptions
aydasraf Apr 19, 2026
04f7dc5
feat(auth): CachedLocalEnabledFilter L1/L2 cache + Hikari fail-fast d…
aydasraf Apr 19, 2026
661f3c3
feat(cache): GroupMetadataCache stale 2-tier (aid-not-breaker) + JobD…
aydasraf Apr 19, 2026
1a3d961
perf(hot-path): zero-copy ArtifactHandler chunks + hoist Yaml/Json Ob…
aydasraf Apr 19, 2026
8fdd73c
refactor(maven-group): StAX streaming metadata merger + HexFormat
aydasraf Apr 19, 2026
0afcb57
feat(http3): optional ProxyConnectionFactory behind PANTERA_HTTP3_PRO…
aydasraf Apr 19, 2026
3651155
fix(index): wrap synchronous RejectedExecutionException from supplyAsync
aydasraf Apr 19, 2026
a1e32ab
docs(v2.2): admin/developer/user guides + deployment checklist + chan…
aydasraf Apr 19, 2026
9d27fbf
docs: consolidate v2.2.0 notes into single CHANGELOG.md; untrack docs…
aydasraf Apr 19, 2026
794a8dd
Merge remote-tracking branch 'origin/master' into 2.2.0
aydasraf Apr 19, 2026
926d02f
feat(release): bump version to 2.2.0
aydasraf Apr 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
67 changes: 67 additions & 0 deletions .github/workflows/perf-baseline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Perf Baseline

on:
pull_request:
branches: [master, 2.2.0]

jobs:
perf-baseline:
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
cache: maven

- name: Build (skip tests)
run: mvn -T8 install -DskipTests -q

- name: Install wrk
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq wrk

- name: Start Pantera
run: |
java -jar pantera-main/target/pantera-main-*.jar &
echo $! > /tmp/pantera.pid
# Wait for health endpoint
for i in $(seq 1 30); do
if curl -sf http://localhost:8080/api/health > /dev/null 2>&1; then
echo "Pantera started"
break
fi
sleep 2
done

- name: Run perf benchmark
run: |
chmod +x scripts/perf-benchmark.sh
scripts/perf-benchmark.sh http://localhost:8080 /tmp/measured.json

- name: Compare against baseline
run: |
chmod +x scripts/perf-compare.sh
scripts/perf-compare.sh tests/perf-baselines/npm-proxy.json /tmp/measured.json

- name: Stop Pantera
if: always()
run: |
if [ -f /tmp/pantera.pid ]; then
kill "$(cat /tmp/pantera.pid)" 2>/dev/null || true
fi

- name: Upload benchmark results
if: always()
uses: actions/upload-artifact@v4
with:
name: perf-results
path: /tmp/measured.json
retention-days: 30
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@ pantera-main/docker-compose/pantera/artifacts/php/composer.lock
/benchmark/setup/repos-old/.tmp
/pantera-backfill
pantera-main/docker-compose/pantera/keys/
logs/.analysis/
performance/*/*
!performance/results/.gitkeep
performance/wiremock/__files/*.bin
performance/pantera-config.yml
performance/secrets/*.pem
performance/fixtures/uploads/*.tgz
173 changes: 173 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build-tools/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.auto1.pantera</groupId>
<artifactId>build-tools</artifactId>
<version>2.1.3</version>
<version>2.2.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
12 changes: 6 additions & 6 deletions composer-adapter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ SOFTWARE.
<parent>
<groupId>com.auto1.pantera</groupId>
<artifactId>pantera</artifactId>
<version>2.1.3</version>
<version>2.2.0</version>
</parent>
<artifactId>composer-adapter</artifactId>
<version>2.1.3</version>
<version>2.2.0</version>
<packaging>jar</packaging>
<name>composer-files</name>
<description>Turns your files/objects into PHP Composer artifacts</description>
Expand All @@ -45,19 +45,19 @@ SOFTWARE.
<dependency>
<groupId>com.auto1.pantera</groupId>
<artifactId>http-client</artifactId>
<version>2.1.3</version>
<version>2.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.auto1.pantera</groupId>
<artifactId>files-adapter</artifactId>
<version>2.1.3</version>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.auto1.pantera</groupId>
<artifactId>pantera-storage-core</artifactId>
<version>2.1.3</version>
<version>2.2.0</version>
<scope>compile</scope>
<!-- Do not remove this exclusion! No tests will run if dependency is not excluded! -->
<exclusions>
Expand All @@ -76,7 +76,7 @@ SOFTWARE.
<dependency>
<groupId>com.auto1.pantera</groupId>
<artifactId>vertx-server</artifactId>
<version>2.1.3</version>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2025-2026 Auto1 Group
* Maintainers: Auto1 DevOps Team
* Lead Maintainer: Ayd Asraf
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License v3.0.
*
* Originally based on Artipie (https://github.com/artipie/artipie), MIT License.
*/
package com.auto1.pantera.composer.cooldown;

import com.auto1.pantera.cooldown.api.CooldownBlock;
import com.auto1.pantera.cooldown.response.CooldownResponseFactory;
import com.auto1.pantera.http.Response;
import com.auto1.pantera.http.ResponseBuilder;

import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

/**
* Composer-specific cooldown 403 response factory.
*
* <p>Returns {@code application/json} body matching the Composer error format.</p>
*
* @since 2.2.0
*/
public final class ComposerCooldownResponseFactory implements CooldownResponseFactory {

private static final DateTimeFormatter ISO = DateTimeFormatter.ISO_OFFSET_DATE_TIME;

@Override
public Response forbidden(final CooldownBlock block) {
final String until = ISO.format(
block.blockedUntil().atOffset(ZoneOffset.UTC)
);
final long retryAfter = Math.max(
1L,
Duration.between(Instant.now(), block.blockedUntil()).getSeconds()
);
final String body = String.format(
"{\"error\":\"version in cooldown\",\"blocked_until\":\"%s\"}", until
);
return ResponseBuilder.forbidden()
.header("Retry-After", String.valueOf(retryAfter))
.header("X-Pantera-Cooldown", "blocked")
.jsonBody(body)
.build();
}

@Override
public String repoType() {
return "composer";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2025-2026 Auto1 Group
* Maintainers: Auto1 DevOps Team
* Lead Maintainer: Ayd Asraf
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License v3.0.
*
* Originally based on Artipie (https://github.com/artipie/artipie), MIT License.
*/
package com.auto1.pantera.composer.cooldown;

import com.auto1.pantera.cooldown.metadata.MetadataFilter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.util.Set;

/**
* PHP Composer metadata filter implementing cooldown SPI.
* Removes blocked version keys from the Composer packages.json version map.
*
* <p>Filters the {@code packages.{vendor/package}} object by removing blocked
* version keys. Composer has no "latest" tag in the metadata format, so
* {@link #updateLatest(JsonNode, String)} is a no-op that returns metadata unchanged.</p>
*
* @since 2.2.0
*/
public final class ComposerMetadataFilter implements MetadataFilter<JsonNode> {

@Override
public JsonNode filter(final JsonNode metadata, final Set<String> blockedVersions) {
if (blockedVersions.isEmpty()) {
return metadata;
}
if (!(metadata instanceof ObjectNode)) {
return metadata;
}
final JsonNode packages = metadata.get("packages");
if (packages == null || !packages.isObject() || packages.size() == 0) {
return metadata;
}
final String name = packages.fieldNames().next();
final JsonNode pkgNode = packages.get(name);
if (pkgNode != null && pkgNode.isObject()) {
final ObjectNode versionsObj = (ObjectNode) pkgNode;
for (final String blocked : blockedVersions) {
versionsObj.remove(blocked);
}
}
return metadata;
}

@Override
public JsonNode updateLatest(final JsonNode metadata, final String newLatest) {
// Composer packages.json has no "latest" tag — the client resolves
// version constraints from the full version map. No-op.
return metadata;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2025-2026 Auto1 Group
* Maintainers: Auto1 DevOps Team
* Lead Maintainer: Ayd Asraf
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License v3.0.
*
* Originally based on Artipie (https://github.com/artipie/artipie), MIT License.
*/
package com.auto1.pantera.composer.cooldown;

import com.auto1.pantera.cooldown.metadata.MetadataParseException;
import com.auto1.pantera.cooldown.metadata.MetadataParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* PHP Composer metadata parser implementing cooldown SPI.
* Parses Composer packages.json metadata and extracts version information.
*
* <p>Composer metadata structure (packages endpoint {@code /packages/{vendor}/{pkg}.json}
* or {@code /p2/{vendor}/{pkg}.json}):</p>
* <pre>
* {
* "packages": {
* "vendor/package": {
* "1.0.0": {"name": "vendor/package", "version": "1.0.0", ...},
* "1.1.0": {"name": "vendor/package", "version": "1.1.0", ...},
* "2.0.0": {"name": "vendor/package", "version": "2.0.0", ...}
* }
* }
* }
* </pre>
*
* @since 2.2.0
*/
public final class ComposerMetadataParser implements MetadataParser<JsonNode> {

/**
* Shared ObjectMapper for JSON parsing (thread-safe).
*/
private static final ObjectMapper MAPPER = new ObjectMapper();

/**
* Content type for Composer metadata.
*/
private static final String CONTENT_TYPE = "application/json";

@Override
public JsonNode parse(final byte[] bytes) throws MetadataParseException {
if (bytes == null || bytes.length == 0) {
throw new MetadataParseException("Empty or null Composer metadata");
}
try {
final JsonNode node = MAPPER.readTree(bytes);
if (node == null) {
throw new MetadataParseException("Parsed Composer metadata is null");
}
return node;
} catch (final IOException ex) {
throw new MetadataParseException("Failed to parse Composer metadata JSON", ex);
}
}

@Override
public List<String> extractVersions(final JsonNode metadata) {
final JsonNode pkgNode = this.findPackageNode(metadata);
if (pkgNode == null || !pkgNode.isObject()) {
return Collections.emptyList();
}
final List<String> result = new ArrayList<>();
final Iterator<String> fields = pkgNode.fieldNames();
while (fields.hasNext()) {
result.add(fields.next());
}
return result;
}

@Override
public Optional<String> getLatestVersion(final JsonNode metadata) {
// Composer packages.json does not have a "latest" tag;
// the client resolves constraints from the full version map.
return Optional.empty();
}

@Override
public String contentType() {
return CONTENT_TYPE;
}

/**
* Get the package name from metadata.
* Returns the first (and typically only) key under the "packages" object.
*
* @param metadata Parsed metadata
* @return Package name if present, empty otherwise
*/
public Optional<String> getPackageName(final JsonNode metadata) {
final JsonNode packages = metadata.get("packages");
if (packages != null && packages.isObject() && packages.size() > 0) {
return Optional.of(packages.fieldNames().next());
}
return Optional.empty();
}

/**
* Find the package version-map node inside the metadata.
* Navigates {@code packages.{first-key}} to reach the object whose
* field names are version strings.
*
* @param metadata Root metadata node
* @return Package version map node, or {@code null} if not found
*/
private JsonNode findPackageNode(final JsonNode metadata) {
final JsonNode packages = metadata.get("packages");
if (packages == null || !packages.isObject() || packages.size() == 0) {
return null;
}
final String name = packages.fieldNames().next();
final JsonNode pkgNode = packages.get(name);
if (pkgNode != null && pkgNode.isObject()) {
return pkgNode;
}
return null;
}
}
Loading