Skip to content

Commit 59ce37e

Browse files
authored
Add bounded Catalog materializer and delegated executor for v0.14.40 (#12)
* Add bounded Catalog materializer and delegated executor for v0.14.40 * Refresh exact builder header and pip vendor evidence * Update integration setuptools to official 84.1.0 fix
1 parent 30d2571 commit 59ce37e

40 files changed

Lines changed: 8324 additions & 146 deletions

‎.github/dapper-headers.json‎

Lines changed: 6225 additions & 0 deletions
Large diffs are not rendered by default.

‎.github/scripts/enforce-builder-vulnerabilities.py‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@ def violations(report, ledger, boundary, headers):
2727
if (headers.get("package") != "linux-libc-dev"
2828
or headers.get("version") != HEADER_VERSION or headers.get("purl") != HEADER_PURL):
2929
raise ValueError("Changed exact header review")
30+
refresh = headers.get("mediumRefresh", {})
31+
refreshed = {}
32+
for entry in refresh.get("entries", []):
33+
identifier = entry.get("id")
34+
official = ("https://raw.githubusercontent.com/canonical/ubuntu-security-notices/"
35+
+ str(refresh.get("officialSnapshot")) + "/osv/cve/2026/UBUNTU-"
36+
+ str(identifier) + ".json")
37+
if (identifier in refreshed or identifier not in headers.get("ids", [])
38+
or entry.get("package") != "linux-libc-dev" or entry.get("version") != HEADER_VERSION
39+
or entry.get("purl") != HEADER_PURL or entry.get("severity") != "MEDIUM"
40+
or entry.get("decision") != "UNFIXED_MEDIUM_HEADER_ONLY"
41+
or entry.get("officialFixedVersions") != []
42+
or entry.get("officialRangeEvents") != [{"introduced": "0"}]
43+
or entry.get("officialUrl") != official
44+
or not entry.get("rawSha256") or not entry.get("target")):
45+
raise ValueError("Invalid exact medium header refresh")
46+
refreshed[identifier] = entry
3047
reviewed = {}
3148
for entry in ledger.get("unfixed", []):
3249
key = (entry["id"], entry["package"], entry["version"], entry["purl"])
@@ -54,7 +71,8 @@ def violations(report, ledger, boundary, headers):
5471
if boundary != expected:
5572
raise ValueError("Missing exact installed header-only proof")
5673
if (not fixed and key[0] in headers.get("ids", [])
57-
and key[2] == HEADER_VERSION and key[3] == HEADER_PURL):
74+
and key[2] == HEADER_VERSION and key[3] == HEADER_PURL
75+
and (key[0] not in refreshed or finding.get("Severity") == "MEDIUM")):
5876
continue
5977
severity = finding.get("Severity", "UNKNOWN")
6078
entry = reviewed.get(key)

‎.github/scripts/test_builder_vulnerability_policy.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import importlib.util
2+
import json
3+
from copy import deepcopy
24
import unittest
35
from pathlib import Path
46
spec = importlib.util.spec_from_file_location("gate", Path(__file__).with_name("enforce-builder-vulnerabilities.py"))
@@ -69,6 +71,28 @@ def test_fixed_header_rejected(self):
6971
self.header()["FixedVersion"] = "7.0.0-99"; self.assertTrue(self.check())
7072
def test_new_header_advisory_rejected(self):
7173
self.header()["VulnerabilityID"] = "NEW"; self.assertTrue(self.check())
74+
def refreshed_header(self):
75+
self.headers = json.loads((Path(__file__).resolve().parents[1] / "dapper-headers.json").read_text())
76+
f = self.header()
77+
f.update(VulnerabilityID=self.headers["mediumRefresh"]["entries"][0]["id"], Severity="MEDIUM")
78+
return f
79+
def test_refreshed_exact_medium_header(self):
80+
self.refreshed_header()
81+
self.assertEqual(len(self.headers["mediumRefresh"]["entries"]), 222)
82+
self.assertEqual(self.check(), [])
83+
def test_refreshed_headers_cannot_allow_high_unknown_or_available_fix(self):
84+
for severity in ("LOW", "HIGH", "CRITICAL", "UNKNOWN"):
85+
self.refreshed_header()["Severity"] = severity
86+
with self.subTest(severity=severity): self.assertTrue(self.check())
87+
self.refreshed_header()["FixedVersion"] = "7.0.0-99"
88+
self.assertTrue(self.check())
89+
def test_refreshed_review_fixed_or_package_drift_rejected(self):
90+
self.refreshed_header()
91+
original = deepcopy(self.headers)
92+
for key, value in (("officialFixedVersions", ["7.0.0-99"]), ("purl", "pkg:generic/other@1")):
93+
self.headers = deepcopy(original)
94+
self.headers["mediumRefresh"]["entries"][0][key] = value
95+
with self.subTest(key=key), self.assertRaises(ValueError): self.check()
7296
def test_header_version_or_purl_drift_rejected(self):
7397
for field in ("InstalledVersion", "PkgIdentifier"):
7498
f = self.header()

‎.github/workflows/security-release-gate.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
env:
2222
DAPPER_IMAGE: pasturestack/compose-cli-dapper:${{ github.sha }}
2323
TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
24-
VERSION_OVERRIDE: 0.14.39
24+
VERSION_OVERRIDE: 0.14.40
2525
PLATFORM_COMPAT_JAR_URL: https://github.com/PastureStack/orchestration-engine/releases/download/v0.183.281/orchestration-engine-0.183.281.jar
2626
PLATFORM_COMPAT_JAR_SHA256: da2a8a51562ed16e296f7e29e99482bb44042ff0834cca679bbe01d951ba1682
2727

@@ -69,7 +69,7 @@ jobs:
6969
}
7070
7171
run_ci
72-
artifact="dist/artifacts/compose-executor-0.14.39-linux-amd64.gz"
72+
artifact="dist/artifacts/compose-executor-0.14.40-linux-amd64.gz"
7373
test -s "$artifact"
7474
cp "$artifact" /tmp/compose-executor-first.gz
7575
@@ -81,7 +81,7 @@ jobs:
8181
mkdir -p evidence/product
8282
gzip -cd "$artifact" > evidence/product/compose-executor
8383
chmod +x evidence/product/compose-executor
84-
evidence/product/compose-executor --version | grep -F '0.14.39' >/dev/null
84+
evidence/product/compose-executor --version | grep -F '0.14.40' >/dev/null
8585
cp "$artifact" evidence/
8686
(
8787
cd evidence

‎COMPATIBILITY.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
The migration preserves Docker Compose parsing, stack event names, generated stack fields, service labels, catalog question data, upgrade and rollback payloads, and compatibility API behavior.
44

5+
The v0.14.40 candidate adds one server-private mode,
6+
`compose-executor.real --catalog-launch-preview`, with fixed inspect/finalize
7+
JSON contracts and bounded stdin/stdout. Existing parser, conversion, service
8+
factory, launch-config and secret-reference owners produce the exact graph;
9+
there is no second renderer or model-supplied URL/file/command interface.
10+
Typed templates use the reviewed bounded subset and freeze the approved bytes.
11+
12+
Only a trusted Engine event may carry `catalogLaunchDelegation`. Its event-local
13+
client uses the fixed delegation header for schema/read/transform/child calls;
14+
missing or invalid proof cannot fall back to service FULL. Control progress and
15+
replies retain the original trusted service channel, and unmarked ordinary
16+
events retain their original client and API behavior. The Engine must recheck
17+
the original actor/Key, source/plan/Stack/event/graph, destination create/update
18+
and references on each request. Completion needs its committed ledger, full
19+
intended links and normal deployment readiness, not only a Stack reply.
20+
21+
KVM, unsupported provider/host/build/file/dependency/network graphs, external or
22+
cross-Stack links and unsupported typed inputs fail closed in this private mode;
23+
they remain pending typed adapters rather than completed UI features. Component
24+
source/unit and official CI evidence do not establish matching Server runtime.
25+
526
The archived external Compose implementation is no longer a build dependency. The conversion, collection-copy, logger, and Compose configuration-hash contracts still required by this compatibility client are implemented locally and covered by behavior tests. This does not expand or silently reinterpret the accepted Compose document format.
627

728
`platform-compose.yml`, `PLATFORM_URL`, `PLATFORM_ACCESS_KEY`, and `PLATFORM_SECRET_KEY` are preferred. The historical companion filename, legacy CLI alias, `RANCHER_*` and `CATTLE_*` variables, `io.rancher.*` labels, generated `RancherCompose` and `RancherClient` types, legacy service-image identifiers, and vendored `github.com/rancher/*` paths remain only as wire, data, or dependency contracts.

‎README.md‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ PastureStack is an independent community effort to preserve, audit, and moderniz
1010

1111
The maintained source uses Ubuntu 26.04, Go 1.27.3, and Python 3.14.9 for integration-source verification. Ubuntu build packages, the base image, and Go archives are fixed by snapshot, exact version, and SHA-256. Go dependencies are managed by `go.mod` and a reproducible module vendor tree. AWS access uses AWS SDK for Go v2; container structures use the maintained Moby API module; the command parser uses urfave/cli v3. The abandoned AWS SDK v1, Rancher generated-client modules, event-subscriber module, `docker/libcompose`, and other GOPATH-era dependencies have been removed. The small Rancher 1.6 protocol boundary retained by this compatibility CLI is maintained and tested locally. The Dapper builder does not embed a Docker CLI or mount a Docker socket; Docker is required only on the host to create and run the isolated builder. The maintained binaries are `pasturestack-compose` and `compose-executor`; `platform-compose.yml` is the preferred companion file. Releases are built and verified from the preserved source history; this repository does not enable automated deployment.
1212

13+
The current published component is
14+
[v0.14.39](https://github.com/PastureStack/compose-cli/releases/tag/v0.14.39).
15+
The **v0.14.40** verification candidate adds the private two-phase Catalog launch
16+
materializer and event-local delegated execution. It shares normal service,
17+
volume, launch-config, secret-reference, sidekick and hash owners; ordinary CLI
18+
and executor calls without a delegation marker keep their existing behavior.
19+
See [the owner contract](docs/catalog-launch-owner.md) and
20+
[candidate release note](docs/releases/compose-executor-0.14.40.md).
21+
22+
The matching Engine owns the original actor/Key ceiling, human approval,
23+
encrypted inputs, exact graph and child-request ledger. Neither a component
24+
build nor a Stack reply proves workload success. This candidate is unpublished;
25+
official gates and complete matching Server runtime acceptance are pending.
26+
1327
## Configuration
1428

1529
Use `PLATFORM_URL`, `PLATFORM_ACCESS_KEY`, and `PLATFORM_SECRET_KEY`. Historical `RANCHER_*` and `CATTLE_*` settings remain compatibility fallbacks. Operator messages support `PASTURESTACK_LOCALE=en-US` and `zh-TW`.
@@ -31,7 +45,8 @@ publishes `compose-executor-0.14.39-linux-amd64.gz`. Published tags and assets
3145
remain immutable; a later recipe or documentation commit does not replace
3246
their bytes or change their producer identity.
3347

34-
Set `VERSION_OVERRIDE=0.14.39` for this recipe. Source changes alone are not
48+
Set `VERSION_OVERRIDE=0.14.40` to verify this candidate; rebuild the published
49+
release only from its numeric tag and original recipe. Source changes alone are not
3550
publication proof: verify the official release assets and their SHA-256.
3651
Component publication and matching Server acceptance are separate evidence
3752
boundaries. The Python integration suite must run against an
@@ -66,6 +81,5 @@ proof; this is not a kernel or runtime exemption. Ubuntu security micro-updates
6681
apply only to already-installed packages without changing the Python ABI.
6782

6883
The focused `Builder full-severity security` workflow validates builder-only
69-
maintenance without rebuilding or republishing the unchanged `v0.14.39`
70-
runtime. Raw distro pip metadata is retained alongside the actual replaced seed
84+
maintenance without rebuilding or replacing published runtime assets. Raw distro pip metadata is retained alongside the actual replaced seed
7185
paths, wheel hash, and existing narrow pip-vendor VEX evidence.

‎catalog_launch_main_test.go‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"os/exec"
7+
"strings"
8+
"testing"
9+
10+
"github.com/PastureStack/compose-cli/cataloglaunch"
11+
)
12+
13+
func TestCatalogLaunchCommandOmitsPrivateParserDiagnostics(t *testing.T) {
14+
if os.Getenv("PST_CATALOG_LAUNCH_TEST_PROCESS") == "1" {
15+
os.Args = []string{"compose-executor", cataloglaunch.Command}
16+
main()
17+
return
18+
}
19+
command := exec.Command(os.Args[0], "-test.run=^TestCatalogLaunchCommandOmitsPrivateParserDiagnostics$")
20+
command.Env = append(os.Environ(), "PST_CATALOG_LAUNCH_TEST_PROCESS=1")
21+
command.Stdin = strings.NewReader(`{"name":"chosen","dockerCompose":"private-parser-answer-marker"}`)
22+
var stdout, stderr bytes.Buffer
23+
command.Stdout = &stdout
24+
command.Stderr = &stderr
25+
if command.Run() == nil {
26+
t.Fatal("malformed input succeeded")
27+
}
28+
if stderr.Len() != 0 || strings.Contains(stdout.String(), "private-parser") || strings.TrimSpace(stdout.String()) != `{"code":"CatalogLaunchUnsupported"}` {
29+
t.Fatal("private diagnostics reached process output")
30+
}
31+
}

‎cataloglaunch/finalize_test.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cataloglaunch
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
func TestCatalogFinalizeRejectsUnknownOrMissingTransformAndUnresolvedReferences(t *testing.T) {
9+
input := exampleInput()
10+
input.Phase = "finalize"
11+
input.TransformedLaunchConfigs = map[string]json.RawMessage{"app": json.RawMessage(`{"imageUuid":"docker:busybox:1.37.0","logConfig":{"config":{}}}`)}
12+
result, err := Prepare(input)
13+
if err != nil || result.Phase != "finalize" || len(result.ServiceRequests) != 1 || result.ServiceRequests[0].ResourceType != "service" {
14+
t.Fatal("finalize failed")
15+
}
16+
for _, change := range []func(*Input){
17+
func(i *Input) { i.TransformedLaunchConfigs["foreign"] = json.RawMessage(`{}`) },
18+
func(i *Input) { delete(i.TransformedLaunchConfigs, "app") },
19+
func(i *Input) { i.TransformedLaunchConfigs["app"] = json.RawMessage(`null`) },
20+
func(i *Input) { i.ResolvedSecrets = map[string]string{"unrequested": "1se7"} },
21+
func(i *Input) { i.Phase = "inspect" },
22+
} {
23+
copy := input
24+
copy.TransformedLaunchConfigs = map[string]json.RawMessage{"app": input.TransformedLaunchConfigs["app"]}
25+
change(&copy)
26+
if _, err := Prepare(copy); err == nil {
27+
t.Fatal("unbound finalize input admitted")
28+
}
29+
}
30+
input.RancherCompose = "version: '2'\nservices:\n app:\n secrets:\n - chosen\n"
31+
if _, err := Prepare(input); err == nil {
32+
t.Fatal("unresolved secret accepted")
33+
}
34+
}

0 commit comments

Comments
 (0)