Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
kind: Changed
body: Record complete isolated Python interpreter compatibility facts before resolving packages.
29 changes: 15 additions & 14 deletions docs/.review/APT_PROVIDER.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
---
artifact: swe-design-review-attestation
schema_version: 2
scope_key: bd8b79396ed3ab3d8e062279e409430bcf54daf5acf561e1b5d63f4302f9edc4
scope: {"kind": "path", "primary_target": "docs/APT_PROVIDER.md", "repository": "/home/omry/dev/reploy", "selector": "docs/APT_PROVIDER.md"}
review_content_identity_sha256: 3cb75520f49dee8255a7bc807e55670c1c59c75981b5753cb3c94ad29749b593
target_content_identity_sha256: 5c8bcb33496743ccef81f81684cf38482d1a9b225e096f47b328b95ea049a68a
baseline_content_identity_sha256: 9f98a355bf1d27e1c417bb5080a2f86eda476110f89ccf264d5ba02f7ee8d13b
target_documents: [{"path": "docs/APT_PROVIDER.md", "repository": "/home/omry/dev/reploy", "sha256": "be2008134ae557cf275893325fcd7caae7588ced609c5dc05bf142ad0a6aa60c"}]
baseline_documents: [{"path": "docs/BLUEPRINT_ENVIRONMENT_MODEL.md", "repository": "/home/omry/dev/reploy", "sha256": "8969ffde4d2f20d2e02fb530d0e9688ed63d9dd8962a9a700473d4a6a54e7ef4"}]
document_repository: "/home/omry/dev/reploy"
schema_version: 4
scope_key: 65b7fe1d45c88c7805c4b9fcd8ce6d6d69dc0e4b962ecdbf253f7e8a872b4662
scope: {"kind": "path", "primary_target": "docs/APT_PROVIDER.md", "repository": ".", "selector": "docs/APT_PROVIDER.md"}
review_content_identity_sha256: 5c23192cdcbc74302da21994a357b4fe52adee05272cafdf1f4316d69cf29637
target_content_identity_sha256: 01b5c3c3907d236c5e62773a80426585be85e44b1d86518411b1efe555a1ca15
baseline_content_identity_sha256: 58468667dc4b7ccc1b53ece6069a0c768862bc3447a74f838ba9c1edfb6a12c9
target_documents: [{"path": "docs/APT_PROVIDER.md", "repository": ".", "sha256": "b6d439338beb432953f9dae5a00dda7db78340c2309778b44ce0d920b8e873d3"}]
baseline_documents: [{"path": "docs/BLUEPRINT_ENVIRONMENT_MODEL.md", "repository": ".", "sha256": "9c9f8af518952cdea721ebdaea548930ee5456833d618eb54735b2c0431badd3"}]
design_dependency_documents: [{"path": "docs/BLUEPRINT_ENVIRONMENT_MODEL.md", "repository": ".", "sha256": "9c9f8af518952cdea721ebdaea548930ee5456833d618eb54735b2c0431badd3"}]
document_repository: "."
document_path: "docs/APT_PROVIDER.md"
document_revision_provenance: "a13e17402b578d5f6c6922ac82773ad2f116237f"
document_sha256: be2008134ae557cf275893325fcd7caae7588ced609c5dc05bf142ad0a6aa60c
document_revision_provenance: "9affd2736d3767b4ea9847fb76585e218c805470"
document_sha256: b6d439338beb432953f9dae5a00dda7db78340c2309778b44ce0d920b8e873d3
verdict: clean
attested_at: 2026-08-22T19:51:20Z
attested_at: 2026-09-15T07:02:11Z
---
<!-- swe-design-review-attestation:v2 -->
<!-- swe-design-review-attestation:v4 -->

# SWE design-review attestation

Review freshness is determined by the target and baseline document bytes
listed in the version-2 header. Revisions are provenance only.
listed in the version-4 header. Revisions are provenance only.

## Durable review state

Expand Down
29 changes: 15 additions & 14 deletions docs/.review/APT_PROVIDER_DETAIL_DESIGN.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
---
artifact: swe-design-review-attestation
schema_version: 2
scope_key: dd04dd2c2041a5035ffd15d245763f28fd28d9c4d7c4e83ca3a0cb45f092a863
scope: {"kind": "path", "primary_target": "docs/APT_PROVIDER_DETAIL_DESIGN.md", "repository": "/home/omry/dev/reploy", "selector": "docs/APT_PROVIDER_DETAIL_DESIGN.md"}
review_content_identity_sha256: 8ba6a9a8660c148eaa26c1de35dd900e98f0d6e274c9cfb4d0b1036fb17f56ff
target_content_identity_sha256: e93421059c4ba62f015aa08c248901201e6f091a26fdee58614dbf6e684456bb
baseline_content_identity_sha256: 3f6f4ef49e1b4270a8aefc5120f8d86e3837e74390ea648dc09505d2047f53f4
target_documents: [{"path": "docs/APT_PROVIDER_DETAIL_DESIGN.md", "repository": "/home/omry/dev/reploy", "sha256": "14e7809c811f921168594cf40aae0c2e9d480016352d59d74fcaa8030c12bdda"}]
baseline_documents: [{"path": "docs/APT_PROVIDER.md", "repository": "/home/omry/dev/reploy", "sha256": "be2008134ae557cf275893325fcd7caae7588ced609c5dc05bf142ad0a6aa60c"}, {"path": "docs/BLUEPRINT_ENVIRONMENT_MODEL.md", "repository": "/home/omry/dev/reploy", "sha256": "8969ffde4d2f20d2e02fb530d0e9688ed63d9dd8962a9a700473d4a6a54e7ef4"}]
document_repository: "/home/omry/dev/reploy"
schema_version: 4
scope_key: 084819e0d337bf7258eb3a959a310688f60b87260bbfd23042ea7eae5370e31b
scope: {"kind": "path", "primary_target": "docs/APT_PROVIDER_DETAIL_DESIGN.md", "repository": ".", "selector": "docs/APT_PROVIDER_DETAIL_DESIGN.md"}
review_content_identity_sha256: a024823502d76bc7edcd697f32274800c1d03f1473f09e70cd41e2c60a071d85
target_content_identity_sha256: 740b68117c56cbfa733e0751383b9a0cbb19f8865892c8d989c3c6b36964831f
baseline_content_identity_sha256: d6436371829ec92e56af5bfa51c9de8ca604088f352b2b6322d444cf11f86164
target_documents: [{"path": "docs/APT_PROVIDER_DETAIL_DESIGN.md", "repository": ".", "sha256": "dbfc92872e7e3a456797eda67e37732f11e3299a9ee7b0543850db18f223b676"}]
baseline_documents: [{"path": "docs/APT_PROVIDER.md", "repository": ".", "sha256": "b6d439338beb432953f9dae5a00dda7db78340c2309778b44ce0d920b8e873d3"}, {"path": "docs/BLUEPRINT_ENVIRONMENT_MODEL.md", "repository": ".", "sha256": "9c9f8af518952cdea721ebdaea548930ee5456833d618eb54735b2c0431badd3"}]
design_dependency_documents: [{"path": "docs/APT_PROVIDER.md", "repository": ".", "sha256": "b6d439338beb432953f9dae5a00dda7db78340c2309778b44ce0d920b8e873d3"}, {"path": "docs/BLUEPRINT_ENVIRONMENT_MODEL.md", "repository": ".", "sha256": "9c9f8af518952cdea721ebdaea548930ee5456833d618eb54735b2c0431badd3"}]
document_repository: "."
document_path: "docs/APT_PROVIDER_DETAIL_DESIGN.md"
document_revision_provenance: "a13e17402b578d5f6c6922ac82773ad2f116237f"
document_sha256: 14e7809c811f921168594cf40aae0c2e9d480016352d59d74fcaa8030c12bdda
document_revision_provenance: "9affd2736d3767b4ea9847fb76585e218c805470"
document_sha256: dbfc92872e7e3a456797eda67e37732f11e3299a9ee7b0543850db18f223b676
verdict: clean
attested_at: 2026-08-22T19:52:51Z
attested_at: 2026-09-15T07:19:52Z
---
<!-- swe-design-review-attestation:v2 -->
<!-- swe-design-review-attestation:v4 -->

# SWE design-review attestation

Review freshness is determined by the target and baseline document bytes
listed in the version-2 header. Revisions are provenance only.
listed in the version-4 header. Revisions are provenance only.

## Durable review state

Expand Down
23 changes: 16 additions & 7 deletions docs/APT_PROVIDER.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
status: Active
updated: 2026-08-22
updated: 2026-09-15
summary: Subdesign for closed .deb package layers, provider outputs, and Python runtime dependencies.
refines: docs/BLUEPRINT_ENVIRONMENT_MODEL.md
---
Expand Down Expand Up @@ -1502,16 +1502,25 @@ does not enumerate link groups, choose an alternative, or accept an unregistered
alternatives link. Both commands run inside the already-required consuming or
final-validation container.

The consuming provider then applies semantic validation as the first operation
in its disposable bundle resolver. For Python, it can execute:
The consuming provider applies semantic validation as the first operation in
its disposable bundle resolver. For Python, it invokes the selected absolute
interpreter with a fixed isolated command prefix:

```text
/usr/bin/python3 -I -S -c
import sys; print(".".join(map(str, sys.version_info[:3])))
/usr/bin/python3 -I -c <provider-owned probe> <tested-tags-json> <architecture>
```

It then checks the requested Python version constraint and separately verifies
that the interpreter can create a virtual environment.
The provider-controlled environment and working directory remain in force.
System-site initialization stays enabled so the probe and ordinary
`python -I -m pip` resolver import the same selected interpreter's installed
pip tag generator, while isolated mode excludes current-directory, user-site,
and environment-controlled import shadows. The bounded tested-tag set and
selected architecture are validated data, never executable source. The strict
result records the complete Python version, implementation, ABI, libc identity
and release, the exact tested tags, and their compatible subset. The probe does
no network work, artifact acquisition, or wheel parsing. The provider then
checks the requested Python version constraint and separately verifies that the
interpreter can create a virtual environment.

Package and logical versions are distinct:

Expand Down
28 changes: 18 additions & 10 deletions docs/APT_PROVIDER_DETAIL_DESIGN.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
status: Active
updated: 2026-08-22
updated: 2026-09-15
summary: Implemented local-Docker design for the provider graph, APT/dpkg bundles, generated image layers, and cross-provider executable consumption.
implements: docs/APT_PROVIDER.md
---
Expand Down Expand Up @@ -1603,8 +1603,8 @@ Environment names match `[A-Za-z_][A-Za-z0-9_]*`, are unique and sorted, and
the profile always sets `InheritNone`. `Umask` is four lowercase octal digits.
Secrets are not transaction fields.

The initial Python transaction uses recipe `python-materialize-v1` and child
environment `python-v1` with `InheritNone=true`, `Umask=0022`, and no variables.
The Python transaction uses recipe `python-materialize-v2` and child environment
`python-v2` with `InheritNone=true`, `Umask=0022`, and no variables.
It runs as numeric root from `/` with `NetworkPolicy=none`. Its read-only script
mount is keyed by the provider-owned script digest; its read-only wheel mount is
keyed by the complete resolved-bundle identity. The selected interpreter is a
Expand Down Expand Up @@ -1960,13 +1960,21 @@ commands.
For the built-in `python3` mapping, the APT provider publishes only the
singleton candidate path and mapping provenance. The consuming Python node
validates it as the first step inside its existing bundle-resolver container,
before network or source work. Its typed adapter uses fixed `-I -S -c` arguments
and provider-owned code to require a Python implementation and parse its actual
version, ABI, and platform. A missing path, non-Python executable, or
unparseable version fails with the mapping identity and an example using
`exports.python.executable`; the resolver performs no path search or fallback
discovery. The materializer proves `venv` support by creating the real component
venv; it does not create a disposable venv first.
before network or source work. Its typed adapter invokes the selected absolute
interpreter with fixed `-I -c` arguments under the provider-owned clean
environment and working directory. System-site initialization remains enabled
so the probe can import the selected interpreter's installed
`pip._vendor.packaging.tags`; isolated mode still excludes current-directory,
user-site, and environment-controlled import shadows. A bounded, sorted, unique
tested-tag set and selected architecture are passed as validated data, never
interpolated into the provider-owned probe. The strict V2 result records the
complete Python version, implementation, ABI, libc identity and release, the
exact tested tags, and their compatible subset. The probe performs no path
search, fallback discovery, network access, acquisition, or wheel parsing. A
missing path, non-Python executable, missing installed pip, or malformed facts
fails with the mapping identity and an example using
`exports.python.executable`. The materializer proves `venv` support by creating
the real component venv; it does not create a disposable venv first.

Python then resolves/builds wheels in a disposable resolver container based on
that exact upstream prefix. An exact complete Python bundle hit skips the
Expand Down
36 changes: 24 additions & 12 deletions internal/dockerdeploy/full_validation_python_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,46 +63,58 @@ func validatePythonProfileObservation(
return providers.ExecutableEvidence{}, fmt.Errorf("validate Python image profile interpreter before execution: %w", err)
}

version, err := session.runPythonInterpreterInspection(ctx, locked.InvocationPath)
lockedFacts, err := pythonprovider.DecodeInterpreterFactsV2(locked.Facts)
if err != nil {
return providers.ExecutableEvidence{}, fmt.Errorf("validate Python image profile interpreter facts: %w", err)
}
facts, err := session.runPythonInterpreterInspection(ctx, locked.InvocationPath, lockedFacts.TestedTags)
if err != nil {
return providers.ExecutableEvidence{}, fmt.Errorf(
"Python interpreter at %s is not usable; configure an explicit Python interpreter executable path: %w",
locked.InvocationPath, err,
)
}
matches, err := pythonprovider.InterpreterVersionSatisfies(requirement.VersionConstraint, version)
matches, err := pythonprovider.InterpreterVersionSatisfies(requirement.VersionConstraint, facts.Version)
if err != nil {
return providers.ExecutableEvidence{}, err
}
if !matches {
return providers.ExecutableEvidence{}, fmt.Errorf(
"Python interpreter at %s has version %s, which does not satisfy %q; configure an explicit Python interpreter executable path",
locked.InvocationPath, version, requirement.VersionConstraint,
locked.InvocationPath, facts.Version, requirement.VersionConstraint,
)
}
fresh, err := ExecutableEvidenceFromProbe(interpreterObservation, ProbeExecutableBinding{
Requirement: &requirement, Output: locked.Output,
Facts: pythonprovider.CanonicalInterpreterFactsV1(version),
Facts: pythonprovider.CanonicalInterpreterFactsV2(facts),
})
if err != nil {
return providers.ExecutableEvidence{}, fmt.Errorf("validate Python image profile interpreter: %w", err)
}
return fresh, nil
}

func (session *ImageValidationSession) runPythonInterpreterInspection(ctx context.Context, interpreterPath string) (string, error) {
func (session *ImageValidationSession) runPythonInterpreterInspection(
ctx context.Context,
interpreterPath string,
testedTags []string,
) (pythonprovider.InterpreterInspectionFactsV2, error) {
if session == nil || session.closed {
return "", fmt.Errorf("image validation session is not open")
return pythonprovider.InterpreterInspectionFactsV2{}, fmt.Errorf("image validation session is not open")
}
if ctx == nil {
return "", fmt.Errorf("image validation Python inspection context is required")
return pythonprovider.InterpreterInspectionFactsV2{}, fmt.Errorf("image validation Python inspection context is required")
}
if err := ctx.Err(); err != nil {
return "", fmt.Errorf("run image validation Python inspection: %w", err)
return pythonprovider.InterpreterInspectionFactsV2{}, fmt.Errorf("run image validation Python inspection: %w", err)
}
targetArchitecture, err := pythonInspectionArchitectureV2(session.descriptor.Platform)
if err != nil {
return pythonprovider.InterpreterInspectionFactsV2{}, err
}
inspection, err := pythonprovider.InterpreterInspectionArgv(interpreterPath)
inspection, err := pythonprovider.InterpreterInspectionArgv(interpreterPath, testedTags, targetArchitecture)
if err != nil {
return "", err
return pythonprovider.InterpreterInspectionFactsV2{}, err
}
args := []string{
"exec", "--user", "0:0", "--workdir", "/", session.containerName,
Expand All @@ -116,7 +128,7 @@ func (session *ImageValidationSession) runPythonInterpreterInspection(ctx contex
if err := session.runDockerCommand(CommandSpec{Name: "docker", Args: args}, RunOptions{
Context: ctx, Stdout: &stdout, Stderr: &stderr,
}); err != nil {
return "", imageValidationCommandError("Python interpreter inspection", session.descriptor.Platform.Canonical, stderr.String(), err)
return pythonprovider.InterpreterInspectionFactsV2{}, imageValidationCommandError("Python interpreter inspection", session.descriptor.Platform.Canonical, stderr.String(), err)
}
return pythonprovider.ParseInterpreterInspectionOutput(stdout.Bytes())
return pythonprovider.ParseInterpreterInspectionOutput(stdout.Bytes(), testedTags)
}
21 changes: 12 additions & 9 deletions internal/dockerdeploy/full_validation_python_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ func TestValidatePythonProfileObservationRunsFixedInspectionInHeldSession(t *tes
input := completion.Validation.Final
profile := input.Profiles[0]
locked := profile.SelectedExecutables[0]
version, ok := locked.Facts.Value["version"].(string)
if !ok {
t.Fatalf("locked interpreter facts = %#v", locked.Facts)
lockedFacts, err := pythonprovider.DecodeInterpreterFactsV2(locked.Facts)
if err != nil {
t.Fatal(err)
}
launcher := directExecutableObservation("shared_launcher", pythonLauncherPath)
interpreter := directExecutableObservation("shared_interpreter", locked.InvocationPath)
Expand All @@ -29,18 +29,22 @@ func TestValidatePythonProfileObservationRunsFixedInspectionInHeldSession(t *tes
commands := []CommandSpec{}
runImageValidationFollowupCommand = func(spec CommandSpec, options RunOptions) error {
commands = append(commands, spec)
_, _ = options.Stdout.Write([]byte(version + "\n"))
_, _ = options.Stdout.Write(pythonInspectionOutputFromFactsV2ForTest(t, locked.Facts))
return nil
}
session := &ImageValidationSession{descriptor: input.Image.Descriptor, containerName: "held-validation"}
fresh, err := validatePythonProfileObservation(context.Background(), session, profile, launcher, interpreter)
if err != nil {
t.Fatal(err)
}
if fresh.Facts.Value["version"] != version || fresh.Terminal.Size != "2" {
if fresh.Facts.Value["version"] != lockedFacts.Version || fresh.Terminal.Size != "2" {
t.Fatalf("fresh interpreter evidence = %#v", fresh)
}
inspection, err := pythonprovider.InterpreterInspectionArgv(locked.InvocationPath)
targetArchitecture, err := pythonInspectionArchitectureV2(input.Image.Descriptor.Platform)
if err != nil {
t.Fatal(err)
}
inspection, err := pythonprovider.InterpreterInspectionArgv(locked.InvocationPath, lockedFacts.TestedTags, targetArchitecture)
if err != nil {
t.Fatal(err)
}
Expand All @@ -61,15 +65,14 @@ func TestValidatePythonProfileObservationRejectsRequestDriftAndIncompatibleVersi
input := completion.Validation.Final
profile := input.Profiles[0]
locked := profile.SelectedExecutables[0]
version := locked.Facts.Value["version"].(string)
launcher := directExecutableObservation("shared_launcher", pythonLauncherPath)
interpreter := directExecutableObservation("shared_interpreter", locked.InvocationPath)
interpreter.Terminal.Size = "2"

previous := runImageValidationFollowupCommand
t.Cleanup(func() { runImageValidationFollowupCommand = previous })
runImageValidationFollowupCommand = func(_ CommandSpec, options RunOptions) error {
_, _ = options.Stdout.Write([]byte(version + "\n"))
_, _ = options.Stdout.Write(pythonInspectionOutputFromFactsV2ForTest(t, locked.Facts))
return nil
}
session := &ImageValidationSession{descriptor: input.Image.Descriptor, containerName: "held-validation"}
Expand All @@ -82,7 +85,7 @@ func TestValidatePythonProfileObservationRejectsRequestDriftAndIncompatibleVersi
}

runImageValidationFollowupCommand = func(_ CommandSpec, options RunOptions) error {
_, _ = options.Stdout.Write([]byte("0.0.0\n"))
_, _ = options.Stdout.Write(pythonInspectionOutputV2ForTest("0.0.0", nil, nil))
return nil
}
if _, err := validatePythonProfileObservation(context.Background(), session, profile, launcher, interpreter); err == nil || !strings.Contains(err.Error(), "does not satisfy") {
Expand Down
4 changes: 2 additions & 2 deletions internal/dockerdeploy/full_validation_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func TestProviderFullImageValidationRunnerValidatesPythonProfileAndOutputFromOne
observations = append(observations, directExecutableObservation(inspection.ID, inspection.InvocationPath))
}
response := mustCanonicalProbeResponse(t, probe.ResponseV1{Schema: probe.ResponseSchemaV1, Observations: observations})
version := input.Profiles[0].SelectedExecutables[0].Facts.Value["version"].(string)
inspectionOutput := pythonInspectionOutputFromFactsV2ForTest(t, input.Profiles[0].SelectedExecutables[0].Facts)
commands := []CommandSpec{}
runImageValidationFollowupCommand = func(spec CommandSpec, options RunOptions) error {
commands = append(commands, spec)
Expand All @@ -159,7 +159,7 @@ func TestProviderFullImageValidationRunnerValidatesPythonProfileAndOutputFromOne
_, _ = options.Stdout.Write(response)
return nil
}
_, _ = options.Stdout.Write([]byte(version + "\n"))
_, _ = options.Stdout.Write(inspectionOutput)
return nil
}
profiles, outputs, err := (ProviderFullImageValidationRunner{}).Run(context.Background(), input)
Expand Down
Loading
Loading