diff --git a/.changes/unreleased/portable-tool-definition-design.yaml b/.changes/unreleased/portable-tool-definition-design.yaml new file mode 100644 index 00000000..3bd0fb78 --- /dev/null +++ b/.changes/unreleased/portable-tool-definition-design.yaml @@ -0,0 +1,2 @@ +kind: Docs +body: Document the accepted portable-tool composition, targeting, acquisition, identity, validation, and embedded-catalog blueprint model. diff --git a/docs/BLUEPRINT_ENVIRONMENT_MODEL.md b/docs/BLUEPRINT_ENVIRONMENT_MODEL.md index 04e29722..26326826 100644 --- a/docs/BLUEPRINT_ENVIRONMENT_MODEL.md +++ b/docs/BLUEPRINT_ENVIRONMENT_MODEL.md @@ -1,6 +1,6 @@ --- status: Active -updated: 2026-08-08 +updated: 2026-08-16 summary: Normative blueprint environment, workload, application, provider contribution, lifecycle, and Docker rendering model. supersedes: docs/CROSS_PLATFORM_INSTALL_LOCATIONS.md --- @@ -1000,6 +1000,94 @@ local source or upstream-version form that it cannot materialize. The same application/package ownership model can be extended to other build and packaging systems later. +## Proposed Embedded Built-In Tools + +This section describes the support surface proposed for the embedded +portable-tool implementation. The current blueprint schema does not yet accept +`packages.tools`; the example and behavior below become normative only when +that implementation lands. + +Until the portable-tool repository is implemented, Reploy will ship a +deliberately small catalog of reviewed tool definitions inside the binary. +These definitions will be versioned implementation data, not a general +repository or an extension point. Changing one will require a new Reploy +binary. Runtime tools will contribute their selected definition-closure digest +to provider identity. + +The concrete catalog structure, exact target tuple, acquisition composition, +and selected-closure identity are specified by the +[Portable Tool Definition Design](PORTABLE_TOOL_DEFINITION_DESIGN.md). This +section defines the proposed user-facing blueprint behavior and support +surface. + +The initial runtime tool will be Playwright 1.61.0 with its Python binding and +Chromium selection: + +```yaml +environment: + base: + image: python:3.13-slim-bookworm + exports: + python: + executable: /usr/local/bin/python + applications: + application: + packages: + tools: + # Proposed; not accepted by the current blueprint schema. + - tool: playwright + version: "1.61.0" + binding: python + select: [chromium] +``` + +`tool` and `select` will be required. When `version` is omitted, Reploy will +select the newest eligible upstream release and definition revision, matching +the repository-backed resolver; explicitly naming `version: "1.61.0"` as above +will constrain that selection. Because `python` is the only binding, Reploy +will infer it when `binding` is omitted; explicitly naming `binding: python` as +above will be equivalent. The embedded definition will declare the complete +Python requirement roots (`playwright==1.61.0`, `pyee>=13,<14`, and +`greenlet>=3.1.1,<4.0.0`), pin the exact Linux AMD64 Playwright wheel, and +record its bundled Node.js 24.17.0 and `playwright-core` +1.61.1-beta-1782139630000 constituents. The Python provider will resolve the +declared closure and reject a Playwright wheel whose filename, tags, size, or +SHA-256 digest differs from the definition. + +Each supported target will be exact to the platform, `/etc/os-release` `ID` and +`VERSION_ID`, native package architecture, and package manager. Reploy will not +infer a target from the image tag or merge package lists across OS generations. +Providers will select one exact target after observing the base image and retain +the selected definition closure in locked provider identity. Shared tool, +release, binding, and payload records may be reused explicitly; target leaves +will retain only the compatibility and package data specific to their exact OS +and architecture. + +The reviewed native Chromium dependencies will also be definition-owned. The +`chromium` selection will include Playwright's coupled full Chromium, Chromium +Headless Shell, and FFmpeg payloads. Reploy will acquire their exact revisions, +verify their sizes and SHA-256 digests, then materialize them with networking +disabled. Neither resolution nor materialization will invoke `playwright +install` or `playwright install-deps`. The final image will set +`PLAYWRIGHT_BROWSERS_PATH` to the Reploy-owned browser directory and disable +Playwright's browser download and garbage-collection behavior. The application +will continue to run as the configured non-root runtime user. + +This built-in definition will support Debian 12 (`bookworm`), Ubuntu 25.10 +(`questing`), and Ubuntu 26.04 LTS (`resolute`) on `linux/amd64`, with the +Python binding and `chromium` selection. Other versions, bindings, browsers, +operating systems, or architectures will fail before artifact acquisition. +Ubuntu targets will own their `t64` package names independently from Debian. +The prebuilt Microsoft Playwright image will be neither required nor used. + +The existing local-source Java build requirement will become `tool:java==21` +and resolve definition revision 1 to Eclipse Temurin JDK `21.0.12+8` in the +embedded catalog for Debian 12, Debian 13, Ubuntu 25.10, and Ubuntu 26.04. Its +ownership behavior will remain unchanged: it will contribute the pinned JDK, +including `java` and `javac`, only to the isolated source builder and will not +add Java to the application runtime. Distribution-default Java packages are not +a fallback. + ## Possible Shape ```yaml diff --git a/docs/PORTABLE_TOOL_DEFINITION_DESIGN.md b/docs/PORTABLE_TOOL_DEFINITION_DESIGN.md new file mode 100644 index 00000000..52045ff1 --- /dev/null +++ b/docs/PORTABLE_TOOL_DEFINITION_DESIGN.md @@ -0,0 +1,1142 @@ +--- +status: Accepted +updated: 2026-08-16 +summary: Accepted composition, targeting, acquisition, identity, and validation model for proposed embedded portable-tool definitions. +refines: docs/REPOSITORY_DESIGN.md +--- + +# Portable Tool Definition Design + +## Status and Authority + +This document defines the accepted concrete portable-tool definition model for +the proposed embedded catalog. It refines the portable-tool contract in +`REPOSITORY_DESIGN.md` and the proposed built-in tool behavior in +`BLUEPRINT_ENVIRONMENT_MODEL.md`. Acceptance fixes the intended design; it does +not claim that the implementation is present in this revision. + +The immediate implementation scope is `tool:java` and `tool:playwright`. +Repository publication, TUF metadata, publisher authorization, and lifecycle +policy remain owned by `REPOSITORY_DESIGN.md`. The embedded catalog will be an +implementation bridge, but its definition boundaries are intended to carry +forward into published tool definitions. + +A separate implementation WIP uses flat, complete JSON files as a checkpoint; +those files are not the final schema described here or part of this design-only +revision. Reploy has not been released, so the migration does not need a +compatibility reader for that format. + +## Goals + +- Model a tool version independently from an operating-system release. +- Express support for an exact OS generation and architecture without copying + the complete tool definition into every target file. +- Allow native packages, pinned upstream artifacts, and ecosystem bindings to + participate in one explicitly composed tool. +- Make unsupported OS, architecture, binding, and selection combinations fail + before acquisition. +- Keep definition resolution deterministic, reviewable, lockable, and usable + without running upstream installer scripts. +- Make it easy to validate a tool on many base images using ordinary Reploy + blueprint fixtures. +- Keep the model open to package managers and distributions other than APT and + Debian-family systems. + +## Non-Goals + +- Defining the repository transport, trust, publication, or lifecycle protocol. +- Accepting third-party definition code or arbitrary installer scripts. +- Normalizing literal package names across distributions. +- Claiming architecture support merely because Reploy can build that OCI + architecture. +- General-purpose inheritance, templating, or conditional expressions inside + definition files. +- Designing every future tool category before Java and Playwright are complete. + +## Decision Summary + +1. A requested tool is identified by its tool name, upstream version, and + Reploy definition revision. The OS version is a target dimension, not the + tool version. +2. Support is declared for an exact target tuple: OS, OS generation, OCI + architecture, native package architecture, and package manager. +3. Every exact target tuple has a small target leaf file. Architecture remains + in that leaf because native packages and upstream artifacts are not + necessarily available on the same architectures. +4. Tool-wide, release-wide, binding, payload, and reusable native-package data + live in separate records. Target leaves compose them through explicit, + digest-checked references. +5. Composition is a closed graph. There is no implicit inheritance, overlay, + package fallback, or closest-version matching. +6. Tools may mix acquisition strategies. Prefer pinned upstream artifacts for + portable versioned payloads and native packages for system libraries or + distribution-coupled tools. +7. Locks and build identity cover the selected definition closure. Adding an + unrelated target must not invalidate materialization for an existing target. +8. A target is supported only after static validation and a real Reploy + integration test of the exact tuple and selected features. +9. Every acquired artifact is identified by its exact byte size and SHA-256 + digest. Locators and upstream provenance authorize where Reploy may obtain + those bytes, but do not replace content verification. +10. One artifact may declare an ordered, statically bounded mirror set. Reploy + automatically falls back between those locators under core network and + resource limits while requiring every mirror to produce the same bytes. + +## Terminology + +### Tool + +A stable user-facing capability such as `java` or `playwright`. The tool record +owns durable naming, summary, provenance, and documentation metadata. It does +not own an OS package list. + +### Release + +One exact upstream-facing tool version plus one Reploy definition revision. +For example, Playwright `1.61.0` revision `1` and Java `21` revision `1` are +different releases. A revision can correct acquisition, target, validation, or +documentation data without pretending the upstream version changed. + +### Target + +One exact execution environment described by: + +- OCI operating system and architecture; +- observed `/etc/os-release` `ID` and `VERSION_ID`; +- package-manager kind; +- package-manager-native architecture. + +For example, Ubuntu 26.04 on `linux/arm64` with APT architecture `arm64` is a +different target from Ubuntu 26.04 on `linux/amd64`, even when their package +names happen to be identical. + +### Binding + +An application-facing ecosystem interface to a shared tool payload, such as +the Python or Node binding for Playwright. A binding owns its ecosystem package +requirements, artifacts, compatibility constraints, and exports. Bindings do +not own browser or operating-system payloads. + +### Payload + +A versioned non-package component materialized by a reviewed Reploy primitive. +Examples include a Java runtime archive, Chromium, Chromium Headless Shell, and +FFmpeg. Payload records are exact to every dimension that changes their bytes, +including architecture. + +### Native Package Set + +A manager-typed set of root package requirements. A target may reference a +named package set when several exact targets truly use the same roots. The set +does not imply that those targets are otherwise equivalent. + +### Selected Closure + +The canonical resolved projections of the release contract and exact target, +plus the selected binding, selected payloads, native package sets, exports, and +probes that affect one resolved tool request. The contract projection contains +the selected context, resolved binding, selections, normalized parameter +values, and every contract field that governs their resulting behavior. The +target projection contains the exact target identity and all selected +materialization contributions, but +excludes validation-fixture and validation-profile references because they +authorize support rather than change built bytes. Both projections exclude +unselected availability. Records and contract options for unrelated targets, +bindings, or selections are outside the closure. + +## Support Unit + +The exact support claim is the following tuple: + +```text +(tool, upstream version, definition revision, context, + target OS, target OS version, OCI architecture, + package manager, native architecture, binding, selections, + normalized parameters) +``` + +The binding, selections, or parameters may be absent only when the release +contract permits that. A definition may advertise a tuple only when every +referenced artifact, binding, native package set, export, and probe is available +and validated for it. + +There is intentionally no tool-wide `supported_architectures` promise. The +supported architectures shown to users are derived from valid target leaves. +This prevents an AMD64-only wheel, browser build, or native package from being +mistaken for ARM64 support. + +## Definition Records + +### Tool Record + +The tool record contains only stable catalog metadata: + +- schema and qualified tool name; +- the immutable shared version scheme used to parse requirements and order + releases; +- summary, upstream project, source, and license references; +- documentation metadata; +- references to the available release manifests. + +The version scheme is one of Reploy's shared `semver`, `pep440`, `integer`, or +`opaque` schemes and cannot change for an existing qualified tool name. Generic +catalog resolution reads it from this record; it does not contain hard-coded +per-tool parsing or ordering rules. Java initially uses `integer`, while +Playwright uses `semver`. + +The tool record also carries an optional exact `default_version`. It is required +for `opaque`, must name one advertised eligible release, and is forbidden for +ordered schemes. An omitted opaque requirement normalizes to equality with this +coordinate. Ordered schemes continue to select their highest compatible release +when no version constraint is supplied. + +### Release Manifest + +The release manifest owns the published or embedded release coordinate and its +complete availability index: + +- tool identity, exact scheme-native tool version, and Reploy definition + revision; +- one digest-checked release-contract reference; +- the complete set of exact target-leaf references; +- exactly one artifact-source reference for every externally acquired artifact + content identity reachable from an advertised target; +- release-level provenance and validation-profile references. + +Adding or removing a target changes the manifest and requires a new immutable +definition revision. The manifest establishes release provenance and what the +release advertises, but it is deliberately outside selected-closure identity. +This is what permits an unchanged target closure to be reused across two +revisions while the lock still records which revision authorized it. + +### Release Contract + +The release contract owns behavior shared by all supported targets and stable +across definition revisions when that behavior has not changed: + +- allowed use contexts such as `build` or `runtime`; +- binding, selection, and typed-parameter schemas, including required/default + behavior; +- public executable and capability exports; +- environment variables and final-image placement rules; +- reviewed resolver primitive names; +- the canonical supported-Reploy version requirement; +- target-independent probes and other compatibility constraints. + +The supported-Reploy requirement uses Reploy's built-in SemVer requirement +grammar and is required for every release contract. During candidate filtering, +the running client's exact version must satisfy it and every named primitive +must be implemented by that client. An incompatible candidate is removed before +tool-version or definition-revision selection, allowing an older compatible +release to win before target/contribution traversal or acquisition. + +The contract does not enumerate targets and does not contain the Reploy +definition revision. The release manifest references the contract and target +leaves independently. Target leaves then reference the binding, payload, and +native-package records that form one supported closure. + +### Target Leaf + +A target leaf owns only data whose truth is specific to one OS generation and +architecture: + +- exact target identity and base-profile match fields; +- the native package manager and native architecture; +- unconditional native package-set references; +- the bindings, selections, and typed-parameter constraints available on that + target; +- unconditional architecture-specific payload references; +- a canonical target-specific contribution mapping for every advertised + binding; +- a canonical target-specific contribution mapping for every advertised + selection; +- target-specific exports or probes when the shared contract is insufficient; +- the integration-fixture and validation-profile references required to prove + the support claim. + +Two target leaves may reference the same immutable native package set or +payload record. They do not inherit from each other. If Ubuntu 25.10 and 26.04 +currently use the same package roots, each remains an independently validated +target and explicitly names the shared set. Either can later switch to a new +set without affecting the other. + +A target leaf's parameter constraints are keyed by a parameter declared in the +release contract. They may narrow that parameter's enumerated values or numeric +range, but cannot change its type, required/default behavior, or widen its +contract-level domain. Omitting a target constraint leaves the complete +contract-level domain available. Publication rejects a target whose narrowed +domain excludes the contract default. Resolution validates normalized +parameter values against both the contract schema and the selected target's +constraints before acquisition. + +The binding contribution mapping is keyed by symbols declared in the release +contract and advertised by the target. Every advertised binding has exactly one +entry; an unadvertised symbol cannot have one. Each entry references exactly one +binding contract and the target-compatible binding artifacts it selects, plus +any binding-specific native package-set references and export or probe values. +Binding inference chooses a public symbol first, and resolution traverses only +that entry. Record names and reverse artifact-to-contract references are never +used as selection conventions. + +The selection contribution mapping is keyed by symbols declared in the release +contract and advertised by the target. Every advertised selection has exactly +one entry; an unadvertised symbol cannot have one. Each entry contains exact +payload and native package-set references plus the export and probe values +contributed by that selection on this target. Resolution unions unconditional +target contributions with only the entries for normalized selected symbols, +then applies the ordinary deduplication and conflict rules. This makes coupled +payloads and selection-specific native roots deterministic without forcing +unselected contributions into the closure. + +### Binding Contract Record + +A binding contract owns ecosystem semantics shared across targets. For the +initial Playwright Python binding these include: + +- exact Python requirement roots; +- supported Python versions and wheel tags; +- bundled Node.js and `playwright-core` constituent metadata; +- the Playwright CLI export. + +### Binding Artifact Record + +A binding artifact record owns one exact platform-specific ecosystem artifact, +such as a Playwright Python wheel: + +- component name and exact ecosystem version; +- OCI platform, ecosystem compatibility tags, and any additional compatibility + fields; +- exact byte size and SHA-256 content digest; +- the reviewed resolver primitive and provider materialization metadata; +- the binding contract that consumes the artifact. + +This separation permits a binding contract to remain constant while its wheel +or other artifact differs by architecture. Like a payload record, an externally +acquired binding artifact has an exact content identity and is eligible for one +release-manifest source mapping. + +### Payload Record + +A payload record contains an exact immutable artifact and its materialization +contract: + +- component name, upstream version, and component revision; +- OCI platform and any additional compatibility fields; +- exact byte size and SHA-256 content digest; +- the reviewed resolver primitive and its materialization metadata; +- archive kind, expected inventory, and validated extraction limits; +- install directory, archive root, and executable or capability probes. + +The SHA-256 digest is the artifact identity and vetting anchor. The URL is only +a locator for bytes expected to have that identity. Content with a different +digest requires a new immutable definition revision even when its advertised +upstream version is unchanged. + +### Artifact Source Record + +An artifact source record contains retrieval metadata for one acquired artifact +content identity, whether owned by a binding artifact or payload record: + +- the expected SHA-256 content digest; +- one or more unique, ordered, credential-free HTTPS mirror URLs; +- upstream release, checksum, signature, or equivalent provenance references; +- source-specific diagnostics that do not affect materialized behavior. + +Source-record URLs are long-lived public locators. They must not contain URL +userinfo, a query string, or a fragment; expiring or signed URLs are therefore +not valid definition data. Redirect targets are transient transport data. Their +query strings, if any, are treated as sensitive and are never written to locks, +diagnostics, or provenance records. Operator-owned proxy credentials are also +never exposed to definitions or retained in tool diagnostics. + +The release manifest owns the source mapping; binding artifacts, payload +records, and target leaves do not reference source records. Changing a URL or +its provenance therefore requires a new immutable release manifest and +definition revision, but does not change selected-closure identity when the +expected artifact bytes and materialization contract are unchanged. Static +validation requires the mapping key, the source record's expected artifact +SHA-256, and the referenced artifact record's content SHA-256 to agree. The +source-record and artifact-record references are validated independently +against their respective canonical record digests. The artifact record's size +and resolver primitive govern every mapped locator. + +The lock records the authorizing source record and acquisition outcome. A +network acquisition records the successful declared source locator; redirect +hops are sanitized transport diagnostics rather than provenance locators. A +verified-cache hit records that no locator was contacted during the operation; +any retained original locator is labeled as historical object provenance rather +than the locator used by the current operation. + +Selections map to contributions through the selected target leaf. Playwright's +`chromium` entry, for example, contributes full Chromium, Chromium Headless +Shell, and FFmpeg as one coupled payload set plus any Chromium-specific native +package sets, exports, or probes. Contributions common to every request remain +unconditional target references rather than being copied into each selection +entry. + +### Native Package-Set Record + +A native package set contains: + +- package-manager kind; +- manager-specific root requirements; +- optional repository requirements already supported by that provider; +- manager-specific validation metadata. + +Package sets are reusable only through explicit references. They do not contain +OS matching expressions and cannot select themselves. + +### Integration Fixture Record + +A `portable-tool-integration-fixture-v1` record binds one exact target tuple to +a tagged base image, its platform-specific immutable image digest, and the +context, binding, selections, and normalized parameters that CI must exercise. +It owns no pass/fail result. Target leaves reference fixture records by +canonical digest. + +### Validation Profile Record + +A `portable-tool-validation-profile-v1` record names the reviewed +Reploy-owned validator and version for one tool release, its required probes, +and the requirement that materialization and validation run without network +access. Release manifests and target leaves reference profile records by +canonical digest. Placeholder or unresolved validation references are not +valid catalog data. + +These records describe required validation work. The resulting pass/fail +evidence remains external to definition identity as described below, avoiding +a cycle in which validating a definition changes the definition being +validated. + +## Catalog Layout + +The filesystem layout follows semantic ownership rather than placing every +definition in one flat directory. A representative layout is: + +```text +internal/toolcatalog/definitions/ + java/ + tool.json + versions/ + 21/ + revisions/ + 1/ + manifest.json + contract.json + sources/ + runtime-linux-amd64.json + runtime-linux-arm64.json + payloads/ + runtime-linux-amd64.json + runtime-linux-arm64.json + package-sets/ + debian-runtime-amd64.json + targets/ + debian/ + 12/ + amd64.json + arm64.json + ubuntu/ + 26.04/ + amd64.json + arm64.json + validation/ + fixtures/ + debian-12-amd64.json + ubuntu-26.04-amd64.json + profiles/ + default.json + playwright/ + tool.json + versions/ + 1.61.0/ + revisions/ + 1/ + manifest.json + contract.json + sources/ + python-linux-amd64.json + chromium-linux-amd64.json + bindings/ + python/ + contract.json + linux-amd64.json + linux-arm64.json + payloads/ + chromium/ + linux-amd64.json + linux-arm64.json + package-sets/ + debian-12-amd64.json + ubuntu-t64-amd64.json + targets/ + debian/ + 12/ + amd64.json + ubuntu/ + 25.10/ + amd64.json + 26.04/ + amd64.json + validation/ + fixtures/ + debian-12-amd64.json + ubuntu-25.10-amd64.json + ubuntu-26.04-amd64.json + profiles/ + default.json +``` + +The tree is organizational, not an inheritance mechanism. Every semantic edge +is an explicit record reference. A record is looked up and persisted by the +`(id, digest)` pair carried by that reference; relative filesystem ancestry does +not determine identity. The catalog may retain multiple canonical records with +the same semantic ID and different digests when different immutable release +revisions reference them. One exact pair must resolve to exactly one canonical +record. + +Architecture remains visible in target and artifact filenames because those +records make architecture-specific claims. It disappears from files whose +contents are architecture-independent. This is the intended balance between a +single oversized definition and complete per-target duplication. + +## Composition Rules + +1. A release manifest explicitly references one release contract, enumerates + its target records, and maps artifact content identities to source records. + The contract, targets, and payloads do not point back to the manifest or + select retrieval sources. +2. A target explicitly references every binding artifact through its binding + contribution mapping. It references every payload and native package set + that can participate in the target either unconditionally or through a + binding or selection contribution mapping. +3. Every reference includes or resolves to an expected content digest. +4. References cannot escape their embedded or published release namespace, + except that a tool record may index release manifests in release namespaces + beneath that same tool ID. This exception permits release discovery; it does + not permit a release graph to reference another release or tool. +5. Cycles, duplicate `(id, digest)` definitions, two digests for the same + record ID in one resolved release graph, and incompatible exact package + requirements are errors. The same record ID at different digests may coexist + in the catalog only when separate immutable release revisions select them. +6. There are no overlays. A child cannot add to, delete from, or override a + parent because there are no semantic parents. +7. There is no implicit fallback between OS versions, architectures, package + managers, bindings, payload variants, or acquisition strategies. +8. Reusable records are allowed only when their complete semantics are truly + identical. Sharing a package set does not share target validation evidence. +9. The selected closure is a canonical, order-independent union. Contributions + with the same semantic key deduplicate only when their complete canonical + value or referenced digest is identical. Otherwise resolution fails. +10. Semantic keys include provider requirement identity, artifact logical path, + artifact install destination, environment-variable name, executable or + capability export name, and probe identity. Payload-owned directory trees + may share an unowned parent but cannot overlap each other's owned paths. + Identical environment values and exports deduplicate; conflicting values or + destinations fail before acquisition. + +These rules make repetition an explicit authoring tradeoff without making the +resolved result depend on merge order. + +Mirror failover is not semantic fallback under rule 7. Every mirror in one +source record is authorized only to supply the same size- and checksum-identified +artifact through the same resolver primitive. + +## Canonical Encoding and Structural Limits + +Every record is strict UTF-8 JSON. Schema v1 uses the JSON Canonicalization +Scheme defined by RFC 8785 for record identity. Parsing rejects invalid UTF-8, +duplicate object member names, and values that cannot be represented by the +schema before semantic decoding. Publication emits canonical bytes; clients +compute record digests from the same canonical representation so alternate +whitespace or member ordering cannot create a second identity. + +All portable-tool record schemas share one versioned canonical identity domain. +For a schema-normalized record value `R`, its record digest is exactly: + +`"sha256:" + lowercase_hex(SHA-256(UTF-8("reploy:portable-tool-record:portable-tool-record-v1") || 0x00 || canonical-json-v1(R)))` + +Here `canonical-json-v1` is the RFC 8785 encoding above, restricted by these +schemas to objects, arrays, strings, booleans, and null; schema integers are +canonical decimal strings rather than JSON numbers. The record's public +`schema` field remains inside `R`. Publishers and clients use the fixed +`portable-tool-record` kind and `portable-tool-record-v1` identity-schema tokens +for every portable-tool record and do not substitute the public record schema +for either token. Digest output is exactly `sha256:` followed by 64 lowercase +hexadecimal characters. + +Core schema policy also places non-raiseable limits on individual and aggregate +definition bytes, record count, reference-edge count and depth, string and +array sizes, and selected-closure contributions. Definitions cannot raise those +limits. Publication and consumption apply the same versioned limits before +allocating or traversing the complete graph, preventing an authenticated but +pathological definition from exhausting client or repository resources. + +## Acquisition Model + +A tool may combine three reviewed sources: + +### Pinned Upstream Artifacts + +Use exact upstream artifacts for portable, versioned payloads when the upstream +project publishes suitable platform builds. Reploy downloads them during the +networked acquisition phase, verifies their size and digest, and materializes +them offline through a named primitive. + +Definition authoring obtains the artifact from its reviewed upstream source, +records its exact size and SHA-256 digest, and retains the available checksum, +signature, release-manifest, or equivalent upstream provenance as review +evidence in its source record. Definition review approves those exact bytes. At +resolution time, Reploy obtains locators from the selected release manifest, +streams the download into its content-addressed artifact store under an enforced +byte limit, and accepts it only after both size and digest match the payload +record. A mismatch is discarded and never reaches a resolver or materializer. + +The downloader's network rules protect Reploy from unsafe retrieval behavior; +they do not replace content verification. Retrieval uses HTTPS, definitions +cannot supply credentials or arbitrary headers, redirects are bounded and +revalidated, and local, loopback, link-local, private, and other non-public +destinations are rejected. Before each connection, Reploy resolves the +hostname, rejects the complete answer if any address is not globally routable, +and pins the connection to a validated address while retaining the hostname for +TLS certificate validation. Redirects cannot downgrade HTTPS or carry +credentials; every permitted redirect hop is independently resolved, +validated, and pinned under the same policy. This prevents a second DNS lookup +or redirect from changing a previously validated public destination into an +internal one. + +Proxy and credential policy is operator-owned, must preserve the same +destination restrictions, and cannot be changed by a definition. A reviewed +resolver primitive may narrow the allowed URL shape for its upstream, but +Reploy does not need a compiled per-tool origin allowlist to establish artifact +identity. + +### Mirror Failover + +The consumer checks its verified content-addressed cache first. When acquisition +is necessary, it tries the source record's mirrors in their declared order under +one fixed, bounded retry policy. A transport failure, timeout, non-success HTTP +response, rejected redirect, size mismatch, or SHA-256 mismatch discards any +partial bytes and advances to the next mirror. Every mirror and redirect is +subject to the same network policy and the same expected size and digest. + +Core policy places non-raiseable limits on the number of mirrors, attempts per +mirror, aggregate attempts, aggregate downloaded bytes, and total elapsed time +for one artifact acquisition. Each attempt independently enforces the expected +artifact-size bound. Definitions may use fewer mirrors or tighter limits but +cannot increase these caps, and an otherwise finite mirror list that exceeds +them fails static validation. + +A successful later mirror may complete acquisition after an earlier failure, +including an integrity mismatch. Reploy retains structured diagnostics for +every failed attempt so a compromised or stale mirror is visible rather than +silently hidden, and the lock records the successful locator as provenance. +The successful locator is evidence, not a future pin: later consumers may use +another declared mirror when it yields the same verified bytes. + +If all mirrors fail, acquisition fails with one diagnostic containing the +ordered per-mirror reasons. Reploy never publishes, caches as verified, or +materializes bytes from a failed attempt. Definitions with duplicate mirrors, +different expected content identities per mirror, an over-limit mirror list, +or an unbounded/dynamic mirror source fail static validation. + +This is the preferred initial model for consistent Java versions and +Playwright browser payloads. It avoids tying the user-visible tool version to +whatever version a distribution happens to ship. + +### Native System Packages + +Use the selected OS provider for system libraries and distribution-coupled +tools. Playwright's browser libraries belong here. Tools such as `debuild` or +`rpmbuild` may also be naturally native because distribution integration is +their purpose. + +Literal package roots stay target-specific. Reploy does not translate an APT +name into an RPM, apk, or another APT name merely because the packages provide +similar capabilities. + +### Ecosystem Bindings + +Bindings contribute strict ecosystem requirements and, when needed, exact +artifacts. They resolve through the owning application provider so the binding +and the application's other dependencies form one dependency graph. + +### Mixed Definitions + +A release may deliberately mix these sources. Playwright combines a Python +binding, pinned browser artifacts, and native OS libraries. Java may combine a +pinned runtime archive with a small native package set required by that +runtime. + +The chosen source for each component is part of the selected closure. Reploy +never silently falls back from an upstream artifact to a system package, or +from one target's package set to another. Changing strategy requires a new +definition revision and explicit target data. + +Definitions select reviewed data-driven primitives only. They cannot run +`curl | sh`, `playwright install`, `playwright install-deps`, or arbitrary +package-manager commands. + +### Safe Materialization + +Archive handling belongs to a named Reploy-owned primitive, not to executable +definition logic. Every primitive enforces path normalization and destination +containment, rejects duplicate normalized paths and unsafe special entries, +applies core entry-count and unpacked-size limits, and installs into a new +destination atomically. It never restores archive-supplied user or group +ownership, ACLs, extended attributes, file capabilities, platform security +descriptors, or other privileged metadata. Reploy applies core-defined ownership +and mode normalization, removes group/world write and privileged bits, and +preserves ordinary read or declared executable access only as required by the +payload contract. Symbolic or hard links are rejected unless the primitive +explicitly supports them and proves that both the link and target remain within +the owned archive tree. Device nodes, sockets, FIFOs, absolute paths, escaping +paths, and encrypted entries are never allowed. + +Definition-provided inventory values may tighten limits or describe the vetted +archive, but cannot raise core safety caps or disable checks. Selected payload +destinations are collision-checked before extraction, and materialization runs +without network access. A failed verification or extraction leaves no accepted +partial installation. + +## Resolution and Materialization + +Reploy resolves all pending tool requirements in this order: + +1. Load each tool record's immutable version scheme and normalize every tool + name, version constraint, optional exact definition revision, context, + binding request, selection set, and typed parameter value without choosing a + release. Retain each requirement's canonical resolution scope: the owning + application provider identity for a runtime requirement or the isolated + source-builder identity for a recipe requirement. Requirements for one + `(scope, qualified tool)` merge under the public constraint rules; identical + normalized requirements in that scope deduplicate, while an incompatible + same-scope merge is an error. The same qualified tool in different scopes + remains separate. When an opaque requirement omits its version, normalize it + to exact equality with the tool record's `default_version`. +2. Observe the base image's OCI platform, `/etc/os-release`, package manager, + and manager-native architecture. +3. For each normalized requirement, enumerate authorized release revisions + satisfying its version constraint and, when supplied, its exact + definition-revision pin. Require the running Reploy version and primitive set + to satisfy each candidate release contract; require exactly one target leaf + matching the observed base; apply binding inference and validate the context, + selection set, and parameter values against that target; then traverse the + selected references and construct the candidate contribution union. A + client, target, binding, selection, parameter, or intrinsic contribution + conflict removes that candidate before joint solving. +4. Resolve the remaining scoped candidates as one constraint problem against + the active provider graph for each scope and every provider or destination + domain shared by those scopes. Requirements are ordered by canonical scope + identity, qualified tool name, and then canonical normalized-request bytes. + Ordered-scheme candidates are tried by descending scheme-native version and + then descending definition revision; an opaque request has one exact version + and tries revisions newest first unless pinned. Bounded deterministic + backtracking selects the lexicographically first complete assignment whose + contributions are conflict-free within each scope and across genuinely + shared package-manager, filesystem, environment, export, and capability + domains. Thus request input order cannot change the result, isolated provider + environments do not constrain one another, and a candidate is eligible only + when it participates in a complete assignment. A non-raiseable core cap + bounds visited assignment states; exceeding it fails closed with a diagnostic + rather than accepting a partial or order-dependent result. +5. Finalize every chosen target, inferred binding, selection set, normalized + parameter map, selected closure, and the already-validated combined + contribution union. No complete assignment is an error that reports the + incompatible requirements. Multiple matching target leaves within one + candidate are invalid definition data, not fallback choices. Recheck that + the provider inputs used for joint solving have not changed before + acquisition. +6. Reject conflicting semantic keys and overlapping owned paths within the + chosen union before acquisition. +7. Use the release manifest's source mapping and bounded automatic mirror + failover to acquire and verify all provider data and upstream artifacts while + networking is permitted. Retrieval sources are acquisition provenance, not + members of the selected closure. +8. Materialize and run every declared probe with networking disabled. +9. Record release provenance and selected-closure identity in the lock and + provider bundle. + +Image tags are not target evidence. Target selection uses the validated base +profile observed by Reploy. + +## Identity and Digests + +Every record has a canonical content digest. Two related identities serve +different purposes: + +- **Release provenance identity** records the tool name, scheme-native tool + version, definition revision, and release-manifest digest. Adding a target + produces a new immutable revision and therefore new provenance. +- **Selected-closure identity** hashes the tool name and exact scheme-native + tool version, the canonical resolved release-contract and target projections, + the normalized parameter values, the exact selected binding, payload, and + package-set records, and selected export, probe, and selection values used by + the request. It excludes unselected availability, validation-fixture and + validation-profile references, the release manifest, definition revision, + artifact source records, and retrieval URLs. The full contract and target + records remain covered transitively by release provenance. + +The selected-closure identity input is an object with exactly five members: +`tool`, `version`, `contract`, `target`, and `records`. `tool` and `version` are +canonical strings. `contract` has exactly `context`, `binding`, `selections`, +`parameters`, `runtime`, `exports`, and `probes`. `binding` and `runtime` are +null when absent; `selections` is the sorted normalized selection set; +`parameters` is an object keyed by canonical parameter name; and the remaining +values are the resolved runtime, export, and probe projections with unselected +availability removed. A runtime object has exactly `install_root` and +`environment`; each environment entry has exactly `name` and `value`. An export +has exactly `name` and `path`. A probe has exactly `path`, `args`, and `network`, +where `args` retains argument order and `network` is `none` in schema v1. + +`target` has exactly `identity`, `package_sets`, `binding`, `payloads`, +`selections`, `exports`, and `probes`. +`identity` has exactly `platform`, `os_release_id`, `version_id`, +`oci_architecture`, `native_architecture`, and `package_manager`; target +`binding` is null when absent and otherwise has exactly `name`, `contract`, +`artifacts`, `package_sets`, `exports`, and `probes`. Every target selection has +exactly `name`, `payloads`, `package_sets`, `exports`, and `probes`. The target's +top-level contribution arrays contain only unconditional contributions; its +binding and selection objects contain only the chosen contributions. `records` +is the sorted unique array of `{id, digest}` references for every selected +binding contract and artifact, payload, and native package set. +Artifact-source, validation-fixture, and validation-profile references never +appear in this input. + +Every member is present. Semantically unordered string arrays are sorted by +UTF-8 byte order; record references are sorted by `id` and then `digest`; +named contribution, export, and environment arrays are sorted by canonical +name; and nested reference arrays use the same reference ordering. Probe arrays +are sorted lexicographically by each complete probe object's +`canonical-json-v1` bytes; duplicate byte-identical probes deduplicate before +sorting. Ordered values such as probe arguments retain declared order. The input +uses the schema-normalized scalar rules above and is encoded with +`canonical-json-v1`. Its digest is exactly: + +`"sha256:" + lowercase_hex(SHA-256(UTF-8("reploy:portable-tool-selected-closure:portable-tool-selected-closure-v1") || 0x00 || canonical-json-v1(input)))` + +Publishers and clients therefore compute this identity as +`canonical.Sum("portable-tool-selected-closure", +"portable-tool-selected-closure-v1", input)`; no release revision, source, or +validation field may be added to that versioned input schema. + +Locks retain both. Diagnostics can therefore identify the definition release +that authorized a build without making unrelated records part of that build's +materialization identity. + +Provider nodes and materialization caches include the selected-closure identity +alongside their ordinary provider inputs. Adding an ARM64 target, a Node +binding, or a WebKit selection must not by itself invalidate an existing AMD64 +Python/Chromium materialization. Reuse across definition revisions is allowed +only when the selected closure is byte-for-byte identical; the lock still +records the newly selected release provenance. + +Changing only an artifact mirror or source-provenance reference changes release +provenance but preserves selected-closure identity. Changing the expected +artifact size, SHA-256 digest, extraction contract, or destination changes the +selected closure. + +This replaces the current aggregate definition digest, where every known target +contributes to one digest and an unrelated target addition invalidates existing +build identity. + +## Tool-Specific Decisions + +### Java + +- Java has an explicit upstream version independent of the OS release and + definition revision. +- A versioned `tool:java` must not use `default-jre-headless` as its stable + implementation. That package currently maps the same request to different + Java releases on different distributions. +- The preferred portable strategy is a pinned upstream runtime or JDK artifact + per supported architecture, with target-specific native dependencies where + necessary. +- A distribution-native Java variant may be added later only as an explicit + strategy with truthful version semantics. It is not an automatic fallback. +- The initial Java context remains build-only and preserves source-builder + ownership. Runtime Java is a separate support decision. +- Build-only recipe requirements use the same compact tool grammar and + version/revision semantics as runtime requirements. For example, + `tool:java==21` requests Java 21 while `tool:java==21~2` pins definition + revision 2 as well. An omitted version retains the shared newest-eligible + resolution rule; the resolved request and lock always contain the exact + upstream version, definition revision, manifest digest, and selected-closure + digest. +- The initial Java distribution is Eclipse Temurin JDK 21. The public request + version is `21`; definition revision 1 pins payload component version + `21.0.12+8`. The JDK is build-only and exports both `java` and `javac`; + runtime Java remains a later support decision. +- The initial Java target matrix is Debian 12, Debian 13, Ubuntu 25.10, and + Ubuntu 26.04 on AMD64. ARM64 is added only after an independently validated + target closure exists. + +### Playwright + +- The Playwright upstream release, ecosystem binding, and browser selections + are distinct dimensions. +- A binding contract may have architecture-specific artifacts. The current + Python wheel must not imply support for architectures for which no validated + wheel is defined. +- Each browser selection owns its complete coupled payload set. For Chromium, + that currently includes Chromium, Chromium Headless Shell, and FFmpeg. +- Native browser libraries remain exact target package roots. +- The initial behavior remains Playwright `1.61.0`, Python binding, and explicit + Chromium selection. Node and additional browsers are later additions, not + inferred capabilities. +- ARM64 is advertised only if every binding artifact, browser payload, native + package, extraction rule, and launch test succeeds for the exact target. + +## Validation and Test Contract + +### Static Definition Validation + +Before definitions can be embedded, validation must reject: + +- invalid UTF-8, duplicate JSON member names, non-canonicalizable values, or a + record graph that exceeds a core structural limit; +- unknown schemas, fields, record kinds, package managers, or primitives; +- a missing or unknown tool version scheme, or a release version or alias that + is invalid under that scheme; +- a version or alias token mapped to more than one canonical tool-version + coordinate anywhere in one tool index; +- a non-canonical, ambiguous, or non-reversible encoded tool-version segment; +- a missing, malformed, unadvertised, or ineligible opaque `default_version`, or + any `default_version` on an ordered scheme; +- a missing or invalid supported-Reploy requirement; +- duplicate `(id, digest)` definitions, missing references, cycles, digest + mismatches, or two digests for one record ID in a resolved release graph; +- references outside the selected release namespace, except same-tool tool + record references to release manifests beneath that tool ID; +- target tuples that are ambiguous or internally inconsistent; +- advertised bindings or selections with incomplete referenced closures, a + target binding without exactly one valid contribution mapping, or a target + selection without exactly one valid contribution mapping; +- invalid selection cardinality, defaults, compatibility groups, or a selection + option not covered by a maximal group; +- a parameter domain that is not finite and enumerable or whose required + integration-coverage product exceeds the core cap; +- malformed integration fixtures or validation profiles, validation references + outside their release namespace, or fixtures whose target tuple disagrees + with the referencing target; +- artifacts without exact size and SHA-256 metadata; +- an externally acquired artifact without exactly one source mapping, or a + mapping whose key and expected artifact SHA-256 fields disagree, whose source + or artifact reference has a canonical record-digest mismatch, or whose size + authority or resolver primitive is inconsistent; +- an artifact-source mapping that is not reachable from any advertised target; +- artifact locators or provenance that violate the selected primitive's + retrieval contract; +- source-record locators containing userinfo, query strings, fragments, or + other credential material; +- duplicate, non-canonical, dynamically discovered, or content-inconsistent + mirror entries; +- conflicting exports or incompatible exact provider requirements; +- a claimed architecture without architecture-compatible artifacts. + +Unit tests cover parsing, reference resolution, target selection, closure +construction, canonical identity, error diagnostics, and the rule that adding +an unrelated target, binding option, or selection option does not change an +existing selected-closure digest. They also cover identical contribution +deduplication and every conflicting semantic key or overlapping destination +described above. Candidate-selection tests cover parameter defaults, +normalization, bounds, target compatibility, exact revision pins, +version-scheme ordering, opaque defaults, canonical version-segment encoding, +alias uniqueness, selection compatibility, and filtering a newer Reploy- or +provider-incompatible release in favor of the highest compatible release. +Binding and selection tests cover complete target mappings, coupled +contributions, and exclusion of unselected artifacts, payloads, and native +roots. Validation-record tests cover strict fixture/profile parsing, namespace +and target agreement, missing or wrong-kind references, and complete bounded +parameter-domain coverage. +Identity tests prove that a source-locator-only change +or validation-only reference change preserves selected-closure identity while +a resolved contract or target behavior, artifact size, digest, or +materialization change does not. Resolver-primitive +tests cover verified-cache hits without networking, deterministic mirror order, +fallback after each eligible failure class, size and digest mismatch cleanup, +network-versus-cache provenance, successful-locator provenance, aggregate +exhaustion diagnostics, mirror and aggregate resource caps, bounded redirects, +mixed public/non-public DNS answers, DNS rebinding resistance, redirect-hop +resolution and pinning, redirect and proxy credential redaction, rejected +non-public destinations, archive path and entry-type attacks, archive ownership, +ACL, extended-attribute, capability, and mode normalization, extraction limits, +and atomic cleanup after failure. Parser tests cover duplicate member names, +canonical-equivalent encodings, and every structural limit. Static mapping tests +cover missing, duplicate, orphaned, and identity-inconsistent artifact-source +mappings. Probe-runner tests prove that declared probes cannot enable networking +or inherit a network-enabled execution policy. + +### Reploy Integration Validation + +Every advertised target tuple requires an integration fixture that uses Reploy +itself against a representative base image. The fixture must exercise the same +definition resolution, provider merge, acquisition, offline materialization, +and final-image behavior used by a real blueprint. + +The integration plan is derived from release manifests and target leaves, not +maintained as an independent handwritten target list. CI fails when any target, +binding, valid normalized selection set, or normalized parameter value lacks a +runnable case. Valid selection sets are enumerated from `minimum`, `maximum`, +and `compatibility_groups`; contribution collisions cannot stand in for this +declared compatibility contract. + +Schema v1 parameter domains must be finite and enumerable under a core +publication cap. Validation exercises every boolean or enumerated value and +every value in an integer range, across every supported target, binding, and +valid selection-set combination. Publication rejects a definition whose +required Cartesian coverage exceeds the cap; a future explicit equivalence- +class contract may relax this without weakening existing support evidence. +Hand-authored evidence metadata cannot satisfy this coverage gate. + +Java validation checks the executable and confirms the requested Java version. +Playwright validation imports the selected binding, launches each selected +browser, loads a local page, and exits cleanly with networking disabled during +materialization and probe execution. Negative fixtures verify that unsupported +OS versions, architectures, bindings, selections, and parameter values fail +before downloads begin. + +Architecture support requires execution on that architecture or an explicitly +approved equivalent CI environment. Schema coverage or successful AMD64 tests +cannot establish ARM64 support. + +Successful cases produce evidence bound to the release provenance and selected +closure, exact observed target tuple, base-image platform and immutable digest, +binding, selections, normalized parameters, and validator version. The +documented support matrix is generated only by joining manifest entries with +current successful external +evidence. Validation results are attestations over immutable definition +digests; they are not embedded back into the records they validate. A target +file's presence, an AMD64 result for an ARM64 leaf, or a manually asserted +result is not sufficient. + +## Migration from the Flat WIP Definitions + +1. Replace the flat loader with strict record parsing and explicit reference + resolution. Do not retain a compatibility path for the unreleased format. +2. Split the current shared Java and Playwright metadata into tool, release + manifest, and target-independent release-contract records. +3. Move Playwright's Python binding contract and architecture-specific wheel + into binding records. +4. Move browser content and materialization contracts into selection payload + records, their URLs and provenance into manifest-owned source records, and + native libraries into manager-typed package sets. +5. Convert each current OS/architecture file into a small exact target leaf. +6. Extend local-source recipe parsing to the shared compact requirement grammar + and carry exact Java upstream version, definition revision, manifest digest, + and selected-closure digest through resolved requests, provider requests, + build evidence, and locks. Update ADR 0001 and its examples with that public + behavior. +7. Replace Java's distribution-default package with explicitly versioned + payloads after choosing the initial Java distribution and versions. +8. Preserve the current verified Debian and Ubuntu behavior while rebuilding + its unit and manifest-derived Reploy integration coverage around the selected + closure. +9. Add ARM64 leaves only after the full artifact and integration contract is + satisfied for each exact target. +10. Remove the old flat definitions, aggregate digest behavior, and tests in the + same change; the two formats must not coexist as public contracts. + +## Initial Implementation Decisions + +All schema fields use lower `snake_case`. Every reference is an object with +exactly `id` and `digest` fields. Canonical record IDs are slash-delimited +semantic names rooted at `tool:`. A release namespace is +`tool:/releases/`; its manifest is +`tool:/releases//revisions//manifest`. +Contracts, targets, bindings, payloads, package sets, integration fixtures, and +validation profiles have semantic IDs beneath the release namespace without a +revision segment. Validation record IDs use `validation/fixtures/` and +`validation/profiles/` beneath that namespace. Their lookup and +persistence identity is the `(id, digest)` pair: an unchanged record reuses the +same pair across manifest revisions, while a corrected record keeps its +semantic ID, receives a new digest, and coexists with the historical pair. +Physical storage may place a changed record blob beneath its owning revision; +the path remains organizational and references never resolve by path. +Manifest-owned source-record IDs live beneath the revision namespace. IDs use +ASCII letters, digits, `.`, `+`, `-`, `_`, `%`, `:`, and `/`, with no empty, +`.` or `..` path segments. + +The encoded tool-version segment is derived reversibly from the version +scheme's canonical UTF-8 representation. ASCII letters, digits, `.`, `+`, `-`, +and `_` remain literal; every other byte, including `%`, is encoded as `%HH` +with uppercase hexadecimal digits. If the result would be `.` or `..`, every +dot is encoded. Decoding rejects lowercase hex, escapes for bytes that should +have remained literal except for the required `.` or `..` escape, invalid +UTF-8, or a value that is not canonical under the tool's scheme. The same +encoded segment is used as the version directory +name, so common versions such as `21` and `1.61.0` remain readable while PEP +440 epochs and arbitrary opaque versions retain one canonical path and ID. + +Definition JSON uses the record schemas named in this document with a +`portable-tool-` prefix and `-v1` suffix. Integer quantities are canonical +decimal strings. Parsing is strict: duplicate members, unknown fields, invalid +UTF-8, noncanonical IDs or decimal strings, and values outside core structural +limits are errors before references are resolved. + +Each release manifest records the exact scheme-native tool version and an +explicit, sorted list of accepted public version aliases. Resolution matches +only that exact version or one of those aliases; generic catalog code does not +infer tool-specific major-version semantics. Exact component or build versions +that are more specific than the public tool coordinate belong to payload or +binding-artifact records. + +Across the canonical tool-version coordinates indexed by one tool record, exact +versions and aliases form one collision-free lookup map. Each normalized input +token resolves to exactly one coordinate, after which ordinary revision +selection chooses among that coordinate's immutable release manifests. Multiple +definition revisions may therefore repeat the coordinate's exact version and +aliases. An alias may also repeat across those revisions when it maps to that +same coordinate, but it cannot equal another coordinate's exact version or an +alias mapped to another coordinate. An alias equal to its own coordinate's exact +version is redundant and invalid. Publication validates this invariant over the +complete tool index before any release is advertised. + +The release contract represents the singular binding request as sorted +`options`, a `required` boolean, and an optional `default`. It represents the +selection set as sorted `options`, canonical-decimal `minimum` and `maximum`, +sorted `defaults`, and canonical `compatibility_groups`. Each compatibility +group is a sorted maximal set of options that may coexist; the group list is +sorted, has no duplicates or subset groups, and covers every option. A request +is valid only when its normalized set satisfies the cardinality bounds and is a +subset of at least one group. Defaults must satisfy the same rule. Typed +parameter schemas are sorted by canonical parameter name and +carry the public type, required/default behavior, and type-specific enum or +range constraints. Resolution normalizes defaults and explicit values to their +canonical typed representation before candidate filtering and identity. A +required binding with a default is present in the resolved request but may be +omitted by the user; resolution inserts the default before identity is +computed. Playwright initially permits only the `python` binding, marks it +required, and sets it as the default, so an omitted binding is inferred while +an explicit `python` remains valid. It permits only the `chromium` selection, +sets both selection cardinality bounds to one, declares `chromium` as its sole +compatibility group, and supplies no selection default. Java permits neither a +binding nor a selection. The initial Java and Playwright contracts declare no +parameters. Binding requirements remain owned by +binding-contract records, while the release contract owns final-image placement +and environment values directly. + +The initial Java implementation is Eclipse Temurin JDK 21 for build use. The +public request `tool:java==21` resolves integer tool version `21`, definition +revision 1, whose JDK payload records exact Temurin component version +`21.0.12+8`. The first target matrix is Debian 12, Debian 13, Ubuntu 25.10, and +Ubuntu 26.04 on AMD64. The JDK payload is pinned by exact size and SHA-256 and +exports `java` and `javac`; no distribution-default Java package is a fallback. + +The initial Playwright implementation remains upstream `1.61.0`, Python +binding, and explicit Chromium selection on the existing validated AMD64 +targets. ARM64 is not advertised for Java or Playwright until every artifact, +native dependency, materialization rule, and target probe in its selected +closure succeeds. + +Validation evidence is external to definition identity. Schema v1 records the +tool, upstream version, definition revision, manifest digest, selected-closure +digest, exact target tuple, immutable base-image digest, binding and selections, +fixture ID, validator version, result, and observed probe digests. Only a +passing record whose immutable fields match the selected manifest and closure +can contribute to the generated support matrix. + +These decisions settle the initial wire representation and support claims. +Additional Java versions, runtime Java, Playwright bindings or browsers, ARM64, +and repository publication remain explicit later extensions; none is inferred +from the initial embedded records. diff --git a/docs/REPOSITORY_DESIGN.md b/docs/REPOSITORY_DESIGN.md index 4edcf7f0..1a14d85e 100644 --- a/docs/REPOSITORY_DESIGN.md +++ b/docs/REPOSITORY_DESIGN.md @@ -1,6 +1,6 @@ --- status: Draft -updated: 2026-08-01 +updated: 2026-08-16 summary: Federated, TUF-authenticated Reploy repositories for published blueprints and portable tool definitions. --- @@ -920,6 +920,12 @@ the publisher tool, not in consumer warnings for direct BURLs. ## Portable Tool Contract +The concrete record decomposition, exact-target model, acquisition composition, +and selected-closure identity are defined in the +[Portable Tool Definition Design](PORTABLE_TOOL_DEFINITION_DESIGN.md). This +section owns the public repository-facing contract; the focused design owns how +one tool release is represented and resolved. + ### Definition Shape and Safety Boundary A tool definition represents: @@ -1064,9 +1070,14 @@ The existing local project recipe form remains build-only: ```yaml requires: - - tool:java + - tool:java==21 ``` +Build-only recipe requirements use the same compact version and revision +grammar as runtime tools. Omitting the version selects the newest eligible +release under the shared rule; resolution and the build lock still retain the +exact upstream version and definition revision. + ### Playwright `tool:playwright` is the first capability expected to exercise the complete @@ -1075,7 +1086,7 @@ compatible browser payloads; contribute reviewed target-specific OS roots; acquire browser payloads through a named Reploy-owned primitive without exposing project source, host credentials, or arbitrary paths; materialize offline; and lock platform, browser revision, provenance, artifact digest, and -definition digest. +release and selected-closure digests. Browser selection is explicit because payloads are large and materially change requirements. Omitting it lists supported browsers. Multiple browsers produce @@ -1087,6 +1098,21 @@ The tool exports the supported Playwright CLI into the application executable namespace. Browser payload executables remain internal unless the definition deliberately exposes a stable named interface. +An interim built-in bridge will implement the approved initial Python/Chromium +profile before the repository protocol exists. Its artifact acquisition will +remain a closed, reviewed primitive. This bridge will not be an official +repository, accept external definitions, or satisfy the publication, trust, +lifecycle, or multi-browser portions of this design. A separate implementation +WIP uses flat, complete-per-target JSON files as a checkpoint; those files will +be migrated to the explicit record composition in the Portable Tool Definition +Design before release. + +Target selection continues to use validated base-profile evidence inside +provider resolution; image-reference parsing and cross-generation package +unions are not target-selection mechanisms. The selected package roots and +definition closure remain part of locked provider identity so cache validation +can re-establish the same target choice. + ### Unsupported Dynamic Installers The protocol does not translate arbitrary install scripts or post-install @@ -1114,8 +1140,8 @@ is not reserved in the initial public schema. and implement disposable global object caching and validated offline import. 5. Publish the independently updated official repository and generated documentation. -6. Move Java's existing portable-tool mapping from Go switches into an official - definition without changing its current project-owned build-only behavior. +6. Migrate Java's interim embedded definition into an official definition + without changing its current project-owned build-only behavior. 7. Add the reviewed Playwright resolver primitive, definition, OS matrix, documentation, and integration evidence.